// SPDX-License-Identifier: LGPL-2.1 OR BSD-2-Clause /* Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. */ #include <stdnoreturn.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <getopt.h> #include <signal.h> #include <sys/types.h> #include <bpf/bpf.h> #include <bpf/libbpf.h> #include <net/if.h> #include <linux/if_link.h> #include <linux/limits.h> static unsigned int ifindex; static __u32 attached_prog_id; static bool attached_tc; static void noreturn cleanup(int sig) { LIBBPF_OPTS(bpf_xdp_attach_opts, opts); int prog_fd; int err; if (attached_prog_id == 0) exit(0); if (attached_tc) { LIBBPF_OPTS(bpf_tc_hook, hook, .ifindex = ifindex, .attach_point = BPF_TC_INGRESS); err = bpf_tc_hook_destroy(&hook); if (err < 0) { fprintf(stderr, "Error: bpf_tc_hook_destroy: %s\n", strerror(-err)); fprintf(stderr, "Failed to destroy the TC hook\n"); exit(1); } exit(0); } prog_fd = bpf_prog_get_fd_by_id(attached_prog_id); if (prog_fd < 0) { fprintf(stderr, "Error: bpf_prog_get_fd_by_id: %s\n", strerror(-prog_fd)); err = bpf_xdp_attach(ifindex, -1, 0, NULL); if (err < 0) { fprintf(stderr, "Error: bpf_set_link_xdp_fd: %s\n", strerror(-err)); fprintf(stderr, "Failed to detach XDP program\n"); exit(1); } } else { opts.old_prog_fd = prog_fd; err = bpf_xdp_attach(ifindex, -1, XDP_FLAGS_REPLACE, &opts); close(prog_fd); if (err < 0) { fprintf(stderr, "Error: bpf_set_link_xdp_fd_opts: %s\n", strerror(-err)); /* Not an error if already replaced by someone else. */ if (err != -EEXIST) { fprintf(stderr, "Failed to detach XDP program\n"); exit(1); } } } exit(0); } static noreturn void usage(const char *progname) { fprintf(stderr, "Usage: %s [--iface <iface>|--prog <prog_id>] [--mss4 <mss ipv4> --mss6 <mss ipv6> --wscale <wscale> --ttl <ttl>] [--ports <port1>,<port2>,...] [--single] [--tc]\n", progname); exit(1); } static unsigned long parse_arg_ul(const char *progname, const char *arg, unsigned long limit) { unsigned long res; char *endptr; errno = 0; res = strtoul(arg, &endptr, 10); if (errno != 0 || *endptr != '\0' || arg[0] == '\0' || res > limit) usage(progname); return res; } static void parse_options(int argc, char *argv[], unsigned int *ifindex, __u32 *prog_id, __u64 *tcpipopts, char **ports, bool *single, bool *tc) { static struct option long_options[] = { { "help", no_argument, NULL, 'h' }, { "iface", required_argument, NULL, 'i' }, { "prog", required_argument, NULL, 'x' }, { "mss4", required_argument, NULL, 4 }, { "mss6", required_argument, NULL, 6 }, { "wscale", required_argument, NULL, 'w' }, { "ttl", required_argument, NULL, 't' }, { "ports", required_argument, NULL, 'p' }, { "single", no_argument, NULL, 's' }, { "tc", no_argument, NULL, 'c' }, { NULL, 0, NULL, 0 }, }; unsigned long mss4, wscale, ttl; unsigned long long mss6; unsigned int tcpipopts_mask = 0; if (argc < 2) usage(argv[0]); *ifindex = 0; *prog_id = 0; *tcpipopts = 0; *ports = NULL; *single = false; *tc = false; while (true) { int opt; opt = getopt_long(argc, argv, "", long_options, NULL); if (opt == -1) break; switch (opt) { case 'h': usage(argv[0]); break; case 'i': *ifindex = if_nametoindex(optarg); if (*ifindex == 0) usage(argv[0]); break; case 'x': *prog_id = parse_arg_ul(argv[0], optarg, UINT32_MAX); if (*prog_id == 0) usage(argv[0]); break; case 4: mss4 = parse_arg_ul(argv[0], optarg, UINT16_MAX); tcpipopts_mask |= 1 << 0; break; case 6: mss6 = parse_arg_ul(argv[0], optarg, UINT16_MAX); tcpipopts_mask |= 1 << 1; break; case 'w': wscale = parse_arg_ul(argv[0], optarg, 14); tcpipopts_mask |= 1 << 2; break; case 't': ttl = parse_arg_ul(argv[0], optarg, UINT8_MAX); tcpipopts_mask |= 1 << 3; break; case 'p': *ports = optarg; break; case 's': *single = true; break; case 'c': *tc = true; break; default: usage(argv[0]); } } if (optind < argc) usage(argv[0]); if (tcpipopts_mask == 0xf) { if (mss4 == 0 || mss6 == 0 || wscale == 0 || ttl == 0) usage(argv[0]); *tcpipopts = (mss6 << 32) | (ttl << 24) | (wscale << 16) | mss4; } else if (tcpipopts_mask != 0) { usage(argv[0]); } if (*ifindex != 0 && *prog_id != 0) usage(argv[0]); if (*ifindex == 0 && *prog_id == 0) usage(argv[0]); } static int syncookie_attach(const char *argv0, unsigned int ifindex, bool tc) { struct bpf_prog_info info = {}; __u32 info_len = sizeof(info); char xdp_filename[PATH_MAX]; struct bpf_program *prog; struct bpf_object *obj; int prog_fd; int err; snprintf(xdp_filename, sizeof(xdp_filename), "%s_kern.bpf.o", argv0); obj = bpf_object__open_file(xdp_filename, NULL); err = libbpf_get_error(obj); if (err < 0) { fprintf(stderr, "Error: bpf_object__open_file: %s\n", strerror(-err)); return err; } err = bpf_object__load(obj); if (err < 0) { fprintf(stderr, "Error: bpf_object__open_file: %s\n", strerror(-err)); return err; } prog = bpf_object__find_program_by_name(obj, tc ? "syncookie_tc" : "syncookie_xdp"); if (!prog) { fprintf(stderr, "Error: bpf_object__find_program_by_name: program was not found\n"); return -ENOENT; } prog_fd = bpf_program__fd(prog); err = bpf_prog_get_info_by_fd(prog_fd, &info, &info_len); if (err < 0) { fprintf(stderr, "Error: bpf_prog_get_info_by_fd: %s\n", strerror(-err)); goto out; } attached_tc = tc; attached_prog_id = info.id; signal(SIGINT, cleanup); signal(SIGTERM, cleanup); if (tc) { LIBBPF_OPTS(bpf_tc_hook, hook, .ifindex = ifindex, .attach_point = BPF_TC_INGRESS); LIBBPF_OPTS(bpf_tc_opts, opts, .handle = 1, .priority = 1, .prog_fd = prog_fd); err = bpf_tc_hook_create(&hook); if (err < 0) { fprintf(stderr, "Error: bpf_tc_hook_create: %s\n", strerror(-err)); goto fail; } err = bpf_tc_attach(&hook, &opts); if (err < 0) { fprintf(stderr, "Error: bpf_tc_attach: %s\n", strerror(-err)); goto fail; } } else { err = bpf_xdp_attach(ifindex, prog_fd, XDP_FLAGS_UPDATE_IF_NOEXIST, NULL); if (err < 0) { fprintf(stderr, "Error: bpf_set_link_xdp_fd: %s\n", strerror(-err)); goto fail; } } err = 0; out: bpf_object__close(obj); return err; fail: signal(SIGINT, SIG_DFL); signal(SIGTERM, SIG_DFL); attached_prog_id = 0; goto out; } static int syncookie_open_bpf_maps(__u32 prog_id, int *values_map_fd, int *ports_map_fd) { struct bpf_prog_info prog_info; __u32 map_ids[8]; __u32 info_len; int prog_fd; int err; int i; *values_map_fd = -1; *ports_map_fd = -1; prog_fd = bpf_prog_get_fd_by_id(prog_id); if (prog_fd < 0) { fprintf(stderr, "Error: bpf_prog_get_fd_by_id: %s\n", strerror(-prog_fd)); return prog_fd; } prog_info = (struct bpf_prog_info) { .nr_map_ids = 8, .map_ids = (__u64)(unsigned long)map_ids, }; info_len = sizeof(prog_info); err = bpf_prog_get_info_by_fd(prog_fd, &prog_info, &info_len); if (err != 0) { fprintf(stderr, "Error: bpf_prog_get_info_by_fd: %s\n", strerror(-err)); goto out; } if (prog_info.nr_map_ids < 2) { fprintf(stderr, "Error: Found %u BPF maps, expected at least 2\n", prog_info.nr_map_ids); err = -ENOENT; goto out; } for (i = 0; i < prog_info.nr_map_ids; i++) { struct bpf_map_info map_info = {}; int map_fd; err = bpf_map_get_fd_by_id(map_ids[i]); if (err < 0) { fprintf(stderr, "Error: bpf_map_get_fd_by_id: %s\n", strerror(-err)); goto err_close_map_fds; } map_fd = err; info_len = sizeof(map_info); err = bpf_map_get_info_by_fd(map_fd, &map_info, &info_len); if (err != 0) { fprintf(stderr, "Error: bpf_map_get_info_by_fd: %s\n", strerror(-err)); close(map_fd); goto err_close_map_fds; } if (strcmp(map_info.name, "values") == 0) { *values_map_fd = map_fd; continue; } if (strcmp(map_info.name, "allowed_ports") == 0) { *ports_map_fd = map_fd; continue; } close(map_fd); } if (*values_map_fd != -1 && *ports_map_fd != -1) { err = 0; goto out; } err = -ENOENT; err_close_map_fds: if (*values_map_fd != -1) close(*values_map_fd); if (*ports_map_fd != -1) close(*ports_map_fd); *values_map_fd = -1; *ports_map_fd = -1; out: close(prog_fd); return err; } int main(int argc, char *argv[]) { int values_map_fd, ports_map_fd; __u64 tcpipopts; bool firstiter; __u64 prevcnt; __u32 prog_id; char *ports; bool single; int err = 0; bool tc; parse_options(argc, argv, &ifindex, &prog_id, &tcpipopts, &ports, &single, &tc); if (prog_id == 0) { if (!tc) { err = bpf_xdp_query_id(ifindex, 0, &prog_id); if (err < 0) { fprintf(stderr, "Error: bpf_get_link_xdp_id: %s\n", strerror(-err)); goto out; } } if (prog_id == 0) { err = syncookie_attach(argv[0], ifindex, tc); if (err < 0) goto out; prog_id = attached_prog_id; } } err = syncookie_open_bpf_maps(prog_id, &values_map_fd, &ports_map_fd); if (err < 0) goto out; if (ports) { __u16 port_last = 0; __u32 port_idx = 0; char *p = ports; fprintf(stderr, "Replacing allowed ports\n"); while (p && *p != '\0') { char *token = strsep(&p, ","); __u16 port; port = parse_arg_ul(argv[0], token, UINT16_MAX); err = bpf_map_update_elem(ports_map_fd, &port_idx, &port, BPF_ANY); if (err != 0) { fprintf(stderr, "Error: bpf_map_update_elem: %s\n", strerror(-err)); fprintf(stderr, "Failed to add port %u (index %u)\n", port, port_idx); goto out_close_maps; } fprintf(stderr, "Added port %u\n", port); port_idx++; } err = bpf_map_update_elem(ports_map_fd, &port_idx, &port_last, BPF_ANY); if (err != 0) { fprintf(stderr, "Error: bpf_map_update_elem: %s\n", strerror(-err)); fprintf(stderr, "Failed to add the terminator value 0 (index %u)\n", port_idx); goto out_close_maps; } } if (tcpipopts) { __u32 key = 0; fprintf(stderr, "Replacing TCP/IP options\n"); err = bpf_map_update_elem(values_map_fd, &key, &tcpipopts, BPF_ANY); if (err != 0) { fprintf(stderr, "Error: bpf_map_update_elem: %s\n", strerror(-err)); goto out_close_maps; } } if ((ports || tcpipopts) && attached_prog_id == 0 && !single) goto out_close_maps; prevcnt = 0; firstiter = true; while (true) { __u32 key = 1; __u64 value; err = bpf_map_lookup_elem(values_map_fd, &key, &value); if (err != 0) { fprintf(stderr, "Error: bpf_map_lookup_elem: %s\n", strerror(-err)); goto out_close_maps; } if (firstiter) { prevcnt = value; firstiter = false; } if (single) { printf("Total SYNACKs generated: %llu\n", value); break; } printf("SYNACKs generated: %llu (total %llu)\n", value - prevcnt, value); prevcnt = value; sleep(1); } out_close_maps: close(values_map_fd); close(ports_map_fd); out: return err == 0 ? 0 : 1; }