Wire up a new "seg6mobile" lightweight tunnel encap type that mirrors the kernel's LWTUNNEL_ENCAP_SEG6_MOBILE namespace, and add parse and print support for the first RFC 9433 behavior it carries: End.MAP. End.MAP swaps the IPv6 destination address with the configured mapped SID and forwards the packet via the IPv6 FIB without consuming the SRH: ip -6 route add 2001:db8:f::/64 \ encap seg6mobile action End.MAP mapped_sid 2001:db8:2::e \ dev Help text and the ip-route(8) man page get the corresponding ENCAPTYPE and SEG6MOBILE entries. Assisted-by: Claude:claude-fable-5 Signed-off-by: Yuya Kusakabe --- ip/iproute.c | 6 +- ip/iproute_lwtunnel.c | 173 +++++++++++++++++++++++++++++++++++++++++++++++++ man/man8/ip-route.8.in | 34 +++++++++- 3 files changed, 210 insertions(+), 3 deletions(-) diff --git a/ip/iproute.c b/ip/iproute.c index 1ce71f78e064..27def3b7e346 100644 --- a/ip/iproute.c +++ b/ip/iproute.c @@ -98,8 +98,8 @@ static void usage(void) "TIME := NUMBER[s|ms]\n" "BOOL := [1|0]\n" "FEATURES := ecn\n" - "ENCAPTYPE := [ mpls | ip | ip6 | seg6 | seg6local | rpl | ioam6 | xfrm ]\n" - "ENCAPHDR := [ MPLSLABEL | SEG6HDR | SEG6LOCAL | IOAM6HDR | XFRMINFO ]\n" + "ENCAPTYPE := [ mpls | ip | ip6 | seg6 | seg6local | seg6mobile | rpl | ioam6 | xfrm ]\n" + "ENCAPHDR := [ MPLSLABEL | SEG6HDR | SEG6LOCAL | SEG6MOBILE | IOAM6HDR | XFRMINFO ]\n" "SEG6HDR := [ mode SEGMODE ] segs ADDR1,ADDRi,ADDRn [hmac HMACKEYID] [cleanup]\n" " [ lookup TABLEID ]\n" "SEGMODE := [ encap | encap.red | inline | l2encap | l2encap.red ]\n" @@ -112,6 +112,8 @@ static void usage(void) " table TABLEID | vrftable TABLEID | endpoint PROGNAME }\n" "FLAVORS := { FLAVOR[,FLAVOR] }\n" "FLAVOR := { psp | usp | usd | next-csid }\n" + "SEG6MOBILE := action MOBILE_ACTION mapped_sid ADDR [ count ]\n" + "MOBILE_ACTION := { End.MAP }\n" "IOAM6HDR := trace prealloc type IOAM6_TRACE_TYPE ns IOAM6_NAMESPACE size IOAM6_TRACE_SIZE\n" "XFRMINFO := if_id IF_ID [ link_dev LINK ]\n" "ROUTE_GET_FLAGS := ROUTE_GET_FLAG [ ROUTE_GET_FLAGS ]\n" diff --git a/ip/iproute_lwtunnel.c b/ip/iproute_lwtunnel.c index 9a1e747c6951..b3a2388906ec 100644 --- a/ip/iproute_lwtunnel.c +++ b/ip/iproute_lwtunnel.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -55,6 +56,8 @@ static const char *format_encap_type(uint16_t type) return "ioam6"; case LWTUNNEL_ENCAP_XFRM: return "xfrm"; + case LWTUNNEL_ENCAP_SEG6_MOBILE: + return "seg6mobile"; default: return "unknown"; } @@ -95,6 +98,8 @@ static uint16_t read_encap_type(const char *name) return LWTUNNEL_ENCAP_IOAM6; else if (strcmp(name, "xfrm") == 0) return LWTUNNEL_ENCAP_XFRM; + else if (strcmp(name, "seg6mobile") == 0) + return LWTUNNEL_ENCAP_SEG6_MOBILE; else if (strcmp(name, "help") == 0) encap_type_usage(); @@ -585,6 +590,91 @@ static void print_encap_seg6local(FILE *fp, struct rtattr *encap) print_seg6_local_flavors(fp, tb[SEG6_LOCAL_FLAVORS]); } +static const char *seg6_mobile_action_names[SEG6_MOBILE_ACTION_MAX + 1] = { + [SEG6_MOBILE_ACTION_END_MAP] = "End.MAP", +}; + +static const char *format_seg6_mobile_action(int action) +{ + if (action < 0 || action > SEG6_MOBILE_ACTION_MAX) + return ""; + + return seg6_mobile_action_names[action] ?: ""; +} + +static int read_seg6_mobile_action(const char *name) +{ + int i; + + for (i = 0; i < SEG6_MOBILE_ACTION_MAX + 1; i++) { + if (!seg6_mobile_action_names[i]) + continue; + + if (strcmp(seg6_mobile_action_names[i], name) == 0) + return i; + } + + return SEG6_MOBILE_ACTION_UNSPEC; +} + +static void print_seg6_mobile_counters(FILE *fp, struct rtattr *encap) +{ + struct rtattr *tb[SEG6_MOBILE_CNT_MAX + 1]; + __u64 packets = 0, bytes = 0, errors = 0; + + parse_rtattr_nested(tb, SEG6_MOBILE_CNT_MAX, encap); + + if (tb[SEG6_MOBILE_CNT_PACKETS]) + packets = rta_getattr_u64(tb[SEG6_MOBILE_CNT_PACKETS]); + + if (tb[SEG6_MOBILE_CNT_BYTES]) + bytes = rta_getattr_u64(tb[SEG6_MOBILE_CNT_BYTES]); + + if (tb[SEG6_MOBILE_CNT_ERRORS]) + errors = rta_getattr_u64(tb[SEG6_MOBILE_CNT_ERRORS]); + + if (is_json_context()) { + open_json_object("stats64"); + + print_u64(PRINT_JSON, "packets", NULL, packets); + print_u64(PRINT_JSON, "bytes", NULL, bytes); + print_u64(PRINT_JSON, "errors", NULL, errors); + + close_json_object(); + } else { + print_string(PRINT_FP, NULL, "%s ", "packets"); + print_num(fp, 1, packets); + + print_string(PRINT_FP, NULL, "%s ", "bytes"); + print_num(fp, 1, bytes); + + print_string(PRINT_FP, NULL, "%s ", "errors"); + print_num(fp, 1, errors); + } +} + +static void print_encap_seg6mobile(FILE *fp, struct rtattr *encap) +{ + struct rtattr *tb[SEG6_MOBILE_MAX + 1]; + int action; + + parse_rtattr_nested(tb, SEG6_MOBILE_MAX, encap); + + if (!tb[SEG6_MOBILE_ACTION]) + return; + + action = rta_getattr_u32(tb[SEG6_MOBILE_ACTION]); + print_string(PRINT_ANY, "action", + "action %s ", format_seg6_mobile_action(action)); + + if (tb[SEG6_MOBILE_MAPPED_SID]) + print_string(PRINT_ANY, "mapped_sid", "mapped_sid %s ", + rt_addr_n2a_rta(AF_INET6, tb[SEG6_MOBILE_MAPPED_SID])); + + if (tb[SEG6_MOBILE_COUNTERS] && show_stats) + print_seg6_mobile_counters(fp, tb[SEG6_MOBILE_COUNTERS]); +} + static void print_encap_mpls(FILE *fp, struct rtattr *encap) { struct rtattr *tb[MPLS_IPTUNNEL_MAX+1]; @@ -900,6 +990,9 @@ void lwt_print_encap(FILE *fp, struct rtattr *encap_type, case LWTUNNEL_ENCAP_XFRM: print_encap_xfrm(fp, encap); break; + case LWTUNNEL_ENCAP_SEG6_MOBILE: + print_encap_seg6mobile(fp, encap); + break; } close_json_object(); } @@ -1607,6 +1700,83 @@ static int parse_encap_seg6local(struct rtattr *rta, size_t len, int *argcp, return ret; } +/* for the moment, counters are always initialized to zero by the kernel; so we + * do not expect to parse any argument here. + */ +static int seg6mobile_fill_counters(struct rtattr *rta, size_t len, int attr) +{ + struct rtattr *nest; + int ret; + + nest = rta_nest(rta, len, attr); + + ret = rta_addattr64(rta, len, SEG6_MOBILE_CNT_PACKETS, 0); + if (ret < 0) + return ret; + + ret = rta_addattr64(rta, len, SEG6_MOBILE_CNT_BYTES, 0); + if (ret < 0) + return ret; + + ret = rta_addattr64(rta, len, SEG6_MOBILE_CNT_ERRORS, 0); + if (ret < 0) + return ret; + + rta_nest_end(rta, nest); + return 0; +} + +static int parse_encap_seg6mobile(struct rtattr *rta, size_t len, int *argcp, + char ***argvp) +{ + int action_ok = 0, mapped_sid_ok = 0, counters_ok = 0; + char **argv = *argvp; + int argc = *argcp; + inet_prefix addr; + __u32 action = 0; + int ret = 0; + + while (argc > 0) { + if (strcmp(*argv, "action") == 0) { + NEXT_ARG(); + if (action_ok++) + duparg2("action", *argv); + action = read_seg6_mobile_action(*argv); + if (!action) + invarg("\"action\" value is invalid\n", *argv); + ret = rta_addattr32(rta, len, SEG6_MOBILE_ACTION, + action); + } else if (strcmp(*argv, "mapped_sid") == 0) { + NEXT_ARG(); + if (mapped_sid_ok++) + duparg2("mapped_sid", *argv); + get_addr(&addr, *argv, AF_INET6); + ret = rta_addattr_l(rta, len, SEG6_MOBILE_MAPPED_SID, + &addr.data, addr.bytelen); + } else if (strcmp(*argv, "count") == 0) { + if (counters_ok++) + duparg2("count", *argv); + ret = seg6mobile_fill_counters(rta, len, + SEG6_MOBILE_COUNTERS); + } else { + break; + } + if (ret) + return ret; + argc--; argv++; + } + + if (!action) { + fprintf(stderr, "Missing action type\n"); + exit(-1); + } + + *argcp = argc + 1; + *argvp = argv - 1; + + return ret; +} + static int parse_encap_mpls(struct rtattr *rta, size_t len, int *argcp, char ***argvp) { @@ -2346,6 +2516,9 @@ int lwt_parse_encap(struct rtattr *rta, size_t len, int *argcp, char ***argvp, case LWTUNNEL_ENCAP_XFRM: ret = parse_encap_xfrm(rta, len, &argc, &argv); break; + case LWTUNNEL_ENCAP_SEG6_MOBILE: + ret = parse_encap_seg6mobile(rta, len, &argc, &argv); + break; default: fprintf(stderr, "Error: unsupported encap type\n"); break; diff --git a/man/man8/ip-route.8.in b/man/man8/ip-route.8.in index fd04432f599b..5a4777153967 100644 --- a/man/man8/ip-route.8.in +++ b/man/man8/ip-route.8.in @@ -195,7 +195,8 @@ throw " | " unreachable " | " prohibit " | " blackhole " | " nat " ]" .ti -8 .IR ENCAP " := [ " .IR ENCAP_MPLS " | " ENCAP_IP " | " ENCAP_BPF " | " -.IR ENCAP_SEG6 " | " ENCAP_SEG6LOCAL " | " ENCAP_IOAM6 " ] " +.IR ENCAP_SEG6 " | " ENCAP_SEG6LOCAL " | " ENCAP_SEG6MOBILE " | " +.IR ENCAP_IOAM6 " ] " .ti -8 .IR ENCAP_MPLS " := " @@ -258,6 +259,14 @@ throw " | " unreachable " | " prohibit " | " blackhole " | " nat " ]" .IR SEG6_ACTION_PARAM " ] [ " .BR count " ] " +.ti -8 +.IR ENCAP_SEG6MOBILE " := " +.B seg6mobile +.BR action +.IR SEG6_MOBILE_ACTION " [ " +.IR SEG6_MOBILE_PARAM " ] [ " +.BR count " ] " + .ti -8 .IR ENCAP_IOAM6 " := " .BR ioam6 " [" @@ -764,6 +773,9 @@ is a string specifying the supported encapsulation type. Namely: .BI seg6local - local SRv6 segment processing .sp +.BI seg6mobile +- SRv6 Mobile User Plane processing (RFC 9433) +.sp .BI ioam6 - encapsulation type IPv6 IOAM .sp @@ -1097,6 +1109,26 @@ flavors. The Locator-Node Function length must be greater than 0 and evenly divisible by 8. This attribute can be used only with NEXT-C-SID flavor. .in -4 +.B seg6mobile +.in +2 +.IR SEG6_MOBILE_ACTION " [ " +.IR SEG6_MOBILE_PARAM " ] [ " +.BR count " ]" +- SRv6 Mobile User Plane operation (RFC 9433) to perform on matching +packets. The optional \fBcount\fR attribute enables per-CPU packet, byte +and error counters for this route; the aggregated values are printed by +the \fB-s\fR flag in the \fBshow\fR command. The following actions are +currently supported. +.in +2 + +.B End.MAP mapped_sid +.I ADDRESS +- Replace the IPv6 destination address with the configured mapped SID +.RI ( ADDRESS ) +and forward via the IPv6 FIB; an SRH, if present, is not modified +(neither the SID list nor the Segments Left value). +.in -4 + .B ioam6 .in +2 .B freq K/N -- 2.50.1