aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKristof Provost <kp@FreeBSD.org>2024-12-16 10:23:59 +0000
committerKristof Provost <kp@FreeBSD.org>2024-12-17 10:07:19 +0000
commit5d1219378dd5d9b031926cf7806455f33677792b (patch)
tree5cb432c6698ca56722a0db944e708d776345af68
parent32cac604487b4c6a8588c5df7641bdb5b452711f (diff)
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")
-rw-r--r--sys/netpfil/pf/pf.c12
-rw-r--r--tests/sys/netpfil/pf/nat64.py61
2 files changed, 72 insertions, 1 deletions
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