From: Yuya Kusakabe <yuya.kusakabe@gmail.com>
To: dsahern@kernel.org
Cc: Yuya Kusakabe <yuya.kusakabe@gmail.com>, netdev@vger.kernel.org
Subject: [PATCH iproute2-next v2 2/6] seg6: add support for the End.M.GTP4.E behavior
Date: Tue, 05 May 2026 01:10:42 +0900 [thread overview]
Message-ID: <20260505-seg6-mobile-v2-2-93291b7b0134@gmail.com> (raw)
In-Reply-To: <20260505-seg6-mobile-v2-0-93291b7b0134@gmail.com>
Add support for the End.M.GTP4.E behavior, which translates SRv6
traffic into IPv4/GTP-U. Four new keywords are introduced:
src IPv6 source-address template
v4_mask_len IPv4 DA portion of the SID, in bits (1..32)
v6_src_prefix_len Source UPF Prefix length P in the IPv6 SA
template (1..127, defaults to 64); requires
P + v4_mask_len <= 128
pdu_type GTP-U PDU Session Container PDU Type
(downlink|dl|uplink|ul or 0..15)
Example:
ip -6 r a 2001:db8:1::/56 encap seg6local action End.M.GTP4.E \
src 2001:db8::1 v4_mask_len 32 v6_src_prefix_len 64 \
pdu_type ul 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 | 6 +++
ip/iproute.c | 6 ++-
ip/iproute_lwtunnel.c | 103 ++++++++++++++++++++++++++++++++++++++++
man/man8/ip-route.8.in | 34 +++++++++++++
4 files changed, 147 insertions(+), 2 deletions(-)
diff --git a/include/uapi/linux/seg6_local.h b/include/uapi/linux/seg6_local.h
index 1678db71e8e7..8bb3cdc3a649 100644
--- a/include/uapi/linux/seg6_local.h
+++ b/include/uapi/linux/seg6_local.h
@@ -29,6 +29,10 @@ enum {
SEG6_LOCAL_VRFTABLE,
SEG6_LOCAL_COUNTERS,
SEG6_LOCAL_FLAVORS,
+ SEG6_LOCAL_MOBILE_SRC_ADDR,
+ SEG6_LOCAL_MOBILE_V4_MASK_LEN,
+ SEG6_LOCAL_MOBILE_PDU_TYPE,
+ SEG6_LOCAL_MOBILE_V6_SRC_PREFIX_LEN,
__SEG6_LOCAL_MAX,
};
#define SEG6_LOCAL_MAX (__SEG6_LOCAL_MAX - 1)
@@ -69,6 +73,8 @@ enum {
SEG6_LOCAL_ACTION_END_DT46 = 16,
/* swap DA with new SID, leave SRH untouched (RFC 9433 Section 6.2) */
SEG6_LOCAL_ACTION_END_MAP = 17,
+ /* SRv6 to IPv4/GTP-U encap (RFC 9433 Section 6.6) */
+ SEG6_LOCAL_ACTION_END_M_GTP4_E = 18,
__SEG6_LOCAL_ACTION_MAX,
};
diff --git a/ip/iproute.c b/ip/iproute.c
index 61394847018f..f9ebba6541af 100644
--- a/ip/iproute.c
+++ b/ip/iproute.c
@@ -106,10 +106,12 @@ 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 }\n"
+ " End.MAP | End.M.GTP4.E }\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 }\n"
+ " table TABLEID | vrftable TABLEID | endpoint PROGNAME | MOBILE_OPTION }\n"
+ "MOBILE_OPTION := { src ADDR | v4_mask_len BITS | 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"
"IOAM6HDR := trace prealloc type IOAM6_TRACE_TYPE ns IOAM6_NAMESPACE size IOAM6_TRACE_SIZE\n"
diff --git a/ip/iproute_lwtunnel.c b/ip/iproute_lwtunnel.c
index 3a25835662d1..49fe563d9b86 100644
--- a/ip/iproute_lwtunnel.c
+++ b/ip/iproute_lwtunnel.c
@@ -406,6 +406,7 @@ static const char *seg6_action_names[SEG6_LOCAL_ACTION_MAX + 1] = {
[SEG6_LOCAL_ACTION_END_BPF] = "End.BPF",
[SEG6_LOCAL_ACTION_END_DT46] = "End.DT46",
[SEG6_LOCAL_ACTION_END_MAP] = "End.MAP",
+ [SEG6_LOCAL_ACTION_END_M_GTP4_E] = "End.M.GTP4.E",
};
static const char *format_action_type(int action)
@@ -577,6 +578,41 @@ static void print_encap_seg6local(FILE *fp, struct rtattr *encap)
if (tb[SEG6_LOCAL_FLAVORS])
print_seg6_local_flavors(fp, tb[SEG6_LOCAL_FLAVORS]);
+
+ if (tb[SEG6_LOCAL_MOBILE_SRC_ADDR])
+ print_string(PRINT_ANY, "src", "src %s ",
+ rt_addr_n2a_rta(AF_INET6,
+ tb[SEG6_LOCAL_MOBILE_SRC_ADDR]));
+
+ if (tb[SEG6_LOCAL_MOBILE_V4_MASK_LEN])
+ 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_V6_SRC_PREFIX_LEN])
+ print_uint(PRINT_ANY, "v6_src_prefix_len",
+ "v6_src_prefix_len %u ",
+ rta_getattr_u8(tb[SEG6_LOCAL_MOBILE_V6_SRC_PREFIX_LEN]));
+
+ if (tb[SEG6_LOCAL_MOBILE_PDU_TYPE]) {
+ __u8 t = rta_getattr_u8(tb[SEG6_LOCAL_MOBILE_PDU_TYPE]);
+ const char *name = NULL;
+
+ switch (t) {
+ case 0:
+ name = "downlink";
+ break;
+ case 1:
+ name = "uplink";
+ break;
+ }
+
+ if (name)
+ print_string(PRINT_ANY, "pdu_type",
+ "pdu_type %s ", name);
+ else
+ print_uint(PRINT_ANY, "pdu_type",
+ "pdu_type %u ", t);
+ }
}
static void print_encap_mpls(FILE *fp, struct rtattr *encap)
@@ -1451,6 +1487,8 @@ static int parse_encap_seg6local(struct rtattr *rta, size_t len, int *argcp,
int nh4_ok = 0, nh6_ok = 0, iif_ok = 0, oif_ok = 0, flavors_ok = 0;
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;
__u32 action = 0, table, vrftable, iif, oif;
struct ipv6_sr_hdr *srh;
char **argv = *argvp;
@@ -1458,6 +1496,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;
int ret = 0;
while (argc > 0) {
@@ -1559,6 +1598,70 @@ static int parse_encap_seg6local(struct rtattr *rta, size_t len, int *argcp,
if (lwt_parse_bpf(rta, len, &argc, &argv, SEG6_LOCAL_BPF,
BPF_PROG_TYPE_LWT_SEG6LOCAL) < 0)
exit(-1);
+ } else if (strcmp(*argv, "src") == 0) {
+ /*
+ * Mobile User Plane "src" template; scoped to the
+ * seg6local block and unrelated to the top-level
+ * "src" prefsrc keyword.
+ */
+ NEXT_ARG();
+ if (mobile_src_ok++)
+ duparg2("src", *argv);
+ get_addr(&addr, *argv, AF_INET6);
+ ret = rta_addattr_l(rta, len, SEG6_LOCAL_MOBILE_SRC_ADDR,
+ &addr.data, addr.bytelen);
+ } else if (strcmp(*argv, "v4_mask_len") == 0) {
+ NEXT_ARG();
+ if (mobile_v4mask_ok++)
+ duparg2("v4_mask_len", *argv);
+ if (get_u8(&v4_mask_len, *argv, 0) ||
+ v4_mask_len == 0 || v4_mask_len > 32)
+ invarg("\"v4_mask_len\" must be in the range 1..32\n",
+ *argv);
+ ret = rta_addattr8(rta, len, SEG6_LOCAL_MOBILE_V4_MASK_LEN,
+ v4_mask_len);
+ } else if (strcmp(*argv, "v6_src_prefix_len") == 0) {
+ NEXT_ARG();
+ if (mobile_v6src_plen_ok++)
+ duparg2("v6_src_prefix_len", *argv);
+ /*
+ * Per RFC 9433 Section 6.6 Figure 10, the IPv6 SA is
+ * "Source UPF Prefix (P bits) | IPv4 SA (b bits) |
+ * padding (128 - P - b)". P is validated as 1..127
+ * here; the kernel enforces P + b <= 128 via netlink
+ * extack.
+ */
+ if (get_u8(&v6_src_prefix_len, *argv, 0) ||
+ v6_src_prefix_len == 0 ||
+ v6_src_prefix_len > 127)
+ invarg("\"v6_src_prefix_len\" must be in the range 1..127\n",
+ *argv);
+ ret = rta_addattr8(rta, len,
+ SEG6_LOCAL_MOBILE_V6_SRC_PREFIX_LEN,
+ v6_src_prefix_len);
+ } else if (strcmp(*argv, "pdu_type") == 0) {
+ __u8 psc_type;
+
+ NEXT_ARG();
+ if (mobile_pdusess_ok++)
+ duparg2("pdu_type", *argv);
+ /*
+ * 3GPP TS 38.415 PDU Session Type is a 4-bit field; the
+ * kernel mirrors that range (0..15). 0 = DL, 1 = UL.
+ */
+ if (strcmp(*argv, "downlink") == 0 ||
+ strcmp(*argv, "dl") == 0) {
+ psc_type = 0;
+ } else if (strcmp(*argv, "uplink") == 0 ||
+ strcmp(*argv, "ul") == 0) {
+ psc_type = 1;
+ } else if (get_u8(&psc_type, *argv, 0) ||
+ psc_type > 15) {
+ invarg("invalid \"pdu_type\" value (must be downlink|dl|uplink|ul or 0..15)\n", *argv);
+ }
+ ret = rta_addattr8(rta, len,
+ SEG6_LOCAL_MOBILE_PDU_TYPE,
+ psc_type);
} else {
break;
}
diff --git a/man/man8/ip-route.8.in b/man/man8/ip-route.8.in
index c0b1e87ad022..a878d4375f03 100644
--- a/man/man8/ip-route.8.in
+++ b/man/man8/ip-route.8.in
@@ -1033,6 +1033,40 @@ with the configured next SID
and forward via the IPv6 FIB. The Segment Routing Header is left
untouched.
+.B End.M.GTP4.E src
+.IR ADDRESS
+.B v4_mask_len
+.IR BITS
+.RB [ "v6_src_prefix_len"
+.IR BITS ]
+.RB [ "pdu_type"
+.IR DIR ]
+- SRv6 Mobile User Plane End.M.GTP4.E behavior (RFC 9433 Section 6.6).
+At the SR egress gateway, the matching SRv6 packet is converted into
+an IPv4/UDP/GTP-U packet for delivery to a legacy IPv4-attached gNB or
+eNB. The IPv6 destination address of the matching SID encodes
+.IR Locator " | " "IPv4 DA" " (\fBv4_mask_len\fR bits) | "
+.IR "Args.Mob.Session" " (40 bits, RFC 9433 Section 6.1)" ,
+and the IPv6 source address is built from
+.B src
+as
+.IR "Source UPF Prefix" " (\fBv6_src_prefix_len\fR bits) | "
+.IR "IPv4 SA" " (\fBv4_mask_len\fR bits) | padding" .
+.B v4_mask_len
+must be in 1..32 and
+.B v6_src_prefix_len
+in 1..127 (default 64); the route prefix length plus
+.BR v4_mask_len " + 40"
+and
+.BR v6_src_prefix_len " + " v4_mask_len
+must each fit in 128 bits.
+.B pdu_type
+.RB ( downlink | dl | uplink | ul " or " 0..15 )
+forces a GTP-U PDU Session Container (3GPP TS 38.415) with the given
+PDU Type; when omitted no Container is inserted, so 5G N3 deployments
+must set it explicitly.
+The action only accepts packets with Segments Left = 0 or no SRH.
+
.B Flavors parameters
The flavors represent additional operations that can modify or extend a
--
2.50.1
next prev parent reply other threads:[~2026-05-04 16:10 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-04 16:10 [PATCH iproute2-next v2 0/6] seg6: SRv6 Mobile User Plane (RFC 9433) Yuya Kusakabe
2026-05-04 16:10 ` [PATCH iproute2-next v2 1/6] seg6: add support for the End.MAP behavior Yuya Kusakabe
2026-05-04 16:10 ` Yuya Kusakabe [this message]
2026-05-04 16:10 ` [PATCH iproute2-next v2 3/6] seg6: add support for the End.M.GTP6.E behavior Yuya Kusakabe
2026-05-04 16:10 ` [PATCH iproute2-next v2 4/6] seg6: add support for the End.M.GTP6.D behavior Yuya Kusakabe
2026-05-04 16:10 ` [PATCH iproute2-next v2 5/6] seg6: add support for the End.M.GTP6.D.Di behavior Yuya Kusakabe
2026-05-04 16:10 ` [PATCH iproute2-next v2 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=20260505-seg6-mobile-v2-2-93291b7b0134@gmail.com \
--to=yuya.kusakabe@gmail.com \
--cc=dsahern@kernel.org \
--cc=netdev@vger.kernel.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