git: 5d1219378dd5 - main - pf: teach nat64 to handle 0 UDP checksums
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Tue, 17 Dec 2024 10:08:20 UTC
The branch main has been updated by kp: URL: https://cgit.FreeBSD.org/src/commit/?id=5d1219378dd5d9b031926cf7806455f33677792b commit 5d1219378dd5d9b031926cf7806455f33677792b Author: Kristof Provost <kp@FreeBSD.org> AuthorDate: 2024-12-16 10:23:59 +0000 Commit: Kristof Provost <kp@FreeBSD.org> CommitDate: 2024-12-17 10:07:19 +0000 pf: teach nat64 to handle 0 UDP checksums For IPv4 it's valid for a UDP checksum to be 0 (i.e. no checksum). This isn't the case for IPv6, so if we translate a UDP packet from IPv4 to IPv6 we need to ensure that the checksum is calculated. Add a test case to verify this. Rework the server jail so it can listen for TCP and UDP packets at the same time. Sponsored by: Rubicon Communications, LLC ("Netgate") --- sys/netpfil/pf/pf.c | 12 +++++++++ tests/sys/netpfil/pf/nat64.py | 61 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c index 9128562fd71c..f2e19693b863 100644 --- a/sys/netpfil/pf/pf.c +++ b/sys/netpfil/pf/pf.c @@ -3469,6 +3469,7 @@ pf_translate_af(struct pf_pdesc *pd) ip4->ip_dst = pd->ndaddr.v4; pd->src = (struct pf_addr *)&ip4->ip_src; pd->dst = (struct pf_addr *)&ip4->ip_dst; + pd->off = sizeof(struct ip); break; case AF_INET6: ip6 = mtod(pd->m, struct ip6_hdr *); @@ -3485,6 +3486,7 @@ pf_translate_af(struct pf_pdesc *pd) ip6->ip6_dst = pd->ndaddr.v6; pd->src = (struct pf_addr *)&ip6->ip6_src; pd->dst = (struct pf_addr *)&ip6->ip6_dst; + pd->off = sizeof(struct ip6_hdr); /* * If we're dealing with a reassembled packet we need to adjust @@ -9094,6 +9096,16 @@ pf_route6(struct mbuf **m, struct pf_krule *r, struct ifnet *oifp, PF_STATE_UNLOCK(s); } + if (pd->af != pd->naf) { + struct udphdr *uh = &pd->hdr.udp; + + if (pd->proto == IPPROTO_UDP && uh->uh_sum == 0) { + uh->uh_sum = in6_cksum_pseudo(ip6, + ntohs(uh->uh_ulen), IPPROTO_UDP, 0); + m_copyback(m0, pd->off, sizeof(*uh), pd->hdr.any); + } + } + if (ifp == NULL) { m0 = *m; *m = NULL; diff --git a/tests/sys/netpfil/pf/nat64.py b/tests/sys/netpfil/pf/nat64.py index eeddd5118168..64ec5ae15262 100644 --- a/tests/sys/netpfil/pf/nat64.py +++ b/tests/sys/netpfil/pf/nat64.py @@ -25,6 +25,9 @@ # SUCH DAMAGE. import pytest +import selectors +import socket +import sys from atf_python.sys.net.tools import ToolsHelper from atf_python.sys.net.vnet import VnetTestTemplate @@ -41,7 +44,44 @@ class TestNAT64(VnetTestTemplate): def vnet3_handler(self, vnet): ToolsHelper.print_output("/sbin/sysctl net.inet.ip.forwarding=1") ToolsHelper.print_output("/sbin/sysctl net.inet.ip.ttl=62") - ToolsHelper.print_output("echo foo | nc -l 1234 &") + ToolsHelper.print_output("/sbin/sysctl net.inet.udp.checksum=0") + + sel = selectors.DefaultSelector() + t = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + t.bind(("0.0.0.0", 1234)) + t.setblocking(False) + t.listen() + sel.register(t, selectors.EVENT_READ, data=None) + + u = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + u.bind(("0.0.0.0", 4444)) + u.setblocking(False) + sel.register(u, selectors.EVENT_READ, data="UDP") + + while True: + events = sel.select(timeout=20) + for key, mask in events: + sock = key.fileobj + if key.data is None: + conn, addr = sock.accept() + print(f"Accepted connection from {addr}") + data = types.SimpleNamespace(addr=addr, inb=b"", outb=b"") + events = selectors.EVENT_READ | selectors.EVENT_WRITE + sel.register(conn, events, data=data) + elif key.data == "UDP": + recv_data, addr = sock.recvfrom(1024) + print(f"Received UDP {recv_data} from {addr}") + sock.sendto(b"foo", addr) + else: + if mask & selectors.EVENT_READ: + recv_data = sock.recv(1024) + print(f"Received TCP {recv_data}") + sock.send(b"foo") + else: + print("Unknown event?") + t.close() + u.close() + return def vnet2_handler(self, vnet): ifname = vnet.iface_alias_map["if1"].name @@ -130,3 +170,22 @@ class TestNAT64(VnetTestTemplate): # Check the hop limit ip6 = reply.getlayer(sp.IPv6) assert ip6.hlim == 62 + + @pytest.mark.require_user("root") + def test_udp_checksum(self): + ToolsHelper.print_output("/sbin/route -6 add default 2001:db8::1") + + import scapy.all as sp + + # Send an outbound UDP packet to establish state + packet = sp.IPv6(dst="64:ff9b::192.0.2.2") \ + / sp.UDP(sport=3333, dport=4444) / sp.Raw("foo") + + # Get a reply + # We'll send the reply without UDP checksum on the IPv4 side + # but that's not valid for IPv6, so expect pf to update the checksum. + reply = sp.sr1(packet, timeout=5) + + udp = reply.getlayer(sp.UDP) + assert udp + assert udp.chksum != 0