-
Notifications
You must be signed in to change notification settings - Fork 277
Performance optimizations: O(n*m) to O(n) lookups, lazy OUI loading, … #457
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -184,23 +184,17 @@ def get_targets(self, old_targets=None, apply_filter=True, target_archives=None) | |
| new_targets = Airodump.get_targets_from_csv(csv_filename) | ||
|
|
||
| # Check if one of the targets is also contained in the old_targets | ||
| # Use dict lookup (O(1)) instead of nested loop (O(n*m)) | ||
| old_by_bssid = {t.bssid: t for t in old_targets} | ||
| for new_target in new_targets: | ||
| just_found = True | ||
| for old_target in old_targets: | ||
| # If the new_target is found in old_target copy attributes from old target | ||
| if old_target == new_target: | ||
| # Identify decloaked targets | ||
| if new_target.essid_known and not old_target.essid_known: | ||
| # We decloaked a target! | ||
| new_target.decloaked = True | ||
|
|
||
| old_target.transfer_info(new_target) | ||
| just_found = False | ||
| break | ||
|
|
||
| # If the new_target is not in old_targets, check target_archives | ||
| # and copy attributes from there | ||
| if just_found and new_target.bssid in target_archives: | ||
| old_target = old_by_bssid.get(new_target.bssid) | ||
| if old_target is not None: | ||
| # Identify decloaked targets | ||
| if new_target.essid_known and not old_target.essid_known: | ||
| new_target.decloaked = True | ||
| old_target.transfer_info(new_target) | ||
| elif new_target.bssid in target_archives: | ||
|
Comment on lines
+187
to
+196
|
||
| # If the new_target is not in old_targets, check target_archives | ||
| target_archives[new_target.bssid].transfer_info(new_target) | ||
|
|
||
| # Check targets for WPS | ||
|
|
@@ -237,18 +231,22 @@ def get_targets_from_csv(csv_filename): | |
| targets2 = [] | ||
| import csv | ||
|
|
||
| # Detect encoding from first 4KB sample to avoid reading entire file twice | ||
| try: | ||
| import chardet | ||
| with open(csv_filename, "rb") as rawdata: | ||
| encoding = chardet.detect(rawdata.read())['encoding'] or 'utf-8' | ||
| with open(csv_filename, 'rb') as rawdata: | ||
| encoding = chardet.detect(rawdata.read(4096))['encoding'] or 'utf-8' | ||
| except ImportError: | ||
| encoding = 'utf-8' | ||
|
|
||
| with open(csv_filename, 'r', encoding=encoding, errors='ignore') as csvopen: | ||
| lines = [] | ||
| has_null = False | ||
| for line in csvopen: | ||
| if '\0' in line: | ||
| log_warning('Airodump', 'Null bytes found in CSV data, stripping them') | ||
| if not has_null: | ||
| log_warning('Airodump', 'Null bytes found in CSV data, stripping them') | ||
| has_null = True | ||
| line = line.replace('\0', '') | ||
| lines.append(line) | ||
|
|
||
|
|
@@ -272,6 +270,12 @@ def get_targets_from_csv(csv_filename): | |
| elif row[0].strip() == 'Station MAC': | ||
| # This is the 'header' for the list of Clients | ||
| hit_clients = True | ||
| # Build BSSID lookup dict for O(1) client-to-target matching | ||
| # Use first occurrence per BSSID to match original behavior | ||
| targets_by_bssid = {} | ||
| for t in targets2: | ||
| if t.bssid not in targets_by_bssid: | ||
| targets_by_bssid[t.bssid] = t | ||
| continue | ||
|
|
||
| if hit_clients: | ||
|
|
@@ -286,11 +290,10 @@ def get_targets_from_csv(csv_filename): | |
| # Ignore unassociated clients | ||
| continue | ||
|
|
||
| # Add this client to the appropriate Target | ||
| for t in targets2: | ||
| if t.bssid == client.bssid: | ||
| t.clients.append(client) | ||
| break | ||
| # Add this client to the appropriate Target (O(1) dict lookup) | ||
| target = targets_by_bssid.get(client.bssid) | ||
| if target: | ||
| target.clients.append(client) | ||
|
|
||
| else: | ||
| # The current row corresponds to a 'Target' (router) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -176,7 +176,7 @@ def __init__(self, command, devnull=False, stdout=PIPE, stderr=PIPE, cwd=None, b | |
| log_warning('Process', 'Delaying process creation due to high FD usage') | ||
| if Configuration.verbose > 0: | ||
| Color.pl('{!} {O}Delaying process creation due to high FD usage{W}') | ||
| time.sleep(0.5) # Brief delay to allow cleanup to complete | ||
| time.sleep(0.1) # Brief delay to allow cleanup to complete | ||
|
|
||
| self.out = None | ||
| self.err = None | ||
|
|
@@ -447,15 +447,27 @@ def cleanup_zombies(): | |
| except Exception: | ||
| pass | ||
|
|
||
| # Cache for FD count to avoid filesystem scan on every process creation | ||
| _fd_cache_time = 0 | ||
| _fd_cache_value = -1 | ||
| _FD_CACHE_TTL = 2.0 # seconds | ||
|
|
||
| @staticmethod | ||
| def get_open_fd_count(): | ||
| """Get current open file descriptor count from /proc/{pid}/fd""" | ||
| """Get current open file descriptor count from /proc/{pid}/fd (cached with TTL)""" | ||
| now = time.time() | ||
| if now - Process._fd_cache_time < Process._FD_CACHE_TTL: | ||
| return Process._fd_cache_value | ||
| try: | ||
| proc_fd_dir = f'/proc/{os.getpid()}/fd' | ||
| if os.path.exists(proc_fd_dir): | ||
| return len(os.listdir(proc_fd_dir)) | ||
| Process._fd_cache_value = len(os.listdir(proc_fd_dir)) | ||
| Process._fd_cache_time = now | ||
| return Process._fd_cache_value | ||
|
Comment on lines
456
to
+466
|
||
| except Exception: | ||
| pass | ||
| Process._fd_cache_value = -1 | ||
| Process._fd_cache_time = now | ||
| return -1 | ||
|
|
||
| @staticmethod | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -108,9 +108,6 @@ def find_targets(self): | |||||||||||||||
| if self.found_target(): | ||||||||||||||||
| return True # We found the target we want | ||||||||||||||||
|
|
||||||||||||||||
| if airodump.pid.poll() is not None: | ||||||||||||||||
| return True # Airodump process died | ||||||||||||||||
|
|
||||||||||||||||
| # Update display based on mode | ||||||||||||||||
| if self.use_tui and self.view: | ||||||||||||||||
| self.view.update_targets(self.targets, airodump.decloaking) | ||||||||||||||||
|
|
@@ -561,13 +558,13 @@ def _cleanup_memory(self): | |||||||||||||||
|
|
||||||||||||||||
| # 2. Limit target list size (keep strongest signals) | ||||||||||||||||
| if len(self.targets) > self._max_targets: | ||||||||||||||||
| # Sort by power (strongest first) | ||||||||||||||||
| self.targets.sort(key=lambda x: x.power, reverse=True) | ||||||||||||||||
| import heapq | ||||||||||||||||
| removed_count = len(self.targets) - self._max_targets | ||||||||||||||||
| self.targets = self.targets[:self._max_targets] | ||||||||||||||||
|
|
||||||||||||||||
| # Use heapq.nlargest: O(n log k) vs O(n log n) for full sort | ||||||||||||||||
| self.targets = heapq.nlargest(self._max_targets, self.targets, key=lambda x: x.power) | ||||||||||||||||
|
||||||||||||||||
| self.targets = heapq.nlargest(self._max_targets, self.targets, key=lambda x: x.power) | |
| # Add deterministic tie-breaker (BSSID) to avoid non-deterministic ordering for equal power | |
| self.targets = heapq.nlargest( | |
| self._max_targets, | |
| self.targets, | |
| key=lambda x: (x.power, getattr(x, 'bssid', '')) | |
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Configuration.load_manufacturers()readscls._manufacturers_loaded, but_manufacturers_loadedis not defined as a class attribute (it’s only set insideinitialize). Ifload_manufacturers()is called beforeinitialize(), this will raiseAttributeError. Define_manufacturers_loaded = Falseat class scope (and consider usinggetattr(cls, '_manufacturers_loaded', False)for extra safety).