The branch main has been updated by kp: URL: https://cgit.FreeBSD.org/src/commit/?id=ad591caf2a70d6f3a5f1ba7aff182591afc0cc04
commit ad591caf2a70d6f3a5f1ba7aff182591afc0cc04 Author: Kristof Provost <k...@freebsd.org> AuthorDate: 2025-06-20 09:41:19 +0000 Commit: Kristof Provost <k...@freebsd.org> CommitDate: 2025-06-26 13:11:01 +0000 pf: decrement TTL in pf_route(6)() When pf(4) forwards incoming packets with route-to or reply-to, decrement the time-to-live or hop-limit field to prevent routing loops. Sending an ICMP time exceeded error makes traceroute work. For outgoing packets ip_forward() has already done this. OK visa@ sashan@ Add a test case for the above, and fix the nat64 tests to account for the nat64 router now decrementing the TTL. Obtained from: OpenBSD, bluhm <bl...@openbsd.org>, 18421856bb Sponsored by: Rubicon Communications, LLC ("Netgate") --- sys/netpfil/pf/pf.c | 24 +++++++++++++++++++- tests/sys/netpfil/pf/nat64.py | 10 ++++----- tests/sys/netpfil/pf/route_to.sh | 47 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 6 deletions(-) diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c index cdf48fc4d60a..a40e1744cbc8 100644 --- a/sys/netpfil/pf/pf.c +++ b/sys/netpfil/pf/pf.c @@ -8949,7 +8949,18 @@ pf_route(struct pf_krule *r, struct ifnet *oifp, dst->sin_len = sizeof(struct sockaddr_in); dst->sin_addr.s_addr = pd->act.rt_addr.v4.s_addr; - if (s != NULL){ + if (pd->dir == PF_IN) { + if (ip->ip_ttl <= IPTTLDEC) { + if (r->rt != PF_DUPTO) + pf_send_icmp(m0, ICMP_TIMXCEED, + ICMP_TIMXCEED_INTRANS, 0, pd->af, r, + pd->act.rtableid); + goto bad_locked; + } + ip->ip_ttl -= IPTTLDEC; + } + + if (s != NULL) { if (ifp == NULL && (pd->af != pd->naf)) { /* We're in the AFTO case. Do a route lookup. */ const struct nhop_object *nh; @@ -9231,6 +9242,17 @@ pf_route6(struct pf_krule *r, struct ifnet *oifp, dst.sin6_len = sizeof(dst); PF_ACPY((struct pf_addr *)&dst.sin6_addr, &pd->act.rt_addr, AF_INET6); + if (pd->dir == PF_IN) { + if (ip6->ip6_hlim <= IPV6_HLIMDEC) { + if (r->rt != PF_DUPTO) + pf_send_icmp(m0, ICMP6_TIME_EXCEEDED, + ICMP6_TIME_EXCEED_TRANSIT, 0, pd->af, r, + pd->act.rtableid); + goto bad_locked; + } + ip6->ip6_hlim -= IPV6_HLIMDEC; + } + if (s != NULL) { if (ifp == NULL && (pd->af != pd->naf)) { const struct nhop_object *nh; diff --git a/tests/sys/netpfil/pf/nat64.py b/tests/sys/netpfil/pf/nat64.py index 32fd8f4245a1..adae2489ce5e 100644 --- a/tests/sys/netpfil/pf/nat64.py +++ b/tests/sys/netpfil/pf/nat64.py @@ -178,7 +178,7 @@ class TestNAT64(VnetTestTemplate): # Check the hop limit ip6 = reply.getlayer(sp.IPv6) - assert ip6.hlim == 62 + assert ip6.hlim == 61 @pytest.mark.require_user("root") @pytest.mark.require_progs(["scapy"]) @@ -236,7 +236,7 @@ class TestNAT64(VnetTestTemplate): ToolsHelper.print_output("/sbin/route -6 add default 2001:db8::1") import scapy.all as sp - packet = sp.IPv6(dst="64:ff9b::198.51.100.2", hlim=1) \ + packet = sp.IPv6(dst="64:ff9b::198.51.100.2", hlim=2) \ / sp.TCP(sport=1111, dport=2222, flags="S") self.common_test_source_addr(packet) @@ -246,7 +246,7 @@ class TestNAT64(VnetTestTemplate): ToolsHelper.print_output("/sbin/route -6 add default 2001:db8::1") import scapy.all as sp - packet = sp.IPv6(dst="64:ff9b::198.51.100.2", hlim=1) \ + packet = sp.IPv6(dst="64:ff9b::198.51.100.2", hlim=2) \ / sp.UDP(sport=1111, dport=2222) / sp.Raw("foo") self.common_test_source_addr(packet) @@ -256,7 +256,7 @@ class TestNAT64(VnetTestTemplate): ToolsHelper.print_output("/sbin/route -6 add default 2001:db8::1") import scapy.all as sp - packet = sp.IPv6(dst="64:ff9b::198.51.100.2", hlim=1) \ + packet = sp.IPv6(dst="64:ff9b::198.51.100.2", hlim=2) \ / sp.SCTP(sport=1111, dport=2222) \ / sp.SCTPChunkInit(init_tag=1, n_in_streams=1, n_out_streams=1, a_rwnd=1500) self.common_test_source_addr(packet) @@ -267,7 +267,7 @@ class TestNAT64(VnetTestTemplate): ToolsHelper.print_output("/sbin/route -6 add default 2001:db8::1") import scapy.all as sp - packet = sp.IPv6(dst="64:ff9b::198.51.100.2", hlim=1) \ + packet = sp.IPv6(dst="64:ff9b::198.51.100.2", hlim=2) \ / sp.ICMPv6EchoRequest() / sp.Raw("foo") reply = self.common_test_source_addr(packet) icmp = reply.getlayer(sp.ICMPv6EchoRequest) diff --git a/tests/sys/netpfil/pf/route_to.sh b/tests/sys/netpfil/pf/route_to.sh index 0354d1f59306..5c0d355b8ea1 100644 --- a/tests/sys/netpfil/pf/route_to.sh +++ b/tests/sys/netpfil/pf/route_to.sh @@ -813,6 +813,52 @@ sticky_cleanup() pft_cleanup } +atf_test_case "ttl" "cleanup" +ttl_head() +{ + atf_set descr 'Ensure we decrement TTL on route-to' + atf_set require.user root +} + +ttl_body() +{ + pft_init + + epair_one=$(vnet_mkepair) + epair_two=$(vnet_mkepair) + ifconfig ${epair_one}b 192.0.2.2/24 up + route add default 192.0.2.1 + + vnet_mkjail alcatraz ${epair_one}a ${epair_two}a + jexec alcatraz ifconfig ${epair_one}a 192.0.2.1/24 up + jexec alcatraz ifconfig ${epair_two}a 198.51.100.1/24 up + jexec alcatraz sysctl net.inet.ip.forwarding=1 + + vnet_mkjail singsing ${epair_two}b + jexec singsing ifconfig ${epair_two}b 198.51.100.2/24 up + jexec singsing route add default 198.51.100.1 + + # Sanity check + atf_check -s exit:0 -o ignore \ + ping -c 3 198.51.100.2 + + jexec alcatraz pfctl -e + pft_set_rules alcatraz \ + "pass out" \ + "pass in route-to (${epair_two}a 198.51.100.2)" + + atf_check -s exit:0 -o ignore \ + ping -c 3 198.51.100.2 + + atf_check -s exit:2 -o ignore \ + ping -m 1 -c 3 198.51.100.2 +} + +ttl_cleanup() +{ + pft_cleanup +} + atf_init_test_cases() { atf_add_test_case "v4" @@ -830,4 +876,5 @@ atf_init_test_cases() atf_add_test_case "dummynet_frag" atf_add_test_case "dummynet_double" atf_add_test_case "sticky" + atf_add_test_case "ttl" }