diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
index 79c9eb0034d5..5b169e915679 100644
--- a/tools/testing/selftests/net/Makefile
+++ b/tools/testing/selftests/net/Makefile
@@ -25,6 +25,7 @@ TEST_PROGS += bareudp.sh
 TEST_PROGS += unicast_extensions.sh
 TEST_PROGS += udpgro_fwd.sh
 TEST_PROGS += veth.sh
+TEST_PROGS += ioam6.sh
 TEST_PROGS_EXTENDED := in_netns.sh
 TEST_GEN_FILES =  socket nettest
 TEST_GEN_FILES += psock_fanout psock_tpacket msg_zerocopy reuseport_addr_any
@@ -36,6 +37,7 @@ TEST_GEN_FILES += fin_ack_lat
 TEST_GEN_FILES += reuseaddr_ports_exhausted
 TEST_GEN_FILES += hwtstamp_config rxtimestamp timestamping txtimestamp
 TEST_GEN_FILES += ipsec
+TEST_GEN_FILES += ioam6_parser
 TEST_GEN_PROGS = reuseport_bpf reuseport_bpf_cpu reuseport_bpf_numa
 TEST_GEN_PROGS += reuseport_dualstack reuseaddr_conflict tls
 
diff --git a/tools/testing/selftests/net/config b/tools/testing/selftests/net/config
index 6f905b53904f..21b646d10b88 100644
--- a/tools/testing/selftests/net/config
+++ b/tools/testing/selftests/net/config
@@ -42,3 +42,4 @@ CONFIG_NET_CLS_FLOWER=m
 CONFIG_NET_ACT_TUNNEL_KEY=m
 CONFIG_NET_ACT_MIRRED=m
 CONFIG_BAREUDP=m
+CONFIG_IPV6_IOAM6_LWTUNNEL=y
diff --git a/tools/testing/selftests/net/ioam6.sh b/tools/testing/selftests/net/ioam6.sh
new file mode 100644
index 000000000000..928b3510898c
--- /dev/null
+++ b/tools/testing/selftests/net/ioam6.sh
@@ -0,0 +1,298 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0+
+#
+# Author: Justin Iurman <justin.iurman@uliege.be>
+#
+# This test evaluates the IOAM insertion for IPv6 by checking the IOAM data
+# integrity on the receiver.
+#
+# The topology is formed by 3 nodes: Alpha (sender), Beta (router in-between)
+# and Gamma (receiver). An IOAM domain is configured from Alpha to Gamma only,
+# which means not on the reverse path. When Gamma is the destination, Alpha
+# adds an IOAM option (Pre-allocated Trace) inside a Hop-by-hop and fills the
+# trace with its own IOAM data. Beta and Gamma also fill the trace. The IOAM
+# data integrity is checked on Gamma, by comparing with the pre-defined IOAM
+# configuration (see below).
+#
+#     +-------------------+            +-------------------+
+#     |                   |            |                   |
+#     |    alpha netns    |            |    gamma netns    |
+#     |                   |            |                   |
+#     |  +-------------+  |            |  +-------------+  |
+#     |  |    veth0    |  |            |  |    veth0    |  |
+#     |  |  db01::2/64 |  |            |  |  db02::2/64 |  |
+#     |  +-------------+  |            |  +-------------+  |
+#     |         .         |            |         .         |
+#     +-------------------+            +-------------------+
+#               .                                .
+#               .                                .
+#               .                                .
+#     +----------------------------------------------------+
+#     |         .                                .         |
+#     |  +-------------+                  +-------------+  |
+#     |  |    veth0    |                  |    veth1    |  |
+#     |  |  db01::1/64 | ................ |  db02::1/64 |  |
+#     |  +-------------+                  +-------------+  |
+#     |                                                    |
+#     |                      beta netns                    |
+#     |                                                    |
+#     +--------------------------+-------------------------+
+#
+#
+# ~~~~~~~~~~~~~~~~~~~~~~
+# | IOAM configuration |
+# ~~~~~~~~~~~~~~~~~~~~~~
+#
+# Alpha
+# +-----------------------------------------------------------+
+# | Type                | Value                               |
+# +-----------------------------------------------------------+
+# | Node ID             | 1                                   |
+# +-----------------------------------------------------------+
+# | Node Wide ID        | 11111111                            |
+# +-----------------------------------------------------------+
+# | Ingress ID          | 0xffff (default value)              |
+# +-----------------------------------------------------------+
+# | Ingress Wide ID     | 0xffffffff (default value)          |
+# +-----------------------------------------------------------+
+# | Egress ID           | 101                                 |
+# +-----------------------------------------------------------+
+# | Egress Wide ID      | 101101                              |
+# +-----------------------------------------------------------+
+# | Namespace Data      | 0xdeadbee0                          |
+# +-----------------------------------------------------------+
+# | Namespace Wide Data | 0xcafec0caf00dc0de                  |
+# +-----------------------------------------------------------+
+# | Schema ID           | 777                                 |
+# +-----------------------------------------------------------+
+# | Schema Data         | something that will be 4n-aligned   |
+# +-----------------------------------------------------------+
+#
+# Note: When Gamma is the destination, Alpha adds an IOAM Pre-allocated Trace
+#       option inside a Hop-by-hop, where 164 bytes are pre-allocated for the
+#       trace, with 123 as the IOAM-Namespace and with 0xfff00200 as the trace
+#       type (= all available options at this time). As a result, and based on
+#       IOAM configurations here, only both Alpha and Beta should be capable of
+#       inserting their IOAM data while Gamma won't have enough space and will
+#       set the overflow bit.
+#
+# Beta
+# +-----------------------------------------------------------+
+# | Type                | Value                               |
+# +-----------------------------------------------------------+
+# | Node ID             | 2                                   |
+# +-----------------------------------------------------------+
+# | Node Wide ID        | 22222222                            |
+# +-----------------------------------------------------------+
+# | Ingress ID          | 201                                 |
+# +-----------------------------------------------------------+
+# | Ingress Wide ID     | 201201                              |
+# +-----------------------------------------------------------+
+# | Egress ID           | 202                                 |
+# +-----------------------------------------------------------+
+# | Egress Wide ID      | 202202                              |
+# +-----------------------------------------------------------+
+# | Namespace Data      | 0xdeadbee1                          |
+# +-----------------------------------------------------------+
+# | Namespace Wide Data | 0xcafec0caf11dc0de                  |
+# +-----------------------------------------------------------+
+# | Schema ID           | 0xffffff (= None)                   |
+# +-----------------------------------------------------------+
+# | Schema Data         |                                     |
+# +-----------------------------------------------------------+
+#
+# Gamma
+# +-----------------------------------------------------------+
+# | Type                | Value                               |
+# +-----------------------------------------------------------+
+# | Node ID             | 3                                   |
+# +-----------------------------------------------------------+
+# | Node Wide ID        | 33333333                            |
+# +-----------------------------------------------------------+
+# | Ingress ID          | 301                                 |
+# +-----------------------------------------------------------+
+# | Ingress Wide ID     | 301301                              |
+# +-----------------------------------------------------------+
+# | Egress ID           | 0xffff (default value)              |
+# +-----------------------------------------------------------+
+# | Egress Wide ID      | 0xffffffff (default value)          |
+# +-----------------------------------------------------------+
+# | Namespace Data      | 0xdeadbee2                          |
+# +-----------------------------------------------------------+
+# | Namespace Wide Data | 0xcafec0caf22dc0de                  |
+# +-----------------------------------------------------------+
+# | Schema ID           | 0xffffff (= None)                   |
+# +-----------------------------------------------------------+
+# | Schema Data         |                                     |
+# +-----------------------------------------------------------+
+
+#===============================================================================
+#
+# WARNING:
+# Do NOT modify the following configuration unless you know what you're doing.
+#
+IOAM_NAMESPACE=123
+IOAM_TRACE_TYPE=0xfff00200
+IOAM_PREALLOC_DATA_SIZE=164
+
+ALPHA=(
+	1					# ID
+	11111111				# Wide ID
+	0xffff					# Ingress ID
+	0xffffffff				# Ingress Wide ID
+	101					# Egress ID
+	101101					# Egress Wide ID
+	0xdeadbee0				# Namespace Data
+	0xcafec0caf00dc0de			# Namespace Wide Data
+	777					# Schema ID (0xffffff = None)
+	"something that will be 4n-aligned"	# Schema Data
+)
+
+BETA=(
+	2
+	22222222
+	201
+	201201
+	202
+	202202
+	0xdeadbee1
+	0xcafec0caf11dc0de
+	0xffffff
+	""
+)
+
+GAMMA=(
+	3
+	33333333
+	301
+	301301
+	0xffff
+	0xffffffff
+	0xdeadbee2
+	0xcafec0caf22dc0de
+	0xffffff
+	""
+)
+#===============================================================================
+
+if [ "$(id -u)" -ne 0 ]; then
+  echo "SKIP: Need root privileges"
+  exit 1
+fi
+
+if [ ! -x "$(command -v ip)" ]; then
+  echo "SKIP: Could not run test without ip tool"
+  exit 1
+fi
+
+ip ioam &>/dev/null
+if [ $? = 1 ]; then
+  echo "SKIP: ip tool must include IOAM"
+  exit 1
+fi
+
+if [ ! -e /proc/sys/net/ipv6/ioam6_id ]; then
+  echo "SKIP: ioam6 sysctls do not exist"
+  exit 1
+fi
+
+cleanup()
+{
+  ip link del ioam-veth-alpha 2>/dev/null || true
+  ip link del ioam-veth-gamma 2>/dev/null || true
+
+  ip netns del ioam-node-alpha || true
+  ip netns del ioam-node-beta || true
+  ip netns del ioam-node-gamma || true
+}
+
+setup()
+{
+  ip netns add ioam-node-alpha
+  ip netns add ioam-node-beta
+  ip netns add ioam-node-gamma
+
+  ip link add name ioam-veth-alpha type veth peer name ioam-veth-betaL
+  ip link add name ioam-veth-betaR type veth peer name ioam-veth-gamma
+
+  ip link set ioam-veth-alpha netns ioam-node-alpha
+  ip link set ioam-veth-betaL netns ioam-node-beta
+  ip link set ioam-veth-betaR netns ioam-node-beta
+  ip link set ioam-veth-gamma netns ioam-node-gamma
+
+  ip -netns ioam-node-alpha link set ioam-veth-alpha name veth0
+  ip -netns ioam-node-beta link set ioam-veth-betaL name veth0
+  ip -netns ioam-node-beta link set ioam-veth-betaR name veth1
+  ip -netns ioam-node-gamma link set ioam-veth-gamma name veth0
+
+  ip -netns ioam-node-alpha addr add db01::2/64 dev veth0
+  ip -netns ioam-node-alpha link set veth0 up
+  ip -netns ioam-node-alpha link set lo up
+  ip -netns ioam-node-alpha route add default via db01::1
+
+  ip -netns ioam-node-beta addr add db01::1/64 dev veth0
+  ip -netns ioam-node-beta addr add db02::1/64 dev veth1
+  ip -netns ioam-node-beta link set veth0 up
+  ip -netns ioam-node-beta link set veth1 up
+  ip -netns ioam-node-beta link set lo up
+
+  ip -netns ioam-node-gamma addr add db02::2/64 dev veth0
+  ip -netns ioam-node-gamma link set veth0 up
+  ip -netns ioam-node-gamma link set lo up
+  ip -netns ioam-node-gamma route add default via db02::1
+
+  # - IOAM config -
+  ip netns exec ioam-node-alpha sysctl -wq net.ipv6.ioam6_id=${ALPHA[0]}
+  ip netns exec ioam-node-alpha sysctl -wq net.ipv6.ioam6_id_wide=${ALPHA[1]}
+  ip netns exec ioam-node-alpha sysctl -wq net.ipv6.conf.veth0.ioam6_id=${ALPHA[4]}
+  ip netns exec ioam-node-alpha sysctl -wq net.ipv6.conf.veth0.ioam6_id_wide=${ALPHA[5]}
+  ip -netns ioam-node-alpha ioam namespace add ${IOAM_NAMESPACE} data ${ALPHA[6]} wide ${ALPHA[7]}
+  ip -netns ioam-node-alpha ioam schema add ${ALPHA[8]} "${ALPHA[9]}"
+  ip -netns ioam-node-alpha ioam namespace set ${IOAM_NAMESPACE} schema ${ALPHA[8]}
+  ip -netns ioam-node-alpha route add db02::/64 encap ioam6 trace type ${IOAM_TRACE_TYPE:0:-2} ns ${IOAM_NAMESPACE} size ${IOAM_PREALLOC_DATA_SIZE} via db01::1 dev veth0
+
+  ip netns exec ioam-node-beta sysctl -wq net.ipv6.conf.all.forwarding=1
+  ip netns exec ioam-node-beta sysctl -wq net.ipv6.ioam6_id=${BETA[0]}
+  ip netns exec ioam-node-beta sysctl -wq net.ipv6.ioam6_id_wide=${BETA[1]}
+  ip netns exec ioam-node-beta sysctl -wq net.ipv6.conf.veth0.ioam6_enabled=1
+  ip netns exec ioam-node-beta sysctl -wq net.ipv6.conf.veth0.ioam6_id=${BETA[2]}
+  ip netns exec ioam-node-beta sysctl -wq net.ipv6.conf.veth0.ioam6_id_wide=${BETA[3]}
+  ip netns exec ioam-node-beta sysctl -wq net.ipv6.conf.veth1.ioam6_id=${BETA[4]}
+  ip netns exec ioam-node-beta sysctl -wq net.ipv6.conf.veth1.ioam6_id_wide=${BETA[5]}
+  ip -netns ioam-node-beta ioam namespace add ${IOAM_NAMESPACE} data ${BETA[6]} wide ${BETA[7]}
+
+  ip netns exec ioam-node-gamma sysctl -wq net.ipv6.ioam6_id=${GAMMA[0]}
+  ip netns exec ioam-node-gamma sysctl -wq net.ipv6.ioam6_id_wide=${GAMMA[1]}
+  ip netns exec ioam-node-gamma sysctl -wq net.ipv6.conf.veth0.ioam6_enabled=1
+  ip netns exec ioam-node-gamma sysctl -wq net.ipv6.conf.veth0.ioam6_id=${GAMMA[2]}
+  ip netns exec ioam-node-gamma sysctl -wq net.ipv6.conf.veth0.ioam6_id_wide=${GAMMA[3]}
+  ip -netns ioam-node-gamma ioam namespace add ${IOAM_NAMESPACE} data ${GAMMA[6]} wide ${GAMMA[7]}
+}
+
+run()
+{
+  echo -n "IOAM test... "
+
+  ip netns exec ioam-node-alpha ping6 -c 5 -W 1 db02::2 &>/dev/null
+  if [ $? != 0 ]; then
+    echo "FAILED"
+    cleanup &>/dev/null
+    exit 0
+  fi
+
+  ip netns exec ioam-node-gamma ./ioam6_parser veth0 2 ${IOAM_NAMESPACE} ${IOAM_TRACE_TYPE} 64 ${ALPHA[0]} ${ALPHA[1]} ${ALPHA[2]} ${ALPHA[3]} ${ALPHA[4]} ${ALPHA[5]} ${ALPHA[6]} ${ALPHA[7]} ${ALPHA[8]} "${ALPHA[9]}" 63 ${BETA[0]} ${BETA[1]} ${BETA[2]} ${BETA[3]} ${BETA[4]} ${BETA[5]} ${BETA[6]} ${BETA[7]} ${BETA[8]} &
+
+  local spid=$!
+  sleep 0.1
+
+  ip netns exec ioam-node-alpha ping6 -c 5 -W 1 db02::2 &>/dev/null
+
+  wait $spid
+  [ $? = 0 ] && echo "PASSED" || echo "FAILED"
+}
+
+cleanup &>/dev/null
+setup
+run
+cleanup &>/dev/null
+
diff --git a/tools/testing/selftests/net/ioam6_parser.c b/tools/testing/selftests/net/ioam6_parser.c
new file mode 100644
index 000000000000..b0a89d544bc8
--- /dev/null
+++ b/tools/testing/selftests/net/ioam6_parser.c
@@ -0,0 +1,403 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Author: Justin Iurman (justin.iurman@uliege.be)
+ *
+ * IOAM parser for IPv6, see ioam6.sh for details.
+ */
+#include <asm/byteorder.h>
+#include <linux/const.h>
+#include <linux/if_ether.h>
+#include <linux/ioam6.h>
+#include <linux/ipv6.h>
+#include <sys/socket.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+struct node_args {
+	__u32 id;
+	__u64 wide;
+	__u16 ingr_id;
+	__u16 egr_id;
+	__u32 ingr_wide;
+	__u32 egr_wide;
+	__u32 ns_data;
+	__u64 ns_wide;
+	__u32 sc_id;
+	__u8 hop_limit;
+	__u8 *sc_data; /* NULL when sc_id = 0xffffff (default empty value) */
+};
+
+/* expected args per node, in that order */
+enum {
+	NODE_ARG_HOP_LIMIT,
+	NODE_ARG_ID,
+	NODE_ARG_WIDE,
+	NODE_ARG_INGR_ID,
+	NODE_ARG_INGR_WIDE,
+	NODE_ARG_EGR_ID,
+	NODE_ARG_EGR_WIDE,
+	NODE_ARG_NS_DATA,
+	NODE_ARG_NS_WIDE,
+	NODE_ARG_SC_ID,
+	__NODE_ARG_MAX,
+};
+
+#define NODE_ARGS_SIZE __NODE_ARG_MAX
+
+struct args {
+	__u16 ns_id;
+	__u32 trace_type;
+	__u8 n_node;
+	__u8 *ifname;
+	struct node_args node[0];
+};
+
+/* expected args, in that order */
+enum {
+	ARG_IFNAME,
+	ARG_N_NODE,
+	ARG_NS_ID,
+	ARG_TRACE_TYPE,
+	__ARG_MAX,
+};
+
+#define ARGS_SIZE __ARG_MAX
+
+int check_ioam6_node_data(__u8 **p, struct ioam6_trace_hdr *trace, __u8 hlim,
+			  __u32 id, __u64 wide, __u16 ingr_id, __u32 ingr_wide,
+			  __u16 egr_id, __u32 egr_wide, __u32 ns_data,
+			  __u64 ns_wide, __u32 sc_id, __u8 *sc_data)
+{
+	__u64 raw64;
+	__u32 raw32;
+	__u8 sc_len;
+
+	if (trace->type.bit0) {
+		raw32 = __be32_to_cpu(*((__u32 *)*p));
+		if (hlim != (raw32 >> 24) || id != (raw32 & 0xffffff))
+			return 1;
+		*p += sizeof(__u32);
+	}
+
+	if (trace->type.bit1) {
+		raw32 = __be32_to_cpu(*((__u32 *)*p));
+		if (ingr_id != (raw32 >> 16) || egr_id != (raw32 & 0xffff))
+			return 1;
+		*p += sizeof(__u32);
+	}
+
+	if (trace->type.bit2)
+		*p += sizeof(__u32);
+
+	if (trace->type.bit3)
+		*p += sizeof(__u32);
+
+	if (trace->type.bit4) {
+		if (__be32_to_cpu(*((__u32 *)*p)) != 0xffffffff)
+			return 1;
+		*p += sizeof(__u32);
+	}
+
+	if (trace->type.bit5) {
+		if (__be32_to_cpu(*((__u32 *)*p)) != ns_data)
+			return 1;
+		*p += sizeof(__u32);
+	}
+
+	if (trace->type.bit6) {
+		if (__be32_to_cpu(*((__u32 *)*p)) != 0xffffffff)
+			return 1;
+		*p += sizeof(__u32);
+	}
+
+	if (trace->type.bit7) {
+		if (__be32_to_cpu(*((__u32 *)*p)) != 0xffffffff)
+			return 1;
+		*p += sizeof(__u32);
+	}
+
+	if (trace->type.bit8) {
+		raw64 = __be64_to_cpu(*((__u64 *)*p));
+		if (hlim != (raw64 >> 56) || wide != (raw64 & 0xffffffffffffff))
+			return 1;
+		*p += sizeof(__u64);
+	}
+
+	if (trace->type.bit9) {
+		if (__be32_to_cpu(*((__u32 *)*p)) != ingr_wide)
+			return 1;
+		*p += sizeof(__u32);
+
+		if (__be32_to_cpu(*((__u32 *)*p)) != egr_wide)
+			return 1;
+		*p += sizeof(__u32);
+	}
+
+	if (trace->type.bit10) {
+		if (__be64_to_cpu(*((__u64 *)*p)) != ns_wide)
+			return 1;
+		*p += sizeof(__u64);
+	}
+
+	if (trace->type.bit11) {
+		if (__be32_to_cpu(*((__u32 *)*p)) != 0xffffffff)
+			return 1;
+		*p += sizeof(__u32);
+	}
+
+	if (trace->type.bit22) {
+		raw32 = __be32_to_cpu(*((__u32 *)*p));
+		sc_len = sc_data ? __ALIGN_KERNEL(strlen(sc_data), 4) : 0;
+		if (sc_len != (raw32 >> 24) * 4 || sc_id != (raw32 & 0xffffff))
+			return 1;
+		*p += sizeof(__u32);
+
+		if (sc_data) {
+			if (strncmp(*p, sc_data, strlen(sc_data)))
+				return 1;
+
+			*p += strlen(sc_data);
+			sc_len -= strlen(sc_data);
+
+			while (sc_len--) {
+				if (**p != '\0')
+					return 1;
+				*p += sizeof(__u8);
+			}
+		}
+	}
+
+	return 0;
+}
+
+int check_ioam6_trace(struct ioam6_trace_hdr *trace, struct args *args)
+{
+	__u8 *p;
+	int i;
+
+	if (__be16_to_cpu(trace->namespace_id) != args->ns_id ||
+	    __be32_to_cpu(trace->type_be32) != args->trace_type)
+		return 1;
+
+	p = trace->data + trace->remlen * 4;
+
+	for (i = args->n_node - 1; i >= 0; i--) {
+		if (check_ioam6_node_data(&p, trace,
+					  args->node[i].hop_limit,
+					  args->node[i].id,
+					  args->node[i].wide,
+					  args->node[i].ingr_id,
+					  args->node[i].ingr_wide,
+					  args->node[i].egr_id,
+					  args->node[i].egr_wide,
+					  args->node[i].ns_data,
+					  args->node[i].ns_wide,
+					  args->node[i].sc_id,
+					  args->node[i].sc_data))
+			return 1;
+	}
+
+	return 0;
+}
+
+int parse_node_args(int *argcp, char ***argvp, struct node_args *node)
+{
+	char **argv = *argvp;
+
+	if (*argcp < NODE_ARGS_SIZE)
+		return 1;
+
+	node->hop_limit = strtoul(argv[NODE_ARG_HOP_LIMIT], NULL, 10);
+	if (!node->hop_limit) {
+		node->hop_limit = strtoul(argv[NODE_ARG_HOP_LIMIT], NULL, 16);
+		if (!node->hop_limit)
+			return 1;
+	}
+
+	node->id = strtoul(argv[NODE_ARG_ID], NULL, 10);
+	if (!node->id) {
+		node->id = strtoul(argv[NODE_ARG_ID], NULL, 16);
+		if (!node->id)
+			return 1;
+	}
+
+	node->wide = strtoull(argv[NODE_ARG_WIDE], NULL, 10);
+	if (!node->wide) {
+		node->wide = strtoull(argv[NODE_ARG_WIDE], NULL, 16);
+		if (!node->wide)
+			return 1;
+	}
+
+	node->ingr_id = strtoul(argv[NODE_ARG_INGR_ID], NULL, 10);
+	if (!node->ingr_id) {
+		node->ingr_id = strtoul(argv[NODE_ARG_INGR_ID], NULL, 16);
+		if (!node->ingr_id)
+			return 1;
+	}
+
+	node->ingr_wide = strtoul(argv[NODE_ARG_INGR_WIDE], NULL, 10);
+	if (!node->ingr_wide) {
+		node->ingr_wide = strtoul(argv[NODE_ARG_INGR_WIDE], NULL, 16);
+		if (!node->ingr_wide)
+			return 1;
+	}
+
+	node->egr_id = strtoul(argv[NODE_ARG_EGR_ID], NULL, 10);
+	if (!node->egr_id) {
+		node->egr_id = strtoul(argv[NODE_ARG_EGR_ID], NULL, 16);
+		if (!node->egr_id)
+			return 1;
+	}
+
+	node->egr_wide = strtoul(argv[NODE_ARG_EGR_WIDE], NULL, 10);
+	if (!node->egr_wide) {
+		node->egr_wide = strtoul(argv[NODE_ARG_EGR_WIDE], NULL, 16);
+		if (!node->egr_wide)
+			return 1;
+	}
+
+	node->ns_data = strtoul(argv[NODE_ARG_NS_DATA], NULL, 16);
+	if (!node->ns_data)
+		return 1;
+
+	node->ns_wide = strtoull(argv[NODE_ARG_NS_WIDE], NULL, 16);
+	if (!node->ns_wide)
+		return 1;
+
+	node->sc_id = strtoul(argv[NODE_ARG_SC_ID], NULL, 10);
+	if (!node->sc_id) {
+		node->sc_id = strtoul(argv[NODE_ARG_SC_ID], NULL, 16);
+		if (!node->sc_id)
+			return 1;
+	}
+
+	*argcp -= NODE_ARGS_SIZE;
+	*argvp += NODE_ARGS_SIZE;
+
+	if (node->sc_id != 0xffffff) {
+		if (!*argcp)
+			return 1;
+
+		node->sc_data = argv[NODE_ARG_SC_ID + 1];
+
+		*argcp -= 1;
+		*argvp += 1;
+	}
+
+	return 0;
+}
+
+struct args *parse_args(int argc, char **argv)
+{
+	struct args *args;
+	int n_node, i;
+
+	if (argc < ARGS_SIZE)
+		goto out;
+
+	n_node = strtoul(argv[ARG_N_NODE], NULL, 10);
+	if (!n_node || n_node > 10)
+		goto out;
+
+	args = calloc(1, sizeof(*args) + n_node * sizeof(struct node_args));
+	if (!args)
+		goto out;
+
+	args->ns_id = strtoul(argv[ARG_NS_ID], NULL, 10);
+	if (!args->ns_id)
+		goto free;
+
+	args->trace_type = strtoul(argv[ARG_TRACE_TYPE], NULL, 16);
+	if (!args->trace_type)
+		goto free;
+
+	args->n_node = n_node;
+	args->ifname = argv[ARG_IFNAME];
+
+	argv += ARGS_SIZE;
+	argc -= ARGS_SIZE;
+
+	for (i = 0; i < n_node; i++) {
+		if (parse_node_args(&argc, &argv, &args->node[i]))
+			goto free;
+	}
+
+	if (argc)
+		goto free;
+
+	return args;
+free:
+	free(args);
+out:
+	return NULL;
+}
+
+int main(int argc, char **argv)
+{
+	int ret, fd, pkts, size, hoplen, found;
+	struct ioam6_trace_hdr *ioam6h;
+	struct ioam6_hdr *opt;
+	struct ipv6hdr *ip6h;
+	__u8 buffer[400], *p;
+	struct args *args;
+
+	args = parse_args(argc - 1, argv + 1);
+	if (!args) {
+		ret = 1;
+		goto out;
+	}
+
+	fd = socket(AF_PACKET, SOCK_DGRAM, __cpu_to_be16(ETH_P_IPV6));
+	if (!fd) {
+		ret = 1;
+		goto out;
+	}
+
+	if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE,
+		       args->ifname, strlen(args->ifname))) {
+		ret = 1;
+		goto close;
+	}
+
+	pkts = 0;
+	found = 0;
+	while (pkts < 3 && !found) {
+		size = recv(fd, buffer, sizeof(buffer), 0);
+		ip6h = (struct ipv6hdr *)buffer;
+		pkts++;
+
+		if (ip6h->nexthdr == IPPROTO_HOPOPTS) {
+			p = buffer + sizeof(*ip6h);
+			hoplen = (p[1] + 1) << 3;
+
+			p += sizeof(struct ipv6_hopopt_hdr);
+			while (hoplen > 0) {
+				opt = (struct ioam6_hdr *)p;
+
+				if (opt->opt_type == IPV6_TLV_IOAM &&
+				    opt->type == IOAM6_TYPE_PREALLOC) {
+					found = 1;
+
+					p += sizeof(*opt);
+					ioam6h = (struct ioam6_trace_hdr *)p;
+
+					ret = check_ioam6_trace(ioam6h, args);
+					break;
+				}
+
+				p += opt->opt_len + 2;
+				hoplen -= opt->opt_len + 2;
+			}
+		}
+	}
+
+	if (!found)
+		ret = 1;
+close:
+	close(fd);
+out:
+	free(args);
+	return ret;
+}
+
