diff options
Diffstat (limited to 'conf.c')
| -rw-r--r-- | conf.c | 376 |
1 files changed, 8 insertions, 368 deletions
@@ -13,7 +13,6 @@ */ #include <arpa/inet.h> -#include <ctype.h> #include <errno.h> #include <fcntl.h> #include <getopt.h> @@ -67,365 +66,6 @@ const char *pasta_default_ifn = "tap0"; /** - * port_range() - Represents a non-empty range of ports - * @first: First port number in the range - * @last: Last port number in the range (inclusive) - * - * Invariant: @last >= @first - */ -struct port_range { - in_port_t first, last; -}; - -/** - * parse_port_range() - Parse a range of port numbers '<first>[-<last>]' - * @s: String to parse - * @endptr: Update to the character after the parsed range (similar to - * strtol() etc.) - * @range: Update with the parsed values on success - * - * Return: -EINVAL on parsing error, -ERANGE on out of range port - * numbers, 0 on success - */ -static int parse_port_range(const char *s, const char **endptr, - struct port_range *range) -{ - unsigned long first, last; - char *ep; - - last = first = strtoul(s, &ep, 10); - if (ep == s) /* Parsed nothing */ - return -EINVAL; - if (*ep == '-') { /* we have a last value too */ - const char *lasts = ep + 1; - last = strtoul(lasts, &ep, 10); - if (ep == lasts) /* Parsed nothing */ - return -EINVAL; - } - - if ((last < first) || (last >= NUM_PORTS)) - return -ERANGE; - - range->first = first; - range->last = last; - *endptr = ep; - - return 0; -} - -/** - * parse_keyword() - Parse a literal keyword - * @s: String to parse - * @endptr: Update to the character after the keyword - * @kw: Keyword to accept - * - * Return: 0, if @s starts with @kw, -EINVAL if it does not - */ -static int parse_keyword(const char *s, const char **endptr, const char *kw) -{ - size_t len = strlen(kw); - - if (strlen(s) < len) - return -EINVAL; - - if (memcmp(s, kw, len)) - return -EINVAL; - - *endptr = s + len; - return 0; -} - -/** - * conf_ports_range_except() - Set up forwarding for a range of ports minus a - * bitmap of exclusions - * @fwd: Forwarding table to be updated - * @proto: Protocol to forward - * @addr: Listening address - * @ifname: Listening interface - * @first: First port to forward - * @last: Last port to forward - * @exclude: Bitmap of ports to exclude (may be NULL) - * @to: Port to translate @first to when forwarding - * @flags: Flags for forwarding entries - */ -static void conf_ports_range_except(struct fwd_table *fwd, uint8_t proto, - const union inany_addr *addr, - const char *ifname, - uint16_t first, uint16_t last, - const uint8_t *exclude, uint16_t to, - uint8_t flags) -{ - struct fwd_rule rule = { - .addr = addr ? *addr : inany_any6, - .ifname = { 0 }, - .proto = proto, - .flags = flags, - }; - char rulestr[FWD_RULE_STRLEN]; - unsigned delta = to - first; - unsigned base, i; - - if (!addr) - rule.flags |= FWD_DUAL_STACK_ANY; - if (ifname) { - int ret; - - ret = snprintf(rule.ifname, sizeof(rule.ifname), - "%s", ifname); - if (ret <= 0 || (size_t)ret >= sizeof(rule.ifname)) - die("Invalid interface name: %s", ifname); - } - - for (base = first; base <= last; base++) { - if (exclude && bitmap_isset(exclude, base)) - continue; - - for (i = base; i <= last; i++) { - if (exclude && bitmap_isset(exclude, i)) - break; - } - - rule.first = base; - rule.last = i - 1; - rule.to = base + delta; - - fwd_rule_conflict_check(&rule, fwd->rules, fwd->count); - if (fwd_rule_add(fwd, &rule) < 0) - goto fail; - - base = i - 1; - } - return; - -fail: - die("Unable to add rule %s", - fwd_rule_fmt(&rule, rulestr, sizeof(rulestr))); -} - -/* - * for_each_chunk - Step through delimited chunks of a string - * @p_: Pointer to start of each chunk (updated) - * @ep_: Pointer to end of each chunk (updated) - * @s_: String to step through - * @sep_: String of all allowed delimiters - */ -#define for_each_chunk(p_, ep_, s_, sep_) \ - for ((p_) = (s_); \ - (ep_) = (p_) + strcspn((p_), (sep_)), *(p_); \ - (p_) = *(ep_) ? (ep_) + 1 : (ep_)) - -/** - * conf_ports_spec() - Parse port range(s) specifier - * @fwd: Forwarding table to be updated - * @proto: Protocol to forward - * @addr: Listening address for forwarding - * @ifname: Interface name for listening - * @spec: Port range(s) specifier - */ -static void conf_ports_spec(struct fwd_table *fwd, uint8_t proto, - const union inany_addr *addr, const char *ifname, - const char *spec) -{ - uint8_t exclude[PORT_BITMAP_SIZE] = { 0 }; - bool exclude_only = true; - const char *p, *ep; - uint8_t flags = 0; - unsigned i; - - if (!strcmp(spec, "all")) { - /* Treat "all" as equivalent to "": all non-ephemeral ports */ - spec = ""; - } - - /* Parse excluded ranges and "auto" in the first pass */ - for_each_chunk(p, ep, spec, ",") { - struct port_range xrange; - - if (isdigit(*p)) { - /* Include range, parse later */ - exclude_only = false; - continue; - } - - if (parse_keyword(p, &p, "auto") == 0) { - if (p != ep) /* Garbage after the keyword */ - goto bad; - - if (!(fwd->caps & FWD_CAP_SCAN)) { - die( -"'auto' port forwarding is only allowed for pasta"); - } - - flags |= FWD_SCAN; - continue; - } - - /* Should be an exclude range */ - if (*p != '~') - goto bad; - p++; - - if (parse_port_range(p, &p, &xrange)) - goto bad; - if (p != ep) /* Garbage after the range */ - goto bad; - - for (i = xrange.first; i <= xrange.last; i++) - bitmap_set(exclude, i); - } - - if (exclude_only) { - /* Exclude ephemeral ports */ - fwd_port_map_ephemeral(exclude); - - conf_ports_range_except(fwd, proto, addr, ifname, - 1, NUM_PORTS - 1, exclude, - 1, flags | FWD_WEAK); - return; - } - - /* Now process base ranges, skipping exclusions */ - for_each_chunk(p, ep, spec, ",") { - struct port_range orig_range, mapped_range; - - if (!isdigit(*p)) - /* Already parsed */ - continue; - - if (parse_port_range(p, &p, &orig_range)) - goto bad; - - if (*p == ':') { /* There's a range to map to as well */ - if (parse_port_range(p + 1, &p, &mapped_range)) - goto bad; - if ((mapped_range.last - mapped_range.first) != - (orig_range.last - orig_range.first)) - goto bad; - } else { - mapped_range = orig_range; - } - - if (p != ep) /* Garbage after the ranges */ - goto bad; - - conf_ports_range_except(fwd, proto, addr, ifname, - orig_range.first, orig_range.last, - exclude, - mapped_range.first, flags); - } - - return; -bad: - die("Invalid port specifier '%s'", spec); -} - -/** - * conf_ports() - Parse port configuration options, initialise UDP/TCP sockets - * @optname: Short option name, t, T, u, or U - * @optarg: Option argument (port specification) - * @fwd: Forwarding table to be updated - */ -static void conf_ports(char optname, const char *optarg, struct fwd_table *fwd) -{ - union inany_addr addr_buf = inany_any6, *addr = &addr_buf; - char buf[BUFSIZ], *spec, *ifname = NULL; - uint8_t proto; - - if (optname == 't' || optname == 'T') - proto = IPPROTO_TCP; - else if (optname == 'u' || optname == 'U') - proto = IPPROTO_UDP; - else - assert(0); - - if (!strcmp(optarg, "none")) { - unsigned i; - - for (i = 0; i < fwd->count; i++) { - if (fwd->rules[i].proto == proto) { - die("-%c none conflicts with previous options", - optname); - } - } - return; - } - - strncpy(buf, optarg, sizeof(buf) - 1); - - if ((spec = strchr(buf, '/'))) { - *spec = 0; - spec++; - - if (optname != 't' && optname != 'u') - die("Listening address not allowed for -%c %s", - optname, optarg); - - if ((ifname = strchr(buf, '%'))) { - *ifname = 0; - ifname++; - - /* spec is already advanced one past the '/', - * so the length of the given ifname is: - * (spec - ifname - 1) - */ - if (spec - ifname - 1 >= IFNAMSIZ) { - die("Interface name '%s' is too long (max %u)", - ifname, IFNAMSIZ - 1); - } - } - - if (ifname == buf + 1) { /* Interface without address */ - addr = NULL; - } else { - char *p = buf; - - /* Allow square brackets for IPv4 too for convenience */ - if (*p == '[' && p[strlen(p) - 1] == ']') { - p[strlen(p) - 1] = '\0'; - p++; - } - - if (!inany_pton(p, addr)) - die("Bad forwarding address '%s'", p); - } - } else { - spec = buf; - - addr = NULL; - } - - if (optname == 'T' || optname == 'U') { - assert(!addr && !ifname); - - if (!(fwd->caps & FWD_CAP_IFNAME)) { - warn( -"SO_BINDTODEVICE unavailable, forwarding only 127.0.0.1 and ::1 for '-%c %s'", - optname, optarg); - - if (fwd->caps & FWD_CAP_IPV4) { - conf_ports_spec(fwd, proto, - &inany_loopback4, NULL, spec); - } - if (fwd->caps & FWD_CAP_IPV6) { - conf_ports_spec(fwd, proto, - &inany_loopback6, NULL, spec); - } - return; - } - - ifname = "lo"; - } - - if (ifname && !(fwd->caps & FWD_CAP_IFNAME)) { - die( -"Device binding for '-%c %s' unsupported (requires kernel 5.7+)", - optname, optarg); - } - - conf_ports_spec(fwd, proto, addr, ifname, spec); -} - -/** * add_dns4() - Possibly add the IPv4 address of a DNS resolver to configuration * @c: Execution context * @addr: Guest nameserver IPv4 address @@ -2160,16 +1800,16 @@ void conf(struct ctx *c, int argc, char **argv) if (name == 't') { opt_t = true; - conf_ports(name, optarg, c->fwd[PIF_HOST]); + fwd_rule_parse(name, optarg, c->fwd[PIF_HOST]); } else if (name == 'u') { opt_u = true; - conf_ports(name, optarg, c->fwd[PIF_HOST]); + fwd_rule_parse(name, optarg, c->fwd[PIF_HOST]); } else if (name == 'T') { opt_T = true; - conf_ports(name, optarg, c->fwd[PIF_SPLICE]); + fwd_rule_parse(name, optarg, c->fwd[PIF_SPLICE]); } else if (name == 'U') { opt_U = true; - conf_ports(name, optarg, c->fwd[PIF_SPLICE]); + fwd_rule_parse(name, optarg, c->fwd[PIF_SPLICE]); } } while (name != -1); @@ -2221,13 +1861,13 @@ void conf(struct ctx *c, int argc, char **argv) if (c->mode == MODE_PASTA) { if (!opt_t) - conf_ports('t', "auto", c->fwd[PIF_HOST]); + fwd_rule_parse('t', "auto", c->fwd[PIF_HOST]); if (!opt_T) - conf_ports('T', "auto", c->fwd[PIF_SPLICE]); + fwd_rule_parse('T', "auto", c->fwd[PIF_SPLICE]); if (!opt_u) - conf_ports('u', "auto", c->fwd[PIF_HOST]); + fwd_rule_parse('u', "auto", c->fwd[PIF_HOST]); if (!opt_U) - conf_ports('U', "auto", c->fwd[PIF_SPLICE]); + fwd_rule_parse('U', "auto", c->fwd[PIF_SPLICE]); } if (!c->quiet) |
