public inbox for passt-dev@passt.top
 help / color / mirror / code / Atom feed
From: David Gibson <david@gibson.dropbear.id.au>
To: Stefano Brivio <sbrivio@redhat.com>, passt-dev@passt.top
Cc: David Gibson <david@gibson.dropbear.id.au>
Subject: [PATCH v3 02/14] conf, fwd: Keep a table of our port forwarding configuration
Date: Thu,  8 Jan 2026 13:29:36 +1100	[thread overview]
Message-ID: <20260108022948.2657573-3-david@gibson.dropbear.id.au> (raw)
In-Reply-To: <20260108022948.2657573-1-david@gibson.dropbear.id.au>

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++) {
+			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
+ */
+static const union inany_addr *fwd_rule_addr(const struct fwd_rule *rule)
+{
+	if (rule->flags & FWD_DUAL_STACK_ANY)
+		return NULL;
+	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));
+
+	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" : "";
+		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
+ *
+ * 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;
+#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);
-- 
2.52.0


  parent reply	other threads:[~2026-01-08  2:29 UTC|newest]

Thread overview: 18+ 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 ` 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-08  2:29 ` [PATCH v3 05/14] fwd: Make space to store listening sockets in forward table 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-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-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=20260108022948.2657573-3-david@gibson.dropbear.id.au \
    --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).