[PATCH 0/6] Unify TCP and UDP forwarding tables
This will make the dynamic update protocol and future forwarding extensions easier. Along the way I spotted a number of other minor faults, and have fixed those too. David Gibson (6): conf, fwd: Make overall forwarding mode local to conf path tcp: Remove stale description of port_to_tap field fwd: Don't initialise unused port bitmaps Fix misnamed field in struct ctx comments fwd: Split forwarding table from port scanning state fwd: Unify TCP and UDP forwarding tables conf.c | 129 ++++++++++++++++------------- flow.c | 23 ++---- fwd.c | 247 ++++++++++++++++++++++++++++++++++---------------------- fwd.h | 58 ++++++------- passt.c | 3 + passt.h | 7 +- tcp.c | 8 +- tcp.h | 11 +-- udp.c | 9 +-- udp.h | 8 +- 10 files changed, 282 insertions(+), 221 deletions(-) -- 2.53.0
@no_udp wasn't listed, but @no_tcp was listed twice.
Fixes: 1e49d194d017 ("passt, pasta: Introduce command-line options and port re-mapping")
Signed-off-by: David Gibson
This field was removed in 163dc5f18899 ("Consolidate port forwarding
configuration into a common structure"), but the corresponding comment
describing it was not. Fix the oversight.
Fixes: 163dc5f18899 ("Consolidate port forwarding configuration into a common structure")
Signed-off-by: David Gibson
The 'mode' field of struct fwd_ports records the overall forwarding mode.
Now that runtime forwarding decisions are made based on the forwarding
table, this is almost unused outside conf().
The only exception is the auto-port scanning code, which uses it to
determine if a port scan is necessary. We can instead derive that from the
forwarding table itself by checking if there are any entries with the
FWD_SCAN flag.
Once that's done, make the mode purely local to conf(). While we're there
rename the constants to FWD_MODE_* to avoid confusion with the forwarding
rule flag bits, which are also prefixed with FWD_.
Signed-off-by: David Gibson
For hsitorical reasons, struct fwd_ports contained both the new forwarding
table and some older state related to port / scanning auto-forwarding
detection. They are related, but keeping them together prevents some
future reworks we want to do.
Separate them into struct fwd_table (for the table) and struct fwd_scan
for the scanning state. Adjusting all the users makes for a logically
straightforward, but fairly extensive patch.
Signed-off-by: David Gibson
Currently TCP and UDP each have their own forwarding tables. This is
awkward in a few places, where we need switch statements to select the
correct table. More importantly, it would make things awkward and messy to
extend to other protocols in future, which we're likely to want to do.
Merge the TCP and UDP tables into a single table per (source) pif, with the
protocol given in each rule entry.
Signed-off-by: David Gibson
On Tue, 10 Mar 2026 15:16:00 +1100
David Gibson
The 'mode' field of struct fwd_ports records the overall forwarding mode. Now that runtime forwarding decisions are made based on the forwarding table, this is almost unused outside conf().
The only exception is the auto-port scanning code, which uses it to determine if a port scan is necessary. We can instead derive that from the forwarding table itself by checking if there are any entries with the FWD_SCAN flag.
Once that's done, make the mode purely local to conf(). While we're there rename the constants to FWD_MODE_* to avoid confusion with the forwarding rule flag bits, which are also prefixed with FWD_.
Signed-off-by: David Gibson
--- conf.c | 82 ++++++++++++++++++++++++++++++++++++---------------------- fwd.c | 27 ++++++++++++++----- fwd.h | 10 ------- 3 files changed, 72 insertions(+), 47 deletions(-) diff --git a/conf.c b/conf.c index 11d84536..c436b88e 100644 --- a/conf.c +++ b/conf.c @@ -199,15 +199,27 @@ static void conf_ports_range_except(const struct ctx *c, char optname, } }
+/** + * enum fwd_mode - Overall forwarding mode for a direction and protocol + */
Nit: I'm actually trying to document enums in the matching kerneldoc style when it comes to it, see for example enum udp_iov_idx. I don't think it's so much of a problem to omit it here, even though "SPEC" and "ALL" might not be that obvious. If you respin, maybe: * @FWD_MODE_UNSET Initial value, not parsed/configured yet * @FWD_MODE_SPEC Forward specified ports * @FWD_MODE_NONE No forwarded ports * @FWD_MODE_AUTO Automatic detection and forwarding based on bound ports * @FWD_MODE_ALL Bind all free ports ? -- Stefano
Two nits and one thing that might be a bit more substantial:
On Tue, 10 Mar 2026 15:16:05 +1100
David Gibson
Currently TCP and UDP each have their own forwarding tables. This is awkward in a few places, where we need switch statements to select the correct table. More importantly, it would make things awkward and messy to extend to other protocols in future, which we're likely to want to do.
Merge the TCP and UDP tables into a single table per (source) pif, with the protocol given in each rule entry.
Signed-off-by: David Gibson
--- conf.c | 65 +++++++++++------------ flow.c | 22 +++----- fwd.c | 157 ++++++++++++++++++++++++++++++++++---------------------- fwd.h | 14 +++-- passt.c | 3 ++ passt.h | 5 ++ tcp.c | 9 +--- tcp.h | 2 - udp.c | 10 +--- udp.h | 4 -- 10 files changed, 152 insertions(+), 139 deletions(-) diff --git a/conf.c b/conf.c index 80d8106d..00b56161 100644 --- a/conf.c +++ b/conf.c @@ -149,12 +149,20 @@ static void conf_ports_range_except(const struct ctx *c, char optname, { unsigned delta = to - first; unsigned base, i; + uint8_t proto;
if (first == 0) { die("Can't forward port 0 for option '-%c %s'", optname, optarg); }
+ if (optname == 't' || optname == 'T') + proto = IPPROTO_TCP; + else if (optname == 'u' || optname == 'U') + proto = IPPROTO_UDP; + else + ASSERT(0); + if (addr) { if (!c->ifi4 && inany_v4(addr)) { die("IPv4 is disabled, can't use -%c %s", @@ -184,15 +192,17 @@ static void conf_ports_range_except(const struct ctx *c, char optname, optname, optarg);
if (c->ifi4) { - fwd_rule_add(fwd, flags, &inany_loopback4, NULL, + fwd_rule_add(fwd, proto, flags, + &inany_loopback4, NULL, base, i - 1, base + delta); } if (c->ifi6) { - fwd_rule_add(fwd, flags, &inany_loopback6, NULL, + fwd_rule_add(fwd, proto, flags, + &inany_loopback6, NULL, base, i - 1, base + delta); } } else { - fwd_rule_add(fwd, flags, addr, ifname, + fwd_rule_add(fwd, proto, flags, addr, ifname, base, i - 1, base + delta); } base = i - 1; @@ -1237,15 +1247,11 @@ dns6: } }
- info("Inbound TCP forwarding:"); - fwd_rules_print(&c->tcp.fwd_in); - info("Inbound UDP forwarding:"); - fwd_rules_print(&c->udp.fwd_in); + info("Inbound forwarding:"); + fwd_rules_print(&c->fwd_in); if (c->mode == MODE_PASTA) { - info("Outbound TCP forwarding:"); - fwd_rules_print(&c->tcp.fwd_out); - info("Outbound UDP forwarding:"); - fwd_rules_print(&c->tcp.fwd_out); + info("Outbound forwarding:"); + fwd_rules_print(&c->fwd_out); } }
@@ -2115,11 +2121,9 @@ void conf(struct ctx *c, int argc, char **argv) name = getopt_long(argc, argv, optstring, options, NULL);
if (name == 't') { - conf_ports(c, name, optarg, - &c->tcp.fwd_in, &tcp_in_mode); + conf_ports(c, name, optarg, &c->fwd_in, &tcp_in_mode); } else if (name == 'u') { - conf_ports(c, name, optarg, - &c->udp.fwd_in, &udp_in_mode); + conf_ports(c, name, optarg, &c->fwd_in, &udp_in_mode); } else if (name == 'D') { struct in6_addr dns6_tmp; struct in_addr dns4_tmp; @@ -2189,13 +2193,10 @@ void conf(struct ctx *c, int argc, char **argv) do { name = getopt_long(argc, argv, optstring, options, NULL);
- if (name == 'T') { - conf_ports(c, name, optarg, - &c->tcp.fwd_out, &tcp_out_mode); - } else if (name == 'U') { - conf_ports(c, name, optarg, - &c->udp.fwd_out, &udp_out_mode); - } + if (name == 'T') + conf_ports(c, name, optarg, &c->fwd_out, &tcp_out_mode); + else if (name == 'U') + conf_ports(c, name, optarg, &c->fwd_out, &udp_out_mode); } while (name != -1);
if (!c->ifi4) @@ -2227,24 +2228,20 @@ void conf(struct ctx *c, int argc, char **argv) udp_out_mode = fwd_default;
if (tcp_in_mode == FWD_MODE_AUTO) { - conf_ports_range_except(c, 't', "auto", &c->tcp.fwd_in, - NULL, NULL, 1, NUM_PORTS - 1, - NULL, 1, FWD_SCAN); + conf_ports_range_except(c, 't', "auto", &c->fwd_in, NULL, NULL, + 1, NUM_PORTS - 1, NULL, 1, FWD_SCAN); } if (tcp_out_mode == FWD_MODE_AUTO) { - conf_ports_range_except(c, 'T', "auto", &c->tcp.fwd_out, - NULL, "lo", 1, NUM_PORTS - 1, - NULL, 1, FWD_SCAN); + conf_ports_range_except(c, 'T', "auto", &c->fwd_out, NULL, "lo", + 1, NUM_PORTS - 1, NULL, 1, FWD_SCAN); } if (udp_in_mode == FWD_MODE_AUTO) { - conf_ports_range_except(c, 'u', "auto", &c->udp.fwd_in, - NULL, NULL, 1, NUM_PORTS - 1, - NULL, 1, FWD_SCAN); + conf_ports_range_except(c, 'u', "auto", &c->fwd_in, NULL, NULL, + 1, NUM_PORTS - 1, NULL, 1, FWD_SCAN); } if (udp_out_mode == FWD_MODE_AUTO) { - conf_ports_range_except(c, 'U', "auto", &c->udp.fwd_out, - NULL, "lo", 1, NUM_PORTS - 1, - NULL, 1, FWD_SCAN); + conf_ports_range_except(c, 'U', "auto", &c->fwd_out, NULL, "lo", + 1, NUM_PORTS - 1, NULL, 1, FWD_SCAN); }
if (!c->quiet) diff --git a/flow.c b/flow.c index 4b07697e..edcbf838 100644 --- a/flow.c +++ b/flow.c @@ -520,28 +520,18 @@ struct flowside *flow_target(const struct ctx *c, union flow *flow, break;
case PIF_SPLICE: - if (proto == IPPROTO_TCP) - fwd = &c->tcp.fwd_out; - else if (proto == IPPROTO_UDP) - fwd = &c->udp.fwd_out; - else - goto nofwd; + fwd = &c->fwd_out;
- if (!(rule = fwd_rule_search(fwd, ini, rule_hint))) + if (!(rule = fwd_rule_search(fwd, ini, proto, rule_hint))) goto norule;
tgtpif = fwd_nat_from_splice(rule, proto, ini, tgt); break;
case PIF_HOST: - if (proto == IPPROTO_TCP) - fwd = &c->tcp.fwd_in; - else if (proto == IPPROTO_UDP) - fwd = &c->udp.fwd_in; - else - goto nofwd; + fwd = &c->fwd_in;
- if (!(rule = fwd_rule_search(fwd, ini, rule_hint))) + if (!(rule = fwd_rule_search(fwd, ini, proto, rule_hint))) goto norule;
tgtpif = fwd_nat_from_host(c, rule, proto, ini, tgt); @@ -1023,8 +1013,8 @@ static int flow_migrate_source_rollback(struct ctx *c, unsigned bound, int ret)
debug("...roll back migration");
- if (fwd_listen_sync(c, &c->tcp.fwd_in, &c->tcp.scan_in, - PIF_HOST, IPPROTO_TCP) < 0) + if (fwd_listen_sync(c, &c->fwd_in, PIF_HOST, + &c->tcp.scan_in, &c->udp.scan_in) < 0) die("Failed to re-establish listening sockets");
foreach_established_tcp_flow(flow) { diff --git a/fwd.c b/fwd.c index 3e0e1063..75605cba 100644 --- a/fwd.c +++ b/fwd.c @@ -334,6 +334,7 @@ bool fwd_port_is_ephemeral(in_port_t port) /** * fwd_rule_add() - Add a rule to a forwarding table * @fwd: Table to add to + * @proto: Protocol to forward * @flags: Flags for this entry * @addr: Our address to forward (NULL for both 0.0.0.0 and ::) * @ifname: Only forward from this interface name, if non-empty @@ -341,7 +342,7 @@ bool fwd_port_is_ephemeral(in_port_t port) * @last: Last port number to forward * @to: First port of target port range to map to */ -void fwd_rule_add(struct fwd_table *fwd, uint8_t flags, +void fwd_rule_add(struct fwd_table *fwd, uint8_t proto, uint8_t flags, const union inany_addr *addr, const char *ifname, in_port_t first, in_port_t last, in_port_t to) { @@ -363,6 +364,10 @@ void fwd_rule_add(struct fwd_table *fwd, uint8_t flags, char newstr[INANY_ADDRSTRLEN], rulestr[INANY_ADDRSTRLEN]; struct fwd_rule *rule = &fwd->rules[i];
+ if (proto != rule->proto) + /* Non-conflicting protocols */ + continue; + if (!inany_matches(addr, fwd_rule_addr(rule))) /* Non-conflicting addresses */ continue; @@ -378,6 +383,7 @@ void fwd_rule_add(struct fwd_table *fwd, uint8_t flags, }
new = &fwd->rules[fwd->count++]; + new->proto = proto; new->flags = flags;
if (addr) { @@ -413,27 +419,30 @@ void fwd_rule_add(struct fwd_table *fwd, uint8_t flags, * fwd_rule_match() - Does a prospective flow match a given forwarding rule? * @rule: Forwarding rule * @ini: Initiating side flow information + * @proto: Protocol to match * * Returns: true if the rule applies to the flow, false otherwise */ static bool fwd_rule_match(const struct fwd_rule *rule, - const struct flowside *ini) + const struct flowside *ini, uint8_t proto) { - return inany_matches(&ini->oaddr, fwd_rule_addr(rule)) && - ini->oport >= rule->first && ini->oport <= rule->last; + return rule->proto == proto && + inany_matches(&ini->oaddr, fwd_rule_addr(rule)) && + ini->oport >= rule->first && ini->oport <= rule->last;
Nit: we usually align things for return clauses like we do with function calls (like in the existing version), that is, here: return rule->proto == proto && inany_matches(&ini->oaddr, fwd_rule_addr(rule)) && ini->oport >= rule->first && ini->oport <= rule->last;
}
/** * fwd_rule_search() - Find a rule which matches a prospective flow * @fwd: Forwarding table * @ini: Initiating side flow information + * @proto: Protocol to match * @hint: Index of the rule in table, if known, otherwise FWD_NO_HINT * * Returns: first matching rule, or NULL if there is none */ const struct fwd_rule *fwd_rule_search(const struct fwd_table *fwd, const struct flowside *ini, - int hint) + uint8_t proto, int hint) { unsigned i;
@@ -442,7 +451,7 @@ const struct fwd_rule *fwd_rule_search(const struct fwd_table *fwd, const struct fwd_rule *rule = &fwd->rules[hint];
ASSERT((unsigned)hint < fwd->count); - if (fwd_rule_match(rule, ini)) + if (fwd_rule_match(rule, ini, proto)) return rule;
debug("Incorrect rule hint: %s:%hu does not match %s:%hu-%hu", @@ -453,7 +462,7 @@ const struct fwd_rule *fwd_rule_search(const struct fwd_table *fwd, }
for (i = 0; i < fwd->count; i++) { - if (fwd_rule_match(&fwd->rules[i], ini)) + if (fwd_rule_match(&fwd->rules[i], ini, proto)) return &fwd->rules[i]; }
@@ -481,13 +490,13 @@ void fwd_rules_print(const struct fwd_table *fwd) scan = " (auto-scan)";
if (rule->first == rule->last) { - info(" [%s]%s%s:%hu => %hu %s%s", - addr, percent, rule->ifname, - rule->first, rule->to, weak, scan); + info(" %s [%s]%s%s:%hu => %hu %s%s", + ipproto_name(rule->proto), addr, percent, + rule->ifname, rule->first, rule->to, weak, scan); } else { - info(" [%s]%s%s:%hu-%hu => %hu-%hu %s%s", - addr, percent, rule->ifname, - rule->first, rule->last, + info(" %s [%s]%s%s:%hu-%hu => %hu-%hu %s%s", + ipproto_name(rule->proto), addr, percent, + rule->ifname, rule->first, rule->last, rule->to, rule->last - rule->first + rule->to, weak, scan); } @@ -499,14 +508,13 @@ void fwd_rules_print(const struct fwd_table *fwd) * @fwd: Forwarding table * @rule: Forwarding rule * @pif: Interface to create listening sockets for - * @proto: Protocol to listen for * @scanmap: Bitmap of ports to listen for on FWD_SCAN entries * * Return: 0 on success, -1 on failure */ static int fwd_sync_one(const struct ctx *c, const struct fwd_table *fwd, - const struct fwd_rule *rule, - uint8_t pif, uint8_t proto, const uint8_t *scanmap) + const struct fwd_rule *rule, uint8_t pif, + const uint8_t *scanmap) { const union inany_addr *addr = fwd_rule_addr(rule); const char *ifname = rule->ifname; @@ -520,6 +528,7 @@ static int fwd_sync_one(const struct ctx *c, const struct fwd_table *fwd,
idx = rule - fwd->rules; ASSERT(idx < MAX_FWD_RULES); + ASSERT(!(rule->flags & FWD_SCAN && !scanmap));
for (port = rule->first; port <= rule->last; port++) { int fd = rule->socks[port - rule->first]; @@ -540,9 +549,9 @@ static int fwd_sync_one(const struct ctx *c, const struct fwd_table *fwd, continue; }
- if (proto == IPPROTO_TCP) + if (rule->proto == IPPROTO_TCP) fd = tcp_listen(c, pif, idx, addr, ifname, port); - else if (proto == IPPROTO_UDP) + else if (rule->proto == IPPROTO_UDP) fd = udp_listen(c, pif, idx, addr, ifname, port); else ASSERT(0); @@ -551,7 +560,7 @@ static int fwd_sync_one(const struct ctx *c, const struct fwd_table *fwd, char astr[INANY_ADDRSTRLEN];
warn("Listen failed for %s %s port %s%s%s/%u: %s", - pif_name(pif), ipproto_name(proto), + pif_name(pif), ipproto_name(rule->proto), inany_ntop(addr, astr, sizeof(astr)), ifname ? "%" : "", ifname ? ifname : "", port, strerror_(-fd)); @@ -570,7 +579,7 @@ static int fwd_sync_one(const struct ctx *c, const struct fwd_table *fwd, char astr[INANY_ADDRSTRLEN];
warn("All listens failed for %s %s %s%s%s/%u-%u", - pif_name(pif), ipproto_name(proto), + pif_name(pif), ipproto_name(rule->proto), inany_ntop(addr, astr, sizeof(astr)), ifname ? "%" : "", ifname ? ifname : "", rule->first, rule->last); @@ -583,17 +592,16 @@ static int fwd_sync_one(const struct ctx *c, const struct fwd_table *fwd, /** struct fwd_listen_args - arguments for fwd_listen_init_() * @c: Execution context * @fwd: Forwarding table - * @scanmap: Bitmap of ports to auto-forward + * @tcpmap: Bitmap of TCP ports to auto-forward + * @udpmap: Bitmap of TCP ports to auto-forward * @pif: Interface to create listening sockets for - * @proto: Protocol * @ret: Return code */ struct fwd_listen_args { const struct ctx *c; const struct fwd_table *fwd; - const uint8_t *scanmap; + const uint8_t *tcpmap, *udpmap; uint8_t pif; - uint8_t proto; int ret; };
@@ -611,8 +619,15 @@ static int fwd_listen_sync_(void *arg) ns_enter(a->c);
for (i = 0; i < a->fwd->count; i++) { + const uint8_t *scanmap = NULL; + + if (a->fwd->rules[i].proto == IPPROTO_TCP) + scanmap = a->tcpmap; + else if (a->fwd->rules[i].proto == IPPROTO_UDP) + scanmap = a->udpmap; + a->ret = fwd_sync_one(a->c, a->fwd, &a->fwd->rules[i], - a->pif, a->proto, a->scanmap); + a->pif, scanmap); if (a->ret < 0) break; } @@ -623,18 +638,20 @@ static int fwd_listen_sync_(void *arg) /** fwd_listen_sync() - Call fwd_listen_sync_() in correct namespace * @c: Execution context * @fwd: Forwarding information - * @scan: Scanning state for direction and protocol * @pif: Interface to create listening sockets for - * @proto: Protocol + * @tcp: Scanning state for TCP + * @udp: Scanning state for UDP * * Return: 0 on success, -1 on failure */ int fwd_listen_sync(const struct ctx *c, const struct fwd_table *fwd, - const struct fwd_scan *scan, uint8_t pif, uint8_t proto) + uint8_t pif, + const struct fwd_scan *tcp, const struct fwd_scan *udp) { struct fwd_listen_args a = { - .c = c, .fwd = fwd, .scanmap = scan->map, - .pif = pif, .proto = proto, + .c = c, .fwd = fwd, + .tcpmap = tcp->map, .udpmap = udp->map, + .pif = pif, };
if (pif == PIF_SPLICE) @@ -643,8 +660,7 @@ int fwd_listen_sync(const struct ctx *c, const struct fwd_table *fwd, fwd_listen_sync_(&a);
if (a.ret < 0) { - err("Couldn't listen on requested %s ports", - ipproto_name(proto)); + err("Couldn't listen on requested ports"); return -1; }
@@ -672,6 +688,26 @@ void fwd_listen_close(const struct fwd_table *fwd) } }
+/** fwd_listen_init() - Set up listening sockets at start up + * @c: Execution context + * + * Return: 0 on success, -1 on failure + */ +int fwd_listen_init(const struct ctx *c) +{ + if (fwd_listen_sync(c, &c->fwd_in, PIF_HOST, + &c->tcp.scan_in, &c->udp.scan_in) < 0) + return -1; + + if (c->mode == MODE_PASTA) { + if (fwd_listen_sync(c, &c->fwd_out, PIF_SPLICE, + &c->tcp.scan_out, &c->udp.scan_out) < 0) + return -1; + } + + return 0; +} + /* See enum in kernel's include/net/tcp_states.h */ #define UDP_LISTEN 0x07 #define TCP_LISTEN 0x0a @@ -717,13 +753,15 @@ static void procfs_scan_listen(int fd, unsigned int lstate, uint8_t *map) /** * has_scan_rules() - Does the given table have any FWD_SCAN rules? * @fwd: Forwarding table + * @proto: Protocol to consider */ -static bool has_scan_rules(const struct fwd_table *fwd) +static bool has_scan_rules(const struct fwd_table *fwd, uint8_t proto) { unsigned i;
for (i = 0; i < fwd->count; i++) { - if (fwd->rules[i].flags & FWD_SCAN) + if (fwd->rules[i].proto == proto && + fwd->rules[i].flags & FWD_SCAN) return true; } return false; @@ -738,7 +776,7 @@ static bool has_scan_rules(const struct fwd_table *fwd) static void fwd_scan_ports_tcp(const struct fwd_table *fwd, struct fwd_scan *scan, const uint8_t *exclude) { - if (!has_scan_rules(fwd)) + if (!has_scan_rules(fwd, IPPROTO_TCP)) return;
memset(scan->map, 0, PORT_BITMAP_SIZE); @@ -759,7 +797,7 @@ static void fwd_scan_ports_udp(const struct fwd_table *fwd, const struct fwd_scan *tcp_scan, const uint8_t *exclude) { - if (!has_scan_rules(fwd)) + if (!has_scan_rules(fwd, IPPROTO_UDP)) return;
memset(scan->map, 0, PORT_BITMAP_SIZE); @@ -781,8 +819,10 @@ static void fwd_scan_ports_udp(const struct fwd_table *fwd, * current_listen_map() - Get bitmap of which ports we're already listening on * @map: Bitmap to populate * @fwd: Forwarding table to consider + * @proto: IP protocol to consider */ -static void current_listen_map(uint8_t *map, const struct fwd_table *fwd) +static void current_listen_map(uint8_t *map, const struct fwd_table *fwd, + uint8_t proto) { unsigned i;
@@ -792,6 +832,9 @@ static void current_listen_map(uint8_t *map, const struct fwd_table *fwd) const struct fwd_rule *rule = &fwd->rules[i]; unsigned port;
+ if (rule->proto != proto) + continue; + for (port = rule->first; port <= rule->last; port++) { if (rule->socks[port - rule->first] >= 0) bitmap_set(map, port); @@ -808,16 +851,16 @@ static void fwd_scan_ports(struct ctx *c) uint8_t excl_tcp_out[PORT_BITMAP_SIZE], excl_udp_out[PORT_BITMAP_SIZE]; uint8_t excl_tcp_in[PORT_BITMAP_SIZE], excl_udp_in[PORT_BITMAP_SIZE];
- current_listen_map(excl_tcp_out, &c->tcp.fwd_in); - current_listen_map(excl_tcp_in, &c->tcp.fwd_out); - current_listen_map(excl_udp_out, &c->udp.fwd_in); - current_listen_map(excl_udp_in, &c->udp.fwd_out); + current_listen_map(excl_tcp_out, &c->fwd_in, IPPROTO_TCP); + current_listen_map(excl_tcp_in, &c->fwd_out, IPPROTO_TCP); + current_listen_map(excl_udp_out, &c->fwd_in, IPPROTO_UDP); + current_listen_map(excl_udp_in, &c->fwd_out, IPPROTO_UDP);
- fwd_scan_ports_tcp(&c->tcp.fwd_out, &c->tcp.scan_out, excl_tcp_out); - fwd_scan_ports_tcp(&c->tcp.fwd_in, &c->tcp.scan_in, excl_tcp_in); - fwd_scan_ports_udp(&c->udp.fwd_out, &c->udp.scan_out, + fwd_scan_ports_tcp(&c->fwd_out, &c->tcp.scan_out, excl_tcp_out); + fwd_scan_ports_tcp(&c->fwd_in, &c->tcp.scan_in, excl_tcp_in); + fwd_scan_ports_udp(&c->fwd_out, &c->udp.scan_out, &c->tcp.scan_out, excl_udp_out); - fwd_scan_ports_udp(&c->udp.fwd_in, &c->udp.scan_in, + fwd_scan_ports_udp(&c->fwd_in, &c->udp.scan_in, &c->tcp.scan_in, excl_udp_in); }
@@ -834,19 +877,19 @@ void fwd_scan_ports_init(struct ctx *c) c->udp.scan_in.scan4 = c->udp.scan_in.scan6 = -1; c->udp.scan_out.scan4 = c->udp.scan_out.scan6 = -1;
- if (has_scan_rules(&c->tcp.fwd_in)) { + if (has_scan_rules(&c->fwd_in, IPPROTO_TCP)) { c->tcp.scan_in.scan4 = open_in_ns(c, "/proc/net/tcp", flags); c->tcp.scan_in.scan6 = open_in_ns(c, "/proc/net/tcp6", flags); } - if (has_scan_rules(&c->udp.fwd_in)) { + if (has_scan_rules(&c->fwd_in, IPPROTO_UDP)) { c->udp.scan_in.scan4 = open_in_ns(c, "/proc/net/udp", flags); c->udp.scan_in.scan6 = open_in_ns(c, "/proc/net/udp6", flags); } - if (has_scan_rules(&c->udp.fwd_out)) { + if (has_scan_rules(&c->fwd_out, IPPROTO_TCP)) { c->tcp.scan_out.scan4 = open("/proc/net/tcp", flags); c->tcp.scan_out.scan6 = open("/proc/net/tcp6", flags); } - if (has_scan_rules(&c->udp.fwd_out)) { + if (has_scan_rules(&c->fwd_out, IPPROTO_UDP)) { c->udp.scan_out.scan4 = open("/proc/net/udp", flags); c->udp.scan_out.scan6 = open("/proc/net/udp6", flags); } @@ -873,18 +916,10 @@ void fwd_scan_ports_timer(struct ctx *c, const struct timespec *now)
fwd_scan_ports(c);
- if (!c->no_tcp) { - fwd_listen_sync(c, &c->tcp.fwd_in, &c->tcp.scan_in, - PIF_HOST, IPPROTO_TCP); - fwd_listen_sync(c, &c->tcp.fwd_out, &c->tcp.scan_out, - PIF_SPLICE, IPPROTO_TCP); - } - if (!c->no_udp) { - fwd_listen_sync(c, &c->udp.fwd_in, &c->udp.scan_in, - PIF_HOST, IPPROTO_UDP); - fwd_listen_sync(c, &c->udp.fwd_out, &c->udp.scan_out, - PIF_SPLICE, IPPROTO_UDP); - } + fwd_listen_sync(c, &c->fwd_in, PIF_HOST, + &c->tcp.scan_in, &c->udp.scan_in); + fwd_listen_sync(c, &c->fwd_out, PIF_SPLICE, + &c->tcp.scan_out, &c->udp.scan_out); }
/** diff --git a/fwd.h b/fwd.h index 1af13ad4..f6f0fa81 100644 --- a/fwd.h +++ b/fwd.h @@ -31,11 +31,12 @@ bool fwd_port_is_ephemeral(in_port_t port); * @first: First port number to forward * @last: Last port number to forward * @to: Target port for @first, port n goes to @to + (n - @first) - * @socks: Array of listening sockets for this entry + * @proto: Protocol to forward * @flags: Flag mask * FWD_DUAL_STACK_ANY - match any IPv4 or IPv6 address (@addr should be ::) * FWD_WEAK - Don't give an error if binds fail for some forwards * FWD_SCAN - Only forward if the matching port in the target is listening + * @socks: Array of listening sockets for this entry * * FIXME: @addr and @ifname currently ignored for outbound tables */ @@ -45,11 +46,12 @@ struct fwd_rule { in_port_t first; in_port_t last; in_port_t to; - int *socks; + uint8_t proto;
Nit: as a result, the comment to struct fwd_table should change to: * struct fwd_table - Table of forwarding rules, per initiating pif as those are not per-protocol anymore. And the comment to MAX_LISTEN_SOCKS ("Maximum number of listening sockets (per pif & protocol)") should also be changed but, at this point, shouldn't we double that? Or at least NUM_PORTS * 5? The rest of the series good to me, so I could apply it with the couple of style fixes I pointed out if you agree... but I guess you should change MAX_LISTEN_SOCKS (unless I'm missing something), so maybe a respin would be more convenient at that point. -- Stefano
On Tue, Mar 10, 2026 at 08:33:11PM +0100, Stefano Brivio wrote:
On Tue, 10 Mar 2026 15:16:00 +1100 David Gibson
wrote: The 'mode' field of struct fwd_ports records the overall forwarding mode. Now that runtime forwarding decisions are made based on the forwarding table, this is almost unused outside conf().
The only exception is the auto-port scanning code, which uses it to determine if a port scan is necessary. We can instead derive that from the forwarding table itself by checking if there are any entries with the FWD_SCAN flag.
Once that's done, make the mode purely local to conf(). While we're there rename the constants to FWD_MODE_* to avoid confusion with the forwarding rule flag bits, which are also prefixed with FWD_.
Signed-off-by: David Gibson
--- conf.c | 82 ++++++++++++++++++++++++++++++++++++---------------------- fwd.c | 27 ++++++++++++++----- fwd.h | 10 ------- 3 files changed, 72 insertions(+), 47 deletions(-) diff --git a/conf.c b/conf.c index 11d84536..c436b88e 100644 --- a/conf.c +++ b/conf.c @@ -199,15 +199,27 @@ static void conf_ports_range_except(const struct ctx *c, char optname, } }
+/** + * enum fwd_mode - Overall forwarding mode for a direction and protocol + */
Nit: I'm actually trying to document enums in the matching kerneldoc style when it comes to it, see for example enum udp_iov_idx.
I don't think it's so much of a problem to omit it here, even though "SPEC" and "ALL" might not be that obvious. If you respin, maybe:
* @FWD_MODE_UNSET Initial value, not parsed/configured yet * @FWD_MODE_SPEC Forward specified ports * @FWD_MODE_NONE No forwarded ports * @FWD_MODE_AUTO Automatic detection and forwarding based on bound ports * @FWD_MODE_ALL Bind all free ports
?
Done. -- David Gibson (he or they) | I'll have my music baroque, and my code david AT gibson.dropbear.id.au | minimalist, thank you, not the other way | around. http://www.ozlabs.org/~dgibson
On Tue, Mar 10, 2026 at 08:33:17PM +0100, Stefano Brivio wrote:
Two nits and one thing that might be a bit more substantial:
On Tue, 10 Mar 2026 15:16:05 +1100 David Gibson
wrote: Currently TCP and UDP each have their own forwarding tables. This is awkward in a few places, where we need switch statements to select the correct table. More importantly, it would make things awkward and messy to extend to other protocols in future, which we're likely to want to do.
Merge the TCP and UDP tables into a single table per (source) pif, with the protocol given in each rule entry.
Signed-off-by: David Gibson
[snip] static bool fwd_rule_match(const struct fwd_rule *rule, - const struct flowside *ini) + const struct flowside *ini, uint8_t proto) { - return inany_matches(&ini->oaddr, fwd_rule_addr(rule)) && - ini->oport >= rule->first && ini->oport <= rule->last; + return rule->proto == proto && + inany_matches(&ini->oaddr, fwd_rule_addr(rule)) && + ini->oport >= rule->first && ini->oport <= rule->last; Nit: we usually align things for return clauses like we do with function calls (like in the existing version), that is, here:
return rule->proto == proto && inany_matches(&ini->oaddr, fwd_rule_addr(rule)) && ini->oport >= rule->first && ini->oport <= rule->last;
Ah, right. That's one of the handful of cases where I didn't figure out how to make emacs auto-indent match our style. I tend to forget because it doesn't arise very often [snip]
diff --git a/fwd.h b/fwd.h index 1af13ad4..f6f0fa81 100644 --- a/fwd.h +++ b/fwd.h @@ -31,11 +31,12 @@ bool fwd_port_is_ephemeral(in_port_t port); * @first: First port number to forward * @last: Last port number to forward * @to: Target port for @first, port n goes to @to + (n - @first) - * @socks: Array of listening sockets for this entry + * @proto: Protocol to forward * @flags: Flag mask * FWD_DUAL_STACK_ANY - match any IPv4 or IPv6 address (@addr should be ::) * FWD_WEAK - Don't give an error if binds fail for some forwards * FWD_SCAN - Only forward if the matching port in the target is listening + * @socks: Array of listening sockets for this entry * * FIXME: @addr and @ifname currently ignored for outbound tables */ @@ -45,11 +46,12 @@ struct fwd_rule { in_port_t first; in_port_t last; in_port_t to; - int *socks; + uint8_t proto;
Nit: as a result, the comment to struct fwd_table should change to:
* struct fwd_table - Table of forwarding rules, per initiating pif
as those are not per-protocol anymore.
Good point, done.
And the comment to MAX_LISTEN_SOCKS ("Maximum number of listening sockets (per pif & protocol)") should also be changed but, at this point, shouldn't we double that? Or at least NUM_PORTS * 5?
Oh, good catch, I didn't think of that. I've updated it to NUM_PORTS * 5, and fixed the comment.
The rest of the series good to me, so I could apply it with the couple of style fixes I pointed out if you agree... but I guess you should change MAX_LISTEN_SOCKS (unless I'm missing something), so maybe a respin would be more convenient at that point.
Will do. -- David Gibson (he or they) | I'll have my music baroque, and my code david AT gibson.dropbear.id.au | minimalist, thank you, not the other way | around. http://www.ozlabs.org/~dgibson
participants (2)
-
David Gibson
-
Stefano Brivio