dev
Threads by month
- ----- 2026 -----
- May
- April
- March
- February
- January
- ----- 2025 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
May 2026
- 7 participants
- 54 discussions
05 May '26
Changes in v6:
* Addressed comments from Jon in 10/18, 11/18, 14/18, and 16/18
* Dodged all warnings from static checkers (Coverity Scan and
clang-tidy) with changes in 10/18, 11/18, 16/18, and with a
new patch, 18/18
* This does *not* include yet the implementation of --add and
--delete switches for pesto as I originally intended, I'm
rather far from being done with those. At the moment I just
have a "mode selection" implementation for command line
parsing but merging rules to / removing rules from / clearing
the current table is something I barely started (and what I
have at the moment isn't really valuable anyway)
David wrote:
---
Here's the next draft of dynamic configuration updates. This now can
successfully update rules, though I've not tested it very extensively.
Patches 1..8/18 are preliminary reworks that make sense even without
pesto - feel free to apply if you're happy with them. I don't think
the rest should be applied yet; we need to at least harden it so passt
can't be blocked indefinitely by a client which sends a partial update
then waits.
Based on my earlier series reworking static checking invocation.
TODO:
- Don't allow a client which sends a partial configuration then
blocks also block passt
- Allow pesto to clear existing configuration, not just add
- Allow pesto selectively delete existing rules, not just add
Changes in v5:
* If multiple clients connect at once, they're now blocked until the
first one finishes, instead of later ones being discarded
Changes in v4:
* Merged with remainder of forward rule parsing rework series
* Fix some bugs in rule checking pointed out by Laurent
* Significantly cleaned up option parsing code
* Changed from replacing all existing rules to adding new rules
(clear and remove still TBD)
* Somewhat simplified protocol (pif names and rules sent in a single
pass)
* pesto is now allocation free
* Fixed commit message and style nits pointed out by Stefano
Changes in v3:
* Removed already applied ASSERT() rename
* Renamed serialisation functions
* Incorporated Stefano's extensions, reworked and fixed
* Several additional cleanups / preliminary reworks
Changes in v2:
* Removed already applied cleanups
* Reworked assert() patch to handle -DNDEBUG properly
* Numerous extra patches:
* Factored out serialisation helpers and use them for migration as
well
* Reworked to allow ip.[ch] and inany.[ch] to be shared with pesto
* Reworks to share some forwarding rule datatypes with pesto
* Implemented sending pif names and current ruleset to pesto
---
David Gibson (17):
conf, fwd: Stricter rule checking in fwd_rule_add()
fwd_rule: Move ephemeral port probing to fwd_rule.c
fwd, conf: Move rule parsing code to fwd_rule.[ch]
fwd_rule: Move conflict checking back within fwd_rule_add()
fwd: Generalise fwd_rules_info()
pif: Limit pif names to 128 bytes
fwd_rule: Fix some format specifiers
pesto: Introduce stub configuration tool
pesto, log: Share log.h (but not log.c) with pesto tool
pesto, conf: Have pesto connect to passt and check versions
pesto: Expose list of pifs to pesto and optionally display
ip: Prepare ip.[ch] for sharing with pesto tool
inany: Prepare inany.[ch] for sharing with pesto tool
pesto: Read current ruleset from passt/pasta and optionally display it
pesto: Parse and add new rules from command line
pesto, conf: Send updated rules from pesto back to passt/pasta
conf, fwd: Allow switching to new rules received from pesto
Stefano Brivio (1):
fwd_rule: Fix static checkers warnings in fwd_rule_add()
.gitignore | 2 +
Makefile | 54 ++--
common.h | 122 +++++++++
conf.c | 701 +++++++++++++++++++++++----------------------------
conf.h | 2 +
epoll_type.h | 4 +
flow.c | 4 +-
fwd.c | 169 ++++---------
fwd.h | 41 +--
fwd_rule.c | 612 ++++++++++++++++++++++++++++++++++++++++++--
fwd_rule.h | 66 ++++-
inany.c | 19 +-
inany.h | 17 +-
ip.c | 56 +---
ip.h | 4 +-
lineread.c | 2 +-
log.h | 59 ++++-
passt.1 | 5 +
passt.c | 8 +
passt.h | 8 +
pesto.1 | 46 ++++
pesto.c | 472 ++++++++++++++++++++++++++++++++++
pesto.h | 54 ++++
pif.c | 2 +-
pif.h | 8 +-
serialise.c | 7 +
serialise.h | 1 +
siphash.h | 13 +
tap.c | 52 ++++
util.h | 110 +-------
30 files changed, 1928 insertions(+), 792 deletions(-)
create mode 100644 common.h
create mode 100644 pesto.1
create mode 100644 pesto.c
create mode 100644 pesto.h
--
2.43.0
2
38
04 May '26
Although fwd_rule_add() performs some sanity checks on the rule it is
given, there are invalid rules we don't check for, assuming that its
callers will do that.
That won't be enough when we can get rules inserted by a dynamic update
client without going through the existing parsing code. So, add stricter
checks to fwd_rule_add(), which is now possible thanks to the capabilities
bits in the struct fwd_table. Where those duplicate existing checks in the
callers, remove the old copies.
Signed-off-by: David Gibson <david(a)gibson.dropbear.id.au>
---
conf.c | 21 ---------------------
fwd.c | 52 +++++++++++++++++++++++++++++++++++++++++++++++-----
2 files changed, 47 insertions(+), 26 deletions(-)
diff --git a/conf.c b/conf.c
index 6e884e54..b470b0d8 100644
--- a/conf.c
+++ b/conf.c
@@ -176,8 +176,6 @@ static void conf_ports_range_except(struct fwd_table *fwd, uint8_t proto,
die("Invalid interface name: %s", ifname);
}
- assert(first != 0);
-
for (base = first; base <= last; base++) {
if (exclude && bitmap_isset(exclude, base))
continue;
@@ -310,10 +308,6 @@ static void conf_ports_spec(struct fwd_table *fwd, uint8_t proto,
if (p != ep) /* Garbage after the ranges */
goto bad;
- if (orig_range.first == 0) {
- die("Can't forward port 0 included in '%s'", spec);
- }
-
conf_ports_range_except(fwd, proto, addr, ifname,
orig_range.first, orig_range.last,
exclude,
@@ -356,11 +350,6 @@ static void conf_ports(char optname, const char *optarg, struct fwd_table *fwd)
return;
}
- if (proto == IPPROTO_TCP && !(fwd->caps & FWD_CAP_TCP))
- die("TCP port forwarding requested but TCP is disabled");
- if (proto == IPPROTO_UDP && !(fwd->caps & FWD_CAP_UDP))
- die("UDP port forwarding requested but UDP is disabled");
-
strncpy(buf, optarg, sizeof(buf) - 1);
if ((spec = strchr(buf, '/'))) {
@@ -405,16 +394,6 @@ static void conf_ports(char optname, const char *optarg, struct fwd_table *fwd)
addr = NULL;
}
- if (addr) {
- if (!(fwd->caps & FWD_CAP_IPV4) && inany_v4(addr)) {
- die("IPv4 is disabled, can't use -%c %s",
- optname, optarg);
- } else if (!(fwd->caps & FWD_CAP_IPV6) && !inany_v4(addr)) {
- die("IPv6 is disabled, can't use -%c %s",
- optname, optarg);
- }
- }
-
if (optname == 'T' || optname == 'U') {
assert(!addr && !ifname);
diff --git a/fwd.c b/fwd.c
index c7fd1a9d..979c1494 100644
--- a/fwd.c
+++ b/fwd.c
@@ -367,17 +367,59 @@ int fwd_rule_add(struct fwd_table *fwd, const struct fwd_rule *new)
new->first, new->last);
return -EINVAL;
}
+ if (!new->first) {
+ warn("Forwarding rule attempts to map from port 0");
+ return -EINVAL;
+ }
+ if (!new->to ||
+ (in_port_t)(new->to + new->last - new->first) < new->to) {
+ warn("Forwarding rule attempts to map to port 0");
+ return -EINVAL;
+ }
if (new->flags & ~allowed_flags) {
warn("Rule has invalid flags 0x%hhx",
new->flags & ~allowed_flags);
return -EINVAL;
}
- if (new->flags & FWD_DUAL_STACK_ANY &&
- !inany_equals(&new->addr, &inany_any6)) {
- char astr[INANY_ADDRSTRLEN];
+ if (new->flags & FWD_DUAL_STACK_ANY) {
+ if (!inany_equals(&new->addr, &inany_any6)) {
+ char astr[INANY_ADDRSTRLEN];
- warn("Dual stack rule has non-wildcard address %s",
- inany_ntop(&new->addr, astr, sizeof(astr)));
+ warn("Dual stack rule has non-wildcard address %s",
+ inany_ntop(&new->addr, astr, sizeof(astr)));
+ return -EINVAL;
+ }
+ if (!(fwd->caps & FWD_CAP_IPV4)) {
+ warn("Dual stack forward, but IPv4 not enabled");
+ return -EINVAL;
+ }
+ if (!(fwd->caps & FWD_CAP_IPV6)) {
+ warn("Dual stack forward, but IPv6 not enabled");
+ return -EINVAL;
+ }
+ } else {
+ if (inany_v4(&new->addr) && !(fwd->caps & FWD_CAP_IPV4)) {
+ warn("IPv4 forward, but IPv4 not enabled");
+ return -EINVAL;
+ }
+ if (!inany_v4(&new->addr) && !(fwd->caps & FWD_CAP_IPV6)) {
+ warn("IPv6 forward, but IPv6 not enabled");
+ return -EINVAL;
+ }
+ }
+ if (new->proto == IPPROTO_TCP) {
+ if (!(fwd->caps & FWD_CAP_TCP)) {
+ warn("Can't add TCP forwarding rule, TCP not enabled");
+ return -EINVAL;
+ }
+ } else if (new->proto == IPPROTO_UDP) {
+ if (!(fwd->caps & FWD_CAP_UDP)) {
+ warn("Can't add UDP forwarding rule, UDP not enabled");
+ return -EINVAL;
+ }
+ } else {
+ warn("Unsupported protocol 0x%hhx (%s) for forwarding rule",
+ new->proto, ipproto_name(new->proto));
return -EINVAL;
}
--
2.53.0
3
4
03 May '26
Here's the next draft of dynamic configuration updates. This now can
successfully update rules, though I've not tested it very extensively.
Patches 1..8/18 are preliminary reworks that make sense even without
pesto - feel free to apply if you're happy with them. I don't think
the rest should be applied yet; we need to at least harden it so passt
can't be blocked indefinitely by a client which sends a partial update
then waits.
Based on my earlier series reworking static checking invocation.
TODO:
- Don't allow a client which sends a partial configuration then
blocks also block passt
- Allow pesto to clear existing configuration, not just add
- Allow pesto selectively delete existing rules, not just add
Changes in v5:
* If multiple clients connect at once, they're now blocked until the
first one finishes, instead of later ones being discarded
Changes in v4:
* Merged with remainder of forward rule parsing rework series
* Fix some bugs in rule checking pointed out by Laurent
* Significantly cleaned up option parsing code
* Changed from replacing all existing rules to adding new rules
(clear and remove still TBD)
* Somewhat simplified protocol (pif names and rules sent in a single
pass)
* pesto is now allocation free
* Fixed commit message and style nits pointed out by Stefano
Changes in v3:
* Removed already applied ASSERT() rename
* Renamed serialisation functions
* Incorporated Stefano's extensions, reworked and fixed
* Several additional cleanups / preliminary reworks
Changes in v2:
* Removed already applied cleanups
* Reworked assert() patch to handle -DNDEBUG properly
* Numerous extra patches:
* Factored out serialisation helpers and use them for migration as
well
* Reworked to allow ip.[ch] and inany.[ch] to be shared with pesto
* Reworks to share some forwarding rule datatypes with pesto
* Implemented sending pif names and current ruleset to pesto
David Gibson (18):
conf, fwd: Stricter rule checking in fwd_rule_add()
fwd_rule: Move ephemeral port probing to fwd_rule.c
fwd, conf: Move rule parsing code to fwd_rule.[ch]
fwd_rule: Move conflict checking back within fwd_rule_add()
fwd: Generalise fwd_rules_info()
pif: Limit pif names to 128 bytes
fwd_rule: Fix some format specifiers
tap, repair: Use SOCK_NONBLOCK and SOCK_CLOEXEC on Unix sockets
pesto: Introduce stub configuration tool
pesto, log: Share log.h (but not log.c) with pesto tool
pesto, conf: Have pesto connect to passt and check versions
pesto: Expose list of pifs to pesto and optionally display
ip: Prepare ip.[ch] for sharing with pesto tool
inany: Prepare inany.[ch] for sharing with pesto tool
pesto: Read current ruleset from passt/pasta and optionally display it
pesto: Parse and add new rules from command line
pesto, conf: Send updated rules from pesto back to passt/pasta
conf, fwd: Allow switching to new rules received from pesto
.gitignore | 2 +
Makefile | 54 ++--
common.h | 122 +++++++++
conf.c | 686 ++++++++++++++++++++++-----------------------------
conf.h | 2 +
epoll_type.h | 4 +
flow.c | 4 +-
fwd.c | 169 ++++---------
fwd.h | 41 +--
fwd_rule.c | 603 ++++++++++++++++++++++++++++++++++++++++++--
fwd_rule.h | 66 ++++-
inany.c | 19 +-
inany.h | 17 +-
ip.c | 56 +----
ip.h | 4 +-
lineread.c | 2 +-
log.h | 59 ++++-
passt.1 | 5 +
passt.c | 8 +
passt.h | 8 +
pesto.1 | 46 ++++
pesto.c | 470 +++++++++++++++++++++++++++++++++++
pesto.h | 55 +++++
pif.c | 2 +-
pif.h | 8 +-
repair.c | 9 +-
serialise.c | 7 +
serialise.h | 1 +
siphash.h | 13 +
tap.c | 64 ++++-
util.c | 2 +-
util.h | 110 +--------
32 files changed, 1921 insertions(+), 797 deletions(-)
create mode 100644 common.h
create mode 100644 pesto.1
create mode 100644 pesto.c
create mode 100644 pesto.h
--
2.53.0
3
28
03 May '26
The code parsing command line options into forwarding rules has now been
decoupled from most of passt/pasta's internals. This is good, because
we'll soon want to share it with a configuration update client.
Make the next step by moving this code into fwd_rule.[ch].
Signed-off-by: David Gibson <david(a)gibson.dropbear.id.au>
Reviewd-by: Laurent Vivier <lvivier(a)redhat.com>
---
conf.c | 376 +------------------------------------------
fwd.c | 94 -----------
fwd.h | 33 ----
fwd_rule.c | 464 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
fwd_rule.h | 36 ++++-
5 files changed, 502 insertions(+), 501 deletions(-)
diff --git a/conf.c b/conf.c
index b470b0d8..5aacfe0f 100644
--- a/conf.c
+++ b/conf.c
@@ -13,7 +13,6 @@
*/
#include <arpa/inet.h>
-#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
@@ -66,365 +65,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
@@ -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)
diff --git a/fwd.c b/fwd.c
index a6d75b74..728a783c 100644
--- a/fwd.c
+++ b/fwd.c
@@ -275,100 +275,6 @@ void fwd_rule_init(struct ctx *c)
c->fwd[PIF_SPLICE] = &fwd_out;
}
-/**
- * fwd_rule_add() - Validate and add a rule to a forwarding table
- * @fwd: Table to add to
- * @new: Rule to add
- *
- * Return: 0 on success, negative error code on failure
- */
-int fwd_rule_add(struct fwd_table *fwd, const struct fwd_rule *new)
-{
- /* Flags which can be set from the caller */
- const uint8_t allowed_flags = FWD_WEAK | FWD_SCAN | FWD_DUAL_STACK_ANY;
- unsigned num = (unsigned)new->last - new->first + 1;
- unsigned port;
-
- if (new->first > new->last) {
- warn("Rule has invalid port range %u-%u",
- new->first, new->last);
- return -EINVAL;
- }
- if (!new->first) {
- warn("Forwarding rule attempts to map from port 0");
- return -EINVAL;
- }
- if (!new->to ||
- (in_port_t)(new->to + new->last - new->first) < new->to) {
- warn("Forwarding rule attempts to map to port 0");
- return -EINVAL;
- }
- if (new->flags & ~allowed_flags) {
- warn("Rule has invalid flags 0x%hhx",
- new->flags & ~allowed_flags);
- return -EINVAL;
- }
- if (new->flags & FWD_DUAL_STACK_ANY) {
- if (!inany_equals(&new->addr, &inany_any6)) {
- char astr[INANY_ADDRSTRLEN];
-
- warn("Dual stack rule has non-wildcard address %s",
- inany_ntop(&new->addr, astr, sizeof(astr)));
- return -EINVAL;
- }
- if (!(fwd->caps & FWD_CAP_IPV4)) {
- warn("Dual stack forward, but IPv4 not enabled");
- return -EINVAL;
- }
- if (!(fwd->caps & FWD_CAP_IPV6)) {
- warn("Dual stack forward, but IPv6 not enabled");
- return -EINVAL;
- }
- } else {
- if (inany_v4(&new->addr) && !(fwd->caps & FWD_CAP_IPV4)) {
- warn("IPv4 forward, but IPv4 not enabled");
- return -EINVAL;
- }
- if (!inany_v4(&new->addr) && !(fwd->caps & FWD_CAP_IPV6)) {
- warn("IPv6 forward, but IPv6 not enabled");
- return -EINVAL;
- }
- }
- if (new->proto == IPPROTO_TCP) {
- if (!(fwd->caps & FWD_CAP_TCP)) {
- warn("Can't add TCP forwarding rule, TCP not enabled");
- return -EINVAL;
- }
- } else if (new->proto == IPPROTO_UDP) {
- if (!(fwd->caps & FWD_CAP_UDP)) {
- warn("Can't add UDP forwarding rule, UDP not enabled");
- return -EINVAL;
- }
- } else {
- warn("Unsupported protocol 0x%hhx (%s) for forwarding rule",
- new->proto, ipproto_name(new->proto));
- return -EINVAL;
- }
-
- if (fwd->count >= ARRAY_SIZE(fwd->rules)) {
- warn("Too many rules (maximum %u)", ARRAY_SIZE(fwd->rules));
- return -ENOSPC;
- }
- if ((fwd->sock_count + num) > ARRAY_SIZE(fwd->socks)) {
- warn("Rules require too many listening sockets (maximum %u)",
- ARRAY_SIZE(fwd->socks));
- return -ENOSPC;
- }
-
- fwd->rulesocks[fwd->count] = &fwd->socks[fwd->sock_count];
- for (port = new->first; port <= new->last; port++)
- fwd->rulesocks[fwd->count][port - new->first] = -1;
-
- fwd->rules[fwd->count++] = *new;
- fwd->sock_count += num;
- return 0;
-}
-
/**
* fwd_rule_match() - Does a prospective flow match a given forwarding rule?
* @rule: Forwarding rule
diff --git a/fwd.h b/fwd.h
index e664d1d0..8f845d09 100644
--- a/fwd.h
+++ b/fwd.h
@@ -20,8 +20,6 @@
struct flowside;
-#define FWD_RULE_BITS 8
-#define MAX_FWD_RULES MAX_FROM_BITS(FWD_RULE_BITS)
#define FWD_NO_HINT (-1)
/**
@@ -36,36 +34,6 @@ struct fwd_listen_ref {
unsigned rule :FWD_RULE_BITS;
};
-/* Maximum number of listening sockets (per pif)
- *
- * Rationale: This lets us listen on every port for two addresses and two
- * protocols (which we need for -T auto -U auto without SO_BINDTODEVICE), plus a
- * comfortable number of extras.
- */
-#define MAX_LISTEN_SOCKS (NUM_PORTS * 5)
-
-/**
- * struct fwd_table - Forwarding state (per initiating pif)
- * @caps: Forwarding capabilities for this initiating pif
- * @count: Number of forwarding rules
- * @rules: Array of forwarding rules
- * @rulesocks: Parallel array of @rules (@count valid entries) of pointers to
- * @socks entries giving the start of the corresponding rule's
- * sockets within the larger array
- * @sock_count: Number of entries used in @socks (for all rules combined)
- * @socks: Listening sockets for forwarding
- */
-struct fwd_table {
- uint32_t caps;
- unsigned count;
- struct fwd_rule rules[MAX_FWD_RULES];
- int *rulesocks[MAX_FWD_RULES];
- unsigned sock_count;
- int socks[MAX_LISTEN_SOCKS];
-};
-
-#define PORT_BITMAP_SIZE DIV_ROUND_UP(NUM_PORTS, 8)
-
/**
* struct fwd_scan - Port scanning state for a protocol+direction
* @scan4: /proc/net fd to scan for IPv4 ports when in AUTO mode
@@ -81,7 +49,6 @@ struct fwd_scan {
#define FWD_PORT_SCAN_INTERVAL 1000 /* ms */
void fwd_rule_init(struct ctx *c);
-int fwd_rule_add(struct fwd_table *fwd, const struct fwd_rule *new);
const struct fwd_rule *fwd_rule_search(const struct fwd_table *fwd,
const struct flowside *ini,
uint8_t proto, int hint);
diff --git a/fwd_rule.c b/fwd_rule.c
index 9d489827..cd3dec04 100644
--- a/fwd_rule.c
+++ b/fwd_rule.c
@@ -15,6 +15,7 @@
* Author: David Gibson <david(a)gibson.dropbear.id.au>
*/
+#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
@@ -89,7 +90,7 @@ parse_err:
* fwd_port_map_ephemeral() - Mark ephemeral ports in a bitmap
* @map: Bitmap to update
*/
-void fwd_port_map_ephemeral(uint8_t *map)
+static void fwd_port_map_ephemeral(uint8_t *map)
{
unsigned port;
@@ -123,6 +124,7 @@ const union inany_addr *fwd_rule_addr(const struct fwd_rule *rule)
*/
__attribute__((noinline))
#endif
+/* cppcheck-suppress staticFunction */
const char *fwd_rule_fmt(const struct fwd_rule *rule, char *dst, size_t size)
{
const char *percent = *rule->ifname ? "%" : "";
@@ -199,8 +201,8 @@ static bool fwd_rule_conflicts(const struct fwd_rule *a, const struct fwd_rule *
* @rules: Existing rules against which to test
* @count: Number of rules in @rules
*/
-void fwd_rule_conflict_check(const struct fwd_rule *new,
- const struct fwd_rule *rules, size_t count)
+static void fwd_rule_conflict_check(const struct fwd_rule *new,
+ const struct fwd_rule *rules, size_t count)
{
unsigned i;
@@ -215,3 +217,459 @@ void fwd_rule_conflict_check(const struct fwd_rule *new,
fwd_rule_fmt(&rules[i], rulestr, sizeof(rulestr)));
}
}
+
+/**
+ * fwd_rule_add() - Validate and add a rule to a forwarding table
+ * @fwd: Table to add to
+ * @new: Rule to add
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int fwd_rule_add(struct fwd_table *fwd, const struct fwd_rule *new)
+{
+ /* Flags which can be set from the caller */
+ const uint8_t allowed_flags = FWD_WEAK | FWD_SCAN | FWD_DUAL_STACK_ANY;
+ unsigned num = (unsigned)new->last - new->first + 1;
+ unsigned port;
+
+ if (new->first > new->last) {
+ warn("Rule has invalid port range %u-%u",
+ new->first, new->last);
+ return -EINVAL;
+ }
+ if (!new->first) {
+ warn("Forwarding rule attempts to map from port 0");
+ return -EINVAL;
+ }
+ if (!new->to ||
+ (in_port_t)(new->to + new->last - new->first) < new->to) {
+ warn("Forwarding rule attempts to map to port 0");
+ return -EINVAL;
+ }
+ if (new->flags & ~allowed_flags) {
+ warn("Rule has invalid flags 0x%hhx",
+ new->flags & ~allowed_flags);
+ return -EINVAL;
+ }
+ if (new->flags & FWD_DUAL_STACK_ANY) {
+ if (!inany_equals(&new->addr, &inany_any6)) {
+ char astr[INANY_ADDRSTRLEN];
+
+ warn("Dual stack rule has non-wildcard address %s",
+ inany_ntop(&new->addr, astr, sizeof(astr)));
+ return -EINVAL;
+ }
+ if (!(fwd->caps & FWD_CAP_IPV4)) {
+ warn("Dual stack forward, but IPv4 not enabled");
+ return -EINVAL;
+ }
+ if (!(fwd->caps & FWD_CAP_IPV6)) {
+ warn("Dual stack forward, but IPv6 not enabled");
+ return -EINVAL;
+ }
+ } else {
+ if (inany_v4(&new->addr) && !(fwd->caps & FWD_CAP_IPV4)) {
+ warn("IPv4 forward, but IPv4 not enabled");
+ return -EINVAL;
+ }
+ if (!inany_v4(&new->addr) && !(fwd->caps & FWD_CAP_IPV6)) {
+ warn("IPv6 forward, but IPv6 not enabled");
+ return -EINVAL;
+ }
+ }
+ if (new->proto == IPPROTO_TCP) {
+ if (!(fwd->caps & FWD_CAP_TCP)) {
+ warn("Can't add TCP forwarding rule, TCP not enabled");
+ return -EINVAL;
+ }
+ } else if (new->proto == IPPROTO_UDP) {
+ if (!(fwd->caps & FWD_CAP_UDP)) {
+ warn("Can't add UDP forwarding rule, UDP not enabled");
+ return -EINVAL;
+ }
+ } else {
+ warn("Unsupported protocol 0x%hhx (%s) for forwarding rule",
+ new->proto, ipproto_name(new->proto));
+ return -EINVAL;
+ }
+
+ if (fwd->count >= ARRAY_SIZE(fwd->rules)) {
+ warn("Too many rules (maximum %u)", ARRAY_SIZE(fwd->rules));
+ return -ENOSPC;
+ }
+ if ((fwd->sock_count + num) > ARRAY_SIZE(fwd->socks)) {
+ warn("Rules require too many listening sockets (maximum %u)",
+ ARRAY_SIZE(fwd->socks));
+ return -ENOSPC;
+ }
+
+ fwd->rulesocks[fwd->count] = &fwd->socks[fwd->sock_count];
+ for (port = new->first; port <= new->last; port++)
+ fwd->rulesocks[fwd->count][port - new->first] = -1;
+
+ fwd->rules[fwd->count++] = *new;
+ fwd->sock_count += num;
+ return 0;
+}
+
+/**
+ * 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;
+}
+
+/**
+ * fwd_rule_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 fwd_rule_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_))
+
+/**
+ * fwd_rule_parse_ports() - 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 fwd_rule_parse_ports(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);
+
+ fwd_rule_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;
+
+ fwd_rule_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);
+}
+
+/**
+ * fwd_rule_parse() - Parse port configuration option
+ * @optname: Short option name, t, T, u, or U
+ * @optarg: Option argument (port specification)
+ * @fwd: Forwarding table to be updated
+ */
+void fwd_rule_parse(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) {
+ fwd_rule_parse_ports(fwd, proto,
+ &inany_loopback4, NULL,
+ spec);
+ }
+ if (fwd->caps & FWD_CAP_IPV6) {
+ fwd_rule_parse_ports(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);
+ }
+
+ fwd_rule_parse_ports(fwd, proto, addr, ifname, spec);
+}
diff --git a/fwd_rule.h b/fwd_rule.h
index 5c7b67aa..f0f4efda 100644
--- a/fwd_rule.h
+++ b/fwd_rule.h
@@ -19,6 +19,7 @@
/* Number of ports for both TCP and UDP */
#define NUM_PORTS (1U << 16)
+#define PORT_BITMAP_SIZE DIV_ROUND_UP(NUM_PORTS, 8)
/* Forwarding capability bits */
#define FWD_CAP_IPV4 BIT(0)
@@ -54,8 +55,38 @@ struct fwd_rule {
uint8_t flags;
};
+#define FWD_RULE_BITS 8
+#define MAX_FWD_RULES MAX_FROM_BITS(FWD_RULE_BITS)
+
+/* Maximum number of listening sockets (per pif)
+ *
+ * Rationale: This lets us listen on every port for two addresses and two
+ * protocols (which we need for -T auto -U auto without SO_BINDTODEVICE), plus a
+ * comfortable number of extras.
+ */
+#define MAX_LISTEN_SOCKS (NUM_PORTS * 5)
+
+/**
+ * struct fwd_table - Forwarding state (per initiating pif)
+ * @caps: Forwarding capabilities for this initiating pif
+ * @count: Number of forwarding rules
+ * @rules: Array of forwarding rules
+ * @rulesocks: Parallel array of @rules (@count valid entries) of pointers to
+ * @socks entries giving the start of the corresponding rule's
+ * sockets within the larger array
+ * @sock_count: Number of entries used in @socks (for all rules combined)
+ * @socks: Listening sockets for forwarding
+ */
+struct fwd_table {
+ uint32_t caps;
+ unsigned count;
+ struct fwd_rule rules[MAX_FWD_RULES];
+ int *rulesocks[MAX_FWD_RULES];
+ unsigned sock_count;
+ int socks[MAX_LISTEN_SOCKS];
+};
+
void fwd_probe_ephemeral(void);
-void fwd_port_map_ephemeral(uint8_t *map);
#define FWD_RULE_STRLEN \
(IPPROTO_STRLEN - 1 \
@@ -67,7 +98,6 @@ void fwd_port_map_ephemeral(uint8_t *map);
const union inany_addr *fwd_rule_addr(const struct fwd_rule *rule);
const char *fwd_rule_fmt(const struct fwd_rule *rule, char *dst, size_t size);
void fwd_rules_info(const struct fwd_rule *rules, size_t count);
-void fwd_rule_conflict_check(const struct fwd_rule *new,
- const struct fwd_rule *rules, size_t count);
+void fwd_rule_parse(char optname, const char *optarg, struct fwd_table *fwd);
#endif /* FWD_RULE_H */
--
2.53.0
3
3