diff options
Diffstat (limited to '')
-rw-r--r-- | xdp-trafficgen/xdp-trafficgen.c | 941 |
1 files changed, 941 insertions, 0 deletions
diff --git a/xdp-trafficgen/xdp-trafficgen.c b/xdp-trafficgen/xdp-trafficgen.c new file mode 100644 index 0000000..293e416 --- /dev/null +++ b/xdp-trafficgen/xdp-trafficgen.c @@ -0,0 +1,941 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#define _GNU_SOURCE +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <signal.h> +#include <errno.h> +#include <stdbool.h> +#include <unistd.h> +#include <fcntl.h> +#include <pthread.h> +#include <sched.h> +#include <net/if.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> + +#include <bpf/bpf.h> +#include <bpf/bpf_endian.h> +#include <bpf/libbpf.h> +#include <xdp/libxdp.h> +#include <xdp/xdp_stats_kern_user.h> +#include <linux/bpf.h> +#include <linux/err.h> +#include <linux/if_link.h> +#include <linux/if_ether.h> +#include <linux/if_packet.h> +#include <linux/ipv6.h> +#include <linux/in6.h> +#include <linux/udp.h> +#include <linux/tcp.h> + +#include "params.h" +#include "logging.h" +#include "util.h" +#include "xdp_sample.h" +#include "xdp-trafficgen.h" + +#include "xdp_trafficgen.skel.h" + +#define PROG_NAME "xdp-trafficgen" + +#ifndef BPF_F_TEST_XDP_LIVE_FRAMES +#define BPF_F_TEST_XDP_LIVE_FRAMES (1U << 1) +#endif + +#define IFINDEX_LO 1 + +static int mask = SAMPLE_DEVMAP_XMIT_CNT_MULTI | SAMPLE_DROP_OK; + +DEFINE_SAMPLE_INIT(xdp_trafficgen); + +static bool status_exited = false; +static bool runners_exited = false; + +struct udp_packet { + struct ethhdr eth; + struct ipv6hdr iph; + struct udphdr udp; + __u8 payload[64 - sizeof(struct udphdr) + - sizeof(struct ethhdr) - sizeof(struct ipv6hdr)]; +} __attribute__((__packed__)); + +static struct udp_packet pkt_udp = { + .eth.h_proto = __bpf_constant_htons(ETH_P_IPV6), + .iph.version = 6, + .iph.nexthdr = IPPROTO_UDP, + .iph.payload_len = bpf_htons(sizeof(struct udp_packet) + - offsetof(struct udp_packet, udp)), + .iph.hop_limit = 1, + .iph.saddr.s6_addr16 = {bpf_htons(0xfe80), 0, 0, 0, 0, 0, 0, bpf_htons(1)}, + .iph.daddr.s6_addr16 = {bpf_htons(0xfe80), 0, 0, 0, 0, 0, 0, bpf_htons(2)}, + .udp.source = bpf_htons(1), + .udp.dest = bpf_htons(1), + .udp.len = bpf_htons(sizeof(struct udp_packet) + - offsetof(struct udp_packet, udp)), +}; + +struct thread_config { + void *pkt; + size_t pkt_size; + __u32 cpu_core_id; + __u32 num_pkts; + __u32 batch_size; + struct xdp_program *prog; +}; + +static int run_prog(const struct thread_config *cfg, bool *status_var) +{ +#ifdef HAVE_LIBBPF_BPF_PROG_TEST_RUN_OPTS + struct xdp_md ctx_in = { + .data_end = cfg->pkt_size, + }; + DECLARE_LIBBPF_OPTS(bpf_test_run_opts, opts, + .data_in = cfg->pkt, + .data_size_in = cfg->pkt_size, + .ctx_in = &ctx_in, + .ctx_size_in = sizeof(ctx_in), + .repeat = cfg->num_pkts ?: 1 << 20, + .flags = BPF_F_TEST_XDP_LIVE_FRAMES, + .batch_size = cfg->batch_size, + ); + __u64 iterations = 0; + cpu_set_t cpu_cores; + int err; + + CPU_ZERO(&cpu_cores); + CPU_SET(cfg->cpu_core_id, &cpu_cores); + pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpu_cores); + do { + err = xdp_program__test_run(cfg->prog, &opts, 0); + if (err) + return -errno; + iterations += opts.repeat; + } while (!*status_var && (!cfg->num_pkts || cfg->num_pkts > iterations)); + + return 0; +#else + __unused const void *c = cfg, *s = status_var; + return -EOPNOTSUPP; +#endif +} + +static void *run_traffic(void *arg) +{ + const struct thread_config *cfg = arg; + int err; + + err = run_prog(cfg, &status_exited); + if (err) + pr_warn("Couldn't run trafficgen program: %s\n", strerror(-err)); + + runners_exited = true; + return NULL; +} + +static int probe_kernel_support(void) +{ + DECLARE_LIBXDP_OPTS(xdp_program_opts, opts); + struct xdp_trafficgen *skel; + struct xdp_program *prog; + int data = 0, err; + bool status = 0; + + skel = xdp_trafficgen__open(); + if (!skel) { + err = -errno; + pr_warn("Couldn't open XDP program: %s\n", strerror(-err)); + return err; + } + + err = sample_init_pre_load(skel, "lo"); + if (err < 0) { + pr_warn("Failed to sample_init_pre_load: %s\n", strerror(-err)); + goto out; + } + + opts.obj = skel->obj; + opts.prog_name = "xdp_drop"; + + prog = xdp_program__create(&opts); + if (!prog) { + err = -errno; + pr_warn("Couldn't load XDP program: %s\n", strerror(-err)); + goto out; + } + + const struct thread_config cfg = { + .pkt = &data, + .pkt_size = sizeof(data), + .num_pkts = 1, + .batch_size = 1, + .prog = prog + }; + err = run_prog(&cfg, &status); + if (err == -EOPNOTSUPP) { + pr_warn("BPF_PROG_RUN with batch size support is missing from libbpf.\n"); + } else if (err == -EINVAL) { + err = -EOPNOTSUPP; + pr_warn("Kernel doesn't support live packet mode for XDP BPF_PROG_RUN.\n"); + } else if (err) { + pr_warn("Error probing kernel support: %s\n", strerror(-err)); + } + + xdp_program__close(prog); +out: + xdp_trafficgen__destroy(skel); + return err; +} + +static int create_runners(pthread_t **runner_threads, struct thread_config **thread_configs, int num_threads, + struct thread_config *tcfg, struct xdp_program *prog) +{ + struct thread_config *t; + pthread_t *threads; + int i, err; + + threads = calloc(sizeof(pthread_t), num_threads); + if (!threads) { + pr_warn("Couldn't allocate memory\n"); + return -ENOMEM; + } + + t = calloc(sizeof(struct thread_config), num_threads); + if (!t) { + pr_warn("Couldn't allocate memory\n"); + free(threads); + return -ENOMEM; + } + + for (i = 0; i < num_threads; i++) { + memcpy(&t[i], tcfg, sizeof(*tcfg)); + tcfg->cpu_core_id++; + + t[i].prog = xdp_program__clone(prog, 0); + err = libxdp_get_error(t[i].prog); + if (err) { + pr_warn("Failed to clone xdp_program: %s\n", strerror(-err)); + t[i].prog = NULL; + goto err; + } + + err = pthread_create(&threads[i], NULL, run_traffic, &t[i]); + if (err < 0) { + pr_warn("Failed to create traffic thread: %s\n", strerror(-err)); + goto err; + } + } + + *runner_threads = threads; + *thread_configs = t; + + return 0; + +err: + for (i = 0; i < num_threads; i++) { + pthread_cancel(threads[i]); + xdp_program__close(t[i].prog); + } + free(t); + free(threads); + + return err; +} + + +static __be16 calc_udp_cksum(const struct udp_packet *pkt) +{ + __u32 chksum = pkt->iph.nexthdr + bpf_ntohs(pkt->iph.payload_len); + int i; + + for (i = 0; i < 8; i++) { + chksum += bpf_ntohs(pkt->iph.saddr.s6_addr16[i]); + chksum += bpf_ntohs(pkt->iph.daddr.s6_addr16[i]); + } + chksum += bpf_ntohs(pkt->udp.source); + chksum += bpf_ntohs(pkt->udp.dest); + chksum += bpf_ntohs(pkt->udp.len); + + while (chksum >> 16) + chksum = (chksum & 0xFFFF) + (chksum >> 16); + return bpf_htons(~chksum); +} + +static const struct udpopt { + __u32 num_pkts; + struct iface iface; + struct mac_addr dst_mac; + struct mac_addr src_mac; + struct ip_addr dst_ip; + struct ip_addr src_ip; + __u16 dst_port; + __u16 src_port; + __u16 dyn_ports; + __u16 threads; + __u16 interval; +} defaults_udp = { + .interval = 1, + .threads = 1, +}; + +static int prepare_udp_pkt(const struct udpopt *cfg) +{ + struct mac_addr src_mac = cfg->src_mac; + int err; + + if (macaddr_is_null(&src_mac)) { + err = get_mac_addr(cfg->iface.ifindex, &src_mac); + if (err) + return err; + } + memcpy(pkt_udp.eth.h_source, &src_mac, sizeof(src_mac)); + if (!macaddr_is_null(&cfg->dst_mac)) + memcpy(pkt_udp.eth.h_dest, &cfg->dst_mac, sizeof(cfg->dst_mac)); + + if (!ipaddr_is_null(&cfg->src_ip)) { + if (cfg->src_ip.af != AF_INET6) { + pr_warn("Only IPv6 is supported\n"); + return 1; + } + pkt_udp.iph.saddr = cfg->src_ip.addr.addr6; + } + + if (!ipaddr_is_null(&cfg->dst_ip)) { + if (cfg->dst_ip.af != AF_INET6) { + pr_warn("Only IPv6 is supported\n"); + return 1; + } + pkt_udp.iph.daddr = cfg->dst_ip.addr.addr6; + } + + if (cfg->src_port) + pkt_udp.udp.source = bpf_htons(cfg->src_port); + if (cfg->dst_port) + pkt_udp.udp.dest = bpf_htons(cfg->dst_port); + pkt_udp.udp.check = calc_udp_cksum(&pkt_udp); + return 0; +} + +static struct prog_option udp_options[] = { + DEFINE_OPTION("dst-mac", OPT_MACADDR, struct udpopt, dst_mac, + .short_opt = 'm', + .metavar = "<mac addr>", + .help = "Destination MAC address of generated packets"), + DEFINE_OPTION("src-mac", OPT_MACADDR, struct udpopt, src_mac, + .short_opt = 'M', + .metavar = "<mac addr>", + .help = "Source MAC address of generated packets"), + DEFINE_OPTION("dst-addr", OPT_IPADDR, struct udpopt, dst_ip, + .short_opt = 'a', + .metavar = "<addr>", + .help = "Destination IP address of generated packets"), + DEFINE_OPTION("src-addr", OPT_IPADDR, struct udpopt, src_ip, + .short_opt = 'A', + .metavar = "<addr>", + .help = "Source IP address of generated packets"), + DEFINE_OPTION("dst-port", OPT_U16, struct udpopt, dst_port, + .short_opt = 'p', + .metavar = "<port>", + .help = "Destination port of generated packets"), + DEFINE_OPTION("src-port", OPT_U16, struct udpopt, src_port, + .short_opt = 'P', + .metavar = "<port>", + .help = "Source port of generated packets"), + DEFINE_OPTION("dyn-ports", OPT_U16, struct udpopt, dyn_ports, + .short_opt = 'd', + .metavar = "<num ports>", + .help = "Dynamically vary destination port over a range of <num ports>"), + DEFINE_OPTION("num-packets", OPT_U32, struct udpopt, num_pkts, + .short_opt = 'n', + .metavar = "<port>", + .help = "Number of packets to send"), + DEFINE_OPTION("threads", OPT_U16, struct udpopt, threads, + .short_opt = 't', + .metavar = "<threads>", + .help = "Number of simultaneous threads to transmit from"), + DEFINE_OPTION("interval", OPT_U16, struct udpopt, interval, + .short_opt = 'I', + .metavar = "<s>", + .help = "Output statistics with this interval"), + DEFINE_OPTION("interface", OPT_IFNAME, struct udpopt, iface, + .positional = true, + .metavar = "<ifname>", + .required = true, + .help = "Load on device <ifname>"), + END_OPTIONS +}; + +int do_udp(const void *opt, __unused const char *pin_root_path) +{ + const struct udpopt *cfg = opt; + + DECLARE_LIBXDP_OPTS(xdp_program_opts, opts); + struct thread_config *t = NULL, tcfg = { + .pkt = &pkt_udp, + .pkt_size = sizeof(pkt_udp), + .num_pkts = cfg->num_pkts, + }; + struct trafficgen_state bpf_state = {}; + struct xdp_trafficgen *skel = NULL; + pthread_t *runner_threads = NULL; + struct xdp_program *prog = NULL; + int err = 0, i; + char buf[100]; + __u32 key = 0; + + err = probe_kernel_support(); + if (err) + return err; + + err = prepare_udp_pkt(cfg); + if (err) + goto out; + + skel = xdp_trafficgen__open(); + if (!skel) { + err = -errno; + pr_warn("Couldn't open XDP program: %s\n", strerror(-err)); + goto out; + } + + err = sample_init_pre_load(skel, cfg->iface.ifname); + if (err < 0) { + pr_warn("Failed to sample_init_pre_load: %s\n", strerror(-err)); + goto out; + } + + skel->rodata->config.port_start = cfg->dst_port; + skel->rodata->config.port_range = cfg->dyn_ports; + skel->rodata->config.ifindex_out = cfg->iface.ifindex; + bpf_state.next_port = cfg->dst_port; + + if (cfg->dyn_ports) + opts.prog_name = "xdp_redirect_update_port"; + else + opts.prog_name = "xdp_redirect_notouch"; + opts.obj = skel->obj; + + prog = xdp_program__create(&opts); + if (!prog) { + err = -errno; + libxdp_strerror(err, buf, sizeof(buf)); + pr_warn("Couldn't open BPF file: %s\n", buf); + goto out; + } + + err = xdp_trafficgen__load(skel); + if (err) + goto out; + + err = bpf_map_update_elem(bpf_map__fd(skel->maps.state_map), + &key, &bpf_state, BPF_EXIST); + if (err) { + err = -errno; + pr_warn("Couldn't set initial state map value: %s\n", strerror(-err)); + goto out; + } + + err = sample_init(skel, mask, IFINDEX_LO, cfg->iface.ifindex); + if (err < 0) { + pr_warn("Failed to initialize sample: %s\n", strerror(-err)); + goto out; + } + + err = create_runners(&runner_threads, &t, cfg->threads, &tcfg, prog); + if (err) + goto out; + + pr_info("Transmitting on %s (ifindex %d)\n", + cfg->iface.ifname, cfg->iface.ifindex); + + err = sample_run(cfg->interval, NULL, NULL); + status_exited = true; + + for (i = 0; i < cfg->threads; i++) { + pthread_join(runner_threads[i], NULL); + xdp_program__close(t[i].prog); + } + +out: + xdp_program__close(prog); + xdp_trafficgen__destroy(skel); + free(runner_threads); + free(t); + return err; +} + +struct tcp_packet { + struct ethhdr eth; + struct ipv6hdr iph; + struct tcphdr tcp; + __u8 payload[1500 - sizeof(struct tcphdr) + - sizeof(struct ethhdr) - sizeof(struct ipv6hdr)]; +} __attribute__((__packed__)); + +static __unused struct tcp_packet pkt_tcp = { + .eth.h_proto = __bpf_constant_htons(ETH_P_IPV6), + .iph.version = 6, + .iph.nexthdr = IPPROTO_TCP, + .iph.payload_len = bpf_htons(sizeof(struct tcp_packet) + - offsetof(struct tcp_packet, tcp)), + .iph.hop_limit = 64, + .iph.saddr.s6_addr16 = {bpf_htons(0xfe80), 0, 0, 0, 0, 0, 0, bpf_htons(1)}, + .iph.daddr.s6_addr16 = {bpf_htons(0xfe80), 0, 0, 0, 0, 0, 0, bpf_htons(2)}, + .tcp.source = bpf_htons(1), + .tcp.dest = bpf_htons(1), + .tcp.window = bpf_htons(0x100), + .tcp.doff = 5, + .tcp.ack = 1, +}; + +static void hexdump_data(void *data, int size) +{ + unsigned char *ptr = data; + int i; + for (i = 0; i < size; i++) { + if (i % 16 == 0) + pr_debug("\n%06X: ", i); + else if (i % 2 == 0) + pr_debug(" "); + pr_debug("%02X", *ptr++); + } + pr_debug("\n"); +} + +static __be16 calc_tcp_cksum(const struct tcp_packet *pkt) +{ + __u32 chksum = bpf_htons(pkt->iph.nexthdr) + pkt->iph.payload_len; + int payload_len = sizeof(pkt->payload); + struct tcphdr tcph_ = pkt->tcp; + __u16 *ptr = (void *)&tcph_; + int i; + + tcph_.check = 0; + + for (i = 0; i < 8; i++) { + chksum += pkt->iph.saddr.s6_addr16[i]; + chksum += pkt->iph.daddr.s6_addr16[i]; + } + for (i = 0; i < 10; i++) + chksum += *(ptr++); + + ptr = (void *)&pkt->payload; + for (i = 0; i < payload_len / 2; i++) + chksum += *(ptr++); + + if (payload_len % 2) + chksum += (*((__u8 *)ptr)) << 8; + + while (chksum >> 16) + chksum = (chksum & 0xFFFF) + (chksum >> 16); + + return ~chksum; +} + +static void prepare_tcp_pkt(const struct tcp_flowkey *fkey, + const struct tcp_flowstate *fstate) +{ + memcpy(pkt_tcp.eth.h_source, fstate->src_mac, ETH_ALEN); + memcpy(pkt_tcp.eth.h_dest, fstate->dst_mac, ETH_ALEN); + + pkt_tcp.iph.saddr = fkey->src_ip; + pkt_tcp.iph.daddr = fkey->dst_ip; + pkt_tcp.tcp.source = fkey->src_port; + pkt_tcp.tcp.dest = fkey->dst_port; + pkt_tcp.tcp.seq = bpf_htonl(fstate->seq); + pkt_tcp.tcp.ack_seq = bpf_htonl(fstate->rcv_seq); + + pkt_tcp.tcp.check = calc_tcp_cksum(&pkt_tcp); + pr_debug("TCP packet:\n"); + hexdump_data(&pkt_tcp, sizeof(pkt_tcp)); +} + +struct enum_val xdp_modes[] = { + {"native", XDP_MODE_NATIVE}, + {"skb", XDP_MODE_SKB}, + {"hw", XDP_MODE_HW}, + {NULL, 0} +}; + +static const struct tcpopt { + __u32 num_pkts; + struct iface iface; + char *dst_addr; + __u16 dst_port; + __u16 interval; + __u16 timeout; + enum xdp_attach_mode mode; +} defaults_tcp = { + .interval = 1, + .dst_port = 10000, + .timeout = 2, + .mode = XDP_MODE_NATIVE, +}; + +static struct prog_option tcp_options[] = { + DEFINE_OPTION("dst-port", OPT_U16, struct tcpopt, dst_port, + .short_opt = 'p', + .metavar = "<port>", + .help = "Connect to destination <port>. Default 10000"), + DEFINE_OPTION("num-packets", OPT_U32, struct tcpopt, num_pkts, + .short_opt = 'n', + .metavar = "<port>", + .help = "Number of packets to send"), + DEFINE_OPTION("interval", OPT_U16, struct tcpopt, interval, + .short_opt = 'I', + .metavar = "<s>", + .help = "Output statistics with this interval"), + DEFINE_OPTION("timeout", OPT_U16, struct tcpopt, timeout, + .short_opt = 't', + .metavar = "<s>", + .help = "TCP connect timeout (default 2 seconds)."), + DEFINE_OPTION("interface", OPT_IFNAME, struct tcpopt, iface, + .metavar = "<ifname>", + .required = true, + .short_opt = 'i', + .help = "Connect through device <ifname>"), + DEFINE_OPTION("mode", OPT_ENUM, struct tcpopt, mode, + .short_opt = 'm', + .typearg = xdp_modes, + .metavar = "<mode>", + .help = "Load ingress XDP program in <mode>; default native"), + DEFINE_OPTION("dst-addr", OPT_STRING, struct tcpopt, dst_addr, + .positional = true, + .required = true, + .metavar = "<hostname>", + .help = "Destination host of generated stream"), + END_OPTIONS +}; + +int do_tcp(const void *opt, __unused const char *pin_root_path) +{ + const struct tcpopt *cfg = opt; + + struct addrinfo *ai = NULL, hints = { + .ai_family = AF_INET6, + .ai_socktype = SOCK_STREAM, + .ai_protocol = IPPROTO_TCP, + }; + struct ip_addr local_addr = { .af = AF_INET6 }, remote_addr = { .af = AF_INET6 }; + struct bpf_map *state_map = NULL, *fstate_map; + DECLARE_LIBXDP_OPTS(xdp_program_opts, opts, + .prog_name = "xdp_handle_tcp_recv"); + struct xdp_program *ifindex_prog = NULL, *test_prog = NULL; + struct sockaddr_in6 local_saddr = {}, *addr6; + struct thread_config *t = NULL, tcfg = { + .pkt = &pkt_tcp, + .pkt_size = sizeof(pkt_tcp), + .num_pkts = cfg->num_pkts, + }; + struct trafficgen_state bpf_state = {}; + struct xdp_trafficgen *skel = NULL; + char buf_local[50], buf_remote[50]; + pthread_t *runner_threads = NULL; + socklen_t sockaddr_sz, tcpi_sz; + __u16 local_port, remote_port; + int sock = -1, err = -EINVAL; + struct tcp_flowstate fstate; + struct timeval timeout = { + .tv_sec = cfg->timeout, + }; + struct tcp_info tcpi = {}; + bool attached = false; + __u16 num_threads = 1; + __u32 key = 0; + char port[6]; + int i, sopt; + + err = probe_kernel_support(); + if (err) + return err; + + skel = xdp_trafficgen__open(); + if (!skel) { + err = -errno; + pr_warn("Couldn't open XDP program: %s\n", strerror(-err)); + goto out; + } + + err = sample_init_pre_load(skel, cfg->iface.ifname); + if (err < 0) { + pr_warn("Failed to sample_init_pre_load: %s\n", strerror(-err)); + goto out; + } + + opts.obj = skel->obj; + skel->rodata->config.ifindex_out = cfg->iface.ifindex; + + snprintf(port, sizeof(port), "%d", cfg->dst_port); + + err = getaddrinfo(cfg->dst_addr, port, &hints, &ai); + if (err) { + pr_warn("Couldn't resolve hostname: %s\n", gai_strerror(err)); + goto out; + } + + addr6 = (struct sockaddr_in6* )ai->ai_addr; + remote_addr.addr.addr6 = addr6->sin6_addr; + remote_port = bpf_ntohs(addr6->sin6_port); + + bpf_state.flow_key.dst_port = addr6->sin6_port; + bpf_state.flow_key.dst_ip = addr6->sin6_addr; + + print_addr(buf_remote, sizeof(buf_remote), &remote_addr); + + ifindex_prog = xdp_program__create(&opts); + if (!ifindex_prog) { + err = -errno; + pr_warn("Couldn't open XDP program: %s\n", strerror(-err)); + goto out; + } + + opts.prog_name = "xdp_redirect_send_tcp"; + test_prog = xdp_program__create(&opts); + if (!test_prog) { + err = -errno; + pr_warn("Couldn't find test program: %s\n", strerror(-err)); + goto out; + } + + state_map = skel->maps.state_map; + fstate_map = skel->maps.flow_state_map; + + if (!fstate_map) { + pr_warn("Couldn't find BPF maps\n"); + goto out; + } + + err = xdp_program__attach(ifindex_prog, cfg->iface.ifindex, cfg->mode, 0); + if (err) { + err = -errno; + pr_warn("Couldn't attach XDP program to iface '%s': %s\n", + cfg->iface.ifname, strerror(-err)); + goto out; + } + attached = true; + + err = bpf_map_update_elem(bpf_map__fd(state_map), + &key, &bpf_state, BPF_EXIST); + + if (err) { + err = -errno; + pr_warn("Couldn't set initial state map value: %s\n", strerror(-err)); + goto out; + } + + err = sample_init(skel, mask, IFINDEX_LO, cfg->iface.ifindex); + if (err < 0) { + pr_warn("Failed to initialize sample: %s\n", strerror(-err)); + goto out; + } + + sock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); + if (sock < 0) { + err = -errno; + pr_warn("Couldn't open TCP socket: %s\n", strerror(-err)); + goto out; + } + + err = setsockopt(sock, SOL_SOCKET, SO_BINDTOIFINDEX, + &cfg->iface.ifindex, sizeof(cfg->iface.ifindex)); + if (err) { + err = -errno; + pr_warn("Couldn't bind to device '%s': %s\n", cfg->iface.ifname, strerror(-err)); + goto out; + } + + sopt = fcntl(sock, F_GETFL, NULL); + if (sopt < 0) { + err = -errno; + pr_warn("Couldn't get socket opts: %s\n", strerror(-err)); + goto out; + } + + err = fcntl(sock, F_SETFL, sopt | O_NONBLOCK); + if (err) { + err = -errno; + pr_warn("Couldn't set socket non-blocking: %s\n", strerror(-err)); + goto out; + } + + err = connect(sock, ai->ai_addr, ai->ai_addrlen); + if (err && errno == EINPROGRESS) { + fd_set wait; + + FD_ZERO(&wait); + FD_SET(sock, &wait); + + err = select(sock + 1, NULL, &wait, NULL, &timeout); + if (!err) { + err = -1; + errno = ETIMEDOUT; + } else if (err > 0) { + err = 0; + } + } + if (err) { + err = -errno; + pr_warn("Couldn't connect to destination: %s\n", strerror(-err)); + goto out; + } + + err = fcntl(sock, F_SETFL, sopt); + if (err) { + err = -errno; + pr_warn("Couldn't reset socket opts: %s\n", strerror(-err)); + goto out; + } + + sockaddr_sz = sizeof(local_saddr); + err = getsockname(sock, &local_saddr, &sockaddr_sz); + if (err) { + err = -errno; + pr_warn("Couldn't get local address: %s\n", strerror(-err)); + goto out; + } + + local_addr.addr.addr6 = local_saddr.sin6_addr; + local_port = bpf_htons(local_saddr.sin6_port); + print_addr(buf_local, sizeof(buf_local), &local_addr); + + printf("Connected to %s port %d from %s port %d\n", + buf_remote, remote_port, buf_local, local_port); + + bpf_state.flow_key.src_port = local_saddr.sin6_port; + bpf_state.flow_key.src_ip = local_saddr.sin6_addr; + + tcpi_sz = sizeof(tcpi); + err = getsockopt(sock, IPPROTO_TCP, TCP_INFO, &tcpi, &tcpi_sz); + if (err) { + err = -errno; + pr_warn("Couldn't get TCP_INFO for socket: %s\n", strerror(-err)); + goto out; + } + + err = bpf_map_lookup_elem(bpf_map__fd(fstate_map), + &bpf_state.flow_key, &fstate); + if (err) { + err = -errno; + pr_warn("Couldn't find flow state in map: %s\n", strerror(-err)); + goto out; + } + + if (tcpi.tcpi_snd_wnd != fstate.window) { + pr_warn("TCP_INFO and packet data disagree on window (%u != %u)\n", + tcpi.tcpi_snd_wnd, fstate.window); + } + + fstate.wscale = tcpi.tcpi_rcv_wscale; + fstate.flow_state = FLOW_STATE_RUNNING; + err = bpf_map_update_elem(bpf_map__fd(fstate_map), + &bpf_state.flow_key, &fstate, BPF_EXIST); + if (err) { + err = -errno; + pr_warn("Couldn't update flow state map: %s\n", strerror(-err)); + goto out; + } + + err = bpf_map_update_elem(bpf_map__fd(state_map), + &key, &bpf_state, BPF_EXIST); + if (err) { + err = -errno; + pr_warn("Couldn't update program state map: %s\n", strerror(-err)); + goto out; + } + + prepare_tcp_pkt(&bpf_state.flow_key, &fstate); + + err = create_runners(&runner_threads, &t, num_threads, &tcfg, test_prog); + if (err) + goto out; + + err = sample_run(cfg->interval, NULL, NULL); + status_exited = true; + for (i = 0; i < num_threads; i++) { + pthread_join(runner_threads[i], NULL); + xdp_program__close(t[i].prog); + } + + /* send 3 RSTs with 200ms interval to kill the other side of the connection */ + for (i = 0; i < 3; i++) { + usleep(200000); + + pkt_tcp.tcp.rst = 1; + pkt_tcp.iph.payload_len = bpf_htons(sizeof(struct tcphdr)); + pkt_tcp.tcp.check = calc_tcp_cksum(&pkt_tcp); + tcfg.cpu_core_id = 0; + tcfg.num_pkts = 1; + tcfg.pkt_size = offsetof(struct tcp_packet, payload); + tcfg.prog = test_prog; + run_traffic(&tcfg); + } + +out: + if (ai) + freeaddrinfo(ai); + if (sock >= 0) + close(sock); + if (attached) + xdp_program__detach(ifindex_prog, cfg->iface.ifindex, cfg->mode, 0); + + xdp_program__close(ifindex_prog); + xdp_program__close(test_prog); + + xdp_trafficgen__destroy(skel); + + free(runner_threads); + free(t); + return err; +} + +static const struct probeopt { +} defaults_probe = {}; + +static struct prog_option probe_options[] = {}; + +int do_probe(__unused const void *cfg, __unused const char *pin_root_path) +{ + int err = probe_kernel_support(); + + if (!err) + pr_info("Kernel supports live packet mode for XDP BPF_PROG_RUN.\n"); + return err; +} + +int do_help(__unused const void *cfg, __unused const char *pin_root_path) +{ + fprintf(stderr, + "Usage: xdp-trafficgen COMMAND [options]\n" + "\n" + "COMMAND can be one of:\n" + " udp - run in UDP mode\n" + " tcp - run in TCP mode\n" + " help - show this help message\n" + "\n" + "Use 'xdp-trafficgen COMMAND --help' to see options for each command\n"); + return -1; +} + +static const struct prog_command cmds[] = { + DEFINE_COMMAND(udp, "Run in UDP mode"), + DEFINE_COMMAND(tcp, "Run in TCP mode"), + DEFINE_COMMAND(probe, "Probe kernel support"), + { .name = "help", .func = do_help, .no_cfg = true }, + END_COMMANDS +}; + +union all_opts { + struct udpopt udp; +}; + +int main(int argc, char **argv) +{ + if (argc > 1) + return dispatch_commands(argv[1], argc - 1, argv + 1, cmds, + sizeof(union all_opts), PROG_NAME, false); + + return do_help(NULL, NULL); +} |