[PATCH iproute2-next 6/6] seg6: add support for the H.M.GTP4.D behavior

Yuya Kusakabe
2026-05-03T15:30:52Z

Add support for the H.M.GTP4.D headend behavior, which translates
IPv4/GTP-U traffic into an SRv6 SR Policy. H.M.GTP4.D is installed
on an IPv4 route and reuses the existing src, v4_mask_len,
sr_prefix_len, and v6_src_prefix_len keywords.

The userspace parser fails fast when sr_prefix_len + v4_mask_len
exceeds 88 bits, since the locator, the embedded IPv4 destination,
and the 40-bit Args.Mob.Session field together must fit inside the
128-bit egress SID.

Example:
ip -4 r a 10.0.0.0/24 encap seg6local action H.M.GTP4.D \
nh6 2001:db8:f:: src 2001:db8::1 \
v4_mask_len 32 sr_prefix_len 56 v6_src_prefix_len 64 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 | 2 ++
ip/iproute.c | 2 +-
ip/iproute_lwtunnel.c | 31 ++++++++++++++++++++++-
man/man8/ip-route.8.in | 45 +++++++++++++++++++++++++++++++++
4 files changed, 78 insertions(+), 2 deletions(-)

diff --git a/include/uapi/linux/seg6_local.h b/include/uapi/linux/seg6_local.h
index 0ca8405df2f2..69a875fcad73 100644
--- a/include/uapi/linux/seg6_local.h
+++ b/include/uapi/linux/seg6_local.h
@@ -82,6 +82,8 @@ enum {
SEG6_LOCAL_ACTION_END_M_GTP6_D = 20,
/* IPv6/GTP-U decap into SRv6, drop-in mode (RFC 9433 Section 6.4) */
SEG6_LOCAL_ACTION_END_M_GTP6_D_DI = 21,
+ /* SR headend: IPv4/GTP-U decap, encap in SRv6 (RFC 9433 Section 6.7) */
+ SEG6_LOCAL_ACTION_H_M_GTP4_D = 22,

__SEG6_LOCAL_ACTION_MAX,
};
diff --git a/ip/iproute.c b/ip/iproute.c
index 1604545febc8..c3025f426f2e 100644
--- a/ip/iproute.c
+++ b/ip/iproute.c
@@ -107,7 +107,7 @@ static void usage(void)
" 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.M.GTP6.D | End.M.GTP6.D.Di }\n"
+ " End.M.GTP6.D | End.M.GTP6.D.Di | H.M.GTP4.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"
diff --git a/ip/iproute_lwtunnel.c b/ip/iproute_lwtunnel.c
index 1234c9f146bf..dfbe8899bca0 100644
--- a/ip/iproute_lwtunnel.c
+++ b/ip/iproute_lwtunnel.c
@@ -410,6 +410,7 @@ static const char *seg6_action_names[SEG6_LOCAL_ACTION_MAX + 1] = {
[SEG6_LOCAL_ACTION_END_M_GTP6_E] = "End.M.GTP6.E",
[SEG6_LOCAL_ACTION_END_M_GTP6_D] = "End.M.GTP6.D",
[SEG6_LOCAL_ACTION_END_M_GTP6_D_DI] = "End.M.GTP6.D.Di",
+ [SEG6_LOCAL_ACTION_H_M_GTP4_D] = "H.M.GTP4.D",
};

static const char *format_action_type(int action)
@@ -646,6 +647,7 @@ static void seg6local_action_check_attrs(int action, int srh_ok, int nh6_ok,
int mobile_sr_plen_ok,
int mobile_v6src_plen_ok,
__u8 v4_mask_len,
+ __u8 sr_prefix_len,
__u8 v6_src_prefix_len,
__u8 dst_len)
{
@@ -673,6 +675,32 @@ static void seg6local_action_check_attrs(int action, int srh_ok, int nh6_ok,
invarg("End.M.GTP6.D.Di does not accept \"sr_prefix_len\"\n",
"");
break;
+ case SEG6_LOCAL_ACTION_H_M_GTP4_D:
+ if (!nh6_ok || !mobile_src_ok || !mobile_v4mask_ok ||
+ !mobile_sr_plen_ok)
+ invarg("H.M.GTP4.D requires \"nh6\", \"src\","
+ " \"v4_mask_len\", and \"sr_prefix_len\"\n", "");
+ /*
+ * The 128-bit egress SID built by H.M.GTP4.D packs the SR
+ * locator, the embedded IPv4 destination, and the 40-bit
+ * Args.Mob.Session field, so sr_prefix_len + v4_mask_len
+ * must leave room for it.
+ */
+ if ((unsigned int)sr_prefix_len +
+ (unsigned int)v4_mask_len > 88)
+ invarg("H.M.GTP4.D requires \"sr_prefix_len\" +"
+ " \"v4_mask_len\" <= 88"
+ " (40 bits reserved for Args.Mob.Session)\n", "");
+ /*
+ * IPv6 SA layout per RFC 9433 Section 6.6 Figure 10 reused
+ * by H.M.GTP4.D headend; same combined bound as End.M.GTP4.E.
+ */
+ if (mobile_v6src_plen_ok &&
+ (unsigned int)v6_src_prefix_len +
+ (unsigned int)v4_mask_len > 128)
+ invarg("H.M.GTP4.D requires \"v6_src_prefix_len\" +"
+ " \"v4_mask_len\" <= 128\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",
@@ -1795,7 +1823,8 @@ 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_sr_plen_ok,
mobile_v6src_plen_ok,
- v4_mask_len, v6_src_prefix_len, dst_len);
+ v4_mask_len, sr_prefix_len,
+ v6_src_prefix_len, dst_len);

if (srh_ok) {
int srhlen;
diff --git a/man/man8/ip-route.8.in b/man/man8/ip-route.8.in
index 0487338707c6..7badfcc1e8c3 100644
--- a/man/man8/ip-route.8.in
+++ b/man/man8/ip-route.8.in
@@ -1133,6 +1133,51 @@ is rejected for this action: the original outer destination is
preserved verbatim instead of being repacked with an Args.Mob.Session
field, so no locator length needs to be carried.

+.B H.M.GTP4.D nh6
+.IR ADDRESS
+.B src
+.IR ADDRESS
+.B v4_mask_len
+.IR BITS
+.B sr_prefix_len
+.IR BITS
+.RB [ "v6_src_prefix_len"
+.IR BITS ]
+- SRv6 Mobile User Plane H.M.GTP4.D headend behavior (RFC 9433 Section
+6.7). Match an IPv4/UDP/GTP-U packet, strip the GTP-U envelope, and
+re-encapsulate the inner T-PDU in a new IPv6 header whose addresses
+encode the per-session identifiers expected by an
+.B End.M.GTP4.E
+SID at the egress SR gateway. The destination UPF prefix template is
+specified by
+.BR nh6 ,
+the source UPF prefix template by
+.BR src ;
+.B v4_mask_len
+is the bit length reserved for the original IPv4 destination/source
+address, and
+.B sr_prefix_len
+is the locator length of the egress End.M.GTP4.E SID (1..88).
+The 40-bit Args.Mob.Session field defined
+by RFC 9433 Section 6.1 follows the embedded IPv4 destination at the
+offset implied by
+.BR sr_prefix_len " + " v4_mask_len ;
+its width is fixed by the RFC and is not exposed as a knob.
+.BR sr_prefix_len " + " v4_mask_len
+must therefore be at most 88 bits so the resulting 128-bit SID can
+hold all three fields.
+.B v6_src_prefix_len
+controls the IPv6 SA layout per RFC 9433 Section 6.6 Figure 10
+(\fIP\fR | IPv4 SA | padding) in the same way as for End.M.GTP4.E:
+1..127, and
+.BR v6_src_prefix_len " + " v4_mask_len " <= 128" ;
+defaults to 64 when omitted.
+.PP
+.B Note:
+because H.M.GTP4.D matches IPv4/GTP-U packets, the route must be
+installed on the IPv4 FIB (\fBip -4 route add ...\fR); installing it
+under \fBip -6 route\fR is rejected by the kernel.