| // 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; | |
| } |