diff options
Diffstat (limited to 'tools/testing/selftests/net/ioam6_parser.c')
-rw-r--r-- | tools/testing/selftests/net/ioam6_parser.c | 402 |
1 files changed, 402 insertions, 0 deletions
diff --git a/tools/testing/selftests/net/ioam6_parser.c b/tools/testing/selftests/net/ioam6_parser.c new file mode 100644 index 000000000000..2256cf5ad637 --- /dev/null +++ b/tools/testing/selftests/net/ioam6_parser.c @@ -0,0 +1,402 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Author: Justin Iurman ([email protected]) + * + * 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; +} |