Skip to content

Commit 74df1a4

Browse files
T3pp31cursoragent
andcommitted
Fix sniff timeout when libpcap select returns readable with no packets
Use nonblock_recv() in timed sniff loops when the socket supports it, so a blocking pcap_next_ex cannot stall past the sniff timeout (#4590). Regression test simulates libpcap reporting the fd as readable while nonblock_recv returns no BPF-matched packet. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 66ef96a commit 74df1a4

4 files changed

Lines changed: 59 additions & 7 deletions

File tree

scapy/contrib/isotp/isotp_utils.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -345,11 +345,14 @@ def __init__(self, *args, **kwargs):
345345
basecls=kwargs.pop("basecls", ISOTP))
346346
super(ISOTPSession, self).__init__(*args, **kwargs)
347347

348-
def recv(self, sock: SuperSocket) -> Iterator[Packet]:
348+
def recv(self, sock: SuperSocket, nonblock: bool = False) -> Iterator[Packet]:
349349
"""
350350
Will be called by sniff() to ask for a packet
351351
"""
352-
pkt = sock.recv()
352+
if nonblock and hasattr(sock, "nonblock_recv"):
353+
pkt = sock.nonblock_recv()
354+
else:
355+
pkt = sock.recv()
353356
if not pkt:
354357
return
355358
self.m.feed(pkt)

scapy/sendrecv.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1336,7 +1336,11 @@ def stop_cb():
13361336
# The session object is passed the socket to call recv() on,
13371337
# and may perform additional processing (ip defrag, etc.)
13381338
try:
1339-
packets = session.recv(s)
1339+
packets = session.recv(
1340+
s,
1341+
nonblock=timeout is not None and
1342+
hasattr(s, "nonblock_recv"),
1343+
)
13401344
# A session can return multiple objects
13411345
for p in packets:
13421346
if lfilter and not lfilter(p):

scapy/sessions.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,14 @@ def process(self, pkt: Packet) -> Optional[Packet]:
5151
return self.supersession.process(pkt)
5252
return pkt
5353

54-
def recv(self, sock: 'SuperSocket') -> Iterator[Packet]:
54+
def recv(self, sock: 'SuperSocket', nonblock: bool = False) -> Iterator[Packet]:
5555
"""
5656
Will be called by sniff() to ask for a packet
5757
"""
58-
pkt = sock.recv()
58+
if nonblock and hasattr(sock, "nonblock_recv"):
59+
pkt = sock.nonblock_recv()
60+
else:
61+
pkt = sock.recv()
5962
if not pkt:
6063
return
6164
pkt = self.process(pkt)
@@ -407,11 +410,14 @@ def process(self,
407410
return pkt
408411
return None
409412

410-
def recv(self, sock: 'SuperSocket') -> Iterator[Packet]:
413+
def recv(self, sock: 'SuperSocket', nonblock: bool = False) -> Iterator[Packet]:
411414
"""
412415
Will be called by sniff() to ask for a packet
413416
"""
414-
pkt = sock.recv(stop_dissection_after=self.stop_dissection_after)
417+
if nonblock and hasattr(sock, "nonblock_recv"):
418+
pkt = sock.nonblock_recv()
419+
else:
420+
pkt = sock.recv(stop_dissection_after=self.stop_dissection_after)
415421
# Now handle TCP reassembly
416422
if self.app:
417423
while pkt is not None:

test/regression.uts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2061,6 +2061,45 @@ o.send(REFPACKET)
20612061
pkts = sniff(opened_socket=[o], timeout=3)
20622062
assert len(pkts) == 10
20632063

2064+
= sniff timeout with select-ready but empty pcap socket (#4590)
2065+
2066+
import time
2067+
from scapy.automaton import ObjectPipe
2068+
from scapy.supersocket import SuperSocket
2069+
2070+
class PcapLikeSocket(SuperSocket):
2071+
def __init__(self):
2072+
self.ins = ObjectPipe(name="pcap_like_socket")
2073+
self.outs = None
2074+
self.recv_called = False
2075+
2076+
def recv(self, x=65535, **kwargs):
2077+
self.recv_called = True
2078+
time.sleep(60)
2079+
return None
2080+
2081+
def nonblock_recv(self, x=65535):
2082+
return None
2083+
2084+
@staticmethod
2085+
def select(sockets, remain=None):
2086+
return list(sockets)
2087+
2088+
def close(self):
2089+
if self.closed:
2090+
return
2091+
self.closed = True
2092+
self.ins.close()
2093+
2094+
sock = PcapLikeSocket()
2095+
t0 = time.monotonic()
2096+
pkts = sniff(opened_socket=sock, timeout=1, count=1)
2097+
elapsed = time.monotonic() - t0
2098+
assert not sock.recv_called, "blocking recv must not be used with timeout"
2099+
assert elapsed < 3, "sniff should stop on timeout, got %.1fs" % elapsed
2100+
assert len(pkts) == 0
2101+
sock.close()
2102+
20642103
= GH issue 3306
20652104
~ netaccess needs_root
20662105

0 commit comments

Comments
 (0)