From: Stefano Brivio <sbrivio@redhat.com>
To: David Gibson <david@gibson.dropbear.id.au>
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 00:26:10 +0100 [thread overview]
Message-ID: <20260113002610.5168fd6a@elisabeth> (raw)
In-Reply-To: <20260108022948.2657573-3-david@gibson.dropbear.id.au>
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).
> + 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.
> + 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:
> +
> + 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.
> + 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.
> + *
> + * 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.
> +#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
next prev parent reply other threads:[~2026-01-12 23:26 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 [this message]
2026-01-13 5:12 ` David Gibson
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=20260113002610.5168fd6a@elisabeth \
--to=sbrivio@redhat.com \
--cc=david@gibson.dropbear.id.au \
--cc=passt-dev@passt.top \
/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).