git: ad6562ec858f - main - pf: Don't pfsync states with unrecoverable routing information
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Thu, 05 Dec 2024 22:09:33 UTC
The branch main has been updated by ks: URL: https://cgit.FreeBSD.org/src/commit/?id=ad6562ec858fffbed83560ed3f79375a44be2705 commit ad6562ec858fffbed83560ed3f79375a44be2705 Author: Kajetan Staszkiewicz <ks@FreeBSD.org> AuthorDate: 2024-11-29 22:20:14 +0000 Commit: Kajetan Staszkiewicz <ks@FreeBSD.org> CommitDate: 2024-12-05 22:03:12 +0000 pf: Don't pfsync states with unrecoverable routing information States created by route-to rules can't be trusted when received with pfsync version 1301 as they lack the rt and rt_kif information. They are imported, though, and pf_route() function attempts to recover the missing information for every forwarded packet. Move the recovery operation to pfsync_state_import() so that it's performed only once and if it's impossible don't import the state. Add an additional check for cases when recovery might produce wrong results. Reviewed by: kp Approved by: kp (mentor) Sponsored by: InnoGames GmbH Differential Revision: https://reviews.freebsd.org/D47906 --- sys/netpfil/pf/if_pfsync.c | 90 ++++++++++++--- sys/netpfil/pf/pf.c | 78 ++++--------- tests/sys/netpfil/pf/pfsync.sh | 256 ++++++++++++++++++++++++++++++++++++----- 3 files changed, 323 insertions(+), 101 deletions(-) diff --git a/sys/netpfil/pf/if_pfsync.c b/sys/netpfil/pf/if_pfsync.c index 8dd3d875dc0b..5923675ff144 100644 --- a/sys/netpfil/pf/if_pfsync.c +++ b/sys/netpfil/pf/if_pfsync.c @@ -110,6 +110,8 @@ #include <netpfil/pf/pfsync_nv.h> +#define DPFPRINTF(n, x) if (V_pf_status.debug >= (n)) printf x + struct pfsync_bucket; struct pfsync_softc; @@ -521,10 +523,13 @@ pfsync_state_import(union pfsync_state_union *sp, int flags, int msg_version) #endif struct pfsync_state_key *kw, *ks; struct pf_kstate *st = NULL; - struct pf_state_key *skw = NULL, *sks = NULL; - struct pf_krule *r = NULL; - struct pfi_kkif *kif; - int error; + struct pf_state_key *skw = NULL, *sks = NULL; + struct pf_krule *r = NULL; + struct pfi_kkif *kif; + struct pfi_kkif *rt_kif = NULL; + struct pf_kpooladdr *rpool_first; + int error; + uint8_t rt = 0; PF_RULES_RASSERT(); @@ -556,6 +561,67 @@ pfsync_state_import(union pfsync_state_union *sp, int flags, int msg_version) else r = &V_pf_default_rule; + /* + * Check routing interface early on. Do it before allocating memory etc. + * because there is a high chance there will be a lot more such states. + */ + switch (msg_version) { + case PFSYNC_MSG_VERSION_1301: + /* + * On FreeBSD <= 13 the routing interface and routing operation + * are not sent over pfsync. If the ruleset is identical, + * though, we might be able to recover the routing information + * from the local ruleset. + */ + if (r != &V_pf_default_rule) { + /* + * The ruleset is identical, try to recover. If the rule + * has a redirection pool with a single interface, there + * is a chance that this interface is identical as on + * the pfsync peer. If there's more than one interface, + * give up, as we can't be sure that we will pick the + * same one as the pfsync peer did. + */ + rpool_first = TAILQ_FIRST(&(r->rpool.list)); + if ((rpool_first == NULL) || + (TAILQ_NEXT(rpool_first, entries) != NULL)) { + DPFPRINTF(PF_DEBUG_MISC, + ("%s: can't recover routing information " + "because of empty or bad redirection pool\n", + __func__)); + return ((flags & PFSYNC_SI_IOCTL) ? EINVAL : 0); + } + rt = r->rt; + rt_kif = rpool_first->kif; + } else if (!PF_AZERO(&sp->pfs_1301.rt_addr, sp->pfs_1301.af)) { + /* + * Ruleset different, routing *supposedly* requested, + * give up on recovering. + */ + DPFPRINTF(PF_DEBUG_MISC, + ("%s: can't recover routing information " + "because of different ruleset\n", __func__)); + return ((flags & PFSYNC_SI_IOCTL) ? EINVAL : 0); + } + break; + case PFSYNC_MSG_VERSION_1400: + /* + * On FreeBSD 14 and above we're not taking any chances. + * We use the information synced to us. + */ + if (sp->pfs_1400.rt) { + rt_kif = pfi_kkif_find(sp->pfs_1400.rt_ifname); + if (rt_kif == NULL) { + DPFPRINTF(PF_DEBUG_MISC, + ("%s: unknown route interface: %s\n", + __func__, sp->pfs_1400.rt_ifname)); + return ((flags & PFSYNC_SI_IOCTL) ? EINVAL : 0); + } + rt = sp->pfs_1400.rt; + } + break; + } + if ((r->max_states && counter_u64_fetch(r->states_cur) >= r->max_states)) goto cleanup; @@ -629,6 +695,9 @@ pfsync_state_import(union pfsync_state_union *sp, int flags, int msg_version) st->act.log = sp->pfs_1301.log; st->timeout = sp->pfs_1301.timeout; + st->act.rt = rt; + st->act.rt_kif = rt_kif; + switch (msg_version) { case PFSYNC_MSG_VERSION_1301: st->state_flags = sp->pfs_1301.state_flags; @@ -680,17 +749,6 @@ pfsync_state_import(union pfsync_state_union *sp, int flags, int msg_version) st->act.max_mss = ntohs(sp->pfs_1400.max_mss); st->act.set_prio[0] = sp->pfs_1400.set_prio[0]; st->act.set_prio[1] = sp->pfs_1400.set_prio[1]; - st->act.rt = sp->pfs_1400.rt; - if (st->act.rt && (st->act.rt_kif = pfi_kkif_find(sp->pfs_1400.rt_ifname)) == NULL) { - if (V_pf_status.debug >= PF_DEBUG_MISC) - printf("%s: unknown route interface: %s\n", - __func__, sp->pfs_1400.rt_ifname); - if (flags & PFSYNC_SI_IOCTL) - error = EINVAL; - else - error = 0; - goto cleanup_keys; - } break; default: panic("%s: Unsupported pfsync_msg_version %d", @@ -737,7 +795,7 @@ pfsync_state_import(union pfsync_state_union *sp, int flags, int msg_version) cleanup: error = ENOMEM; -cleanup_keys: + if (skw == sks) sks = NULL; uma_zfree(V_pf_state_key_z, skw); diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c index 8d9d595dc1ec..a0c9a92c6b84 100644 --- a/sys/netpfil/pf/pf.c +++ b/sys/netpfil/pf/pf.c @@ -7690,14 +7690,14 @@ pf_route(struct mbuf **m, struct pf_krule *r, struct ifnet *oifp, struct mbuf *m0, *m1, *md; struct sockaddr_in dst; struct ip *ip; - struct ifnet *ifp = NULL; - struct pf_addr naddr; + struct ifnet *ifp; int error = 0; uint16_t ip_len, ip_off; uint16_t tmp; int r_dir; - KASSERT(m && *m && r && oifp, ("%s: invalid parameters", __func__)); + KASSERT(m && *m && r && oifp && pd->act.rt_kif, + ("%s: invalid parameters", __func__)); SDT_PROBE4(pf, ip, route_to, entry, *m, pd, s, oifp); @@ -7720,13 +7720,15 @@ pf_route(struct mbuf **m, struct pf_krule *r, struct ifnet *oifp, goto bad_locked; } + if ((ifp = pd->act.rt_kif->pfik_ifp) == NULL) { + m0 = *m; + *m = NULL; + SDT_PROBE1(pf, ip, route_to, drop, __LINE__); + goto bad_locked; + } + if (pd->act.rt == PF_DUPTO) { if ((pd->pf_mtag->flags & PF_MTAG_FLAG_DUPLICATED)) { - ifp = pd->act.rt_kif ? pd->act.rt_kif->pfik_ifp : NULL; - /* If pfsync'd from FreeBSD < 14 */ - if (ifp == NULL && r->rpool.cur != NULL) - ifp = r->rpool.cur->kif ? - r->rpool.cur->kif->pfik_ifp : NULL; if (s != NULL) { PF_STATE_UNLOCK(s); } @@ -7764,36 +7766,18 @@ pf_route(struct mbuf **m, struct pf_krule *r, struct ifnet *oifp, dst.sin_len = sizeof(dst); dst.sin_addr = ip->ip_dst; dst.sin_addr.s_addr = pd->act.rt_addr.v4.s_addr; - ifp = pd->act.rt_kif ? pd->act.rt_kif->pfik_ifp : NULL; - - bzero(&naddr, sizeof(naddr)); if (s != NULL){ - struct pfi_kkif *kif; - - kif = pd->act.rt_kif; - /* If pfsync'd from FreeBSD < 14 */ - if (ifp == NULL && r->rpool.cur != NULL) { - ifp = r->rpool.cur->kif ? - r->rpool.cur->kif->pfik_ifp : NULL; - kif = r->rpool.cur->kif; - } - if (ifp != NULL && kif != NULL && - r->rule_flag & PFRULE_IFBOUND && + if (r->rule_flag & PFRULE_IFBOUND && pd->act.rt == PF_REPLYTO && s->kif == V_pfi_all) { - s->kif = kif; + s->kif = pd->act.rt_kif; s->orig_kif = oifp->if_pf_kif; } PF_STATE_UNLOCK(s); } - if (ifp == NULL) { - SDT_PROBE1(pf, ip, route_to, drop, __LINE__); - goto bad; - } - if (pd->dir == PF_IN) { if (pf_test(AF_INET, PF_OUT, PFIL_FWD, ifp, &m0, inp, &pd->act) != PF_PASS) { @@ -7943,10 +7927,10 @@ pf_route6(struct mbuf **m, struct pf_krule *r, struct ifnet *oifp, struct sockaddr_in6 dst; struct ip6_hdr *ip6; struct ifnet *ifp = NULL; - struct pf_addr naddr; int r_dir; - KASSERT(m && *m && r && oifp, ("%s: invalid parameters", __func__)); + KASSERT(m && *m && r && oifp && pd->act.rt_kif, + ("%s: invalid parameters", __func__)); SDT_PROBE4(pf, ip6, route_to, entry, *m, pd, s, oifp); @@ -7969,13 +7953,15 @@ pf_route6(struct mbuf **m, struct pf_krule *r, struct ifnet *oifp, goto bad_locked; } + if ((ifp = pd->act.rt_kif->pfik_ifp) == NULL) { + m0 = *m; + *m = NULL; + SDT_PROBE1(pf, ip6, route_to, drop, __LINE__); + goto bad_locked; + } + if (pd->act.rt == PF_DUPTO) { if ((pd->pf_mtag->flags & PF_MTAG_FLAG_DUPLICATED)) { - ifp = pd->act.rt_kif ? pd->act.rt_kif->pfik_ifp : NULL; - /* If pfsync'd from FreeBSD < 14 */ - if (ifp == NULL && r->rpool.cur != NULL) - ifp = r->rpool.cur->kif ? - r->rpool.cur->kif->pfik_ifp : NULL; if (s != NULL) { PF_STATE_UNLOCK(s); } @@ -8013,35 +7999,17 @@ pf_route6(struct mbuf **m, struct pf_krule *r, struct ifnet *oifp, dst.sin6_len = sizeof(dst); dst.sin6_addr = ip6->ip6_dst; PF_ACPY((struct pf_addr *)&dst.sin6_addr, &pd->act.rt_addr, AF_INET6); - bzero(&naddr, sizeof(naddr)); - ifp = pd->act.rt_kif ? pd->act.rt_kif->pfik_ifp : NULL; if (s != NULL) { - struct pfi_kkif *kif; - - kif = pd->act.rt_kif; - /* If pfsync'd from FreeBSD < 14 */ - if (ifp == NULL && r->rpool.cur != NULL) { - ifp = r->rpool.cur->kif ? - r->rpool.cur->kif->pfik_ifp : NULL; - kif = r->rpool.cur->kif; - } - if (ifp != NULL && kif != NULL && - r->rule_flag & PFRULE_IFBOUND && + if (r->rule_flag & PFRULE_IFBOUND && pd->act.rt == PF_REPLYTO && s->kif == V_pfi_all) { - s->kif = kif; + s->kif = pd->act.rt_kif; s->orig_kif = oifp->if_pf_kif; } - PF_STATE_UNLOCK(s); } - if (ifp == NULL) { - SDT_PROBE1(pf, ip6, route_to, drop, __LINE__); - goto bad; - } - if (pd->dir == PF_IN) { if (pf_test(AF_INET6, PF_OUT, PFIL_FWD | PF_PFIL_NOREFRAGMENT, ifp, &m0, inp, &pd->act) != PF_PASS) { diff --git a/tests/sys/netpfil/pf/pfsync.sh b/tests/sys/netpfil/pf/pfsync.sh index b97b06c5f9a2..0f3505460b50 100644 --- a/tests/sys/netpfil/pf/pfsync.sh +++ b/tests/sys/netpfil/pf/pfsync.sh @@ -835,16 +835,11 @@ basic_ipv6_cleanup() pfsynct_cleanup } -atf_test_case "route_to" "cleanup" -route_to_head() +route_to_common_head() { - atf_set descr 'Test route-to with default rule' - atf_set require.user root - atf_set require.progs scapy -} + pfsync_version=$1 + shift -route_to_body() -{ pfsynct_init epair_sync=$(vnet_mkepair) @@ -866,40 +861,111 @@ route_to_body() jexec one ifconfig pfsync0 \ syncdev ${epair_sync}a \ maxupd 1 \ + version $pfsync_version \ up jexec two ifconfig ${epair_sync}b 192.0.2.2/24 up jexec two ifconfig ${epair_two}a 198.51.100.2/24 up jexec two ifconfig ${epair_out_two}a 203.0.113.2/24 up - #jexec two ifconfig ${epair_out_two}a name outif + jexec two ifconfig ${epair_out_two}a name outif jexec two sysctl net.inet.ip.forwarding=1 jexec two arp -s 203.0.113.254 00:01:02:03:04:05 jexec two ifconfig pfsync0 \ syncdev ${epair_sync}b \ maxupd 1 \ + version $pfsync_version \ up - # Enable pf! + ifconfig ${epair_one}b 198.51.100.254/24 up + ifconfig ${epair_two}b 198.51.100.253/24 up + route add -net 203.0.113.0/24 198.51.100.1 + ifconfig ${epair_two}b up + ifconfig ${epair_out_one}b up + ifconfig ${epair_out_two}b up +} + +route_to_common_tail() +{ + atf_check -s exit:0 env PYTHONPATH=${common_dir} \ + ${common_dir}/pft_ping.py \ + --sendif ${epair_one}b \ + --fromaddr 198.51.100.254 \ + --to 203.0.113.254 \ + --recvif ${epair_out_one}b + + # Allow time for sync + sleep 2 + + states_one=$(mktemp) + states_two=$(mktemp) + jexec one pfctl -qvvss | normalize_pfctl_s > $states_one + jexec two pfctl -qvvss | normalize_pfctl_s > $states_two +} + +atf_test_case "route_to_1301_body" "cleanup" +route_to_1301_head() +{ + atf_set descr 'Test route-to with pfsync version 13.1' + atf_set require.user root + atf_set require.progs scapy +} + +route_to_1301_body() +{ + route_to_common_head 1301 + jexec one pfctl -e pft_set_rules one \ "set skip on ${epair_sync}a" \ "pass out route-to (outif 203.0.113.254)" + jexec two pfctl -e + pft_set_rules two \ + "set skip on ${epair_sync}b" \ + "pass out route-to (outif 203.0.113.254)" + + route_to_common_tail - # Make sure we have different rulesets so the synced state is associated with - # V_pf_default_rule + # Sanity check + grep -qE 'all icmp 198.51.100.254 -> 203.0.113.254:8 .*, rule 0 .* route-to: 203.0.113.254@outif origif: outif' $states_one || + atf_fail "State missing on router one" + + # With identical ruleset the routing information is recovered from the matching rule. + grep -qE 'all icmp 198.51.100.254 -> 203.0.113.254:8 .*, rule 0 .* route-to: 203.0.113.254@outif' $states_two || + atf_fail "State missing on router two" + + true +} + +route_to_1301_cleanup() +{ + pfsynct_cleanup +} + +atf_test_case "route_to_1301_bad_ruleset" "cleanup" +route_to_1301_bad_ruleset_head() +{ + atf_set descr 'Test route-to with pfsync version 13.1 and incompatible ruleset' + atf_set require.user root + atf_set require.progs scapy +} + +route_to_1301_bad_ruleset_body() +{ + route_to_common_head 1301 + + jexec one pfctl -e + pft_set_rules one \ + "set skip on ${epair_sync}a" \ + "pass out route-to (outif 203.0.113.254)" + + jexec two pfctl -e pft_set_rules two \ + "set debug loud" \ "set skip on ${epair_sync}b" \ "pass out route-to (outif 203.0.113.254)" \ "pass out proto tcp" - ifconfig ${epair_one}b 198.51.100.254/24 up - ifconfig ${epair_two}b 198.51.100.253/24 up - route add -net 203.0.113.0/24 198.51.100.1 - ifconfig ${epair_two}b up - ifconfig ${epair_out_one}b up - ifconfig ${epair_out_two}b up - atf_check -s exit:0 env PYTHONPATH=${common_dir} \ ${common_dir}/pft_ping.py \ --sendif ${epair_one}b \ @@ -907,25 +973,151 @@ route_to_body() --to 203.0.113.254 \ --recvif ${epair_out_one}b - # Allow time for sync - ifconfig ${epair_one}b inet 198.51.100.254 -alias - route del -net 203.0.113.0/24 198.51.100.1 - route add -net 203.0.113.0/24 198.51.100.2 + route_to_common_tail - sleep 2 + # Sanity check + grep -qE 'all icmp 198.51.100.254 -> 203.0.113.254:8 .*, rule 0 .* route-to: 203.0.113.254@outif origif: outif' $states_one || + atf_fail "State missing on router one" - # Now try to trigger the state on the other pfsync member - env PYTHONPATH=${common_dir} \ + # Different ruleset on each router means the routing information recovery + # from rule is impossible. The state is not synced. + grep -qE 'all icmp 198.51.100.254 -> 203.0.113.254:8 .*' $states_two && + atf_fail "State present on router two" + + true +} + +route_to_1301_bad_ruleset_cleanup() +{ + pfsynct_cleanup +} + +atf_test_case "route_to_1301_bad_rpool" "cleanup" +route_to_1301_bad_rpool_head() +{ + atf_set descr 'Test route-to with pfsync version 13.1 and different interface' + atf_set require.user root + atf_set require.progs scapy +} + +route_to_1301_bad_rpool_body() +{ + route_to_common_head 1301 + + jexec one pfctl -e + pft_set_rules one \ + "set skip on ${epair_sync}a" \ + "pass out route-to { (outif 203.0.113.254) (outif 203.0.113.254) }" + + jexec two pfctl -e + pft_set_rules two \ + "set skip on ${epair_sync}b" \ + "pass out route-to { (outif 203.0.113.254) (outif 203.0.113.254) }" + + atf_check -s exit:0 env PYTHONPATH=${common_dir} \ ${common_dir}/pft_ping.py \ - --sendif ${epair_two}b \ + --sendif ${epair_one}b \ --fromaddr 198.51.100.254 \ --to 203.0.113.254 \ - --recvif ${epair_out_two}b + --recvif ${epair_out_one}b + + route_to_common_tail + + # Sanity check + grep -qE 'all icmp 198.51.100.254 -> 203.0.113.254:8 .*, rule 0 .* route-to: 203.0.113.254@outif origif: outif' $states_one || + atf_fail "State missing on router one" + + # The ruleset is identical but since the redirection pool contains multiple interfaces + # pfsync will not attempt to recover the routing information from the rule. + grep -qE 'all icmp 198.51.100.254 -> 203.0.113.254:8 .*' $states_two && + atf_fail "State present on router two" + + true +} + +route_to_1301_bad_rpool_cleanup() +{ + pfsynct_cleanup +} + +atf_test_case "route_to_1400_bad_ruleset" "cleanup" +route_to_1400_bad_ruleset_head() +{ + atf_set descr 'Test route-to with pfsync version 14.0' + atf_set require.user root + atf_set require.progs scapy +} + +route_to_1400_bad_ruleset_body() +{ + route_to_common_head 1400 + + jexec one pfctl -e + pft_set_rules one \ + "set skip on ${epair_sync}a" \ + "pass out route-to (outif 203.0.113.254)" + + jexec two pfctl -e + pft_set_rules two \ + "set skip on ${epair_sync}b" + + route_to_common_tail + + # Sanity check + grep -qE 'all icmp 198.51.100.254 -> 203.0.113.254:8 .*, rule 0 .* route-to: 203.0.113.254@outif origif: outif' $states_one || + atf_fail "State missing on router one" + + # Even with a different ruleset FreeBSD 14 syncs the state just fine. + # There's no recovery involved, the pfsync packet contains the routing information. + grep -qE 'all icmp 198.51.100.254 -> 203.0.113.254:8 .* route-to: 203.0.113.254@outif' $states_two || + atf_fail "State missing on router two" + + true +} + +route_to_1400_bad_ruleset_cleanup() +{ + pfsynct_cleanup +} + +atf_test_case "route_to_1400_bad_ifname" "cleanup" +route_to_1400_bad_ifname_head() +{ + atf_set descr 'Test route-to with pfsync version 14.0' + atf_set require.user root + atf_set require.progs scapy +} + +route_to_1400_bad_ifname_body() +{ + route_to_common_head 1400 + + jexec one pfctl -e + pft_set_rules one \ + "set skip on ${epair_sync}a" \ + "pass out route-to (outif 203.0.113.254)" + + jexec two pfctl -e + jexec two ifconfig outif name outif_new + pft_set_rules two \ + "set skip on ${epair_sync}b" \ + "pass out route-to (outif_new 203.0.113.254)" + + route_to_common_tail + + # Sanity check + grep -qE 'all icmp 198.51.100.254 -> 203.0.113.254:8 .*, rule 0 .* route-to: 203.0.113.254@outif origif: outif' $states_one || + atf_fail "State missing on router one" + + # Since FreeBSD 14 never attempts recovery of missing routing information + # a state synced to a router with a different interface name is dropped. + grep -qE 'all icmp 198.51.100.254 -> 203.0.113.254:8 .*' $states_two && + atf_fail "State present on router two" true } -route_to_cleanup() +route_to_1400_bad_ifname_cleanup() { pfsynct_cleanup } @@ -942,5 +1134,9 @@ atf_init_test_cases() atf_add_test_case "timeout" atf_add_test_case "basic_ipv6_unicast" atf_add_test_case "basic_ipv6" - atf_add_test_case "route_to" + atf_add_test_case "route_to_1301" + atf_add_test_case "route_to_1301_bad_ruleset" + atf_add_test_case "route_to_1301_bad_rpool" + atf_add_test_case "route_to_1400_bad_ruleset" + atf_add_test_case "route_to_1400_bad_ifname" }