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