pf: bind route-to states to their route-to interface

When we route-to the state should be bound to the route-to interface,
not the default route interface. However, we should only do so for
outbound traffic, because inbound traffic should bind on the arriving
interface, not the one we eventually transmit on.

Explicitly check for this in BOUND_IFACE().

We must also extend pf_find_state(), because subsequent packets within
the established state will attempt to match the original interface, not
the route-to interface.

Sponsored by:	Rubicon Communications, LLC ("Netgate")
Differential Revision:	https://reviews.freebsd.org/D43589
This commit is contained in:
Kristof Provost 2024-01-25 11:16:49 +01:00
parent ffeab76b68
commit 31828075e4
2 changed files with 66 additions and 4 deletions

View file

@ -412,8 +412,27 @@ VNET_DEFINE(struct pf_limit, pf_limits[PF_LIMIT_MAX]);
return (PF_PASS); \
} while (0)
#define BOUND_IFACE(r, k) \
((r)->rule_flag & PFRULE_IFBOUND) ? (k) : V_pfi_all
static struct pfi_kkif *
BOUND_IFACE(struct pf_krule *r, struct pfi_kkif *k, struct pf_pdesc *pd)
{
/* Floating unless otherwise specified. */
if (! (r->rule_flag & PFRULE_IFBOUND))
return (V_pfi_all);
/* Don't overrule the interface for states created on incoming packets. */
if (pd->dir == PF_IN)
return (k);
/* No route-to, so don't overrrule. */
if (r->rt != PF_ROUTETO)
return (k);
if (r->rpool.cur == NULL)
return (k);
/* Bind to the route-to interface. */
return (r->rpool.cur->kif);
}
#define STATE_INC_COUNTERS(s) \
do { \
@ -1600,7 +1619,7 @@ pf_find_state(struct pfi_kkif *kif, struct pf_state_key_cmp *key, u_int dir)
/* List is sorted, if-bound states before floating ones. */
TAILQ_FOREACH(s, &sk->states[idx], key_list[idx])
if (s->kif == V_pfi_all || s->kif == kif) {
if (s->kif == V_pfi_all || s->kif == kif || s->orig_kif == kif) {
PF_STATE_LOCK(s);
PF_HASHROW_UNLOCK(kh);
if (__predict_false(s->timeout >= PFTM_MAX)) {
@ -4999,7 +5018,7 @@ pf_create_state(struct pf_krule *r, struct pf_krule *nr, struct pf_krule *a,
__func__, nr, sk, nk));
/* Swap sk/nk for PF_OUT. */
if (pf_state_insert(BOUND_IFACE(r, kif), kif,
if (pf_state_insert(BOUND_IFACE(r, kif, pd), kif,
(pd->dir == PF_IN) ? sk : nk,
(pd->dir == PF_IN) ? nk : sk, s)) {
REASON_SET(&reason, PFRES_STATEINS);

View file

@ -365,6 +365,48 @@ dummynet_cleanup()
pft_cleanup
}
atf_test_case "ifbound" "cleanup"
ifbound_head()
{
atf_set descr 'Test that route-to states bind the expected interface'
atf_set require.user root
}
ifbound_body()
{
pft_init
j="route_to:ifbound"
epair_one=$(vnet_mkepair)
epair_two=$(vnet_mkepair)
ifconfig ${epair_one}b up
vnet_mkjail ${j}2 ${epair_two}b
jexec ${j}2 ifconfig ${epair_two}b inet 198.51.100.2/24 up
jexec ${j}2 ifconfig ${epair_two}b inet alias 203.0.113.1/24
jexec ${j}2 route add default 198.51.100.1
vnet_mkjail $j ${epair_one}a ${epair_two}a
jexec $j ifconfig ${epair_one}a 192.0.2.1/24 up
jexec $j ifconfig ${epair_two}a 198.51.100.1/24 up
jexec $j route add default 192.0.2.2
jexec $j pfctl -e
pft_set_rules $j \
"set state-policy if-bound" \
"block" \
"pass out route-to (${epair_two}a 198.51.100.2)"
atf_check -s exit:0 -o ignore \
jexec $j ping -c 3 203.0.113.1
}
ifbound_cleanup()
{
pft_cleanup
}
atf_init_test_cases()
{
atf_add_test_case "v4"
@ -373,4 +415,5 @@ atf_init_test_cases()
atf_add_test_case "multiwanlocal"
atf_add_test_case "icmp_nat"
atf_add_test_case "dummynet"
atf_add_test_case "ifbound"
}