41
41
# 11-byte signature (constructed in this way to detect possible mangled bytes), flags, header extension
42
42
# https://www.postgresql.org/docs/9.0/sql-copy.html#AEN59377
43
43
PG_COPYFROM_INIT = struct .pack ('!11sII' , b'PGCOPY\n \377 \r \n \0 ' , 0 , 0 )
44
- # 4-byte INETv4 prefix: family, netmask, is_cidr, n bytes
44
+ # 4-byte INETv4/v6 prefix: family, netmask, is_cidr, n bytes
45
45
# https://doxygen.postgresql.org/network_8c_source.html#l00193
46
- IPV4_PREFIX = struct .pack ('!BBBB' , socket .AF_INET , 32 , 0 , 4 )
46
+ IPV4_ADDRESS_PREFIX = struct .pack ('!BBBB' , socket .AF_INET , 32 , 0 , 4 )
47
+ # Gotcha: IPv6 address family in Postgres is *not* socket.AF_INET6 (10),
48
+ # instead it is defined as socket.AF_INET + 1 (2 + 1 == 3)
49
+ # https://doxygen.postgresql.org/utils_2inet_8h_source.html#l00040
50
+ IPV6_ADDRESS_PREFIX = struct .pack ('!BBBB' , socket .AF_INET + 1 , 128 , 0 , 16 )
47
51
48
52
49
53
def _pgwriter_init ():
@@ -52,22 +56,30 @@ def _pgwriter_init():
52
56
return pg_writer
53
57
54
58
55
- def _pgwriter_write (pgwriter , ts , client_ip , IN_BYTES , PROTOCOL , DIRECTION , L4_DST_PORT , L4_SRC_PORT , INPUT_SNMP , OUTPUT_SNMP , IPV4_DST_ADDR , IPV4_SRC_ADDR ):
56
- buf = struct .pack ('!HiIi4s4siQiHiHiIiIiHiHi4s4si4s4s ' ,
59
+ def _pgwriter_write (pgwriter , ts , client_ip , IN_BYTES , PROTOCOL , DIRECTION , L4_DST_PORT , L4_SRC_PORT , INPUT_SNMP , OUTPUT_SNMP , address_family , IPVx_DST_ADDR , IPVx_SRC_ADDR ):
60
+ buf = struct .pack ('!HiIi4s4siQiHiHiIiIiHiH ' ,
57
61
11 , # number of columns
58
62
4 , int (ts ), # integer - beware of Y2038 problem! :)
59
- 8 , IPV4_PREFIX , socket .inet_aton (client_ip ), # 4 bytes prefix + 4 bytes IP
63
+ 8 , IPV4_ADDRESS_PREFIX , socket .inet_aton (client_ip ), # 4 bytes prefix + 4 bytes IP
60
64
8 , IN_BYTES , # bigint
61
65
2 , PROTOCOL ,
62
66
2 , DIRECTION ,
63
67
4 , L4_DST_PORT ,
64
68
4 , L4_SRC_PORT ,
65
69
2 , INPUT_SNMP ,
66
70
2 , OUTPUT_SNMP ,
67
- 8 , IPV4_PREFIX , IPV4_DST_ADDR ,
68
- 8 , IPV4_PREFIX , IPV4_SRC_ADDR ,
69
71
)
70
- pgwriter .write (buf )
72
+ if address_family != socket .AF_INET6 :
73
+ buf2 = struct .pack ('!i4s4si4s4s' ,
74
+ 8 , IPV4_ADDRESS_PREFIX , IPVx_DST_ADDR ,
75
+ 8 , IPV4_ADDRESS_PREFIX , IPVx_SRC_ADDR ,
76
+ )
77
+ else :
78
+ buf2 = struct .pack ('!i4s16si4s16s' ,
79
+ 4 + 16 , IPV6_ADDRESS_PREFIX , IPVx_DST_ADDR ,
80
+ 4 + 16 , IPV6_ADDRESS_PREFIX , IPVx_SRC_ADDR ,
81
+ )
82
+ pgwriter .write (buf + buf2 )
71
83
72
84
73
85
def _pgwriter_finish (pgwriter ):
@@ -138,9 +150,8 @@ def process_named_pipe(named_pipe_filename):
138
150
except UnknownNetFlowVersion :
139
151
log .warning ("Unknown NetFlow version" )
140
152
continue
141
- except TemplateNotRecognized :
142
- log .warning ("Failed to decode a v9 ExportPacket, template not "
143
- "recognized (if this happens at the start, it's ok)" )
153
+ except TemplateNotRecognized as ex :
154
+ log .warning (f"Failed to decode a v9 ExportPacket, template not recognized (if this happens at the start, it's ok). Template id: { ex .template_id } " )
144
155
continue
145
156
146
157
except Exception as ex :
@@ -194,39 +205,36 @@ def write_buffer(buffer, partition_no):
194
205
195
206
196
207
log .debug (f"Writing { len (buffer )} records to DB, partition { partition_no } " )
197
- ipv6_ignored_records = 0 # we don't support IPv6 yet
198
208
# save each of the flows within the record, but use execute_values() to perform bulk insert:
199
209
def _get_data (buffer ):
200
210
for ts , client_ip , export in buffer :
201
211
netflow_version , flows = export .header .version , export .flows
202
212
if netflow_version == 9 :
203
213
for f in flows :
204
214
try :
205
- if f .data .get ("IP_PROTOCOL_VERSION" , 4 ) == 6 :
206
- ipv6_ignored_records += 1
207
- continue
215
+ # if f.data.get("IP_PROTOCOL_VERSION", 4) == 6:
216
+ if not f .data .get ("IPV6_DST_ADDR" , None ) is None :
217
+ address_family = socket .AF_INET6
218
+ dst = socket .inet_pton (address_family , f .data ["IPV6_DST_ADDR" ])
219
+ src = socket .inet_pton (address_family , f .data ["IPV6_SRC_ADDR" ])
220
+ else :
221
+ address_family = socket .AF_INET
222
+ dst = socket .inet_aton (f .data ["IPV4_DST_ADDR" ])
223
+ src = socket .inet_aton (f .data ["IPV4_SRC_ADDR" ])
208
224
209
225
yield (
210
226
ts ,
211
227
client_ip ,
212
- # "IN_BYTES":
213
228
f .data ["IN_BYTES" ],
214
- # "PROTOCOL":
215
229
f .data ["PROTOCOL" ],
216
- # "DIRECTION":
217
230
f .data .get ("DIRECTION" , DIRECTION_INGRESS ),
218
- # "L4_DST_PORT":
219
231
f .data ["L4_DST_PORT" ],
220
- # "L4_SRC_PORT":
221
232
f .data ["L4_SRC_PORT" ],
222
- # "INPUT_SNMP":
223
233
f .data ["INPUT_SNMP" ],
224
- # "OUTPUT_SNMP":
225
234
f .data ["OUTPUT_SNMP" ],
226
- # "IPV4_DST_ADDR":
227
- socket .inet_aton (f .data ["IPV4_DST_ADDR" ]),
228
- # "IPV4_SRC_ADDR":
229
- socket .inet_aton (f .data ["IPV4_SRC_ADDR" ]),
235
+ address_family ,
236
+ dst ,
237
+ src ,
230
238
)
231
239
except KeyError :
232
240
log .exception (f"[{ client_ip } ] Error decoding v9 flow. Contents: { repr (f .data )} " )
@@ -250,6 +258,8 @@ def _get_data(buffer):
250
258
f .data ["INPUT" ],
251
259
# "OUTPUT_SNMP":
252
260
f .data ["OUTPUT" ],
261
+ # address_family is always IPv4:
262
+ socket .AF_INET ,
253
263
# netflow v5 IP addresses are decoded to integers, which is less suitable for us - pack
254
264
# them back to bytes:
255
265
# "IPV4_DST_ADDR":
@@ -267,9 +277,6 @@ def _get_data(buffer):
267
277
_pgwriter_write (pgwriter , * data )
268
278
_pgwriter_finish (pgwriter )
269
279
270
- if ipv6_ignored_records > 0 :
271
- log .error (f"We do not support IPv6 (yet), some IPv6 flow records were ignored: { ipv6_ignored_records } " )
272
-
273
280
274
281
if __name__ == "__main__" :
275
282
NAMED_PIPE_FILENAME = os .environ .get ('NAMED_PIPE_FILENAME' , None )
0 commit comments