public inbox for passt-dev@passt.top
 help / color / mirror / code / Atom feed
* [PATCH v2 0/9] Unify TCP and UDP forwarding tables
@ 2026-03-11 12:03 David Gibson
  2026-03-11 12:03 ` [PATCH v2 1/9] conf, fwd: Make overall forwarding mode local to conf path David Gibson
                   ` (9 more replies)
  0 siblings, 10 replies; 12+ messages in thread
From: David Gibson @ 2026-03-11 12:03 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: David Gibson

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.

Changes in v2:
 * Several documentation and whitespace changes
 * Updated MAX_LISTEN_SOCKS since it's now per pif but not per protocol
 * Actually removed old TCP tables, overlooked before
 * Added 2 additional cleanup patches

David Gibson (9):
  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
  fwd: Always open /proc/net{tcp,tcp6,udp,udp6} in pasta mode
  conf: Don't defer handling of --dns option
  conf: Parse all forwarding options at the same time

 conf.c  | 225 ++++++++++++++++++++++++++------------------------
 flow.c  |  25 ++----
 fwd.c   | 249 ++++++++++++++++++++++++++++++++++----------------------
 fwd.h   |  67 +++++++--------
 passt.c |   3 +
 passt.h |   7 +-
 tcp.c   |   8 +-
 tcp.h   |   9 +-
 udp.c   |   9 +-
 udp.h   |   8 +-
 10 files changed, 331 insertions(+), 279 deletions(-)

-- 
2.53.0


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v2 1/9] conf, fwd: Make overall forwarding mode local to conf path
  2026-03-11 12:03 [PATCH v2 0/9] Unify TCP and UDP forwarding tables David Gibson
@ 2026-03-11 12:03 ` David Gibson
  2026-03-11 12:03 ` [PATCH v2 2/9] tcp: Remove stale description of port_to_tap field David Gibson
                   ` (8 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: David Gibson @ 2026-03-11 12:03 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: 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 <david@gibson.dropbear.id.au>
---
 conf.c | 87 +++++++++++++++++++++++++++++++++++++---------------------
 fwd.c  | 27 ++++++++++++++----
 fwd.h  | 10 -------
 3 files changed, 77 insertions(+), 47 deletions(-)

diff --git a/conf.c b/conf.c
index 11d84536..a1a67f94 100644
--- a/conf.c
+++ b/conf.c
@@ -199,15 +199,32 @@ static void conf_ports_range_except(const struct ctx *c, char optname,
 	}
 }
 
+/**
+ * enum fwd_mode - Overall forwarding mode for a direction and protocol
+ * @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
+ */
+enum fwd_mode {
+	FWD_MODE_UNSET = 0,
+	FWD_MODE_SPEC,
+	FWD_MODE_NONE,
+	FWD_MODE_AUTO,
+	FWD_MODE_ALL,
+};
+
 /**
  * conf_ports() - Parse port configuration options, initialise UDP/TCP sockets
  * @c:		Execution context
  * @optname:	Short option name, t, T, u, or U
  * @optarg:	Option argument (port specification)
  * @fwd:	Pointer to @fwd_ports to be updated
+ * @mode:	Overall port forwarding mode (updated)
  */
 static void conf_ports(const struct ctx *c, char optname, const char *optarg,
-		       struct fwd_ports *fwd)
+		       struct fwd_ports *fwd, enum fwd_mode *mode)
 {
 	union inany_addr addr_buf = inany_any6, *addr = &addr_buf;
 	char buf[BUFSIZ], *spec, *ifname = NULL, *p;
@@ -216,10 +233,10 @@ static void conf_ports(const struct ctx *c, char optname, const char *optarg,
 	unsigned i;
 
 	if (!strcmp(optarg, "none")) {
-		if (fwd->mode)
+		if (*mode)
 			goto mode_conflict;
 
-		fwd->mode = FWD_NONE;
+		*mode = FWD_MODE_NONE;
 		return;
 	}
 
@@ -229,7 +246,7 @@ static void conf_ports(const struct ctx *c, char optname, const char *optarg,
 		die("UDP port forwarding requested but UDP is disabled");
 
 	if (!strcmp(optarg, "auto")) {
-		if (fwd->mode)
+		if (*mode)
 			goto mode_conflict;
 
 		if (c->mode != MODE_PASTA)
@@ -241,18 +258,18 @@ static void conf_ports(const struct ctx *c, char optname, const char *optarg,
 			warn(
 "Forwarding from addresses other than 127.0.0.1 will not work");
 		}
-		fwd->mode = FWD_AUTO;
+		*mode = FWD_MODE_AUTO;
 		return;
 	}
 
 	if (!strcmp(optarg, "all")) {
-		if (fwd->mode)
+		if (*mode)
 			goto mode_conflict;
 
 		if (c->mode == MODE_PASTA)
 			die("'all' port forwarding is only allowed for passt");
 
-		fwd->mode = FWD_ALL;
+		*mode = FWD_MODE_ALL;
 
 		/* Exclude ephemeral ports */
 		for (i = 0; i < NUM_PORTS; i++)
@@ -266,10 +283,10 @@ static void conf_ports(const struct ctx *c, char optname, const char *optarg,
 		return;
 	}
 
-	if (fwd->mode > FWD_SPEC)
+	if (*mode > FWD_MODE_SPEC)
 		die("Specific ports cannot be specified together with all/none/auto");
 
-	fwd->mode = FWD_SPEC;
+	*mode = FWD_MODE_SPEC;
 
 	strncpy(buf, optarg, sizeof(buf) - 1);
 
@@ -1525,7 +1542,11 @@ void conf(struct ctx *c, int argc, char **argv)
 	const char *logname = (c->mode == MODE_PASTA) ? "pasta" : "passt";
 	char userns[PATH_MAX] = { 0 }, netns[PATH_MAX] = { 0 };
 	bool copy_addrs_opt = false, copy_routes_opt = false;
-	enum fwd_ports_mode fwd_default = FWD_NONE;
+	enum fwd_mode tcp_out_mode = FWD_MODE_UNSET;
+	enum fwd_mode udp_out_mode = FWD_MODE_UNSET;
+	enum fwd_mode tcp_in_mode = FWD_MODE_UNSET;
+	enum fwd_mode udp_in_mode = FWD_MODE_UNSET;
+	enum fwd_mode fwd_default = FWD_MODE_NONE;
 	bool v4_only = false, v6_only = false;
 	unsigned dns4_idx = 0, dns6_idx = 0;
 	unsigned long max_mtu = IP_MAX_MTU;
@@ -1540,17 +1561,16 @@ void conf(struct ctx *c, int argc, char **argv)
 	int name, ret;
 	uid_t uid;
 	gid_t gid;
+	
 
 	if (c->mode == MODE_PASTA) {
 		c->no_dhcp_dns = c->no_dhcp_dns_search = 1;
-		fwd_default = FWD_AUTO;
+		fwd_default = FWD_MODE_AUTO;
 	}
 
 	if (tap_l2_max_len(c) - ETH_HLEN < max_mtu)
 		max_mtu = tap_l2_max_len(c) - ETH_HLEN;
 	c->mtu = ROUND_DOWN(max_mtu, sizeof(uint32_t));
-	c->tcp.fwd_in.mode = c->tcp.fwd_out.mode = FWD_UNSET;
-	c->udp.fwd_in.mode = c->udp.fwd_out.mode = FWD_UNSET;
 	memcpy(c->our_tap_mac, MAC_OUR_LAA, ETH_ALEN);
 
 	optind = 0;
@@ -2100,9 +2120,11 @@ 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);
+			conf_ports(c, name, optarg,
+				   &c->tcp.fwd_in, &tcp_in_mode);
 		} else if (name == 'u') {
-			conf_ports(c, name, optarg, &c->udp.fwd_in);
+			conf_ports(c, name, optarg,
+				   &c->udp.fwd_in, &udp_in_mode);
 		} else if (name == 'D') {
 			struct in6_addr dns6_tmp;
 			struct in_addr dns4_tmp;
@@ -2172,10 +2194,13 @@ 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);
-		else if (name == 'U')
-			conf_ports(c, name, optarg, &c->udp.fwd_out);
+		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);
+		}
 	} while (name != -1);
 
 	if (!c->ifi4)
@@ -2197,31 +2222,31 @@ void conf(struct ctx *c, int argc, char **argv)
 			if_indextoname(c->ifi6, c->pasta_ifn);
 	}
 
-	if (!c->tcp.fwd_in.mode)
-		c->tcp.fwd_in.mode = fwd_default;
-	if (!c->tcp.fwd_out.mode)
-		c->tcp.fwd_out.mode = fwd_default;
-	if (!c->udp.fwd_in.mode)
-		c->udp.fwd_in.mode = fwd_default;
-	if (!c->udp.fwd_out.mode)
-		c->udp.fwd_out.mode = fwd_default;
+	if (!tcp_in_mode)
+		tcp_in_mode = fwd_default;
+	if (!tcp_out_mode)
+		tcp_out_mode = fwd_default;
+	if (!udp_in_mode)
+		udp_in_mode = fwd_default;
+	if (!udp_out_mode)
+		udp_out_mode = fwd_default;
 
-	if (c->tcp.fwd_in.mode == FWD_AUTO) {
+	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);
 	}
-	if (c->tcp.fwd_out.mode == FWD_AUTO) {
+	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);
 	}
-	if (c->udp.fwd_in.mode == FWD_AUTO) {
+	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);
 	}
-	if (c->udp.fwd_out.mode == FWD_AUTO) {
+	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);
diff --git a/fwd.c b/fwd.c
index 4052b797..c5090fb1 100644
--- a/fwd.c
+++ b/fwd.c
@@ -717,6 +717,21 @@ 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
+ */
+static bool has_scan_rules(const struct fwd_ports *fwd)
+{
+	unsigned i;
+
+	for (i = 0; i < fwd->count; i++) {
+		if (fwd->rules[i].flags & FWD_SCAN)
+			return true;
+	}
+	return false;
+}
+
 /**
  * fwd_scan_ports_tcp() - Scan /proc to update TCP forwarding map
  * @fwd:	Forwarding information to update
@@ -724,7 +739,7 @@ static void procfs_scan_listen(int fd, unsigned int lstate, uint8_t *map)
  */
 static void fwd_scan_ports_tcp(struct fwd_ports *fwd, const uint8_t *exclude)
 {
-	if (fwd->mode != FWD_AUTO)
+	if (!has_scan_rules(fwd))
 		return;
 
 	memset(fwd->map, 0, PORT_BITMAP_SIZE);
@@ -743,7 +758,7 @@ static void fwd_scan_ports_udp(struct fwd_ports *fwd,
 			       const struct fwd_ports *tcp_fwd,
 			       const uint8_t *exclude)
 {
-	if (fwd->mode != FWD_AUTO)
+	if (!has_scan_rules(fwd))
 		return;
 
 	memset(fwd->map, 0, PORT_BITMAP_SIZE);
@@ -816,19 +831,19 @@ void fwd_scan_ports_init(struct ctx *c)
 	c->udp.fwd_in.scan4 = c->udp.fwd_in.scan6 = -1;
 	c->udp.fwd_out.scan4 = c->udp.fwd_out.scan6 = -1;
 
-	if (c->tcp.fwd_in.mode == FWD_AUTO) {
+	if (has_scan_rules(&c->tcp.fwd_in)) {
 		c->tcp.fwd_in.scan4 = open_in_ns(c, "/proc/net/tcp", flags);
 		c->tcp.fwd_in.scan6 = open_in_ns(c, "/proc/net/tcp6", flags);
 	}
-	if (c->udp.fwd_in.mode == FWD_AUTO) {
+	if (has_scan_rules(&c->udp.fwd_in)) {
 		c->udp.fwd_in.scan4 = open_in_ns(c, "/proc/net/udp", flags);
 		c->udp.fwd_in.scan6 = open_in_ns(c, "/proc/net/udp6", flags);
 	}
-	if (c->tcp.fwd_out.mode == FWD_AUTO) {
+	if (has_scan_rules(&c->tcp.fwd_out)) {
 		c->tcp.fwd_out.scan4 = open("/proc/net/tcp", flags);
 		c->tcp.fwd_out.scan6 = open("/proc/net/tcp6", flags);
 	}
-	if (c->udp.fwd_out.mode == FWD_AUTO) {
+	if (has_scan_rules(&c->udp.fwd_out)) {
 		c->udp.fwd_out.scan4 = open("/proc/net/udp", flags);
 		c->udp.fwd_out.scan6 = open("/proc/net/udp6", flags);
 	}
diff --git a/fwd.h b/fwd.h
index 7f52f76e..6d657ddc 100644
--- a/fwd.h
+++ b/fwd.h
@@ -68,14 +68,6 @@ struct fwd_listen_ref {
 	unsigned	rule :FWD_RULE_BITS;
 };
 
-enum fwd_ports_mode {
-	FWD_UNSET = 0,
-	FWD_SPEC = 1,
-	FWD_NONE,
-	FWD_AUTO,
-	FWD_ALL,
-};
-
 #define PORT_BITMAP_SIZE	DIV_ROUND_UP(NUM_PORTS, 8)
 
 /* Maximum number of listening sockets (per pif & protocol)
@@ -87,7 +79,6 @@ enum fwd_ports_mode {
 
 /**
  * fwd_ports() - Describes port forwarding for one protocol and direction
- * @mode:	Overall 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
@@ -97,7 +88,6 @@ enum fwd_ports_mode {
  * @socks:	Listening sockets for forwarding
  */
 struct fwd_ports {
-	enum fwd_ports_mode mode;
 	int scan4;
 	int scan6;
 	unsigned count;
-- 
2.53.0


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v2 2/9] tcp: Remove stale description of port_to_tap field
  2026-03-11 12:03 [PATCH v2 0/9] Unify TCP and UDP forwarding tables David Gibson
  2026-03-11 12:03 ` [PATCH v2 1/9] conf, fwd: Make overall forwarding mode local to conf path David Gibson
@ 2026-03-11 12:03 ` David Gibson
  2026-03-11 12:03 ` [PATCH v2 3/9] fwd: Don't initialise unused port bitmaps David Gibson
                   ` (7 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: David Gibson @ 2026-03-11 12:03 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: 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 <david@gibson.dropbear.id.au>
---
 tcp.h | 1 -
 1 file changed, 1 deletion(-)

diff --git a/tcp.h b/tcp.h
index 384974d1..c9455e85 100644
--- a/tcp.h
+++ b/tcp.h
@@ -38,7 +38,6 @@ extern bool peek_offset_cap;
 
 /**
  * struct tcp_ctx - Execution context for TCP routines
- * @port_to_tap:	Ports bound host-side, packets to tap or spliced
  * @fwd_in:		Port forwarding configuration for inbound packets
  * @fwd_out:		Port forwarding configuration for outbound packets
  * @timer_run:		Timestamp of most recent timer run
-- 
2.53.0


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v2 3/9] fwd: Don't initialise unused port bitmaps
  2026-03-11 12:03 [PATCH v2 0/9] Unify TCP and UDP forwarding tables David Gibson
  2026-03-11 12:03 ` [PATCH v2 1/9] conf, fwd: Make overall forwarding mode local to conf path David Gibson
  2026-03-11 12:03 ` [PATCH v2 2/9] tcp: Remove stale description of port_to_tap field David Gibson
@ 2026-03-11 12:03 ` David Gibson
  2026-03-11 12:03 ` [PATCH v2 4/9] Fix misnamed field in struct ctx comments David Gibson
                   ` (6 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: David Gibson @ 2026-03-11 12:03 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: David Gibson

Since b223bec48213 ("fwd, tcp, udp: Set up listening sockets based on
forward table"), the 'map' field of struct fwd_ports has only been used
for forwarding rules with the FWD_SCAN bit, where it is initialised from a
port scan.  However, we still pointlessly initialise it for rules with
!FWD_SCAN.  Remove the unneeded code.

Fixes: b223bec48213 ("fwd, tcp, udp: Set up listening sockets based on forward table")

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
---
 fwd.c | 7 +------
 1 file changed, 1 insertion(+), 6 deletions(-)

diff --git a/fwd.c b/fwd.c
index c5090fb1..9e455094 100644
--- a/fwd.c
+++ b/fwd.c
@@ -405,13 +405,8 @@ void fwd_rule_add(struct fwd_ports *fwd, uint8_t flags,
 	new->socks = &fwd->socks[fwd->sock_count];
 	fwd->sock_count += num;
 
-	for (port = new->first; port <= new->last; port++) {
+	for (port = new->first; port <= new->last; port++)
 		new->socks[port - new->first] = -1;
-
-		/* Fill in the legacy forwarding data structures to match the table */
-		if (!(new->flags & FWD_SCAN))
-			bitmap_set(fwd->map, port);
-	}
 }
 
 /**
-- 
2.53.0


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v2 4/9] Fix misnamed field in struct ctx comments
  2026-03-11 12:03 [PATCH v2 0/9] Unify TCP and UDP forwarding tables David Gibson
                   ` (2 preceding siblings ...)
  2026-03-11 12:03 ` [PATCH v2 3/9] fwd: Don't initialise unused port bitmaps David Gibson
@ 2026-03-11 12:03 ` David Gibson
  2026-03-11 12:03 ` [PATCH v2 5/9] fwd: Split forwarding table from port scanning state David Gibson
                   ` (5 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: David Gibson @ 2026-03-11 12:03 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: David Gibson

@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 <david@gibson.dropbear.id.au>
---
 passt.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/passt.h b/passt.h
index 299185ba..0548dacd 100644
--- a/passt.h
+++ b/passt.h
@@ -186,7 +186,7 @@ struct ip6_ctx {
  * @pasta_conf_ns:	Configure namespace after creating it
  * @no_tcp:		Disable TCP operation
  * @tcp:		Context for TCP protocol handler
- * @no_tcp:		Disable UDP operation
+ * @no_udp:		Disable UDP operation
  * @udp:		Context for UDP protocol handler
  * @no_icmp:		Disable ICMP operation
  * @icmp:		Context for ICMP protocol handler
-- 
2.53.0


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v2 5/9] fwd: Split forwarding table from port scanning state
  2026-03-11 12:03 [PATCH v2 0/9] Unify TCP and UDP forwarding tables David Gibson
                   ` (3 preceding siblings ...)
  2026-03-11 12:03 ` [PATCH v2 4/9] Fix misnamed field in struct ctx comments David Gibson
@ 2026-03-11 12:03 ` David Gibson
  2026-03-11 12:03 ` [PATCH v2 6/9] fwd: Unify TCP and UDP forwarding tables David Gibson
                   ` (4 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: David Gibson @ 2026-03-11 12:03 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: 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 <david@gibson.dropbear.id.au>
---
 conf.c |  12 +++---
 flow.c |   5 ++-
 fwd.c  | 114 +++++++++++++++++++++++++++++++--------------------------
 fwd.h  |  38 +++++++++++--------
 tcp.c  |   5 ++-
 tcp.h  |  12 ++++--
 udp.c  |   5 ++-
 udp.h  |  12 ++++--
 8 files changed, 116 insertions(+), 87 deletions(-)

diff --git a/conf.c b/conf.c
index a1a67f94..4f5a6dba 100644
--- a/conf.c
+++ b/conf.c
@@ -130,7 +130,7 @@ static int parse_port_range(const char *s, char **endptr,
  * @c:		Execution context
  * @optname:	Short option name, t, T, u, or U
  * @optarg:	Option argument (port specification)
- * @fwd:	Pointer to @fwd_ports to be updated
+ * @fwd:	Forwarding table to be updated
  * @addr:	Listening address
  * @ifname:	Listening interface
  * @first:	First port to forward
@@ -140,7 +140,7 @@ static int parse_port_range(const char *s, char **endptr,
  * @flags:	Flags for forwarding entries
  */
 static void conf_ports_range_except(const struct ctx *c, char optname,
-				    const char *optarg, struct fwd_ports *fwd,
+				    const char *optarg, struct fwd_table *fwd,
 				    const union inany_addr *addr,
 				    const char *ifname,
 				    uint16_t first, uint16_t last,
@@ -220,11 +220,11 @@ enum fwd_mode {
  * @c:		Execution context
  * @optname:	Short option name, t, T, u, or U
  * @optarg:	Option argument (port specification)
- * @fwd:	Pointer to @fwd_ports to be updated
+ * @fwd:	Forwarding table to be updated
  * @mode:	Overall port forwarding mode (updated)
  */
 static void conf_ports(const struct ctx *c, char optname, const char *optarg,
-		       struct fwd_ports *fwd, enum fwd_mode *mode)
+		       struct fwd_table *fwd, enum fwd_mode *mode)
 {
 	union inany_addr addr_buf = inany_any6, *addr = &addr_buf;
 	char buf[BUFSIZ], *spec, *ifname = NULL, *p;
@@ -1250,7 +1250,7 @@ dns6:
 		info("Outbound TCP forwarding:");
 		fwd_rules_print(&c->tcp.fwd_out);
 		info("Outbound UDP forwarding:");
-		fwd_rules_print(&c->udp.fwd_out);
+		fwd_rules_print(&c->tcp.fwd_out);
 	}
 }
 
@@ -2121,7 +2121,7 @@ void conf(struct ctx *c, int argc, char **argv)
 
 		if (name == 't') {
 			conf_ports(c, name, optarg,
-				   &c->tcp.fwd_in, &tcp_in_mode);
+				&c->tcp.fwd_in, &tcp_in_mode);
 		} else if (name == 'u') {
 			conf_ports(c, name, optarg,
 				   &c->udp.fwd_in, &udp_in_mode);
diff --git a/flow.c b/flow.c
index 5207143d..4b07697e 100644
--- a/flow.c
+++ b/flow.c
@@ -505,7 +505,7 @@ struct flowside *flow_target(const struct ctx *c, union flow *flow,
 	const struct flowside *ini = &f->side[INISIDE];
 	struct flowside *tgt = &f->side[TGTSIDE];
 	const struct fwd_rule *rule = NULL;
-	const struct fwd_ports *fwd;
+	const struct fwd_table *fwd;
 	uint8_t tgtpif = PIF_NONE;
 
 	ASSERT(flow_new_entry == flow && f->state == FLOW_STATE_INI);
@@ -1023,7 +1023,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, PIF_HOST, IPPROTO_TCP) < 0)
+	if (fwd_listen_sync(c, &c->tcp.fwd_in, &c->tcp.scan_in,
+			    PIF_HOST, IPPROTO_TCP) < 0)
 		die("Failed to re-establish listening sockets");
 
 	foreach_established_tcp_flow(flow) {
diff --git a/fwd.c b/fwd.c
index 9e455094..3e0e1063 100644
--- a/fwd.c
+++ b/fwd.c
@@ -341,7 +341,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_ports *fwd, uint8_t flags,
+void fwd_rule_add(struct fwd_table *fwd, uint8_t flags,
 		  const union inany_addr *addr, const char *ifname,
 		  in_port_t first, in_port_t last, in_port_t to)
 {
@@ -431,7 +431,7 @@ static bool fwd_rule_match(const struct fwd_rule *rule,
  *
  * Returns: first matching rule, or NULL if there is none
  */
-const struct fwd_rule *fwd_rule_search(const struct fwd_ports *fwd,
+const struct fwd_rule *fwd_rule_search(const struct fwd_table *fwd,
 				       const struct flowside *ini,
 				       int hint)
 {
@@ -464,7 +464,7 @@ const struct fwd_rule *fwd_rule_search(const struct fwd_ports *fwd,
  * fwd_rules_print() - Print forwarding rules for debugging
  * @fwd:	Table to print
  */
-void fwd_rules_print(const struct fwd_ports *fwd)
+void fwd_rules_print(const struct fwd_table *fwd)
 {
 	unsigned i;
 
@@ -504,8 +504,8 @@ void fwd_rules_print(const struct fwd_ports *fwd)
  *
  * Return: 0 on success, -1 on failure
  */
-static int fwd_sync_one(const struct ctx *c,
-			const struct fwd_ports *fwd, const struct fwd_rule *rule,
+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 union inany_addr *addr = fwd_rule_addr(rule);
@@ -582,7 +582,7 @@ static int fwd_sync_one(const struct ctx *c,
 
 /** struct fwd_listen_args - arguments for fwd_listen_init_()
  * @c:		Execution context
- * @fwd:	Forwarding information
+ * @fwd:	Forwarding table
  * @scanmap:	Bitmap of ports to auto-forward
  * @pif:	Interface to create listening sockets for
  * @proto:	Protocol
@@ -590,7 +590,7 @@ static int fwd_sync_one(const struct ctx *c,
  */
 struct fwd_listen_args {
 	const struct ctx *c;
-	const struct fwd_ports *fwd;
+	const struct fwd_table *fwd;
 	const uint8_t *scanmap;
 	uint8_t pif;
 	uint8_t proto;
@@ -612,7 +612,7 @@ static int fwd_listen_sync_(void *arg)
 
 	for (i = 0; i < a->fwd->count; i++) {
 		a->ret = fwd_sync_one(a->c, a->fwd, &a->fwd->rules[i],
-				      a->pif, a->proto, a->fwd->map);
+				      a->pif, a->proto, a->scanmap);
 		if (a->ret < 0)
 			break;
 	}
@@ -623,16 +623,18 @@ 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
  *
  * Return: 0 on success, -1 on failure
  */
-int fwd_listen_sync(const struct ctx *c, const struct fwd_ports *fwd,
-		    uint8_t pif, uint8_t proto)
+int fwd_listen_sync(const struct ctx *c, const struct fwd_table *fwd,
+		    const struct fwd_scan *scan, uint8_t pif, uint8_t proto)
 {
 	struct fwd_listen_args a = {
-		.c = c, .fwd = fwd, .pif = pif, .proto = proto,
+		.c = c, .fwd = fwd, .scanmap = scan->map,
+		.pif = pif, .proto = proto,
 	};
 
 	if (pif == PIF_SPLICE)
@@ -652,7 +654,7 @@ int fwd_listen_sync(const struct ctx *c, const struct fwd_ports *fwd,
 /** fwd_listen_close() - Close all listening sockets
  * @fwd:	Forwarding information
  */
-void fwd_listen_close(const struct fwd_ports *fwd)
+void fwd_listen_close(const struct fwd_table *fwd)
 {
 	unsigned i;
 
@@ -716,7 +718,7 @@ 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
  */
-static bool has_scan_rules(const struct fwd_ports *fwd)
+static bool has_scan_rules(const struct fwd_table *fwd)
 {
 	unsigned i;
 
@@ -729,46 +731,50 @@ static bool has_scan_rules(const struct fwd_ports *fwd)
 
 /**
  * fwd_scan_ports_tcp() - Scan /proc to update TCP forwarding map
- * @fwd:	Forwarding information to update
+ * @fwd:	Forwarding table
+ * @scan:	Scanning state to update
  * @exclude:	Ports to _not_ forward
  */
-static void fwd_scan_ports_tcp(struct fwd_ports *fwd, const uint8_t *exclude)
+static void fwd_scan_ports_tcp(const struct fwd_table *fwd,
+			       struct fwd_scan *scan, const uint8_t *exclude)
 {
 	if (!has_scan_rules(fwd))
 		return;
 
-	memset(fwd->map, 0, PORT_BITMAP_SIZE);
-	procfs_scan_listen(fwd->scan4, TCP_LISTEN, fwd->map);
-	procfs_scan_listen(fwd->scan6, TCP_LISTEN, fwd->map);
-	bitmap_and_not(fwd->map, PORT_BITMAP_SIZE, fwd->map, exclude);
+	memset(scan->map, 0, PORT_BITMAP_SIZE);
+	procfs_scan_listen(scan->scan4, TCP_LISTEN, scan->map);
+	procfs_scan_listen(scan->scan6, TCP_LISTEN, scan->map);
+	bitmap_and_not(scan->map, PORT_BITMAP_SIZE, scan->map, exclude);
 }
 
 /**
  * fwd_scan_ports_udp() - Scan /proc to update UDP forwarding map
- * @fwd:	Forwarding information to update
- * @tcp_fwd:	Corresponding TCP forwarding information
+ * @fwd:	Forwarding table
+ * @scan:	Scanning state to update
+ * @tcp_scan:	Corresponding TCP scanning state
  * @exclude:	Ports to _not_ forward
  */
-static void fwd_scan_ports_udp(struct fwd_ports *fwd,
-			       const struct fwd_ports *tcp_fwd,
+static void fwd_scan_ports_udp(const struct fwd_table *fwd,
+			       struct fwd_scan *scan,
+			       const struct fwd_scan *tcp_scan,
 			       const uint8_t *exclude)
 {
 	if (!has_scan_rules(fwd))
 		return;
 
-	memset(fwd->map, 0, PORT_BITMAP_SIZE);
-	procfs_scan_listen(fwd->scan4, UDP_LISTEN, fwd->map);
-	procfs_scan_listen(fwd->scan6, UDP_LISTEN, fwd->map);
+	memset(scan->map, 0, PORT_BITMAP_SIZE);
+	procfs_scan_listen(scan->scan4, UDP_LISTEN, scan->map);
+	procfs_scan_listen(scan->scan6, UDP_LISTEN, scan->map);
 
 	/* Also forward UDP ports with the same numbers as bound TCP ports.
 	 * This is useful for a handful of protocols (e.g. iperf3) where a TCP
 	 * control port is used to set up transfers on a corresponding UDP
 	 * port.
 	 */
-	procfs_scan_listen(tcp_fwd->scan4, TCP_LISTEN, fwd->map);
-	procfs_scan_listen(tcp_fwd->scan6, TCP_LISTEN, fwd->map);
+	procfs_scan_listen(tcp_scan->scan4, TCP_LISTEN, scan->map);
+	procfs_scan_listen(tcp_scan->scan6, TCP_LISTEN, scan->map);
 
-	bitmap_and_not(fwd->map, PORT_BITMAP_SIZE, fwd->map, exclude);
+	bitmap_and_not(scan->map, PORT_BITMAP_SIZE, scan->map, exclude);
 }
 
 /**
@@ -776,7 +782,7 @@ static void fwd_scan_ports_udp(struct fwd_ports *fwd,
  * @map:	Bitmap to populate
  * @fwd:	Forwarding table to consider
  */
-static void current_listen_map(uint8_t *map, const struct fwd_ports *fwd)
+static void current_listen_map(uint8_t *map, const struct fwd_table *fwd)
 {
 	unsigned i;
 
@@ -807,10 +813,12 @@ static void fwd_scan_ports(struct ctx *c)
 	current_listen_map(excl_udp_out, &c->udp.fwd_in);
 	current_listen_map(excl_udp_in, &c->udp.fwd_out);
 
-	fwd_scan_ports_tcp(&c->tcp.fwd_out, excl_tcp_out);
-	fwd_scan_ports_tcp(&c->tcp.fwd_in, excl_tcp_in);
-	fwd_scan_ports_udp(&c->udp.fwd_out, &c->tcp.fwd_out, excl_udp_out);
-	fwd_scan_ports_udp(&c->udp.fwd_in, &c->tcp.fwd_in, excl_udp_in);
+	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,
+			   &c->tcp.scan_out, excl_udp_out);
+	fwd_scan_ports_udp(&c->udp.fwd_in, &c->udp.scan_in,
+			   &c->tcp.scan_in, excl_udp_in);
 }
 
 /**
@@ -821,26 +829,26 @@ void fwd_scan_ports_init(struct ctx *c)
 {
 	const int flags = O_RDONLY | O_CLOEXEC;
 
-	c->tcp.fwd_in.scan4 = c->tcp.fwd_in.scan6 = -1;
-	c->tcp.fwd_out.scan4 = c->tcp.fwd_out.scan6 = -1;
-	c->udp.fwd_in.scan4 = c->udp.fwd_in.scan6 = -1;
-	c->udp.fwd_out.scan4 = c->udp.fwd_out.scan6 = -1;
+	c->tcp.scan_in.scan4 = c->tcp.scan_in.scan6 = -1;
+	c->tcp.scan_out.scan4 = c->tcp.scan_out.scan6 = -1;
+	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)) {
-		c->tcp.fwd_in.scan4 = open_in_ns(c, "/proc/net/tcp", flags);
-		c->tcp.fwd_in.scan6 = open_in_ns(c, "/proc/net/tcp6", flags);
+		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)) {
-		c->udp.fwd_in.scan4 = open_in_ns(c, "/proc/net/udp", flags);
-		c->udp.fwd_in.scan6 = open_in_ns(c, "/proc/net/udp6", flags);
+		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->tcp.fwd_out)) {
-		c->tcp.fwd_out.scan4 = open("/proc/net/tcp", flags);
-		c->tcp.fwd_out.scan6 = open("/proc/net/tcp6", flags);
+	if (has_scan_rules(&c->udp.fwd_out)) {
+		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)) {
-		c->udp.fwd_out.scan4 = open("/proc/net/udp", flags);
-		c->udp.fwd_out.scan6 = open("/proc/net/udp6", flags);
+		c->udp.scan_out.scan4 = open("/proc/net/udp", flags);
+		c->udp.scan_out.scan6 = open("/proc/net/udp6", flags);
 	}
 	fwd_scan_ports(c);
 }
@@ -866,12 +874,16 @@ 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, PIF_HOST, IPPROTO_TCP);
-		fwd_listen_sync(c, &c->tcp.fwd_out, PIF_SPLICE, IPPROTO_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, PIF_HOST, IPPROTO_UDP);
-		fwd_listen_sync(c, &c->udp.fwd_out, PIF_SPLICE, IPPROTO_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);
 	}
 }
 
diff --git a/fwd.h b/fwd.h
index 6d657ddc..1af13ad4 100644
--- a/fwd.h
+++ b/fwd.h
@@ -68,8 +68,6 @@ struct fwd_listen_ref {
 	unsigned	rule :FWD_RULE_BITS;
 };
 
-#define PORT_BITMAP_SIZE	DIV_ROUND_UP(NUM_PORTS, 8)
-
 /* Maximum number of listening sockets (per pif & protocol)
  *
  * Rationale: This lets us listen on every port for two addresses (which we need
@@ -78,41 +76,49 @@ struct fwd_listen_ref {
 #define MAX_LISTEN_SOCKS	(NUM_PORTS * 3)
 
 /**
- * fwd_ports() - Describes port forwarding for one protocol and direction
- * @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
+ * struct fwd_table - Table of forwarding rules (per protocol and ini pif)
  * @count:	Number of forwarding rules
  * @rules:	Array of forwarding rules
- * @map:	Bitmap describing which ports are forwarded
  * @sock_count:	Number of entries used in @socks
  * @socks:	Listening sockets for forwarding
  */
-struct fwd_ports {
-	int scan4;
-	int scan6;
+struct fwd_table {
 	unsigned count;
 	struct fwd_rule rules[MAX_FWD_RULES];
-	uint8_t map[PORT_BITMAP_SIZE];
 	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
+ * @scan6:	/proc/net fd to scan for IPv6 ports when in AUTO mode
+ * @map:	Bitmap describing which ports are forwarded
+ */
+struct fwd_scan {
+	int scan4;
+	int scan6;
+	uint8_t map[PORT_BITMAP_SIZE];
+};
+
 #define FWD_PORT_SCAN_INTERVAL		1000	/* ms */
 
-void fwd_rule_add(struct fwd_ports *fwd, uint8_t flags,
+void fwd_rule_add(struct fwd_table *fwd, uint8_t flags,
 		  const union inany_addr *addr, const char *ifname,
 		  in_port_t first, in_port_t last, in_port_t to);
-const struct fwd_rule *fwd_rule_search(const struct fwd_ports *fwd,
+const struct fwd_rule *fwd_rule_search(const struct fwd_table *fwd,
 				       const struct flowside *ini,
 				       int hint);
-void fwd_rules_print(const struct fwd_ports *fwd);
+void fwd_rules_print(const struct fwd_table *fwd);
 
 void fwd_scan_ports_init(struct ctx *c);
 void fwd_scan_ports_timer(struct ctx * c, const struct timespec *now);
 
-int fwd_listen_sync(const struct ctx *c, const struct fwd_ports *fwd,
-		    uint8_t pif, uint8_t proto);
-void fwd_listen_close(const struct fwd_ports *fwd);
+int fwd_listen_sync(const struct ctx *c, const struct fwd_table *fwd,
+		    const struct fwd_scan *scan, uint8_t pif, uint8_t proto);
+void fwd_listen_close(const struct fwd_table *fwd);
 
 bool nat_inbound(const struct ctx *c, const union inany_addr *addr,
 		 union inany_addr *translated);
diff --git a/tcp.c b/tcp.c
index 5808e6b8..976bfdb2 100644
--- a/tcp.c
+++ b/tcp.c
@@ -2864,11 +2864,12 @@ int tcp_init(struct ctx *c)
 
 	tcp_sock_refill_init(c);
 
-	if (fwd_listen_sync(c, &c->tcp.fwd_in, PIF_HOST, IPPROTO_TCP) < 0)
+	if (fwd_listen_sync(c, &c->tcp.fwd_in, &c->tcp.scan_in,
+			    PIF_HOST, IPPROTO_TCP) < 0)
 		return -1;
 	if (c->mode == MODE_PASTA) {
 		tcp_splice_init(c);
-		if (fwd_listen_sync(c, &c->tcp.fwd_out,
+		if (fwd_listen_sync(c, &c->tcp.fwd_out, &c->tcp.scan_out,
 				    PIF_SPLICE, IPPROTO_TCP) < 0)
 			return -1;
 	}
diff --git a/tcp.h b/tcp.h
index c9455e85..c4f200c1 100644
--- a/tcp.h
+++ b/tcp.h
@@ -38,8 +38,10 @@ extern bool peek_offset_cap;
 
 /**
  * struct tcp_ctx - Execution context for TCP routines
- * @fwd_in:		Port forwarding configuration for inbound packets
- * @fwd_out:		Port forwarding configuration for outbound packets
+ * @fwd_in:		Forwarding table for inbound flows
+ * @scan_in:		Port scanning state for inbound packets
+ * @fwd_out:		Forwarding table for outbound flows
+ * @scan_out:		Port scanning state for outbound packets
  * @timer_run:		Timestamp of most recent timer run
  * @pipe_size:		Size of pipes for spliced connections
  * @rto_max:		Maximum retry timeout (in s)
@@ -49,8 +51,10 @@ extern bool peek_offset_cap;
  * @inactivity_run:	Time we last scanned for inactive connections
  */
 struct tcp_ctx {
-	struct fwd_ports fwd_in;
-	struct fwd_ports fwd_out;
+	struct fwd_table fwd_in;
+	struct fwd_scan scan_in;
+	struct fwd_table fwd_out;
+	struct fwd_scan scan_out;
 	struct timespec timer_run;
 	size_t pipe_size;
 	int rto_max;
diff --git a/udp.c b/udp.c
index 464aa093..ed23ab7c 100644
--- a/udp.c
+++ b/udp.c
@@ -1215,12 +1215,13 @@ int udp_init(struct ctx *c)
 
 	udp_iov_init(c);
 
-	if (fwd_listen_sync(c, &c->udp.fwd_in, PIF_HOST, IPPROTO_UDP) < 0)
+	if (fwd_listen_sync(c, &c->udp.fwd_in, &c->udp.scan_in,
+			    PIF_HOST, IPPROTO_UDP) < 0)
 		return -1;
 
 	if (c->mode == MODE_PASTA) {
 		udp_splice_iov_init();
-		if (fwd_listen_sync(c, &c->udp.fwd_out,
+		if (fwd_listen_sync(c, &c->udp.fwd_out, &c->udp.scan_out,
 				    PIF_SPLICE, IPPROTO_UDP) < 0)
 			return -1;
 	}
diff --git a/udp.h b/udp.h
index 0eca1abe..785133ef 100644
--- a/udp.h
+++ b/udp.h
@@ -26,15 +26,19 @@ void udp_update_l2_buf(const unsigned char *eth_d);
 
 /**
  * struct udp_ctx - Execution context for UDP
- * @fwd_in:		Port forwarding configuration for inbound packets
- * @fwd_out:		Port forwarding configuration for outbound packets
+ * @fwd_in:		Forwarding table for inbound flows
+ * @scan_in:		Port scanning state for inbound packets
+ * @fwd_out:		Forwarding table for outbound flows
+ * @scan_out:		Port scanning state for outbound packets
  * @timer_run:		Timestamp of most recent timer run
  * @timeout:		Timeout for unidirectional flows (in s)
  * @stream_timeout:	Timeout for stream-like flows (in s)
  */
 struct udp_ctx {
-	struct fwd_ports fwd_in;
-	struct fwd_ports fwd_out;
+	struct fwd_table fwd_in;
+	struct fwd_scan scan_in;
+	struct fwd_table fwd_out;
+	struct fwd_scan scan_out;
 	struct timespec timer_run;
 	int timeout;
 	int stream_timeout;
-- 
2.53.0


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v2 6/9] fwd: Unify TCP and UDP forwarding tables
  2026-03-11 12:03 [PATCH v2 0/9] Unify TCP and UDP forwarding tables David Gibson
                   ` (4 preceding siblings ...)
  2026-03-11 12:03 ` [PATCH v2 5/9] fwd: Split forwarding table from port scanning state David Gibson
@ 2026-03-11 12:03 ` David Gibson
  2026-03-11 12:03 ` [PATCH v2 7/9] fwd: Always open /proc/net{tcp,tcp6,udp,udp6} in pasta mode David Gibson
                   ` (3 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: David Gibson @ 2026-03-11 12:03 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: 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 <david@gibson.dropbear.id.au>
---
 conf.c  |  65 ++++++++++++------------
 flow.c  |  24 +++------
 fwd.c   | 155 ++++++++++++++++++++++++++++++++++----------------------
 fwd.h   |  25 +++++----
 passt.c |   3 ++
 passt.h |   5 ++
 tcp.c   |   9 +---
 tcp.h   |   4 --
 udp.c   |  10 +---
 udp.h   |   4 --
 10 files changed, 158 insertions(+), 146 deletions(-)

diff --git a/conf.c b/conf.c
index 4f5a6dba..96aa506d 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;
@@ -1242,15 +1252,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);
 	}
 }
 
@@ -2120,11 +2126,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;
@@ -2194,13 +2198,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)
@@ -2232,24 +2233,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..735d3c5f 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) {
@@ -1158,7 +1148,7 @@ int flow_migrate_source(struct ctx *c, const struct migrate_stage *stage,
 	 * fix that is to not allow local to local migration, which arguably we
 	 * should (use namespaces for testing instead). */
 	debug("Stop listen()s");
-	fwd_listen_close(&c->tcp.fwd_in);
+	fwd_listen_close(&c->fwd_in);
 
 	debug("Sending %u flows", ntohl(count));
 
diff --git a/fwd.c b/fwd.c
index 3e0e1063..1843ec8b 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,13 +419,15 @@ 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)) &&
+	return rule->proto == proto &&
+	       inany_matches(&ini->oaddr, fwd_rule_addr(rule)) &&
 	       ini->oport >= rule->first && ini->oport <= rule->last;
 }
 
@@ -427,13 +435,14 @@ static bool fwd_rule_match(const struct fwd_rule *rule,
  * 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..958eee25 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;
 #define FWD_DUAL_STACK_ANY	BIT(0)
 #define FWD_WEAK		BIT(1)
 #define FWD_SCAN		BIT(2)
 	uint8_t flags;
+	int *socks;
 };
 
 #define FWD_RULE_BITS	8
@@ -68,15 +70,16 @@ struct fwd_listen_ref {
 	unsigned	rule :FWD_RULE_BITS;
 };
 
-/* Maximum number of listening sockets (per pif & protocol)
+/* Maximum number of listening sockets (per pif)
  *
- * Rationale: This lets us listen on every port for two addresses (which we need
- * for -T auto without SO_BINDTODEVICE), plus a comfortable number of extras.
+ * 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 * 3)
+#define MAX_LISTEN_SOCKS	(NUM_PORTS * 5)
 
 /**
- * struct fwd_table - Table of forwarding rules (per protocol and ini pif)
+ * struct fwd_table - Table of forwarding rules (per initiating pif)
  * @count:	Number of forwarding rules
  * @rules:	Array of forwarding rules
  * @sock_count:	Number of entries used in @socks
@@ -105,20 +108,22 @@ struct fwd_scan {
 
 #define FWD_PORT_SCAN_INTERVAL		1000	/* ms */
 
-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);
 const struct fwd_rule *fwd_rule_search(const struct fwd_table *fwd,
 				       const struct flowside *ini,
-				       int hint);
+				       uint8_t proto, int hint);
 void fwd_rules_print(const struct fwd_table *fwd);
 
 void fwd_scan_ports_init(struct ctx *c);
 void fwd_scan_ports_timer(struct ctx * c, const struct timespec *now);
 
 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);
 void fwd_listen_close(const struct fwd_table *fwd);
+int fwd_listen_init(const struct ctx *c);
 
 bool nat_inbound(const struct ctx *c, const union inany_addr *addr,
 		 union inany_addr *translated);
diff --git a/passt.c b/passt.c
index 7488a84c..fc3b89b8 100644
--- a/passt.c
+++ b/passt.c
@@ -401,6 +401,9 @@ int main(int argc, char **argv)
 	if ((!c.no_udp && udp_init(&c)) || (!c.no_tcp && tcp_init(&c)))
 		passt_exit(EXIT_FAILURE);
 
+	if (fwd_listen_init(&c))
+		passt_exit(EXIT_FAILURE);
+
 	proto_update_l2_buf(c.guest_mac);
 
 	if (c.ifi4 && !c.no_dhcp)
diff --git a/passt.h b/passt.h
index 0548dacd..b614bdf0 100644
--- a/passt.h
+++ b/passt.h
@@ -184,6 +184,8 @@ struct ip6_ctx {
  * @pasta_ifn:		Name of namespace interface for pasta
  * @pasta_ifi:		Index of namespace interface for pasta
  * @pasta_conf_ns:	Configure namespace after creating it
+ * @fwd_in:		Forwarding table for inbound flows
+ * @fwd_out:		Forwarding table for outbound flows
  * @no_tcp:		Disable TCP operation
  * @tcp:		Context for TCP protocol handler
  * @no_udp:		Disable UDP operation
@@ -262,6 +264,9 @@ struct ctx {
 	unsigned int pasta_ifi;
 	int pasta_conf_ns;
 
+	struct fwd_table fwd_in;
+	struct fwd_table fwd_out;
+
 	int no_tcp;
 	struct tcp_ctx tcp;
 	int no_udp;
diff --git a/tcp.c b/tcp.c
index 976bfdb2..9d91c3c8 100644
--- a/tcp.c
+++ b/tcp.c
@@ -2864,15 +2864,8 @@ int tcp_init(struct ctx *c)
 
 	tcp_sock_refill_init(c);
 
-	if (fwd_listen_sync(c, &c->tcp.fwd_in, &c->tcp.scan_in,
-			    PIF_HOST, IPPROTO_TCP) < 0)
-		return -1;
-	if (c->mode == MODE_PASTA) {
+	if (c->mode == MODE_PASTA)
 		tcp_splice_init(c);
-		if (fwd_listen_sync(c, &c->tcp.fwd_out, &c->tcp.scan_out,
-				    PIF_SPLICE, IPPROTO_TCP) < 0)
-			return -1;
-	}
 
 	peek_offset_cap = (!c->ifi4 || tcp_probe_peek_offset_cap(AF_INET)) &&
 			  (!c->ifi6 || tcp_probe_peek_offset_cap(AF_INET6));
diff --git a/tcp.h b/tcp.h
index c4f200c1..8a0eb930 100644
--- a/tcp.h
+++ b/tcp.h
@@ -38,9 +38,7 @@ extern bool peek_offset_cap;
 
 /**
  * struct tcp_ctx - Execution context for TCP routines
- * @fwd_in:		Forwarding table for inbound flows
  * @scan_in:		Port scanning state for inbound packets
- * @fwd_out:		Forwarding table for outbound flows
  * @scan_out:		Port scanning state for outbound packets
  * @timer_run:		Timestamp of most recent timer run
  * @pipe_size:		Size of pipes for spliced connections
@@ -51,9 +49,7 @@ extern bool peek_offset_cap;
  * @inactivity_run:	Time we last scanned for inactive connections
  */
 struct tcp_ctx {
-	struct fwd_table fwd_in;
 	struct fwd_scan scan_in;
-	struct fwd_table fwd_out;
 	struct fwd_scan scan_out;
 	struct timespec timer_run;
 	size_t pipe_size;
diff --git a/udp.c b/udp.c
index ed23ab7c..2275c16b 100644
--- a/udp.c
+++ b/udp.c
@@ -1215,16 +1215,8 @@ int udp_init(struct ctx *c)
 
 	udp_iov_init(c);
 
-	if (fwd_listen_sync(c, &c->udp.fwd_in, &c->udp.scan_in,
-			    PIF_HOST, IPPROTO_UDP) < 0)
-		return -1;
-
-	if (c->mode == MODE_PASTA) {
+	if (c->mode == MODE_PASTA)
 		udp_splice_iov_init();
-		if (fwd_listen_sync(c, &c->udp.fwd_out, &c->udp.scan_out,
-				    PIF_SPLICE, IPPROTO_UDP) < 0)
-			return -1;
-	}
 
 	return 0;
 }
diff --git a/udp.h b/udp.h
index 785133ef..a75d5aea 100644
--- a/udp.h
+++ b/udp.h
@@ -26,18 +26,14 @@ void udp_update_l2_buf(const unsigned char *eth_d);
 
 /**
  * struct udp_ctx - Execution context for UDP
- * @fwd_in:		Forwarding table for inbound flows
  * @scan_in:		Port scanning state for inbound packets
- * @fwd_out:		Forwarding table for outbound flows
  * @scan_out:		Port scanning state for outbound packets
  * @timer_run:		Timestamp of most recent timer run
  * @timeout:		Timeout for unidirectional flows (in s)
  * @stream_timeout:	Timeout for stream-like flows (in s)
  */
 struct udp_ctx {
-	struct fwd_table fwd_in;
 	struct fwd_scan scan_in;
-	struct fwd_table fwd_out;
 	struct fwd_scan scan_out;
 	struct timespec timer_run;
 	int timeout;
-- 
2.53.0


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v2 7/9] fwd: Always open /proc/net{tcp,tcp6,udp,udp6} in pasta mode
  2026-03-11 12:03 [PATCH v2 0/9] Unify TCP and UDP forwarding tables David Gibson
                   ` (5 preceding siblings ...)
  2026-03-11 12:03 ` [PATCH v2 6/9] fwd: Unify TCP and UDP forwarding tables David Gibson
@ 2026-03-11 12:03 ` David Gibson
  2026-03-11 12:03 ` [PATCH v2 8/9] conf: Don't defer handling of --dns option David Gibson
                   ` (2 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: David Gibson @ 2026-03-11 12:03 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: David Gibson

Currently we open these files only if have forwarding rules based on the
scanning these are used for.  We plan to allow dynamic updates to the
forwarding rules, which could add such a rule after the point
fwd_scan_ports_init() is called.  We can't open the /proc files later,
because of our self-isolation.

In any case, not opening these files when unneeded doesn't have very much
advantage.  So, in anticipation of dynamic updates, always open these files
when in pasta mode.

This also fixes an arguable small bug.  To deal with certain protocols like
iperf3, we automatically forward UDP ports if the corresponding TCP ports
are open.  However, we only open /proc/net/tcp* if we have TCP port scans.
That means that:
    $ pasta --config-net -T none -U auto
might open different UDP ports than:
    $ pasta --config-net -T auto -U auto
which is surprising behaviour.  This change removes that buglet as a side
effect.

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
---
 fwd.c | 20 ++++++++------------
 1 file changed, 8 insertions(+), 12 deletions(-)

diff --git a/fwd.c b/fwd.c
index 1843ec8b..bedbf98a 100644
--- a/fwd.c
+++ b/fwd.c
@@ -877,23 +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->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->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->fwd_out, IPPROTO_TCP)) {
+	if (c->mode == MODE_PASTA) {
 		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->fwd_out, IPPROTO_UDP)) {
 		c->udp.scan_out.scan4 = open("/proc/net/udp", flags);
 		c->udp.scan_out.scan6 = open("/proc/net/udp6", flags);
+
+		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);
+		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);
+
+		fwd_scan_ports(c);
 	}
-	fwd_scan_ports(c);
 }
 
 /* Last time we scanned for open ports */
-- 
2.53.0


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v2 8/9] conf: Don't defer handling of --dns option
  2026-03-11 12:03 [PATCH v2 0/9] Unify TCP and UDP forwarding tables David Gibson
                   ` (6 preceding siblings ...)
  2026-03-11 12:03 ` [PATCH v2 7/9] fwd: Always open /proc/net{tcp,tcp6,udp,udp6} in pasta mode David Gibson
@ 2026-03-11 12:03 ` David Gibson
  2026-03-11 12:03 ` [PATCH v2 9/9] conf: Parse all forwarding options at the same time David Gibson
  2026-03-11 21:12 ` [PATCH v2 0/9] Unify TCP and UDP forwarding tables Stefano Brivio
  9 siblings, 0 replies; 12+ messages in thread
From: David Gibson @ 2026-03-11 12:03 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: David Gibson

For various reasons we make several passes through our command line options
in conf().  First there's the main pass, then some supplemental passes for
options that couldn't be handled in the initial pass.

The --dns / -D option is handled in the second pass since f6d5a5239264
("conf: Delay handling -D option until after addresses are configured").
The reason was that it called add_dns[46]() which relied on the gateway
address already being configured which needed the first pass to complete.

However, since 0b25cac94eca ("conf: Treat --dns addresses as guest visible
addresses") that reason no longer applies - add_dns[46]() do nothing but
update tables in a very simple way.

So, move the --dns handling back into the main parsing pass.

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
---
 conf.c | 80 ++++++++++++++++++++++++++++------------------------------
 1 file changed, 39 insertions(+), 41 deletions(-)

diff --git a/conf.c b/conf.c
index 96aa506d..ffbd403d 100644
--- a/conf.c
+++ b/conf.c
@@ -2001,9 +2001,44 @@ void conf(struct ctx *c, int argc, char **argv)
 			break;
 		case 't':
 		case 'u':
-		case 'D':
 			/* Handle these later, once addresses are configured */
 			break;
+		case 'D': {
+			struct in6_addr dns6_tmp;
+			struct in_addr dns4_tmp;
+
+			if (!strcmp(optarg, "none")) {
+				c->no_dns = 1;
+
+				dns4_idx = 0;
+				memset(c->ip4.dns, 0, sizeof(c->ip4.dns));
+				c->ip4.dns[0]    = (struct in_addr){ 0 };
+				c->ip4.dns_match = (struct in_addr){ 0 };
+				c->ip4.dns_host  = (struct in_addr){ 0 };
+
+				dns6_idx = 0;
+				memset(c->ip6.dns, 0, sizeof(c->ip6.dns));
+				c->ip6.dns_match = (struct in6_addr){ 0 };
+				c->ip6.dns_host  = (struct in6_addr){ 0 };
+
+				continue;
+			}
+
+			c->no_dns = 0;
+
+			if (inet_pton(AF_INET, optarg, &dns4_tmp)) {
+				dns4_idx += add_dns4(c, &dns4_tmp, dns4_idx);
+				continue;
+			}
+
+			if (inet_pton(AF_INET6, optarg, &dns6_tmp)) {
+				dns6_idx += add_dns6(c, &dns6_tmp, dns6_idx);
+				continue;
+			}
+
+			die("Cannot use DNS address %s", optarg);
+		}
+			break;
 		case 'T':
 		case 'U':
 			if (c->mode != MODE_PASTA)
@@ -2117,53 +2152,16 @@ void conf(struct ctx *c, int argc, char **argv)
 	if (c->ifi4 && IN4_IS_ADDR_UNSPECIFIED(&c->ip4.guest_gw))
 		c->no_dhcp = 1;
 
-	/* Inbound port options and DNS can be parsed now, after IPv4/IPv6
-	 * settings
-	 */
+	/* Inbound port options can be parsed now, after IPv4/IPv6 settings */
 	fwd_probe_ephemeral();
 	optind = 0;
 	do {
 		name = getopt_long(argc, argv, optstring, options, NULL);
 
-		if (name == 't') {
+		if (name == 't')
 			conf_ports(c, name, optarg, &c->fwd_in, &tcp_in_mode);
-		} else if (name == 'u') {
+		else if (name == 'u')
 			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;
-
-			if (!strcmp(optarg, "none")) {
-				c->no_dns = 1;
-
-				dns4_idx = 0;
-				memset(c->ip4.dns, 0, sizeof(c->ip4.dns));
-				c->ip4.dns[0]    = (struct in_addr){ 0 };
-				c->ip4.dns_match = (struct in_addr){ 0 };
-				c->ip4.dns_host  = (struct in_addr){ 0 };
-
-				dns6_idx = 0;
-				memset(c->ip6.dns, 0, sizeof(c->ip6.dns));
-				c->ip6.dns_match = (struct in6_addr){ 0 };
-				c->ip6.dns_host  = (struct in6_addr){ 0 };
-
-				continue;
-			}
-
-			c->no_dns = 0;
-
-			if (inet_pton(AF_INET, optarg, &dns4_tmp)) {
-				dns4_idx += add_dns4(c, &dns4_tmp, dns4_idx);
-				continue;
-			}
-
-			if (inet_pton(AF_INET6, optarg, &dns6_tmp)) {
-				dns6_idx += add_dns6(c, &dns6_tmp, dns6_idx);
-				continue;
-			}
-
-			die("Cannot use DNS address %s", optarg);
-		}
 	} while (name != -1);
 
 	if (c->mode == MODE_PASTA)
-- 
2.53.0


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v2 9/9] conf: Parse all forwarding options at the same time
  2026-03-11 12:03 [PATCH v2 0/9] Unify TCP and UDP forwarding tables David Gibson
                   ` (7 preceding siblings ...)
  2026-03-11 12:03 ` [PATCH v2 8/9] conf: Don't defer handling of --dns option David Gibson
@ 2026-03-11 12:03 ` David Gibson
  2026-03-11 21:12 ` [PATCH v2 0/9] Unify TCP and UDP forwarding tables Stefano Brivio
  9 siblings, 0 replies; 12+ messages in thread
From: David Gibson @ 2026-03-11 12:03 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: David Gibson

We don't handle the -t and -u options in the main option parsing loop,
because they rely on some IP parameters already being finalised
(specifically, conf_ports() uses use ifi4 and ifi6 to determine if IP
versions are enabled).  So, we parse them in a second pass through the
command line.

The -T and -U options are parsed in a third pass through the command line,
because they relied on setup of the guest namespace.  However, since we
reworked their handling to use the forward table structure, that's no
longer the case.  So, move their handling into the same loop as the -t
and -u options.

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
---
 conf.c | 17 +++++------------
 1 file changed, 5 insertions(+), 12 deletions(-)

diff --git a/conf.c b/conf.c
index ffbd403d..dafac463 100644
--- a/conf.c
+++ b/conf.c
@@ -2152,7 +2152,7 @@ void conf(struct ctx *c, int argc, char **argv)
 	if (c->ifi4 && IN4_IS_ADDR_UNSPECIFIED(&c->ip4.guest_gw))
 		c->no_dhcp = 1;
 
-	/* Inbound port options can be parsed now, after IPv4/IPv6 settings */
+	/* Forwarding options can be parsed now, after IPv4/IPv6 settings */
 	fwd_probe_ephemeral();
 	optind = 0;
 	do {
@@ -2162,6 +2162,10 @@ void conf(struct ctx *c, int argc, char **argv)
 			conf_ports(c, name, optarg, &c->fwd_in, &tcp_in_mode);
 		else if (name == 'u')
 			conf_ports(c, name, optarg, &c->fwd_in, &udp_in_mode);
+		else 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->mode == MODE_PASTA)
@@ -2191,17 +2195,6 @@ void conf(struct ctx *c, int argc, char **argv)
 	if (c->mode == MODE_PASTA)
 		nl_sock_init(c, true);
 
-	/* ...and outbound port options now that namespaces are set up. */
-	optind = 0;
-	do {
-		name = getopt_long(argc, argv, optstring, options, NULL);
-
-		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)
 		c->no_dhcp = 1;
 
-- 
2.53.0


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v2 0/9] Unify TCP and UDP forwarding tables
  2026-03-11 12:03 [PATCH v2 0/9] Unify TCP and UDP forwarding tables David Gibson
                   ` (8 preceding siblings ...)
  2026-03-11 12:03 ` [PATCH v2 9/9] conf: Parse all forwarding options at the same time David Gibson
@ 2026-03-11 21:12 ` Stefano Brivio
  2026-03-11 23:33   ` David Gibson
  9 siblings, 1 reply; 12+ messages in thread
From: Stefano Brivio @ 2026-03-11 21:12 UTC (permalink / raw)
  To: David Gibson; +Cc: passt-dev

On Wed, 11 Mar 2026 23:03:05 +1100
David Gibson <david@gibson.dropbear.id.au> wrote:

> 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.
> 
> Changes in v2:
>  * Several documentation and whitespace changes
>  * Updated MAX_LISTEN_SOCKS since it's now per pif but not per protocol
>  * Actually removed old TCP tables, overlooked before
>  * Added 2 additional cleanup patches

...it's three patches added in total though, it took me a bit to
convince myself while rebasing back and forth. :)

Applied.

-- 
Stefano


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v2 0/9] Unify TCP and UDP forwarding tables
  2026-03-11 21:12 ` [PATCH v2 0/9] Unify TCP and UDP forwarding tables Stefano Brivio
@ 2026-03-11 23:33   ` David Gibson
  0 siblings, 0 replies; 12+ messages in thread
From: David Gibson @ 2026-03-11 23:33 UTC (permalink / raw)
  To: Stefano Brivio; +Cc: passt-dev

[-- Attachment #1: Type: text/plain, Size: 968 bytes --]

On Wed, Mar 11, 2026 at 10:12:34PM +0100, Stefano Brivio wrote:
> On Wed, 11 Mar 2026 23:03:05 +1100
> David Gibson <david@gibson.dropbear.id.au> wrote:
> 
> > 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.
> > 
> > Changes in v2:
> >  * Several documentation and whitespace changes
> >  * Updated MAX_LISTEN_SOCKS since it's now per pif but not per protocol
> >  * Actually removed old TCP tables, overlooked before
> >  * Added 2 additional cleanup patches
> 
> ...it's three patches added in total though, it took me a bit to
> convince myself while rebasing back and forth. :)

Oh, sorry, I must have lost track of one.

-- 
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 --]

^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2026-03-11 23:44 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-03-11 12:03 [PATCH v2 0/9] Unify TCP and UDP forwarding tables David Gibson
2026-03-11 12:03 ` [PATCH v2 1/9] conf, fwd: Make overall forwarding mode local to conf path David Gibson
2026-03-11 12:03 ` [PATCH v2 2/9] tcp: Remove stale description of port_to_tap field David Gibson
2026-03-11 12:03 ` [PATCH v2 3/9] fwd: Don't initialise unused port bitmaps David Gibson
2026-03-11 12:03 ` [PATCH v2 4/9] Fix misnamed field in struct ctx comments David Gibson
2026-03-11 12:03 ` [PATCH v2 5/9] fwd: Split forwarding table from port scanning state David Gibson
2026-03-11 12:03 ` [PATCH v2 6/9] fwd: Unify TCP and UDP forwarding tables David Gibson
2026-03-11 12:03 ` [PATCH v2 7/9] fwd: Always open /proc/net{tcp,tcp6,udp,udp6} in pasta mode David Gibson
2026-03-11 12:03 ` [PATCH v2 8/9] conf: Don't defer handling of --dns option David Gibson
2026-03-11 12:03 ` [PATCH v2 9/9] conf: Parse all forwarding options at the same time David Gibson
2026-03-11 21:12 ` [PATCH v2 0/9] Unify TCP and UDP forwarding tables Stefano Brivio
2026-03-11 23:33   ` David Gibson

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).