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