git: ad591caf2a70 - main - pf: decrement TTL in pf_route(6)()
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Thu, 26 Jun 2025 13:11:45 UTC
The branch main has been updated by kp:
URL: https://cgit.FreeBSD.org/src/commit/?id=ad591caf2a70d6f3a5f1ba7aff182591afc0cc04
commit ad591caf2a70d6f3a5f1ba7aff182591afc0cc04
Author: Kristof Provost <kp@FreeBSD.org>
AuthorDate: 2025-06-20 09:41:19 +0000
Commit: Kristof Provost <kp@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 <bluhm@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"
}