Skip to content

Commit f9073ed

Browse files
committed
chg: [add d4 python client]
1 parent 08b192c commit f9073ed

File tree

9 files changed

+260
-0
lines changed

9 files changed

+260
-0
lines changed

conf.sample/destination

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
127.0.0.1:4443

conf.sample/key

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
private key to change

conf.sample/metaheader.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
{"type": "ja3-jl"}
2+

conf.sample/snaplen

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
4096

conf.sample/source

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
stdin

conf.sample/type

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
8

conf.sample/uuid

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
03c00bcf-fe53-46a1-85bb-ee6084cb5bb2

conf.sample/version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1

d4-pyclient.py

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
#!/usr/bin/env python3
2+
3+
import argparse
4+
import os
5+
import io
6+
import ipaddress
7+
import json
8+
import hmac
9+
import sys
10+
import struct
11+
import time
12+
import uuid # use from
13+
14+
import socket
15+
import ssl
16+
17+
from urllib.parse import urlparse
18+
19+
import datetime
20+
21+
import logging
22+
import logging.handlers
23+
# # TODO: replace print by logger
24+
25+
def generate_uuid(filename):
26+
sensor_uuid = str(uuid.uuid4())
27+
with open(filename, 'w') as f:
28+
f.write(sensor_uuid)
29+
return sensor_uuid
30+
31+
def create_hmac(hmac_key, data):
32+
data_hmac = hmac.new(hmac_key, msg=data, digestmod='sha256')
33+
return data_hmac.digest()
34+
35+
def create_d4_header(version, type, sensor_uuid, hmac_key, data):
36+
# Get header fieldscat
37+
h_version_type = struct.pack('BB', version, type)
38+
h_timestamp = struct.pack('Q', int(time.time()))
39+
h_size = struct.pack('I', len(data))
40+
h_uuid = bytearray.fromhex(sensor_uuid)
41+
42+
# Get Hmac field
43+
# The HMAC is computed on the header with a HMAC value set to 0
44+
d4_header = h_version_type + h_uuid + h_timestamp + bytearray(32) + h_size
45+
d4_data = d4_header + data
46+
h_hmac = create_hmac(hmac_key, d4_data)
47+
48+
d4_header = h_version_type + h_uuid + h_timestamp + h_hmac + h_size
49+
return d4_header
50+
51+
def prepare_data(version, type, sensor_uuid, hmac_key, snaplen, buffer, destination):
52+
# Pack data
53+
while len(buffer) > snaplen:
54+
data_to_pack = buffer[0:snaplen]
55+
buffer = buffer[snaplen:]
56+
# Pack data
57+
pack_d4_data(version, type, sensor_uuid, hmac_key, data_to_pack, destination)
58+
return buffer
59+
60+
61+
def pack_d4_data(version, type, sensor_uuid, hmac_key, data, destination):
62+
# Pack data
63+
d4_header = create_d4_header(version, type, sensor_uuid, hmac_key, data)
64+
d4_data = d4_header + data
65+
66+
print(data)
67+
68+
# Send data
69+
send_d4_data(destination, d4_data)
70+
71+
def send_d4_data(destination, d4_data):
72+
if destination == 'stdout':
73+
sys.stdout.buffer.write(d4_data)
74+
sys.stdout.flush()
75+
else:
76+
destination.send(d4_data)
77+
78+
def get_config_from_file(filename, r_type='str'):
79+
if not os.path.isfile(filename):
80+
print('error config file not found')
81+
82+
with open(filename, 'r') as f:
83+
config = f.read()
84+
while config[-1] == '\n':
85+
config = config[:-1]
86+
87+
if r_type == 'int':
88+
try:
89+
config = int(config)
90+
except:
91+
print('error config file, invalid type')
92+
else:
93+
config = config.encode()
94+
return config
95+
96+
# # TODO: check if valid uuid
97+
def get_sensor_uuid(config_dir):
98+
filename = os.path.join(config_dir, 'uuid')
99+
if not os.path.isfile(filename):
100+
sensor_uuid = generate_uuid(filename)
101+
else:
102+
with open(filename, 'r') as f:
103+
sensor_uuid = f.read()
104+
if sensor_uuid[-1] == '\n':
105+
sensor_uuid = sensor_uuid[:-1]
106+
return sensor_uuid.replace('-', '')
107+
108+
def load_config(config_dir):
109+
if not os.path.isdir(config_dir):
110+
print('error config file not found')
111+
112+
# HMAC Key
113+
dict_config = {}
114+
filename = os.path.join(config_dir, 'key')
115+
dict_config['key'] = get_config_from_file(filename, r_type='str')
116+
117+
filename = os.path.join(config_dir, 'type')
118+
dict_config['type'] = get_config_from_file(filename, r_type='int')
119+
if dict_config['type'] < 0 and dict_config['type'] > 255:
120+
print('error, unsuported type')
121+
122+
filename = os.path.join(config_dir, 'version')
123+
dict_config['version'] = get_config_from_file(filename, r_type='int')
124+
if dict_config['version'] < 0:
125+
print('error, unsuported type')
126+
127+
filename = os.path.join(config_dir, 'snaplen')
128+
dict_config['snaplen'] = get_config_from_file(filename, r_type='int')
129+
if dict_config['snaplen'] < 0:
130+
print('error, unsuported type')
131+
132+
# Sensor UUID
133+
dict_config['uuid'] = get_sensor_uuid(config_dir)
134+
return dict_config
135+
136+
def get_destination(config_dir, verify_cert=True):
137+
filename = os.path.join(config_dir, 'destination')
138+
if not os.path.isfile(filename):
139+
print('error destination file not found')
140+
141+
with open(filename, 'r') as f:
142+
destination = f.read().replace('\n', '')
143+
144+
if destination == 'stdout':
145+
return destination
146+
# Get server address
147+
else:
148+
if not ':' in destination:
149+
# port = 80 ?
150+
print('error, destination')
151+
host, port = destination.rsplit(':', 1)
152+
# verify port
153+
try:
154+
port = int(port)
155+
except:
156+
print('error, invalid port')
157+
# verify address
158+
try:
159+
host = str(ipaddress.ip_address(host))
160+
except ValueError:
161+
# get IP host
162+
host = socket.gethostbyname(host)
163+
try:
164+
host = socket.gethostbyname(host)
165+
except:
166+
print('Destination Host: Name or service not known')
167+
print(host)
168+
169+
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
170+
# TCP Keepalive
171+
s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
172+
s.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 1)
173+
s.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 15)
174+
s.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 15)
175+
176+
## TODO: add flag
177+
verify_cert = False
178+
179+
# SSL
180+
if verify_cert:
181+
cert_reqs_option = ssl.CERT_REQUIRED
182+
else:
183+
cert_reqs_option = ssl.CERT_NONE
184+
client_socket = ssl.wrap_socket(s, cert_reqs=cert_reqs_option, ca_certs=None, ssl_version=ssl.PROTOCOL_TLS)
185+
186+
# TCP connect
187+
try:
188+
client_socket.connect((host, port))
189+
except ConnectionRefusedError:
190+
print('error, Connection to {}:{} refused'.format(host, port))
191+
sys.exit(1)
192+
except ssl.SSLError as e:
193+
print(e)
194+
sys.exit(1)
195+
return client_socket
196+
197+
198+
def get_metaheader_json(config_dir):
199+
filename = os.path.join(config_dir, 'metaheader.json')
200+
if not os.path.isfile(filename):
201+
print('error metaheader file not found')
202+
203+
with open(filename, 'rb') as f:
204+
metaheader = f.read()
205+
try:
206+
metaheader = json.loads(metaheader)
207+
except:
208+
print('error, invalid json file')
209+
return json.dumps(metaheader).encode()
210+
211+
if __name__ == "__main__":
212+
parser = argparse.ArgumentParser()
213+
parser.add_argument('-c', '--config' ,help='config_directory' ,type=str, dest='config', required=True)
214+
args = parser.parse_args()
215+
config_dir = args.config
216+
217+
config = load_config(config_dir)
218+
destination = get_destination(config_dir)
219+
220+
buffer = b''
221+
# config['type'] = 2
222+
config['snaplen'] = 64
223+
224+
# handle extended type
225+
if config['type'] == 2 or config['type'] == 254:
226+
# send meadata json
227+
buffer = get_metaheader_json(config_dir)
228+
buffer = prepare_data(config['version'], 2, config['uuid'], config['key'], config['snaplen'], buffer, destination)
229+
pack_d4_data(config['version'], 2, config['uuid'], config['key'], buffer, destination)
230+
# change type
231+
config['type'] = 254
232+
buffer = b''
233+
234+
try:
235+
for data in io.open(sys.stdin.fileno(), mode='rb', buffering=0):
236+
237+
print(data)
238+
239+
if data:
240+
buffer = buffer + data
241+
buffer = prepare_data(config['version'], config['type'], config['uuid'], config['key'], config['snaplen'], buffer, destination)
242+
243+
pack_d4_data(config['version'], config['type'], config['uuid'], config['key'], buffer, destination)
244+
destination.close()
245+
246+
# Send buffer content
247+
except KeyboardInterrupt:
248+
# Pack data
249+
buffer = prepare_data(config['version'], config['type'], config['uuid'], config['key'], config['snaplen'], buffer, destination)
250+
pack_d4_data(config['version'], config['type'], config['uuid'], config['key'], buffer, destination)
251+
destination.close()

0 commit comments

Comments
 (0)