Skip to content

Instantly share code, notes, and snippets.

@majek
Last active July 12, 2023 14:07
Show Gist options
  • Save majek/5e8fd12e7a1663cea63877920fe86f18 to your computer and use it in GitHub Desktop.
Save majek/5e8fd12e7a1663cea63877920fe86f18 to your computer and use it in GitHub Desktop.
udp-gro-forwarding-bug.py
#!/bin/env python3
'''
$ unshare -Urn python3 udp-gro-forwarding-bug.py
15:25:33.947820 tap0 In IP 10.0.0.2.55892 > 1.1.1.1.5021: UDP, length 4000
15:25:33.947847 lo P IP 10.0.0.2.55892 > 1.1.1.1.5021: UDP, bad length 4000 > 1392
15:25:33.947849 lo P IP 10.0.0.2.43690 > 1.1.1.1.43690: UDP, bad length 43682 > 1392
15:25:33.947852 lo P IP 10.0.0.2.43690 > 1.1.1.1.43690: UDP, bad length 43682 > 1200
'''
import os
import fcntl
import struct
import binascii
import subprocess
import sys
import time
import signal
tapname = "tap0"
ethname = "lo"
os.system("ip l set %s up" % ethname)
os.system("ip tuntap del mode tap %s" % tapname)
os.system("ip tuntap add mode tap name %s" % tapname)
os.system("ip l set %s up" % tapname)
os.system("ip l set dev %s addr 00:44:33:22:11:00" % tapname)
os.system("ip a add 10.0.0.1/24 dev %s" % tapname)
os.system("ip neigh add 10.0.0.2 dev %s lladdr 00:11:22:33:44:55" % tapname)
os.system("tc qdisc add dev %s ingress" % tapname)
os.system("tc filter add dev %s ingress pref 10 protocol ip u32 match ip src 10.0.0.2 action mirred egress redirect dev %s" % (tapname, ethname))
# see https://github.com/the-tcpdump-group/tcpdump/issues/1062
# some versions of tcpdump refuse to work under `unshare`
pid = subprocess.Popen(("strace -e trace=setgroups -o /dev/null -e inject=setgroups:retval=0 tcpdump -ln -Zroot -q -iany -c4 host 10.0.0.2" % ()).split(), stdout=sys.stdout)
pid.poll()
time.sleep(0.3)
TUNSETIFF = 0x400454ca
TUNSETVNETHDRSZ = 0x400454d8
TUNSETOFFLOAD = 0x400454d0
IFF_TAP = 0x0002
IFF_NO_PI = 0x1000
IFF_VNET_HDR = 0x4000
TUN_F_CSUM = 1
TUN_F_TSO4 = 2
TUN_F_TSO6 = 4
TUN_F_USO4 = 0x20
TUN_F_USO6 = 0x40
tap_fd = os.open("/dev/net/tun", os.O_RDWR)
ifs = fcntl.ioctl(tap_fd,
TUNSETIFF,
struct.pack("16sH",
tapname.encode(),
IFF_TAP | IFF_NO_PI | IFF_VNET_HDR))
ifname = ifs[:16].strip(b"\x00")
fcntl.ioctl(tap_fd, TUNSETVNETHDRSZ, struct.pack("I", 12))
off_flags = TUN_F_CSUM | TUN_F_TSO4 | TUN_F_TSO6 | TUN_F_USO4 | TUN_F_USO6
fcntl.ioctl(tap_fd, TUNSETOFFLOAD, off_flags);
'''
struct virtio_net_hdr {
#define VIRTIO_NET_HDR_F_NEEDS_CSUM 1
#define VIRTIO_NET_HDR_F_DATA_VALID 2
#define VIRTIO_NET_HDR_F_RSC_INFO 4
uint8_t flags;
#define VIRTIO_NET_HDR_GSO_NONE 0
#define VIRTIO_NET_HDR_GSO_TCPV4 1
#define VIRTIO_NET_HDR_GSO_UDP 3
#define VIRTIO_NET_HDR_GSO_TCPV6 4
#define VIRTIO_NET_HDR_GSO_UDP_L4 5
#define VIRTIO_NET_HDR_GSO_ECN 0x80
uint8_t gso_type;
uint16_t hdr_len;
uint16_t gso_size;
uint16_t csum_start;
uint16_t csum_offset;
uint16_t num_buffers;
};
vhdr:
flags=0x1 --> VIRTIO_NET_HDR_F_NEEDS_CSUM
gso_type=0x5 --> VIRTIO_NET_HDR_GSO_UDP_L4
hdr_len = 0x002a --> 42 (14+20+8)
gso_size = 0x0578 --> 1400
csum_start = 0x0022 --> offset 34
csum_offset = 0x0006 --> offset 6
num_buffers = 0
'''
payload = '''
01 05 2a00 7805 2200 0600 0000
004433221100 001122334455 0800
45000fbc 52ce0000 40110c60
0a000002 01010101
da54 139d 0fa8e6a8
'''
payload = binascii.a2b_hex(''.join(payload.split())) + (b'\xaa' * 4000)
os.write(tap_fd, payload)
time.sleep(0.1)
pid.send_signal(signal.SIGINT)
pid.send_signal(signal.SIGINT)
pid.wait()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment