From: David Gibson <david@gibson.dropbear.id.au>
To: Stefano Brivio <sbrivio@redhat.com>
Cc: passt-dev@passt.top
Subject: Re: [PATCH v3 02/14] conf, fwd: Keep a table of our port forwarding configuration
Date: Tue, 13 Jan 2026 16:12:02 +1100 [thread overview]
Message-ID: <aWXUIl7Lw63IXH5q@zatzit> (raw)
In-Reply-To: <20260113002610.5168fd6a@elisabeth>
[-- Attachment #1: Type: text/plain, Size: 14888 bytes --]
On Tue, Jan 13, 2026 at 12:26:10AM +0100, Stefano Brivio wrote:
> On Thu, 8 Jan 2026 13:29:36 +1100
> David Gibson <david@gibson.dropbear.id.au> wrote:
>
> > At present, we set up forwarding as we parse the -t, and -u options, not
> > keeping a persistent data structure with all the details. We do have
> > some information in struct fwd_ports, but it doesn't capture all the nuance
> > that the options do.
> >
> > As a first step to generalising our forwarding model, add a table of all
> > the forwarding rules to struct fwd_ports. For now it covers only explicit
> > forwards, not automatic, and we don't do anything with it other than print
> > some additional debug information. We'll do more with it in future
> > patches.
> >
> > Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
> > ---
> > conf.c | 80 ++++++++++++++++++++++++++++++-------------------
> > fwd.c | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
> > fwd.h | 35 +++++++++++++++++++++-
> > 3 files changed, 179 insertions(+), 31 deletions(-)
> >
> > diff --git a/conf.c b/conf.c
> > index c936664b..127e69f5 100644
> > --- a/conf.c
> > +++ b/conf.c
> > @@ -137,7 +137,7 @@ static int parse_port_range(const char *s, char **endptr,
> > * @last: Last port to forward
> > * @exclude: Bitmap of ports to exclude
> > * @to: Port to translate @first to when forwarding
> > - * @weak: Ignore errors, as long as at least one port is mapped
> > + * @flags: Flags for forwarding entries
> > */
> > static void conf_ports_range_except(const struct ctx *c, char optname,
> > const char *optarg, struct fwd_ports *fwd,
> > @@ -145,10 +145,11 @@ static void conf_ports_range_except(const struct ctx *c, char optname,
> > const char *ifname,
> > uint16_t first, uint16_t last,
> > const uint8_t *exclude, uint16_t to,
> > - bool weak)
> > + uint8_t flags)
> > {
> > + unsigned delta = to - first;
> > bool bound_one = false;
> > - unsigned i;
> > + unsigned base, i;
> > int fd;
> >
> > if (first == 0) {
> > @@ -172,37 +173,45 @@ static void conf_ports_range_except(const struct ctx *c, char optname,
> > }
> > }
> >
> > - for (i = first; i <= last; i++) {
> > - if (bitmap_isset(exclude, i))
> > + for (base = first; base <= last; base++) {
> > + if (bitmap_isset(exclude, base))
> > continue;
> >
> > - if (bitmap_isset(fwd->map, i)) {
> > - warn(
> > -"Altering mapping of already mapped port number: %s", optarg);
> > - }
> > + for (i = base; i <= last; i++) {
>
> This nested loop is a bit convoluted, but I don't have better ideas and
> I suppose it goes away later (I just reached 10/14 so far).
The nested loop remains, so far. A lot of the rest of the loop body
does go away, so the overall complexity does reduce.
> > + if (bitmap_isset(exclude, i))
> > + break;
> >
> > - bitmap_set(fwd->map, i);
> > - fwd->delta[i] = to - first;
> > + if (bitmap_isset(fwd->map, i)) {
> > + warn(
> > +"Altering mapping of already mapped port number: %s", optarg);
> > + }
> >
> > - if (optname == 't')
> > - fd = tcp_listen(c, PIF_HOST, addr, ifname, i);
> > - else if (optname == 'u')
> > - fd = udp_listen(c, PIF_HOST, addr, ifname, i);
> > - else
> > - /* No way to check in advance for -T and -U */
> > - fd = 0;
> > + if (optname == 't')
> > + fd = tcp_listen(c, PIF_HOST, addr, ifname, i);
> > + else if (optname == 'u')
> > + fd = udp_listen(c, PIF_HOST, addr, ifname, i);
> > + else
> > + /* No way to check in advance for -T and -U */
> > + fd = 0;
> > +
> > + if (fd == -ENFILE || fd == -EMFILE) {
> > + die(
> > +"Can't open enough sockets for port specifier: %s",
> > + optarg);
> > + }
> >
> > - if (fd == -ENFILE || fd == -EMFILE) {
> > - die("Can't open enough sockets for port specifier: %s",
> > - optarg);
> > + if (fd >= 0) {
> > + bound_one = true;
> > + } else if (!(flags & FWD_WEAK)) {
> > + die(
> > +"Failed to bind port %u (%s) for option '-%c %s'",
> > + i, strerror_(-fd), optname, optarg);
> > + }
> > }
> >
> > - if (fd >= 0) {
> > - bound_one = true;
> > - } else if (!weak) {
> > - die("Failed to bind port %u (%s) for option '-%c %s'",
> > - i, strerror_(-fd), optname, optarg);
> > - }
> > + fwd_rule_add(fwd, flags, addr, ifname, base, i - 1,
> > + base + delta);
> > + base = i - 1;
> > }
> >
> > if (!bound_one)
> > @@ -272,7 +281,7 @@ static void conf_ports(const struct ctx *c, char optname, const char *optarg,
> > conf_ports_range_except(c, optname, optarg, fwd,
> > NULL, NULL,
> > 1, NUM_PORTS - 1, exclude,
> > - 1, true);
> > + 1, FWD_WEAK);
> > return;
> > }
> >
> > @@ -357,7 +366,7 @@ static void conf_ports(const struct ctx *c, char optname, const char *optarg,
> > conf_ports_range_except(c, optname, optarg, fwd,
> > addr, ifname,
> > 1, NUM_PORTS - 1, exclude,
> > - 1, true);
> > + 1, FWD_WEAK);
> > return;
> > }
> >
> > @@ -390,7 +399,7 @@ static void conf_ports(const struct ctx *c, char optname, const char *optarg,
> > addr, ifname,
> > orig_range.first, orig_range.last,
> > exclude,
> > - mapped_range.first, false);
> > + mapped_range.first, 0);
> > } while ((p = next_chunk(p, ',')));
> >
> > return;
> > @@ -1225,6 +1234,17 @@ dns6:
> > info(" %s", c->dns_search[i].n);
> > }
> > }
> > +
> > + info("Inbound TCP forwarding:");
> > + fwd_rules_print(&c->tcp.fwd_in);
> > + info("Inbound UDP forwarding:");
> > + fwd_rules_print(&c->udp.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->udp.fwd_out);
> > + }
> > }
> >
> > /**
> > diff --git a/fwd.c b/fwd.c
> > index 961c533f..ad398e54 100644
> > --- a/fwd.c
> > +++ b/fwd.c
> > @@ -13,6 +13,7 @@
> > * Author: David Gibson <david@gibson.dropbear.id.au>
> > */
> >
> > +#include <assert.h>
> > #include <stdint.h>
> > #include <errno.h>
> > #include <fcntl.h>
> > @@ -22,6 +23,8 @@
> >
> > #include "util.h"
> > #include "ip.h"
> > +#include "siphash.h"
> > +#include "inany.h"
> > #include "fwd.h"
> > #include "passt.h"
> > #include "lineread.h"
> > @@ -300,6 +303,19 @@ parse_err:
> > warn("Unable to parse %s", PORT_RANGE_SYSCTL);
> > }
> >
> > +/**
> > + * fwd_rule_addr() - Return match address for a rule
> > + * @rule: Forwarding rule
> > + *
> > + * Return: Rule's match address, or NULL, if it matches any address
>
> What about:
>
> matching address for rule, NULL if it matches all addresses
> > + */
> > +static const union inany_addr *fwd_rule_addr(const struct fwd_rule *rule)
> > +{
> > + if (rule->flags & FWD_DUAL_STACK_ANY)
> > + return NULL;
>
> Nit: usual newline here.
Fixed.
> > + return &rule->addr;
> > +}
> > +
> > /**
> > * fwd_port_is_ephemeral() - Is port number ephemeral?
> > * @port: Port number
> > @@ -313,6 +329,85 @@ bool fwd_port_is_ephemeral(in_port_t port)
> > return (port >= fwd_ephemeral_min) && (port <= fwd_ephemeral_max);
> > }
> >
> > +/**
> > + * fwd_rule_add() - Add a rule to a forwarding table
> > + * @fwd: Table to add to
> > + * @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
> > + * @first: First port number to forward
> > + * @last: Last port number to forward
> > + * @to: First port of target port range to map to
> > + */
> > +void fwd_rule_add(struct fwd_ports *fwd, uint8_t flags,
> > + const union inany_addr *addr, const char *ifname,
> > + in_port_t first, in_port_t last, in_port_t to)
> > +{
> > + /* Flags which can be set from the caller */
> > + const uint8_t allowed_flags = FWD_WEAK;
> > + struct fwd_rule *new;
> > + unsigned port;
> > +
> > + ASSERT(!(flags & ~allowed_flags));
> > +
> > + if (fwd->count >= ARRAY_SIZE(fwd->rules))
> > + die("Too many port forwarding ranges");
> > +
> > + new = &fwd->rules[fwd->count++];
> > + new->flags = flags;
> > +
> > + if (addr) {
> > + new->addr = *addr;
> > + } else {
> > + new->addr = inany_any6;
> > + new->flags |= FWD_DUAL_STACK_ANY;
> > + }
> > +
> > + memset(new->ifname, 0, sizeof(new->ifname));
> > + if (ifname)
> > + strncpy(new->ifname, ifname, sizeof(new->ifname));
>
> Coverity Scan complains because we copy exactly up to
> sizeof(new->ifname) bytes, and the terminator might be missing:
>
> /home/sbrivio/passt/fwd.c:368:3:
> Type: Buffer not null terminated (BUFFER_SIZE)
>
> /home/sbrivio/passt/fwd.c:351:2:
> 1. path: Condition "!(flags & -3 /* ~allowed_flags */)", taking true branch.
> /home/sbrivio/passt/fwd.c:353:2:
> 2. path: Condition "fwd->count >= 128U /* (int)(sizeof (fwd->rules) / sizeof (fwd->rules[0])) */", taking false branch.
> /home/sbrivio/passt/fwd.c:359:2:
> 3. path: Condition "addr", taking true branch.
> /home/sbrivio/passt/fwd.c:361:2:
> 4. path: Falling through to end of if statement.
> /home/sbrivio/passt/fwd.c:367:2:
> 5. path: Condition "ifname", taking true branch.
> /home/sbrivio/passt/fwd.c:368:3:
> 6. buffer_size_warning: Calling "strncpy" with a maximum size argument of 16 bytes on destination array "new->ifname" of size 16 bytes might leave the destination string unterminated.
> /home/sbrivio/passt/fwd.c:370:2:
Ah, good point. I added a check.
> > +
> > + ASSERT(first <= last);
> > + new->first = first;
> > + new->last = last;
> > +
> > + new->to = to;
> > +
> > + for (port = new->first; port <= new->last; port++) {
> > + /* Fill in the legacy data structures to match the table */
> > + bitmap_set(fwd->map, port);
> > + fwd->delta[port] = new->to - new->first;
> > + }
> > +}
> > +
> > +/**
> > + * fwd_rules_print() - Print forwarding rules for debugging
> > + * @fwd: Table to print
> > + */
> > +void fwd_rules_print(const struct fwd_ports *fwd)
> > +{
> > + unsigned i;
> > +
> > + for (i = 0; i < fwd->count; i++) {
> > + const struct fwd_rule *rule = &fwd->rules[i];
> > + const char *weak = rule->flags & FWD_WEAK ? " WEAK" : "";
>
> Should we print " might fail" or " can fail" instead of " WEAK"? This
> is for users.
Good point. THough I'm not sure "might fail" or "can fail" is
terribly clear in context either. I've gone with " (best effort)" for
now.
>
> > + const char *percent = *rule->ifname ? "%" : "";
> > + char addr[INANY_ADDRSTRLEN];
> > +
> > + inany_ntop(fwd_rule_addr(rule), addr, sizeof(addr));
> > +
> > + if (rule->first == rule->last) {
> > + info(" [%s]%s%s:%hu => %hu %s",
> > + addr, percent, rule->ifname,
> > + rule->first, rule->to, weak);
> > + } else {
> > + info(" [%s]%s%s:%hu-%hu => %hu-%hu %s",
> > + addr, percent, rule->ifname, rule->first, rule->last,
> > + rule->to, rule->last - rule->first + rule->to, weak);
> > + }
> > + }
> > +}
> > +
> > /* See enum in kernel's include/net/tcp_states.h */
> > #define UDP_LISTEN 0x07
> > #define TCP_LISTEN 0x0a
> > diff --git a/fwd.h b/fwd.h
> > index 934bab33..3dfc7959 100644
> > --- a/fwd.h
> > +++ b/fwd.h
> > @@ -16,6 +16,30 @@ struct flowside;
> > void fwd_probe_ephemeral(void);
> > bool fwd_port_is_ephemeral(in_port_t port);
> >
> > +/**
> > + * struct fwd_rule - Forwarding rule governing a range of ports
> > + * @addr: Address to forward from
> > + * @ifname: Interface to forward from
> > + * @first: First port number to forward
> > + * @last: Last port number to forward
> > + * @to: Port number to forward port @first to.
> > + * @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
>
> Usually we document bit flags inline as we define them (see struct
> tcp_tap_conn), I guess it's a bit easier to follow, but this has the
> advantage of looking less cramped (unless we add comment lines before
> the #defines, which would also be reasonable I think).
>
> Not a strong preference either way.
Keeping it as-is, for now, since some of the descriptions are quite
long so they'd wrap if put on the same line as the #define.
>
> > + *
> > + * FIXME: @addr and @ifname currently ignored for outbound tables
> > + */
> > +struct fwd_rule {
> > + union inany_addr addr;
> > + char ifname[IFNAMSIZ];
> > + in_port_t first, last, to;
>
> Having one line for each helps (me at least) visualising the size of the
> struct. Not a strong preference.
Fair point, altered.
> > +#define FWD_DUAL_STACK_ANY BIT(0)
> > +#define FWD_WEAK BIT(1)
> > + uint8_t flags;
> > +};
> > +
> > +#define MAX_FWD_RULES 128
> > +
> > /**
> > * union fwd_listen_ref - information about a single listening socket
> > * @port: Bound port number of the socket
> > @@ -44,6 +68,8 @@ enum fwd_ports_mode {
> > * @mode: Overall forwarding mode (all, none, auto, specific ports)
> > * @scan4: /proc/net fd to scan for IPv4 ports when in AUTO mode
> > * @scan6: /proc/net fd to scan for IPv6 ports when in AUTO mode
> > + * @count: Number of forwarding rules
> > + * @rules: Array of forwarding rules
> > * @map: Bitmap describing which ports are forwarded
> > * @delta: Offset between the original destination and mapped port number
> > */
> > @@ -51,14 +77,21 @@ struct fwd_ports {
> > enum fwd_ports_mode mode;
> > int scan4;
> > int scan6;
> > + unsigned count;
> > + struct fwd_rule rules[MAX_FWD_RULES];
> > uint8_t map[PORT_BITMAP_SIZE];
> > in_port_t delta[NUM_PORTS];
> > };
> >
> > #define FWD_PORT_SCAN_INTERVAL 1000 /* ms */
> >
> > +void fwd_rule_add(struct fwd_ports *fwd, uint8_t flags,
> > + const union inany_addr *addr, const char *ifname,
> > + in_port_t first, in_port_t last, in_port_t to);
> > +void fwd_rules_print(const struct fwd_ports *fwd);
> > +
> > void fwd_scan_ports_init(struct ctx *c);
> > -void fwd_scan_ports_timer(struct ctx *c, const struct timespec *now);
> > +void fwd_scan_ports_timer(struct ctx * c, const struct timespec *now);
> >
> > bool nat_inbound(const struct ctx *c, const union inany_addr *addr,
> > union inany_addr *translated);
>
> --
> Stefano
>
--
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
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
next prev parent reply other threads:[~2026-01-13 5:29 UTC|newest]
Thread overview: 28+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-01-08 2:29 [PATCH v3 00/14] Introduce forwarding table David Gibson
2026-01-08 2:29 ` [PATCH v3 01/14] inany: Extend inany_ntop() to treat NULL as a fully unspecified address David Gibson
2026-01-08 13:16 ` Laurent Vivier
2026-01-08 2:29 ` [PATCH v3 02/14] conf, fwd: Keep a table of our port forwarding configuration David Gibson
2026-01-12 23:26 ` Stefano Brivio
2026-01-13 5:12 ` David Gibson [this message]
2026-01-08 2:29 ` [PATCH v3 03/14] conf: Accurately record ifname and address for outbound forwards David Gibson
2026-01-08 2:29 ` [PATCH v3 04/14] conf, fwd: Record "auto" port forwards in forwarding table David Gibson
2026-01-12 23:26 ` Stefano Brivio
2026-01-13 5:14 ` David Gibson
2026-01-08 2:29 ` [PATCH v3 05/14] fwd: Make space to store listening sockets in forward table David Gibson
2026-01-12 23:26 ` Stefano Brivio
2026-01-13 5:28 ` David Gibson
2026-01-08 2:29 ` [PATCH v3 06/14] ip: Add ipproto_name() function David Gibson
2026-01-08 13:22 ` Laurent Vivier
2026-01-08 23:12 ` David Gibson
2026-01-08 2:29 ` [PATCH v3 07/14] fwd, tcp, udp: Set up listening sockets based on forward table David Gibson
2026-01-12 23:26 ` Stefano Brivio
2026-01-13 5:38 ` David Gibson
2026-01-08 2:29 ` [PATCH v3 08/14] tcp, udp: Remove old auto-forwarding socket arrays David Gibson
2026-01-08 2:29 ` [PATCH v3 09/14] conf, fwd: Check forwarding table for conflicting rules David Gibson
2026-01-12 23:26 ` Stefano Brivio
2026-01-13 5:41 ` David Gibson
2026-01-08 2:29 ` [PATCH v3 10/14] fwd: Generate auto-forward exclusions from socket fd tables David Gibson
2026-01-08 2:29 ` [PATCH v3 11/14] flow, fwd: Consult rules table when forwarding a new flow from socket David Gibson
2026-01-08 2:29 ` [PATCH v3 12/14] fwd: Remap ports based directly on forwarding rule David Gibson
2026-01-08 2:29 ` [PATCH v3 13/14] fwd, tcp, udp: Add forwarding rule to listening socket epoll references David Gibson
2026-01-08 2:29 ` [PATCH v3 14/14] flow, fwd: Optimise forwarding rule lookup using epoll ref when possible David Gibson
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=aWXUIl7Lw63IXH5q@zatzit \
--to=david@gibson.dropbear.id.au \
--cc=passt-dev@passt.top \
--cc=sbrivio@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://passt.top/passt
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for IMAP folder(s).