// SPDX-License-Identifier: GPL-2.0+ /* * Author: Justin Iurman (justin.iurman@uliege.be) * * IOAM tester for IPv6, see ioam6.sh for details on each test case. */ #include <arpa/inet.h> #include <errno.h> #include <limits.h> #include <linux/const.h> #include <linux/if_ether.h> #include <linux/ioam6.h> #include <linux/ipv6.h> #include <stdlib.h> #include <string.h> #include <unistd.h> struct ioam_config { __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 hlim; char *sc_data; }; /* * Be careful if you modify structs below - everything MUST be kept synchronized * with configurations inside ioam6.sh and always reflect the same. */ static struct ioam_config node1 = { .id = 1, .wide = 11111111, .ingr_id = 0xffff, /* default value */ .egr_id = 101, .ingr_wide = 0xffffffff, /* default value */ .egr_wide = 101101, .ns_data = 0xdeadbee0, .ns_wide = 0xcafec0caf00dc0de, .sc_id = 777, .sc_data = "something that will be 4n-aligned", .hlim = 64, }; static struct ioam_config node2 = { .id = 2, .wide = 22222222, .ingr_id = 201, .egr_id = 202, .ingr_wide = 201201, .egr_wide = 202202, .ns_data = 0xdeadbee1, .ns_wide = 0xcafec0caf11dc0de, .sc_id = 666, .sc_data = "Hello there -Obi", .hlim = 63, }; static struct ioam_config node3 = { .id = 3, .wide = 33333333, .ingr_id = 301, .egr_id = 0xffff, /* default value */ .ingr_wide = 301301, .egr_wide = 0xffffffff, /* default value */ .ns_data = 0xdeadbee2, .ns_wide = 0xcafec0caf22dc0de, .sc_id = 0xffffff, /* default value */ .sc_data = NULL, .hlim = 62, }; enum { /********** * OUTPUT * **********/ TEST_OUT_UNDEF_NS, TEST_OUT_NO_ROOM, TEST_OUT_BIT0, TEST_OUT_BIT1, TEST_OUT_BIT2, TEST_OUT_BIT3, TEST_OUT_BIT4, TEST_OUT_BIT5, TEST_OUT_BIT6, TEST_OUT_BIT7, TEST_OUT_BIT8, TEST_OUT_BIT9, TEST_OUT_BIT10, TEST_OUT_BIT11, TEST_OUT_BIT22, TEST_OUT_FULL_SUPP_TRACE, /********* * INPUT * *********/ TEST_IN_UNDEF_NS, TEST_IN_NO_ROOM, TEST_IN_OFLAG, TEST_IN_BIT0, TEST_IN_BIT1, TEST_IN_BIT2, TEST_IN_BIT3, TEST_IN_BIT4, TEST_IN_BIT5, TEST_IN_BIT6, TEST_IN_BIT7, TEST_IN_BIT8, TEST_IN_BIT9, TEST_IN_BIT10, TEST_IN_BIT11, TEST_IN_BIT22, TEST_IN_FULL_SUPP_TRACE, /********** * GLOBAL * **********/ TEST_FWD_FULL_SUPP_TRACE, __TEST_MAX, }; static int check_ioam_header(int tid, struct ioam6_trace_hdr *ioam6h, __u32 trace_type, __u16 ioam_ns) { if (__be16_to_cpu(ioam6h->namespace_id) != ioam_ns || __be32_to_cpu(ioam6h->type_be32) != (trace_type << 8)) return 1; switch (tid) { case TEST_OUT_UNDEF_NS: case TEST_IN_UNDEF_NS: return ioam6h->overflow || ioam6h->nodelen != 1 || ioam6h->remlen != 1; case TEST_OUT_NO_ROOM: case TEST_IN_NO_ROOM: case TEST_IN_OFLAG: return !ioam6h->overflow || ioam6h->nodelen != 2 || ioam6h->remlen != 1; case TEST_OUT_BIT0: case TEST_IN_BIT0: case TEST_OUT_BIT1: case TEST_IN_BIT1: case TEST_OUT_BIT2: case TEST_IN_BIT2: case TEST_OUT_BIT3: case TEST_IN_BIT3: case TEST_OUT_BIT4: case TEST_IN_BIT4: case TEST_OUT_BIT5: case TEST_IN_BIT5: case TEST_OUT_BIT6: case TEST_IN_BIT6: case TEST_OUT_BIT7: case TEST_IN_BIT7: case TEST_OUT_BIT11: case TEST_IN_BIT11: return ioam6h->overflow || ioam6h->nodelen != 1 || ioam6h->remlen; case TEST_OUT_BIT8: case TEST_IN_BIT8: case TEST_OUT_BIT9: case TEST_IN_BIT9: case TEST_OUT_BIT10: case TEST_IN_BIT10: return ioam6h->overflow || ioam6h->nodelen != 2 || ioam6h->remlen; case TEST_OUT_BIT22: case TEST_IN_BIT22: return ioam6h->overflow || ioam6h->nodelen || ioam6h->remlen; case TEST_OUT_FULL_SUPP_TRACE: case TEST_IN_FULL_SUPP_TRACE: case TEST_FWD_FULL_SUPP_TRACE: return ioam6h->overflow || ioam6h->nodelen != 15 || ioam6h->remlen; default: break; } return 1; } static int check_ioam6_data(__u8 **p, struct ioam6_trace_hdr *ioam6h, const struct ioam_config cnf) { unsigned int len; __u8 aligned; __u64 raw64; __u32 raw32; if (ioam6h->type.bit0) { raw32 = __be32_to_cpu(*((__u32 *)*p)); if (cnf.hlim != (raw32 >> 24) || cnf.id != (raw32 & 0xffffff)) return 1; *p += sizeof(__u32); } if (ioam6h->type.bit1) { raw32 = __be32_to_cpu(*((__u32 *)*p)); if (cnf.ingr_id != (raw32 >> 16) || cnf.egr_id != (raw32 & 0xffff)) return 1; *p += sizeof(__u32); } if (ioam6h->type.bit2) *p += sizeof(__u32); if (ioam6h->type.bit3) *p += sizeof(__u32); if (ioam6h->type.bit4) { if (__be32_to_cpu(*((__u32 *)*p)) != 0xffffffff) return 1; *p += sizeof(__u32); } if (ioam6h->type.bit5) { if (__be32_to_cpu(*((__u32 *)*p)) != cnf.ns_data) return 1; *p += sizeof(__u32); } if (ioam6h->type.bit6) *p += sizeof(__u32); if (ioam6h->type.bit7) { if (__be32_to_cpu(*((__u32 *)*p)) != 0xffffffff) return 1; *p += sizeof(__u32); } if (ioam6h->type.bit8) { raw64 = __be64_to_cpu(*((__u64 *)*p)); if (cnf.hlim != (raw64 >> 56) || cnf.wide != (raw64 & 0xffffffffffffff)) return 1; *p += sizeof(__u64); } if (ioam6h->type.bit9) { if (__be32_to_cpu(*((__u32 *)*p)) != cnf.ingr_wide) return 1; *p += sizeof(__u32); if (__be32_to_cpu(*((__u32 *)*p)) != cnf.egr_wide) return 1; *p += sizeof(__u32); } if (ioam6h->type.bit10) { if (__be64_to_cpu(*((__u64 *)*p)) != cnf.ns_wide) return 1; *p += sizeof(__u64); } if (ioam6h->type.bit11) { if (__be32_to_cpu(*((__u32 *)*p)) != 0xffffffff) return 1; *p += sizeof(__u32); } if (ioam6h->type.bit12) { if (__be32_to_cpu(*((__u32 *)*p)) != 0xffffffff) return 1; *p += sizeof(__u32); } if (ioam6h->type.bit13) { if (__be32_to_cpu(*((__u32 *)*p)) != 0xffffffff) return 1; *p += sizeof(__u32); } if (ioam6h->type.bit14) { if (__be32_to_cpu(*((__u32 *)*p)) != 0xffffffff) return 1; *p += sizeof(__u32); } if (ioam6h->type.bit15) { if (__be32_to_cpu(*((__u32 *)*p)) != 0xffffffff) return 1; *p += sizeof(__u32); } if (ioam6h->type.bit16) { if (__be32_to_cpu(*((__u32 *)*p)) != 0xffffffff) return 1; *p += sizeof(__u32); } if (ioam6h->type.bit17) { if (__be32_to_cpu(*((__u32 *)*p)) != 0xffffffff) return 1; *p += sizeof(__u32); } if (ioam6h->type.bit18) { if (__be32_to_cpu(*((__u32 *)*p)) != 0xffffffff) return 1; *p += sizeof(__u32); } if (ioam6h->type.bit19) { if (__be32_to_cpu(*((__u32 *)*p)) != 0xffffffff) return 1; *p += sizeof(__u32); } if (ioam6h->type.bit20) { if (__be32_to_cpu(*((__u32 *)*p)) != 0xffffffff) return 1; *p += sizeof(__u32); } if (ioam6h->type.bit21) { if (__be32_to_cpu(*((__u32 *)*p)) != 0xffffffff) return 1; *p += sizeof(__u32); } if (ioam6h->type.bit22) { len = cnf.sc_data ? strlen(cnf.sc_data) : 0; aligned = cnf.sc_data ? __ALIGN_KERNEL(len, 4) : 0; raw32 = __be32_to_cpu(*((__u32 *)*p)); if (aligned != (raw32 >> 24) * 4 || cnf.sc_id != (raw32 & 0xffffff)) return 1; *p += sizeof(__u32); if (cnf.sc_data) { if (strncmp((char *)*p, cnf.sc_data, len)) return 1; *p += len; aligned -= len; while (aligned--) { if (**p != '\0') return 1; *p += sizeof(__u8); } } } return 0; } static int check_ioam_header_and_data(int tid, struct ioam6_trace_hdr *ioam6h, __u32 trace_type, __u16 ioam_ns) { __u8 *p; if (check_ioam_header(tid, ioam6h, trace_type, ioam_ns)) return 1; p = ioam6h->data + ioam6h->remlen * 4; switch (tid) { case TEST_OUT_BIT0: case TEST_OUT_BIT1: case TEST_OUT_BIT2: case TEST_OUT_BIT3: case TEST_OUT_BIT4: case TEST_OUT_BIT5: case TEST_OUT_BIT6: case TEST_OUT_BIT7: case TEST_OUT_BIT8: case TEST_OUT_BIT9: case TEST_OUT_BIT10: case TEST_OUT_BIT11: case TEST_OUT_BIT22: case TEST_OUT_FULL_SUPP_TRACE: return check_ioam6_data(&p, ioam6h, node1); case TEST_IN_BIT0: case TEST_IN_BIT1: case TEST_IN_BIT2: case TEST_IN_BIT3: case TEST_IN_BIT4: case TEST_IN_BIT5: case TEST_IN_BIT6: case TEST_IN_BIT7: case TEST_IN_BIT8: case TEST_IN_BIT9: case TEST_IN_BIT10: case TEST_IN_BIT11: case TEST_IN_BIT22: case TEST_IN_FULL_SUPP_TRACE: { __u32 tmp32 = node2.egr_wide; __u16 tmp16 = node2.egr_id; int res; node2.egr_id = 0xffff; node2.egr_wide = 0xffffffff; res = check_ioam6_data(&p, ioam6h, node2); node2.egr_id = tmp16; node2.egr_wide = tmp32; return res; } case TEST_FWD_FULL_SUPP_TRACE: if (check_ioam6_data(&p, ioam6h, node3)) return 1; if (check_ioam6_data(&p, ioam6h, node2)) return 1; return check_ioam6_data(&p, ioam6h, node1); default: break; } return 1; } static int str2id(const char *tname) { if (!strcmp("out_undef_ns", tname)) return TEST_OUT_UNDEF_NS; if (!strcmp("out_no_room", tname)) return TEST_OUT_NO_ROOM; if (!strcmp("out_bit0", tname)) return TEST_OUT_BIT0; if (!strcmp("out_bit1", tname)) return TEST_OUT_BIT1; if (!strcmp("out_bit2", tname)) return TEST_OUT_BIT2; if (!strcmp("out_bit3", tname)) return TEST_OUT_BIT3; if (!strcmp("out_bit4", tname)) return TEST_OUT_BIT4; if (!strcmp("out_bit5", tname)) return TEST_OUT_BIT5; if (!strcmp("out_bit6", tname)) return TEST_OUT_BIT6; if (!strcmp("out_bit7", tname)) return TEST_OUT_BIT7; if (!strcmp("out_bit8", tname)) return TEST_OUT_BIT8; if (!strcmp("out_bit9", tname)) return TEST_OUT_BIT9; if (!strcmp("out_bit10", tname)) return TEST_OUT_BIT10; if (!strcmp("out_bit11", tname)) return TEST_OUT_BIT11; if (!strcmp("out_bit22", tname)) return TEST_OUT_BIT22; if (!strcmp("out_full_supp_trace", tname)) return TEST_OUT_FULL_SUPP_TRACE; if (!strcmp("in_undef_ns", tname)) return TEST_IN_UNDEF_NS; if (!strcmp("in_no_room", tname)) return TEST_IN_NO_ROOM; if (!strcmp("in_oflag", tname)) return TEST_IN_OFLAG; if (!strcmp("in_bit0", tname)) return TEST_IN_BIT0; if (!strcmp("in_bit1", tname)) return TEST_IN_BIT1; if (!strcmp("in_bit2", tname)) return TEST_IN_BIT2; if (!strcmp("in_bit3", tname)) return TEST_IN_BIT3; if (!strcmp("in_bit4", tname)) return TEST_IN_BIT4; if (!strcmp("in_bit5", tname)) return TEST_IN_BIT5; if (!strcmp("in_bit6", tname)) return TEST_IN_BIT6; if (!strcmp("in_bit7", tname)) return TEST_IN_BIT7; if (!strcmp("in_bit8", tname)) return TEST_IN_BIT8; if (!strcmp("in_bit9", tname)) return TEST_IN_BIT9; if (!strcmp("in_bit10", tname)) return TEST_IN_BIT10; if (!strcmp("in_bit11", tname)) return TEST_IN_BIT11; if (!strcmp("in_bit22", tname)) return TEST_IN_BIT22; if (!strcmp("in_full_supp_trace", tname)) return TEST_IN_FULL_SUPP_TRACE; if (!strcmp("fwd_full_supp_trace", tname)) return TEST_FWD_FULL_SUPP_TRACE; return -1; } static int ipv6_addr_equal(const struct in6_addr *a1, const struct in6_addr *a2) { return ((a1->s6_addr32[0] ^ a2->s6_addr32[0]) | (a1->s6_addr32[1] ^ a2->s6_addr32[1]) | (a1->s6_addr32[2] ^ a2->s6_addr32[2]) | (a1->s6_addr32[3] ^ a2->s6_addr32[3])) == 0; } static int get_u32(__u32 *val, const char *arg, int base) { unsigned long res; char *ptr; if (!arg || !*arg) return -1; res = strtoul(arg, &ptr, base); if (!ptr || ptr == arg || *ptr) return -1; if (res == ULONG_MAX && errno == ERANGE) return -1; if (res > 0xFFFFFFFFUL) return -1; *val = res; return 0; } static int get_u16(__u16 *val, const char *arg, int base) { unsigned long res; char *ptr; if (!arg || !*arg) return -1; res = strtoul(arg, &ptr, base); if (!ptr || ptr == arg || *ptr) return -1; if (res == ULONG_MAX && errno == ERANGE) return -1; if (res > 0xFFFFUL) return -1; *val = res; return 0; } static int (*func[__TEST_MAX])(int, struct ioam6_trace_hdr *, __u32, __u16) = { [TEST_OUT_UNDEF_NS] = check_ioam_header, [TEST_OUT_NO_ROOM] = check_ioam_header, [TEST_OUT_BIT0] = check_ioam_header_and_data, [TEST_OUT_BIT1] = check_ioam_header_and_data, [TEST_OUT_BIT2] = check_ioam_header_and_data, [TEST_OUT_BIT3] = check_ioam_header_and_data, [TEST_OUT_BIT4] = check_ioam_header_and_data, [TEST_OUT_BIT5] = check_ioam_header_and_data, [TEST_OUT_BIT6] = check_ioam_header_and_data, [TEST_OUT_BIT7] = check_ioam_header_and_data, [TEST_OUT_BIT8] = check_ioam_header_and_data, [TEST_OUT_BIT9] = check_ioam_header_and_data, [TEST_OUT_BIT10] = check_ioam_header_and_data, [TEST_OUT_BIT11] = check_ioam_header_and_data, [TEST_OUT_BIT22] = check_ioam_header_and_data, [TEST_OUT_FULL_SUPP_TRACE] = check_ioam_header_and_data, [TEST_IN_UNDEF_NS] = check_ioam_header, [TEST_IN_NO_ROOM] = check_ioam_header, [TEST_IN_OFLAG] = check_ioam_header, [TEST_IN_BIT0] = check_ioam_header_and_data, [TEST_IN_BIT1] = check_ioam_header_and_data, [TEST_IN_BIT2] = check_ioam_header_and_data, [TEST_IN_BIT3] = check_ioam_header_and_data, [TEST_IN_BIT4] = check_ioam_header_and_data, [TEST_IN_BIT5] = check_ioam_header_and_data, [TEST_IN_BIT6] = check_ioam_header_and_data, [TEST_IN_BIT7] = check_ioam_header_and_data, [TEST_IN_BIT8] = check_ioam_header_and_data, [TEST_IN_BIT9] = check_ioam_header_and_data, [TEST_IN_BIT10] = check_ioam_header_and_data, [TEST_IN_BIT11] = check_ioam_header_and_data, [TEST_IN_BIT22] = check_ioam_header_and_data, [TEST_IN_FULL_SUPP_TRACE] = check_ioam_header_and_data, [TEST_FWD_FULL_SUPP_TRACE] = check_ioam_header_and_data, }; int main(int argc, char **argv) { int fd, size, hoplen, tid, ret = 1; struct in6_addr src, dst; struct ioam6_hdr *opt; struct ipv6hdr *ip6h; __u8 buffer[400], *p; __u16 ioam_ns; __u32 tr_type; if (argc != 7) goto out; tid = str2id(argv[2]); if (tid < 0 || !func[tid]) goto out; if (inet_pton(AF_INET6, argv[3], &src) != 1 || inet_pton(AF_INET6, argv[4], &dst) != 1) goto out; if (get_u32(&tr_type, argv[5], 16) || get_u16(&ioam_ns, argv[6], 0)) goto out; fd = socket(AF_PACKET, SOCK_DGRAM, __cpu_to_be16(ETH_P_IPV6)); if (!fd) goto out; if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, argv[1], strlen(argv[1]))) goto close; recv: size = recv(fd, buffer, sizeof(buffer), 0); if (size <= 0) goto close; ip6h = (struct ipv6hdr *)buffer; if (!ipv6_addr_equal(&ip6h->saddr, &src) || !ipv6_addr_equal(&ip6h->daddr, &dst)) goto recv; if (ip6h->nexthdr != IPPROTO_HOPOPTS) goto close; 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) { p += sizeof(*opt); ret = func[tid](tid, (struct ioam6_trace_hdr *)p, tr_type, ioam_ns); break; } p += opt->opt_len + 2; hoplen -= opt->opt_len + 2; } close: close(fd); out: return ret; }