1#!/usr/bin/env python3
2# Parses the nl80211.h interface and generate appropriate enums and fields
3# (value_string) for packet-netlink-nl80211.c
4#
5# Copyright (c) 2017, Peter Wu <peter@lekensteyn.nl>
6# Copyright (c) 2018, Mikael Kanstrup <mikael.kanstrup@sony.com>
7#
8# Wireshark - Network traffic analyzer
9# By Gerald Combs <gerald@wireshark.org>
10# Copyright 1998 Gerald Combs
11#
12# SPDX-License-Identifier: GPL-2.0-or-later
13#
14#
15# To update the dissector source file, run this from the source directory:
16#
17#   python tools/generate-nl80211-fields.py --update
18#
19
20import argparse
21import re
22import requests
23import sys
24
25# Begin of comment, followed by the actual array definition
26HEADER = "/* Definitions from linux/nl80211.h {{{ */\n"
27FOOTER = "/* }}} */\n"
28# Enums to extract from the header file
29EXPORT_ENUMS = {
30    # 'enum_name': ('field_name', field_type', 'field_blurb')
31    'nl80211_commands': ('Command', 'FT_UINT8', '"Generic Netlink Command"'),
32    'nl80211_attrs': (None, None, None),
33    'nl80211_iftype': (None, None, None),
34    'nl80211_sta_flags': (None, None, None),
35    'nl80211_sta_p2p_ps_status': ('Attribute Value', 'FT_UINT8', None),
36    'nl80211_he_gi': (None, None, None),
37    'nl80211_he_ru_alloc': (None, None, None),
38    'nl80211_rate_info': (None, None, None),
39    'nl80211_sta_bss_param': (None, None, None),
40    'nl80211_sta_info': (None, None, None),
41    'nl80211_tid_stats': (None, None, None),
42    'nl80211_txq_stats': (None, None, None),
43    'nl80211_mpath_flags': (None, None, None),
44    'nl80211_mpath_info': (None, None, None),
45    'nl80211_band_iftype_attr': (None, None, None),
46    'nl80211_band_attr': (None, None, None),
47    'nl80211_wmm_rule': (None, None, None),
48    'nl80211_frequency_attr': (None, None, None),
49    'nl80211_bitrate_attr': (None, None, None),
50    'nl80211_reg_initiator': ('Attribute Value', 'FT_UINT8', None),
51    'nl80211_reg_type': ('Attribute Value', 'FT_UINT8', None),
52    'nl80211_reg_rule_attr': (None, None, None),
53    'nl80211_sched_scan_match_attr': (None, None, None),
54    'nl80211_reg_rule_flags': (None, None, None),
55    'nl80211_dfs_regions': ('Attribute Value', 'FT_UINT8', None),
56    'nl80211_user_reg_hint_type': ('Attribute Value', 'FT_UINT32', None),
57    'nl80211_survey_info': (None, None, None),
58    'nl80211_mntr_flags': (None, None, None),
59    'nl80211_mesh_power_mode': ('Attribute Value', 'FT_UINT32', None),
60    'nl80211_meshconf_params': (None, None, None),
61    'nl80211_mesh_setup_params': (None, None, None),
62    'nl80211_txq_attr': (None, None, None),
63    'nl80211_ac': (None, None, None),
64    'nl80211_channel_type': ('Attribute Value', 'FT_UINT32', None),
65    'nl80211_key_mode': (None, None, None),
66    'nl80211_chan_width': ('Attribute Value', 'FT_UINT32', None),
67    'nl80211_bss_scan_width': ('Attribute Value', 'FT_UINT32', None),
68    'nl80211_bss': (None, None, None),
69    'nl80211_bss_status': ('Attribute Value', 'FT_UINT32', None),
70    'nl80211_auth_type': ('Attribute Value', 'FT_UINT32', None),
71    'nl80211_key_type': ('Attribute Value', 'FT_UINT32', None),
72    'nl80211_mfp': ('Attribute Value', 'FT_UINT32', None),
73    'nl80211_wpa_versions': (None, None, None),
74    'nl80211_key_default_types': (None, None, None),
75    'nl80211_key_attributes': (None, None, None),
76    'nl80211_tx_rate_attributes': (None, None, None),
77    'nl80211_txrate_gi': (None, None, None),
78    'nl80211_band': (None, None, None),
79    'nl80211_ps_state': ('Attribute Value', 'FT_UINT32', None),
80    'nl80211_attr_cqm': (None, None, None),
81    'nl80211_cqm_rssi_threshold_event': (None, None, None),
82    'nl80211_tx_power_setting': ('Attribute Value', 'FT_UINT32', None),
83    'nl80211_packet_pattern_attr': (None, None, None),
84    'nl80211_wowlan_triggers': (None, None, None),
85    'nl80211_wowlan_tcp_attrs': (None, None, None),
86    'nl80211_attr_coalesce_rule': (None, None, None),
87    'nl80211_coalesce_condition': (None, None, None),
88    'nl80211_iface_limit_attrs': (None, None, None),
89    'nl80211_if_combination_attrs': (None, None, None),
90    'nl80211_plink_state': ('Attribute Value', 'FT_UINT8', None),
91    'plink_actions': ('Attribute Value', 'FT_UINT8', None),
92    'nl80211_rekey_data': (None, None, None),
93    'nl80211_hidden_ssid': (None, None, None),
94    'nl80211_sta_wme_attr': (None, None, None),
95    'nl80211_pmksa_candidate_attr': (None, None, None),
96    'nl80211_tdls_operation': ('Attribute Value', 'FT_UINT8', None),
97    #Reserved for future use 'nl80211_ap_sme_features': (None, None, None),
98    'nl80211_feature_flags': (None, None, None),
99    'nl80211_ext_feature_index': (None, None, None),
100    'nl80211_probe_resp_offload_support_attr': (None, None, None),
101    'nl80211_connect_failed_reason': ('Attribute Value', 'FT_UINT32', None),
102    'nl80211_timeout_reason': ('Attribute Value', 'FT_UINT32', None),
103    'nl80211_scan_flags': (None, None, None),
104    'nl80211_acl_policy': ('Attribute Value', 'FT_UINT32', None),
105    'nl80211_smps_mode': ('Attribute Value', 'FT_UINT8', None),
106    'nl80211_radar_event': ('Attribute Value', 'FT_UINT32', None),
107    'nl80211_dfs_state': (None, None, None),
108    'nl80211_protocol_features': (None, None, None),
109    'nl80211_crit_proto_id': ('Attribute Value', 'FT_UINT16', None),
110    'nl80211_rxmgmt_flags': (None, None, None),
111    'nl80211_tdls_peer_capability': (None, None, None),
112    'nl80211_sched_scan_plan': (None, None, None),
113    'nl80211_bss_select_attr': (None, None, None),
114    'nl80211_nan_function_type': (None, None, None),
115    'nl80211_nan_publish_type': (None, None, None),
116    'nl80211_nan_func_term_reason': (None, None, None),
117    'nl80211_nan_func_attributes': (None, None, None),
118    'nl80211_nan_srf_attributes': (None, None, None),
119    'nl80211_nan_match_attributes': (None, None, None),
120    'nl80211_external_auth_action': ('Attribute Value', 'FT_UINT32', None),
121    'nl80211_ftm_responder_attributes': (None, None, None),
122    'nl80211_ftm_responder_stats': (None, None, None),
123    'nl80211_preamble': (None, None, None),
124    'nl80211_peer_measurement_type': (None, None, None),
125    'nl80211_peer_measurement_status': (None, None, None),
126    'nl80211_peer_measurement_req': (None, None, None),
127    'nl80211_peer_measurement_resp': (None, None, None),
128    'nl80211_peer_measurement_peer_attrs': (None, None, None),
129    'nl80211_peer_measurement_attrs': (None, None, None),
130    'nl80211_peer_measurement_ftm_capa': (None, None, None),
131    'nl80211_peer_measurement_ftm_req': (None, None, None),
132    'nl80211_peer_measurement_ftm_failure_reasons': (None, None, None),
133    'nl80211_peer_measurement_ftm_resp': (None, None, None),
134    'nl80211_obss_pd_attributes': (None, None, None),
135}
136# File to be patched
137SOURCE_FILE = "epan/dissectors/packet-netlink-nl80211.c"
138# URL where the latest version can be found
139URL = "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/plain/include/uapi/linux/nl80211.h"
140
141def make_enum(name, values, expressions, indent):
142    code = 'enum ws_%s {\n' % name
143    for value, expression in zip(values, expressions):
144        if expression and 'NL80211' in expression:
145            expression = 'WS_%s' % expression
146        if expression:
147            code += '%sWS_%s = %s,\n' % (indent, value, expression)
148        else:
149            code += '%sWS_%s,\n' % (indent, value)
150
151    code += '};\n'
152    return code
153
154def make_value_string(name, values, indent,):
155    code = 'static const value_string ws_%s_vals[] = {\n' % name
156    align = 40
157    for value in values:
158        code += indent + ('{ WS_%s,' % value).ljust(align - 1) + ' '
159        code += '"%s" },\n' % value
160    code += '%s{ 0, NULL }\n' % indent
161    code += '};\n'
162    code += 'static value_string_ext ws_%s_vals_ext =' % name
163    code += ' VALUE_STRING_EXT_INIT(ws_%s_vals);\n' % name
164    return code
165
166def remove_prefix(prefix, text):
167    if text.startswith(prefix):
168        return text[len(prefix):]
169    return text
170
171def make_hfi(name, indent):
172    (field_name, field_type, field_blurb) = EXPORT_ENUMS.get(name)
173    field_abbrev = name
174
175    # Fill in default values
176    if not field_name:
177        field_name = 'Attribute Type'
178    if not field_type:
179        field_type = 'FT_UINT16'
180    if not field_blurb:
181        field_blurb = 'NULL'
182
183    # Special treatment of already existing field names
184    rename_fields = {
185        'nl80211_attrs': 'nl80211_attr_type',
186        'nl80211_commands': 'nl80211_cmd'
187    }
188    if rename_fields.get(name):
189        field_abbrev = rename_fields[name]
190    field_abbrev = remove_prefix('nl80211_', field_abbrev)
191
192    code = 'static header_field_info hfi_%s NETLINK_NL80211_HFI_INIT =\n' % name
193    code += indent + '{ "%s", "nl80211.%s", %s, BASE_DEC | BASE_EXT_STRING,\n' % \
194        (field_name, field_abbrev, field_type)
195    code += indent + '  VALS_EXT_PTR(&ws_%s_vals_ext), 0x00, %s, HFILL };\n' % (name, field_blurb)
196    return code
197
198def make_ett_defs(name, indent):
199    code = 'static gint ett_%s = -1;' % name
200    return code
201
202def make_hfi_init(name, indent):
203    code = indent + indent + '&hfi_%s,' % name
204    return code
205
206def make_ett(name, indent):
207    code = indent + indent + '&ett_%s,' % name
208    return code
209
210class EnumStore(object):
211    __RE_ENUM_VALUE = re.compile(
212        r'\s+?(?P<value>\w+)(?:\ /\*.*?\*\/)?(?:\s*=\s*(?P<expression>.*?))?(?:\s*,|$)',
213        re.MULTILINE | re.DOTALL)
214
215    def __init__(self, name, values):
216        self.name = name
217        self.values = []
218        self.expressions = []
219        self.active = True
220        self.parse_values(values)
221
222
223    def parse_values(self, values):
224        for m in self.__RE_ENUM_VALUE.finditer(values):
225            value, expression = m.groups()
226            if value.startswith('NUM_'):
227                break
228            if value.endswith('_AFTER_LAST'):
229                break
230            if value.endswith('_LAST'):
231                break
232            if value.startswith('__') and value.endswith('_NUM'):
233                break
234            if expression and expression in self.values:
235                # Skip aliases
236                continue
237            self.values.append(value)
238            self.expressions.append(expression)
239
240    def finish(self):
241        return self.name, self.values, self.expressions
242
243RE_ENUM = re.compile(
244    r'enum\s+?(?P<enum>\w+)\s+?\{(?P<values>.*?)\}\;',
245    re.MULTILINE | re.DOTALL)
246RE_COMMENT = re.compile(r'/\*.*?\*/', re.MULTILINE | re.DOTALL)
247
248def parse_header(content):
249    # Strip comments
250    content = re.sub(RE_COMMENT, '', content)
251
252    enums = []
253    for m in RE_ENUM.finditer(content):
254        enum = m.group('enum')
255        values = m.group('values')
256        if enum in EXPORT_ENUMS:
257            enums.append(EnumStore(enum, values).finish())
258
259    return enums
260
261def parse_source():
262    """
263    Reads the source file and tries to split it in the parts before, inside and
264    after the block.
265    """
266    begin, block, end = '', '', ''
267    parts = []
268    # Stages: 1 (before block), 2 (in block, skip), 3 (after block)
269    stage = 1
270    with open(SOURCE_FILE) as f:
271        for line in f:
272            if line == FOOTER and stage == 2:
273                stage = 3   # End of block
274            if stage == 1:
275                begin += line
276            elif stage == 2:
277                block += line
278            elif stage == 3:
279                end += line
280            if line == HEADER and stage == 1:
281                stage = 2   # Begin of block
282            if line == HEADER and stage == 3:
283                stage = 2   # Begin of next code block
284                parts.append((begin, block, end))
285                begin, block, end = '', '', ''
286
287    parts.append((begin, block, end))
288    if stage != 3 or len(parts) != 3:
289        raise RuntimeError("Could not parse file (in stage %d) (parts %d)" % (stage, len(parts)))
290    return parts
291
292parser = argparse.ArgumentParser()
293parser.add_argument("--update", action="store_true",
294        help="Update %s as needed instead of writing to stdout" % SOURCE_FILE)
295parser.add_argument("--indent", default=" " * 4,
296        help="indentation (use \\t for tabs, default 4 spaces)")
297parser.add_argument("header_file", nargs="?", default=URL,
298        help="nl80211.h header file (use - for stdin or a HTTP(S) URL, "
299             "default %(default)s)")
300
301def main():
302    args = parser.parse_args()
303
304    indent = args.indent.replace("\\t", "\t")
305
306    if any(args.header_file.startswith(proto) for proto in ('http:', 'https')):
307        r = requests.get(args.header_file)
308        r.raise_for_status()
309        enums = parse_header(r.text)
310    elif args.header_file == "-":
311        enums = parse_header(sys.stdin.read())
312    else:
313        with open(args.header_file) as f:
314            enums = parse_header(f.read())
315
316    assert len(enums) == len(EXPORT_ENUMS), \
317            "Could not parse data, found %d/%d results" % \
318            (len(enums), len(EXPORT_ENUMS))
319
320    code_enums, code_vals, code_hfi, code_ett_defs, code_hfi_init, code_ett = '', '', '', '', '', ''
321    for enum_name, enum_values, expressions in enums:
322        code_enums += make_enum(enum_name, enum_values, expressions, indent) + '\n'
323        code_vals += make_value_string(enum_name, enum_values, indent) + '\n'
324        code_hfi += make_hfi(enum_name, indent) + '\n'
325        code_ett_defs += make_ett_defs(enum_name, indent) + '\n'
326        code_hfi_init += make_hfi_init(enum_name, indent) + '\n'
327        code_ett += make_ett(enum_name, indent) + '\n'
328
329    code_top = code_enums + code_vals + code_hfi + code_ett_defs
330    code_top = code_top.rstrip("\n") + "\n"
331
332    code = [code_top, code_hfi_init, code_ett]
333
334    update = False
335    if args.update:
336        parts = parse_source()
337
338        # Check if file needs update
339        for (begin, old_code, end), new_code in zip(parts, code):
340            if old_code != new_code:
341                update = True
342                break
343        if not update:
344            print("File is up-to-date")
345            return
346        # Update file
347        with open(SOURCE_FILE, "w") as f:
348            for (begin, old_code, end), new_code in zip(parts, code):
349                f.write(begin)
350                f.write(new_code)
351                f.write(end)
352        print("Updated %s" % SOURCE_FILE)
353    else:
354        for new_code in code:
355            print(new_code)
356
357if __name__ == '__main__':
358    main()
359
360#
361# Editor modelines  -  https://www.wireshark.org/tools/modelines.html
362#
363# Local variables:
364# c-basic-offset: 4
365# tab-width: 8
366# indent-tabs-mode: nil
367# End:
368#
369# vi: set shiftwidth=4 tabstop=8 expandtab:
370# :indentSize=4:tabSize=8:noTabs=true:
371#
372