3 # Wireshark - Network traffic analyzer
4 # By Gerald Combs <gerald@wireshark.org>
5 # Copyright 1998 Gerald Combs
7 # SPDX-License-Identifier: GPL-2.0-or-later
10 Generate Sysdig event dissector sections from the sysdig sources.
12 Reads driver/event_table.c and driver/ppm_events_public.h and generates
13 corresponding dissection code in packet-sysdig-event.c. Updates are
14 performed in-place in the dissector code.
16 Requires an Internet connection. Assets are loaded from GitHub over HTTPS, from falcosecurity/libs master.
23 import urllib
.request
, urllib
.error
, urllib
.parse
26 sysdig_repo_pfx
= 'https://raw.githubusercontent.com/falcosecurity/libs/master/'
28 def exit_msg(msg
=None, status
=1):
30 sys
.stderr
.write(msg
+ '\n\n')
31 sys
.stderr
.write(__doc__
+ '\n')
34 def get_url_lines(url
):
36 Returns the URL body as a list of lines.
38 req_headers
= { 'User-Agent': 'Wireshark generate-sysdig-event' }
40 req
= urllib
.request
.Request(url
, headers
=req_headers
)
41 response
= urllib
.request
.urlopen(req
)
42 lines
= response
.read().decode().splitlines()
44 except urllib
.error
.HTTPError
as err
:
45 exit_msg("HTTP error fetching {0}: {1}".format(url
, err
.reason
))
46 except urllib
.error
.URLError
as err
:
47 exit_msg("URL error fetching {0}: {1}".format(url
, err
.reason
))
48 except OSError as err
:
49 exit_msg("OS error fetching {0}: {1}".format(url
, err
.strerror
))
51 exit_msg("Unexpected error:", sys
.exc_info()[0])
56 ppm_ev_pub_lines
= get_url_lines(sysdig_repo_pfx
+ 'driver/ppm_events_public.h')
58 ppme_re
= re
.compile('^\s+PPME_([A-Z0-9_]+_[EX])\s*=\s*([0-9]+)\s*,')
59 ppm_sc_x_re
= re
.compile('^\s+PPM_SC_X\s*\(\s*(\S+)\s*,\s*(\d+)\s*\)')
63 def get_event_defines():
65 for line
in ppm_ev_pub_lines
:
66 m
= ppme_re
.match(line
)
68 event_d
[int(m
.group(2))] = m
.group(1)
71 def get_syscall_code_defines():
73 for line
in ppm_ev_pub_lines
:
74 m
= ppm_sc_x_re
.match(line
)
76 sc_d
[int(m
.group(2))] = m
.group(1)
79 ppm_ev_table_lines
= get_url_lines(sysdig_repo_pfx
+ 'driver/event_table.c')
83 event_info_re
= re
.compile('^\s+\[\s*PPME_.*\]\s*=\s*{\s*"([A-Za-z0-9_]+)"\s*,[^,]+,[^,]+,\s*([0-9]+)\s*[,{}]')
84 event_param_re
= re
.compile('{\s*"([A-Za-z0-9_ ]+)"\s*,\s*PT_([A-Z0-9_]+)\s*,\s*PF_([A-Z0-9_]+)\s*[,}]')
86 def get_event_names():
87 '''Return a contiguous list of event names. Names are lower case.'''
89 for line
in ppm_ev_table_lines
:
90 ei
= event_info_re
.match(line
)
92 event_name_l
.append(ei
.group(1))
105 'FSRELPATH': 'STRING',
110 'SYSCALLID': 'UINT16',
114 force_param_formats
= {
119 def get_event_params():
120 '''Return a list of dictionaries containing event names and parameter info.'''
123 force_string_l
= ['args', 'env']
124 for line
in ppm_ev_table_lines
:
125 ei
= event_info_re
.match(line
)
126 ep
= event_param_re
.findall(line
)
128 event_name
= ei
.group(1)
129 src_param_count
= int(ei
.group(2))
130 if len(ep
) != src_param_count
:
131 err_msg
= '{}: found {} parameters. Expected {}. Params: {}'.format(
132 event_name
, len(ep
), src_param_count
, repr(ep
))
133 if len(ep
) > src_param_count
:
134 logging
.warning(err_msg
)
135 del ep
[src_param_count
:]
137 raise NameError(err_msg
)
139 if p
[0] in force_string_l
:
140 param_type
= 'STRING'
141 elif p
[1] in pt_to_ft
:
142 param_type
= pt_to_ft
[p
[1]]
143 elif p
[0] == 'flags' and p
[1].startswith('INT') and 'HEX' in p
[2]:
144 param_type
= 'U' + p
[1]
149 print(f
"p fallback {p}")
154 if 'INT' in param_type
:
157 param_format
= 'NONE'
158 elif param_type
== 'BYTES':
159 param_format
= 'NONE'
163 for pt_pat
, force_pf
in force_param_formats
.items():
164 if re
.match(pt_pat
, param_type
) and param_format
!= force_pf
:
165 err_msg
= 'Forcing {} {} format to {}. Params: {}'.format(
166 event_name
, param_type
, force_pf
, repr(ep
))
167 logging
.warning(err_msg
)
168 param_format
= force_pf
171 'event_name': event_name
,
172 'event_num': event_num
,
173 # use replace() to account for "plugin ID" param name (ie: param names with space)
174 'param_name': p
[0].replace(" ", "_"),
175 'param_type': param_type
,
176 'param_format': param_format
,
178 event_param_l
.append(param_d
)
183 def param_to_hf_name(param
):
184 return 'hf_param_{}_{}'.format(param
['param_name'], param
['param_type'].lower())
186 def param_to_value_string_name(param
):
187 return '{}_{}_vals'.format(param
['param_name'], param
['param_type'].lower())
189 def get_param_desc(param
):
190 # Try to coerce event names and parameters into human-friendly
192 # XXX This could use some work.
194 # Specific descriptions. Event name + parameter name.
196 'accept.queuepct': 'Accept queue per connection',
197 'execve.args': 'Program arguments',
198 'execve.comm': 'Command',
199 'execve.cwd': 'Current working directory',
201 # General descriptions. Event name only.
203 'ioctl': 'I/O control',
206 event_name
= param
['event_name']
207 param_id
= '{}.{}'.format(event_name
, param
['param_name'])
208 if param_id
in param_descs
:
209 param_desc
= param_descs
[param_id
]
210 elif event_name
in event_descs
:
211 param_desc
= '{}: {}'.format(event_descs
[event_name
], param
['param_name'])
213 param_desc
= param
['param_name']
217 logging
.basicConfig(format
='%(levelname)s: %(message)s')
220 event_d
= get_event_defines()
221 event_nums
= list(event_d
.keys())
224 event_name_l
= get_event_names()
225 event_param_l
= get_event_params()
228 for param
in event_param_l
:
229 hf_name
= param_to_hf_name(param
)
230 hf_d
[hf_name
] = param
232 idx_id_to_name
= { '': 'no' }
233 parameter_index_l
= []
235 for en
in range (0, len(event_nums
)):
238 event_var
= event_d
[en
].lower()
239 for param
in event_param_l
:
240 if param
['event_num'] == en
:
241 hf_name
= param_to_hf_name(param
)
242 param_l
.append(hf_name
)
243 param_id
+= ':' + param
['param_name'] + '_' + param
['param_type']
246 if param_id
not in idx_id_to_name
:
247 idx_id_to_name
[param_id
] = event_var
248 ei_str
= 'static int * const {}_indexes[] = {{ &{}, NULL }};'.format(
253 ei_str
= '#define {}_indexes {}_indexes'.format(event_var
, idx_id_to_name
[param_id
])
255 parameter_index_l
.append(ei_str
)
257 dissector_path
= os
.path
.join(os
.path
.dirname(__file__
),
258 '..', 'epan', 'dissectors', 'packet-sysdig-event.c')
259 dissector_f
= open(dissector_path
, 'r')
260 dissector_lines
= list(dissector_f
)
261 dissector_f
= open(dissector_path
, 'w+')
263 # Strip out old content
265 strip_re_l
.append(re
.compile('^static\s+int\s+hf_param_.*;'))
266 strip_re_l
.append(re
.compile('^#define\s+EVT_STR_[A-Z0-9_]+\s+"[A-Za-z0-9_]+"'))
267 strip_re_l
.append(re
.compile('^#define\s+EVT_[A-Z0-9_]+\s+[0-9]+'))
268 strip_re_l
.append(re
.compile('^\s*{\s*EVT_[A-Z0-9_]+\s*,\s*EVT_STR_[A-Z0-9_]+\s*}'))
269 strip_re_l
.append(re
.compile('^static\s+const\s+int\s+\*\s*[a-z0-9_]+_[ex]_indexes\[\]\s*=\s*\{\s*&hf_param_.*NULL\s*\}\s*;'))
270 strip_re_l
.append(re
.compile('^static\s+int\s*\*\s+const\s+[a-z0-9_]+_[ex]_indexes\[\]\s*=\s*\{\s*&hf_param_.*NULL\s*\}\s*;'))
271 strip_re_l
.append(re
.compile('^\s*#define\s+[a-z0-9_]+_[ex]_indexes\s+[a-z0-9_]+_indexes'))
272 strip_re_l
.append(re
.compile('^\s*\{\s*EVT_[A-Z0-9_]+_[EX]\s*,\s*[a-z0-9_]+_[ex]_indexes\s*}\s*,'))
273 strip_re_l
.append(re
.compile('^\s*\{\s*\d+\s*,\s*"\S+"\s*}\s*,\s*//\s*PPM_SC_\S+'))
274 strip_re_l
.append(re
.compile('^\s*{\s*&hf_param_.*},')) # Must all be on one line
276 for strip_re
in strip_re_l
:
277 dissector_lines
= [line
for line
in dissector_lines
if not strip_re
.search(line
)]
279 # Find our value strings
280 value_string_re
= re
.compile('static\s+const\s+value_string\s+([A-Za-z0-9_]+_vals)')
282 for line
in dissector_lines
:
283 vs
= value_string_re
.match(line
)
285 value_string_l
.append(vs
.group(1))
287 # Add in new content after comments.
289 header_fields_c
= 'Header fields'
290 header_fields_re
= re
.compile('/\*\s+' + header_fields_c
, flags
= re
.IGNORECASE
)
292 for hf_name
in sorted(hf_d
.keys()):
293 header_fields_l
.append('static int {};'.format(hf_name
))
295 event_names_c
= 'Event names'
296 event_names_re
= re
.compile('/\*\s+' + event_names_c
, flags
= re
.IGNORECASE
)
298 event_str_l
= list(set(event_name_l
))
300 for evt_str
in event_str_l
:
301 event_names_l
.append('#define EVT_STR_{0:24s} "{1:s}"'.format(evt_str
.upper(), evt_str
))
303 event_definitions_c
= 'Event definitions'
304 event_definitions_re
= re
.compile('/\*\s+' + event_definitions_c
, flags
= re
.IGNORECASE
)
305 event_definitions_l
= []
306 for evt
in event_nums
:
307 event_definitions_l
.append('#define EVT_{0:24s} {1:3d}'.format(event_d
[evt
], evt
))
309 value_strings_c
= 'Value strings'
310 value_strings_re
= re
.compile('/\*\s+' + value_strings_c
, flags
= re
.IGNORECASE
)
312 for evt
in event_nums
:
313 evt_num
= 'EVT_{},'.format(event_d
[evt
])
314 evt_str
= 'EVT_STR_' + event_name_l
[evt
].upper()
315 value_strings_l
.append(' {{ {0:<32s} {1:s} }},'.format(evt_num
, evt_str
))
317 parameter_index_c
= 'Parameter indexes'
318 parameter_index_re
= re
.compile('/\*\s+' + parameter_index_c
, flags
= re
.IGNORECASE
)
319 # parameter_index_l defined above.
321 event_tree_c
= 'Event tree'
322 event_tree_re
= re
.compile('/\*\s+' + event_tree_c
, flags
= re
.IGNORECASE
)
324 for evt
in event_nums
:
325 evt_num
= 'EVT_{}'.format(event_d
[evt
])
326 evt_idx
= '{}_indexes'.format(event_d
[evt
].lower())
327 event_tree_l
.append(' {{ {}, {} }},'.format(evt_num
, evt_idx
))
330 syscall_code_d
= get_syscall_code_defines()
331 syscall_code_c
= 'Syscall codes'
332 syscall_code_re
= re
.compile('/\*\s+' + syscall_code_c
, flags
= re
.IGNORECASE
)
334 for sc_num
in syscall_code_d
:
335 syscall_code_l
.append(f
' {{ {sc_num:3}, "{syscall_code_d[sc_num].lower()}" }}, // PPM_SC_{syscall_code_d[sc_num]}')
337 header_field_reg_c
= 'Header field registration'
338 header_field_reg_re
= re
.compile('/\*\s+' + header_field_reg_c
, flags
= re
.IGNORECASE
)
339 header_field_reg_l
= []
340 for hf_name
in sorted(hf_d
.keys()):
341 param
= hf_d
[hf_name
]
342 event_name
= param
['event_name']
343 param_desc
= get_param_desc(param
)
344 param_name
= param
['param_name']
345 param_type
= param
['param_type']
346 param_format
= param
['param_format']
347 fieldconvert
= 'NULL'
348 vs_name
= param_to_value_string_name(param
)
349 if vs_name
in value_string_l
and 'INT' in param_type
:
350 fieldconvert
= 'VALS({})'.format(vs_name
)
351 header_field_reg_l
.append(' {{ &{}, {{ "{}", "sysdig.param.{}.{}", FT_{}, BASE_{}, {}, 0, NULL, HFILL }} }},'.format(
361 for line
in dissector_lines
:
365 if header_fields_re
.match(line
):
366 fill_comment
= header_fields_c
367 fill_l
= header_fields_l
368 elif event_names_re
.match(line
):
369 fill_comment
= event_names_c
370 fill_l
= event_names_l
371 elif event_definitions_re
.match(line
):
372 fill_comment
= event_definitions_c
373 fill_l
= event_definitions_l
374 elif value_strings_re
.match(line
):
375 fill_comment
= value_strings_c
376 fill_l
= value_strings_l
377 elif parameter_index_re
.match(line
):
378 fill_comment
= parameter_index_c
379 fill_l
= parameter_index_l
380 elif event_tree_re
.match(line
):
381 fill_comment
= event_tree_c
382 fill_l
= event_tree_l
383 elif syscall_code_re
.match(line
):
384 fill_comment
= syscall_code_c
385 fill_l
= syscall_code_l
386 elif header_field_reg_re
.match(line
):
387 fill_comment
= header_field_reg_c
388 fill_l
= header_field_reg_l
390 if fill_comment
is not None:
391 # Write our comment followed by the content
392 print(('Generating {}, {:d} lines'.format(fill_comment
, len(fill_l
))))
393 dissector_f
.write('/* {}. Automatically generated by tools/{} */\n'.format(
395 os
.path
.basename(__file__
)
398 dissector_f
.write('{}\n'.format(line
))
399 # Fill each section only once
403 dissector_f
.write(line
)
411 if __name__
== "__main__":