| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Inject packets with all sorts of encapsulation into the kernel. |
| * |
| * IPv4/IPv6 outer layer 3 |
| * GRE/GUE/BARE outer layer 4, where bare is IPIP/SIT/IPv4-in-IPv6/.. |
| * IPv4/IPv6 inner layer 3 |
| */ |
| |
| #define _GNU_SOURCE |
| |
| #include <stddef.h> |
| #include <arpa/inet.h> |
| #include <asm/byteorder.h> |
| #include <error.h> |
| #include <errno.h> |
| #include <linux/if_packet.h> |
| #include <linux/if_ether.h> |
| #include <linux/ipv6.h> |
| #include <netinet/ip.h> |
| #include <netinet/in.h> |
| #include <netinet/udp.h> |
| #include <poll.h> |
| #include <stdbool.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <sys/ioctl.h> |
| #include <sys/socket.h> |
| #include <sys/stat.h> |
| #include <sys/time.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #define CFG_PORT_INNER 8000 |
| |
| /* Add some protocol definitions that do not exist in userspace */ |
| |
| struct grehdr { |
| uint16_t unused; |
| uint16_t protocol; |
| } __attribute__((packed)); |
| |
| struct guehdr { |
| union { |
| struct { |
| #if defined(__LITTLE_ENDIAN_BITFIELD) |
| __u8 hlen:5, |
| control:1, |
| version:2; |
| #elif defined (__BIG_ENDIAN_BITFIELD) |
| __u8 version:2, |
| control:1, |
| hlen:5; |
| #else |
| #error "Please fix <asm/byteorder.h>" |
| #endif |
| __u8 proto_ctype; |
| __be16 flags; |
| }; |
| __be32 word; |
| }; |
| }; |
| |
| static uint8_t cfg_dsfield_inner; |
| static uint8_t cfg_dsfield_outer; |
| static uint8_t cfg_encap_proto; |
| static bool cfg_expect_failure = false; |
| static int cfg_l3_extra = AF_UNSPEC; /* optional SIT prefix */ |
| static int cfg_l3_inner = AF_UNSPEC; |
| static int cfg_l3_outer = AF_UNSPEC; |
| static int cfg_num_pkt = 10; |
| static int cfg_num_secs = 0; |
| static char cfg_payload_char = 'a'; |
| static int cfg_payload_len = 100; |
| static int cfg_port_gue = 6080; |
| static bool cfg_only_rx; |
| static bool cfg_only_tx; |
| static int cfg_src_port = 9; |
| |
| static char buf[ETH_DATA_LEN]; |
| |
| #define INIT_ADDR4(name, addr4, port) \ |
| static struct sockaddr_in name = { \ |
| .sin_family = AF_INET, \ |
| .sin_port = __constant_htons(port), \ |
| .sin_addr.s_addr = __constant_htonl(addr4), \ |
| }; |
| |
| #define INIT_ADDR6(name, addr6, port) \ |
| static struct sockaddr_in6 name = { \ |
| .sin6_family = AF_INET6, \ |
| .sin6_port = __constant_htons(port), \ |
| .sin6_addr = addr6, \ |
| }; |
| |
| INIT_ADDR4(in_daddr4, INADDR_LOOPBACK, CFG_PORT_INNER) |
| INIT_ADDR4(in_saddr4, INADDR_LOOPBACK + 2, 0) |
| INIT_ADDR4(out_daddr4, INADDR_LOOPBACK, 0) |
| INIT_ADDR4(out_saddr4, INADDR_LOOPBACK + 1, 0) |
| INIT_ADDR4(extra_daddr4, INADDR_LOOPBACK, 0) |
| INIT_ADDR4(extra_saddr4, INADDR_LOOPBACK + 1, 0) |
| |
| INIT_ADDR6(in_daddr6, IN6ADDR_LOOPBACK_INIT, CFG_PORT_INNER) |
| INIT_ADDR6(in_saddr6, IN6ADDR_LOOPBACK_INIT, 0) |
| INIT_ADDR6(out_daddr6, IN6ADDR_LOOPBACK_INIT, 0) |
| INIT_ADDR6(out_saddr6, IN6ADDR_LOOPBACK_INIT, 0) |
| INIT_ADDR6(extra_daddr6, IN6ADDR_LOOPBACK_INIT, 0) |
| INIT_ADDR6(extra_saddr6, IN6ADDR_LOOPBACK_INIT, 0) |
| |
| static unsigned long util_gettime(void) |
| { |
| struct timeval tv; |
| |
| gettimeofday(&tv, NULL); |
| return (tv.tv_sec * 1000) + (tv.tv_usec / 1000); |
| } |
| |
| static void util_printaddr(const char *msg, struct sockaddr *addr) |
| { |
| unsigned long off = 0; |
| char nbuf[INET6_ADDRSTRLEN]; |
| |
| switch (addr->sa_family) { |
| case PF_INET: |
| off = __builtin_offsetof(struct sockaddr_in, sin_addr); |
| break; |
| case PF_INET6: |
| off = __builtin_offsetof(struct sockaddr_in6, sin6_addr); |
| break; |
| default: |
| error(1, 0, "printaddr: unsupported family %u\n", |
| addr->sa_family); |
| } |
| |
| if (!inet_ntop(addr->sa_family, ((void *) addr) + off, nbuf, |
| sizeof(nbuf))) |
| error(1, errno, "inet_ntop"); |
| |
| fprintf(stderr, "%s: %s\n", msg, nbuf); |
| } |
| |
| static unsigned long add_csum_hword(const uint16_t *start, int num_u16) |
| { |
| unsigned long sum = 0; |
| int i; |
| |
| for (i = 0; i < num_u16; i++) |
| sum += start[i]; |
| |
| return sum; |
| } |
| |
| static uint16_t build_ip_csum(const uint16_t *start, int num_u16, |
| unsigned long sum) |
| { |
| sum += add_csum_hword(start, num_u16); |
| |
| while (sum >> 16) |
| sum = (sum & 0xffff) + (sum >> 16); |
| |
| return ~sum; |
| } |
| |
| static void build_ipv4_header(void *header, uint8_t proto, |
| uint32_t src, uint32_t dst, |
| int payload_len, uint8_t tos) |
| { |
| struct iphdr *iph = header; |
| |
| iph->ihl = 5; |
| iph->version = 4; |
| iph->tos = tos; |
| iph->ttl = 8; |
| iph->tot_len = htons(sizeof(*iph) + payload_len); |
| iph->id = htons(1337); |
| iph->protocol = proto; |
| iph->saddr = src; |
| iph->daddr = dst; |
| iph->check = build_ip_csum((void *) iph, iph->ihl << 1, 0); |
| } |
| |
| static void ipv6_set_dsfield(struct ipv6hdr *ip6h, uint8_t dsfield) |
| { |
| uint16_t val, *ptr = (uint16_t *)ip6h; |
| |
| val = ntohs(*ptr); |
| val &= 0xF00F; |
| val |= ((uint16_t) dsfield) << 4; |
| *ptr = htons(val); |
| } |
| |
| static void build_ipv6_header(void *header, uint8_t proto, |
| struct sockaddr_in6 *src, |
| struct sockaddr_in6 *dst, |
| int payload_len, uint8_t dsfield) |
| { |
| struct ipv6hdr *ip6h = header; |
| |
| ip6h->version = 6; |
| ip6h->payload_len = htons(payload_len); |
| ip6h->nexthdr = proto; |
| ip6h->hop_limit = 8; |
| ipv6_set_dsfield(ip6h, dsfield); |
| |
| memcpy(&ip6h->saddr, &src->sin6_addr, sizeof(ip6h->saddr)); |
| memcpy(&ip6h->daddr, &dst->sin6_addr, sizeof(ip6h->daddr)); |
| } |
| |
| static uint16_t build_udp_v4_csum(const struct iphdr *iph, |
| const struct udphdr *udph, |
| int num_words) |
| { |
| unsigned long pseudo_sum; |
| int num_u16 = sizeof(iph->saddr); /* halfwords: twice byte len */ |
| |
| pseudo_sum = add_csum_hword((void *) &iph->saddr, num_u16); |
| pseudo_sum += htons(IPPROTO_UDP); |
| pseudo_sum += udph->len; |
| return build_ip_csum((void *) udph, num_words, pseudo_sum); |
| } |
| |
| static uint16_t build_udp_v6_csum(const struct ipv6hdr *ip6h, |
| const struct udphdr *udph, |
| int num_words) |
| { |
| unsigned long pseudo_sum; |
| int num_u16 = sizeof(ip6h->saddr); /* halfwords: twice byte len */ |
| |
| pseudo_sum = add_csum_hword((void *) &ip6h->saddr, num_u16); |
| pseudo_sum += htons(ip6h->nexthdr); |
| pseudo_sum += ip6h->payload_len; |
| return build_ip_csum((void *) udph, num_words, pseudo_sum); |
| } |
| |
| static void build_udp_header(void *header, int payload_len, |
| uint16_t dport, int family) |
| { |
| struct udphdr *udph = header; |
| int len = sizeof(*udph) + payload_len; |
| |
| udph->source = htons(cfg_src_port); |
| udph->dest = htons(dport); |
| udph->len = htons(len); |
| udph->check = 0; |
| if (family == AF_INET) |
| udph->check = build_udp_v4_csum(header - sizeof(struct iphdr), |
| udph, len >> 1); |
| else |
| udph->check = build_udp_v6_csum(header - sizeof(struct ipv6hdr), |
| udph, len >> 1); |
| } |
| |
| static void build_gue_header(void *header, uint8_t proto) |
| { |
| struct guehdr *gueh = header; |
| |
| gueh->proto_ctype = proto; |
| } |
| |
| static void build_gre_header(void *header, uint16_t proto) |
| { |
| struct grehdr *greh = header; |
| |
| greh->protocol = htons(proto); |
| } |
| |
| static int l3_length(int family) |
| { |
| if (family == AF_INET) |
| return sizeof(struct iphdr); |
| else |
| return sizeof(struct ipv6hdr); |
| } |
| |
| static int build_packet(void) |
| { |
| int ol3_len = 0, ol4_len = 0, il3_len = 0, il4_len = 0; |
| int el3_len = 0; |
| |
| if (cfg_l3_extra) |
| el3_len = l3_length(cfg_l3_extra); |
| |
| /* calculate header offsets */ |
| if (cfg_encap_proto) { |
| ol3_len = l3_length(cfg_l3_outer); |
| |
| if (cfg_encap_proto == IPPROTO_GRE) |
| ol4_len = sizeof(struct grehdr); |
| else if (cfg_encap_proto == IPPROTO_UDP) |
| ol4_len = sizeof(struct udphdr) + sizeof(struct guehdr); |
| } |
| |
| il3_len = l3_length(cfg_l3_inner); |
| il4_len = sizeof(struct udphdr); |
| |
| if (el3_len + ol3_len + ol4_len + il3_len + il4_len + cfg_payload_len >= |
| sizeof(buf)) |
| error(1, 0, "packet too large\n"); |
| |
| /* |
| * Fill packet from inside out, to calculate correct checksums. |
| * But create ip before udp headers, as udp uses ip for pseudo-sum. |
| */ |
| memset(buf + el3_len + ol3_len + ol4_len + il3_len + il4_len, |
| cfg_payload_char, cfg_payload_len); |
| |
| /* add zero byte for udp csum padding */ |
| buf[el3_len + ol3_len + ol4_len + il3_len + il4_len + cfg_payload_len] = 0; |
| |
| switch (cfg_l3_inner) { |
| case PF_INET: |
| build_ipv4_header(buf + el3_len + ol3_len + ol4_len, |
| IPPROTO_UDP, |
| in_saddr4.sin_addr.s_addr, |
| in_daddr4.sin_addr.s_addr, |
| il4_len + cfg_payload_len, |
| cfg_dsfield_inner); |
| break; |
| case PF_INET6: |
| build_ipv6_header(buf + el3_len + ol3_len + ol4_len, |
| IPPROTO_UDP, |
| &in_saddr6, &in_daddr6, |
| il4_len + cfg_payload_len, |
| cfg_dsfield_inner); |
| break; |
| } |
| |
| build_udp_header(buf + el3_len + ol3_len + ol4_len + il3_len, |
| cfg_payload_len, CFG_PORT_INNER, cfg_l3_inner); |
| |
| if (!cfg_encap_proto) |
| return il3_len + il4_len + cfg_payload_len; |
| |
| switch (cfg_l3_outer) { |
| case PF_INET: |
| build_ipv4_header(buf + el3_len, cfg_encap_proto, |
| out_saddr4.sin_addr.s_addr, |
| out_daddr4.sin_addr.s_addr, |
| ol4_len + il3_len + il4_len + cfg_payload_len, |
| cfg_dsfield_outer); |
| break; |
| case PF_INET6: |
| build_ipv6_header(buf + el3_len, cfg_encap_proto, |
| &out_saddr6, &out_daddr6, |
| ol4_len + il3_len + il4_len + cfg_payload_len, |
| cfg_dsfield_outer); |
| break; |
| } |
| |
| switch (cfg_encap_proto) { |
| case IPPROTO_UDP: |
| build_gue_header(buf + el3_len + ol3_len + ol4_len - |
| sizeof(struct guehdr), |
| cfg_l3_inner == PF_INET ? IPPROTO_IPIP |
| : IPPROTO_IPV6); |
| build_udp_header(buf + el3_len + ol3_len, |
| sizeof(struct guehdr) + il3_len + il4_len + |
| cfg_payload_len, |
| cfg_port_gue, cfg_l3_outer); |
| break; |
| case IPPROTO_GRE: |
| build_gre_header(buf + el3_len + ol3_len, |
| cfg_l3_inner == PF_INET ? ETH_P_IP |
| : ETH_P_IPV6); |
| break; |
| } |
| |
| switch (cfg_l3_extra) { |
| case PF_INET: |
| build_ipv4_header(buf, |
| cfg_l3_outer == PF_INET ? IPPROTO_IPIP |
| : IPPROTO_IPV6, |
| extra_saddr4.sin_addr.s_addr, |
| extra_daddr4.sin_addr.s_addr, |
| ol3_len + ol4_len + il3_len + il4_len + |
| cfg_payload_len, 0); |
| break; |
| case PF_INET6: |
| build_ipv6_header(buf, |
| cfg_l3_outer == PF_INET ? IPPROTO_IPIP |
| : IPPROTO_IPV6, |
| &extra_saddr6, &extra_daddr6, |
| ol3_len + ol4_len + il3_len + il4_len + |
| cfg_payload_len, 0); |
| break; |
| } |
| |
| return el3_len + ol3_len + ol4_len + il3_len + il4_len + |
| cfg_payload_len; |
| } |
| |
| /* sender transmits encapsulated over RAW or unencap'd over UDP */ |
| static int setup_tx(void) |
| { |
| int family, fd, ret; |
| |
| if (cfg_l3_extra) |
| family = cfg_l3_extra; |
| else if (cfg_l3_outer) |
| family = cfg_l3_outer; |
| else |
| family = cfg_l3_inner; |
| |
| fd = socket(family, SOCK_RAW, IPPROTO_RAW); |
| if (fd == -1) |
| error(1, errno, "socket tx"); |
| |
| if (cfg_l3_extra) { |
| if (cfg_l3_extra == PF_INET) |
| ret = connect(fd, (void *) &extra_daddr4, |
| sizeof(extra_daddr4)); |
| else |
| ret = connect(fd, (void *) &extra_daddr6, |
| sizeof(extra_daddr6)); |
| if (ret) |
| error(1, errno, "connect tx"); |
| } else if (cfg_l3_outer) { |
| /* connect to destination if not encapsulated */ |
| if (cfg_l3_outer == PF_INET) |
| ret = connect(fd, (void *) &out_daddr4, |
| sizeof(out_daddr4)); |
| else |
| ret = connect(fd, (void *) &out_daddr6, |
| sizeof(out_daddr6)); |
| if (ret) |
| error(1, errno, "connect tx"); |
| } else { |
| /* otherwise using loopback */ |
| if (cfg_l3_inner == PF_INET) |
| ret = connect(fd, (void *) &in_daddr4, |
| sizeof(in_daddr4)); |
| else |
| ret = connect(fd, (void *) &in_daddr6, |
| sizeof(in_daddr6)); |
| if (ret) |
| error(1, errno, "connect tx"); |
| } |
| |
| return fd; |
| } |
| |
| /* receiver reads unencapsulated UDP */ |
| static int setup_rx(void) |
| { |
| int fd, ret; |
| |
| fd = socket(cfg_l3_inner, SOCK_DGRAM, 0); |
| if (fd == -1) |
| error(1, errno, "socket rx"); |
| |
| if (cfg_l3_inner == PF_INET) |
| ret = bind(fd, (void *) &in_daddr4, sizeof(in_daddr4)); |
| else |
| ret = bind(fd, (void *) &in_daddr6, sizeof(in_daddr6)); |
| if (ret) |
| error(1, errno, "bind rx"); |
| |
| return fd; |
| } |
| |
| static int do_tx(int fd, const char *pkt, int len) |
| { |
| int ret; |
| |
| ret = write(fd, pkt, len); |
| if (ret == -1) |
| error(1, errno, "send"); |
| if (ret != len) |
| error(1, errno, "send: len (%d < %d)\n", ret, len); |
| |
| return 1; |
| } |
| |
| static int do_poll(int fd, short events, int timeout) |
| { |
| struct pollfd pfd; |
| int ret; |
| |
| pfd.fd = fd; |
| pfd.events = events; |
| |
| ret = poll(&pfd, 1, timeout); |
| if (ret == -1) |
| error(1, errno, "poll"); |
| if (ret && !(pfd.revents & POLLIN)) |
| error(1, errno, "poll: unexpected event 0x%x\n", pfd.revents); |
| |
| return ret; |
| } |
| |
| static int do_rx(int fd) |
| { |
| char rbuf; |
| int ret, num = 0; |
| |
| while (1) { |
| ret = recv(fd, &rbuf, 1, MSG_DONTWAIT); |
| if (ret == -1 && errno == EAGAIN) |
| break; |
| if (ret == -1) |
| error(1, errno, "recv"); |
| if (rbuf != cfg_payload_char) |
| error(1, 0, "recv: payload mismatch"); |
| num++; |
| } |
| |
| return num; |
| } |
| |
| static int do_main(void) |
| { |
| unsigned long tstop, treport, tcur; |
| int fdt = -1, fdr = -1, len, tx = 0, rx = 0; |
| |
| if (!cfg_only_tx) |
| fdr = setup_rx(); |
| if (!cfg_only_rx) |
| fdt = setup_tx(); |
| |
| len = build_packet(); |
| |
| tcur = util_gettime(); |
| treport = tcur + 1000; |
| tstop = tcur + (cfg_num_secs * 1000); |
| |
| while (1) { |
| if (!cfg_only_rx) |
| tx += do_tx(fdt, buf, len); |
| |
| if (!cfg_only_tx) |
| rx += do_rx(fdr); |
| |
| if (cfg_num_secs) { |
| tcur = util_gettime(); |
| if (tcur >= tstop) |
| break; |
| if (tcur >= treport) { |
| fprintf(stderr, "pkts: tx=%u rx=%u\n", tx, rx); |
| tx = 0; |
| rx = 0; |
| treport = tcur + 1000; |
| } |
| } else { |
| if (tx == cfg_num_pkt) |
| break; |
| } |
| } |
| |
| /* read straggler packets, if any */ |
| if (rx < tx) { |
| tstop = util_gettime() + 100; |
| while (rx < tx) { |
| tcur = util_gettime(); |
| if (tcur >= tstop) |
| break; |
| |
| do_poll(fdr, POLLIN, tstop - tcur); |
| rx += do_rx(fdr); |
| } |
| } |
| |
| fprintf(stderr, "pkts: tx=%u rx=%u\n", tx, rx); |
| |
| if (fdr != -1 && close(fdr)) |
| error(1, errno, "close rx"); |
| if (fdt != -1 && close(fdt)) |
| error(1, errno, "close tx"); |
| |
| /* |
| * success (== 0) only if received all packets |
| * unless failure is expected, in which case none must arrive. |
| */ |
| if (cfg_expect_failure) |
| return rx != 0; |
| else |
| return rx != tx; |
| } |
| |
| |
| static void __attribute__((noreturn)) usage(const char *filepath) |
| { |
| fprintf(stderr, "Usage: %s [-e gre|gue|bare|none] [-i 4|6] [-l len] " |
| "[-O 4|6] [-o 4|6] [-n num] [-t secs] [-R] [-T] " |
| "[-s <osrc> [-d <odst>] [-S <isrc>] [-D <idst>] " |
| "[-x <otos>] [-X <itos>] [-f <isport>] [-F]\n", |
| filepath); |
| exit(1); |
| } |
| |
| static void parse_addr(int family, void *addr, const char *optarg) |
| { |
| int ret; |
| |
| ret = inet_pton(family, optarg, addr); |
| if (ret == -1) |
| error(1, errno, "inet_pton"); |
| if (ret == 0) |
| error(1, 0, "inet_pton: bad string"); |
| } |
| |
| static void parse_addr4(struct sockaddr_in *addr, const char *optarg) |
| { |
| parse_addr(AF_INET, &addr->sin_addr, optarg); |
| } |
| |
| static void parse_addr6(struct sockaddr_in6 *addr, const char *optarg) |
| { |
| parse_addr(AF_INET6, &addr->sin6_addr, optarg); |
| } |
| |
| static int parse_protocol_family(const char *filepath, const char *optarg) |
| { |
| if (!strcmp(optarg, "4")) |
| return PF_INET; |
| if (!strcmp(optarg, "6")) |
| return PF_INET6; |
| |
| usage(filepath); |
| } |
| |
| static void parse_opts(int argc, char **argv) |
| { |
| int c; |
| |
| while ((c = getopt(argc, argv, "d:D:e:f:Fhi:l:n:o:O:Rs:S:t:Tx:X:")) != -1) { |
| switch (c) { |
| case 'd': |
| if (cfg_l3_outer == AF_UNSPEC) |
| error(1, 0, "-d must be preceded by -o"); |
| if (cfg_l3_outer == AF_INET) |
| parse_addr4(&out_daddr4, optarg); |
| else |
| parse_addr6(&out_daddr6, optarg); |
| break; |
| case 'D': |
| if (cfg_l3_inner == AF_UNSPEC) |
| error(1, 0, "-D must be preceded by -i"); |
| if (cfg_l3_inner == AF_INET) |
| parse_addr4(&in_daddr4, optarg); |
| else |
| parse_addr6(&in_daddr6, optarg); |
| break; |
| case 'e': |
| if (!strcmp(optarg, "gre")) |
| cfg_encap_proto = IPPROTO_GRE; |
| else if (!strcmp(optarg, "gue")) |
| cfg_encap_proto = IPPROTO_UDP; |
| else if (!strcmp(optarg, "bare")) |
| cfg_encap_proto = IPPROTO_IPIP; |
| else if (!strcmp(optarg, "none")) |
| cfg_encap_proto = IPPROTO_IP; /* == 0 */ |
| else |
| usage(argv[0]); |
| break; |
| case 'f': |
| cfg_src_port = strtol(optarg, NULL, 0); |
| break; |
| case 'F': |
| cfg_expect_failure = true; |
| break; |
| case 'h': |
| usage(argv[0]); |
| break; |
| case 'i': |
| if (!strcmp(optarg, "4")) |
| cfg_l3_inner = PF_INET; |
| else if (!strcmp(optarg, "6")) |
| cfg_l3_inner = PF_INET6; |
| else |
| usage(argv[0]); |
| break; |
| case 'l': |
| cfg_payload_len = strtol(optarg, NULL, 0); |
| break; |
| case 'n': |
| cfg_num_pkt = strtol(optarg, NULL, 0); |
| break; |
| case 'o': |
| cfg_l3_outer = parse_protocol_family(argv[0], optarg); |
| break; |
| case 'O': |
| cfg_l3_extra = parse_protocol_family(argv[0], optarg); |
| break; |
| case 'R': |
| cfg_only_rx = true; |
| break; |
| case 's': |
| if (cfg_l3_outer == AF_INET) |
| parse_addr4(&out_saddr4, optarg); |
| else |
| parse_addr6(&out_saddr6, optarg); |
| break; |
| case 'S': |
| if (cfg_l3_inner == AF_INET) |
| parse_addr4(&in_saddr4, optarg); |
| else |
| parse_addr6(&in_saddr6, optarg); |
| break; |
| case 't': |
| cfg_num_secs = strtol(optarg, NULL, 0); |
| break; |
| case 'T': |
| cfg_only_tx = true; |
| break; |
| case 'x': |
| cfg_dsfield_outer = strtol(optarg, NULL, 0); |
| break; |
| case 'X': |
| cfg_dsfield_inner = strtol(optarg, NULL, 0); |
| break; |
| } |
| } |
| |
| if (cfg_only_rx && cfg_only_tx) |
| error(1, 0, "options: cannot combine rx-only and tx-only"); |
| |
| if (cfg_encap_proto && cfg_l3_outer == AF_UNSPEC) |
| error(1, 0, "options: must specify outer with encap"); |
| else if ((!cfg_encap_proto) && cfg_l3_outer != AF_UNSPEC) |
| error(1, 0, "options: cannot combine no-encap and outer"); |
| else if ((!cfg_encap_proto) && cfg_l3_extra != AF_UNSPEC) |
| error(1, 0, "options: cannot combine no-encap and extra"); |
| |
| if (cfg_l3_inner == AF_UNSPEC) |
| cfg_l3_inner = AF_INET6; |
| if (cfg_l3_inner == AF_INET6 && cfg_encap_proto == IPPROTO_IPIP) |
| cfg_encap_proto = IPPROTO_IPV6; |
| |
| /* RFC 6040 4.2: |
| * on decap, if outer encountered congestion (CE == 0x3), |
| * but inner cannot encode ECN (NoECT == 0x0), then drop packet. |
| */ |
| if (((cfg_dsfield_outer & 0x3) == 0x3) && |
| ((cfg_dsfield_inner & 0x3) == 0x0)) |
| cfg_expect_failure = true; |
| } |
| |
| static void print_opts(void) |
| { |
| if (cfg_l3_inner == PF_INET6) { |
| util_printaddr("inner.dest6", (void *) &in_daddr6); |
| util_printaddr("inner.source6", (void *) &in_saddr6); |
| } else { |
| util_printaddr("inner.dest4", (void *) &in_daddr4); |
| util_printaddr("inner.source4", (void *) &in_saddr4); |
| } |
| |
| if (!cfg_l3_outer) |
| return; |
| |
| fprintf(stderr, "encap proto: %u\n", cfg_encap_proto); |
| |
| if (cfg_l3_outer == PF_INET6) { |
| util_printaddr("outer.dest6", (void *) &out_daddr6); |
| util_printaddr("outer.source6", (void *) &out_saddr6); |
| } else { |
| util_printaddr("outer.dest4", (void *) &out_daddr4); |
| util_printaddr("outer.source4", (void *) &out_saddr4); |
| } |
| |
| if (!cfg_l3_extra) |
| return; |
| |
| if (cfg_l3_outer == PF_INET6) { |
| util_printaddr("extra.dest6", (void *) &extra_daddr6); |
| util_printaddr("extra.source6", (void *) &extra_saddr6); |
| } else { |
| util_printaddr("extra.dest4", (void *) &extra_daddr4); |
| util_printaddr("extra.source4", (void *) &extra_saddr4); |
| } |
| |
| } |
| |
| int main(int argc, char **argv) |
| { |
| parse_opts(argc, argv); |
| print_opts(); |
| return do_main(); |
| } |