diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h --- a/sys/net/pfvar.h +++ b/sys/net/pfvar.h @@ -1531,6 +1531,9 @@ #define PFI_IFLAG_SKIP 0x0100 /* skip filtering on interface */ #ifdef _KERNEL +struct pf_sctp_multihome_job; +SLIST_HEAD(pf_sctp_multihome_jobs, pf_sctp_multihome_job); + struct pf_pdesc { struct { int done; @@ -1578,10 +1581,20 @@ #define PFDESC_SCTP_SHUTDOWN 0x0010 #define PFDESC_SCTP_SHUTDOWN_COMPLETE 0x0020 #define PFDESC_SCTP_DATA 0x0040 -#define PFDESC_SCTP_OTHER 0x0080 +#define PFDESC_SCTP_ASCONF 0x0080 +#define PFDESC_SCTP_OTHER 0x0100 u_int16_t sctp_flags; u_int32_t sctp_initiate_tag; + + struct pf_sctp_multihome_jobs sctp_multihome_jobs; +}; + +struct pf_sctp_multihome_job { + SLIST_ENTRY(pf_sctp_multihome_job) next; + struct pf_pdesc pd; + struct pf_addr src; }; + #endif /* flags for RDR options */ @@ -2251,6 +2264,11 @@ int pf_refragment6(struct ifnet *, struct mbuf **, struct m_tag *, bool); #endif /* INET6 */ +int pf_multihome_scan_init(struct mbuf *, int, int, struct pf_pdesc *, + struct pfi_kkif *); +int pf_multihome_scan_asconf(struct mbuf *, int, int, struct pf_pdesc *, + struct pfi_kkif *); + u_int32_t pf_new_isn(struct pf_kstate *); void *pf_pull_hdr(struct mbuf *, int, void *, int, u_short *, u_short *, sa_family_t); 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 @@ -307,6 +307,8 @@ static int pf_test_state_icmp(struct pf_kstate **, struct pfi_kkif *, struct mbuf *, int, void *, struct pf_pdesc *, u_short *); +static void pf_sctp_multihome_delayed(struct pf_pdesc *, + struct pfi_kkif *, struct pf_kstate *); static int pf_test_state_sctp(struct pf_kstate **, struct pfi_kkif *, struct mbuf *, int, void *, struct pf_pdesc *, u_short *); @@ -5912,6 +5914,176 @@ return (PF_PASS); } +static void +pf_sctp_multihome_delayed(struct pf_pdesc *pd, struct pfi_kkif *kif, + struct pf_kstate *s) +{ + struct pf_sctp_multihome_job *j, *tmp; + int action, rewrite = 0; + struct pf_ksrc_node *nsn = NULL; + struct pf_kstate *sm = NULL; + struct pf_state_key *sk = NULL, *nk = NULL; + + SLIST_FOREACH_SAFE(j, &pd->sctp_multihome_jobs, next, tmp) { + action = pf_create_state(s->rule.ptr, + NULL, // NAT rule + s->anchor.ptr, + &j->pd, + nsn, + nk, sk, + NULL, // 'm' is only used for TCP + 0, // 'off' is only used for TCP + *j->pd.sport, *j->pd.dport, + &rewrite, // 'rewrite' is TCP only + kif, + &sm, + s->rule.ptr->tag, + 0, // 'bproto_sum' is TCP only + 0, // 'bip_sum' is TCP only + 0, // 'hdrlen' is TCP only + &s->match_rules); + MPASS(rewrite == 0); + if (action != PF_PASS) + printf("Failed to create SCTP multihome state?\n"); + if (sm) + PF_STATE_UNLOCK(sm); + + MPASS(rewrite == 0); + + free(j, M_PFTEMP); + } +} + +static int +pf_multihome_scan(struct mbuf *m, int start, int len, struct pf_pdesc *pd, + struct pfi_kkif *kif) +{ + int off = 0; + struct pf_sctp_multihome_job *job; + + while (off < len) { + struct sctp_paramhdr h; + + if (!pf_pull_hdr(m, start + off, &h, sizeof(h), NULL, NULL, + pd->af)) + return (PF_DROP); + + /* Parameters are at least 4 bytes. */ + if (ntohs(h.param_length) < 4) + return (PF_DROP); + + switch (ntohs(h.param_type)) { + case SCTP_IPV4_ADDRESS: { + struct in_addr t; + + if (ntohs(h.param_length) != + (sizeof(struct sctp_paramhdr) + sizeof(t))) + return (PF_DROP); + + if (!pf_pull_hdr(m, start + off + sizeof(h), &t, sizeof(t), + NULL, NULL, pd->af)) + return (PF_DROP); + + /* + * Avoid duplicating states. We'll already have + * created a state based on the source address of + * the packet, but SCTP endpoints may also list this + * address again in the INIT(_ACK) parameters. + */ + if (t.s_addr == pd->src->v4.s_addr) + break; + + /* + * We hold the state lock (idhash) here, which means + * that we can't acquire the keyhash, or we'll get a + * LOR (and potentially double-lock things too). We also + * can't release the state lock here, so instead we'll + * enqueue this for async handling. + * There's a relatively small race here, in that a + * packet using the new addresses could arrive already, + * but that's just though luck for it. + */ + job = malloc(sizeof(*job), M_PFTEMP, M_NOWAIT | M_ZERO); + if (! job) + return (PF_DROP); + + memcpy(&job->pd, pd, sizeof(*pd)); + + // New source address! + memcpy(&job->src, &t, sizeof(t)); + job->pd.src = &job->src; + + SLIST_INSERT_HEAD(&pd->sctp_multihome_jobs, job, next); + break; + } + case SCTP_IPV6_ADDRESS: { + struct in6_addr t; + + if (ntohs(h.param_length) != + (sizeof(struct sctp_paramhdr) + sizeof(t))) + return (PF_DROP); + + if (!pf_pull_hdr(m, start + off + sizeof(h), &t, sizeof(t), + NULL, NULL, pd->af)) + return (PF_DROP); + + if (memcmp(&t, &pd->src->v6, sizeof(t)) == 0) + break; + + job = malloc(sizeof(*job), M_PFTEMP, M_NOWAIT | M_ZERO); + if (! job) + return (PF_DROP); + + memcpy(&job->pd, pd, sizeof(*pd)); + memcpy(&job->src, &t, sizeof(t)); + job->pd.src = &job->src; + + SLIST_INSERT_HEAD(&pd->sctp_multihome_jobs, job, next); + break; + } + case SCTP_ADD_IP_ADDRESS: { + int ret; + struct sctp_asconf_paramhdr ah; + + if (!pf_pull_hdr(m, start + off, &ah, sizeof(ah), + NULL, NULL, pd->af)) + return (PF_DROP); + + ret = pf_multihome_scan(m, start + off + sizeof(ah), + ntohs(ah.ph.param_length) - sizeof(ah), pd, kif); + if (ret != PF_PASS) + return (ret); + break; + } + default: + break; + } + + off += roundup(ntohs(h.param_length), 4); + } + + return (PF_PASS); +} +int +pf_multihome_scan_init(struct mbuf *m, int start, int len, struct pf_pdesc *pd, + struct pfi_kkif *kif) +{ + start += sizeof(struct sctp_init_chunk); + len -= sizeof(struct sctp_init_chunk); + + return (pf_multihome_scan(m, start, len, pd, kif)); +} + +int +pf_multihome_scan_asconf(struct mbuf *m, int start, int len, + struct pf_pdesc *pd, struct pfi_kkif *kif) +{ + start += sizeof(struct sctp_asconf_chunk); + len -= sizeof(struct sctp_asconf_chunk); + + return (pf_multihome_scan(m, start, len, pd, kif)); +} + static int pf_test_state_icmp(struct pf_kstate **state, struct pfi_kkif *kif, struct mbuf *m, int off, void *h, struct pf_pdesc *pd, u_short *reason) @@ -7896,7 +8068,7 @@ /* pf_route() returns unlocked. */ if (rt) { pf_route(m0, r, kif->pfik_ifp, s, &pd, inp); - return (action); + goto out; } if (pf_dummynet(&pd, s, r, m0) != 0) { action = PF_DROP; @@ -7910,6 +8082,9 @@ if (s) PF_STATE_UNLOCK(s); +out: + pf_sctp_multihome_delayed(&pd, kif, s); + return (action); } #endif /* INET */ @@ -8441,7 +8616,7 @@ /* pf_route6() returns unlocked. */ if (rt) { pf_route6(m0, r, kif->pfik_ifp, s, &pd, inp); - return (action); + goto out; } if (pf_dummynet(&pd, s, r, m0) != 0) { action = PF_DROP; @@ -8458,8 +8633,11 @@ (mtag = m_tag_find(m, PACKET_TAG_PF_REASSEMBLED, NULL)) != NULL) action = pf_refragment6(ifp, m0, mtag, pflags & PFIL_FWD); +out: SDT_PROBE4(pf, ip, test6, done, action, reason, r, s); + pf_sctp_multihome_delayed(&pd, kif, s); + return (action); } #endif /* INET6 */ diff --git a/sys/netpfil/pf/pf_norm.c b/sys/netpfil/pf/pf_norm.c --- a/sys/netpfil/pf/pf_norm.c +++ b/sys/netpfil/pf/pf_norm.c @@ -2021,11 +2021,13 @@ } static int -pf_scan_sctp(struct mbuf *m, int ipoff, int off, struct pf_pdesc *pd) +pf_scan_sctp(struct mbuf *m, int ipoff, int off, struct pf_pdesc *pd, + struct pfi_kkif *kif) { struct sctp_chunkhdr ch = { }; int chunk_off = sizeof(struct sctphdr); int chunk_start; + int ret; while (off + chunk_off < pd->tot_len) { if (!pf_pull_hdr(m, off + chunk_off, &ch, sizeof(ch), NULL, @@ -2040,7 +2042,8 @@ chunk_off += roundup(ntohs(ch.chunk_length), 4); switch (ch.chunk_type) { - case SCTP_INITIATION: { + case SCTP_INITIATION: + case SCTP_INITIATION_ACK: { struct sctp_init_chunk init; if (!pf_pull_hdr(m, off + chunk_start, &init, @@ -2064,17 +2067,24 @@ * RFC 9260, Section 3.1, INIT chunks MUST have zero * verification tag. */ - if (pd->hdr.sctp.v_tag != 0) + if (ch.chunk_type == SCTP_INITIATION && + pd->hdr.sctp.v_tag != 0) return (PF_DROP); pd->sctp_initiate_tag = init.init.initiate_tag; - pd->sctp_flags |= PFDESC_SCTP_INIT; + if (ch.chunk_type == SCTP_INITIATION) + pd->sctp_flags |= PFDESC_SCTP_INIT; + else + pd->sctp_flags |= PFDESC_SCTP_INIT_ACK; + + ret = pf_multihome_scan_init(m, off + chunk_start, + ntohs(init.ch.chunk_length), pd, kif); + if (ret != PF_PASS) + return (ret); + break; } - case SCTP_INITIATION_ACK: - pd->sctp_flags |= PFDESC_SCTP_INIT_ACK; - break; case SCTP_ABORT_ASSOCIATION: pd->sctp_flags |= PFDESC_SCTP_ABORT; break; @@ -2092,6 +2102,14 @@ case SCTP_DATA: pd->sctp_flags |= PFDESC_SCTP_DATA; break; + case SCTP_ASCONF: + pd->sctp_flags |= PFDESC_SCTP_ASCONF; + + ret = pf_multihome_scan_asconf(m, off + chunk_start, + ntohs(ch.chunk_length), pd, kif); + if (ret != PF_PASS) + return (ret); + break; default: pd->sctp_flags |= PFDESC_SCTP_OTHER; break; @@ -2133,7 +2151,7 @@ /* Unconditionally scan the SCTP packet, because we need to look for * things like shutdown and asconf chunks. */ - if (pf_scan_sctp(m, ipoff, off, pd) != PF_PASS) + if (pf_scan_sctp(m, ipoff, off, pd, kif) != PF_PASS) goto sctp_drop; r = TAILQ_FIRST(pf_main_ruleset.rules[PF_RULESET_SCRUB].active.ptr);