Repository: secdev/scapy

Issue:
sendp can enter a busy loop
#### Brief description

Recent version of scapy (>= 2.4.3rc2) can enter a busy loop in `scapy.arch.linux._flush_fd`. I can repro on scapy 2.4.4 but not 2.4.0. I have had this script running fine for ages on 2.4.0. With no 

This reproduces on hosts having a lot of traffic. I have this issue reproducing in an environment where we use scapy to send gratuitous arp/ND's neighbor advertisement messages which calls [`scapy.sendrecv.sendp`](https://github.com/secdev/scapy/blob/v2.4.4/scapy/sendrecv.py#L355).

This issue was likely introduced in 009b3229dbfcb3dfd1876c35170bad96b2a907f7 when [this line got removed](https://github.com/secdev/scapy/commit/009b3229dbfcb3dfd1876c35170bad96b2a907f7#diff-acf5ae2bc4119bd174ec794dc144ea76580ad720d0442524167739d73039b074L451).

MonkeyPatching `_flush_fd` to force setting `RCVBUF` to 0 before calling the real `_flush_fd` does fix the issue.

Based on the commit message, it is not clear to me why RCVBUF is not set to 0 anymore and I am not sure if putting this line back would cause issue in other code path. If there is no obvious side effect, I think this issue can be closed by simply setting the buffer to 0 again.


#### Environment

-   Scapy version: `v2.4.4`
-   Python version: `3.7`
-   Operating System: `Linux 5.6`


#### How to reproduce

Taking a host that has constant network traffic, calling this code:
```python
packet = Ether(src=mac, dst=MAC_BCAST)
packet /= ARP(op=2, psrc=str(vip), pdst=str(vip), hwsrc=mac, hwdst=mac)
sendp(packet, iface=iface, count=npkts)
```
will get the process spinning at 100%. Profiling reveals that the time is spent in 
sendp -> L2socker.__init__ -> _flush_fd


#### Actual result

python process is busylooping in `flush_fd`
#### Expected result

`flush_fd` should exit rather quickly

#### Related resources

Here are some of the troubleshooting that led to the conclusion detailed in the description.

stracing the process shows that process is consistently using going from select and read syscalls in a loop.

```
...
...
select(13, [12], [], [], {tv_sec=0, tv_usec=0}) = 1 (in [12], left {tv_sec=0, tv_usec=0})
read(12, "raw_ethernet_packet_here_1"..., 65535) = 1294
select(13, [12], [], [], {tv_sec=0, tv_usec=0}) = 1 (in [12], left {tv_sec=0, tv_usec=0})
read(12, "\raw_ethernet_packet_here_2"..., 65535) = 1294
select(13, [12], [], [], {tv_sec=0, tv_usec=0}) = 1 (in [12], left {tv_sec=0, tv_usec=0})
read(12, "raw_ethernet_packet_here_3"..., 65535) = 1294
select(13, [12], [], [], {tv_sec=0, tv_usec=0}) = 1 (in [12], left {tv_sec=0, tv_usec=0})
read(12, "raw_ethernet_packet_here_4"..., 65535) = 1294
...
...
```

The `fd` is a RAW_SOCKET, the one created by L2socket.__init__

```
python3.7  88 root   12u     pack 132257917      0t0        ALL type=SOCK_RAW
```

The issue is reproducing consistently on a host when using 2.4.4, but is fine using 2.4.0. Looking at `_flush_fd`, we see that the while loop will never exit as long as the buffer is already filed with something when `select` is called.
Looking at the difference between `scapy.arch.linux.L2socket.__init__` in [v2.4.0](https://github.com/secdev/scapy/blob/ae348f8611529fe20dcd703e2bd37f20322c4535/scapy/arch/linux.py#L498) and [v2.4.4](https://github.com/secdev/scapy/blob/95ba5b8504152a1f820bbe679ccf03668cb5118f/scapy/arch/linux.py#L386), 

One of the main difference, when it comes to reading, is that the `RCVBUF` is not set to 0 prior to calling `_flush_fd` anymore.

As a workaround, using the following monkey patching technique:

```
+old_flush_fd = scapy.arch.linux._flush_fd
+
+
+def new_flush_fd(fd):
+    rcvbuf = fd.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF)
+    log.info(
+        "Monkey patching scapy.arch.linux._flush_fd. SO_RCVBUF was {}, setting to 0".format(
+            rcvbuf
+        )
+    )
+    fd.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 0)
+    return old_flush_fd(fd)
+
+
+scapy.arch.linux._flush_fd = new_flush_fd
```

fixes the issue on those hosts where the issue happened.




Discussion:

Resolving patch:
diff --git a/scapy/arch/linux.py b/scapy/arch/linux.py
--- a/scapy/arch/linux.py
+++ b/scapy/arch/linux.py
@@ -445,6 +445,7 @@ def __init__(self, iface=None, type=ETH_P_ALL, promisc=None, filter=None,
             )
         self.ins = socket.socket(
             socket.AF_PACKET, socket.SOCK_RAW, socket.htons(type))
+        self.ins.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 0)
         if not nofilter:
             if conf.except_filter:
                 if filter:


Test patch: