Repository: secdev/scapy

Issue:
Add stop_filter parameter to SndRcvHandler
### Brief description

`SndRcvHandler` has a field **multi** to enable receiving multiple answer to a single `Packet`. Currently, if **multi** is set, the only way to stop sniffing for more answers is to specify a **timeout** and wait for it to expire, or to give a KeyboardInterrupt. Waiting for a specified timeout is not very feasibly for a few reasons: the timeout to receive an answer may not always come in a reliably consistent amount of time, and waiting for a timeout may result in decreased responsiveness for applications using Scapy. 

`AsyncSniffer` already has a field **stop_filter** for a callback fn which takes a Packet and returns True if the `AsyncSniffer` should be stopped early.

I'm proposing adding a field **stop_filter** to `SndRcvHandler.__init__()` to allow `sr()` related functions to pass the **stop_filter** to the `AsyncSniffer` in `SndRcvHandler()._sndrcv_rcv()`

### Scapy version

2.5.0

### Python version

3.12.2

### Operating system

Windows 10

### Additional environment information

_No response_

### How to reproduce

```python
pkt: Packet = ...
sr(pkt, multi=1, timeout=1)
```

### Actual result

_No response_

### Expected result

_No response_

### Related resources

_No response_


Discussion:

Resolving patch:
diff --git a/scapy/sendrecv.py b/scapy/sendrecv.py
--- a/scapy/sendrecv.py
+++ b/scapy/sendrecv.py
@@ -93,6 +93,8 @@ class debug:
     :param threaded: if True, packets will be sent in an individual thread
     :param session: a flow decoder used to handle stream of packets
     :param chainEX: if True, exceptions during send will be forwarded
+    :param stop_filter: Python function applied to each packet to determine if
+        we have to stop the capture after this packet.
     """
 
 
@@ -127,7 +129,8 @@ def __init__(self,
                  _flood=None,  # type: Optional[_FloodGenerator]
                  threaded=False,  # type: bool
                  session=None,  # type: Optional[_GlobSessionType]
-                 chainEX=False  # type: bool
+                 chainEX=False,  # type: bool
+                 stop_filter=None  # type: Optional[Callable[[Packet], bool]]
                  ):
         # type: (...) -> None
         # Instantiate all arguments
@@ -148,6 +151,7 @@ def __init__(self,
         self.timeout = timeout
         self.session = session
         self.chainEX = chainEX
+        self.stop_filter = stop_filter
         self._send_done = False
         self.notans = 0
         self.noans = 0
@@ -294,7 +298,7 @@ def _process_packet(self, r):
                         sentpkt._answered = 1
                     break
         if self._send_done and self.noans >= self.notans and not self.multi:
-            if self.sniffer:
+            if self.sniffer and self.sniffer.running:
                 self.sniffer.stop(join=False)
         if not ok:
             if self.verbose > 1:
@@ -315,6 +319,7 @@ def _sndrcv_rcv(self, callback):
                 store=False,
                 opened_socket=self.rcv_pks,
                 session=self.session,
+                stop_filter=self.stop_filter,
                 started_callback=callback,
                 chainCC=self.chainCC,
             )


Test patch: