diff --git a/wifi_monitor.py b/wifi_monitor.py index 01a3da4..4700ce4 100755 --- a/wifi_monitor.py +++ b/wifi_monitor.py @@ -12,6 +12,9 @@ Usage: # Read from pcap file: python3 wifi_monitor.py --pcap /tmp/capture.pcap python3 wifi_monitor.py /tmp/capture.pcap # Also supported for backward compatibility + + # High-rate traffic (e.g. iperf): capture with tcpdump to avoid scapy drops, then analyze: + sudo python3 wifi_monitor.py -i wlan0 -c 11 -t 10 --tcpdump-capture """ # Standard library imports @@ -40,6 +43,7 @@ class Config: self.wifi_interface = os.environ.get("WIFI_INTERFACE", "wlan0") self.keep_pcap = False self.full_packet = False + self.tcpdump_capture = False # Use tcpdump for capture (recommended for high packet rates) self.default_snaplen = 256 # Header + some payload @property @@ -680,8 +684,12 @@ class PacketCapture: self.parser = PacketParser() self.analyzer = CaptureAnalyzer(self.parser) - def capture_from_pcap(self, pcap_file): - """Read and analyze packets from a PCAP file.""" + def capture_from_pcap(self, pcap_file, duration_sec=None): + """Read and analyze packets from a PCAP file. + + duration_sec: If set (e.g. when file was captured with -t N), used for packet rate. + If None, rate uses 1.0 second. + """ print("=== Reading packets from PCAP file ===") print(f"PCAP file: {pcap_file}") print() @@ -694,7 +702,8 @@ class PacketCapture: print(f"File size: {file_size} bytes") print() - self.analyzer.analyze(packets, 1.0, None) + duration = duration_sec if duration_sec is not None and duration_sec > 0 else 1.0 + self.analyzer.analyze(packets, duration, None) return 0 except OSError as e: print(f"Error reading PCAP file: {e}") @@ -727,6 +736,8 @@ class PacketCapture: if not await self._test_capture_async(interface): return 1 + if self.config.tcpdump_capture: + return await self._capture_with_tcpdump_async(interface, duration) return await self._main_capture_async(interface, duration) finally: await monitor.stop() @@ -868,10 +879,77 @@ class PacketCapture: traceback.print_exc() return None + async def _capture_with_tcpdump_async(self, interface, duration): + """Capture using tcpdump (full line rate), then analyze with scapy. + Use this for high packet rates where scapy sniff() would drop packets. + """ + print(f"\n=== Capturing with tcpdump ({duration} seconds) ===") + print("(tcpdump captures at line rate; analysis runs afterward)\n") + + keep = self.config.keep_pcap + if keep: + pcap_f = tempfile.NamedTemporaryFile(delete=False, suffix='.pcap', prefix='wifi_monitor_') + pcap_path = pcap_f.name + pcap_f.close() + print(f"Output: {pcap_path}") + else: + pcap_f = tempfile.NamedTemporaryFile(delete=False, suffix='.pcap', prefix='wifi_monitor_') + pcap_path = pcap_f.name + pcap_f.close() + + try: + proc = await asyncio.create_subprocess_exec( + "tcpdump", + "-i", interface, + "-w", pcap_path, + "-n", + stdout=asyncio.subprocess.DEVNULL, + stderr=asyncio.subprocess.PIPE + ) + await asyncio.sleep(duration) + try: + proc.terminate() + await asyncio.wait_for(proc.wait(), timeout=2.0) + except asyncio.TimeoutError: + proc.kill() + await proc.wait() + + if not os.path.isfile(pcap_path) or os.path.getsize(pcap_path) == 0: + print("Error: tcpdump produced no capture file or empty file.") + return 1 + + packets = rdpcap(pcap_path) + print(f"Captured {len(packets)} packets. Analyzing...\n") + self.analyzer.analyze(packets, duration, None) + + if keep: + print(f"Kept pcap: {pcap_path}") + print(f" (Use: python3 wifi_monitor.py {pcap_path} to re-analyze)") + else: + try: + os.unlink(pcap_path) + except OSError: + pass + return 0 + except FileNotFoundError: + print("Error: tcpdump not found. Install tcpdump or use default scapy capture.") + return 1 + except Exception as e: + print(f"Error during tcpdump capture: {e}") + import traceback + traceback.print_exc() + if not keep and os.path.isfile(pcap_path): + try: + os.unlink(pcap_path) + except OSError: + pass + return 1 + async def _main_capture_async(self, interface, duration): """Perform the main packet capture (async).""" print(f"\n=== Starting scapy capture ({duration} seconds) ===") print("Press Ctrl+C to stop early\n") + print("Note: At high packet rates scapy may drop packets. Use --tcpdump-capture (-T) for full-rate capture.\n") if self.config.full_packet: print("Capturing full packets...") @@ -978,6 +1056,9 @@ Examples: # Save captured packets to pcap file: sudo python3 wifi_monitor.py -i wlan0 -c 36 -t 10 --keep-pcap + # High-rate traffic (e.g. iperf): capture with tcpdump to avoid drops: + sudo python3 wifi_monitor.py -i wlan0 -c 11 -t 10 --tcpdump-capture + # Analyze existing pcap file: python3 wifi_monitor.py --pcap /tmp/capture.pcap python3 wifi_monitor.py /tmp/capture.pcap # Also supported @@ -1024,6 +1105,11 @@ Examples: action='store_true', help='Capture full packets (default: header only, not supported for live capture)' ) + parser.add_argument( + '--tcpdump-capture', '-T', + action='store_true', + help='Use tcpdump for capture (recommended for high packet rates; avoids scapy drops)' + ) # Backward compatibility: if first positional arg looks like a pcap file, use it args, unknown = parser.parse_known_args() @@ -1041,6 +1127,7 @@ Examples: # Apply flags to config self.config.keep_pcap = args.keep_pcap self.config.full_packet = args.full_packet + self.config.tcpdump_capture = args.tcpdump_capture # Determine mode if args.pcap: