[PATCH iproute2-next 1/6] seg6: add support for the End.MAP behavior

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

Add support for the End.MAP behavior, which swaps the IPv6 destination
address with the next SID without consuming the SRH. The new SID is
specified using the existing nh6 attribute.

A small per-action attribute validator is also introduced so that
invalid keyword combinations fail at the userspace layer instead of
returning an opaque kernel EINVAL. Subsequent SRv6 Mobile behaviors
extend the validator with their own keyword bookkeeping.

Example:
ip -6 r a 2001:db8:f::/64 encap seg6local action End.MAP \
nh6 2001:db8:1::beef 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 | 3 ++-
ip/iproute_lwtunnel.c | 13 +++++++++++++
man/man8/ip-route.8.in | 9 +++++++++
4 files changed, 26 insertions(+), 1 deletion(-)

diff --git a/include/uapi/linux/seg6_local.h b/include/uapi/linux/seg6_local.h
index 6e71d97f6f44..1678db71e8e7 100644
--- a/include/uapi/linux/seg6_local.h
+++ b/include/uapi/linux/seg6_local.h
@@ -67,6 +67,8 @@ enum {
SEG6_LOCAL_ACTION_END_BPF = 15,
/* decap and lookup of DA in v4 or v6 table */
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,

__SEG6_LOCAL_ACTION_MAX,
};
diff --git a/ip/iproute.c b/ip/iproute.c
index 5b9e7ac1134a..61394847018f 100644
--- a/ip/iproute.c
+++ b/ip/iproute.c
@@ -105,7 +105,8 @@ static void usage(void)
"SEG6LOCAL := action ACTION [ OPTIONS ] [ count ]\n"
"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.BM | End.S | End.AS | End.AM | End.BPF |\n"
+ " End.MAP }\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"
diff --git a/ip/iproute_lwtunnel.c b/ip/iproute_lwtunnel.c
index 00b4f7565be6..15c0077a4566 100644
--- a/ip/iproute_lwtunnel.c
+++ b/ip/iproute_lwtunnel.c
@@ -405,6 +405,7 @@ static const char *seg6_action_names[SEG6_LOCAL_ACTION_MAX + 1] = {
[SEG6_LOCAL_ACTION_END_AM] = "End.AM",
[SEG6_LOCAL_ACTION_END_BPF] = "End.BPF",
[SEG6_LOCAL_ACTION_END_DT46] = "End.DT46",
+ [SEG6_LOCAL_ACTION_END_MAP] = "End.MAP",
};

static const char *format_action_type(int action)
@@ -578,6 +579,16 @@ static void print_encap_seg6local(FILE *fp, struct rtattr *encap)
print_seg6_local_flavors(fp, tb[SEG6_LOCAL_FLAVORS]);
}

+static void seg6local_action_check_attrs(int action, int nh6_ok)
+{
+ switch (action) {
+ case SEG6_LOCAL_ACTION_END_MAP:
+ if (!nh6_ok)
+ invarg("End.MAP requires \"nh6\"\n", "");
+ break;
+ }
+}
+
static void print_encap_mpls(FILE *fp, struct rtattr *encap)
{
struct rtattr *tb[MPLS_IPTUNNEL_MAX+1];
@@ -1571,6 +1582,8 @@ static int parse_encap_seg6local(struct rtattr *rta, size_t len, int *argcp,
exit(-1);
}

+ seg6local_action_check_attrs(action, nh6_ok);
+
if (srh_ok) {
int srhlen;

diff --git a/man/man8/ip-route.8.in b/man/man8/ip-route.8.in
index 9f29fd436f59..c0b1e87ad022 100644
--- a/man/man8/ip-route.8.in
+++ b/man/man8/ip-route.8.in
@@ -1024,6 +1024,15 @@ followed by the specified SRH. The destination address of the outer IPv6
header is set to the first segment of the new SRH. The source
address is set as described in \fBip-sr\fR(8).

+.B End.MAP nh6
+.IR ADDRESS
+- SRv6 Mobile User Plane End.MAP behavior (RFC 9433 Section 6.2).
+Decrement the IPv6 Hop Limit, replace the IPv6 destination address
+with the configured next SID
+.RI ( nh6 ),
+and forward via the IPv6 FIB. The Segment Routing Header is left
+untouched.