git: 4a2b92d99fee - main - sctp: initial implementation of draft-tuexen-tsvwg-sctp-zero-checksum

From: Michael Tuexen <tuexen_at_FreeBSD.org>
Date: Fri, 10 Mar 2023 00:46:21 UTC
The branch main has been updated by tuexen:

URL: https://cgit.FreeBSD.org/src/commit/?id=4a2b92d99fee987e87a5c3789e0892ab6e680018

commit 4a2b92d99fee987e87a5c3789e0892ab6e680018
Author:     Michael Tuexen <tuexen@FreeBSD.org>
AuthorDate: 2023-03-10 00:45:46 +0000
Commit:     Michael Tuexen <tuexen@FreeBSD.org>
CommitDate: 2023-03-10 00:45:46 +0000

    sctp: initial implementation of draft-tuexen-tsvwg-sctp-zero-checksum
---
 sys/netinet/sctp.h           |   1 +
 sys/netinet/sctp_constants.h |   2 +-
 sys/netinet/sctp_input.c     | 157 ++++++++++++++++++++++++++++++-------------
 sys/netinet/sctp_output.c    |  71 ++++++++++++++++---
 sys/netinet/sctp_pcb.c       |   9 +++
 sys/netinet/sctp_pcb.h       |   1 +
 sys/netinet/sctp_structs.h   |   6 ++
 sys/netinet/sctp_sysctl.c    |   5 ++
 sys/netinet/sctp_sysctl.h    |   8 ++-
 sys/netinet/sctp_uio.h       |   6 +-
 sys/netinet/sctp_usrreq.c    |  23 +++++++
 sys/netinet/sctputil.c       |   1 +
 12 files changed, 232 insertions(+), 58 deletions(-)

diff --git a/sys/netinet/sctp.h b/sys/netinet/sctp.h
index d67b33acd8ad..95dd85c5d8c0 100644
--- a/sys/netinet/sctp.h
+++ b/sys/netinet/sctp.h
@@ -129,6 +129,7 @@ struct sctp_paramhdr {
 #define SCTP_NRSACK_SUPPORTED           0x00000030
 #define SCTP_PKTDROP_SUPPORTED          0x00000031
 #define SCTP_MAX_CWND                   0x00000032
+#define SCTP_ACCEPT_ZERO_CHECKSUM       0x00000033
 
 /*
  * read-only options
diff --git a/sys/netinet/sctp_constants.h b/sys/netinet/sctp_constants.h
index 3df6ad6db2aa..3772a933bf85 100644
--- a/sys/netinet/sctp_constants.h
+++ b/sys/netinet/sctp_constants.h
@@ -410,7 +410,7 @@ __FBSDID("$FreeBSD$");
 
 /*************0x8000 series*************/
 #define SCTP_ECN_CAPABLE		0x8000
-
+#define SCTP_ZERO_CHECKSUM_ACCEPTABLE	0x8001
 /* RFC 4895 */
 #define SCTP_RANDOM			0x8002
 #define SCTP_CHUNK_LIST			0x8003
diff --git a/sys/netinet/sctp_input.c b/sys/netinet/sctp_input.c
index 3f5046811258..dfc74e1e84f3 100644
--- a/sys/netinet/sctp_input.c
+++ b/sys/netinet/sctp_input.c
@@ -5263,73 +5263,138 @@ sctp_common_input_processing(struct mbuf **mm, int iphlen, int offset, int lengt
     uint8_t mflowtype, uint32_t mflowid, uint16_t fibnum,
     uint32_t vrf_id, uint16_t port)
 {
-	uint32_t high_tsn;
-	int fwd_tsn_seen = 0, data_processed = 0;
-	struct mbuf *m = *mm, *op_err;
 	char msg[SCTP_DIAG_INFO_LEN];
-	int un_sent;
-	int cnt_ctrl_ready = 0;
+	struct mbuf *m = *mm, *op_err;
 	struct sctp_inpcb *inp = NULL, *inp_decr = NULL;
 	struct sctp_tcb *stcb = NULL;
 	struct sctp_nets *net = NULL;
+	uint32_t high_tsn;
+	uint32_t cksum_in_hdr;
+	int un_sent;
+	int cnt_ctrl_ready = 0;
+	int fwd_tsn_seen = 0, data_processed = 0;
+	bool cksum_validated, stcb_looked_up;
 
 	SCTP_STAT_INCR(sctps_recvdatagrams);
 #ifdef SCTP_AUDITING_ENABLED
 	sctp_audit_log(0xE0, 1);
 	sctp_auditing(0, inp, stcb, net);
 #endif
+
+	stcb_looked_up = false;
 	if (compute_crc != 0) {
-		uint32_t check, calc_check;
-
-		check = sh->checksum;
-		sh->checksum = 0;
-		calc_check = sctp_calculate_cksum(m, iphlen);
-		sh->checksum = check;
-		if (calc_check != check) {
-			SCTPDBG(SCTP_DEBUG_INPUT1, "Bad CSUM on SCTP packet calc_check:%x check:%x  m:%p mlen:%d iphlen:%d\n",
-			    calc_check, check, (void *)m, length, iphlen);
-			stcb = sctp_findassociation_addr(m, offset, src, dst,
-			    sh, ch, &inp, &net, vrf_id);
+		cksum_validated = false;
+		cksum_in_hdr = sh->checksum;
+		if (cksum_in_hdr != htonl(0)) {
+			uint32_t cksum_calculated;
+
+	validate_cksum:
+			sh->checksum = 0;
+			cksum_calculated = sctp_calculate_cksum(m, iphlen);
+			sh->checksum = cksum_in_hdr;
+			if (cksum_calculated != cksum_in_hdr) {
+				if (stcb_looked_up) {
+					/*
+					 * The packet has a zero checksum,
+					 * which is not the correct CRC, no
+					 * stcb has been found or an stcb
+					 * has been found but an incorrect
+					 * zero checksum is not acceptable.
+					 */
+					KASSERT(cksum_in_hdr == htonl(0),
+					    ("cksum in header not zero: %x",
+					    ntohl(cksum_in_hdr)));
+					if ((inp == NULL) &&
+					    (SCTP_BASE_SYSCTL(sctp_ootb_with_zero_cksum) == 1)) {
+						/*
+						 * This is an OOTB packet,
+						 * depending on the sysctl
+						 * variable, pretend that
+						 * the checksum is
+						 * acceptable, to allow an
+						 * appropriate response
+						 * (ABORT, for examlpe) to
+						 * be sent.
+						 */
+						KASSERT(stcb == NULL,
+						    ("stcb is %p", stcb));
+						SCTP_STAT_INCR(sctps_recvzerocrc);
+						goto cksum_validated;
+					}
+				} else {
+					stcb = sctp_findassociation_addr(m, offset, src, dst,
+					    sh, ch, &inp, &net, vrf_id);
+				}
+				SCTPDBG(SCTP_DEBUG_INPUT1, "Bad cksum in SCTP packet:%x calculated:%x m:%p mlen:%d iphlen:%d\n",
+				    ntohl(cksum_in_hdr), ntohl(cksum_calculated), (void *)m, length, iphlen);
 #if defined(INET) || defined(INET6)
-			if ((ch->chunk_type != SCTP_INITIATION) &&
-			    (net != NULL) && (net->port != port)) {
-				if (net->port == 0) {
-					/* UDP encapsulation turned on. */
-					net->mtu -= sizeof(struct udphdr);
-					if (stcb->asoc.smallest_mtu > net->mtu) {
-						sctp_pathmtu_adjustment(stcb, net->mtu, true);
+				if ((ch->chunk_type != SCTP_INITIATION) &&
+				    (net != NULL) && (net->port != port)) {
+					if (net->port == 0) {
+						/*
+						 * UDP encapsulation turned
+						 * on.
+						 */
+						net->mtu -= sizeof(struct udphdr);
+						if (stcb->asoc.smallest_mtu > net->mtu) {
+							sctp_pathmtu_adjustment(stcb, net->mtu, true);
+						}
+					} else if (port == 0) {
+						/*
+						 * UDP encapsulation turned
+						 * off.
+						 */
+						net->mtu += sizeof(struct udphdr);
+						/* XXX Update smallest_mtu */
 					}
-				} else if (port == 0) {
-					/* UDP encapsulation turned off. */
-					net->mtu += sizeof(struct udphdr);
-					/* XXX Update smallest_mtu */
+					net->port = port;
 				}
-				net->port = port;
-			}
 #endif
-			if (net != NULL) {
-				net->flowtype = mflowtype;
-				net->flowid = mflowid;
+				if (net != NULL) {
+					net->flowtype = mflowtype;
+					net->flowid = mflowid;
+				}
+				SCTP_PROBE5(receive, NULL, stcb, m, stcb, sh);
+				if ((inp != NULL) && (stcb != NULL)) {
+					if (stcb->asoc.pktdrop_supported) {
+						sctp_send_packet_dropped(stcb, net, m, length, iphlen, 1);
+						sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_INPUT_ERROR, SCTP_SO_NOT_LOCKED);
+					}
+				} else if ((inp != NULL) && (stcb == NULL)) {
+					inp_decr = inp;
+				}
+				SCTP_STAT_INCR(sctps_badsum);
+				SCTP_STAT_INCR_COUNTER32(sctps_checksumerrors);
+				goto out;
+			} else {
+				cksum_validated = true;
 			}
-			SCTP_PROBE5(receive, NULL, stcb, m, stcb, sh);
-			if ((inp != NULL) && (stcb != NULL)) {
-				sctp_send_packet_dropped(stcb, net, m, length, iphlen, 1);
-				sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_INPUT_ERROR, SCTP_SO_NOT_LOCKED);
-			} else if ((inp != NULL) && (stcb == NULL)) {
-				inp_decr = inp;
-			}
-			SCTP_STAT_INCR(sctps_badsum);
-			SCTP_STAT_INCR_COUNTER32(sctps_checksumerrors);
-			goto out;
+		}
+		KASSERT(cksum_validated || cksum_in_hdr == htonl(0),
+		    ("cksum 0x%08x not zero and not validated", ntohl(cksum_in_hdr)));
+		if (!cksum_validated) {
+			stcb = sctp_findassociation_addr(m, offset, src, dst,
+			    sh, ch, &inp, &net, vrf_id);
+			stcb_looked_up = true;
+			if ((stcb == NULL) || (stcb->asoc.zero_checksum == 0)) {
+				goto validate_cksum;
+			}
+			SCTP_STAT_INCR(sctps_recvzerocrc);
 		}
 	}
+cksum_validated:
 	/* Destination port of 0 is illegal, based on RFC4960. */
-	if (sh->dest_port == 0) {
+	if (sh->dest_port == htons(0)) {
 		SCTP_STAT_INCR(sctps_hdrops);
+		if ((stcb == NULL) && (inp != NULL)) {
+			inp_decr = inp;
+		}
 		goto out;
 	}
-	stcb = sctp_findassociation_addr(m, offset, src, dst,
-	    sh, ch, &inp, &net, vrf_id);
+	if (!stcb_looked_up) {
+		stcb = sctp_findassociation_addr(m, offset, src, dst,
+		    sh, ch, &inp, &net, vrf_id);
+	}
 #if defined(INET) || defined(INET6)
 	if ((ch->chunk_type != SCTP_INITIATION) &&
 	    (net != NULL) && (net->port != port)) {
@@ -5352,8 +5417,8 @@ sctp_common_input_processing(struct mbuf **mm, int iphlen, int offset, int lengt
 		net->flowid = mflowid;
 	}
 	if (inp == NULL) {
-		SCTP_PROBE5(receive, NULL, stcb, m, stcb, sh);
 		SCTP_STAT_INCR(sctps_noport);
+		SCTP_PROBE5(receive, NULL, stcb, m, stcb, sh);
 		if (badport_bandlim(BANDLIM_SCTP_OOTB) < 0) {
 			goto out;
 		}
diff --git a/sys/netinet/sctp_output.c b/sys/netinet/sctp_output.c
index feb3fc31561d..97e89cb1396c 100644
--- a/sys/netinet/sctp_output.c
+++ b/sys/netinet/sctp_output.c
@@ -3969,6 +3969,7 @@ sctp_lowlevel_chunk_output(struct sctp_inpcb *inp,
     uint16_t port,
     union sctp_sockstore *over_addr,
     uint8_t mflowtype, uint32_t mflowid,
+    bool use_zero_crc,
     int so_locked)
 {
 /* nofragment_flag to tell if IP_DF should be set (IPv4 only) */
@@ -4203,15 +4204,23 @@ sctp_lowlevel_chunk_output(struct sctp_inpcb *inp,
 			}
 			SCTP_ATTACH_CHAIN(o_pak, m, packet_length);
 			if (port) {
-				sctphdr->checksum = sctp_calculate_cksum(m, sizeof(struct ip) + sizeof(struct udphdr));
-				SCTP_STAT_INCR(sctps_sendswcrc);
+				if (use_zero_crc) {
+					SCTP_STAT_INCR(sctps_sendzerocrc);
+				} else {
+					sctphdr->checksum = sctp_calculate_cksum(m, sizeof(struct ip) + sizeof(struct udphdr));
+					SCTP_STAT_INCR(sctps_sendswcrc);
+				}
 				if (V_udp_cksum) {
 					SCTP_ENABLE_UDP_CSUM(o_pak);
 				}
 			} else {
-				m->m_pkthdr.csum_flags = CSUM_SCTP;
-				m->m_pkthdr.csum_data = offsetof(struct sctphdr, checksum);
-				SCTP_STAT_INCR(sctps_sendhwcrc);
+				if (use_zero_crc) {
+					SCTP_STAT_INCR(sctps_sendzerocrc);
+				} else {
+					m->m_pkthdr.csum_flags = CSUM_SCTP;
+					m->m_pkthdr.csum_data = offsetof(struct sctphdr, checksum);
+					SCTP_STAT_INCR(sctps_sendhwcrc);
+				}
 			}
 #ifdef SCTP_PACKET_LOGGING
 			if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_LAST_PACKET_TRACING)
@@ -4710,6 +4719,15 @@ sctp_send_initiate(struct sctp_inpcb *inp, struct sctp_tcb *stcb, int so_locked)
 		chunk_len += parameter_len;
 	}
 
+	/* Zero checksum acceptable parameter */
+	if (stcb->asoc.zero_checksum > 0) {
+		parameter_len = (uint16_t)sizeof(struct sctp_paramhdr);
+		ph = (struct sctp_paramhdr *)(mtod(m, caddr_t)+chunk_len);
+		ph->param_type = htons(SCTP_ZERO_CHECKSUM_ACCEPTABLE);
+		ph->param_length = htons(parameter_len);
+		chunk_len += parameter_len;
+	}
+
 	/* Add NAT friendly parameter. */
 	if (SCTP_BASE_SYSCTL(sctp_inits_include_nat_friendly)) {
 		parameter_len = (uint16_t)sizeof(struct sctp_paramhdr);
@@ -4883,7 +4901,7 @@ sctp_send_initiate(struct sctp_inpcb *inp, struct sctp_tcb *stcb, int so_locked)
 	    inp->sctp_lport, stcb->rport, htonl(0),
 	    net->port, NULL,
 	    0, 0,
-	    so_locked))) {
+	    false, so_locked))) {
 		SCTPDBG(SCTP_DEBUG_OUTPUT4, "Gak send error %d\n", error);
 		if (error == ENOBUFS) {
 			stcb->asoc.ifp_had_enobuf = 1;
@@ -5909,6 +5927,16 @@ do_a_abort:
 		chunk_len += parameter_len;
 	}
 
+	/* Zero checksum acceptable parameter */
+	if (((asoc != NULL) && (asoc->zero_checksum > 0)) ||
+	    ((asoc == NULL) && (inp->zero_checksum == 1))) {
+		parameter_len = (uint16_t)sizeof(struct sctp_paramhdr);
+		ph = (struct sctp_paramhdr *)(mtod(m, caddr_t)+chunk_len);
+		ph->param_type = htons(SCTP_ZERO_CHECKSUM_ACCEPTABLE);
+		ph->param_length = htons(parameter_len);
+		chunk_len += parameter_len;
+	}
+
 	/* Add NAT friendly parameter */
 	if (nat_friendly) {
 		parameter_len = (uint16_t)sizeof(struct sctp_paramhdr);
@@ -6117,6 +6145,7 @@ do_a_abort:
 	    inp->sctp_lport, sh->src_port, init_chk->init.initiate_tag,
 	    port, over_addr,
 	    mflowtype, mflowid,
+	    false,		/* XXXMT: Improve this! */
 	    SCTP_SO_NOT_LOCKED))) {
 		SCTPDBG(SCTP_DEBUG_OUTPUT4, "Gak send error %d\n", error);
 		if (error == ENOBUFS) {
@@ -7784,6 +7813,7 @@ sctp_med_chunk_output(struct sctp_inpcb *inp,
 	 * destination.
 	 */
 	int quit_now = 0;
+	bool use_zero_crc;
 
 	*num_out = 0;
 	*reason_code = 0;
@@ -8145,7 +8175,7 @@ again_one_more_time:
 					    htonl(stcb->asoc.peer_vtag),
 					    net->port, NULL,
 					    0, 0,
-					    so_locked))) {
+					    false, so_locked))) {
 						/*
 						 * error, we could not
 						 * output
@@ -8167,6 +8197,7 @@ again_one_more_time:
 							 */
 							sctp_move_chunks_from_net(stcb, net);
 						}
+						asconf = 0;
 						*reason_code = 7;
 						break;
 					} else {
@@ -8180,6 +8211,7 @@ again_one_more_time:
 					outchain = endoutchain = NULL;
 					auth = NULL;
 					auth_offset = 0;
+					asconf = 0;
 					if (!no_out_cnt)
 						*num_out += ctl_cnt;
 					/* recalc a clean slate and setup */
@@ -8391,8 +8423,10 @@ again_one_more_time:
 					 * flight size since this little guy
 					 * is a control only packet.
 					 */
+					use_zero_crc = asoc->zero_checksum = 2;
 					if (asconf) {
 						sctp_timer_start(SCTP_TIMER_TYPE_ASCONF, inp, stcb, net);
+						use_zero_crc = false;
 						/*
 						 * do NOT clear the asconf
 						 * flag as it is used to do
@@ -8402,6 +8436,7 @@ again_one_more_time:
 					}
 					if (cookie) {
 						sctp_timer_start(SCTP_TIMER_TYPE_COOKIE, inp, stcb, net);
+						use_zero_crc = false;
 						cookie = 0;
 					}
 					/* Only HB or ASCONF advances time */
@@ -8423,7 +8458,7 @@ again_one_more_time:
 					    htonl(stcb->asoc.peer_vtag),
 					    net->port, NULL,
 					    0, 0,
-					    so_locked))) {
+					    use_zero_crc, so_locked))) {
 						/*
 						 * error, we could not
 						 * output
@@ -8444,6 +8479,7 @@ again_one_more_time:
 							 */
 							sctp_move_chunks_from_net(stcb, net);
 						}
+						asconf = 0;
 						*reason_code = 7;
 						break;
 					} else {
@@ -8457,6 +8493,7 @@ again_one_more_time:
 					outchain = endoutchain = NULL;
 					auth = NULL;
 					auth_offset = 0;
+					asconf = 0;
 					if (!no_out_cnt)
 						*num_out += ctl_cnt;
 					/* recalc a clean slate and setup */
@@ -8719,9 +8756,11 @@ no_data_fill:
 		/* Is there something to send for this destination? */
 		if (outchain) {
 			/* We may need to start a control timer or two */
+			use_zero_crc = asoc->zero_checksum == 2;
 			if (asconf) {
 				sctp_timer_start(SCTP_TIMER_TYPE_ASCONF, inp,
 				    stcb, net);
+				use_zero_crc = false;
 				/*
 				 * do NOT clear the asconf flag as it is
 				 * used to do appropriate source address
@@ -8730,6 +8769,7 @@ no_data_fill:
 			}
 			if (cookie) {
 				sctp_timer_start(SCTP_TIMER_TYPE_COOKIE, inp, stcb, net);
+				use_zero_crc = false;
 				cookie = 0;
 			}
 			/* must start a send timer if data is being sent */
@@ -8764,6 +8804,7 @@ no_data_fill:
 			    htonl(stcb->asoc.peer_vtag),
 			    net->port, NULL,
 			    0, 0,
+			    use_zero_crc,
 			    so_locked))) {
 				/* error, we could not output */
 				SCTPDBG(SCTP_DEBUG_OUTPUT3, "Gak send error %d\n", error);
@@ -8781,6 +8822,7 @@ no_data_fill:
 					 */
 					sctp_move_chunks_from_net(stcb, net);
 				}
+				asconf = 0;
 				*reason_code = 6;
 				/*-
 				 * I add this line to be paranoid. As far as
@@ -8797,6 +8839,7 @@ no_data_fill:
 			endoutchain = NULL;
 			auth = NULL;
 			auth_offset = 0;
+			asconf = 0;
 			if (!no_out_cnt) {
 				*num_out += (ctl_cnt + bundle_at);
 			}
@@ -9385,6 +9428,7 @@ sctp_chunk_retransmission(struct sctp_inpcb *inp,
 	int override_ok = 1;
 	int data_auth_reqd = 0;
 	uint32_t dmtu = 0;
+	bool use_zero_crc;
 
 	SCTP_TCB_LOCK_ASSERT(stcb);
 	tmr_started = ctl_cnt = 0;
@@ -9448,10 +9492,15 @@ sctp_chunk_retransmission(struct sctp_inpcb *inp,
 	/* do we have control chunks to retransmit? */
 	if (m != NULL) {
 		/* Start a timer no matter if we succeed or fail */
+		use_zero_crc = asoc->zero_checksum == 2;
 		if (chk->rec.chunk_id.id == SCTP_COOKIE_ECHO) {
 			sctp_timer_start(SCTP_TIMER_TYPE_COOKIE, inp, stcb, chk->whoTo);
-		} else if (chk->rec.chunk_id.id == SCTP_ASCONF)
+			use_zero_crc = false;
+		} else if (chk->rec.chunk_id.id == SCTP_ASCONF) {
+			/* XXXMT: Can this happen? */
 			sctp_timer_start(SCTP_TIMER_TYPE_ASCONF, inp, stcb, chk->whoTo);
+			use_zero_crc = false;
+		}
 		chk->snd_count++;	/* update our count */
 		if ((error = sctp_lowlevel_chunk_output(inp, stcb, chk->whoTo,
 		    (struct sockaddr *)&chk->whoTo->ro._l_addr, m,
@@ -9460,6 +9509,7 @@ sctp_chunk_retransmission(struct sctp_inpcb *inp,
 		    inp->sctp_lport, stcb->rport, htonl(stcb->asoc.peer_vtag),
 		    chk->whoTo->port, NULL,
 		    0, 0,
+		    use_zero_crc,
 		    so_locked))) {
 			SCTPDBG(SCTP_DEBUG_OUTPUT3, "Gak send error %d\n", error);
 			if (error == ENOBUFS) {
@@ -9737,6 +9787,7 @@ one_chunk_around:
 			    inp->sctp_lport, stcb->rport, htonl(stcb->asoc.peer_vtag),
 			    net->port, NULL,
 			    0, 0,
+			    asoc->zero_checksum == 2,
 			    so_locked))) {
 				/* error, we could not output */
 				SCTPDBG(SCTP_DEBUG_OUTPUT3, "Gak send error %d\n", error);
@@ -10897,6 +10948,7 @@ sctp_send_abort_tcb(struct sctp_tcb *stcb, struct mbuf *operr, int so_locked)
 	    stcb->sctp_ep->sctp_lport, stcb->rport, htonl(vtag),
 	    stcb->asoc.primary_destination->port, NULL,
 	    0, 0,
+	    stcb->asoc.zero_checksum == 2,
 	    so_locked))) {
 		SCTPDBG(SCTP_DEBUG_OUTPUT3, "Gak send error %d\n", error);
 		if (error == ENOBUFS) {
@@ -10945,6 +10997,7 @@ sctp_send_shutdown_complete(struct sctp_tcb *stcb,
 	    htonl(vtag),
 	    net->port, NULL,
 	    0, 0,
+	    stcb->asoc.zero_checksum == 2,
 	    SCTP_SO_NOT_LOCKED))) {
 		SCTPDBG(SCTP_DEBUG_OUTPUT3, "Gak send error %d\n", error);
 		if (error == ENOBUFS) {
diff --git a/sys/netinet/sctp_pcb.c b/sys/netinet/sctp_pcb.c
index fa6ad102f864..7567764bfd72 100644
--- a/sys/netinet/sctp_pcb.c
+++ b/sys/netinet/sctp_pcb.c
@@ -2433,6 +2433,7 @@ sctp_inpcb_alloc(struct socket *so, uint32_t vrf_id)
 	inp->nrsack_supported = (uint8_t)SCTP_BASE_SYSCTL(sctp_nrsack_enable);
 	inp->pktdrop_supported = (uint8_t)SCTP_BASE_SYSCTL(sctp_pktdrop_enable);
 	inp->idata_supported = 0;
+	inp->zero_checksum = 0;
 
 	inp->fibnum = so->so_fibnum;
 	/* init the small hash table we use to track asocid <-> tcb */
@@ -6402,6 +6403,14 @@ sctp_load_addresses_from_init(struct sctp_tcb *stcb, struct mbuf *m,
 		} else if (ptype == SCTP_PRSCTP_SUPPORTED) {
 			/* Peer supports pr-sctp */
 			peer_supports_prsctp = 1;
+		} else if (ptype == SCTP_ZERO_CHECKSUM_ACCEPTABLE) {
+			/*
+			 * Only send zero checksums if the upper layer has
+			 * also enabled the support for this.
+			 */
+			if (stcb->asoc.zero_checksum == 1) {
+				stcb->asoc.zero_checksum = 2;
+			}
 		} else if (ptype == SCTP_SUPPORTED_CHUNK_EXT) {
 			/* A supported extension chunk */
 			struct sctp_supported_chunk_types_param *pr_supported;
diff --git a/sys/netinet/sctp_pcb.h b/sys/netinet/sctp_pcb.h
index fd8115a8101a..39d8b4e7013e 100644
--- a/sys/netinet/sctp_pcb.h
+++ b/sys/netinet/sctp_pcb.h
@@ -409,6 +409,7 @@ struct sctp_inpcb {
 	uint8_t reconfig_supported;
 	uint8_t nrsack_supported;
 	uint8_t pktdrop_supported;
+	uint8_t zero_checksum;
 	struct sctp_nonpad_sndrcvinfo def_send;
 	/*-
 	 * These three are here for the sosend_dgram
diff --git a/sys/netinet/sctp_structs.h b/sys/netinet/sctp_structs.h
index e28c2265589d..504e9ad79fe3 100644
--- a/sys/netinet/sctp_structs.h
+++ b/sys/netinet/sctp_structs.h
@@ -1185,6 +1185,12 @@ struct sctp_association {
 	uint8_t pktdrop_supported;
 	uint8_t idata_supported;
 
+	/*
+	 * Zero checksum supported information: 0: disabled 1: enabled for
+	 * rcv 2: enabled for snd/rcv
+	 */
+	uint8_t zero_checksum;
+
 	/* Did the peer make the stream config (add out) request */
 	uint8_t peer_req_out;
 
diff --git a/sys/netinet/sctp_sysctl.c b/sys/netinet/sctp_sysctl.c
index fcc02f315a1b..b48aedf0b950 100644
--- a/sys/netinet/sctp_sysctl.c
+++ b/sys/netinet/sctp_sysctl.c
@@ -121,6 +121,7 @@ sctp_init_sysctls(void)
 	SCTP_BASE_SYSCTL(sctp_blackhole) = SCTPCTL_BLACKHOLE_DEFAULT;
 	SCTP_BASE_SYSCTL(sctp_sendall_limit) = SCTPCTL_SENDALL_LIMIT_DEFAULT;
 	SCTP_BASE_SYSCTL(sctp_diag_info_code) = SCTPCTL_DIAG_INFO_CODE_DEFAULT;
+	SCTP_BASE_SYSCTL(sctp_ootb_with_zero_cksum) = SCTPCTL_OOTB_WITH_ZERO_CKSUM_DEFAULT;
 #if defined(SCTP_LOCAL_TRACE_BUF)
 	memset(&SCTP_BASE_SYSCTL(sctp_log), 0, sizeof(struct sctp_log));
 #endif
@@ -828,6 +829,9 @@ sctp_sysctl_handle_stats(SYSCTL_HANDLER_ARGS)
 		sb.sctps_send_burst_avoid += sarry->sctps_send_burst_avoid;
 		sb.sctps_send_cwnd_avoid += sarry->sctps_send_cwnd_avoid;
 		sb.sctps_fwdtsn_map_over += sarry->sctps_fwdtsn_map_over;
+		sb.sctps_queue_upd_ecne += sarry->sctps_queue_upd_ecne;
+		sb.sctps_recvzerocrc += sarry->sctps_recvzerocrc;
+		sb.sctps_sendzerocrc += sarry->sctps_sendzerocrc;
 		if (req->newptr != NULL) {
 			memcpy(sarry, &sb_temp, sizeof(struct sctpstat));
 		}
@@ -970,6 +974,7 @@ SCTP_UINT_SYSCTL(use_dcccecn, sctp_use_dccc_ecn, SCTPCTL_RTTVAR_DCCCECN)
 SCTP_UINT_SYSCTL(blackhole, sctp_blackhole, SCTPCTL_BLACKHOLE)
 SCTP_UINT_SYSCTL(sendall_limit, sctp_sendall_limit, SCTPCTL_SENDALL_LIMIT)
 SCTP_UINT_SYSCTL(diag_info_code, sctp_diag_info_code, SCTPCTL_DIAG_INFO_CODE)
+SCTP_UINT_SYSCTL(ootb_with_zero_cksum, sctp_ootb_with_zero_cksum, SCTPCTL_OOTB_WITH_ZERO_CKSUM)
 #ifdef SCTP_DEBUG
 SCTP_UINT_SYSCTL(debug, sctp_debug_on, SCTPCTL_DEBUG)
 #endif
diff --git a/sys/netinet/sctp_sysctl.h b/sys/netinet/sctp_sysctl.h
index 695cd743403b..12a7c5200b40 100644
--- a/sys/netinet/sctp_sysctl.h
+++ b/sys/netinet/sctp_sysctl.h
@@ -106,7 +106,6 @@ struct sctp_sysctl {
 	uint32_t sctp_rttvar_eqret;
 	uint32_t sctp_steady_step;
 	uint32_t sctp_use_dccc_ecn;
-	uint32_t sctp_diag_info_code;
 #if defined(SCTP_LOCAL_TRACE_BUF)
 	struct sctp_log sctp_log;
 #endif
@@ -117,6 +116,8 @@ struct sctp_sysctl {
 	uint32_t sctp_initial_cwnd;
 	uint32_t sctp_blackhole;
 	uint32_t sctp_sendall_limit;
+	uint32_t sctp_diag_info_code;
+	uint32_t sctp_ootb_with_zero_cksum;
 #if defined(SCTP_DEBUG)
 	uint32_t sctp_debug_on;
 #endif
@@ -546,6 +547,11 @@ struct sctp_sysctl {
 #define SCTPCTL_DIAG_INFO_CODE_MAX	65535
 #define SCTPCTL_DIAG_INFO_CODE_DEFAULT	0
 
+#define SCTPCTL_OOTB_WITH_ZERO_CKSUM_DESC	"Accept OOTB packets with zero checksum"
+#define SCTPCTL_OOTB_WITH_ZERO_CKSUM_MIN	0
+#define SCTPCTL_OOTB_WITH_ZERO_CKSUM_MAX	1
+#define SCTPCTL_OOTB_WITH_ZERO_CKSUM_DEFAULT	0
+
 #if defined(SCTP_DEBUG)
 /* debug: Configure debug output */
 #define SCTPCTL_DEBUG_DESC	"Configure debug output"
diff --git a/sys/netinet/sctp_uio.h b/sys/netinet/sctp_uio.h
index 330a4529b039..e833b0ab33ae 100644
--- a/sys/netinet/sctp_uio.h
+++ b/sys/netinet/sctp_uio.h
@@ -1120,7 +1120,11 @@ struct sctpstat {
 					 * fwd-tsn's */
 	uint32_t sctps_queue_upd_ecne;	/* Number of times we queued or
 					 * updated an ECN chunk on send queue */
-	uint32_t sctps_reserved[31];	/* Future ABI compat - remove int's
+	uint32_t sctps_recvzerocrc;	/* Number of accepted packets with
+					 * zero CRC */
+	uint32_t sctps_sendzerocrc;	/* Number of packets sent with zero
+					 * CRC */
+	uint32_t sctps_reserved[29];	/* Future ABI compat - remove int's
 					 * from here when adding new */
 };
 
diff --git a/sys/netinet/sctp_usrreq.c b/sys/netinet/sctp_usrreq.c
index d308b75aa90d..57d5abdebd30 100644
--- a/sys/netinet/sctp_usrreq.c
+++ b/sys/netinet/sctp_usrreq.c
@@ -6834,6 +6834,29 @@ sctp_setopt(struct socket *so, int optname, void *optval, size_t optsize,
 			}
 			break;
 		}
+	case SCTP_ACCEPT_ZERO_CHECKSUM:
+		{
+			uint32_t *value;
+
+			SCTP_CHECK_AND_CAST(value, optval, uint32_t, optsize);
+			SCTP_INP_WLOCK(inp);
+			if (*value == 0) {
+				/*
+				 * Do not allow turning zero checksum
+				 * acceptance off again, since this could
+				 * result in inconsistent behaviour for
+				 * listeners.
+				 */
+				if (inp->zero_checksum > 0) {
+					error = EINVAL;
+				}
+			} else {
+				inp->zero_checksum = 1;
+			}
+			SCTP_INP_WUNLOCK(inp);
+			break;
+		}
+
 	default:
 		SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, ENOPROTOOPT);
 		error = ENOPROTOOPT;
diff --git a/sys/netinet/sctputil.c b/sys/netinet/sctputil.c
index f4b6ca8c0836..8bb0f7446b68 100644
--- a/sys/netinet/sctputil.c
+++ b/sys/netinet/sctputil.c
@@ -1149,6 +1149,7 @@ sctp_init_asoc(struct sctp_inpcb *inp, struct sctp_tcb *stcb,
 	asoc->nrsack_supported = inp->nrsack_supported;
 	asoc->pktdrop_supported = inp->pktdrop_supported;
 	asoc->idata_supported = inp->idata_supported;
+	asoc->zero_checksum = inp->zero_checksum;
 	asoc->sctp_cmt_pf = (uint8_t)0;
 	asoc->sctp_frag_point = inp->sctp_frag_point;
 	asoc->sctp_features = inp->sctp_features;