Subject: Re: [iproute2-next v1] seg6: add support for flavors in SRv6 End* behaviors

From: Anton Makarov <[email-masked]>
Date: Mon, 13 Jun 2022 15:54:51 +0300

Hi Paolo,
Please see my comment inline.

On Sat, 11 Jun 2022 13:06:45 +0200
Paolo Lungaroni <[email-masked]> wrote:

> As described in RFC 8986 [1], processing operations carried out by SRv6
> End, End.X and End.T (End* for short) behaviors can be modified or
> extended using the "flavors" mechanism. This patch adds the support for
> PSP,USP,USD flavors (defined in [1]) and for NEXT-C-SID flavor (defined
> in [2]) in SRv6 End* behaviors. Specifically, we add a new optional
> attribute named "flavors" that can be leveraged by the user to enable
> specific flavors while creating an SRv6 End* behavior instance.
> Multiple flavors can be specified together by separating them using
> commas.
> 
> If a specific flavor (or a combination of flavors) is not supported by the
> underlying Linux kernel, an error message is reported to the user and the
> creation of the specific behavior instance is aborted.
> 
> When the flavors attribute is omitted, the regular SRv6 End* behavior is
> performed.
> 
> Flavors such as PSP, USP and USD do not accept additional configuration
> attributes. Conversely, the NEXT-C-SID flavor can be configured to support
> user-provided Locator-Block and Locator-Node Function lengths using,
> respectively, the lblen and the nflen attributes.
> 
> Both lblen and nflen values must be evenly divisible by 8 and their sum
> must not exceed 128 bit (i.e. the C-SID container size).
> 
> If the lblen attribute is omitted, the default value chosen by the Linux
> kernel is 32-bit. If the nflen attribute is omitted, the default value
> chosen by the Linux kernel is 16-bit.
> 
> Some examples:
> ip -6 route add 2001:db8::1 encap seg6local action End flavors next-csid dev eth0
> ip -6 route add 2001:db8::2 encap seg6local action End flavors next-csid lblen 48 nflen 16 dev eth0

As I already noted in another thread mandatory dev parameter
of End behavior violates RFC 8986 definitions. Egress interface has
to be defined during IP lookup.

> 
> Standard Output:
> ip -6 route show 2001:db8::2
> 2001:db8::2  encap seg6local action End flavors next-csid lblen 48 nflen 16 dev eth0 metric 1024 pref medium
> 
> JSON Output:
> ip -6 -j -p route show 2001:db8::2
> [ {
>         "dst": "2001:db8::2",
>         "encap": "seg6local",
>         "action": "End",
>         "flavors": [ "next-csid" ],
>         "lblen": 48,
>         "nflen": 16,
>         "dev": "eth0",
>         "metric": 1024,
>         "flags": [ ],
>         "pref": "medium"
> } ]
> 
> [1] - https://datatracker.ietf.org/doc/html/rfc8986
> [2] - https://datatracker.ietf.org/doc/html/draft-ietf-spring-srv6-srh-compression
> 
> Signed-off-by: Paolo Lungaroni <[email-masked]>
> ---
>  include/uapi/linux/seg6_local.h |  24 ++++
>  ip/iproute_lwtunnel.c           | 188 +++++++++++++++++++++++++++++++-
>  man/man8/ip-route.8.in          |  71 +++++++++++-
>  3 files changed, 280 insertions(+), 3 deletions(-)
> 
> diff --git a/include/uapi/linux/seg6_local.h b/include/uapi/linux/seg6_local.h
> index ab724498..12f76829 100644
> --- a/include/uapi/linux/seg6_local.h
> +++ b/include/uapi/linux/seg6_local.h
> @@ -28,6 +28,7 @@ enum {
>  	SEG6_LOCAL_BPF,
>  	SEG6_LOCAL_VRFTABLE,
>  	SEG6_LOCAL_COUNTERS,
> +	SEG6_LOCAL_FLAVORS,
>  	__SEG6_LOCAL_MAX,
>  };
>  #define SEG6_LOCAL_MAX (__SEG6_LOCAL_MAX - 1)
> @@ -110,4 +111,27 @@ enum {
>  
>  #define SEG6_LOCAL_CNT_MAX (__SEG6_LOCAL_CNT_MAX - 1)
>  
> +/* SRv6 End* Flavor attributes */
> +enum {
> +	SEG6_LOCAL_FLV_UNSPEC,
> +	SEG6_LOCAL_FLV_OPERATION,
> +	SEG6_LOCAL_FLV_LCBLOCK_LEN,
> +	SEG6_LOCAL_FLV_LCNODE_FN_LEN,
> +	__SEG6_LOCAL_FLV_MAX,
> +};
> +
> +#define SEG6_LOCAL_FLV_MAX (__SEG6_LOCAL_FLV_MAX - 1)
> +
> +/* Designed flavor operations for SRv6 End* Behavior */
> +enum {
> +	SEG6_LOCAL_FLV_OP_UNSPEC,
> +	SEG6_LOCAL_FLV_OP_PSP,
> +	SEG6_LOCAL_FLV_OP_USP,
> +	SEG6_LOCAL_FLV_OP_USD,
> +	SEG6_LOCAL_FLV_OP_NEXT_CSID,
> +	__SEG6_LOCAL_FLV_OP_MAX
> +};
> +
> +#define SEG6_LOCAL_FLV_OP_MAX (__SEG6_LOCAL_FLV_OP_MAX - 1)
> +
>  #endif
> diff --git a/ip/iproute_lwtunnel.c b/ip/iproute_lwtunnel.c
> index f4192229..112846cc 100644
> --- a/ip/iproute_lwtunnel.c
> +++ b/ip/iproute_lwtunnel.c
> @@ -157,6 +157,102 @@ static int read_seg6mode_type(const char *mode)
>  	return -1;
>  }
>  
> +static const char *seg6_flavor_names[SEG6_LOCAL_FLV_OP_MAX + 1] = {
> +	[SEG6_LOCAL_FLV_OP_PSP]		= "psp",
> +	[SEG6_LOCAL_FLV_OP_USP]		= "usp",
> +	[SEG6_LOCAL_FLV_OP_USD]		= "usd",
> +	[SEG6_LOCAL_FLV_OP_NEXT_CSID]	= "next-csid"
> +};
> +
> +static int read_seg6_local_flv_type(const char *name)
> +{
> +	int i;
> +
> +	for (i = 1; i < SEG6_LOCAL_FLV_OP_MAX + 1; ++i) {
> +		if (!seg6_flavor_names[i])
> +			continue;
> +
> +		if (strcasecmp(seg6_flavor_names[i], name) == 0)
> +			return i;
> +	}
> +
> +	return -1;
> +}
> +
> +#define SEG6_LOCAL_FLV_BUF_SIZE 32
> +static int parse_seg6local_flavors(const char *buf, __u32 *flv_mask)
> +{
> +	unsigned char flavor_ok[SEG6_LOCAL_FLV_OP_MAX + 1] = { 0, };
> +	char wbuf[SEG6_LOCAL_FLV_BUF_SIZE];
> +	__u32 mask = 0;
> +	int index;
> +	char *s;
> +
> +	/* strtok changes first parameter, so we need to make a local copy */
> +	strlcpy(wbuf, buf, SEG6_LOCAL_FLV_BUF_SIZE);
> +	wbuf[SEG6_LOCAL_FLV_BUF_SIZE - 1] = 0;
> +
> +	if (strlen(wbuf) == 0)
> +		return -1;
> +
> +	for (s = strtok((char *) wbuf, ","); s; s = strtok(NULL, ",")) {
> +		index = read_seg6_local_flv_type(s);
> +		if (index < 0 || index > SEG6_LOCAL_FLV_OP_MAX)
> +			return -1;
> +		/* we check for duplicates */
> +		if (flavor_ok[index]++)
> +			return -1;
> +
> +		mask |= (1 << index);
> +	}
> +
> +	*flv_mask = mask;
> +	return 0;
> +}
> +
> +static void print_flavors(FILE *fp, __u32 flavors)
> +{
> +	int i, fnumber = 0;
> +	char *flv_name;
> +
> +	if (is_json_context())
> +		open_json_array(PRINT_JSON, "flavors");
> +	else
> +		fprintf(fp, "flavors ");
> +
> +	for (i = 0; i < SEG6_LOCAL_FLV_OP_MAX + 1; ++i) {
> +		if (flavors & (1 << i)) {
> +			flv_name = (char *) seg6_flavor_names[i];
> +			if (!flv_name)
> +				continue;
> +
> +			if (is_json_context())
> +				print_string(PRINT_JSON, NULL, NULL, flv_name);
> +			else {
> +				if (fnumber++ == 0)
> +					fprintf(fp, "%s", flv_name);
> +				else
> +					fprintf(fp, ",%s", flv_name);
> +			}
> +		}
> +	}
> +
> +	if (is_json_context())
> +		close_json_array(PRINT_JSON, NULL);
> +	else
> +		fprintf(fp, " ");
> +}
> +
> +static void print_flavors_attr(FILE *fp, const char *key, __u32 value)
> +{
> +	if (is_json_context()) {
> +		print_u64(PRINT_JSON, key, NULL, value);
> +	} else {
> +		print_string(PRINT_FP, NULL, "%s ", key);
> +		print_num(fp, 1, value);
> +	}
> +}
> +
>  static void print_encap_seg6(FILE *fp, struct rtattr *encap)
>  {
>  	struct rtattr *tb[SEG6_IPTUNNEL_MAX+1];
> @@ -374,6 +470,30 @@ static void print_seg6_local_counters(FILE *fp, struct rtattr *encap)
>  	}
>  }
>  
> +static void print_seg6_local_flavors(FILE *fp, struct rtattr *encap)
> +{
> +	struct rtattr *tb[SEG6_LOCAL_FLV_MAX + 1];
> +	__u8 lbl = 0, nfl = 0;
> +	__u32 flavors = 0;
> +
> +	parse_rtattr_nested(tb, SEG6_LOCAL_FLV_MAX, encap);
> +
> +	if (tb[SEG6_LOCAL_FLV_OPERATION]) {
> +		flavors = rta_getattr_u32(tb[SEG6_LOCAL_FLV_OPERATION]);
> +		print_flavors(fp, flavors);
> +	}
> +
> +	if (tb[SEG6_LOCAL_FLV_LCBLOCK_LEN]) {
> +		lbl = rta_getattr_u8(tb[SEG6_LOCAL_FLV_LCBLOCK_LEN]);
> +		print_flavors_attr(fp, "lblen", lbl);
> +	}
> +
> +	if (tb[SEG6_LOCAL_FLV_LCNODE_FN_LEN]) {
> +		nfl = rta_getattr_u8(tb[SEG6_LOCAL_FLV_LCNODE_FN_LEN]);
> +		print_flavors_attr(fp, "nflen", nfl);
> +	}
> +}
> +
>  static void print_encap_seg6local(FILE *fp, struct rtattr *encap)
>  {
>  	struct rtattr *tb[SEG6_LOCAL_MAX + 1];
> @@ -436,6 +556,9 @@ static void print_encap_seg6local(FILE *fp, struct rtattr *encap)
>  
>  	if (tb[SEG6_LOCAL_COUNTERS] && show_stats)
>  		print_seg6_local_counters(fp, tb[SEG6_LOCAL_COUNTERS]);
> +
> +	if (tb[SEG6_LOCAL_FLAVORS])
> +		print_seg6_local_flavors(fp, tb[SEG6_LOCAL_FLAVORS]);
>  }
>  
>  static void print_encap_mpls(FILE *fp, struct rtattr *encap)
> @@ -1175,12 +1298,66 @@ static int seg6local_fill_counters(struct rtattr *rta, size_t len, int attr)
>  	return 0;
>  }
>  
> +static int seg6local_parse_flavors(struct rtattr *rta, size_t len,
> +			 int *argcp, char ***argvp, int attr)
> +{
> +	int lbl_ok = 0, nfl_ok = 0;
> +	__u8 lbl = 0, nfl = 0;
> +	struct rtattr *nest;
> +	__u32 flavors = 0;
> +	int ret;
> +
> +	char **argv = *argvp;
> +	int argc = *argcp;
> +
> +	nest = rta_nest(rta, len, attr);
> +
> +	ret = parse_seg6local_flavors(*argv, &flavors);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = rta_addattr32(rta, len, SEG6_LOCAL_FLV_OPERATION, flavors);
> +	if (ret < 0)
> +		return ret;
> +
> +	if (flavors & (1 << SEG6_LOCAL_FLV_OP_NEXT_CSID)) {
> +		NEXT_ARG_FWD();
> +		if (strcmp(*argv, "lblen") == 0){
> +			NEXT_ARG();
> +			if (lbl_ok++)
> +				duparg2("lblen", *argv);
> +			if (get_u8(&lbl, *argv, 0))
> +				invarg("\"locator-block length\" value is invalid\n", *argv);
> +			ret = rta_addattr8(rta, len, SEG6_LOCAL_FLV_LCBLOCK_LEN, lbl);
> +			NEXT_ARG_FWD();
> +		}
> +
> +		if (strcmp(*argv, "nflen") == 0){
> +			NEXT_ARG();
> +			if (nfl_ok++)
> +				duparg2("nflen", *argv);
> +			if (get_u8(&nfl, *argv, 0))
> +				invarg("\"locator-node function length\" value is invalid\n", *argv);
> +			ret = rta_addattr8(rta, len, SEG6_LOCAL_FLV_LCNODE_FN_LEN, nfl);
> +			NEXT_ARG_FWD();
> +		}
> +		PREV_ARG();
> +	}
> +
> +	rta_nest_end(rta, nest);
> +
> +	*argcp = argc;
> +	*argvp = argv;
> +
> +	return 0;
> +}
> +
>  static int parse_encap_seg6local(struct rtattr *rta, size_t len, int *argcp,
>  				 char ***argvp)
>  {
> +	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 nh4_ok = 0, nh6_ok = 0, iif_ok = 0, oif_ok = 0;
>  	__u32 action = 0, table, vrftable, iif, oif;
>  	struct ipv6_sr_hdr *srh;
>  	char **argv = *argvp;
> @@ -1250,6 +1427,15 @@ static int parse_encap_seg6local(struct rtattr *rta, size_t len, int *argcp,
>  				duparg2("count", *argv);
>  			ret = seg6local_fill_counters(rta, len,
>  						      SEG6_LOCAL_COUNTERS);
> +		} else if (strcmp(*argv, "flavors") == 0) {
> +			NEXT_ARG();
> +			if (flavors_ok++)
> +				duparg2("flavors", *argv);
> +
> +			if (seg6local_parse_flavors(rta, len, &argc, &argv,
> +						    SEG6_LOCAL_FLAVORS))
> +				invarg("invalid \"flavors\" attribute\n",
> +					*argv);
>  		} else if (strcmp(*argv, "srh") == 0) {
>  			NEXT_ARG();
>  			if (srh_ok++)
> diff --git a/man/man8/ip-route.8.in b/man/man8/ip-route.8.in
> index 462ff269..3364815c 100644
> --- a/man/man8/ip-route.8.in
> +++ b/man/man8/ip-route.8.in
> @@ -834,10 +834,14 @@ related to an action use the \fB-s\fR flag in the \fBshow\fR command.
>  The following actions are currently supported (\fBLinux 4.14+ only\fR).
>  .in +2
>  
> -.B End
> +.BR End " [ " flavors
> +.IR FLAVORS " ] "
>  - Regular SRv6 processing as intermediate segment endpoint.
>  This action only accepts packets with a non-zero Segments Left
> -value. Other matching packets are dropped.
> +value. Other matching packets are dropped. The presence of flavors
> +can change the regular processing of an End behavior according to
> +the user-provided Flavor operations and information carried in the packet.
> +See \fBFlavors parameters\fR section.
>  
>  .B End.X nh6
>  .I NEXTHOP
> @@ -917,8 +921,61 @@ Additionally, encapsulate the matching packet within an outer IPv6 header
>  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 Flavors parameters
> +
> +The flavors represent additional operations that can modify or extend a
> +subset of the existing behaviors.
> +.in +2
> +
> +.B flavors
> +.IR OPERATION "[," OPERATION "] [" ATTRIBUTES "]"
> +.in +2
> +
> +.IR OPERATION " := { "
> +.BR psp " | "
> +.BR usp " | "
> +.BR usd " | "
> +.BR next-csid " }"
> +
> +.IR ATTRIBUTES " := {"
> +.IR "KEY VALUE" " } ["
> +.IR ATTRIBUTES " ]"
> +
> +.IR KEY " := { "
> +.BR lblen " | "
> +.BR nflen " } "
>  .in -2
>  
> +.B psp
> +- Penultimate Segment Pop of the SRH (not yet supported in kernel)
> +
> +.B usp
> +- Ultimate Segment Pop of the SRH (not yet supported in kernel)
> +
> +.B usd
> +- Ultimate Segment Decapsulation (not yet supported in kernel)
> +
> +.B next-csid
> +- The NEXT-C-SID mechanism offers the possibility of encoding
> +several SRv6 segments within a single 128 bit SID address. The NEXT-C-SID
> +flavor can be configured to support user-provided Locator-Block and
> +Locator-Node Function lengths. If Locator-Block and/or Locator-Node Function
> +lengths are not provided by the user during configuration of an SRv6 End
> +behavior instance with NEXT-C-SID flavor, the default value is 32-bit for
> +Locator-Block and 16-bit for Locator-Node Function.
> +
> +.BI lblen " VALUE "
> +- defines the Locator-Block length for NEXT-C-SID flavor.
> +The Locator Block length must be evenly divisible by 8. This attribute
> +can be used only with NEXT-C-SID flavor.
> +
> +.BI nflen " VALUE "
> +- defines the Locator-Node Function length for NEXT-C-SID
> +flavors. The Locator-Node Function length must be evenly divisible
> +by 8. This attribute can be used only with NEXT-C-SID flavor.
> +.in -4
> +
>  .B ioam6
>  .in +2
>  .B freq K/N
> @@ -1279,6 +1336,16 @@ ip -6 route add 2001:db8:1::/64 encap seg6local action End.DT46 vrftable 100 dev
>  Adds an IPv6 route with SRv6 decapsulation and forward with lookup in VRF table.
>  .RE
>  .PP
> +ip -6 route add 2001:db8:1::/64 encap seg6local action End flavors next-csid dev eth0
> +.RS 4
> +Adds an IPv6 route with SRv6 End behavior with next-csid flavor enabled.
> +.RE
> +.PP
> +ip -6 route add 2001:db8:1::/64 encap seg6local action End flavors next-csid lblen 48 nflen 16 dev eth0
> +.RS 4
> +Adds an IPv6 route with SRv6 End behavior with next-csid flavor enabled and user-provided Locator-Block and Locator-Node Function lengths.
> +.RE
> +.PP
>  ip -6 route add 2001:db8:1::/64 encap ioam6 freq 2/5 mode encap tundst 2001:db8:42::1 trace prealloc type 0x800000 ns 1 size 12 dev eth0
>  .RS 4
>  Adds an IPv6 route with an IOAM Pre-allocated Trace encapsulation (ip6ip6) that only includes the hop limit and the node id, configured for the IOAM namespace 1 and a pre-allocated data block of 12 octets (will be injected in 2 packets every 5 packets).
> -- 
> 2.20.1
> 

Anton