Skip to content

Commit 6cc9845

Browse files
T3pp31cursoragent
andcommitted
Fix AsyncSniffer not stopping promptly when timeout is set
Always add a control ObjectPipe so stop() can wake select() on nonblocking backends (e.g. BPF on macOS). Previously the pipe was only used for blocking sockets, leaving the sniff thread blocked for the full timeout after stop(join=False). Fixes #4890 AI-Assisted: yes [Cursor] Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 66ef96a commit 6cc9845

2 files changed

Lines changed: 47 additions & 20 deletions

File tree

scapy/sendrecv.py

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1286,32 +1286,22 @@ def _run(self,
12861286
# Get select information from the sockets
12871287
_main_socket = next(iter(sniff_sockets))
12881288
select_func = _main_socket.select
1289-
nonblocking_socket = getattr(_main_socket, "nonblocking_socket", False)
12901289
# We check that all sockets use the same select(), or raise a warning
12911290
if not all(select_func == sock.select for sock in sniff_sockets):
12921291
warning("Warning: inconsistent socket types ! "
12931292
"The used select function "
12941293
"will be the one of the first socket")
12951294

1296-
close_pipe = None # type: Optional[ObjectPipe[None]]
1297-
if not nonblocking_socket:
1298-
# select is blocking: Add special control socket
1299-
from scapy.automaton import ObjectPipe
1300-
close_pipe = ObjectPipe[None]("control_socket")
1301-
sniff_sockets[close_pipe] = "control_socket" # type: ignore
1302-
1303-
def stop_cb():
1304-
# type: () -> None
1305-
if self.running and close_pipe:
1306-
close_pipe.send(None)
1307-
self.continue_sniff = False
1308-
self.stop_cb = stop_cb
1309-
else:
1310-
# select is non blocking
1311-
def stop_cb():
1312-
# type: () -> None
1313-
self.continue_sniff = False
1314-
self.stop_cb = stop_cb
1295+
from scapy.automaton import ObjectPipe
1296+
close_pipe = ObjectPipe[None]("control_socket")
1297+
sniff_sockets[close_pipe] = "control_socket" # type: ignore
1298+
1299+
def stop_cb():
1300+
# type: () -> None
1301+
if self.running and close_pipe:
1302+
close_pipe.send(None)
1303+
self.continue_sniff = False
1304+
self.stop_cb = stop_cb
13151305

13161306
try:
13171307
self.continue_sniff = True

test/regression.uts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1782,6 +1782,43 @@ try:
17821782
except ValueError:
17831783
assert True
17841784

1785+
= AsyncSniffer early stop with timeout on nonblocking socket (#4890)
1786+
1787+
import select as select_mod
1788+
import time
1789+
from scapy.automaton import ObjectPipe
1790+
from scapy.supersocket import SuperSocket
1791+
1792+
class NonblockingSelectSocket(SuperSocket):
1793+
nonblocking_socket = True
1794+
def __init__(self):
1795+
self.ins = ObjectPipe(name="dummy_sniff_socket")
1796+
self.outs = None
1797+
def recv(self, x=65535, **kwargs):
1798+
self.ins.recv()
1799+
return None
1800+
@staticmethod
1801+
def select(sockets, remain=None):
1802+
if remain is None:
1803+
remain = 0.05
1804+
ready, unused_wr, unused_ex = select_mod.select(sockets, [], [], remain)
1805+
return ready
1806+
def close(self):
1807+
if self.closed:
1808+
return
1809+
self.closed = True
1810+
self.ins.close()
1811+
1812+
sock = NonblockingSelectSocket()
1813+
sniffer = AsyncSniffer(opened_socket=sock, timeout=10, count=0)
1814+
sniffer.start()
1815+
time.sleep(0.2)
1816+
sniffer.stop(join=False)
1817+
sniffer.thread.join(timeout=1)
1818+
assert not sniffer.running
1819+
assert not sniffer.thread.is_alive()
1820+
sock.close()
1821+
17851822
= Sending a TCP syn 'forever' at layer 2 and layer 3
17861823
~ netaccess needs_root IP
17871824
def _test():

0 commit comments

Comments
 (0)