git: 6053adafaa54 - main - pf: add SCTP NAT support

From: Kristof Provost <kp_at_FreeBSD.org>
Date: Fri, 21 Jul 2023 10:32:42 UTC
The branch main has been updated by kp:

URL: https://cgit.FreeBSD.org/src/commit/?id=6053adafaa54204f91c43939fa334bde835403cb

commit 6053adafaa54204f91c43939fa334bde835403cb
Author:     Kristof Provost <kp@FreeBSD.org>
AuthorDate: 2023-06-01 13:04:48 +0000
Commit:     Kristof Provost <kp@FreeBSD.org>
CommitDate: 2023-07-21 10:32:19 +0000

    pf: add SCTP NAT support
    
    Support NAT-ing SCTP connections.
    
    This is mostly similar to UDP and TCP, but we refuse to change ports for
    SCTP, to avoid interfering with multihomed connections.
    
    As a result we also never copy the SCTP header back or recalculate
    checksums as we'd do for TCP or UDP (because we don't modify the header
    for SCTP).
    
    We do use the existing pf_change_ap() function to modify the packet,
    because we may still need to update the IPv4 header checksum.
    
    Reviewed by:    tuexen
    MFC after:      3 weeks
    Sponsored by:   Orange Business Services
    Differential Revision:  https://reviews.freebsd.org/D40866
---
 sys/netpfil/pf/pf.c    | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++
 sys/netpfil/pf/pf_lb.c | 14 ++++++++++++-
 2 files changed, 69 insertions(+), 1 deletion(-)

diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c
index 70a00147ccf2..9162f8cf949b 100644
--- a/sys/netpfil/pf/pf.c
+++ b/sys/netpfil/pf/pf.c
@@ -528,6 +528,23 @@ pf_packet_rework_nat(struct mbuf *m, struct pf_pdesc *pd, int off,
 		m_copyback(m, off, sizeof(*uh), (caddr_t)uh);
 		break;
 	}
+	case IPPROTO_SCTP: {
+		struct sctphdr *sh = &pd->hdr.sctp;
+		uint16_t checksum = 0;
+
+		if (PF_ANEQ(pd->src, &nk->addr[pd->sidx], pd->af)) {
+			pf_change_ap(m, pd->src, &sh->src_port, pd->ip_sum,
+			    &checksum, &nk->addr[pd->sidx],
+			    nk->port[pd->sidx], 1, pd->af);
+		}
+		if (PF_ANEQ(pd->dst, &nk->addr[pd->didx], pd->af)) {
+			pf_change_ap(m, pd->dst, &sh->dest_port, pd->ip_sum,
+			    &checksum, &nk->addr[pd->didx],
+			    nk->port[pd->didx], 1, pd->af);
+		}
+
+		break;
+	}
 	case IPPROTO_ICMP: {
 		struct icmp *ih = &pd->hdr.icmp;
 
@@ -4472,6 +4489,25 @@ pf_test_rule(struct pf_krule **rm, struct pf_kstate **sm, struct pfi_kkif *kif,
 			}
 			rewrite++;
 			break;
+		case IPPROTO_SCTP: {
+			uint16_t checksum = 0;
+
+			if (PF_ANEQ(saddr, &nk->addr[pd->sidx], af) ||
+			    nk->port[pd->sidx] != sport) {
+				pf_change_ap(m, saddr, &pd->hdr.sctp.src_port,
+				    pd->ip_sum, &checksum,
+				    &nk->addr[pd->sidx],
+				    nk->port[pd->sidx], 1, af);
+			}
+			if (PF_ANEQ(daddr, &nk->addr[pd->didx], af) ||
+			    nk->port[pd->didx] != dport) {
+				pf_change_ap(m, daddr, &pd->hdr.sctp.dest_port,
+				    pd->ip_sum, &checksum,
+				    &nk->addr[pd->didx],
+				    nk->port[pd->didx], 1, af);
+			}
+			break;
+		}
 #ifdef INET
 		case IPPROTO_ICMP:
 			nk->port[0] = nk->port[1];
@@ -5856,6 +5892,26 @@ pf_test_state_sctp(struct pf_kstate **state, struct pfi_kkif *kif,
 
 	(*state)->expire = time_uptime;
 
+	/* translate source/destination address, if necessary */
+	if ((*state)->key[PF_SK_WIRE] != (*state)->key[PF_SK_STACK]) {
+		uint16_t checksum = 0;
+		struct pf_state_key *nk = (*state)->key[pd->didx];
+
+		if (PF_ANEQ(pd->src, &nk->addr[pd->sidx], pd->af) ||
+		    nk->port[pd->sidx] != pd->hdr.sctp.src_port) {
+			pf_change_ap(m, pd->src, &pd->hdr.sctp.src_port,
+			    pd->ip_sum, &checksum, &nk->addr[pd->sidx],
+			    nk->port[pd->sidx], 1, pd->af);
+		}
+
+		if (PF_ANEQ(pd->dst, &nk->addr[pd->didx], pd->af) ||
+		    nk->port[pd->didx] != pd->hdr.sctp.dest_port) {
+			pf_change_ap(m, pd->dst, &pd->hdr.sctp.dest_port,
+			    pd->ip_sum, &checksum, &nk->addr[pd->didx],
+			    nk->port[pd->didx], 1, pd->af);
+		}
+	}
+
 	return (PF_PASS);
 }
 
diff --git a/sys/netpfil/pf/pf_lb.c b/sys/netpfil/pf/pf_lb.c
index 98e1a2d32488..3055741f1fc0 100644
--- a/sys/netpfil/pf/pf_lb.c
+++ b/sys/netpfil/pf/pf_lb.c
@@ -240,7 +240,15 @@ pf_get_sport(sa_family_t af, u_int8_t proto, struct pf_krule *r,
 		 * port search; start random, step;
 		 * similar 2 portloop in in_pcbbind
 		 */
-		if (!(proto == IPPROTO_TCP || proto == IPPROTO_UDP ||
+		if (proto == IPPROTO_SCTP) {
+			key.port[1] = sport;
+			if (!pf_find_state_all_exists(&key, PF_IN)) {
+				*nport = sport;
+				return (0);
+			} else {
+				return (1); /* Fail mapping. */
+			}
+		} else if (!(proto == IPPROTO_TCP || proto == IPPROTO_UDP ||
 		    proto == IPPROTO_ICMP) || (low == 0 && high == 0)) {
 			/*
 			 * XXX bug: icmp states don't use the id on both sides.
@@ -711,6 +719,10 @@ pf_get_translation(struct pf_pdesc *pd, struct mbuf *m, int off,
 			PF_POOLMASK(naddr, naddr, &r->rpool.cur->addr.v.a.mask,
 			    daddr, pd->af);
 
+		/* Do not change SCTP ports. */
+		if (pd->proto == IPPROTO_SCTP)
+			break;
+
 		if (r->rpool.proxy_port[1]) {
 			uint32_t	tmp_nport;