diff options
Diffstat (limited to 'fwd_rule.c')
| -rw-r--r-- | fwd_rule.c | 270 |
1 files changed, 158 insertions, 112 deletions
@@ -439,17 +439,102 @@ fail: 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 +/** + * enum fwd_port_chunk_kind - Kind of port specifier piece + * @CHUNK_ALL "all" + * @CHUNK_AUTO "auto" + * @CHUNK_EXCLUDE "~1111[-2222]" + * @CHUNK_INCLUDE "1111[-2222][:3333[-4444]]" */ -#define for_each_chunk(p_, ep_, s_, sep_) \ - for ((p_) = (s_); \ - (ep_) = (p_) + strcspn((p_), (sep_)), *(p_); \ - (p_) = *(ep_) ? (ep_) + 1 : (ep_)) +enum fwd_port_chunk_kind { + CHUNK_ALL, + CHUNK_AUTO, + CHUNK_EXCLUDE, + CHUNK_INCLUDE, +}; + +/** + * parse_port_chunk() - Parse one chunk of a port specifier + * @cursor: Parsing point (see parse.c) + * @kindp: Updated with kind of chunk we parsed + * @lrange: Updated with listening port range (for INCLUDE & EXCLUDE) + * @trange: Updated with target port range (for INCLUDE) + */ +static bool parse_port_chunk(const char **cursor, + enum fwd_port_chunk_kind *kindp, + struct port_range *lrange, + struct port_range *trange) +{ + struct port_range lr = { 0 }, tr = { 0 }; + enum fwd_port_chunk_kind kind; + const char *p = *cursor; + + if (parse_literal(&p, "all")) { + kind = CHUNK_ALL; + } else if (parse_literal(&p, "auto")) { + kind = CHUNK_AUTO; + } else if (parse_literal(&p, "~")) { + kind = CHUNK_EXCLUDE; + if (!parse_port_range(&p, &lr)) + return false; + } else if (parse_port_range(&p, &lr)) { + kind = CHUNK_INCLUDE; + + if (parse_literal(&p, ":")) { + if (!parse_port_range(&p, &tr)) + return false; + } else { + tr = lr; + } + } else { + return false; + } + + *kindp = kind; + *lrange = lr; + if (trange) + *trange = tr; + *cursor = p; + return true; +} + +/** + * parse_addrifname() - Parse ADDRESS[%IFNAME]/ + * @cursor: Parsing cursor (see parse.c) + * @addr: Updated with parsed inany address (NULL for *) + * @abuf: Buffer to store address + * @ifname: Updated with parsed interface name ("" if none) + */ +static bool parse_addrifname(const char **cursor, + const union inany_addr **addr, + union inany_addr *abuf, + char *ifname) +{ + union inany_addr atmp = inany_any6; + char iftmp[IFNAMSIZ] = {0}; + const char *p; + + if (p = *cursor, + parse_inany(&p, &atmp) && + parse_ifspec(&p, iftmp) && + parse_literal(&p, "/")) { + /* Specific listening address */ + *addr = abuf; + } else if (p = *cursor, + parse_literal(&p, "*"), + parse_ifspec(&p, iftmp) && + parse_literal(&p, "/")) { + /* Missing or "*" address */ + *addr = NULL; + } else { + return false; + } + + *abuf = atmp; + memcpy(ifname, iftmp, IFNAMSIZ); + *cursor = p; + return true; +} /** * fwd_rule_parse_ports() - Parse port range(s) specifier @@ -466,87 +551,70 @@ static void fwd_rule_parse_ports(struct fwd_table *fwd, bool del, uint8_t proto, const char *spec) { uint8_t exclude[PORT_BITMAP_SIZE] = { 0 }; + enum fwd_port_chunk_kind kind; + struct port_range lrange; bool exclude_only = true; - const char *p, *ep; uint8_t flags = 0; + const char *p; unsigned i; - /* Parse excluded ranges and "auto" in the first pass */ - for_each_chunk(p, ep, spec, ",") { - struct port_range xrange; - - /* Include range, parse later */ - if (parse_literal(&p, "all") || isdigit(*p)) - continue; - - if (parse_literal(&p, "auto")) { - if (p != ep) /* Garbage after the keyword */ - goto bad; + /* Consider excluded ranges and "auto" in the first pass */ + p = spec; + do { + if (!parse_port_chunk(&p, &kind, &lrange, NULL)) + goto bad; + switch (kind) { + case CHUNK_AUTO: 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 (!parse_literal(&p, "~")) - goto bad; - - if (!parse_port_range(&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); - } - - /* Now process base ranges, skipping exclusions */ - for_each_chunk(p, ep, spec, ",") { - struct port_range orig_range, mapped_range; - - /* Handle "all" like exclude only */ - if (parse_literal(&p, "all")) { - if (p != ep) /* Garbage after the keyword */ - goto bad; + break; - continue; + case CHUNK_EXCLUDE: + for (i = lrange.first; i <= lrange.last; i++) + bitmap_set(exclude, i); + break; + default: + ; /* Handled later */ } + } while (parse_literal(&p, ",")); - if (!isdigit(*p)) - /* Already parsed */ - continue; + /* Consider included ranges in next pass */ + p = spec; + do { + struct port_range trange; - if (!parse_port_range(&p, &orig_range)) + if (!parse_port_chunk(&p, &kind, &lrange, &trange)) goto bad; - exclude_only = false; + switch (kind) { + case CHUNK_AUTO: /* already handled */ + case CHUNK_EXCLUDE: /* already handled */ + case CHUNK_ALL: /* handled later */ + continue; - if (parse_literal(&p, ":")) { - /* There's a range to map to as well */ - if (!parse_port_range(&p, &mapped_range)) + case CHUNK_INCLUDE: + exclude_only = false; + if (trange.last - trange.first != + lrange.last - lrange.first) 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 */ + fwd_rule_range_except(fwd, del, proto, addr, ifname, + lrange.first, lrange.last, + exclude, trange.first, flags); + break; + default: goto bad; + } + } while (parse_literal(&p, ",")); - fwd_rule_range_except(fwd, del, proto, addr, ifname, - orig_range.first, orig_range.last, - exclude, - mapped_range.first, flags); - } + if (!parse_eoi(p)) + goto bad; /* trailing garbage */ - /* Finally handle "all" and exclude only specs */ + /* Finally handle "all" and exclude only cases */ if (exclude_only) { fwd_port_map_ephemeral(exclude); @@ -569,10 +637,11 @@ bad: void fwd_rule_parse(char optname, bool del, const char *optarg, struct fwd_table *fwd) { - char buf[BUFSIZ], *spec, *ifname = NULL; - union inany_addr addr_buf = inany_any6; - const union inany_addr *addr = &addr_buf; + const union inany_addr *addr; + union inany_addr addr_buf; + char ifname[IFNAMSIZ]; uint8_t proto; + const char *p; if (optname == 't' || optname == 'T') proto = IPPROTO_TCP; @@ -581,7 +650,8 @@ void fwd_rule_parse(char optname, bool del, const char *optarg, else assert(0); - if (!strcmp(optarg, "none")) { + if (p = optarg, + parse_literal(&p, "none") && parse_eoi(p)) { unsigned i; for (i = 0; i < fwd->count; i++) { @@ -593,45 +663,21 @@ void fwd_rule_parse(char optname, bool del, const char *optarg, return; } - strncpy(buf, optarg, sizeof(buf) - 1); - - if ((spec = strchr(buf, '/'))) { - const char *p = buf; - - *spec = 0; - spec++; - - if (optname != 't' && optname != 'u') + if (p = optarg, + parse_addrifname(&p, &addr, &addr_buf, ifname)) { + 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 */ - !strcmp(buf, "*")) /* Explicit wildcard address */ - addr = NULL; - else if (!parse_inany(&p, &addr_buf) && parse_eoi(p)) - die("Bad forwarding address '%s'", buf); + if (!strcmp(ifname, ".") || !strcmp(ifname, "..")) + die("Invalid interface name: %s", ifname); } else { - spec = buf; - + /* No address or ifname */ addr = NULL; + ifname[0] = '\0'; } if (optname == 'T' || optname == 'U') { - assert(!addr && !ifname); + assert(!addr && !*ifname); if (!(fwd->caps & FWD_CAP_IFNAME)) { warn( @@ -640,18 +686,18 @@ void fwd_rule_parse(char optname, bool del, const char *optarg, if (fwd->caps & FWD_CAP_IPV4) { fwd_rule_parse_ports(fwd, del, proto, - &inany_loopback4, NULL, - spec); + &inany_loopback4, NULL, p); } if (fwd->caps & FWD_CAP_IPV6) { fwd_rule_parse_ports(fwd, del, proto, - &inany_loopback6, NULL, - spec); + &inany_loopback6, NULL, p); } return; } - ifname = "lo"; + static_assert(sizeof("lo") <= sizeof(ifname), + "ifname buffer too small"); + strcpy(ifname, "lo"); } /* No need for dual stack if we only have one IP version */ @@ -660,13 +706,13 @@ void fwd_rule_parse(char optname, bool del, const char *optarg, else if (!addr && !(fwd->caps & FWD_CAP_IPV6)) addr = &inany_any4; - if (ifname && !(fwd->caps & FWD_CAP_IFNAME)) { + if (*ifname && !(fwd->caps & FWD_CAP_IFNAME)) { die( "Device binding for '-%c %s' unsupported (requires kernel 5.7+)", optname, optarg); } - fwd_rule_parse_ports(fwd, del, proto, addr, ifname, spec); + fwd_rule_parse_ports(fwd, del, proto, addr, *ifname ? ifname : NULL, p); } /** |
