Skip to content

Commit

Permalink
pf: add 'allow-related' to always allow SCTP multihome extra connections
Browse files Browse the repository at this point in the history
Allow users to choose to allow permitted SCTP connections to set up additional
multihomed connections regardless of the ruleset. That is, allow an already
established connection to set up flows that would otherwise be disallowed.

In case of if-bound connections we initially set the extra associations to
be floating, because we don't know what path they'll be taking when they're
created. Once we see the first traffic we can bind them.

MFC after:	2 weeks
Sponsored by:	Orange Business Services
Differential Revision:	https://reviews.freebsd.org/D48453

(cherry picked from commit e4f2733df8c9d2fd0c5e8fdc8bec002bf39811f3)
  • Loading branch information
kprovost authored and fichtner committed Feb 18, 2025
1 parent 80d698b commit f15f440
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 6 deletions.
22 changes: 20 additions & 2 deletions sbin/pfctl/parse.y
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,8 @@ enum { PF_STATE_OPT_MAX, PF_STATE_OPT_NOSYNC, PF_STATE_OPT_SRCTRACK,
PF_STATE_OPT_MAX_SRC_STATES, PF_STATE_OPT_MAX_SRC_CONN,
PF_STATE_OPT_MAX_SRC_CONN_RATE, PF_STATE_OPT_MAX_SRC_NODES,
PF_STATE_OPT_OVERLOAD, PF_STATE_OPT_STATELOCK,
PF_STATE_OPT_TIMEOUT, PF_STATE_OPT_SLOPPY, };
PF_STATE_OPT_TIMEOUT, PF_STATE_OPT_SLOPPY,
PF_STATE_OPT_ALLOW_RELATED };

enum { PF_SRCTRACK_NONE, PF_SRCTRACK, PF_SRCTRACK_GLOBAL, PF_SRCTRACK_RULE };

Expand Down Expand Up @@ -512,7 +513,7 @@ int parseport(char *, struct range *r, int);
%token DNPIPE DNQUEUE RIDENTIFIER
%token LOAD RULESET_OPTIMIZATION PRIO
%token STICKYADDRESS MAXSRCSTATES MAXSRCNODES SOURCETRACK GLOBAL RULE
%token MAXSRCCONN MAXSRCCONNRATE OVERLOAD FLUSH SLOPPY
%token MAXSRCCONN MAXSRCCONNRATE OVERLOAD FLUSH SLOPPY ALLOW_RELATED
%token TAGGED TAG IFBOUND FLOATING STATEPOLICY STATEDEFAULTS ROUTE SETTOS
%token DIVERTTO DIVERTREPLY BRIDGE_TO
%token <v.string> STRING
Expand Down Expand Up @@ -2615,6 +2616,14 @@ pfrule : action dir logquick interface route af proto fromto
}
r.rule_flag |= PFRULE_STATESLOPPY;
break;
case PF_STATE_OPT_ALLOW_RELATED:
if (r.rule_flag & PFRULE_ALLOW_RELATED) {
yyerror("state allow-related option: "
"multiple definitions");
YYERROR;
}
r.rule_flag |= PFRULE_ALLOW_RELATED;
break;
case PF_STATE_OPT_TIMEOUT:
if (o->data.timeout.number ==
PFTM_ADAPTIVE_START ||
Expand Down Expand Up @@ -4368,6 +4377,14 @@ state_opt_item : MAXIMUM NUMBER {
$$->next = NULL;
$$->tail = $$;
}
| ALLOW_RELATED {
$$ = calloc(1, sizeof(struct node_state_opt));
if ($$ == NULL)
err(1, "state_opt_item: calloc");
$$->type = PF_STATE_OPT_ALLOW_RELATED;
$$->next = NULL;
$$->tail = $$;
}
| STRING NUMBER {
int i;

Expand Down Expand Up @@ -6240,6 +6257,7 @@ lookup(char *s)
static const struct keywords keywords[] = {
{ "all", ALL},
{ "allow-opts", ALLOWOPTS},
{ "allow-related", ALLOW_RELATED},
{ "altq", ALTQ},
{ "anchor", ANCHOR},
{ "antispoof", ANTISPOOF},
Expand Down
6 changes: 5 additions & 1 deletion share/man/man5/pf.conf.5
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
.\" ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
.\" POSSIBILITY OF SUCH DAMAGE.
.\"
.Dd June 24, 2024
.Dd January 10, 2025
.Dt PF.CONF 5
.Os
.Sh NAME
Expand Down Expand Up @@ -2438,6 +2438,10 @@ easier.
This is intended to be used in situations where one does not see all
packets of a connection, e.g. in asymmetric routing situations.
Cannot be used with modulate or synproxy state.
.It Ar allow-related
Automatically allow connections related to this one, regardless of rules that
might otherwise affect them.
This currently only applies to SCTP multihomed connection.
.El
.Pp
Multiple options can be specified, separated by commas:
Expand Down
1 change: 1 addition & 0 deletions sys/net/pfvar.h
Original file line number Diff line number Diff line change
Expand Up @@ -1592,6 +1592,7 @@ struct pf_pdesc {
#define PFDESC_SCTP_ADD_IP 0x1000
u_int16_t sctp_flags;
u_int32_t sctp_initiate_tag;
struct pf_krule *related_rule;

struct pf_sctp_multihome_jobs sctp_multihome_jobs;
};
Expand Down
37 changes: 34 additions & 3 deletions sys/netpfil/pf/pf.c
Original file line number Diff line number Diff line change
Expand Up @@ -427,8 +427,23 @@ enum { PF_ICMP_MULTI_NONE, PF_ICMP_MULTI_LINK };
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 *rule, struct pfi_kkif *k, struct pf_pdesc *pd)
{
/* Floating unless otherwise specified. */
if (! (rule->rule_flag & PFRULE_IFBOUND))
return (V_pfi_all);

/*
* If this state is created based on another state (e.g. SCTP
* multihome) always set it floating initially. We can't know for sure
* what interface the actual traffic for this state will come in on.
*/
if (pd->related_rule)
return (V_pfi_all);

return (k);
}

#define STATE_INC_COUNTERS(s) \
do { \
Expand Down Expand Up @@ -4902,6 +4917,10 @@ pf_test_rule(struct pf_krule **rm, struct pf_kstate **sm, struct pfi_kkif *kif,
}

while (r != NULL) {
if (pd->related_rule) {
*rm = pd->related_rule;
break;
}
pf_counter_u64_add(&r->evaluations, 1);
if (pfi_kkif_match(r->kif, kif) == r->ifnot)
r = r->skip[PF_SKIP_IFP].ptr;
Expand Down Expand Up @@ -5265,7 +5284,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);
Expand Down Expand Up @@ -6221,6 +6240,15 @@ pf_test_state_sctp(struct pf_kstate **state, struct pfi_kkif *kif,
dst->scrub->pfss_v_tag = pd->sctp_initiate_tag;
}

/*
* Bind to the correct interface if we're if-bound. For multihomed
* extra associations we don't know which interface that will be until
* here, so we've inserted the state on V_pf_all. Fix that now.
*/
if ((*state)->kif == V_pfi_all &&
(*state)->rule.ptr->rule_flag & PFRULE_IFBOUND)
(*state)->kif = kif;

if (pd->sctp_flags & (PFDESC_SCTP_COOKIE | PFDESC_SCTP_HEARTBEAT_ACK)) {
if (src->state < SCTP_ESTABLISHED) {
pf_set_protostate(*state, psrc, SCTP_ESTABLISHED);
Expand Down Expand Up @@ -6424,6 +6452,9 @@ pf_sctp_multihome_delayed(struct pf_pdesc *pd, int off, struct pfi_kkif *kif,
j->pd.sctp_flags |= PFDESC_SCTP_ADD_IP;
PF_RULES_RLOCK();
sm = NULL;
if (s->rule.ptr->rule_flag & PFRULE_ALLOW_RELATED) {
j->pd.related_rule = s->rule.ptr;
}
ret = pf_test_rule(&r, &sm, kif,
j->m, off, &j->pd, &ra, &rs, NULL);
PF_RULES_RUNLOCK();
Expand Down
1 change: 1 addition & 0 deletions sys/netpfil/pf/pf.h
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,7 @@ struct pf_rule {
#define PFRULE_SET_TOS 0x00002000
#define PFRULE_IFBOUND 0x00010000 /* if-bound */
#define PFRULE_STATESLOPPY 0x00020000 /* sloppy state tracking */
#define PFRULE_ALLOW_RELATED 0x00080000

#ifdef _KERNEL
#define PFRULE_REFS 0x0080 /* rule has references */
Expand Down
76 changes: 76 additions & 0 deletions tests/sys/netpfil/pf/sctp.py
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,82 @@ def test_permutation_floating(self):
assert re.search(r"all sctp 192.0.2.4:.*192.0.2.3:1234", states)
assert re.search(r"all sctp 192.0.2.4:.*192.0.2.2:1234", states)

@pytest.mark.require_user("root")
def test_disallow_related(self):
srv_vnet = self.vnet_map["vnet2"]

ToolsHelper.print_output("/sbin/pfctl -e")
ToolsHelper.pf_rules([
"block proto sctp",
"pass inet proto sctp to 192.0.2.3",
"pass on lo"])

# Sanity check, we can communicate with the primary address.
client = SCTPClient("192.0.2.3", 1234)
client.send(b"hello", 0)
rcvd = self.wait_object(srv_vnet.pipe)
print(rcvd)
assert rcvd['ppid'] == 0
assert rcvd['data'] == "hello"

# This shouldn't work
success=False
try:
client.newpeer("192.0.2.2")
client.send(b"world", 0)
rcvd = self.wait_object(srv_vnet.pipe)
print(rcvd)
assert rcvd['ppid'] == 0
assert rcvd['data'] == "world"
success=True
except:
success=False
assert not success

# Check that we have a state for 192.0.2.3, but not 192.0.2.2 to 192.0.2.1
states = ToolsHelper.get_output("/sbin/pfctl -ss")
assert re.search(r"all sctp 192.0.2.1:.*192.0.2.3:1234", states)
assert not re.search(r"all sctp 192.0.2.1:.*192.0.2.2:1234", states)

@pytest.mark.require_user("root")
def test_allow_related(self):
srv_vnet = self.vnet_map["vnet2"]

ToolsHelper.print_output("/sbin/pfctl -e")
ToolsHelper.pf_rules([
"set state-policy if-bound",
"block proto sctp",
"pass inet proto sctp to 192.0.2.3 keep state (allow-related)",
"pass on lo"])

# Sanity check, we can communicate with the primary address.
client = SCTPClient("192.0.2.3", 1234)
client.send(b"hello", 0)
rcvd = self.wait_object(srv_vnet.pipe)
print(rcvd)
assert rcvd['ppid'] == 0
assert rcvd['data'] == "hello"

success=False
try:
client.newpeer("192.0.2.2")
client.send(b"world", 0)
rcvd = self.wait_object(srv_vnet.pipe)
print(rcvd)
assert rcvd['ppid'] == 0
assert rcvd['data'] == "world"
success=True
finally:
# Debug output
ToolsHelper.print_output("/sbin/pfctl -ss")
ToolsHelper.print_output("/sbin/pfctl -sr -vv")
assert success

# Check that we have a state for 192.0.2.3 and 192.0.2.2 to 192.0.2.1
states = ToolsHelper.get_output("/sbin/pfctl -ss")
assert re.search(r"epair.*sctp 192.0.2.1:.*192.0.2.3:1234", states)
assert re.search(r"epair.*sctp 192.0.2.1:.*192.0.2.2:1234", states)

class TestSCTPv6(VnetTestTemplate):
REQUIRED_MODULES = ["sctp", "pf"]
TOPOLOGY = {
Expand Down

0 comments on commit f15f440

Please sign in to comment.