forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
780 lines
18 KiB
780 lines
18 KiB
// 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(); |
|
}
|
|
|