The branch stable/13 has been updated by kp:

URL: 
https://cgit.FreeBSD.org/src/commit/?id=9b60a37c1e9b77162cf3816ef32a50345c111c59

commit 9b60a37c1e9b77162cf3816ef32a50345c111c59
Author:     Kristof Provost <[email protected]>
AuthorDate: 2023-04-27 08:58:02 +0000
Commit:     Kristof Provost <[email protected]>
CommitDate: 2023-08-11 12:13:09 +0000

    pf: initial SCTP support
    
    Basic state tracking for SCTP. This means we scan through the packet to
    identify the different chunks (so we can identify state changes).
    
    MFC after:      3 weeks
    Sponsored by:   Orange Business Services
    Differential Revision:  https://reviews.freebsd.org/D40862
    
    (cherry picked from commit 010ee43f5673eea4c86f846893eadc3c5529b2f8)
---
 sbin/pfctl/pf_print_state.c |  34 +++++++++
 sys/net/pfvar.h             |  13 ++++
 sys/netpfil/pf/pf.c         | 133 ++++++++++++++++++++++++++++++++
 sys/netpfil/pf/pf_norm.c    | 180 ++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 360 insertions(+)

diff --git a/sbin/pfctl/pf_print_state.c b/sbin/pfctl/pf_print_state.c
index b66a296d6080..a6c1ab9bf267 100644
--- a/sbin/pfctl/pf_print_state.c
+++ b/sbin/pfctl/pf_print_state.c
@@ -41,6 +41,7 @@ __FBSDID("$FreeBSD$");
 #include <net/if.h>
 #define TCPSTATES
 #include <netinet/tcp_fsm.h>
+#include <netinet/sctp.h>
 #include <net/pfvar.h>
 #include <arpa/inet.h>
 #include <netdb.h>
@@ -206,6 +207,36 @@ print_seq(struct pfctl_state_peer *p)
                    p->seqhi - p->seqlo);
 }
 
+
+static const char *
+sctp_state_name(int state)
+{
+       switch (state) {
+       case SCTP_CLOSED:
+               return ("CLOSED");
+       case SCTP_BOUND:
+               return ("BOUND");
+       case SCTP_LISTEN:
+               return ("LISTEN");
+       case SCTP_COOKIE_WAIT:
+               return ("COOKIE_WAIT");
+       case SCTP_COOKIE_ECHOED:
+               return ("COOKIE_ECHOED");
+       case SCTP_ESTABLISHED:
+               return ("ESTABLISHED");
+       case SCTP_SHUTDOWN_SENT:
+               return ("SHUTDOWN_SENT");
+       case SCTP_SHUTDOWN_RECEIVED:
+               return ("SHUTDOWN_RECEIVED");
+       case SCTP_SHUTDOWN_ACK_SENT:
+               return ("SHUTDOWN_ACK_SENT");
+       case SCTP_SHUTDOWN_PENDING:
+               return ("SHUTDOWN_PENDING");
+       default:
+               return ("?");
+       }
+}
+
 void
 print_state(struct pfctl_state *s, int opts)
 {
@@ -300,6 +331,9 @@ print_state(struct pfctl_state *s, int opts)
                const char *states[] = PFUDPS_NAMES;
 
                printf("   %s:%s\n", states[src->state], states[dst->state]);
+       } else if (proto == IPPROTO_SCTP) {
+               printf("   %s:%s\n", sctp_state_name(src->state),
+                   sctp_state_name(dst->state));
 #ifndef INET6
        } else if (proto != IPPROTO_ICMP && src->state < PFOTHERS_NSTATES &&
            dst->state < PFOTHERS_NSTATES) {
diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h
index 4b7f34122337..55bd25a3d29e 100644
--- a/sys/net/pfvar.h
+++ b/sys/net/pfvar.h
@@ -56,6 +56,7 @@
 #include <netinet/ip.h>
 #include <netinet/tcp.h>
 #include <netinet/udp.h>
+#include <netinet/sctp.h>
 #include <netinet/ip_icmp.h>
 #include <netinet/icmp6.h>
 #endif
@@ -1290,6 +1291,7 @@ struct pf_pdesc {
        union pf_headers {
                struct tcphdr           tcp;
                struct udphdr           udp;
+               struct sctphdr          sctp;
                struct icmp             icmp;
 #ifdef INET6
                struct icmp6_hdr        icmp6;
@@ -1319,6 +1321,15 @@ struct pf_pdesc {
        u_int8_t         dir;           /* direction */
        u_int8_t         sidx;          /* key index for source */
        u_int8_t         didx;          /* key index for destination */
+#define PFDESC_SCTP_INIT       0x0001
+#define PFDESC_SCTP_INIT_ACK   0x0002
+#define PFDESC_SCTP_COOKIE     0x0004
+#define PFDESC_SCTP_ABORT      0x0008
+#define PFDESC_SCTP_SHUTDOWN   0x0010
+#define PFDESC_SCTP_SHUTDOWN_COMPLETE  0x0020
+#define PFDESC_SCTP_DATA       0x0040
+#define PFDESC_SCTP_OTHER      0x0080
+       u_int16_t        sctp_flags;
 };
 #endif
 
@@ -2013,6 +2024,8 @@ int       pf_normalize_tcp_init(struct mbuf *, int, 
struct pf_pdesc *,
 int    pf_normalize_tcp_stateful(struct mbuf *, int, struct pf_pdesc *,
            u_short *, struct tcphdr *, struct pf_kstate *,
            struct pf_state_peer *, struct pf_state_peer *, int *);
+int    pf_normalize_sctp(int, struct pfi_kkif *, struct mbuf *, int,
+           int, void *, struct pf_pdesc *);
 u_int32_t
        pf_state_expires(const struct pf_kstate *);
 void   pf_purge_expired_fragments(void);
diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c
index 57c9bb3bf3b1..16e0ee762f6a 100644
--- a/sys/netpfil/pf/pf.c
+++ b/sys/netpfil/pf/pf.c
@@ -290,6 +290,9 @@ static int           pf_test_state_udp(struct pf_kstate **, 
int,
 static int              pf_test_state_icmp(struct pf_kstate **, int,
                            struct pfi_kkif *, struct mbuf *, int,
                            void *, struct pf_pdesc *, u_short *);
+static int              pf_test_state_sctp(struct pf_kstate **,
+                           struct pfi_kkif *, struct mbuf *, int,
+                           void *, struct pf_pdesc *, u_short *);
 static int              pf_test_state_other(struct pf_kstate **, int,
                            struct pfi_kkif *, struct mbuf *, struct pf_pdesc 
*);
 static u_int16_t        pf_calc_mss(struct pf_addr *, sa_family_t,
@@ -3700,6 +3703,11 @@ pf_test_rule(struct pf_krule **rm, struct pf_kstate 
**sm, int direction,
                dport = pd->hdr.udp.uh_dport;
                hdrlen = sizeof(pd->hdr.udp);
                break;
+       case IPPROTO_SCTP:
+               sport = pd->hdr.sctp.src_port;
+               dport = pd->hdr.sctp.dest_port;
+               hdrlen = sizeof(pd->hdr.sctp);
+               break;
 #ifdef INET
        case IPPROTO_ICMP:
                if (pd->af != AF_INET)
@@ -4138,6 +4146,11 @@ pf_create_state(struct pf_krule *r, struct pf_krule *nr, 
struct pf_krule *a,
                pf_set_protostate(s, PF_PEER_DST, PFUDPS_NO_TRAFFIC);
                s->timeout = PFTM_UDP_FIRST_PACKET;
                break;
+       case IPPROTO_SCTP:
+               pf_set_protostate(s, PF_PEER_SRC, SCTP_COOKIE_WAIT);
+               pf_set_protostate(s, PF_PEER_DST, SCTP_CLOSED);
+               s->timeout = PFTM_TCP_FIRST_PACKET;
+               break;
        case IPPROTO_ICMP:
 #ifdef INET6
        case IPPROTO_ICMPV6:
@@ -5693,6 +5706,66 @@ pf_test_state_icmp(struct pf_kstate **state, int 
direction, struct pfi_kkif *kif
        }
 }
 
+static int
+pf_test_state_sctp(struct pf_kstate **state, struct pfi_kkif *kif,
+    struct mbuf *m, int off, void *h, struct pf_pdesc *pd, u_short *reason)
+{
+       struct pf_state_key_cmp  key;
+       struct pf_state_peer    *src; //, *dst;
+       struct sctphdr          *sh = &pd->hdr.sctp;
+       u_int8_t                 psrc; //, pdst;
+
+       bzero(&key, sizeof(key));
+       key.af = pd->af;
+       key.proto = IPPROTO_SCTP;
+       if (pd->dir == PF_IN)   {       /* wire side, straight */
+               PF_ACPY(&key.addr[0], pd->src, key.af);
+               PF_ACPY(&key.addr[1], pd->dst, key.af);
+               key.port[0] = sh->src_port;
+               key.port[1] = sh->dest_port;
+       } else {                        /* stack side, reverse */
+               PF_ACPY(&key.addr[1], pd->src, key.af);
+               PF_ACPY(&key.addr[0], pd->dst, key.af);
+               key.port[1] = sh->src_port;
+               key.port[0] = sh->dest_port;
+       }
+
+       STATE_LOOKUP(kif, &key, pd->dir, *state, pd);
+
+       if (pd->dir == (*state)->direction) {
+               src = &(*state)->src;
+               psrc = PF_PEER_SRC;
+       } else {
+               src = &(*state)->dst;
+               psrc = PF_PEER_DST;
+       }
+
+       /* Track state. */
+       if (pd->sctp_flags & PFDESC_SCTP_INIT) {
+               if (src->state < SCTP_COOKIE_WAIT) {
+                       pf_set_protostate(*state, psrc, SCTP_COOKIE_WAIT);
+                       (*state)->timeout = PFTM_TCP_OPENING;
+               }
+       }
+       if (pd->sctp_flags & PFDESC_SCTP_COOKIE) {
+               if (src->state < SCTP_ESTABLISHED) {
+                       pf_set_protostate(*state, psrc, SCTP_ESTABLISHED);
+                       (*state)->timeout = PFTM_TCP_ESTABLISHED;
+               }
+       }
+       if (pd->sctp_flags & (PFDESC_SCTP_SHUTDOWN | PFDESC_SCTP_ABORT |
+           PFDESC_SCTP_SHUTDOWN_COMPLETE)) {
+               if (src->state < SCTP_SHUTDOWN_PENDING) {
+                       pf_set_protostate(*state, psrc, SCTP_SHUTDOWN_PENDING);
+                       (*state)->timeout = PFTM_TCP_CLOSING;
+               }
+       }
+
+       (*state)->expire = time_uptime;
+
+       return (PF_PASS);
+}
+
 static int
 pf_test_state_other(struct pf_kstate **state, int direction, struct pfi_kkif 
*kif,
     struct mbuf *m, struct pf_pdesc *pd)
@@ -6550,6 +6623,36 @@ pf_test(int dir, int pflags, struct ifnet *ifp, struct 
mbuf **m0, struct inpcb *
                break;
        }
 
+       case IPPROTO_SCTP: {
+               if (!pf_pull_hdr(m, off, &pd.hdr.sctp, sizeof(pd.hdr.sctp),
+                   &action, &reason, AF_INET)) {
+                       log = action != PF_PASS;
+                       goto done;
+               }
+               pd.sport = &pd.hdr.sctp.src_port;
+               pd.dport = &pd.hdr.sctp.dest_port;
+               if (pd.hdr.sctp.src_port == 0 || pd.hdr.sctp.dest_port == 0) {
+                       action = PF_DROP;
+                       REASON_SET(&reason, PFRES_SHORT);
+                       goto done;
+               }
+               action = pf_normalize_sctp(dir, kif, m, 0, off, h, &pd);
+               if (action == PF_DROP)
+                       goto done;
+               action = pf_test_state_sctp(&s, kif, m, off, h, &pd,
+                   &reason);
+               if (action == PF_PASS) {
+                       if (V_pfsync_update_state_ptr != NULL)
+                               V_pfsync_update_state_ptr(s);
+                       r = s->rule.ptr;
+                       a = s->anchor.ptr;
+               } else {
+                       action = pf_test_rule(&r, &s, pd.dir, kif, m, off,
+                           &pd, &a, &ruleset, inp);
+               }
+               break;
+       }
+
        case IPPROTO_ICMP: {
                if (!pf_pull_hdr(m, off, &pd.hdr.icmp, ICMP_MINLEN,
                    &action, &reason, AF_INET)) {
@@ -6996,6 +7099,36 @@ pf_test6(int dir, int pflags, struct ifnet *ifp, struct 
mbuf **m0, struct inpcb
                break;
        }
 
+       case IPPROTO_SCTP: {
+               if (!pf_pull_hdr(m, off, &pd.hdr.sctp, sizeof(pd.hdr.sctp),
+                   &action, &reason, AF_INET6)) {
+                       log = action != PF_PASS;
+                       goto done;
+               }
+               pd.sport = &pd.hdr.sctp.src_port;
+               pd.dport = &pd.hdr.sctp.dest_port;
+               if (pd.hdr.sctp.src_port == 0 || pd.hdr.sctp.dest_port == 0) {
+                       action = PF_DROP;
+                       REASON_SET(&reason, PFRES_SHORT);
+                       goto done;
+               }
+               action = pf_normalize_sctp(dir, kif, m, 0, off, h, &pd);
+               if (action == PF_DROP)
+                       goto done;
+               action = pf_test_state_sctp(&s, kif, m, off, h, &pd,
+                   &reason);
+               if (action == PF_PASS) {
+                       if (V_pfsync_update_state_ptr != NULL)
+                               V_pfsync_update_state_ptr(s);
+                       r = s->rule.ptr;
+                       a = s->anchor.ptr;
+               } else {
+                       action = pf_test_rule(&r, &s, pd.dir, kif, m, off,
+                           &pd, &a, &ruleset, inp);
+               }
+               break;
+       }
+
        case IPPROTO_ICMP: {
                action = PF_DROP;
                DPFPRINTF(PF_DEBUG_MISC,
diff --git a/sys/netpfil/pf/pf_norm.c b/sys/netpfil/pf/pf_norm.c
index ae026fb9cee1..06aa577b45a7 100644
--- a/sys/netpfil/pf/pf_norm.c
+++ b/sys/netpfil/pf/pf_norm.c
@@ -55,6 +55,8 @@ __FBSDID("$FreeBSD$");
 #include <netinet/tcp.h>
 #include <netinet/tcp_fsm.h>
 #include <netinet/tcp_seq.h>
+#include <netinet/sctp_constants.h>
+#include <netinet/sctp_header.h>
 
 #ifdef INET6
 #include <netinet/ip6.h>
@@ -1988,6 +1990,184 @@ pf_normalize_tcpopt(struct pf_krule *r, struct mbuf *m, 
struct tcphdr *th,
        return (rewrite);
 }
 
+static int
+pf_scan_sctp(struct mbuf *m, int ipoff, int off, struct pf_pdesc *pd)
+{
+       struct sctp_chunkhdr ch = { };
+       int chunk_off = sizeof(struct sctphdr);
+       int chunk_start;
+
+       while (off + chunk_off < pd->tot_len) {
+               if (!pf_pull_hdr(m, off + chunk_off, &ch, sizeof(ch), NULL,
+                   NULL, pd->af))
+                       return (PF_DROP);
+
+               /* Length includes the header, this must be at least 4. */
+               if (ntohs(ch.chunk_length) < 4)
+                       return (PF_DROP);
+
+               chunk_start = chunk_off;
+               chunk_off += roundup(ntohs(ch.chunk_length), 4);
+
+               switch (ch.chunk_type) {
+               case SCTP_INITIATION: {
+                       struct sctp_init_chunk init;
+
+                       if (!pf_pull_hdr(m, off + chunk_start, &init,
+                           sizeof(init), NULL, NULL, pd->af))
+                               return (PF_DROP);
+
+                       /*
+                        * RFC 9620, Section 3.3.2, "The Initiate Tag is 
allowed to have
+                        * any value except 0."
+                        */
+                       if (init.init.initiate_tag == 0)
+                               return (PF_DROP);
+                       if (init.init.num_inbound_streams == 0)
+                               return (PF_DROP);
+                       if (init.init.num_outbound_streams == 0)
+                               return (PF_DROP);
+                       if (ntohl(init.init.a_rwnd) < SCTP_MIN_RWND)
+                               return (PF_DROP);
+
+                       /*
+                        * RFC 9260, Section 3.1, INIT chunks MUST have zero
+                        * verification tag.
+                        */
+                       if (pd->hdr.sctp.v_tag != 0)
+                               return (PF_DROP);
+
+                       pd->sctp_flags |= PFDESC_SCTP_INIT;
+                       break;
+               }
+               case SCTP_INITIATION_ACK:
+                       pd->sctp_flags |= PFDESC_SCTP_INIT_ACK;
+                       break;
+               case SCTP_ABORT_ASSOCIATION:
+                       pd->sctp_flags |= PFDESC_SCTP_ABORT;
+                       break;
+               case SCTP_SHUTDOWN:
+               case SCTP_SHUTDOWN_ACK:
+                       pd->sctp_flags |= PFDESC_SCTP_SHUTDOWN;
+                       break;
+               case SCTP_SHUTDOWN_COMPLETE:
+                       pd->sctp_flags |= PFDESC_SCTP_SHUTDOWN_COMPLETE;
+                       break;
+               case SCTP_COOKIE_ECHO:
+               case SCTP_COOKIE_ACK:
+                       pd->sctp_flags |= PFDESC_SCTP_COOKIE;
+                       break;
+               case SCTP_DATA:
+                       pd->sctp_flags |= PFDESC_SCTP_DATA;
+                       break;
+               default:
+                       pd->sctp_flags |= PFDESC_SCTP_OTHER;
+                       break;
+               }
+       }
+
+       /* Validate chunk lengths vs. packet length. */
+       if (off + chunk_off != pd->tot_len)
+               return (PF_DROP);
+
+       /*
+        * INIT, INIT_ACK or SHUTDOWN_COMPLETE chunks must always be the only
+        * one in a packet.
+        */
+       if ((pd->sctp_flags & PFDESC_SCTP_INIT) &&
+           (pd->sctp_flags & ~PFDESC_SCTP_INIT))
+               return (PF_DROP);
+       if ((pd->sctp_flags & PFDESC_SCTP_INIT_ACK) &&
+           (pd->sctp_flags & ~PFDESC_SCTP_INIT_ACK))
+               return (PF_DROP);
+       if ((pd->sctp_flags & PFDESC_SCTP_SHUTDOWN_COMPLETE) &&
+           (pd->sctp_flags & ~PFDESC_SCTP_SHUTDOWN_COMPLETE))
+               return (PF_DROP);
+
+       return (PF_PASS);
+}
+
+int
+pf_normalize_sctp(int dir, struct pfi_kkif *kif, struct mbuf *m, int ipoff,
+    int off, void *h, struct pf_pdesc *pd)
+{
+       struct pf_krule *r, *rm = NULL;
+       struct sctphdr  *sh = &pd->hdr.sctp;
+       u_short          reason;
+       sa_family_t      af = pd->af;
+       int              srs;
+
+       PF_RULES_RASSERT();
+
+       /* 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)
+               goto sctp_drop;
+
+       r = TAILQ_FIRST(pf_main_ruleset.rules[PF_RULESET_SCRUB].active.ptr);
+       /* Check if there any scrub rules. Lack of scrub rules means enforced
+        * packet normalization operation just like in OpenBSD. */
+       srs = (r != NULL);
+       while (r != NULL) {
+               pf_counter_u64_add(&r->evaluations, 1);
+               if (pfi_kkif_match(r->kif, kif) == r->ifnot)
+                       r = r->skip[PF_SKIP_IFP].ptr;
+               else if (r->direction && r->direction != dir)
+                       r = r->skip[PF_SKIP_DIR].ptr;
+               else if (r->af && r->af != af)
+                       r = r->skip[PF_SKIP_AF].ptr;
+               else if (r->proto && r->proto != pd->proto)
+                       r = r->skip[PF_SKIP_PROTO].ptr;
+               else if (PF_MISMATCHAW(&r->src.addr, pd->src, af,
+                   r->src.neg, kif, M_GETFIB(m)))
+                       r = r->skip[PF_SKIP_SRC_ADDR].ptr;
+               else if (r->src.port_op && !pf_match_port(r->src.port_op,
+                           r->src.port[0], r->src.port[1], sh->src_port))
+                       r = r->skip[PF_SKIP_SRC_PORT].ptr;
+               else if (PF_MISMATCHAW(&r->dst.addr, pd->dst, af,
+                   r->dst.neg, NULL, M_GETFIB(m)))
+                       r = r->skip[PF_SKIP_DST_ADDR].ptr;
+               else if (r->dst.port_op && !pf_match_port(r->dst.port_op,
+                           r->dst.port[0], r->dst.port[1], sh->dest_port))
+                       r = r->skip[PF_SKIP_DST_PORT].ptr;
+               else {
+                       rm = r;
+                       break;
+               }
+       }
+
+       if (srs) {
+               /* With scrub rules present SCTP normalization happens only
+                * if one of rules has matched and it's not a "no scrub" rule */
+               if (rm == NULL || rm->action == PF_NOSCRUB)
+                       return (PF_PASS);
+
+               pf_counter_u64_critical_enter();
+               pf_counter_u64_add_protected(&r->packets[dir == PF_OUT], 1);
+               pf_counter_u64_add_protected(&r->bytes[dir == PF_OUT], 
pd->tot_len);
+               pf_counter_u64_critical_exit();
+       }
+
+       /* Verify we're a multiple of 4 bytes long */
+       if ((pd->tot_len - off - sizeof(struct sctphdr)) % 4)
+               goto sctp_drop;
+
+       /* INIT chunk needs to be the only chunk */
+       if (pd->sctp_flags & PFDESC_SCTP_INIT)
+               if (pd->sctp_flags & ~PFDESC_SCTP_INIT)
+                       goto sctp_drop;
+
+       return (PF_PASS);
+
+sctp_drop:
+       REASON_SET(&reason, PFRES_NORM);
+       if (rm != NULL && r->log)
+               PFLOG_PACKET(kif, m, AF_INET, pd->dir, reason, r, NULL, NULL, 
pd,
+                   1);
+
+       return (PF_DROP);
+}
+
 #ifdef INET
 static void
 pf_scrub_ip(struct mbuf **m0, u_int32_t flags, u_int8_t min_ttl, u_int8_t tos)

Reply via email to