git: 32a462ba9cdc - main - ecn(9): Update ecn tunneling functions to RFC 6040

From: Pouria Mousavizadeh Tehrani <pouria_at_FreeBSD.org>
Date: Thu, 19 Mar 2026 10:29:05 UTC
The branch main has been updated by pouria:

URL: https://cgit.FreeBSD.org/src/commit/?id=32a462ba9cdc8a927c1aba5d9bff9d16d367d7da

commit 32a462ba9cdc8a927c1aba5d9bff9d16d367d7da
Author:     Pouria Mousavizadeh Tehrani <pouria@FreeBSD.org>
AuthorDate: 2026-03-19 10:18:34 +0000
Commit:     Pouria Mousavizadeh Tehrani <pouria@FreeBSD.org>
CommitDate: 2026-03-19 10:18:34 +0000

    ecn(9): Update ecn tunneling functions to RFC 6040
    
    Update ECN tunneling functions from obsolete RFC 3168 to
    newer RFC 6040.
    Also, add ECN_COMPLETE to support dangerous packet reporting
    without causing extra costs to existing caller functions.
    Finally, return values are specified as macro to reduce
    confusion, considering extra return values for ECN_WARN
    and ECN_ALARM were added.
    
    Reviewed By: glebius, tuexen
    Differential Revision: https://reviews.freebsd.org/D53516
---
 sys/netinet/ip_ecn.c   | 149 +++++++++++++++++++++++++++++++------------------
 sys/netinet/ip_ecn.h   |  17 +++---
 sys/netinet6/ip6_ecn.h |   5 --
 3 files changed, 106 insertions(+), 65 deletions(-)

diff --git a/sys/netinet/ip_ecn.c b/sys/netinet/ip_ecn.c
index 0166c8b5d9c5..9b708b265037 100644
--- a/sys/netinet/ip_ecn.c
+++ b/sys/netinet/ip_ecn.c
@@ -31,10 +31,6 @@
  * SUCH DAMAGE.
  *
  */
-/*
- * ECN consideration on tunnel ingress/egress operation.
- * http://www.aciri.org/floyd/papers/draft-ipsec-ecn-00.txt
- */
 
 #include "opt_inet.h"
 #include "opt_inet6.h"
@@ -58,7 +54,7 @@
 
 /*
  * ECN and TOS (or TCLASS) processing rules at tunnel encapsulation and
- * decapsulation from RFC3168:
+ * decapsulation from RFC6040:
  *
  *                      Outer Hdr at                 Inner Hdr at
  *                      Encapsulator                 Decapsulator
@@ -66,18 +62,42 @@
  *     DS Field         copied from inner hdr        no change
  *     ECN Field        constructed by (I)           constructed by (E)
  *
- * ECN_ALLOWED (full functionality):
- *    (I) if the ECN field in the inner header is set to CE, then set the
- *    ECN field in the outer header to ECT(0).
- *    otherwise, copy the ECN field to the outer header.
+ * ECN_ALLOWED (normal mode):
+ *    (I) copy the ECN field to the outer header.
  *
  *    (E) if the ECN field in the outer header is set to CE and the ECN
  *    field of the inner header is not-ECT, drop the packet.
- *    if the ECN field in the inner header is set to ECT(0) or ECT(1)
- *    and the ECN field in the outer header is set to CE, then copy CE to
- *    the inner header.  otherwise, make no change to the inner header.
+ *    If the ECN field in the inner header is set to ECT(0) and the ECN
+ *    field in the outer header is set to ECT(1), copy ECT(1) to
+ *    the inner header. If the ECN field in the inner header is set
+ *    to ECT(0) or ECT(1) and the ECN field in the outer header is set to
+ *    CE, copy CE to the inner header.
+ *    Otherwise, make no change to the inner header. This behaviour can be
+ *    summarized in the table below:
+ *
+ *                      Outer Header at Decapsulator
+ *               +---------+------------+------------+------------+
+ *               | Not-ECT | ECT(0)     | ECT(1)     |     CE     |
+ *    Inner Hdr: +---------+------------+------------+------------+
+ *      Not-ECT  | Not-ECT |Not-ECT(!!!)|Not-ECT(!!!)| <drop>(!!!)|
+ *       ECT(0)  |  ECT(0) | ECT(0)     | ECT(1)     |     CE     |
+ *       ECT(1)  |  ECT(1) | ECT(1) (!) | ECT(1)     |     CE     |
+ *         CE    |      CE |     CE     |     CE(!!!)|     CE     |
+ *               +---------+------------+------------+------------+
+ *
+ * ECN_COMPLETE (normal mode with security log):
+ *    certain combinations indicated in table by '(!!!)' or '(!)',
+ *    where '(!!!)' means the combination always potentially dangerous which
+ *    returns 3, while '(!)' means possibly dangerous in which returns 2.
+ *    These combinations are unsed by previous ECN tunneling specifications
+ *    and could be logged. Also, in case of more dangerous ones, the
+ *    decapsulator SHOULD log the event and MAY also raise an alarm.
+ *
+ *    Note: Caller SHOULD use rate-limited alarms so that the anomalous
+ *    combinations will not amplify into a flood of alarm messages.
+ *    Also, it MUST be possible to suppress alarms or logging.
  *
- * ECN_FORBIDDEN (limited functionality):
+ * ECN_FORBIDDEN (compatibility mode):
  *    (I) set the ECN field to not-ECT in the outer header.
  *
  *    (E) if the ECN field in the outer header is set to CE, drop the packet.
@@ -94,26 +114,22 @@ void
 ip_ecn_ingress(int mode, uint8_t *outer, const uint8_t *inner)
 {
 
-	if (!outer || !inner)
-		panic("NULL pointer passed to ip_ecn_ingress");
+	KASSERT(outer != NULL && inner != NULL,
+	    ("NULL pointer passed to %s", __func__));
 
 	*outer = *inner;
 	switch (mode) {
-	case ECN_ALLOWED:		/* ECN allowed */
-		/*
-		 * full-functionality: if the inner is CE, set ECT(0)
-		 * to the outer.  otherwise, copy the ECN field.
-		 */
-		if ((*inner & IPTOS_ECN_MASK) == IPTOS_ECN_CE)
-			*outer &= ~IPTOS_ECN_ECT1;
+	case ECN_COMPLETE:
+	case ECN_ALLOWED:
+		/* normal mode: always copy the ECN field. */
 		break;
-	case ECN_FORBIDDEN:		/* ECN forbidden */
-		/*
-		 * limited-functionality: set not-ECT to the outer
-		 */
+
+	case ECN_FORBIDDEN:
+		/* compatibility mode: set not-ECT to the outer */
 		*outer &= ~IPTOS_ECN_MASK;
 		break;
-	case ECN_NOCARE:	/* no consideration to ECN */
+
+	case ECN_NOCARE:
 		break;
 	}
 }
@@ -126,33 +142,57 @@ int
 ip_ecn_egress(int mode, const uint8_t *outer, uint8_t *inner)
 {
 
-	if (!outer || !inner)
-		panic("NULL pointer passed to ip_ecn_egress");
+	KASSERT(outer != NULL && inner != NULL,
+	    ("NULL pointer passed to %s", __func__));
 
 	switch (mode) {
+	case ECN_COMPLETE:
+		if ((*outer & IPTOS_ECN_MASK) == IPTOS_ECN_ECT0) {
+			/* if the outer is ECT(0) and inner is ECT(1) raise a warning */
+			if ((*inner & IPTOS_ECN_MASK) == IPTOS_ECN_ECT1)
+				return (ECN_WARN);
+			/* if the inner is not-ECT and outer is ECT(0) raise an alarm */
+			if ((*inner & IPTOS_ECN_MASK) == IPTOS_ECN_NOTECT)
+				return (ECN_ALARM);
+			return (ECN_SUCCESS);
+		} else if ((*outer & IPTOS_ECN_MASK) == IPTOS_ECN_ECT1) {
+			/* if the outer is ECT(1) and inner is CE or ECT(1), raise an alarm */
+			if (((*inner & IPTOS_ECN_MASK) == IPTOS_ECN_CE) ||
+			    ((*inner & IPTOS_ECN_MASK) == IPTOS_ECN_NOTECT))
+				return (ECN_ALARM);
+			/* if the outer is ECT(1) and inner is ECT(0), copy ECT(1) */
+			if ((*inner & IPTOS_ECN_MASK) == IPTOS_ECN_ECT0)
+				*inner = IPTOS_ECN_ECT1;
+			return (ECN_SUCCESS);
+		}
+		/* fallthrough */
 	case ECN_ALLOWED:
-		/*
-		 * full-functionality: if the outer is CE and the inner is
-		 * not-ECT, should drop it.  otherwise, copy CE.
-		 */
 		if ((*outer & IPTOS_ECN_MASK) == IPTOS_ECN_CE) {
+			/* if the outer is CE and the inner is not-ECT, drop it. */
 			if ((*inner & IPTOS_ECN_MASK) == IPTOS_ECN_NOTECT)
-				return (0);
+				return (ECN_DROP);
+			/* otherwise, copy CE */
 			*inner |= IPTOS_ECN_CE;
+		} else if ((*outer & IPTOS_ECN_MASK) == IPTOS_ECN_ECT1) {
+			/* if the outer is ECT(1) and inner is ECT(0), copy ECT(1) */
+			if ((*inner & IPTOS_ECN_MASK) == IPTOS_ECN_ECT0)
+				*inner = IPTOS_ECN_ECT1;
 		}
 		break;
-	case ECN_FORBIDDEN:		/* ECN forbidden */
+
+	case ECN_FORBIDDEN:
 		/*
-		 * limited-functionality: if the outer is CE, should drop it.
+		 * compatibility mode: if the outer is CE, should drop it.
 		 * otherwise, leave the inner.
 		 */
 		if ((*outer & IPTOS_ECN_MASK) == IPTOS_ECN_CE)
-			return (0);
+			return (ECN_DROP);
 		break;
-	case ECN_NOCARE:	/* no consideration to ECN */
+
+	case ECN_NOCARE:
 		break;
 	}
-	return (1);
+	return (ECN_SUCCESS);
 }
 
 #ifdef INET6
@@ -161,31 +201,34 @@ ip6_ecn_ingress(int mode, uint32_t *outer, const uint32_t *inner)
 {
 	uint8_t outer8, inner8;
 
-	if (!outer || !inner)
-		panic("NULL pointer passed to ip6_ecn_ingress");
+	KASSERT(outer != NULL && inner != NULL,
+	    ("NULL pointer passed to %s", __func__));
 
-	inner8 = (ntohl(*inner) >> 20) & 0xff;
+	inner8 = (ntohl(*inner) >> IPV6_FLOWLABEL_LEN) & 0xff;
 	ip_ecn_ingress(mode, &outer8, &inner8);
-	*outer &= ~htonl(0xff << 20);
-	*outer |= htonl((uint32_t)outer8 << 20);
+	*outer &= ~htonl(0xff << IPV6_FLOWLABEL_LEN);
+	*outer |= htonl((uint32_t)outer8 << IPV6_FLOWLABEL_LEN);
 }
 
 int
 ip6_ecn_egress(int mode, const uint32_t *outer, uint32_t *inner)
 {
 	uint8_t outer8, inner8, oinner8;
+	int ret;
+
+	KASSERT(outer != NULL && inner != NULL,
+	    ("NULL pointer passed to %s", __func__));
 
-	if (!outer || !inner)
-		panic("NULL pointer passed to ip6_ecn_egress");
+	outer8 = (ntohl(*outer) >> IPV6_FLOWLABEL_LEN) & 0xff;
+	inner8 = oinner8 = (ntohl(*inner) >> IPV6_FLOWLABEL_LEN) & 0xff;
 
-	outer8 = (ntohl(*outer) >> 20) & 0xff;
-	inner8 = oinner8 = (ntohl(*inner) >> 20) & 0xff;
-	if (ip_ecn_egress(mode, &outer8, &inner8) == 0)
-		return (0);
+	ret = ip_ecn_egress(mode, &outer8, &inner8);
+	if (ret == ECN_DROP)
+		return (ECN_DROP);
 	if (inner8 != oinner8) {
-		*inner &= ~htonl(0xff << 20);
-		*inner |= htonl((uint32_t)inner8 << 20);
+		*inner &= ~htonl(0xff << IPV6_FLOWLABEL_LEN);
+		*inner |= htonl((uint32_t)inner8 << IPV6_FLOWLABEL_LEN);
 	}
-	return (1);
+	return (ret);
 }
 #endif
diff --git a/sys/netinet/ip_ecn.h b/sys/netinet/ip_ecn.h
index 6632418fc9ca..9ecb7f036fe7 100644
--- a/sys/netinet/ip_ecn.h
+++ b/sys/netinet/ip_ecn.h
@@ -31,19 +31,22 @@
  * SUCH DAMAGE.
  *
  */
-/*
- * ECN consideration on tunnel ingress/egress operation.
- * http://www.aciri.org/floyd/papers/draft-ipsec-ecn-00.txt
- */
 
 #ifndef _NETINET_IP_ECN_H_
 #define _NETINET_IP_ECN_H_
 
-#define ECN_ALLOWED	1	/* ECN allowed */
-#define ECN_FORBIDDEN	0	/* ECN forbidden */
+#ifdef _KERNEL
+#define ECN_COMPLETE	2	/* ECN normal mode with security log */
+#define ECN_ALLOWED	1	/* ECN normal mode */
+#define ECN_FORBIDDEN	0	/* ECN compatibility mode */
 #define ECN_NOCARE	(-1)	/* no consideration to ECN */
 
-#ifdef _KERNEL
+/* ip[6]_ecn_egress return values */
+#define ECN_DROP	0	/* caller MUST drop the packet */
+#define ECN_SUCCESS	1	/* success */
+#define ECN_WARN	2	/* caller MAY log */
+#define ECN_ALARM	3	/* caller SHOULD log and MAY raise alarm */
+
 extern void ip_ecn_ingress(int, uint8_t *, const uint8_t *);
 extern int ip_ecn_egress(int, const uint8_t *, uint8_t *);
 #endif
diff --git a/sys/netinet6/ip6_ecn.h b/sys/netinet6/ip6_ecn.h
index 169e027dc265..6b82796e83a5 100644
--- a/sys/netinet6/ip6_ecn.h
+++ b/sys/netinet6/ip6_ecn.h
@@ -31,11 +31,6 @@
  *	$KAME: ip_ecn.h,v 1.5 2000/03/27 04:58:38 sumikawa Exp $
  */
 
-/*
- * ECN consideration on tunnel ingress/egress operation.
- * http://www.aciri.org/floyd/papers/draft-ipsec-ecn-00.txt
- */
-
 #ifdef _KERNEL
 extern void ip6_ecn_ingress(int, uint32_t *, const uint32_t *);
 extern int ip6_ecn_egress(int, const uint32_t *, uint32_t *);