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