1#!/usr/bin/env python3 2# Wireshark - Network traffic analyzer 3# By Gerald Combs <gerald@wireshark.org> 4# Copyright 1998 Gerald Combs 5# 6# SPDX-License-Identifier: GPL-2.0-or-later 7 8import os 9import re 10import subprocess 11import argparse 12import signal 13 14# This utility scans for tfs items, and works out if standard ones 15# could have been used intead (from epan/tfs.c) 16 17# TODO: 18# - check how many of the definitions in epan/tfs.c are used in other dissectors 19# - see if there are other values that should be in epan/tfs.c and shared 20 21 22# Try to exit soon after Ctrl-C is pressed. 23should_exit = False 24 25def signal_handler(sig, frame): 26 global should_exit 27 should_exit = True 28 print('You pressed Ctrl+C - exiting') 29 30signal.signal(signal.SIGINT, signal_handler) 31 32 33# Keep track of custom entries that might appear in multiple dissectors, 34# so we can consider adding them to tfs.c 35custom_tfs_entries = {} 36def AddCustomEntry(val1, val2, file): 37 global custom_tfs_entries 38 if (val1, val2) in custom_tfs_entries: 39 custom_tfs_entries[(val1, val2)].append(file) 40 else: 41 custom_tfs_entries[(val1, val2)] = [file] 42 43 44 45class TFS: 46 def __init__(self, file, name, val1, val2): 47 self.file = file 48 self.name = name 49 self.val1 = val1 50 self.val2 = val2 51 52 # Do some extra checks on values. 53 if val1.startswith(' ') or val1.endswith(' '): 54 print('N.B.: file=' + self.file + ' ' + self.name + ' - false val begins or ends with space \"' + self.val1 + '\"') 55 if val2.startswith(' ') or val2.endswith(' '): 56 print('N.B.: file=' + self.file + ' ' + self.name + ' - true val begins or ends with space \"' + self.val2 + '\"') 57 58 def __str__(self): 59 return '{' + '"' + self.val1 + '", "' + self.val2 + '"}' 60 61 62def removeComments(code_string): 63 code_string = re.sub(re.compile(r"/\*.*?\*/",re.DOTALL ) ,"" ,code_string) # C-style comment 64 code_string = re.sub(re.compile(r"//.*?\n" ) ,"" ,code_string) # C++-style comment 65 return code_string 66 67 68# Look for hf items in a dissector file. 69def findItems(filename): 70 items = {} 71 72 with open(filename, 'r') as f: 73 contents = f.read() 74 # Example: const true_false_string tfs_true_false = { "True", "False" }; 75 76 # Remove comments so as not to trip up RE. 77 contents = removeComments(contents) 78 79 matches = re.finditer(r'.*const\s*true_false_string\s*([a-z_]*)\s*=\s*{\s*\"([a-zA-Z_ ]*)\"\s*,\s*\"([a-zA-Z_ ]*)\"', contents) 80 for m in matches: 81 name = m.group(1) 82 val1 = m.group(2) 83 val2 = m.group(3) 84 # Store this entry. 85 items[name] = TFS(filename, name, val1, val2) 86 87 return items 88 89 90 91def is_dissector_file(filename): 92 p = re.compile(r'.*packet-.*\.c') 93 return p.match(filename) 94 95def findDissectorFilesInFolder(folder): 96 # Look at files in sorted order, to give some idea of how far through is. 97 files = [] 98 99 for f in sorted(os.listdir(folder)): 100 if should_exit: 101 return 102 if is_dissector_file(f): 103 filename = os.path.join(folder, f) 104 files.append(filename) 105 return files 106 107warnings_found = 0 108errors_found = 0 109 110# Check the given dissector file. 111def checkFile(filename, tfs_items, look_for_common=False): 112 global warnings_found 113 global errors_found 114 115 # Check file exists - e.g. may have been deleted in a recent commit. 116 if not os.path.exists(filename): 117 print(filename, 'does not exist!') 118 return 119 120 # Find items. 121 items = findItems(filename) 122 123 # See if any of these items already existed in tfs.c 124 for i in items: 125 for t in tfs_items: 126 found = False 127 128 # 129 # Do not do this check for plugins; plugins cannot import 130 # data values from libwireshark (functions, yes; data 131 # values, no). 132 # 133 # Test whether there's a common prefix for the file name 134 # and "plugin/epan/"; if so, this is a plugin, and there 135 # is no common path and os.path.commonprefix returns an 136 # empty string, otherwise it returns the common path, so 137 # we check whether the common path is an empty string. 138 # 139 if os.path.commonprefix([filename, 'plugin/epan/']) == '': 140 exact_case = False 141 if tfs_items[t].val1 == items[i].val1 and tfs_items[t].val2 == items[i].val2: 142 found = True 143 exact_case = True 144 elif tfs_items[t].val1.upper() == items[i].val1.upper() and tfs_items[t].val2.upper() == items[i].val2.upper(): 145 found = True 146 147 if found: 148 print(filename, i, "- could have used", t, 'from tfs.c instead: ', tfs_items[t], 149 '' if exact_case else ' (capitalisation differs)') 150 if exact_case: 151 errors_found += 1 152 else: 153 warnings_found += 1 154 break 155 if not found: 156 if look_for_common: 157 AddCustomEntry(items[i].val1, items[i].val2, filename) 158 159 160################################################################# 161# Main logic. 162 163# command-line args. Controls which dissector files should be checked. 164# If no args given, will just scan epan/dissectors folder. 165parser = argparse.ArgumentParser(description='Check calls in dissectors') 166parser.add_argument('--file', action='store', default='', 167 help='specify individual dissector file to test') 168parser.add_argument('--commits', action='store', 169 help='last N commits to check') 170parser.add_argument('--open', action='store_true', 171 help='check open files') 172parser.add_argument('--common', action='store_true', 173 help='check for potential new entries for tfs.c') 174 175 176args = parser.parse_args() 177 178 179# Get files from wherever command-line args indicate. 180files = [] 181if args.file: 182 # Add single specified file.. 183 if not args.file.startswith('epan'): 184 files.append(os.path.join('epan', 'dissectors', args.file)) 185 else: 186 files.append(args.file) 187elif args.commits: 188 # Get files affected by specified number of commits. 189 command = ['git', 'diff', '--name-only', 'HEAD~' + args.commits] 190 files = [f.decode('utf-8') 191 for f in subprocess.check_output(command).splitlines()] 192 # Will examine dissector files only 193 files = list(filter(lambda f : is_dissector_file(f), files)) 194elif args.open: 195 # Unstaged changes. 196 command = ['git', 'diff', '--name-only'] 197 files = [f.decode('utf-8') 198 for f in subprocess.check_output(command).splitlines()] 199 # Only interested in dissector files. 200 files = list(filter(lambda f : is_dissector_file(f), files)) 201 # Staged changes. 202 command = ['git', 'diff', '--staged', '--name-only'] 203 files_staged = [f.decode('utf-8') 204 for f in subprocess.check_output(command).splitlines()] 205 # Only interested in dissector files. 206 files_staged = list(filter(lambda f : is_dissector_file(f), files_staged)) 207 for f in files_staged: 208 if not f in files: 209 files.append(f) 210else: 211 # Find all dissector files from folder. 212 files = findDissectorFilesInFolder(os.path.join('epan', 'dissectors')) 213 214 215# If scanning a subset of files, list them here. 216print('Examining:') 217if args.file or args.commits or args.open: 218 if files: 219 print(' '.join(files), '\n') 220 else: 221 print('No files to check.\n') 222else: 223 print('All dissector modules\n') 224 225 226# Get standard/ shared ones. 227tfs_entries = findItems(os.path.join('epan', 'tfs.c')) 228 229# Now check the files to see if they could have used shared ones instead. 230for f in files: 231 if should_exit: 232 exit(1) 233 checkFile(f, tfs_entries, look_for_common=args.common) 234 235 236# Show summary. 237print(warnings_found, 'warnings found') 238if errors_found: 239 print(errors_found, 'errors found') 240 exit(1) 241 242if args.common: 243 # Looking for items that could potentially be moved to tfs.c 244 for c in custom_tfs_entries: 245 # Only want to see items that have 3 or more occurrences. 246 # Even then, probably only want to consider ones that sound generic. 247 if len(custom_tfs_entries[c]) > 2: 248 print(c, 'appears', len(custom_tfs_entries[c]), 'times, in: ', custom_tfs_entries[c]) 249