public inbox for netdev@vger.kernel.org
 help / color / mirror / Atom feed
From: Yuya Kusakabe <yuya.kusakabe@gmail.com>
To: stephen@networkplumber.org, dsahern@kernel.org
Cc: netdev@vger.kernel.org, Yuya Kusakabe <yuya.kusakabe@gmail.com>
Subject: [PATCH iproute2-next v1 RESEND 4/6] seg6: add support for the End.M.GTP6.D behavior
Date: Mon,  4 May 2026 00:45:08 +0900	[thread overview]
Message-ID: <20260503154510.912576-5-yuya.kusakabe@gmail.com> (raw)
In-Reply-To: <20260503154510.912576-4-yuya.kusakabe@gmail.com>

Add support for the End.M.GTP6.D behavior, which translates IPv6/GTP-U
traffic into an SRv6 SR Policy.  The SR Policy is supplied through the
existing srh segs syntax, and a new sr_prefix_len keyword specifies
the locator length of the egress End.M.GTP6.E SID (1..88, leaving
40 bits for the Args.Mob.Session field).

Example:
ip -6 r a 2001:db8:f::/64 encap seg6local action End.M.GTP6.D \
    srh segs 2001:db8:2::1,2001:db8:3::e \
    src 2001:db8::1 sr_prefix_len 88 dev sr0

Link: https://datatracker.ietf.org/doc/html/rfc9433

Signed-off-by: Yuya Kusakabe <yuya.kusakabe@gmail.com>
---
 include/uapi/linux/seg6_local.h |  3 ++
 ip/iproute.c                    |  6 ++--
 ip/iproute_lwtunnel.c           | 61 +++++++++++++++++++++++++++++----
 man/man8/ip-route.8.in          | 29 ++++++++++++++++
 4 files changed, 90 insertions(+), 9 deletions(-)

diff --git a/include/uapi/linux/seg6_local.h b/include/uapi/linux/seg6_local.h
index 6af145259ffb..ed44fb858600 100644
--- a/include/uapi/linux/seg6_local.h
+++ b/include/uapi/linux/seg6_local.h
@@ -33,6 +33,7 @@ enum {
 	SEG6_LOCAL_MOBILE_V4_MASK_LEN,
 	SEG6_LOCAL_MOBILE_PDU_TYPE,
 	SEG6_LOCAL_MOBILE_V6_SRC_PREFIX_LEN,
+	SEG6_LOCAL_MOBILE_SR_PREFIX_LEN,
 	__SEG6_LOCAL_MAX,
 };
 #define SEG6_LOCAL_MAX (__SEG6_LOCAL_MAX - 1)
@@ -77,6 +78,8 @@ enum {
 	SEG6_LOCAL_ACTION_END_M_GTP4_E	= 18,
 	/* SRv6 to IPv6/GTP-U encap (RFC 9433 Section 6.5) */
 	SEG6_LOCAL_ACTION_END_M_GTP6_E	= 19,
+	/* IPv6/GTP-U decap into SRv6 (RFC 9433 Section 6.3) */
+	SEG6_LOCAL_ACTION_END_M_GTP6_D	= 20,
 
 	__SEG6_LOCAL_ACTION_MAX,
 };
diff --git a/ip/iproute.c b/ip/iproute.c
index a77df0da0efe..8ced32a4f84e 100644
--- a/ip/iproute.c
+++ b/ip/iproute.c
@@ -106,11 +106,13 @@ static void usage(void)
 		"ACTION := { End | End.X | End.T | End.DX2 | End.DX6 | End.DX4 |\n"
 		"            End.DT6 | End.DT4 | End.DT46 | End.B6 | End.B6.Encaps |\n"
 		"            End.BM | End.S | End.AS | End.AM | End.BPF |\n"
-		"            End.MAP | End.M.GTP4.E | End.M.GTP6.E }\n"
+		"            End.MAP | End.M.GTP4.E | End.M.GTP6.E |\n"
+		"            End.M.GTP6.D }\n"
 		"OPTIONS := OPTION [ OPTIONS ]\n"
 		"OPTION := { flavors FLAVORS | srh SEG6HDR | nh4 ADDR | nh6 ADDR | iif DEV | oif DEV |\n"
 		"            table TABLEID | vrftable TABLEID | endpoint PROGNAME | MOBILE_OPTION }\n"
-		"MOBILE_OPTION := { src ADDR | v4_mask_len BITS | v6_src_prefix_len BITS |\n"
+		"MOBILE_OPTION := { src ADDR | v4_mask_len BITS | sr_prefix_len BITS |\n"
+		"                   v6_src_prefix_len BITS |\n"
 		"                   pdu_type { downlink | dl | uplink | ul | 0..15 } }\n"
 		"FLAVORS := { FLAVOR[,FLAVOR] }\n"
 		"FLAVOR := { psp | usp | usd | next-csid }\n"
diff --git a/ip/iproute_lwtunnel.c b/ip/iproute_lwtunnel.c
index 7a4acc33602b..d54bf4628f6f 100644
--- a/ip/iproute_lwtunnel.c
+++ b/ip/iproute_lwtunnel.c
@@ -408,6 +408,7 @@ static const char *seg6_action_names[SEG6_LOCAL_ACTION_MAX + 1] = {
 	[SEG6_LOCAL_ACTION_END_MAP]		= "End.MAP",
 	[SEG6_LOCAL_ACTION_END_M_GTP4_E]	= "End.M.GTP4.E",
 	[SEG6_LOCAL_ACTION_END_M_GTP6_E]	= "End.M.GTP6.E",
+	[SEG6_LOCAL_ACTION_END_M_GTP6_D]	= "End.M.GTP6.D",
 };
 
 static const char *format_action_type(int action)
@@ -589,6 +590,11 @@ static void print_encap_seg6local(FILE *fp, struct rtattr *encap)
 		print_uint(PRINT_ANY, "v4_mask_len", "v4_mask_len %u ",
 			   rta_getattr_u8(tb[SEG6_LOCAL_MOBILE_V4_MASK_LEN]));
 
+	if (tb[SEG6_LOCAL_MOBILE_SR_PREFIX_LEN])
+		print_uint(PRINT_ANY, "sr_prefix_len",
+			   "sr_prefix_len %u ",
+			   rta_getattr_u8(tb[SEG6_LOCAL_MOBILE_SR_PREFIX_LEN]));
+
 	if (tb[SEG6_LOCAL_MOBILE_V6_SRC_PREFIX_LEN])
 		print_uint(PRINT_ANY, "v6_src_prefix_len",
 			   "v6_src_prefix_len %u ",
@@ -616,9 +622,26 @@ static void print_encap_seg6local(FILE *fp, struct rtattr *encap)
 	}
 }
 
+/*
+ * SRH-supplying actions (the seg6local equivalents of seg6 inline mode)
+ * pass the entire segment list explicitly; parse_srh() must not append the
+ * implicit terminating SID it adds for inline-style callers.
+ */
+static bool seg6local_action_excludes_final_seg(int action)
+{
+	switch (action) {
+	case SEG6_LOCAL_ACTION_END_B6_ENCAP:
+	case SEG6_LOCAL_ACTION_END_M_GTP6_D:
+		return true;
+	default:
+		return false;
+	}
+}
+
 static void seg6local_action_check_attrs(int action, int srh_ok, int nh6_ok,
 					 int mobile_src_ok,
 					 int mobile_v4mask_ok,
+					 int mobile_sr_plen_ok,
 					 int mobile_v6src_plen_ok,
 					 __u8 v4_mask_len,
 					 __u8 v6_src_prefix_len,
@@ -635,6 +658,11 @@ static void seg6local_action_check_attrs(int action, int srh_ok, int nh6_ok,
 		if (srh_ok)
 			invarg("End.M.GTP6.E does not accept \"srh\"\n", "");
 		break;
+	case SEG6_LOCAL_ACTION_END_M_GTP6_D:
+		if (!srh_ok || !mobile_src_ok || !mobile_sr_plen_ok)
+			invarg("End.M.GTP6.D requires \"srh segs\", \"src\","
+			       " and \"sr_prefix_len\"\n", "");
+		break;
 	case SEG6_LOCAL_ACTION_END_M_GTP4_E:
 		if (!mobile_src_ok || !mobile_v4mask_ok)
 			invarg("End.M.GTP4.E requires \"src\" and \"v4_mask_len\"\n",
@@ -666,9 +694,10 @@ static void seg6local_action_check_attrs(int action, int srh_ok, int nh6_ok,
 			       " \"v4_mask_len\" + 40 <= 128\n", "");
 		break;
 	default:
-		if (mobile_src_ok || mobile_v4mask_ok || mobile_v6src_plen_ok)
-			invarg("\"src\", \"v4_mask_len\", and"
-			       " \"v6_src_prefix_len\" are only valid for"
+		if (mobile_src_ok || mobile_v4mask_ok || mobile_sr_plen_ok ||
+		    mobile_v6src_plen_ok)
+			invarg("\"src\", \"v4_mask_len\", \"sr_prefix_len\","
+			       " and \"v6_src_prefix_len\" are only valid for"
 			       " SRv6 Mobile User Plane actions\n", "");
 		break;
 	}
@@ -1547,7 +1576,7 @@ static int parse_encap_seg6local(struct rtattr *rta, size_t len, int *argcp,
 	int segs_ok = 0, hmac_ok = 0, table_ok = 0, vrftable_ok = 0;
 	int action_ok = 0, srh_ok = 0, bpf_ok = 0, counters_ok = 0;
 	int mobile_src_ok = 0, mobile_v4mask_ok = 0, mobile_pdusess_ok = 0;
-	int mobile_v6src_plen_ok = 0;
+	int mobile_sr_plen_ok = 0, mobile_v6src_plen_ok = 0;
 	__u32 action = 0, table, vrftable, iif, oif;
 	struct ipv6_sr_hdr *srh;
 	char **argv = *argvp;
@@ -1555,7 +1584,7 @@ static int parse_encap_seg6local(struct rtattr *rta, size_t len, int *argcp,
 	char segbuf[1024];
 	inet_prefix addr;
 	__u32 hmac = 0;
-	__u8 v4_mask_len = 0, v6_src_prefix_len = 0;
+	__u8 v4_mask_len = 0, sr_prefix_len = 0, v6_src_prefix_len = 0;
 	int ret = 0;
 
 	while (argc > 0) {
@@ -1679,6 +1708,23 @@ static int parse_encap_seg6local(struct rtattr *rta, size_t len, int *argcp,
 				       *argv);
 			ret = rta_addattr8(rta, len, SEG6_LOCAL_MOBILE_V4_MASK_LEN,
 					   v4_mask_len);
+		} else if (strcmp(*argv, "sr_prefix_len") == 0) {
+			NEXT_ARG();
+			if (mobile_sr_plen_ok++)
+				duparg2("sr_prefix_len", *argv);
+			/*
+			 * The egress SID must leave room for the 40-bit
+			 * Args.Mob.Session field, so the locator can be at
+			 * most (128 - 40) = 88 bits.
+			 */
+			if (get_u8(&sr_prefix_len, *argv, 0) ||
+			    sr_prefix_len == 0 ||
+			    sr_prefix_len > 88)
+				invarg("\"sr_prefix_len\" must be in the range 1..88\n",
+				       *argv);
+			ret = rta_addattr8(rta, len,
+					   SEG6_LOCAL_MOBILE_SR_PREFIX_LEN,
+					   sr_prefix_len);
 		} else if (strcmp(*argv, "v6_src_prefix_len") == 0) {
 			NEXT_ARG();
 			if (mobile_v6src_plen_ok++)
@@ -1737,14 +1783,15 @@ static int parse_encap_seg6local(struct rtattr *rta, size_t len, int *argcp,
 	}
 
 	seg6local_action_check_attrs(action, srh_ok, nh6_ok, mobile_src_ok,
-				     mobile_v4mask_ok, mobile_v6src_plen_ok,
+				     mobile_v4mask_ok, mobile_sr_plen_ok,
+				     mobile_v6src_plen_ok,
 				     v4_mask_len, v6_src_prefix_len, dst_len);
 
 	if (srh_ok) {
 		int srhlen;
 
 		srh = parse_srh(segbuf, hmac,
-				action == SEG6_LOCAL_ACTION_END_B6_ENCAP);
+				seg6local_action_excludes_final_seg(action));
 		srhlen = (srh->hdrlen + 1) << 3;
 		ret = rta_addattr_l(rta, len, SEG6_LOCAL_SRH, srh, srhlen);
 		free(srh);
diff --git a/man/man8/ip-route.8.in b/man/man8/ip-route.8.in
index 7cf97924d699..35e6e2080a1f 100644
--- a/man/man8/ip-route.8.in
+++ b/man/man8/ip-route.8.in
@@ -1088,6 +1088,35 @@ takes the same syntax and semantics as in
 .B End.M.GTP4.E
 above (no PDU Session Container is inserted unless explicitly set).
 
+.B End.M.GTP6.D srh segs
+.IR SEGMENTS
+.B src
+.IR ADDRESS
+.B sr_prefix_len
+.IR BITS
+- SRv6 Mobile User Plane End.M.GTP6.D behavior (RFC 9433 Section 6.3).
+At the SR ingress gateway, the matching IPv6/UDP/GTP-U packet has its
+GTP-U envelope removed and the inner T-PDU is re-encapsulated in SRv6
+using the supplied SR Policy
+.RI ( srh
+\fBsegs\fR\~\fISEGMENTS\fR).  The TEID is folded into the 40-bit
+Args.Mob.Session field placed immediately after the egress
+End.M.GTP6.E SID's locator (RFC 9433 Section 6.5),
+.B sr_prefix_len
+specifying the locator length in bits (1..88 -- the upper bound
+leaves room for the 40-bit Args.Mob.Session field within the
+128-bit egress SID).  The egress SID's locator
+length cannot be inferred from local state at the SR Gateway.  The
+egress
+.B End.M.GTP6.E
+SID can then recover the per-session identifier from the same offset.
+The new outer IPv6 source address is taken from
+.BR src .
+The action requires either no SRH or an SRH with
+.B Segments Left
+equal to zero on the inbound packet; other matching packets are
+dropped.
+
 .B Flavors parameters
 
 The flavors represent additional operations that can modify or extend a
-- 
2.50.1


  reply	other threads:[~2026-05-03 15:45 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-05-03 15:45 [PATCH iproute2-next v1 RESEND 0/6] seg6: SRv6 Mobile User Plane (RFC 9433) Yuya Kusakabe
2026-05-03 15:45 ` [PATCH iproute2-next v1 RESEND 1/6] seg6: add support for the End.MAP behavior Yuya Kusakabe
2026-05-03 15:45   ` [PATCH iproute2-next v1 RESEND 2/6] seg6: add support for the End.M.GTP4.E behavior Yuya Kusakabe
2026-05-03 15:45     ` [PATCH iproute2-next v1 RESEND 3/6] seg6: add support for the End.M.GTP6.E behavior Yuya Kusakabe
2026-05-03 15:45       ` Yuya Kusakabe [this message]
2026-05-03 15:45         ` [PATCH iproute2-next v1 RESEND 5/6] seg6: add support for the End.M.GTP6.D.Di behavior Yuya Kusakabe
2026-05-03 15:45           ` [PATCH iproute2-next v1 RESEND 6/6] seg6: add support for the H.M.GTP4.D behavior Yuya Kusakabe

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260503154510.912576-5-yuya.kusakabe@gmail.com \
    --to=yuya.kusakabe@gmail.com \
    --cc=dsahern@kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=stephen@networkplumber.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox