pf: make reply-to work with nat64

Just like route-to reply-to is problematic when used in combination with nat64.

In the normal (i.e. without nat64) flow we return immediately from pf_route().
However, with nat64 we need to continue and do a route lookup. In that case
we should not make the extra pf_test(PF_OUT) call to remain similar to the
non-nat64 flow.

We also have to fix the interface binding. We can only bind to the interface
after we've done the route lookup, not before.

Add a funcional test case, and a test for pfctl's rule printing.

Sponsored by:	Rubicon Communications, LLC ("Netgate")
This commit is contained in:
Kristof Provost 2025-01-22 17:49:22 +01:00
parent ca0e693453
commit 7a372bded8
7 changed files with 106 additions and 37 deletions

View file

@ -5505,8 +5505,8 @@ filter_consistent(struct pfctl_rule *r, int anchor_call)
problems++;
}
if (r->rule_flag & PFRULE_AFTO && r->rt) {
if (r->rt != PF_ROUTETO) {
yyerror("reply-to and dup-to "
if (r->rt != PF_ROUTETO && r->rt != PF_REPLYTO) {
yyerror("dup-to "
"must not be used on af-to rules");
problems++;
}

View file

@ -944,7 +944,8 @@ print_rule(struct pfctl_rule *r, const char *anchor_call, int verbose, int numer
printf(" ");
print_pool(&r->rdr, 0, 0, r->af, PF_PASS);
print_pool(&r->route, 0, 0,
r->rule_flag & PFRULE_AFTO ? r->naf : r->af, PF_PASS);
r->rule_flag & PFRULE_AFTO && r->rt != PF_REPLYTO ? r->naf : r->af,
PF_PASS);
}
if (r->af) {
if (r->af == AF_INET)

View file

@ -0,0 +1 @@
pass in on epair2b reply-to (epair0a 2001:db8::1) inet6 from any to 64:ff9b::/96 af-to inet from (epair0a)

View file

@ -0,0 +1 @@
pass in on epair2b reply-to (epair0a 2001:db8::1) inet6 from any to 64:ff9b::/96 flags S/SA keep state af-to inet from (epair0a)

View file

@ -135,3 +135,4 @@ PFCTL_TEST(1023, "Test match log(matches)")
PFCTL_TEST(1024, "nat64")
PFCTL_TEST(1025, "nat64 with implicit address family")
PFCTL_TEST(1026, "nat64 with route-to")
PFCTL_TEST(1027, "nat64 with reply-to")

View file

@ -8891,6 +8891,7 @@ pf_route(struct mbuf **m, struct pf_krule *r, struct ifnet *oifp,
uint16_t ip_len, ip_off;
uint16_t tmp;
int r_dir;
bool skip_test = false;
KASSERT(m && *m && r && oifp, ("%s: invalid parameters", __func__));
@ -8941,12 +8942,15 @@ pf_route(struct mbuf **m, struct pf_krule *r, struct ifnet *oifp,
}
}
} else {
if (((pd->act.rt == PF_REPLYTO) == (r_dir == pd->dir)) &&
(pd->af == pd->naf)) {
pf_dummynet(pd, s, r, m);
if (s)
PF_STATE_UNLOCK(s);
return;
if ((pd->act.rt == PF_REPLYTO) == (r_dir == pd->dir)) {
if (pd->af == pd->naf) {
pf_dummynet(pd, s, r, m);
if (s)
PF_STATE_UNLOCK(s);
return;
} else {
skip_test = true;
}
}
/*
@ -8954,9 +8958,15 @@ pf_route(struct mbuf **m, struct pf_krule *r, struct ifnet *oifp,
* reply direction.
*/
if (pd->act.rt_kif && pd->act.rt_kif->pfik_ifp &&
pd->af != pd->naf && r->naf != AF_INET) {
/* Un-set ifp so we do a plain route lookup. */
ifp = NULL;
pd->af != pd->naf) {
if (pd->act.rt == PF_ROUTETO && r->naf != AF_INET) {
/* Un-set ifp so we do a plain route lookup. */
ifp = NULL;
}
if (pd->act.rt == PF_REPLYTO && r->naf != AF_INET6) {
/* Un-set ifp so we do a plain route lookup. */
ifp = NULL;
}
}
m0 = *m;
}
@ -8970,13 +8980,6 @@ pf_route(struct mbuf **m, struct pf_krule *r, struct ifnet *oifp,
dst.sin_addr.s_addr = pd->act.rt_addr.v4.s_addr;
if (s != NULL){
if (r->rule_flag & PFRULE_IFBOUND &&
pd->act.rt == PF_REPLYTO &&
s->kif == V_pfi_all) {
s->kif = pd->act.rt_kif;
s->orig_kif = oifp->if_pf_kif;
}
if (ifp == NULL && (pd->af != pd->naf)) {
/* We're in the AFTO case. Do a route lookup. */
const struct nhop_object *nh;
@ -9002,6 +9005,13 @@ pf_route(struct mbuf **m, struct pf_krule *r, struct ifnet *oifp,
}
}
if (r->rule_flag & PFRULE_IFBOUND &&
pd->act.rt == PF_REPLYTO &&
s->kif == V_pfi_all) {
s->kif = pd->act.rt_kif;
s->orig_kif = oifp->if_pf_kif;
}
PF_STATE_UNLOCK(s);
}
@ -9012,7 +9022,7 @@ pf_route(struct mbuf **m, struct pf_krule *r, struct ifnet *oifp,
goto bad;
}
if (pd->dir == PF_IN) {
if (pd->dir == PF_IN && !skip_test) {
if (pf_test(AF_INET, PF_OUT, PFIL_FWD, ifp, &m0, inp,
&pd->act) != PF_PASS) {
SDT_PROBE1(pf, ip, route_to, drop, __LINE__);
@ -9162,6 +9172,7 @@ pf_route6(struct mbuf **m, struct pf_krule *r, struct ifnet *oifp,
struct ip6_hdr *ip6;
struct ifnet *ifp = NULL;
int r_dir;
bool skip_test = false;
KASSERT(m && *m && r && oifp, ("%s: invalid parameters", __func__));
@ -9212,12 +9223,15 @@ pf_route6(struct mbuf **m, struct pf_krule *r, struct ifnet *oifp,
}
}
} else {
if (((pd->act.rt == PF_REPLYTO) == (r_dir == pd->dir)) &&
(pd->af == pd->naf)) {
pf_dummynet(pd, s, r, m);
if (s)
PF_STATE_UNLOCK(s);
return;
if ((pd->act.rt == PF_REPLYTO) == (r_dir == pd->dir)) {
if (pd->af == pd->naf) {
pf_dummynet(pd, s, r, m);
if (s)
PF_STATE_UNLOCK(s);
return;
} else {
skip_test = true;
}
}
/*
@ -9225,9 +9239,15 @@ pf_route6(struct mbuf **m, struct pf_krule *r, struct ifnet *oifp,
* reply direction.
*/
if (pd->act.rt_kif && pd->act.rt_kif->pfik_ifp &&
pd->af != pd->naf && r->naf != AF_INET6) {
/* Un-set ifp so we do a plain route lookup. */
ifp = NULL;
pd->af != pd->naf) {
if (pd->act.rt == PF_ROUTETO && r->naf != AF_INET6) {
/* Un-set ifp so we do a plain route lookup. */
ifp = NULL;
}
if (pd->act.rt == PF_REPLYTO && r->naf != AF_INET) {
/* Un-set ifp so we do a plain route lookup. */
ifp = NULL;
}
}
m0 = *m;
}
@ -9241,13 +9261,6 @@ pf_route6(struct mbuf **m, struct pf_krule *r, struct ifnet *oifp,
PF_ACPY((struct pf_addr *)&dst.sin6_addr, &pd->act.rt_addr, AF_INET6);
if (s != NULL) {
if (r->rule_flag & PFRULE_IFBOUND &&
pd->act.rt == PF_REPLYTO &&
s->kif == V_pfi_all) {
s->kif = pd->act.rt_kif;
s->orig_kif = oifp->if_pf_kif;
}
if (ifp == NULL && (pd->af != pd->naf)) {
const struct nhop_object *nh;
nh = fib6_lookup(M_GETFIB(*m), &ip6->ip6_dst, 0, NHR_NONE, 0);
@ -9273,6 +9286,13 @@ pf_route6(struct mbuf **m, struct pf_krule *r, struct ifnet *oifp,
}
}
if (r->rule_flag & PFRULE_IFBOUND &&
pd->act.rt == PF_REPLYTO &&
s->kif == V_pfi_all) {
s->kif = pd->act.rt_kif;
s->orig_kif = oifp->if_pf_kif;
}
PF_STATE_UNLOCK(s);
}
@ -9293,7 +9313,7 @@ pf_route6(struct mbuf **m, struct pf_krule *r, struct ifnet *oifp,
goto bad;
}
if (pd->dir == PF_IN) {
if (pd->dir == PF_IN && !skip_test) {
if (pf_test(AF_INET6, PF_OUT, PFIL_FWD | PF_PFIL_NOREFRAGMENT,
ifp, &m0, inp, &pd->act) != PF_PASS) {
SDT_PROBE1(pf, ip6, route_to, drop, __LINE__);

View file

@ -717,6 +717,50 @@ route_to_cleanup()
pft_cleanup
}
atf_test_case "reply_to" "cleanup"
reply_to_head()
{
atf_set descr 'Test reply-to on af-to rules'
atf_set require.user root
}
reply_to_body()
{
pft_init
epair_link=$(vnet_mkepair)
epair=$(vnet_mkepair)
ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad
route -6 add default 2001:db8::1
vnet_mkjail rtr ${epair}b ${epair_link}a
jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad
jexec rtr ifconfig ${epair_link}a 192.0.2.1/24 up
vnet_mkjail dst ${epair_link}b
jexec dst ifconfig ${epair_link}b 192.0.2.2/24 up
jexec dst route add default 192.0.2.1
# Sanity checks
atf_check -s exit:0 -o ignore \
ping6 -c 1 2001:db8::1
jexec rtr pfctl -e
pft_set_rules rtr \
"set reassemble yes" \
"set state-policy if-bound" \
"pass in on ${epair}b reply-to (${epair}b 2001:db8::2) inet6 from any to 64:ff9b::/96 af-to inet from 192.0.2.1"
atf_check -s exit:0 -o ignore \
ping6 -c 3 64:ff9b::192.0.2.2
}
reply_to_cleanup()
{
pft_cleanup
}
atf_init_test_cases()
{
atf_add_test_case "icmp_echo"
@ -734,4 +778,5 @@ atf_init_test_cases()
atf_add_test_case "dummynet"
atf_add_test_case "gateway6"
atf_add_test_case "route_to"
atf_add_test_case "reply_to"
}