Skip to content

Commit a014080

Browse files
ccie18643claude
andcommitted
feat(socket): enforce SO_RCVBUF on the raw receive queue
Extends the UDP SO_RCVBUF enforcement to RawSocket for symmetry: process_raw_packet drops an inbound packet whose payload would push the queued receive bytes past the cap (Linux sk_rcvqueues_full), enforced only when SO_RCVBUF is set; unset stays unbounded. Tests-first: two unit tests in test__runtime__socket__raw__socket.py (over-cap drop, unset-is-unbounded). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent d51abda commit a014080

2 files changed

Lines changed: 72 additions & 0 deletions

File tree

packages/pytcp/pytcp/runtime/socket/raw__socket.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,19 @@ def process_raw_packet(self, packet_rx_md: RawMetadata) -> None:
544544
with self._lock__io:
545545
if self._closed:
546546
return
547+
# SO_RCVBUF enforcement (Linux 'sk_rcvqueues_full'): once the
548+
# operator sets a receive-buffer cap, drop an inbound packet
549+
# whose payload would push the queued bytes past it. Unset
550+
# ('None') stays unbounded. Measured against summed payload
551+
# bytes (the Linux 'truesize' overhead is not modelled).
552+
if self._so_rcvbuf is not None:
553+
queued = sum(len(md.raw__data) for md in self._packet_rx_md)
554+
if queued + len(packet_rx_md.raw__data) > self._so_rcvbuf:
555+
__debug__ and log(
556+
"socket",
557+
f"<g>[{self}]</> - Dropped packet: SO_RCVBUF cap " f"{self._so_rcvbuf} exceeded",
558+
)
559+
return
547560
self._packet_rx_md.append(packet_rx_md)
548561
self._packet_rx_md_ready.release()
549562
self._signal_readable()

packages/pytcp/pytcp/tests/unit/runtime/socket/test__runtime__socket__raw__socket.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@
5252
IPPROTO_IPV6,
5353
IPV6_HOPLIMIT,
5454
IPV6_RECVHOPLIMIT,
55+
SO_RCVBUF,
56+
SOL_SOCKET,
5557
AddressFamily,
5658
SocketType,
5759
gaierror,
@@ -1354,3 +1356,60 @@ def test__raw_socket__non_icmp6_payload_unchanged(self) -> None:
13541356
)
13551357

13561358
self.assertEqual(result, payload, msg="A non-ICMPv6 raw socket must not rewrite the payload.")
1359+
1360+
1361+
class TestRawSocketRcvbuf(_RawSocketTestCase):
1362+
"""
1363+
The 'RawSocket' SO_RCVBUF receive-queue-cap tests.
1364+
"""
1365+
1366+
def _make_md(self) -> RawMetadata:
1367+
"""
1368+
Build a canonical IPv4 'RawMetadata' envelope with a 7-byte
1369+
payload.
1370+
"""
1371+
1372+
return RawMetadata(
1373+
ip__ver=IpVersion.IP4,
1374+
ip__local_address=Ip4Address("10.0.0.1"),
1375+
ip__remote_address=Ip4Address("10.0.0.2"),
1376+
ip__proto=IpProto.ICMP4,
1377+
raw__data=b"payload",
1378+
)
1379+
1380+
def test__raw_socket__so_rcvbuf_drops_packet_over_cap(self) -> None:
1381+
"""
1382+
Ensure that once 'SO_RCVBUF' is set, an inbound packet whose
1383+
payload would push the queued receive bytes past the cap is
1384+
dropped rather than enqueued.
1385+
1386+
Reference: RFC 1122 §4.2.2.16 (receive-buffer bound).
1387+
"""
1388+
1389+
s = RawSocket(family=AddressFamily.INET4, protocol=IpProto.ICMP4)
1390+
s.setsockopt(SOL_SOCKET, SO_RCVBUF, 20)
1391+
s.process_raw_packet(self._make_md())
1392+
s.process_raw_packet(self._make_md())
1393+
s.process_raw_packet(self._make_md())
1394+
self.assertEqual(
1395+
len(s._packet_rx_md),
1396+
2,
1397+
msg="A raw packet over the SO_RCVBUF cap must be dropped, not enqueued.",
1398+
)
1399+
1400+
def test__raw_socket__so_rcvbuf_unset_is_unbounded(self) -> None:
1401+
"""
1402+
Ensure that with 'SO_RCVBUF' unset the raw receive queue is
1403+
unbounded (no default cap).
1404+
1405+
Reference: PyTCP test infrastructure (no RFC clause).
1406+
"""
1407+
1408+
s = RawSocket(family=AddressFamily.INET4, protocol=IpProto.ICMP4)
1409+
for _ in range(50):
1410+
s.process_raw_packet(self._make_md())
1411+
self.assertEqual(
1412+
len(s._packet_rx_md),
1413+
50,
1414+
msg="With SO_RCVBUF unset every raw packet must be enqueued.",
1415+
)

0 commit comments

Comments
 (0)