/* Copyright (c) 2016 Facebook * * This program is free software; you can redistribute it and/or * modify it under the terms of version 2 of the GNU General Public * License as published by the Free Software Foundation. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bpf_load.h" #include "libbpf.h" #include "bpf_util.h" #include "xdp_tx_iptunnel_common.h" #define STATS_INTERVAL_S 2U static int ifindex = -1; static __u32 xdp_flags = 0; static void int_exit(int sig) { if (ifindex > -1) set_link_xdp_fd(ifindex, -1, xdp_flags); exit(0); } /* simple per-protocol drop counter */ static void poll_stats(unsigned int kill_after_s) { const unsigned int nr_protos = 256; unsigned int nr_cpus = bpf_num_possible_cpus(); time_t started_at = time(NULL); __u64 values[nr_cpus], prev[nr_protos][nr_cpus]; __u32 proto; int i; memset(prev, 0, sizeof(prev)); while (!kill_after_s || time(NULL) - started_at <= kill_after_s) { sleep(STATS_INTERVAL_S); for (proto = 0; proto < nr_protos; proto++) { __u64 sum = 0; assert(bpf_map_lookup_elem(map_fd[0], &proto, values) == 0); for (i = 0; i < nr_cpus; i++) sum += (values[i] - prev[proto][i]); if (sum) printf("proto %u: sum:%10llu pkts, rate:%10llu pkts/s\n", proto, sum, sum / STATS_INTERVAL_S); memcpy(prev[proto], values, sizeof(values)); } } } static void usage(const char *cmd) { printf("Start a XDP prog which encapsulates incoming packets\n" "in an IPv4/v6 header and XDP_TX it out. The dst \n" "is used to select packets to encapsulate\n\n"); printf("Usage: %s [...]\n", cmd); printf(" -i Interface Index\n"); printf(" -a IPv4 or IPv6\n"); printf(" -p A port range (e.g. 433-444) is also allowed\n"); printf(" -s Used in the IPTunnel header\n"); printf(" -d Used in the IPTunnel header\n"); printf(" -m Used in sending the IP Tunneled pkt\n"); printf(" -T Default: 0 (forever)\n"); printf(" -P Default is TCP\n"); printf(" -S use skb-mode\n"); printf(" -N enforce native mode\n"); printf(" -h Display this help\n"); } static int parse_ipstr(const char *ipstr, unsigned int *addr) { if (inet_pton(AF_INET6, ipstr, addr) == 1) { return AF_INET6; } else if (inet_pton(AF_INET, ipstr, addr) == 1) { addr[1] = addr[2] = addr[3] = 0; return AF_INET; } fprintf(stderr, "%s is an invalid IP\n", ipstr); return AF_UNSPEC; } static int parse_ports(const char *port_str, int *min_port, int *max_port) { char *end; long tmp_min_port; long tmp_max_port; tmp_min_port = strtol(optarg, &end, 10); if (tmp_min_port < 1 || tmp_min_port > 65535) { fprintf(stderr, "Invalid port(s):%s\n", optarg); return 1; } if (*end == '-') { end++; tmp_max_port = strtol(end, NULL, 10); if (tmp_max_port < 1 || tmp_max_port > 65535) { fprintf(stderr, "Invalid port(s):%s\n", optarg); return 1; } } else { tmp_max_port = tmp_min_port; } if (tmp_min_port > tmp_max_port) { fprintf(stderr, "Invalid port(s):%s\n", optarg); return 1; } if (tmp_max_port - tmp_min_port + 1 > MAX_IPTNL_ENTRIES) { fprintf(stderr, "Port range (%s) is larger than %u\n", port_str, MAX_IPTNL_ENTRIES); return 1; } *min_port = tmp_min_port; *max_port = tmp_max_port; return 0; } int main(int argc, char **argv) { unsigned char opt_flags[256] = {}; unsigned int kill_after_s = 0; const char *optstr = "i:a:p:s:d:m:T:P:SNh"; int min_port = 0, max_port = 0; struct iptnl_info tnl = {}; struct rlimit r = {RLIM_INFINITY, RLIM_INFINITY}; struct vip vip = {}; char filename[256]; int opt; int i; tnl.family = AF_UNSPEC; vip.protocol = IPPROTO_TCP; for (i = 0; i < strlen(optstr); i++) if (optstr[i] != 'h' && 'a' <= optstr[i] && optstr[i] <= 'z') opt_flags[(unsigned char)optstr[i]] = 1; while ((opt = getopt(argc, argv, optstr)) != -1) { unsigned short family; unsigned int *v6; switch (opt) { case 'i': ifindex = atoi(optarg); break; case 'a': vip.family = parse_ipstr(optarg, vip.daddr.v6); if (vip.family == AF_UNSPEC) return 1; break; case 'p': if (parse_ports(optarg, &min_port, &max_port)) return 1; break; case 'P': vip.protocol = atoi(optarg); break; case 's': case 'd': if (opt == 's') v6 = tnl.saddr.v6; else v6 = tnl.daddr.v6; family = parse_ipstr(optarg, v6); if (family == AF_UNSPEC) return 1; if (tnl.family == AF_UNSPEC) { tnl.family = family; } else if (tnl.family != family) { fprintf(stderr, "The IP version of the src and dst addresses used in the IP encapsulation does not match\n"); return 1; } break; case 'm': if (!ether_aton_r(optarg, (struct ether_addr *)tnl.dmac)) { fprintf(stderr, "Invalid mac address:%s\n", optarg); return 1; } break; case 'T': kill_after_s = atoi(optarg); break; case 'S': xdp_flags |= XDP_FLAGS_SKB_MODE; break; case 'N': xdp_flags |= XDP_FLAGS_DRV_MODE; break; default: usage(argv[0]); return 1; } opt_flags[opt] = 0; } for (i = 0; i < strlen(optstr); i++) { if (opt_flags[(unsigned int)optstr[i]]) { fprintf(stderr, "Missing argument -%c\n", optstr[i]); usage(argv[0]); return 1; } } if (setrlimit(RLIMIT_MEMLOCK, &r)) { perror("setrlimit(RLIMIT_MEMLOCK, RLIM_INFINITY)"); return 1; } snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]); if (load_bpf_file(filename)) { printf("%s", bpf_log_buf); return 1; } if (!prog_fd[0]) { printf("load_bpf_file: %s\n", strerror(errno)); return 1; } signal(SIGINT, int_exit); signal(SIGTERM, int_exit); while (min_port <= max_port) { vip.dport = htons(min_port++); if (bpf_map_update_elem(map_fd[1], &vip, &tnl, BPF_NOEXIST)) { perror("bpf_map_update_elem(&vip2tnl)"); return 1; } } if (set_link_xdp_fd(ifindex, prog_fd[0], xdp_flags) < 0) { printf("link set xdp fd failed\n"); return 1; } poll_stats(kill_after_s); set_link_xdp_fd(ifindex, -1, xdp_flags); return 0; }