Add the srl2 link type for creating SRv6 Ethernet pseudowire devices with a segment list parameter. Usage: ip link add srl2-0 type srl2 segs fc00::a,fc00::b Signed-off-by: Andrea Mayer --- include/uapi/linux/srl2.h | 20 +++++ ip/Makefile | 2 +- ip/iplink_srl2.c | 176 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 197 insertions(+), 1 deletion(-) create mode 100644 include/uapi/linux/srl2.h create mode 100644 ip/iplink_srl2.c diff --git a/include/uapi/linux/srl2.h b/include/uapi/linux/srl2.h new file mode 100644 index 00000000..e7c8f6fc --- /dev/null +++ b/include/uapi/linux/srl2.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later WITH Linux-syscall-note */ +/* + * SRv6 L2 tunnel device + * + * Author: + * Andrea Mayer + */ + +#ifndef _UAPI_LINUX_SRL2_H +#define _UAPI_LINUX_SRL2_H + +enum { + IFLA_SRL2_UNSPEC, + IFLA_SRL2_SRH, /* binary: struct ipv6_sr_hdr + segments */ + __IFLA_SRL2_MAX, +}; + +#define IFLA_SRL2_MAX (__IFLA_SRL2_MAX - 1) + +#endif diff --git a/ip/Makefile b/ip/Makefile index 3535ba78..99a1724d 100644 --- a/ip/Makefile +++ b/ip/Makefile @@ -11,7 +11,7 @@ IPOBJ=ip.o ipaddress.o ipaddrlabel.o iproute.o iprule.o ipnetns.o \ iplink_bridge.o iplink_bridge_slave.o iplink_dsa.o ipfou.o iplink_ipvlan.o \ iplink_geneve.o iplink_vrf.o iproute_lwtunnel.o ipmacsec.o ipila.o \ ipvrf.o iplink_xstats.o ipseg6.o iplink_netdevsim.o iplink_rmnet.o \ - ipnexthop.o ipmptcp.o iplink_bareudp.o iplink_wwan.o ipioam6.o \ + ipnexthop.o ipmptcp.o iplink_bareudp.o iplink_wwan.o ipioam6.o iplink_srl2.o \ iplink_amt.o iplink_batadv.o iplink_gtp.o iplink_virt_wifi.o \ iplink_netkit.o ipstats.o diff --git a/ip/iplink_srl2.c b/ip/iplink_srl2.c new file mode 100644 index 00000000..b0a546b3 --- /dev/null +++ b/ip/iplink_srl2.c @@ -0,0 +1,176 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * iproute2 support for SRv6 L2 tunnel device (srl2) + * + * Usage: + * ip link add srl2-0 type srl2 segs fc00::a,fc00::b + * ip link set srl2-0 up + * ip link set srl2-0 master br0 + */ + +#include +#include +#include +#include +#include + +#include "libnetlink.h" +#include "utils.h" +#include "ip_common.h" +#include "json_print.h" + +static void print_explain(FILE *f) +{ + fprintf(f, + "Usage: ... srl2 segs SEG1,SEG2,...,SEGn\n" + "\n" + "Where: SEGi := IPv6 address (SRv6 SID)\n" + ); +} + +static void explain(void) +{ + print_explain(stderr); +} + +/* Build an SRH from a comma-separated list of segments. + * The segment list is stored in reverse order in the SRH: + * segments[0] = last SID (final destination) + * segments[first_segment] = first SID (first hop) + * + * For a srl2 device, the SRH is used as an encap template. + * The first SID becomes the outer IPv6 DA after encapsulation. + * + * Returns a malloc'd SRH or NULL on error. Caller must free. + */ +static struct ipv6_sr_hdr *srl2_parse_srh(char *segbuf) +{ + struct ipv6_sr_hdr *srh; + int nsegs = 0; + int srhlen; + char *s; + int i; + + if (!segbuf || !*segbuf) + invarg("missing segment list", "segs"); + + s = segbuf; + for (i = 0; *s; *s++ == ',' ? i++ : *s); + nsegs = i + 1; + + srhlen = 8 + 16 * nsegs; + + srh = calloc(1, srhlen); + if (!srh) + return NULL; + + srh->hdrlen = (srhlen >> 3) - 1; + srh->type = 4; + srh->segments_left = nsegs - 1; + srh->first_segment = nsegs - 1; + + i = srh->first_segment; + for (s = strtok(segbuf, ","); s; s = strtok(NULL, ",")) { + inet_prefix addr; + + get_addr(&addr, s, AF_INET6); + memcpy(&srh->segments[i], addr.data, sizeof(struct in6_addr)); + i--; + } + + return srh; +} + +static int srl2_parse_opt(struct link_util *lu, int argc, char **argv, + struct nlmsghdr *n) +{ + struct ipv6_sr_hdr *srh = NULL; + char segbuf[1024] = {}; + int segs_ok = 0; + + while (argc > 0) { + if (strcmp(*argv, "segs") == 0) { + NEXT_ARG(); + if (segs_ok++) + duparg2("segs", *argv); + if (strlen(*argv) >= sizeof(segbuf)) + invarg("segment list too long", "segs"); + strlcpy(segbuf, *argv, sizeof(segbuf)); + } else if (strcmp(*argv, "help") == 0) { + explain(); + return -1; + } else { + fprintf(stderr, "srl2: unknown command \"%s\"?\n", + *argv); + explain(); + return -1; + } + argc--, argv++; + } + + if (!segs_ok) { + fprintf(stderr, "srl2: missing \"segs\" argument\n"); + explain(); + return -1; + } + + srh = srl2_parse_srh(segbuf); + if (!srh) { + fprintf(stderr, "srl2: failed to parse segment list\n"); + return -1; + } + + addattr_l(n, 1024, IFLA_SRL2_SRH, srh, + (srh->hdrlen + 1) << 3); + + free(srh); + return 0; +} + +static void srl2_print_opt(struct link_util *lu, FILE *f, + struct rtattr *tb[]) +{ + struct ipv6_sr_hdr *srh; + int i; + + if (!tb) + return; + + if (!tb[IFLA_SRL2_SRH]) + return; + + srh = RTA_DATA(tb[IFLA_SRL2_SRH]); + + if (is_json_context()) + open_json_array(PRINT_JSON, "segs"); + else + print_string(PRINT_FP, NULL, "segs %s", ""); + + for (i = srh->first_segment; i >= 0; i--) { + if (!is_json_context() && i < srh->first_segment) + print_string(PRINT_FP, NULL, "%s", ","); + + print_color_string(PRINT_ANY, COLOR_INET6, NULL, "%s", + rt_addr_n2a(AF_INET6, 16, + &srh->segments[i])); + } + + if (is_json_context()) + close_json_array(PRINT_JSON, NULL); + else + print_string(PRINT_FP, NULL, "%s", " "); +} + +static void srl2_print_help(struct link_util *lu, int argc, char **argv, + FILE *f) +{ + print_explain(f); +} + +struct link_util srl2_link_util = { + .id = "srl2", + .maxattr = IFLA_SRL2_MAX, + .parse_opt = srl2_parse_opt, + .print_opt = srl2_print_opt, + .print_help = srl2_print_help, +}; -- 2.20.1