diff mbox series

[iproute2-next,v2,2/3] New IOAM6 encap type for routes

Message ID 20210724172108.26524-3-justin.iurman@uliege.be
State Superseded
Headers show
Series Provide support for IOAM | expand

Commit Message

Justin Iurman July 24, 2021, 5:21 p.m. UTC
This patch provides a new encap type for routes to insert an IOAM pre-allocated
trace:

$ ip -6 ro ad fc00::1/128 encap ioam6 trace type 0x800000 ns 1 size 12 dev eth0

where:
 - "trace" may appear as useless but just anticipates for future implementations
   of other ioam option types.
 - "type" is a bitfield (=u32) defining the IOAM pre-allocated trace type (see
   the corresponding uapi).
 - "ns" is an IOAM namespace ID attached to the pre-allocated trace.
 - "size" is the trace pre-allocated size in bytes; must be a 4-octet multiple;
   limited size (see IOAM6_TRACE_DATA_SIZE_MAX).

Signed-off-by: Justin Iurman <justin.iurman@uliege.be>
---
 include/uapi/linux/ioam6.h          | 133 ++++++++++++++++++++++++++++
 include/uapi/linux/ioam6_iptunnel.h |  20 +++++
 include/uapi/linux/lwtunnel.h       |   1 +
 ip/iproute.c                        |   5 +-
 ip/iproute_lwtunnel.c               | 121 +++++++++++++++++++++++++
 5 files changed, 278 insertions(+), 2 deletions(-)
 create mode 100644 include/uapi/linux/ioam6.h
 create mode 100644 include/uapi/linux/ioam6_iptunnel.h

Comments

Justin Iurman July 27, 2021, 1:42 p.m. UTC | #1
> This patch provides a new encap type for routes to insert an IOAM pre-allocated

> trace:

> 

> $ ip -6 ro ad fc00::1/128 encap ioam6 trace type 0x800000 ns 1 size 12 dev eth0

> 

> where:

> - "trace" may appear as useless but just anticipates for future implementations

>   of other ioam option types.

> - "type" is a bitfield (=u32) defining the IOAM pre-allocated trace type (see

>   the corresponding uapi).

> - "ns" is an IOAM namespace ID attached to the pre-allocated trace.

> - "size" is the trace pre-allocated size in bytes; must be a 4-octet multiple;

>   limited size (see IOAM6_TRACE_DATA_SIZE_MAX).

> 

> Signed-off-by: Justin Iurman <justin.iurman@uliege.be>

> ---

> include/uapi/linux/ioam6.h          | 133 ++++++++++++++++++++++++++++

> include/uapi/linux/ioam6_iptunnel.h |  20 +++++

> include/uapi/linux/lwtunnel.h       |   1 +

> ip/iproute.c                        |   5 +-

> ip/iproute_lwtunnel.c               | 121 +++++++++++++++++++++++++

> 5 files changed, 278 insertions(+), 2 deletions(-)

> create mode 100644 include/uapi/linux/ioam6.h

> create mode 100644 include/uapi/linux/ioam6_iptunnel.h

> 

> diff --git a/include/uapi/linux/ioam6.h b/include/uapi/linux/ioam6.h

> new file mode 100644

> index 00000000..0a2cc17d

> --- /dev/null

> +++ b/include/uapi/linux/ioam6.h

> @@ -0,0 +1,133 @@

> +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */

> +/*

> + *  IPv6 IOAM implementation

> + *

> + *  Author:

> + *  Justin Iurman <justin.iurman@uliege.be>

> + */

> +

> +#ifndef _UAPI_LINUX_IOAM6_H

> +#define _UAPI_LINUX_IOAM6_H

> +

> +#include <asm/byteorder.h>

> +#include <linux/types.h>

> +

> +#define IOAM6_U16_UNAVAILABLE U16_MAX

> +#define IOAM6_U32_UNAVAILABLE U32_MAX

> +#define IOAM6_U64_UNAVAILABLE U64_MAX

> +

> +#define IOAM6_DEFAULT_ID (IOAM6_U32_UNAVAILABLE >> 8)

> +#define IOAM6_DEFAULT_ID_WIDE (IOAM6_U64_UNAVAILABLE >> 8)

> +#define IOAM6_DEFAULT_IF_ID IOAM6_U16_UNAVAILABLE

> +#define IOAM6_DEFAULT_IF_ID_WIDE IOAM6_U32_UNAVAILABLE

> +

> +/*

> + * IPv6 IOAM Option Header

> + */

> +struct ioam6_hdr {

> +	__u8 opt_type;

> +	__u8 opt_len;

> +	__u8 :8;				/* reserved */

> +#define IOAM6_TYPE_PREALLOC 0

> +	__u8 type;

> +} __attribute__((__packed__));

> +

> +/*

> + * IOAM Trace Header

> + */

> +struct ioam6_trace_hdr {

> +	__be16	namespace_id;

> +

> +#if defined(__LITTLE_ENDIAN_BITFIELD)

> +

> +	__u8	:1,				/* unused */

> +		:1,				/* unused */

> +		overflow:1,

> +		nodelen:5;

> +

> +	__u8	remlen:7,

> +		:1;				/* unused */

> +

> +	union {

> +		__be32 type_be32;

> +

> +		struct {

> +			__u32	bit7:1,

> +				bit6:1,

> +				bit5:1,

> +				bit4:1,

> +				bit3:1,

> +				bit2:1,

> +				bit1:1,

> +				bit0:1,

> +				bit15:1,	/* unused */

> +				bit14:1,	/* unused */

> +				bit13:1,	/* unused */

> +				bit12:1,	/* unused */

> +				bit11:1,

> +				bit10:1,

> +				bit9:1,

> +				bit8:1,

> +				bit23:1,	/* reserved */

> +				bit22:1,

> +				bit21:1,	/* unused */

> +				bit20:1,	/* unused */

> +				bit19:1,	/* unused */

> +				bit18:1,	/* unused */

> +				bit17:1,	/* unused */

> +				bit16:1,	/* unused */

> +				:8;		/* reserved */

> +		} type;

> +	};

> +

> +#elif defined(__BIG_ENDIAN_BITFIELD)

> +

> +	__u8	nodelen:5,

> +		overflow:1,

> +		:1,				/* unused */

> +		:1;				/* unused */

> +

> +	__u8	:1,				/* unused */

> +		remlen:7;

> +

> +	union {

> +		__be32 type_be32;

> +

> +		struct {

> +			__u32	bit0:1,

> +				bit1:1,

> +				bit2:1,

> +				bit3:1,

> +				bit4:1,

> +				bit5:1,

> +				bit6:1,

> +				bit7:1,

> +				bit8:1,

> +				bit9:1,

> +				bit10:1,

> +				bit11:1,

> +				bit12:1,	/* unused */

> +				bit13:1,	/* unused */

> +				bit14:1,	/* unused */

> +				bit15:1,	/* unused */

> +				bit16:1,	/* unused */

> +				bit17:1,	/* unused */

> +				bit18:1,	/* unused */

> +				bit19:1,	/* unused */

> +				bit20:1,	/* unused */

> +				bit21:1,	/* unused */

> +				bit22:1,

> +				bit23:1,	/* reserved */

> +				:8;		/* reserved */

> +		} type;

> +	};

> +

> +#else

> +#error "Please fix <asm/byteorder.h>"

> +#endif

> +

> +#define IOAM6_TRACE_DATA_SIZE_MAX 244

> +	__u8	data[0];

> +} __attribute__((__packed__));

> +

> +#endif /* _UAPI_LINUX_IOAM6_H */

> diff --git a/include/uapi/linux/ioam6_iptunnel.h

> b/include/uapi/linux/ioam6_iptunnel.h

> new file mode 100644

> index 00000000..bae14636

> --- /dev/null

> +++ b/include/uapi/linux/ioam6_iptunnel.h

> @@ -0,0 +1,20 @@

> +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */

> +/*

> + *  IPv6 IOAM Lightweight Tunnel API

> + *

> + *  Author:

> + *  Justin Iurman <justin.iurman@uliege.be>

> + */

> +

> +#ifndef _UAPI_LINUX_IOAM6_IPTUNNEL_H

> +#define _UAPI_LINUX_IOAM6_IPTUNNEL_H

> +

> +enum {

> +	IOAM6_IPTUNNEL_UNSPEC,

> +	IOAM6_IPTUNNEL_TRACE,		/* struct ioam6_trace_hdr */

> +	__IOAM6_IPTUNNEL_MAX,

> +};

> +

> +#define IOAM6_IPTUNNEL_MAX (__IOAM6_IPTUNNEL_MAX - 1)

> +

> +#endif /* _UAPI_LINUX_IOAM6_IPTUNNEL_H */

> diff --git a/include/uapi/linux/lwtunnel.h b/include/uapi/linux/lwtunnel.h

> index b7c0191f..78f0ecd1 100644

> --- a/include/uapi/linux/lwtunnel.h

> +++ b/include/uapi/linux/lwtunnel.h

> @@ -14,6 +14,7 @@ enum lwtunnel_encap_types {

> 	LWTUNNEL_ENCAP_BPF,

> 	LWTUNNEL_ENCAP_SEG6_LOCAL,

> 	LWTUNNEL_ENCAP_RPL,

> +	LWTUNNEL_ENCAP_IOAM6,

> 	__LWTUNNEL_ENCAP_MAX,

> };

> 

> diff --git a/ip/iproute.c b/ip/iproute.c

> index bdeb9644..330203b0 100644

> --- a/ip/iproute.c

> +++ b/ip/iproute.c

> @@ -101,8 +101,8 @@ static void usage(void)

> 		"TIME := NUMBER[s|ms]\n"

> 		"BOOL := [1|0]\n"

> 		"FEATURES := ecn\n"

> -		"ENCAPTYPE := [ mpls | ip | ip6 | seg6 | seg6local | rpl ]\n"

> -		"ENCAPHDR := [ MPLSLABEL | SEG6HDR | SEG6LOCAL ]\n"

> +		"ENCAPTYPE := [ mpls | ip | ip6 | seg6 | seg6local | rpl | ioam6 ]\n"

> +		"ENCAPHDR := [ MPLSLABEL | SEG6HDR | SEG6LOCAL | IOAM6HDR ]\n"

> 		"SEG6HDR := [ mode SEGMODE ] segs ADDR1,ADDRi,ADDRn [hmac HMACKEYID]

> 		[cleanup]\n"

> 		"SEGMODE := [ encap | inline ]\n"

> 		"SEG6LOCAL := action ACTION [ OPTIONS ] [ count ]\n"

> @@ -112,6 +112,7 @@ static void usage(void)

> 		"OPTIONS := OPTION [ OPTIONS ]\n"

> 		"OPTION := { srh SEG6HDR | nh4 ADDR | nh6 ADDR | iif DEV | oif DEV |\n"

> 		"            table TABLEID | vrftable TABLEID | endpoint PROGNAME }\n"

> +		"IOAM6HDR := trace type IOAM6_TRACE_TYPE ns IOAM6_NAMESPACE size

> IOAM6_TRACE_SIZE\n"

> 		"ROUTE_GET_FLAGS := [ fibmatch ]\n");

> 	exit(-1);

> }

> diff --git a/ip/iproute_lwtunnel.c b/ip/iproute_lwtunnel.c

> index c4bae68d..f1e001c4 100644

> --- a/ip/iproute_lwtunnel.c

> +++ b/ip/iproute_lwtunnel.c

> @@ -34,6 +34,8 @@

> #include <linux/seg6_hmac.h>

> #include <linux/seg6_local.h>

> #include <linux/if_tunnel.h>

> +#include <linux/ioam6.h>

> +#include <linux/ioam6_iptunnel.h>

> 

> static const char *format_encap_type(int type)

> {

> @@ -54,6 +56,8 @@ static const char *format_encap_type(int type)

> 		return "seg6local";

> 	case LWTUNNEL_ENCAP_RPL:

> 		return "rpl";

> +	case LWTUNNEL_ENCAP_IOAM6:

> +		return "ioam6";

> 	default:

> 		return "unknown";

> 	}

> @@ -90,6 +94,8 @@ static int read_encap_type(const char *name)

> 		return LWTUNNEL_ENCAP_SEG6_LOCAL;

> 	else if (strcmp(name, "rpl") == 0)

> 		return LWTUNNEL_ENCAP_RPL;

> +	else if (strcmp(name, "ioam6") == 0)

> +		return LWTUNNEL_ENCAP_IOAM6;

> 	else if (strcmp(name, "help") == 0)

> 		encap_type_usage();

> 

> @@ -204,6 +210,23 @@ static void print_encap_rpl(FILE *fp, struct rtattr *encap)

> 	print_rpl_srh(fp, srh);

> }

> 

> +static void print_encap_ioam6(FILE *fp, struct rtattr *encap)

> +{

> +	struct rtattr *tb[IOAM6_IPTUNNEL_MAX + 1];

> +	struct ioam6_trace_hdr *trace;

> +

> +	parse_rtattr_nested(tb, IOAM6_IPTUNNEL_MAX, encap);

> +

> +	if (!tb[IOAM6_IPTUNNEL_TRACE])

> +		return;

> +

> +	trace = RTA_DATA(tb[IOAM6_IPTUNNEL_TRACE]);

> +

> +	print_hex(PRINT_ANY, "type", "type %#06x ", ntohl(trace->type_be32) >> 8);


Uh oh... Should be %#08x instead, sorry for that. Should I submit a v3 to reflect it?

> +	print_uint(PRINT_ANY, "ns", "ns %u ", ntohs(trace->namespace_id));

> +	print_uint(PRINT_ANY, "size", "size %u ", trace->remlen * 4);

> +}

> +

> static const char *seg6_action_names[SEG6_LOCAL_ACTION_MAX + 1] = {

> 	[SEG6_LOCAL_ACTION_END]			= "End",

> 	[SEG6_LOCAL_ACTION_END_X]		= "End.X",

> @@ -657,6 +680,9 @@ void lwt_print_encap(FILE *fp, struct rtattr *encap_type,

> 	case LWTUNNEL_ENCAP_RPL:

> 		print_encap_rpl(fp, encap);

> 		break;

> +	case LWTUNNEL_ENCAP_IOAM6:

> +		print_encap_ioam6(fp, encap);

> +		break;

> 	}

> }

> 

> @@ -853,6 +879,98 @@ out:

> 	return ret;

> }

> 

> +static int parse_encap_ioam6(struct rtattr *rta, size_t len, int *argcp,

> +			     char ***argvp)

> +{

> +	struct ioam6_trace_hdr *trace;

> +	char **argv = *argvp;

> +	int argc = *argcp;

> +	int ns_found = 0;

> +	__u16 size = 0;

> +	__u32 type = 0;

> +	__u16 ns;

> +

> +	trace = calloc(1, sizeof(*trace));

> +	if (!trace)

> +		return -1;

> +

> +	if (strcmp(*argv, "trace"))

> +		missarg("trace");

> +

> +	while (NEXT_ARG_OK()) {

> +		NEXT_ARG_FWD();

> +

> +		if (strcmp(*argv, "type") == 0) {

> +			NEXT_ARG();

> +

> +			if (type)

> +				duparg2("type", *argv);

> +

> +			if (get_u32(&type, *argv, 0) || !type)

> +				invarg("Invalid type", *argv);

> +

> +			trace->type_be32 = htonl(type << 8);

> +

> +		} else if (strcmp(*argv, "ns") == 0) {

> +			NEXT_ARG();

> +

> +			if (ns_found++)

> +				duparg2("ns", *argv);

> +

> +			if (!type)

> +				missarg("type");

> +

> +			if (get_u16(&ns, *argv, 0))

> +				invarg("Invalid namespace ID", *argv);

> +

> +			trace->namespace_id = htons(ns);

> +

> +		} else if (strcmp(*argv, "size") == 0) {

> +			NEXT_ARG();

> +

> +			if (size)

> +				duparg2("size", *argv);

> +

> +			if (!type)

> +				missarg("type");

> +			if (!ns_found)

> +				missarg("ns");

> +

> +			if (get_u16(&size, *argv, 0) || !size)

> +				invarg("Invalid size", *argv);

> +

> +			if (size % 4)

> +				invarg("Size must be a 4-octet multiple", *argv);

> +			if (size > IOAM6_TRACE_DATA_SIZE_MAX)

> +				invarg("Size too big", *argv);

> +

> +			trace->remlen = (__u8)(size / 4);

> +

> +		} else {

> +			break;

> +		}

> +	}

> +

> +	if (!type)

> +		missarg("type");

> +	if (!ns_found)

> +		missarg("ns");

> +	if (!size)

> +		missarg("size");

> +

> +	if (rta_addattr_l(rta, len, IOAM6_IPTUNNEL_TRACE, trace,

> +			  sizeof(*trace))) {

> +		free(trace);

> +		return -1;

> +	}

> +

> +	*argcp = argc + 1;

> +	*argvp = argv - 1;

> +

> +	free(trace);

> +	return 0;

> +}

> +

> struct lwt_x {

> 	struct rtattr *rta;

> 	size_t len;

> @@ -1744,6 +1862,9 @@ int lwt_parse_encap(struct rtattr *rta, size_t len, int

> *argcp, char ***argvp,

> 	case LWTUNNEL_ENCAP_RPL:

> 		ret = parse_encap_rpl(rta, len, &argc, &argv);

> 		break;

> +	case LWTUNNEL_ENCAP_IOAM6:

> +		ret = parse_encap_ioam6(rta, len, &argc, &argv);

> +		break;

> 	default:

> 		fprintf(stderr, "Error: unsupported encap type\n");

> 		break;

> --

> 2.25.1
David Ahern July 29, 2021, 2:44 a.m. UTC | #2
On 7/27/21 7:42 AM, Justin Iurman wrote:
>> +	trace = RTA_DATA(tb[IOAM6_IPTUNNEL_TRACE]);

>> +

>> +	print_hex(PRINT_ANY, "type", "type %#06x ", ntohl(trace->type_be32) >> 8);

> Uh oh... Should be %#08x instead, sorry for that. Should I submit a v3 to reflect it?

> 

>> +	print_uint(PRINT_ANY, "ns", "ns %u ", ntohs(trace->namespace_id));

>> +	print_uint(PRINT_ANY, "size", "size %u ", trace->remlen * 4);

>> +}

>> +


roll it into v3.

(also, please chop unrelated content in the reply; it is easy to miss
that one line comment scrolling the long message)
diff mbox series

Patch

diff --git a/include/uapi/linux/ioam6.h b/include/uapi/linux/ioam6.h
new file mode 100644
index 00000000..0a2cc17d
--- /dev/null
+++ b/include/uapi/linux/ioam6.h
@@ -0,0 +1,133 @@ 
+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
+/*
+ *  IPv6 IOAM implementation
+ *
+ *  Author:
+ *  Justin Iurman <justin.iurman@uliege.be>
+ */
+
+#ifndef _UAPI_LINUX_IOAM6_H
+#define _UAPI_LINUX_IOAM6_H
+
+#include <asm/byteorder.h>
+#include <linux/types.h>
+
+#define IOAM6_U16_UNAVAILABLE U16_MAX
+#define IOAM6_U32_UNAVAILABLE U32_MAX
+#define IOAM6_U64_UNAVAILABLE U64_MAX
+
+#define IOAM6_DEFAULT_ID (IOAM6_U32_UNAVAILABLE >> 8)
+#define IOAM6_DEFAULT_ID_WIDE (IOAM6_U64_UNAVAILABLE >> 8)
+#define IOAM6_DEFAULT_IF_ID IOAM6_U16_UNAVAILABLE
+#define IOAM6_DEFAULT_IF_ID_WIDE IOAM6_U32_UNAVAILABLE
+
+/*
+ * IPv6 IOAM Option Header
+ */
+struct ioam6_hdr {
+	__u8 opt_type;
+	__u8 opt_len;
+	__u8 :8;				/* reserved */
+#define IOAM6_TYPE_PREALLOC 0
+	__u8 type;
+} __attribute__((__packed__));
+
+/*
+ * IOAM Trace Header
+ */
+struct ioam6_trace_hdr {
+	__be16	namespace_id;
+
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+
+	__u8	:1,				/* unused */
+		:1,				/* unused */
+		overflow:1,
+		nodelen:5;
+
+	__u8	remlen:7,
+		:1;				/* unused */
+
+	union {
+		__be32 type_be32;
+
+		struct {
+			__u32	bit7:1,
+				bit6:1,
+				bit5:1,
+				bit4:1,
+				bit3:1,
+				bit2:1,
+				bit1:1,
+				bit0:1,
+				bit15:1,	/* unused */
+				bit14:1,	/* unused */
+				bit13:1,	/* unused */
+				bit12:1,	/* unused */
+				bit11:1,
+				bit10:1,
+				bit9:1,
+				bit8:1,
+				bit23:1,	/* reserved */
+				bit22:1,
+				bit21:1,	/* unused */
+				bit20:1,	/* unused */
+				bit19:1,	/* unused */
+				bit18:1,	/* unused */
+				bit17:1,	/* unused */
+				bit16:1,	/* unused */
+				:8;		/* reserved */
+		} type;
+	};
+
+#elif defined(__BIG_ENDIAN_BITFIELD)
+
+	__u8	nodelen:5,
+		overflow:1,
+		:1,				/* unused */
+		:1;				/* unused */
+
+	__u8	:1,				/* unused */
+		remlen:7;
+
+	union {
+		__be32 type_be32;
+
+		struct {
+			__u32	bit0:1,
+				bit1:1,
+				bit2:1,
+				bit3:1,
+				bit4:1,
+				bit5:1,
+				bit6:1,
+				bit7:1,
+				bit8:1,
+				bit9:1,
+				bit10:1,
+				bit11:1,
+				bit12:1,	/* unused */
+				bit13:1,	/* unused */
+				bit14:1,	/* unused */
+				bit15:1,	/* unused */
+				bit16:1,	/* unused */
+				bit17:1,	/* unused */
+				bit18:1,	/* unused */
+				bit19:1,	/* unused */
+				bit20:1,	/* unused */
+				bit21:1,	/* unused */
+				bit22:1,
+				bit23:1,	/* reserved */
+				:8;		/* reserved */
+		} type;
+	};
+
+#else
+#error "Please fix <asm/byteorder.h>"
+#endif
+
+#define IOAM6_TRACE_DATA_SIZE_MAX 244
+	__u8	data[0];
+} __attribute__((__packed__));
+
+#endif /* _UAPI_LINUX_IOAM6_H */
diff --git a/include/uapi/linux/ioam6_iptunnel.h b/include/uapi/linux/ioam6_iptunnel.h
new file mode 100644
index 00000000..bae14636
--- /dev/null
+++ b/include/uapi/linux/ioam6_iptunnel.h
@@ -0,0 +1,20 @@ 
+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
+/*
+ *  IPv6 IOAM Lightweight Tunnel API
+ *
+ *  Author:
+ *  Justin Iurman <justin.iurman@uliege.be>
+ */
+
+#ifndef _UAPI_LINUX_IOAM6_IPTUNNEL_H
+#define _UAPI_LINUX_IOAM6_IPTUNNEL_H
+
+enum {
+	IOAM6_IPTUNNEL_UNSPEC,
+	IOAM6_IPTUNNEL_TRACE,		/* struct ioam6_trace_hdr */
+	__IOAM6_IPTUNNEL_MAX,
+};
+
+#define IOAM6_IPTUNNEL_MAX (__IOAM6_IPTUNNEL_MAX - 1)
+
+#endif /* _UAPI_LINUX_IOAM6_IPTUNNEL_H */
diff --git a/include/uapi/linux/lwtunnel.h b/include/uapi/linux/lwtunnel.h
index b7c0191f..78f0ecd1 100644
--- a/include/uapi/linux/lwtunnel.h
+++ b/include/uapi/linux/lwtunnel.h
@@ -14,6 +14,7 @@  enum lwtunnel_encap_types {
 	LWTUNNEL_ENCAP_BPF,
 	LWTUNNEL_ENCAP_SEG6_LOCAL,
 	LWTUNNEL_ENCAP_RPL,
+	LWTUNNEL_ENCAP_IOAM6,
 	__LWTUNNEL_ENCAP_MAX,
 };
 
diff --git a/ip/iproute.c b/ip/iproute.c
index bdeb9644..330203b0 100644
--- a/ip/iproute.c
+++ b/ip/iproute.c
@@ -101,8 +101,8 @@  static void usage(void)
 		"TIME := NUMBER[s|ms]\n"
 		"BOOL := [1|0]\n"
 		"FEATURES := ecn\n"
-		"ENCAPTYPE := [ mpls | ip | ip6 | seg6 | seg6local | rpl ]\n"
-		"ENCAPHDR := [ MPLSLABEL | SEG6HDR | SEG6LOCAL ]\n"
+		"ENCAPTYPE := [ mpls | ip | ip6 | seg6 | seg6local | rpl | ioam6 ]\n"
+		"ENCAPHDR := [ MPLSLABEL | SEG6HDR | SEG6LOCAL | IOAM6HDR ]\n"
 		"SEG6HDR := [ mode SEGMODE ] segs ADDR1,ADDRi,ADDRn [hmac HMACKEYID] [cleanup]\n"
 		"SEGMODE := [ encap | inline ]\n"
 		"SEG6LOCAL := action ACTION [ OPTIONS ] [ count ]\n"
@@ -112,6 +112,7 @@  static void usage(void)
 		"OPTIONS := OPTION [ OPTIONS ]\n"
 		"OPTION := { srh SEG6HDR | nh4 ADDR | nh6 ADDR | iif DEV | oif DEV |\n"
 		"            table TABLEID | vrftable TABLEID | endpoint PROGNAME }\n"
+		"IOAM6HDR := trace type IOAM6_TRACE_TYPE ns IOAM6_NAMESPACE size IOAM6_TRACE_SIZE\n"
 		"ROUTE_GET_FLAGS := [ fibmatch ]\n");
 	exit(-1);
 }
diff --git a/ip/iproute_lwtunnel.c b/ip/iproute_lwtunnel.c
index c4bae68d..f1e001c4 100644
--- a/ip/iproute_lwtunnel.c
+++ b/ip/iproute_lwtunnel.c
@@ -34,6 +34,8 @@ 
 #include <linux/seg6_hmac.h>
 #include <linux/seg6_local.h>
 #include <linux/if_tunnel.h>
+#include <linux/ioam6.h>
+#include <linux/ioam6_iptunnel.h>
 
 static const char *format_encap_type(int type)
 {
@@ -54,6 +56,8 @@  static const char *format_encap_type(int type)
 		return "seg6local";
 	case LWTUNNEL_ENCAP_RPL:
 		return "rpl";
+	case LWTUNNEL_ENCAP_IOAM6:
+		return "ioam6";
 	default:
 		return "unknown";
 	}
@@ -90,6 +94,8 @@  static int read_encap_type(const char *name)
 		return LWTUNNEL_ENCAP_SEG6_LOCAL;
 	else if (strcmp(name, "rpl") == 0)
 		return LWTUNNEL_ENCAP_RPL;
+	else if (strcmp(name, "ioam6") == 0)
+		return LWTUNNEL_ENCAP_IOAM6;
 	else if (strcmp(name, "help") == 0)
 		encap_type_usage();
 
@@ -204,6 +210,23 @@  static void print_encap_rpl(FILE *fp, struct rtattr *encap)
 	print_rpl_srh(fp, srh);
 }
 
+static void print_encap_ioam6(FILE *fp, struct rtattr *encap)
+{
+	struct rtattr *tb[IOAM6_IPTUNNEL_MAX + 1];
+	struct ioam6_trace_hdr *trace;
+
+	parse_rtattr_nested(tb, IOAM6_IPTUNNEL_MAX, encap);
+
+	if (!tb[IOAM6_IPTUNNEL_TRACE])
+		return;
+
+	trace = RTA_DATA(tb[IOAM6_IPTUNNEL_TRACE]);
+
+	print_hex(PRINT_ANY, "type", "type %#06x ", ntohl(trace->type_be32) >> 8);
+	print_uint(PRINT_ANY, "ns", "ns %u ", ntohs(trace->namespace_id));
+	print_uint(PRINT_ANY, "size", "size %u ", trace->remlen * 4);
+}
+
 static const char *seg6_action_names[SEG6_LOCAL_ACTION_MAX + 1] = {
 	[SEG6_LOCAL_ACTION_END]			= "End",
 	[SEG6_LOCAL_ACTION_END_X]		= "End.X",
@@ -657,6 +680,9 @@  void lwt_print_encap(FILE *fp, struct rtattr *encap_type,
 	case LWTUNNEL_ENCAP_RPL:
 		print_encap_rpl(fp, encap);
 		break;
+	case LWTUNNEL_ENCAP_IOAM6:
+		print_encap_ioam6(fp, encap);
+		break;
 	}
 }
 
@@ -853,6 +879,98 @@  out:
 	return ret;
 }
 
+static int parse_encap_ioam6(struct rtattr *rta, size_t len, int *argcp,
+			     char ***argvp)
+{
+	struct ioam6_trace_hdr *trace;
+	char **argv = *argvp;
+	int argc = *argcp;
+	int ns_found = 0;
+	__u16 size = 0;
+	__u32 type = 0;
+	__u16 ns;
+
+	trace = calloc(1, sizeof(*trace));
+	if (!trace)
+		return -1;
+
+	if (strcmp(*argv, "trace"))
+		missarg("trace");
+
+	while (NEXT_ARG_OK()) {
+		NEXT_ARG_FWD();
+
+		if (strcmp(*argv, "type") == 0) {
+			NEXT_ARG();
+
+			if (type)
+				duparg2("type", *argv);
+
+			if (get_u32(&type, *argv, 0) || !type)
+				invarg("Invalid type", *argv);
+
+			trace->type_be32 = htonl(type << 8);
+
+		} else if (strcmp(*argv, "ns") == 0) {
+			NEXT_ARG();
+
+			if (ns_found++)
+				duparg2("ns", *argv);
+
+			if (!type)
+				missarg("type");
+
+			if (get_u16(&ns, *argv, 0))
+				invarg("Invalid namespace ID", *argv);
+
+			trace->namespace_id = htons(ns);
+
+		} else if (strcmp(*argv, "size") == 0) {
+			NEXT_ARG();
+
+			if (size)
+				duparg2("size", *argv);
+
+			if (!type)
+				missarg("type");
+			if (!ns_found)
+				missarg("ns");
+
+			if (get_u16(&size, *argv, 0) || !size)
+				invarg("Invalid size", *argv);
+
+			if (size % 4)
+				invarg("Size must be a 4-octet multiple", *argv);
+			if (size > IOAM6_TRACE_DATA_SIZE_MAX)
+				invarg("Size too big", *argv);
+
+			trace->remlen = (__u8)(size / 4);
+
+		} else {
+			break;
+		}
+	}
+
+	if (!type)
+		missarg("type");
+	if (!ns_found)
+		missarg("ns");
+	if (!size)
+		missarg("size");
+
+	if (rta_addattr_l(rta, len, IOAM6_IPTUNNEL_TRACE, trace,
+			  sizeof(*trace))) {
+		free(trace);
+		return -1;
+	}
+
+	*argcp = argc + 1;
+	*argvp = argv - 1;
+
+	free(trace);
+	return 0;
+}
+
 struct lwt_x {
 	struct rtattr *rta;
 	size_t len;
@@ -1744,6 +1862,9 @@  int lwt_parse_encap(struct rtattr *rta, size_t len, int *argcp, char ***argvp,
 	case LWTUNNEL_ENCAP_RPL:
 		ret = parse_encap_rpl(rta, len, &argc, &argv);
 		break;
+	case LWTUNNEL_ENCAP_IOAM6:
+		ret = parse_encap_ioam6(rta, len, &argc, &argv);
+		break;
 	default:
 		fprintf(stderr, "Error: unsupported encap type\n");
 		break;