@@ -22,9 +22,6 @@ use crate::{PortState, ScanResult, ScannedPort, TimingBackend, TimingRequest, Ti
2222pub struct ScanEngineConfig {
2323 /// Network interface for XDP (None = auto-detect from /proc/net/route).
2424 pub interface : Option < String > ,
25- /// When true, use raw socket TX only (no AF_XDP redirect).
26- /// BPF timestamps packets but XDP_PASS lets them through to the kernel.
27- pub passthrough : bool ,
2825}
2926
3027/// Scanning engine. Shared via `Arc<Engine>` between discovery and timing loops.
@@ -44,8 +41,6 @@ pub struct ScanEngine {
4441 scanner : Arc < Mutex < SynScanner > > ,
4542 interface : String ,
4643 backend : TimingBackend ,
47- #[ allow( dead_code) ]
48- passthrough : bool ,
4944}
5045
5146// ─────────────────────────────────────────────────────────────────────────────
@@ -108,10 +103,6 @@ impl Engine {
108103 /// - SYN scanner creation fails (no IPv4 on interface, raw socket error)
109104 #[ cfg( target_os = "linux" ) ]
110105 pub fn new ( config : ScanEngineConfig ) -> Result < Self , String > {
111- if config. passthrough {
112- tracing:: info!( "passthrough mode: using raw socket TX, no AF_XDP redirect" ) ;
113- }
114-
115106 let ( backend, collector) = crate :: timing:: detect_timing_backend ( & config. interface )
116107 . map_err ( |e| {
117108 format ! (
@@ -127,12 +118,11 @@ impl Engine {
127118 tracing:: info!(
128119 backend = %backend,
129120 interface = %iface,
130- passthrough = config. passthrough,
131121 "BPF timing backend initialised"
132122 ) ;
133123
134124 let bpf = Arc :: new ( Mutex :: new ( collector) ) ;
135- let scanner = Self :: create_scanner ( & iface, & bpf, config . passthrough ) . ok_or_else ( || {
125+ let scanner = Self :: create_scanner ( & iface, & bpf) . ok_or_else ( || {
136126 format ! (
137127 "SYN scanner creation failed on interface '{iface}'. \
138128 Check that the interface has an IPv4 address and raw socket permissions."
@@ -144,7 +134,6 @@ impl Engine {
144134 scanner,
145135 interface : iface,
146136 backend,
147- passthrough : config. passthrough ,
148137 } ) )
149138 }
150139
@@ -158,18 +147,13 @@ impl Engine {
158147 }
159148 }
160149
161- /// Create a `SynScanner` with the appropriate sender.
162- ///
163- /// In passthrough mode, uses `RawSocketSender` only (no AF_XDP redirect).
164- /// Otherwise creates a `HybridSender` and registers its AF_XDP fd in xsk_map.
150+ /// Create a `SynScanner` with raw socket TX + BPF timestamps.
165151 #[ cfg( target_os = "linux" ) ]
166152 fn create_scanner (
167153 iface : & str ,
168- bpf : & Arc < Mutex < BpfTimingCollector > > ,
169- passthrough : bool ,
154+ _bpf : & Arc < Mutex < BpfTimingCollector > > ,
170155 ) -> Option < Arc < Mutex < SynScanner > > > {
171156 use crate :: scanner:: afxdp_sender:: AfXdpSend ;
172- use crate :: scanner:: hybrid_sender:: HybridSender ;
173157 use crate :: scanner:: raw_socket_sender:: RawSocketSender ;
174158 use crate :: scanner:: syn_sender:: interface_source_ip;
175159
@@ -181,39 +165,11 @@ impl Engine {
181165 }
182166 } ;
183167
184- let xdp_sender: Box < dyn AfXdpSend > = if passthrough {
185- match RawSocketSender :: new ( src_ip, Some ( iface) ) {
186- Ok ( s) => Box :: new ( s) ,
187- Err ( e) => {
188- tracing:: error!( error = %e, "raw socket sender failed" ) ;
189- return None ;
190- }
191- }
192- } else {
193- match HybridSender :: new ( iface, 0 , src_ip) {
194- Ok ( sender) => {
195- // Register AF_XDP socket in BPF xsk_map.
196- // try_lock() is safe here — called at init, no contention.
197- if let Ok ( bpf_guard) = bpf. try_lock ( ) {
198- if let Err ( e) = bpf_guard. register_xsk_fd ( sender. fd ( ) ) {
199- tracing:: warn!( error = %e, "xsk_map registration failed" ) ;
200- }
201- }
202- Box :: new ( sender)
203- }
204- Err ( e) => {
205- tracing:: warn!(
206- error = %e,
207- "hybrid sender unavailable — falling back to raw socket TX"
208- ) ;
209- match RawSocketSender :: new ( src_ip, Some ( iface) ) {
210- Ok ( s) => Box :: new ( s) ,
211- Err ( e) => {
212- tracing:: error!( error = %e, "raw socket fallback also failed" ) ;
213- return None ;
214- }
215- }
216- }
168+ let xdp_sender: Box < dyn AfXdpSend > = match RawSocketSender :: new ( src_ip, Some ( iface) ) {
169+ Ok ( s) => Box :: new ( s) ,
170+ Err ( e) => {
171+ tracing:: error!( error = %e, "raw socket sender failed" ) ;
172+ return None ;
217173 }
218174 } ;
219175
@@ -331,21 +287,19 @@ impl ScanEngine {
331287 let timeout = Duration :: from_millis ( timeout_ms as u64 ) ;
332288 let target_ip_u32 = u32:: from_be_bytes ( target_ip. octets ( ) ) ;
333289
334- // Apply per-request pacing to the shared scanner. We hold the lock
335- // for the entire batch-send phase, so no concurrent access sees the
336- // changed profile. Restored to Aggressive after sending.
337- let mut scanner_guard = self . scanner . lock ( ) . await ;
338- let original_profile = scanner_guard. profile ( ) . clone ( ) ;
290+ // Build per-request stealth profile (no shared state mutation).
291+ // Scanner lock is held per-batch, not per-scan, so concurrent
292+ // discovery requests can interleave their batches.
339293 let mut scan_profile = StealthProfile :: linux_6x_default ( ) ;
340294 request. pacing . apply_to ( & mut scan_profile) ;
341- scanner_guard. set_profile ( scan_profile) ;
342295
343296 let mut all_probes = Vec :: new ( ) ;
344297 for batch in ports. chunks ( batch_size) {
298+ let mut scanner_guard = self . scanner . lock ( ) . await ;
299+ scanner_guard. set_profile ( scan_profile. clone ( ) ) ;
345300 let result = match scanner_guard. send_syn_batch ( target_ip, batch) {
346301 Ok ( r) => r,
347302 Err ( e) => {
348- scanner_guard. set_profile ( original_profile) ;
349303 drop ( scanner_guard) ;
350304 return ScanResult {
351305 request_id : request. request_id ,
@@ -363,12 +317,9 @@ impl ScanEngine {
363317
364318 // Drain AF_XDP RX ring between batches to prevent overflow
365319 scanner_guard. poll_rx ( 0 ) ;
320+ drop ( scanner_guard) ;
366321 }
367322
368- // Restore Aggressive profile for timing probes
369- scanner_guard. set_profile ( original_profile) ;
370- drop ( scanner_guard) ;
371-
372323 // Early-return polling: check every 5ms if all probes have responses.
373324 // Falls back to full timeout as the deadline.
374325 {
@@ -736,7 +687,6 @@ mod tests {
736687 // Either way, it must not panic.
737688 let result = Engine :: new ( ScanEngineConfig {
738689 interface : None ,
739- passthrough : true ,
740690 } ) ;
741691 match result {
742692 Ok ( engine) => {
@@ -757,7 +707,6 @@ mod tests {
757707 fn test_engine_new_returns_connect_only_on_non_linux ( ) {
758708 let engine = Engine :: new ( ScanEngineConfig {
759709 interface : None ,
760- passthrough : true ,
761710 } ) ;
762711 assert_eq ! ( engine. backend_str( ) , "connect" ) ;
763712 }
0 commit comments