diff --git a/sys/netinet/ip_input.c b/sys/netinet/ip_input.c --- a/sys/netinet/ip_input.c +++ b/sys/netinet/ip_input.c @@ -136,6 +136,12 @@ CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(ip_sav), true, "Drop incoming packets with source address that is a local address"); +VNET_DEFINE_STATIC(bool, ip_filter_local_output) = false; +#define V_ip_filter_local_output VNET(ip_filter_local_output) +SYSCTL_BOOL(_net_inet_ip, OID_AUTO, filter_local_output, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(ip_filter_local_output), false, + "Generate filter output events for packets delivered for local processing"); + VNET_DEFINE(pfil_head_t, inet_pfil_head); /* Packet filter hooks */ static struct netisr_handler ip_nh = { @@ -816,6 +822,20 @@ return; #endif /* IPSTEALTH */ + /* + * We are going to ship the packet to the local protocol stack. Call the + * filter again for this 'output' action, allowing redirect-like rules + * to adjust the source address. + */ + if (PFIL_HOOKED_OUT(V_inet_pfil_head) && V_ip_filter_local_output) { + if (pfil_mbuf_out(V_inet_pfil_head, &m, V_loif, NULL) != + PFIL_PASS) + return; + if (m == NULL) /* consumed by filter */ + return; + ip = mtod(m, struct ip *); + } + /* * Attempt reassembly; if it succeeds, proceed. * ip_reass() will return a different mbuf. diff --git a/sys/netinet6/ip6_input.c b/sys/netinet6/ip6_input.c --- a/sys/netinet6/ip6_input.c +++ b/sys/netinet6/ip6_input.c @@ -177,6 +177,12 @@ CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(ip6_sav), true, "Drop incoming packets with source address that is a local address"); +VNET_DEFINE_STATIC(bool, ip6_filter_local_output) = false; +#define V_ip6_filter_local_output VNET(ip6_filter_local_output) +SYSCTL_BOOL(_net_inet6_ip6, OID_AUTO, filter_local_output, + CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(ip6_filter_local_output), false, + "Generate filter output events for packets delivered for local processing"); + #ifdef RSS static struct netisr_handler ip6_direct_nh = { .nh_name = "ip6_direct", @@ -884,6 +890,20 @@ return; } + /* + * We are going to ship the packet to the local protocol stack. Call the + * filter again for this 'output' action, allowing redirect-like rules + * to adjust the source address. + */ + if (PFIL_HOOKED_OUT(V_inet_pfil_head) && V_ip6_filter_local_output) { + if (pfil_mbuf_out(V_inet6_pfil_head, &m, V_loif, NULL) != + PFIL_PASS) + return; + if (m == NULL) /* consumed by filter */ + return; + ip6 = mtod(m, struct ip6_hdr *); + } + /* * Tell launch routine the next header */ diff --git a/tests/sys/netpfil/pf/rdr.sh b/tests/sys/netpfil/pf/rdr.sh --- a/tests/sys/netpfil/pf/rdr.sh +++ b/tests/sys/netpfil/pf/rdr.sh @@ -67,7 +67,59 @@ pft_cleanup } +atf_test_case "local_redirect" "cleanup" +local_redirect_head() +{ + atf_set descr 'Redirect local traffic test' + atf_set require.user root +} + +local_redirect_body() +{ + pft_init + + bridge=$(vnet_mkbridge) + ifconfig ${bridge} 192.0.2.1/24 up + + epair1=$(vnet_mkepair) + epair2=$(vnet_mkepair) + + vnet_mkjail first ${epair1}b + ifconfig ${epair1}a up + ifconfig ${bridge} addm ${epair1}a + jexec first ifconfig ${epair1}b 192.0.2.2/24 up + jexec first ifconfig lo0 127.0.0.1/8 up + + vnet_mkjail second ${epair2}b + ifconfig ${epair2}a up + ifconfig ${bridge} addm ${epair2}a + jexec second ifconfig ${epair2}b 192.0.2.3/24 up + jexec second ifconfig lo0 127.0.0.1/8 up + jexec second sysctl net.inet.ip.forwarding=1 + jexec second sysctl net.inet.ip.filter_local_output=1 + + # Enable pf! + jexec second pfctl -e + pft_set_rules second \ + "rdr pass proto tcp from any to 192.0.2.3/24 port 1234 -> 192.0.2.2 port 4321" + + echo "foo" | jexec first nc -N -l 4321 & + sleep 1 + + # Verify that second can use its rule to redirect local connections to first + result=$(jexec second nc -N -w 3 192.0.2.3 1234) + if [ "$result" != "foo" ]; then + atf_fail "Redirect failed" + fi +} + +local_redirect_cleanup() +{ + pft_cleanup +} + atf_init_test_cases() { atf_add_test_case "basic" + atf_add_test_case "local_redirect" }