diff options
Diffstat (limited to 'lib/util/params.c')
-rw-r--r-- | lib/util/params.c | 808 |
1 files changed, 808 insertions, 0 deletions
diff --git a/lib/util/params.c b/lib/util/params.c new file mode 100644 index 0000000..838a520 --- /dev/null +++ b/lib/util/params.c @@ -0,0 +1,808 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#define _GNU_SOURCE + +#include <stddef.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <stdbool.h> +#include <getopt.h> +#include <errno.h> + +#include <net/if.h> +#include <linux/if_ether.h> +#include <linux/if_link.h> /* XDP_FLAGS_* depend on kernel-headers installed */ +#include <linux/if_xdp.h> +#include <arpa/inet.h> + +#include "params.h" +#include "logging.h" +#include "util.h" + +#define BUFSIZE 30 +#define FIRST_PRINTABLE 65 /* ord('A') = 65 */ +#define VERSION_SHORT_OPT 0 + +static bool opt_needs_arg(const struct prog_option *opt) +{ + return opt->type > OPT_BOOL && !opt->positional; +} + +static bool opt_is_multi(const struct prog_option *opt) +{ + return opt->type == OPT_MULTISTRING || opt->type == OPT_IFNAME_MULTI || + opt->type == OPT_U32_MULTI; +} + +static int handle_bool(__unused char *optarg, void *tgt, __unused struct prog_option *opt) +{ + bool *opt_set = tgt; + + *opt_set = true; + return 0; +} + +static int handle_string(char *optarg, void *tgt, __unused struct prog_option *opt) +{ + char **opt_set = tgt; + + *opt_set = optarg; + return 0; +} + +static int handle_multistring(char *optarg, void *tgt, __unused struct prog_option *opt) +{ + struct multistring *opt_set = tgt; + void *ptr; + + if (opt_set->num_strings +1 > SIZE_MAX / sizeof(*opt_set->strings)) + return -ENOMEM; + + ptr = realloc(opt_set->strings, sizeof(*opt_set->strings) * (opt_set->num_strings +1)); + + if (!ptr) + return -errno; + + opt_set->strings = ptr; + opt_set->strings[opt_set->num_strings++] = optarg; + return 0; +} + +static int handle_u32(char *optarg, void *tgt, __unused struct prog_option *opt) +{ + __u32 *opt_set = tgt; + unsigned long val; + + errno = 0; + val = strtoul(optarg, NULL, 10); + if (errno || val > 0xffffffff) + return -EINVAL; + + *opt_set = val; + return 0; +} + +static int handle_u32_multi(char *optarg, void *tgt, struct prog_option *opt) +{ + struct u32_multi *opt_set = tgt; + __u32 val; + void *ptr; + int ret; + + if (opt_set->num_vals +1 > SIZE_MAX / sizeof(*opt_set->vals)) + return -ENOMEM; + + ret = handle_u32(optarg, &val, opt); + if (ret) + return ret; + + ptr = realloc(opt_set->vals, sizeof(*opt_set->vals) * (opt_set->num_vals +1)); + if (!ptr) + return -errno; + + opt_set->vals = ptr; + opt_set->vals[opt_set->num_vals++] = val; + return 0; +} + +static int handle_u16(char *optarg, void *tgt, __unused struct prog_option *opt) +{ + __u16 *opt_set = tgt; + unsigned long val; + + errno = 0; + val = strtoul(optarg, NULL, 10); + if (errno || val > 0xffff) + return -EINVAL; + *opt_set = val; + return 0; +} + +static int parse_mac(char *str, unsigned char mac[ETH_ALEN]) +{ + unsigned int v[ETH_ALEN]; + int len, i; + + /* Based on https://stackoverflow.com/a/20553913 */ + len = sscanf(str, "%x:%x:%x:%x:%x:%x%*c", + &v[0], &v[1], &v[2], &v[3], &v[4], &v[5]); + + if (len != ETH_ALEN) + return -EINVAL; + + for (i = 0; i < ETH_ALEN; i++) { + if (v[i] > 0xFF) + return -EINVAL; + mac[i] = v[i]; + } + return 0; +} + +static int handle_macaddr(char *optarg, void *tgt, __unused struct prog_option *opt) +{ + struct mac_addr *opt_set = tgt; + int err; + + err = parse_mac(optarg, opt_set->addr); + if (err) + pr_warn("Invalid MAC address: %s\n", optarg); + + return err; +} + +void print_macaddr(char *buf, size_t buf_len, const struct mac_addr *addr) +{ + int i, len; + + for (i = 0; buf_len > 0 && i < ETH_ALEN; i++) { + len = snprintf(buf, buf_len, "%02x", addr->addr[i]); + if (len < 0 || (size_t)len >= buf_len) + break; + + buf += len; + buf_len -= len; + + if (i < ETH_ALEN - 1) { + *buf++ = ':'; + buf_len -= 1; + } + } + + *buf = '\0'; +} + +bool macaddr_is_null(const struct mac_addr *addr) { + static struct mac_addr nulladdr = {}; + + return memcmp(addr, &nulladdr, sizeof(nulladdr)) == 0; +} + +static const struct flag_val *find_flag(const struct flag_val *flag_vals, + const char *chr) +{ + while (flag_vals->flagstring) { + if (strcmp(chr, flag_vals->flagstring) == 0) + return flag_vals; + flag_vals++; + } + return NULL; +} + +static int handle_flags(char *optarg, void *tgt, struct prog_option *opt) +{ + const struct flag_val *flag, *flag_vals = opt->typearg; + unsigned int *opt_set = tgt; + unsigned int flagval = 0; + char *c = NULL; + + while (*optarg) { + c = strchr(optarg, ','); + if (c) + *c = '\0'; + flag = find_flag(flag_vals, optarg); + if (!flag) + return -EINVAL; + flagval |= flag->flagval; + + if (!c) + break; + optarg = c + 1; + } + *opt_set = flagval; + return 0; +} + +static int get_ifindex(const char *ifname) +{ + int ifindex; + + ifindex = if_nametoindex(ifname); + if (!ifindex) { + pr_warn("Couldn't find network interface '%s'.\n", ifname); + return -ENOENT; + } + return ifindex; +} + +static int handle_ifname(char *optarg, void *tgt, __unused struct prog_option *opt) +{ + struct iface *iface = tgt; + int ifindex; + + ifindex = get_ifindex(optarg); + if (ifindex < 0) + return ifindex; + + iface->ifname = optarg; + iface->ifindex = ifindex; + return 0; +} + +static int handle_ifname_multi(char *optarg, void *tgt, __unused struct prog_option *opt) +{ + struct iface **ifaces = tgt; + struct iface *iface, *tmp; + int ifindex; + + ifindex = get_ifindex(optarg); + if (ifindex < 0) + return ifindex; + + iface = calloc(sizeof(*iface), 1); + if (!iface) + return -ENOMEM; + + iface->ifname = optarg; + iface->ifindex = ifindex; + + if (!*ifaces) { + *ifaces = iface; + return 0; + } + + tmp = *ifaces; + while(tmp->next) + tmp = tmp->next; + + tmp->next = iface; + return 0; +} + +void print_addr(char *buf, size_t buf_len, const struct ip_addr *addr) +{ + inet_ntop(addr->af, &addr->addr, buf, buf_len); +} + +bool ipaddr_is_null(const struct ip_addr *addr) { + static struct ip_addr nulladdr = {}; + + return memcmp(addr, &nulladdr, sizeof(nulladdr)) == 0; +} + +static int handle_ipaddr(char *optarg, void *tgt, __unused struct prog_option *opt) +{ + struct ip_addr *addr = tgt; + int af; + + af = strchr(optarg, ':') ? AF_INET6 : AF_INET; + + if (inet_pton(af, optarg, &addr->addr) != 1) { + pr_warn("Invalid IP address: %s\n", optarg); + return -ENOENT; /* caller won't print error on ENOENT */ + } + + addr->af = af; + return 0; +} + +static const struct enum_val *find_enum(const struct enum_val *enum_vals, + const char *chr) +{ + while (enum_vals->name) { + if (strcmp(chr, enum_vals->name) == 0) + return enum_vals; + enum_vals++; + } + return NULL; +} + +static int handle_enum(char *optarg, void *tgt, struct prog_option *opt) +{ + const struct enum_val *val, *all_vals = opt->typearg; + unsigned int *opt_set = tgt; + + val = find_enum(all_vals, optarg); + if (!val) + return -EINVAL; + *opt_set = val->value; + return 0; +} + +static void print_enum_vals(char *buf, size_t buf_len, + const struct enum_val *vals) +{ + const struct enum_val *val; + bool first = true; + + for (val = vals; buf_len && val->name; val++) { + int len; + + if (!first) { + *buf++ = ','; + buf_len--; + } + first = false; + + len = snprintf(buf, buf_len, "%s", val->name); + if (len < 0 || (size_t)len >= buf_len) + break; + buf += len; + buf_len -= len; + } + *buf = '\0'; +} + +const char *get_enum_name(const struct enum_val *vals, unsigned int value) +{ + const struct enum_val *val; + + for (val = vals; val->name; val++) + if (val->value == value) + return val->name; + return NULL; +} + +static const struct opthandler { + int (*func)(char *optarg, void *tgt, struct prog_option *opt); +} handlers[__OPT_MAX] = { + {NULL}, + {handle_bool}, + {handle_flags}, + {handle_string}, + {handle_u16}, + {handle_u32}, + {handle_u32_multi}, + {handle_macaddr}, + {handle_ifname}, + {handle_ifname_multi}, + {handle_ipaddr}, + {handle_enum}, + {handle_multistring} +}; + +void print_flags(char *buf, size_t buf_len, const struct flag_val *flags, + unsigned long flags_set) +{ + const struct flag_val *flag; + bool first = true; + + for (flag = flags; buf_len && flag->flagstring; flag++) { + int len; + + if (!(flag->flagval & flags_set)) + continue; + + if (!first) { + *buf++ = ','; + buf_len--; + } + first = false; + len = snprintf(buf, buf_len, "%s", flag->flagstring); + if (len < 0 || (size_t)len >= buf_len) + break; + buf += len; + buf_len -= len; + } + *buf = '\0'; +} + +static void print_help_flags(const struct prog_option *opt) +{ + char buf[100] = {}; + + if (!opt->typearg) + pr_warn("Missing typearg for opt %s\n", opt->name); + else + print_flags(buf, sizeof(buf), opt->typearg, -1); + + printf(" %s (valid values: %s)", opt->help, buf); +} + +static void print_help_enum(const struct prog_option *opt) +{ + char buf[100] = {}; + + if (!opt->typearg) + pr_warn("Missing typearg for opt %s\n", opt->name); + else + print_enum_vals(buf, sizeof(buf), opt->typearg); + + printf(" %s (valid values: %s)", opt->help, buf); +} + +static const struct helprinter { + void (*func)(const struct prog_option *opt); +} help_printers[__OPT_MAX] = { + {NULL}, + {NULL}, + {print_help_flags}, + {NULL}, + {NULL}, + {NULL}, + {NULL}, + {NULL}, + {NULL}, + {NULL}, + {NULL}, + {print_help_enum}, + {NULL} +}; + + +static void _print_positional(const struct prog_option *long_options) +{ + const struct prog_option *opt; + + FOR_EACH_OPTION (long_options, opt) { + if (!opt->positional) + continue; + + printf(" %s", opt->metavar ?: opt->name); + } +} + +static void _print_options(const struct prog_option *poptions, bool required) +{ + const struct prog_option *opt; + + FOR_EACH_OPTION (poptions, opt) { + if (opt->required != required) + continue; + + if (opt->positional) { + printf(" %-30s", opt->metavar ?: opt->name); + } else { + char buf[BUFSIZE]; + int pos; + + if (opt->short_opt >= FIRST_PRINTABLE) + printf(" -%c,", opt->short_opt); + else + printf(" "); + pos = snprintf(buf, BUFSIZE, " --%s", opt->name); + if (pos < 0 || pos >= BUFSIZE) { + pr_warn("opt name too long: %s\n", opt->name); + continue; + } + if (opt->metavar) + snprintf(&buf[pos], BUFSIZE - pos, " %s", + opt->metavar); + printf("%-28s", buf); + } + + if (help_printers[opt->type].func != NULL) + help_printers[opt->type].func(opt); + else if (opt->help) + printf(" %s", opt->help); + printf("\n"); + } +} + +bool is_prefix(const char *pfx, const char *str) +{ + if (!pfx) + return false; + if (strlen(str) < strlen(pfx)) + return false; + + return !memcmp(str, pfx, strlen(pfx)); +} + +void usage(const char *prog_name, const char *doc, + const struct prog_option *poptions, bool full) +{ + const struct prog_option *opt; + int num_req = 0; + + printf("\nUsage: %s [options]", prog_name); + _print_positional(poptions); + printf("\n"); + + if (!full) { + printf("Use --help (or -h) to see full option list.\n"); + return; + } + + FOR_EACH_OPTION (poptions, opt) + if (opt->required) + num_req++; + + printf("\n %s\n\n", doc); + if (num_req) { + printf("Required parameters:\n"); + _print_options(poptions, true); + printf("\n"); + } + printf("Options:\n"); + _print_options(poptions, false); + printf(" -v, --verbose Enable verbose logging (-vv: more verbose)\n"); + printf(" --version Display version information\n"); + printf(" -h, --help Show this help\n"); + printf("\n"); +} + +static int prog_options_to_options(struct prog_option *poptions, + struct option **options, char **optstring) +{ + int num = 0, num_cmn = 0, n_sopt = VERSION_SHORT_OPT + 1; + struct option *new_options, *nopt; + struct prog_option *opt; + char buf[100], *c = buf; + + struct option common_opts[] = { + {"help", no_argument, NULL, 'h'}, + {"verbose", no_argument, NULL, 'v'}, + {"version", no_argument, NULL, VERSION_SHORT_OPT}, + {} + }; + + for (nopt = common_opts; nopt->name; nopt++) { + num++; + num_cmn++; + if (nopt->val != VERSION_SHORT_OPT) + *c++ = nopt->val; + } + + FOR_EACH_OPTION (poptions, opt) + if (!opt->positional) + num++; + + new_options = calloc(num + 1, sizeof(struct option)); + if (!new_options) + return -ENOMEM; + + memcpy(new_options, &common_opts, sizeof(struct option) * num_cmn); + nopt = new_options + num_cmn; + + FOR_EACH_OPTION (poptions, opt) { + if (opt->positional) + continue; + if (opt->short_opt) { + *(c++) = opt->short_opt; + if (opt_needs_arg(opt)) + *(c++) = ':'; + } else { + /* getopt expects options to have unique values in the + * 'val' field, however we want to be able to define + * options that don't have a short opt. So get around + * that, just number such options sequentially. + */ + if (n_sopt >= FIRST_PRINTABLE) { + pr_warn("Too many options with no short opt\n"); + goto err; + } + opt->short_opt = n_sopt++; + } + nopt->has_arg = opt_needs_arg(opt) ? required_argument : no_argument; + nopt->name = opt->name; + nopt->val = opt->short_opt; + nopt->flag = NULL; + nopt++; + } + *(c++) = '\0'; + + *optstring = strdup(buf); + if (!*optstring) + goto err; + + /* Make sure we clear the last option, or else we crash. */ + memset(new_options + num, 0, sizeof(struct option)); + + *options = new_options; + return 0; + +err: + free(new_options); + return -EINVAL; +} + +static struct prog_option *find_opt(struct prog_option *all_opts, int optchar) +{ + struct prog_option *opt; + + FOR_EACH_OPTION (all_opts, opt) + if (opt->short_opt == optchar) + return opt; + return NULL; +} + +static int _set_opt(void *cfg, struct prog_option *opt, char *optarg) +{ + int ret; + + if (opt->max_num && opt->num_set + 1 > opt->max_num) { + pr_warn("Too many parameters for %s (max %u)\n", + opt->metavar ?: opt->name, opt->max_num); + return -E2BIG; + } + + ret = handlers[opt->type].func(optarg, (cfg + opt->cfg_offset), opt); + if (!ret) + opt->num_set++; + else if (ret != -ENOENT) + pr_warn("Couldn't parse option %s: %s.\n", opt->name, strerror(-ret)); + return ret; +} + +static int set_opt(void *cfg, struct prog_option *all_opts, int optchar, + char *optarg) +{ + struct prog_option *opt; + + if (!cfg) + return -EFAULT; + + opt = find_opt(all_opts, optchar); + if (!opt) + return -ENOENT; + + return _set_opt(cfg, opt, optarg); +} + +static int set_pos_opt(void *cfg, struct prog_option *all_opts, char *optarg) +{ + struct prog_option *o, *opt = NULL; + + FOR_EACH_OPTION (all_opts, o) { + if (o->positional && (!o->num_set || opt_is_multi(o))) { + opt = o; + break; + } + } + + if (!opt) + return -ENOENT; + + return _set_opt(cfg, opt, optarg); +} + +int parse_cmdline_args(int argc, char **argv, struct prog_option *poptions, + void *cfg, const char *prog, const char *usage_cmd, + const char *doc, const void *defaults) +{ + struct prog_option *opt_iter; + struct option *long_options; + bool full_help = false; + int i, opt, err = 0; + int longindex = 0; + char *optstring; + + if (prog_options_to_options(poptions, &long_options, &optstring)) { + pr_warn("Unable to malloc()\n"); + return -ENOMEM; + } + + /* Parse commands line args */ + while ((opt = getopt_long(argc, argv, optstring, + long_options, &longindex)) != -1) { + switch (opt) { + case 'h': + usage(usage_cmd, doc, poptions, true); + err = EXIT_FAILURE; + goto out; + case 'v': + increase_log_level(); + break; + case VERSION_SHORT_OPT: + printf("%s version %s using libbpf version %s\n", + prog, + TOOLS_VERSION, + get_libbpf_version()); + err = EXIT_FAILURE; + goto out; + default: + if (set_opt(cfg, poptions, opt, optarg)) { + usage(prog, doc, poptions, full_help); + err = EXIT_FAILURE; + goto out; + } + break; + } + } + + for (i = optind; i < argc; i++) { + if (set_pos_opt(cfg, poptions, argv[i])) { + usage(usage_cmd, doc, poptions, full_help); + err = EXIT_FAILURE; + goto out; + } + } + + FOR_EACH_OPTION (poptions, opt_iter) { + if (opt_iter->num_set && (!opt_iter->min_num || + opt_iter->num_set >= opt_iter->min_num)) + continue; + + if (opt_iter->required) { + if (opt_iter->positional) + pr_warn("Missing required parameter %s\n", + opt_iter->metavar ?: opt_iter->name); + else + pr_warn("Missing required option '--%s'\n", + opt_iter->name); + usage(prog, doc, poptions, full_help); + err = EXIT_FAILURE; + goto out; + } else if (defaults) { + void *dst = cfg + opt_iter->cfg_offset; + const void *src = defaults + opt_iter->cfg_offset; + + memcpy(dst, src, opt_iter->opt_size); + } + } +out: + free(long_options); + free(optstring); + + return err; +} + +int dispatch_commands(const char *argv0, int argc, char **argv, + const struct prog_command *cmds, size_t cfg_size, + const char *prog_name, bool needs_bpffs) +{ + const struct prog_command *c, *cmd = NULL; + int ret = EXIT_FAILURE, err, len; + char pin_root_path[PATH_MAX]; + char usagebuf[100]; + void *cfg; + + for (c = cmds; c->name; c++) { + if (is_prefix(argv0, c->name)) { + cmd = c; + break; + } + } + + if (!cmd) { + pr_warn("Command '%s' is unknown, try '%s help'.\n", + argv0, prog_name); + return EXIT_FAILURE; + } + + if (cmd->no_cfg) + return cmd->func(NULL, NULL); + + cfg = calloc(1, cfg_size); + if (!cfg) { + pr_warn("Couldn't allocate memory\n"); + return EXIT_FAILURE; + } + + len = snprintf(usagebuf, sizeof(usagebuf), "%s %s", prog_name, cmd->name); + if (len < 0 || (size_t)len >= sizeof(usagebuf)) + goto out; + + err = parse_cmdline_args(argc, argv, cmd->options, cfg, prog_name, usagebuf, + cmd->doc, cmd->default_cfg); + if (err) + goto out; + + err = get_bpf_root_dir(pin_root_path, sizeof(pin_root_path), prog_name, + needs_bpffs); + if (err && needs_bpffs) + goto out; + + err = check_bpf_environ(); + if (err) + goto out; + + if (prog_lock_get(prog_name)) + goto out; + + ret = cmd->func(cfg, pin_root_path); + prog_lock_release(0); +out: + free(cfg); + return ret; +} |