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,7 +136,9 @@ CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(ip_sav), true, "Drop incoming packets with source address that is a local address"); -VNET_DEFINE(pfil_head_t, inet_pfil_head); /* Packet filter hooks */ +/* Packet filter hooks */ +VNET_DEFINE(pfil_head_t, inet_pfil_head); +VNET_DEFINE(pfil_head_t, inet_local_pfil_head); static struct netisr_handler ip_nh = { .nh_name = "ip", @@ -327,6 +329,10 @@ args.pa_headname = PFIL_INET_NAME; V_inet_pfil_head = pfil_head_register(&args); + args.pa_flags = PFIL_OUT; + args.pa_headname = PFIL_INET_LOCAL_NAME; + V_inet_local_pfil_head = pfil_head_register(&args); + if (hhook_head_register(HHOOK_TYPE_IPSEC_IN, AF_INET, &V_ipsec_hhh_in[HHOOK_IPSEC_INET], HHOOK_WAITOK | HHOOK_HEADISINVNET) != 0) @@ -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_local_pfil_head)) { + if (pfil_mbuf_out(V_inet_local_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/netinet/ip_var.h b/sys/netinet/ip_var.h --- a/sys/netinet/ip_var.h +++ b/sys/netinet/ip_var.h @@ -255,6 +255,10 @@ #define V_inet_pfil_head VNET(inet_pfil_head) #define PFIL_INET_NAME "inet" +VNET_DECLARE(struct pfil_head *, inet_local_pfil_head); +#define V_inet_local_pfil_head VNET(inet_local_pfil_head) +#define PFIL_INET_LOCAL_NAME "inet-local" + void in_delayed_cksum(struct mbuf *m); /* Hooks for ipfw, dummynet, divert etc. Most are declared in raw_ip.c */ 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 @@ -208,6 +208,7 @@ #endif VNET_DEFINE(pfil_head_t, inet6_pfil_head); +VNET_DEFINE(pfil_head_t, inet6_local_pfil_head); VNET_PCPUSTAT_DEFINE(struct ip6stat, ip6stat); VNET_PCPUSTAT_SYSINIT(ip6stat); @@ -245,6 +246,10 @@ args.pa_headname = PFIL_INET6_NAME; V_inet6_pfil_head = pfil_head_register(&args); + args.pa_flags = PFIL_OUT; + args.pa_headname = PFIL_INET6_LOCAL_NAME; + V_inet6_local_pfil_head = pfil_head_register(&args); + if (hhook_head_register(HHOOK_TYPE_IPSEC_IN, AF_INET6, &V_ipsec_hhh_in[HHOOK_IPSEC_INET6], HHOOK_WAITOK | HHOOK_HEADISINVNET) != 0) @@ -884,6 +889,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_inet6_local_pfil_head)) { + if (pfil_mbuf_out(V_inet6_local_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/sys/netinet6/ip6_var.h b/sys/netinet6/ip6_var.h --- a/sys/netinet6/ip6_var.h +++ b/sys/netinet6/ip6_var.h @@ -325,6 +325,10 @@ #define V_inet6_pfil_head VNET(inet6_pfil_head) #define PFIL_INET6_NAME "inet6" +VNET_DECLARE(struct pfil_head *, inet6_local_pfil_head); +#define V_inet6_local_pfil_head VNET(inet6_local_pfil_head) +#define PFIL_INET6_LOCAL_NAME "inet6-local" + #ifdef IPSTEALTH VNET_DECLARE(int, ip6stealth); #define V_ip6stealth VNET(ip6stealth) diff --git a/tests/sys/netpfil/common/Makefile b/tests/sys/netpfil/common/Makefile --- a/tests/sys/netpfil/common/Makefile +++ b/tests/sys/netpfil/common/Makefile @@ -9,6 +9,7 @@ dummynet \ pass_block \ nat \ + rdr \ tos \ fragments \ forward diff --git a/tests/sys/netpfil/pf/rdr.sh b/tests/sys/netpfil/common/rdr.sh rename from tests/sys/netpfil/pf/rdr.sh rename to tests/sys/netpfil/common/rdr.sh --- a/tests/sys/netpfil/pf/rdr.sh +++ b/tests/sys/netpfil/common/rdr.sh @@ -26,17 +26,19 @@ # SUCH DAMAGE. . $(atf_get_srcdir)/utils.subr +. $(atf_get_srcdir)/runner.subr -atf_test_case "basic" "cleanup" basic_head() { - atf_set descr 'Basic rdr test' + atf_set descr 'Basic IPv4 NAT test' atf_set require.user root } basic_body() { - pft_init + firewall=$1 + firewall_init $firewall + nat_init $firewall epair=$(vnet_mkepair) @@ -48,10 +50,13 @@ jexec alcatraz ifconfig ${epair}b 192.0.2.1/24 up jexec alcatraz sysctl net.inet.ip.forwarding=1 - # Enable pf! - jexec alcatraz pfctl -e - pft_set_rules alcatraz \ - "rdr pass on ${epair}b proto tcp from any to 198.51.100.0/24 port 1234 -> 192.0.2.1 port 4321" + # Enable redirect filter rule + firewall_config alcatraz ${firewall} \ + "pf" \ + "rdr pass on ${epair}b proto tcp from any to 198.51.100.0/24 port 1234 -> 192.0.2.1 port 4321" \ + "ipfnat" \ + "rdr ${epair}b from any to 198.51.100.0/24 port = 1234 -> 192.0.2.1 port 4321 tcp" + echo "foo" | jexec alcatraz nc -N -l 4321 & sleep 1 @@ -64,10 +69,69 @@ basic_cleanup() { - pft_cleanup + firewall=$1 + firewall_cleanup $firewall } -atf_init_test_cases() +local_redirect_head() { - atf_add_test_case "basic" + atf_set descr 'Redirect local traffic test' + atf_set require.user root } + +local_redirect_body() +{ + firewall=$1 + firewall_init $firewall + nat_init $firewall + + 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 + + # Enable redirect filter rule + firewall_config second ${firewall} \ + "pf" \ + "rdr pass proto tcp from any to 192.0.2.3/24 port 1234 -> 192.0.2.2 port 4321" \ + "ipfnat" \ + "rdr '*' from any to 192.0.2.3/24 port = 1234 -> 192.0.2.2 port 4321 tcp" + + 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() +{ + firewall=$1 + firewall_cleanup $firewall +} + +setup_tests \ + basic \ + pf \ + ipfnat \ + local_redirect \ + pf \ + ipfnat + diff --git a/tests/sys/netpfil/common/utils.subr b/tests/sys/netpfil/common/utils.subr --- a/tests/sys/netpfil/common/utils.subr +++ b/tests/sys/netpfil/common/utils.subr @@ -58,12 +58,16 @@ jexec ${jname} pfctl -e jexec ${jname} pfctl -F all jexec ${jname} pfctl -f $cwd/pf.rule + jexec ${jname} pfilctl link -o pf:default-out inet-local + jexec ${jname} pfilctl link -o pf:default-out6 inet6-local elif [ ${fw} == "ipf" ]; then jexec ${jname} ipf -E jexec ${jname} ipf -Fa -f $cwd/ipf.rule elif [ ${fw} == "ipfnat" ]; then jexec ${jname} service ipfilter start jexec ${jname} ipnat -CF -f $cwd/ipfnat.rule + jexec ${jname} pfilctl link -o ipfilter:default-ip4 inet-local + jexec ${jname} pfilctl link -o ipfilter:default-ip6 inet6-local else atf_fail "$fw is not a valid firewall to configure" fi diff --git a/tests/sys/netpfil/pf/Makefile b/tests/sys/netpfil/pf/Makefile --- a/tests/sys/netpfil/pf/Makefile +++ b/tests/sys/netpfil/pf/Makefile @@ -25,7 +25,6 @@ pfsync \ prio \ proxy \ - rdr \ ridentifier \ route_to \ rtable \