Skip to content

Commit e0042b3

Browse files
committed
linux-local-udp: small experiment around UDP
1 parent 75ecd88 commit e0042b3

File tree

3 files changed

+105
-0
lines changed

3 files changed

+105
-0
lines changed

lab-linux-local-udp/README.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# UDP local delivery on Linux
2+
3+
With Linux, IP local delivery is done synchronously: loopback xmit method (in
4+
`drivers/net/loopback.c`) is calling `__netif_rx()` to deliver the packet. I
5+
mistakently believed that this would allow reliable delivery with UDP on
6+
loopback, providing a reliable datagram delivery option.
7+
8+
```
9+
# bpftrace -e 'kprobe:udp_queue_rcv_skb { print(kstack); }'
10+
11+
udp_queue_rcv_skb+1
12+
udp_unicast_rcv_skb+118
13+
__udp4_lib_rcv+2721
14+
ip_protocol_deliver_rcu+213
15+
ip_local_deliver_finish+118
16+
ip_local_deliver+103
17+
__netif_receive_skb_one_core+133
18+
process_backlog+135
19+
__napi_poll+43
20+
net_rx_action+820
21+
handle_softirqs+223
22+
do_softirq.part.0+59
23+
__local_bh_enable_ip+96
24+
__dev_queue_xmit+620
25+
ip_finish_output2+738
26+
ip_send_skb+137
27+
udp_send_skb+398
28+
udp_sendmsg+2471
29+
__sys_sendto+467
30+
__x64_sys_sendto+36
31+
do_syscall_64+130
32+
entry_SYSCALL_64_after_hwframe+118
33+
```
34+
35+
This small example shows this is false. One coroutine is reading packets, the
36+
other is sending them a bit faster. We can check with `ss` that we are dropping
37+
packets:
38+
39+
```
40+
# ss -aupen --info --extended --memory sport = :8888
41+
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
42+
UNCONN 2880 0 127.0.0.1:8888 0.0.0.0:* users:(("python3",pid=763,fd=6)) ino:3559 sk:b cgroup:unreachable:1 <->
43+
skmem:(r2880,rb2304,t0,tb212992,f1216,w0,o0,bl0,d6)
44+
```
45+
46+
BTW, I didn't find a way to use `MSG_ERRQUEUE` with asyncio, so I can't detect
47+
the delivery failed. I think this should be possible.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#!/usr/bin/env python3
2+
3+
import asyncio
4+
import socket
5+
import errno
6+
7+
8+
async def udp_receiver(host="127.0.0.1", port=8888):
9+
"""Coroutine to receive UDP packets"""
10+
loop = asyncio.get_event_loop()
11+
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
12+
sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 10)
13+
sock.bind((host, port))
14+
sock.setblocking(False) # Make socket non-blocking for asyncio
15+
16+
print(f"UDP receiver listening on {host}:{port}")
17+
18+
try:
19+
while True:
20+
_, addr = await loop.sock_recvfrom(sock, 1024)
21+
print("r", end='', flush=True)
22+
await asyncio.sleep(10)
23+
finally:
24+
sock.close()
25+
26+
27+
async def udp_sender(target_host="localhost", target_port=8888):
28+
"""Coroutine to send UDP packets periodically"""
29+
loop = asyncio.get_event_loop()
30+
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
31+
sock.setblocking(False)
32+
33+
try:
34+
while True:
35+
await loop.sock_sendto(
36+
sock, b'.', (target_host, target_port)
37+
)
38+
print('.', end='', flush=True)
39+
await asyncio.sleep(1)
40+
finally:
41+
sock.close()
42+
43+
44+
async def main():
45+
print("Starting UDP sender and receiver...")
46+
47+
# Run both coroutines concurrently
48+
await asyncio.gather(udp_receiver(), udp_sender())
49+
50+
51+
asyncio.run(main())

lab-linux-local-udp/setup

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/bin/sh
2+
3+
cd "$(dirname "$(readlink -f "$0")")"
4+
. ../common/lab-setup
5+
6+
MEM=512M spawn vm R network oob
7+
run

0 commit comments

Comments
 (0)