diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c --- a/sys/netpfil/pf/pf.c +++ b/sys/netpfil/pf/pf.c @@ -126,6 +126,8 @@ SDT_PROBE_DEFINE5(pf, ip, state, lookup, "struct pfi_kkif *", "struct pf_state_key_cmp *", "int", "struct pf_pdesc *", "struct pf_kstate *"); +SDT_PROBE_DEFINE2(pf, ip, state, conflict, "struct pf_state_key *", + "struct pf_state_key *"); SDT_PROBE_DEFINE2(pf, ip, , bound_iface, "struct pf_kstate *", "struct pfi_kkif *"); SDT_PROBE_DEFINE4(pf, sctp, multihome, test, "struct pfi_kkif *", @@ -4862,6 +4864,12 @@ u_int16_t mss = V_tcp_mssdflt; u_short reason, sn_reason; struct pf_krule_item *ri; + struct pf_keyhash *skh = NULL; + struct pf_state_key *cur = NULL; + struct pf_state_key *wk = NULL; + struct pf_state_key *stk = NULL; + int fixed_srcport = 0; + /* check maximums */ if (r->max_states && @@ -5026,13 +5034,49 @@ __func__, nr, sk, nk)); /* Swap sk/nk for PF_OUT. */ - if (pf_state_insert(BOUND_IFACE(s, kif), kif, - (pd->dir == PF_IN) ? sk : nk, - (pd->dir == PF_IN) ? nk : sk, s)) { + wk = (pd->dir == PF_IN) ? sk : nk; + stk = (pd->dir == PF_IN) ? nk : sk; + +tryagain: + + /* check if we can insert stack key */ + skh = &V_pf_keyhash[pf_hashkey(stk)]; + + PF_HASHROW_LOCK(skh); + + LIST_FOREACH(cur, &skh->keys, entry) + if (bcmp(cur, stk, sizeof(struct pf_state_key_cmp)) == 0) + break; + PF_HASHROW_UNLOCK(skh); + + if (cur != NULL) { + /* A mapping for this src port already exists */ + SDT_PROBE2(pf, ip, state, conflict, stk, cur); + + /* try to find an avialable sk src port */ + if (stk->port[0] == IPPORT_MAX) { + REASON_SET(&reason, PFRES_STATEINS); + goto drop; + } + stk->port[0]++; + fixed_srcport = stk->port[0]; + + goto tryagain; + } + + if (pf_state_insert(BOUND_IFACE(s, kif), kif, wk, stk, s)) { REASON_SET(&reason, PFRES_STATEINS); goto drop; - } else + } else { + if (fixed_srcport) { + pf_change_ap(m, pd->src, &th->th_sport, + pd->ip_sum, &th->th_sum, &stk->addr[pd->sidx], + stk->port[pd->sidx], 0, pd->af); + + pd->sport = &th->th_sport; + } *sm = s; + } if (tag > 0) s->tag = tag; 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 @@ -67,6 +67,7 @@ pfsync_defer.py \ pft_ether.py \ pft_read_ipfix.py \ + rdr_srcport.c \ utils.subr ${PACKAGE}FILESMODE_CVE-2019-5597.py= 0555 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 @@ -121,7 +121,96 @@ pft_cleanup } + +atf_test_case "srcport" "cleanup" +srcport_head() +{ + atf_set descr 'TCP rdr srcport modulation' + atf_set require.user root + atf_set require.progs python3 +} + +# +# Test that rdr works for multiple TCP with same srcip and srcport. +# +# a:5454 <-----> b[0]:7777 <-----> c:8000 +# a:5454 <-----> b[1]:7777 <-----> c:8000 +# a:5454 <-----> b[2]:7777 <-----> c:8000 +# +# Test configures three jails (a, b and c) and connects them together with b as +# a router between a and c. +# +# TCP traffic to b on port 7777 is redirected to c on port 8000 +# +# Multiple connections from a with the same srcport need to remapped to succeed +# +srcport_body() +{ + pft_init + + j="rdr:srcport" + epair_one=$(vnet_mkepair) + epair_two=$(vnet_mkepair) + + echo $epair_one + echo $epair_two + + vnet_mkjail ${j}a ${epair_one}b + vnet_mkjail ${j}b ${epair_one}a ${epair_two}a + vnet_mkjail ${j}c ${epair_two}b + + # configure addresses for b + jexec ${j}b ifconfig lo0 up + jexec ${j}b ifconfig ${epair_one}a inet 198.51.100.1/24 up + jexec ${j}b ifconfig ${epair_one}a inet alias 198.51.100.2/24 + jexec ${j}b ifconfig ${epair_one}a inet alias 198.51.100.3/24 + jexec ${j}b ifconfig ${epair_two}a inet 203.0.113.1/24 up + + # configure addresses for a + jexec ${j}a ifconfig lo0 up + jexec ${j}a ifconfig ${epair_one}b inet 198.51.100.50/24 up + + # configure addresses for c + jexec ${j}c ifconfig lo0 up + jexec ${j}c ifconfig ${epair_two}b inet 203.0.113.50/24 up + + # enable forwarding in the b jail + jexec ${j}b sysctl net.inet.ip.forwarding=1 + + jexec ${j}b pfctl -e + + pft_set_rules ${j}b \ + "nat on ${epair_one}a inet from 203.0.113.0/24 to any -> ${epair_one}a port 1024:65535 static-port" \ + "rdr on ${epair_one}a proto tcp from any to ${epair_one}a port 7777 -> 203.0.113.50 port 8000" + + # add routes so c can reply to a + jexec ${j}c route add 198.51.100.0/24 203.0.113.1 + + # Check that c can reach a over the router + atf_check -s exit:0 -o ignore \ + jexec ${j}a ping -c 1 198.51.100.50 + + # start a web server and give it a second to start + jexec ${j}c python3 -m http.server & + sleep 1 + + # Use SO_REUSEPORT to make multiple connections with the same srcport + tmp=`pwd` + jexec ${j}a clang -o ${tmp}/rdr_srcport $(atf_get_srcdir)/rdr_srcport.c + + # http from a to b with a redirect -> a ---> b + atf_check -s exit:0 -o ignore \ + jexec ${j}a ${tmp}/rdr_srcport 198.51.100.1 198.51.100.2 198.51.100.3 + +} + +srcport() +{ + pft_cleanup +} + atf_init_test_cases() { atf_add_test_case "tcp_v6" + atf_add_test_case "srcport" } diff --git a/tests/sys/netpfil/pf/rdr_srcport.c b/tests/sys/netpfil/pf/rdr_srcport.c new file mode 100644 --- /dev/null +++ b/tests/sys/netpfil/pf/rdr_srcport.c @@ -0,0 +1,70 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +static int +newsock(void) +{ + struct sockaddr_in sin; + int on, s; + + s = socket(PF_INET, SOCK_STREAM, 0); + assert(s >= 0); + + on = 1; + assert(setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on)) == 0); + + sin.sin_family = AF_INET; + sin.sin_port = htons(5454); + sin.sin_addr.s_addr = INADDR_ANY; + memset(&sin.sin_zero, 0, sizeof(sin.sin_zero)); + + assert(bind(s, (struct sockaddr *)&sin, sizeof(sin)) == 0); + return (s); +} + +int +main(int argc, char *argv[]) +{ + int *sock; + int *err; + int remotes; + int i; + + struct sockaddr_in dest; + char outbuf[] = "GET / HTTP/1.1\nConnection: Keep-alive\n\n"; + + sock = calloc(argc, sizeof(sock)); + err = calloc(argc, sizeof(sock)); + + remotes = argc - 1; + + for (i = 0; i < remotes; i++) { + sock[i] = newsock(); + printf("Connecting to %s:7777\n", argv[i + 1]); + dest.sin_family = AF_INET; + dest.sin_port = htons(7777); + dest.sin_addr.s_addr = inet_addr(argv[i + 1]); + memset(&dest.sin_zero, 0, sizeof(dest.sin_zero)); + err[i] = connect(sock[i], (struct sockaddr *)&dest, sizeof(dest)); + printf("Socket[%d] ret=%d errno=%d\n", i, err[i], errno); + } + + for (i = 0; i < remotes; i++) { + assert(err[i] == 0); + assert(write(sock[i], outbuf, sizeof(outbuf) - 1) == sizeof(outbuf) - 1); + } + + printf("Holding the connection open...\n"); + sleep(1); + printf("Exiting\n"); + +}