Skip to content

Commit bf0ff90

Browse files
authored
Merge pull request #457 from kimocoder/claude/scan-project-issues-rodxf
Performance optimizations: O(n*m) to O(n) lookups, lazy OUI loading, …
2 parents 16d4778 + 8a6a2cb commit bf0ff90

File tree

7 files changed

+76
-53
lines changed

7 files changed

+76
-53
lines changed

wifite/config.py

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,27 @@ class Configuration:
119119
# System check mode
120120
syscheck = None
121121

122+
@classmethod
123+
def load_manufacturers(cls):
124+
"""Lazy-load OUI manufacturer database on first access."""
125+
if cls._manufacturers_loaded:
126+
return
127+
cls._manufacturers_loaded = True
128+
if os.path.isfile('/usr/share/ieee-data/oui.txt'):
129+
mfr_file = '/usr/share/ieee-data/oui.txt'
130+
else:
131+
mfr_file = 'ieee-oui.txt'
132+
if os.path.exists(mfr_file):
133+
cls.manufacturers = {}
134+
with open(mfr_file, 'r', encoding='utf-8') as f:
135+
for line in f:
136+
if not re.match(r'^\w', line):
137+
continue
138+
line = line.replace('(hex)', '').replace('(base 16)', '')
139+
fields = line.split()
140+
if len(fields) >= 2:
141+
cls.manufacturers[fields[0]] = ' '.join(fields[1:]).rstrip('.')
142+
122143
@classmethod
123144
def initialize(cls, load_interface=True):
124145
"""
@@ -249,22 +270,9 @@ def initialize(cls, load_interface=True):
249270
cls.wordlists = [wlist]
250271
break
251272

252-
if os.path.isfile('/usr/share/ieee-data/oui.txt'):
253-
manufacturers = '/usr/share/ieee-data/oui.txt'
254-
else:
255-
manufacturers = 'ieee-oui.txt'
256-
257-
if os.path.exists(manufacturers):
258-
cls.manufacturers = {}
259-
with open(manufacturers, "r", encoding='utf-8') as f:
260-
# Parse txt format into dict
261-
for line in f:
262-
if not re.match(r"^\w", line):
263-
continue
264-
line = line.replace('(hex)', '').replace('(base 16)', '')
265-
fields = line.split()
266-
if len(fields) >= 2:
267-
cls.manufacturers[fields[0]] = " ".join(fields[1:]).rstrip('.')
273+
# Manufacturers database is lazy-loaded on first access via load_manufacturers()
274+
cls.manufacturers = None
275+
cls._manufacturers_loaded = False
268276

269277
# WPS variables
270278
cls.wps_filter = False # Only attack WPS networks

wifite/model/target.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,8 @@ def to_str(self, show_bssid=False, show_manufacturer=False):
268268
bssid = Color.s('{D}%s{W}' % self.bssid) if show_bssid else ''
269269
if show_manufacturer:
270270
oui = ''.join(self.bssid.split(':')[:3])
271-
self.manufacturer = Configuration.manufacturers.get(oui, "")
271+
Configuration.load_manufacturers()
272+
self.manufacturer = Configuration.manufacturers.get(oui, "") if Configuration.manufacturers else ""
272273

273274
max_oui_len = 21
274275
mfg_name = self.manufacturer

wifite/tools/airodump.py

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -184,23 +184,17 @@ def get_targets(self, old_targets=None, apply_filter=True, target_archives=None)
184184
new_targets = Airodump.get_targets_from_csv(csv_filename)
185185

186186
# Check if one of the targets is also contained in the old_targets
187+
# Use dict lookup (O(1)) instead of nested loop (O(n*m))
188+
old_by_bssid = {t.bssid: t for t in old_targets}
187189
for new_target in new_targets:
188-
just_found = True
189-
for old_target in old_targets:
190-
# If the new_target is found in old_target copy attributes from old target
191-
if old_target == new_target:
192-
# Identify decloaked targets
193-
if new_target.essid_known and not old_target.essid_known:
194-
# We decloaked a target!
195-
new_target.decloaked = True
196-
197-
old_target.transfer_info(new_target)
198-
just_found = False
199-
break
200-
201-
# If the new_target is not in old_targets, check target_archives
202-
# and copy attributes from there
203-
if just_found and new_target.bssid in target_archives:
190+
old_target = old_by_bssid.get(new_target.bssid)
191+
if old_target is not None:
192+
# Identify decloaked targets
193+
if new_target.essid_known and not old_target.essid_known:
194+
new_target.decloaked = True
195+
old_target.transfer_info(new_target)
196+
elif new_target.bssid in target_archives:
197+
# If the new_target is not in old_targets, check target_archives
204198
target_archives[new_target.bssid].transfer_info(new_target)
205199

206200
# Check targets for WPS
@@ -237,18 +231,22 @@ def get_targets_from_csv(csv_filename):
237231
targets2 = []
238232
import csv
239233

234+
# Detect encoding from first 4KB sample to avoid reading entire file twice
240235
try:
241236
import chardet
242-
with open(csv_filename, "rb") as rawdata:
243-
encoding = chardet.detect(rawdata.read())['encoding'] or 'utf-8'
237+
with open(csv_filename, 'rb') as rawdata:
238+
encoding = chardet.detect(rawdata.read(4096))['encoding'] or 'utf-8'
244239
except ImportError:
245240
encoding = 'utf-8'
246241

247242
with open(csv_filename, 'r', encoding=encoding, errors='ignore') as csvopen:
248243
lines = []
244+
has_null = False
249245
for line in csvopen:
250246
if '\0' in line:
251-
log_warning('Airodump', 'Null bytes found in CSV data, stripping them')
247+
if not has_null:
248+
log_warning('Airodump', 'Null bytes found in CSV data, stripping them')
249+
has_null = True
252250
line = line.replace('\0', '')
253251
lines.append(line)
254252

@@ -272,6 +270,12 @@ def get_targets_from_csv(csv_filename):
272270
elif row[0].strip() == 'Station MAC':
273271
# This is the 'header' for the list of Clients
274272
hit_clients = True
273+
# Build BSSID lookup dict for O(1) client-to-target matching
274+
# Use first occurrence per BSSID to match original behavior
275+
targets_by_bssid = {}
276+
for t in targets2:
277+
if t.bssid not in targets_by_bssid:
278+
targets_by_bssid[t.bssid] = t
275279
continue
276280

277281
if hit_clients:
@@ -286,11 +290,10 @@ def get_targets_from_csv(csv_filename):
286290
# Ignore unassociated clients
287291
continue
288292

289-
# Add this client to the appropriate Target
290-
for t in targets2:
291-
if t.bssid == client.bssid:
292-
t.clients.append(client)
293-
break
293+
# Add this client to the appropriate Target (O(1) dict lookup)
294+
target = targets_by_bssid.get(client.bssid)
295+
if target:
296+
target.clients.append(client)
294297

295298
else:
296299
# The current row corresponds to a 'Target' (router)

wifite/ui/scanner_view.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,8 @@ def _format_manufacturer(self, target) -> Text:
344344

345345
# Get OUI (first 3 octets of BSSID)
346346
oui = ''.join(target.bssid.split(':')[:3])
347-
manufacturer = Configuration.manufacturers.get(oui, "Unknown")
347+
Configuration.load_manufacturers()
348+
manufacturer = Configuration.manufacturers.get(oui, "Unknown") if Configuration.manufacturers else "Unknown"
348349

349350
# Truncate if too long
350351
max_len = 20

wifite/ui/selector_view.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,7 @@ def _format_manufacturer(self, target) -> Text:
412412

413413
# Get OUI (first 3 octets of BSSID)
414414
oui = ''.join(target.bssid.split(':')[:3])
415+
Configuration.load_manufacturers()
415416
manufacturer = Configuration.manufacturers.get(oui, "Unknown") if Configuration.manufacturers else "Unknown"
416417

417418
# Truncate if too long

wifite/util/process.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ def __init__(self, command, devnull=False, stdout=PIPE, stderr=PIPE, cwd=None, b
176176
log_warning('Process', 'Delaying process creation due to high FD usage')
177177
if Configuration.verbose > 0:
178178
Color.pl('{!} {O}Delaying process creation due to high FD usage{W}')
179-
time.sleep(0.5) # Brief delay to allow cleanup to complete
179+
time.sleep(0.1) # Brief delay to allow cleanup to complete
180180

181181
self.out = None
182182
self.err = None
@@ -447,15 +447,27 @@ def cleanup_zombies():
447447
except Exception:
448448
pass
449449

450+
# Cache for FD count to avoid filesystem scan on every process creation
451+
_fd_cache_time = 0
452+
_fd_cache_value = -1
453+
_FD_CACHE_TTL = 2.0 # seconds
454+
450455
@staticmethod
451456
def get_open_fd_count():
452-
"""Get current open file descriptor count from /proc/{pid}/fd"""
457+
"""Get current open file descriptor count from /proc/{pid}/fd (cached with TTL)"""
458+
now = time.time()
459+
if now - Process._fd_cache_time < Process._FD_CACHE_TTL:
460+
return Process._fd_cache_value
453461
try:
454462
proc_fd_dir = f'/proc/{os.getpid()}/fd'
455463
if os.path.exists(proc_fd_dir):
456-
return len(os.listdir(proc_fd_dir))
464+
Process._fd_cache_value = len(os.listdir(proc_fd_dir))
465+
Process._fd_cache_time = now
466+
return Process._fd_cache_value
457467
except Exception:
458468
pass
469+
Process._fd_cache_value = -1
470+
Process._fd_cache_time = now
459471
return -1
460472

461473
@staticmethod

wifite/util/scanner.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,6 @@ def find_targets(self):
108108
if self.found_target():
109109
return True # We found the target we want
110110

111-
if airodump.pid.poll() is not None:
112-
return True # Airodump process died
113-
114111
# Update display based on mode
115112
if self.use_tui and self.view:
116113
self.view.update_targets(self.targets, airodump.decloaking)
@@ -561,13 +558,13 @@ def _cleanup_memory(self):
561558

562559
# 2. Limit target list size (keep strongest signals)
563560
if len(self.targets) > self._max_targets:
564-
# Sort by power (strongest first)
565-
self.targets.sort(key=lambda x: x.power, reverse=True)
561+
import heapq
566562
removed_count = len(self.targets) - self._max_targets
567-
self.targets = self.targets[:self._max_targets]
568-
563+
# Use heapq.nlargest: O(n log k) vs O(n log n) for full sort
564+
self.targets = heapq.nlargest(self._max_targets, self.targets, key=lambda x: x.power)
565+
569566
if Configuration.verbose > 1:
570-
Color.pl('{!} {O}Trimmed %d weak targets (limit: %d){W}' %
567+
Color.pl('{!} {O}Trimmed %d weak targets (limit: %d){W}' %
571568
(removed_count, self._max_targets))
572569

573570
# 3. Clean up old archived targets with time-based expiration

0 commit comments

Comments
 (0)