public inbox for passt-dev@passt.top
 help / color / mirror / code / Atom feed
* [PATCH] conf, pasta: Add --no-tap option
@ 2025-12-29  9:55 Yumei Huang
  2025-12-31 15:07 ` Stefano Brivio
                   ` (3 more replies)
  0 siblings, 4 replies; 16+ messages in thread
From: Yumei Huang @ 2025-12-29  9:55 UTC (permalink / raw)
  To: passt-dev, sbrivio; +Cc: david, yuhuang

This patch introduces a mode where we only forward loopback connections
and traffic between two namespaces (via the loopback interface, 'lo'),
without a tap device.

With this, podman can support forwarding ::1 in custom networks when using
rootlesskit for forwarding ports.

In --no-tap mode, --host-lo-to-ns-lo, --no-icmp and --no-ra is automatically
enabled. Options requiring a tap device (--ns-ifname, --ns-mac-addr,
--config-net, --outbound-if4/6) are rejected.

Link: https://bugs.passt.top/show_bug.cgi?id=149
Signed-off-by: Yumei Huang <yuhuang@redhat.com>
---
 conf.c  | 56 +++++++++++++++++++++++++++++++++++++++++---------------
 fwd.c   |  3 +++
 passt.1 |  5 +++++
 passt.h |  2 ++
 pasta.c |  3 +++
 tap.c   | 11 +++++++----
 6 files changed, 61 insertions(+), 19 deletions(-)

diff --git a/conf.c b/conf.c
index 84ae12b..353d0a5 100644
--- a/conf.c
+++ b/conf.c
@@ -1049,7 +1049,8 @@ pasta_opts:
 		"  --no-copy-addrs	DEPRECATED:\n"
 		"			Don't copy all addresses to namespace\n"
 		"  --ns-mac-addr ADDR	Set MAC address on tap interface\n"
-		"  --no-splice		Disable inbound socket splicing\n");
+		"  --no-splice		Disable inbound socket splicing\n"
+		"  --no-tap		Don't create tap device\n");
 
 	passt_exit(status);
 }
@@ -1451,6 +1452,7 @@ void conf(struct ctx *c, int argc, char **argv)
 		{"no-ndp",	no_argument,		&c->no_ndp,	1 },
 		{"no-ra",	no_argument,		&c->no_ra,	1 },
 		{"no-splice",	no_argument,		&c->no_splice,	1 },
+		{"no-tap",	no_argument,		&c->no_tap,	1 },
 		{"freebind",	no_argument,		&c->freebind,	1 },
 		{"no-map-gw",	no_argument,		&no_map_gw,	1 },
 		{"ipv4-only",	no_argument,		NULL,		'4' },
@@ -1947,8 +1949,11 @@ void conf(struct ctx *c, int argc, char **argv)
 		}
 	} while (name != -1);
 
-	if (c->mode != MODE_PASTA)
+	if (c->mode != MODE_PASTA) {
 		c->no_splice = 1;
+		if (c->no_tap)
+			die("--no-tap is for pasta mode only");
+	}
 
 	if (c->mode == MODE_PASTA && !c->pasta_conf_ns) {
 		if (copy_routes_opt)
@@ -1957,6 +1962,25 @@ void conf(struct ctx *c, int argc, char **argv)
 			die("--no-copy-addrs needs --config-net");
 	}
 
+	if (c->mode == MODE_PASTA && c->no_tap) {
+		if (c->no_splice)
+			die("--no-tap is incompatible with --no-splice");
+		if (*c->ip4.ifname_out || *c->ip6.ifname_out)
+			die("--no-tap is incompatible with --outbound-if4/6");
+		if (*c->pasta_ifn)
+			die("--no-tap is incompatible with --ns-ifname");
+		if (*c->guest_mac)
+			die("--no-tap is incompatible with --ns-mac-addr");
+		if (c->pasta_conf_ns)
+			die("--no-tap is incompatible with --config-net");
+
+		c->host_lo_to_ns_lo = 1;
+		c->no_icmp = 1;
+		c->no_ra = 1;
+		c->no_dns = 1;
+		c->no_dns_search = 1;
+	}
+
 	if (!ifi4 && *c->ip4.ifname_out)
 		ifi4 = if_nametoindex(c->ip4.ifname_out);
 
@@ -1980,9 +2004,9 @@ void conf(struct ctx *c, int argc, char **argv)
 	log_conf_parsed = true;		/* Stop printing everything */
 
 	nl_sock_init(c, false);
-	if (!v6_only)
+	if (!v6_only && !c->no_tap)
 		c->ifi4 = conf_ip4(ifi4, &c->ip4);
-	if (!v4_only)
+	if (!v4_only && !c->no_tap)
 		c->ifi6 = conf_ip6(ifi6, &c->ip6);
 
 	if (c->ifi4 && c->mtu < IPV4_MIN_MTU) {
@@ -1998,30 +2022,32 @@ void conf(struct ctx *c, int argc, char **argv)
 	    (*c->ip6.ifname_out && !c->ifi6))
 		die("External interface not usable");
 
-	if (!c->ifi4 && !c->ifi6 && !*c->pasta_ifn) {
+	if (!c->ifi4 && !c->ifi6 && !*c->pasta_ifn && !c->no_tap) {
 		strncpy(c->pasta_ifn, pasta_default_ifn,
 			sizeof(c->pasta_ifn) - 1);
 	}
 
 	if (!c->ifi4 && !v6_only) {
-		info("IPv4: no external interface as template, use local mode");
-
-		conf_ip4_local(&c->ip4);
+		if (!c->no_tap) {
+			info("IPv4: no external interface as template, use local mode");
+			conf_ip4_local(&c->ip4);
+		}
 		c->ifi4 = -1;
 	}
 
 	if (!c->ifi6 && !v4_only) {
-		info("IPv6: no external interface as template, use local mode");
-
-		conf_ip6_local(&c->ip6);
+		if (!c->no_tap) {
+			info("IPv6: no external interface as template, use local mode");
+			conf_ip6_local(&c->ip6);
+		}
 		c->ifi6 = -1;
 	}
 
-	if (c->ifi4 && !no_map_gw &&
+	if (c->ifi4 > 0 && !no_map_gw &&
 	    IN4_IS_ADDR_UNSPECIFIED(&c->ip4.map_host_loopback))
 		c->ip4.map_host_loopback = c->ip4.guest_gw;
 
-	if (c->ifi6 && !no_map_gw &&
+	if (c->ifi6 > 0 && !no_map_gw &&
 	    IN6_IS_ADDR_UNSPECIFIED(&c->ip6.map_host_loopback))
 		c->ip6.map_host_loopback = c->ip6.guest_gw;
 
@@ -2116,10 +2142,10 @@ void conf(struct ctx *c, int argc, char **argv)
 			conf_ports(c, name, optarg, &c->udp.fwd_out);
 	} while (name != -1);
 
-	if (!c->ifi4)
+	if (c->ifi4 <= 0)
 		c->no_dhcp = 1;
 
-	if (!c->ifi6) {
+	if (c->ifi6 <= 0) {
 		c->no_ndp = 1;
 		c->no_dhcpv6 = 1;
 	} else if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addr)) {
diff --git a/fwd.c b/fwd.c
index 44a0e10..2f4a89a 100644
--- a/fwd.c
+++ b/fwd.c
@@ -780,6 +780,9 @@ uint8_t fwd_nat_from_host(const struct ctx *c, uint8_t proto,
 		return PIF_SPLICE;
 	}
 
+	if (c->no_tap)
+		return PIF_NONE;
+
 	if (!nat_inbound(c, &ini->eaddr, &tgt->oaddr)) {
 		if (inany_v4(&ini->eaddr)) {
 			if (IN4_IS_ADDR_UNSPECIFIED(&c->ip4.our_tap_addr))
diff --git a/passt.1 b/passt.1
index db0d662..2d643f7 100644
--- a/passt.1
+++ b/passt.1
@@ -755,6 +755,11 @@ Default is to let the tap driver build a pseudorandom hardware address.
 Disable the bypass path for inbound, local traffic. See the section \fBHandling
 of local traffic in pasta\fR in the \fBNOTES\fR for more details.
 
+.TP
+.BR \-\-no-tap
+Do not create a tap device in the namespace. In this mode, only local loopback
+traffic between namespaces is forwarded using splice.
+
 .SH EXAMPLES
 
 .SS \fBpasta
diff --git a/passt.h b/passt.h
index 79d01dd..0c1ec4c 100644
--- a/passt.h
+++ b/passt.h
@@ -200,6 +200,7 @@ struct ip6_ctx {
  * @no_ndp:		Disable NDP handler altogether
  * @no_ra:		Disable router advertisements
  * @no_splice:		Disable socket splicing for inbound traffic
+ * @no_tap:		Do not create tap device
  * @host_lo_to_ns_lo:	Map host loopback addresses to ns loopback addresses
  * @freebind:		Allow binding of non-local addresses for forwarding
  * @low_wmem:		Low probed net.core.wmem_max
@@ -277,6 +278,7 @@ struct ctx {
 	int no_ndp;
 	int no_ra;
 	int no_splice;
+	int no_tap;
 	int host_lo_to_ns_lo;
 	int freebind;
 
diff --git a/pasta.c b/pasta.c
index 0ddd6b0..3510ec5 100644
--- a/pasta.c
+++ b/pasta.c
@@ -316,6 +316,9 @@ void pasta_ns_conf(struct ctx *c)
 		die("Couldn't bring up loopback interface in namespace: %s",
 		    strerror_(-rc));
 
+	if (c->no_tap)
+		return;
+
 	/* Get or set MAC in target namespace */
 	if (MAC_IS_ZERO(c->guest_mac))
 		nl_link_get_mac(nl_sock_ns, c->pasta_ifi, c->guest_mac);
diff --git a/tap.c b/tap.c
index 9d1344b..9b4eedc 100644
--- a/tap.c
+++ b/tap.c
@@ -1491,13 +1491,16 @@ static int tap_ns_tun(void *arg)
  */
 static void tap_sock_tun_init(struct ctx *c)
 {
-	NS_CALL(tap_ns_tun, c);
-	if (c->fd_tap == -1)
-		die("Failed to set up tap device in namespace");
+	if (!c->no_tap) {
+		NS_CALL(tap_ns_tun, c);
+		if (c->fd_tap == -1)
+			die("Failed to set up tap device in namespace");
+	}
 
 	pasta_ns_conf(c);
 
-	tap_start_connection(c);
+	if (!c->no_tap)
+		tap_start_connection(c);
 }
 
 /**
-- 
2.49.0


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

* Re: [PATCH] conf, pasta: Add --no-tap option
  2025-12-29  9:55 [PATCH] conf, pasta: Add --no-tap option Yumei Huang
@ 2025-12-31 15:07 ` Stefano Brivio
  2026-01-05  4:18 ` David Gibson
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 16+ messages in thread
From: Stefano Brivio @ 2025-12-31 15:07 UTC (permalink / raw)
  To: Yumei Huang; +Cc: passt-dev, david, Paul Holzinger

Cc'ing Paul with full quote for review, as he's the one who will
probably take care of using this from Podman.

By the way, I didn't finish reviewing this yet, but, so far, it looks
simpler than I thought!

I still need to find a moment to use my little hammer on it, and find
out if it's perhaps a bit too simple. :)

On Mon, 29 Dec 2025 17:55:58 +0800
Yumei Huang <yuhuang@redhat.com> wrote:

> This patch introduces a mode where we only forward loopback connections
> and traffic between two namespaces (via the loopback interface, 'lo'),
> without a tap device.
> 
> With this, podman can support forwarding ::1 in custom networks when using
> rootlesskit for forwarding ports.
> 
> In --no-tap mode, --host-lo-to-ns-lo, --no-icmp and --no-ra is automatically
> enabled. Options requiring a tap device (--ns-ifname, --ns-mac-addr,
> --config-net, --outbound-if4/6) are rejected.
> 
> Link: https://bugs.passt.top/show_bug.cgi?id=149
> Signed-off-by: Yumei Huang <yuhuang@redhat.com>
> ---
>  conf.c  | 56 +++++++++++++++++++++++++++++++++++++++++---------------
>  fwd.c   |  3 +++
>  passt.1 |  5 +++++
>  passt.h |  2 ++
>  pasta.c |  3 +++
>  tap.c   | 11 +++++++----
>  6 files changed, 61 insertions(+), 19 deletions(-)
> 
> diff --git a/conf.c b/conf.c
> index 84ae12b..353d0a5 100644
> --- a/conf.c
> +++ b/conf.c
> @@ -1049,7 +1049,8 @@ pasta_opts:
>  		"  --no-copy-addrs	DEPRECATED:\n"
>  		"			Don't copy all addresses to namespace\n"
>  		"  --ns-mac-addr ADDR	Set MAC address on tap interface\n"
> -		"  --no-splice		Disable inbound socket splicing\n");
> +		"  --no-splice		Disable inbound socket splicing\n"
> +		"  --no-tap		Don't create tap device\n");
>  
>  	passt_exit(status);
>  }
> @@ -1451,6 +1452,7 @@ void conf(struct ctx *c, int argc, char **argv)
>  		{"no-ndp",	no_argument,		&c->no_ndp,	1 },
>  		{"no-ra",	no_argument,		&c->no_ra,	1 },
>  		{"no-splice",	no_argument,		&c->no_splice,	1 },
> +		{"no-tap",	no_argument,		&c->no_tap,	1 },
>  		{"freebind",	no_argument,		&c->freebind,	1 },
>  		{"no-map-gw",	no_argument,		&no_map_gw,	1 },
>  		{"ipv4-only",	no_argument,		NULL,		'4' },
> @@ -1947,8 +1949,11 @@ void conf(struct ctx *c, int argc, char **argv)
>  		}
>  	} while (name != -1);
>  
> -	if (c->mode != MODE_PASTA)
> +	if (c->mode != MODE_PASTA) {
>  		c->no_splice = 1;
> +		if (c->no_tap)
> +			die("--no-tap is for pasta mode only");
> +	}
>  
>  	if (c->mode == MODE_PASTA && !c->pasta_conf_ns) {
>  		if (copy_routes_opt)
> @@ -1957,6 +1962,25 @@ void conf(struct ctx *c, int argc, char **argv)
>  			die("--no-copy-addrs needs --config-net");
>  	}
>  
> +	if (c->mode == MODE_PASTA && c->no_tap) {
> +		if (c->no_splice)
> +			die("--no-tap is incompatible with --no-splice");
> +		if (*c->ip4.ifname_out || *c->ip6.ifname_out)
> +			die("--no-tap is incompatible with --outbound-if4/6");
> +		if (*c->pasta_ifn)
> +			die("--no-tap is incompatible with --ns-ifname");
> +		if (*c->guest_mac)
> +			die("--no-tap is incompatible with --ns-mac-addr");
> +		if (c->pasta_conf_ns)
> +			die("--no-tap is incompatible with --config-net");
> +
> +		c->host_lo_to_ns_lo = 1;
> +		c->no_icmp = 1;
> +		c->no_ra = 1;
> +		c->no_dns = 1;
> +		c->no_dns_search = 1;
> +	}
> +
>  	if (!ifi4 && *c->ip4.ifname_out)
>  		ifi4 = if_nametoindex(c->ip4.ifname_out);
>  
> @@ -1980,9 +2004,9 @@ void conf(struct ctx *c, int argc, char **argv)
>  	log_conf_parsed = true;		/* Stop printing everything */
>  
>  	nl_sock_init(c, false);
> -	if (!v6_only)
> +	if (!v6_only && !c->no_tap)
>  		c->ifi4 = conf_ip4(ifi4, &c->ip4);
> -	if (!v4_only)
> +	if (!v4_only && !c->no_tap)
>  		c->ifi6 = conf_ip6(ifi6, &c->ip6);
>  
>  	if (c->ifi4 && c->mtu < IPV4_MIN_MTU) {
> @@ -1998,30 +2022,32 @@ void conf(struct ctx *c, int argc, char **argv)
>  	    (*c->ip6.ifname_out && !c->ifi6))
>  		die("External interface not usable");
>  
> -	if (!c->ifi4 && !c->ifi6 && !*c->pasta_ifn) {
> +	if (!c->ifi4 && !c->ifi6 && !*c->pasta_ifn && !c->no_tap) {
>  		strncpy(c->pasta_ifn, pasta_default_ifn,
>  			sizeof(c->pasta_ifn) - 1);
>  	}
>  
>  	if (!c->ifi4 && !v6_only) {
> -		info("IPv4: no external interface as template, use local mode");
> -
> -		conf_ip4_local(&c->ip4);
> +		if (!c->no_tap) {
> +			info("IPv4: no external interface as template, use local mode");
> +			conf_ip4_local(&c->ip4);
> +		}
>  		c->ifi4 = -1;
>  	}
>  
>  	if (!c->ifi6 && !v4_only) {
> -		info("IPv6: no external interface as template, use local mode");
> -
> -		conf_ip6_local(&c->ip6);
> +		if (!c->no_tap) {
> +			info("IPv6: no external interface as template, use local mode");
> +			conf_ip6_local(&c->ip6);
> +		}
>  		c->ifi6 = -1;
>  	}
>  
> -	if (c->ifi4 && !no_map_gw &&
> +	if (c->ifi4 > 0 && !no_map_gw &&
>  	    IN4_IS_ADDR_UNSPECIFIED(&c->ip4.map_host_loopback))
>  		c->ip4.map_host_loopback = c->ip4.guest_gw;
>  
> -	if (c->ifi6 && !no_map_gw &&
> +	if (c->ifi6 > 0 && !no_map_gw &&
>  	    IN6_IS_ADDR_UNSPECIFIED(&c->ip6.map_host_loopback))
>  		c->ip6.map_host_loopback = c->ip6.guest_gw;
>  
> @@ -2116,10 +2142,10 @@ void conf(struct ctx *c, int argc, char **argv)
>  			conf_ports(c, name, optarg, &c->udp.fwd_out);
>  	} while (name != -1);
>  
> -	if (!c->ifi4)
> +	if (c->ifi4 <= 0)
>  		c->no_dhcp = 1;
>  
> -	if (!c->ifi6) {
> +	if (c->ifi6 <= 0) {
>  		c->no_ndp = 1;
>  		c->no_dhcpv6 = 1;
>  	} else if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addr)) {
> diff --git a/fwd.c b/fwd.c
> index 44a0e10..2f4a89a 100644
> --- a/fwd.c
> +++ b/fwd.c
> @@ -780,6 +780,9 @@ uint8_t fwd_nat_from_host(const struct ctx *c, uint8_t proto,
>  		return PIF_SPLICE;
>  	}
>  
> +	if (c->no_tap)
> +		return PIF_NONE;
> +
>  	if (!nat_inbound(c, &ini->eaddr, &tgt->oaddr)) {
>  		if (inany_v4(&ini->eaddr)) {
>  			if (IN4_IS_ADDR_UNSPECIFIED(&c->ip4.our_tap_addr))
> diff --git a/passt.1 b/passt.1
> index db0d662..2d643f7 100644
> --- a/passt.1
> +++ b/passt.1
> @@ -755,6 +755,11 @@ Default is to let the tap driver build a pseudorandom hardware address.
>  Disable the bypass path for inbound, local traffic. See the section \fBHandling
>  of local traffic in pasta\fR in the \fBNOTES\fR for more details.
>  
> +.TP
> +.BR \-\-no-tap
> +Do not create a tap device in the namespace. In this mode, only local loopback
> +traffic between namespaces is forwarded using splice.
> +
>  .SH EXAMPLES
>  
>  .SS \fBpasta
> diff --git a/passt.h b/passt.h
> index 79d01dd..0c1ec4c 100644
> --- a/passt.h
> +++ b/passt.h
> @@ -200,6 +200,7 @@ struct ip6_ctx {
>   * @no_ndp:		Disable NDP handler altogether
>   * @no_ra:		Disable router advertisements
>   * @no_splice:		Disable socket splicing for inbound traffic
> + * @no_tap:		Do not create tap device
>   * @host_lo_to_ns_lo:	Map host loopback addresses to ns loopback addresses
>   * @freebind:		Allow binding of non-local addresses for forwarding
>   * @low_wmem:		Low probed net.core.wmem_max
> @@ -277,6 +278,7 @@ struct ctx {
>  	int no_ndp;
>  	int no_ra;
>  	int no_splice;
> +	int no_tap;
>  	int host_lo_to_ns_lo;
>  	int freebind;
>  
> diff --git a/pasta.c b/pasta.c
> index 0ddd6b0..3510ec5 100644
> --- a/pasta.c
> +++ b/pasta.c
> @@ -316,6 +316,9 @@ void pasta_ns_conf(struct ctx *c)
>  		die("Couldn't bring up loopback interface in namespace: %s",
>  		    strerror_(-rc));
>  
> +	if (c->no_tap)
> +		return;
> +
>  	/* Get or set MAC in target namespace */
>  	if (MAC_IS_ZERO(c->guest_mac))
>  		nl_link_get_mac(nl_sock_ns, c->pasta_ifi, c->guest_mac);
> diff --git a/tap.c b/tap.c
> index 9d1344b..9b4eedc 100644
> --- a/tap.c
> +++ b/tap.c
> @@ -1491,13 +1491,16 @@ static int tap_ns_tun(void *arg)
>   */
>  static void tap_sock_tun_init(struct ctx *c)
>  {
> -	NS_CALL(tap_ns_tun, c);
> -	if (c->fd_tap == -1)
> -		die("Failed to set up tap device in namespace");
> +	if (!c->no_tap) {
> +		NS_CALL(tap_ns_tun, c);
> +		if (c->fd_tap == -1)
> +			die("Failed to set up tap device in namespace");
> +	}
>  
>  	pasta_ns_conf(c);
>  
> -	tap_start_connection(c);
> +	if (!c->no_tap)
> +		tap_start_connection(c);
>  }
>  
>  /**

-- 
Stefano


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

* Re: [PATCH] conf, pasta: Add --no-tap option
  2025-12-29  9:55 [PATCH] conf, pasta: Add --no-tap option Yumei Huang
  2025-12-31 15:07 ` Stefano Brivio
@ 2026-01-05  4:18 ` David Gibson
  2026-01-05  8:53   ` Yumei Huang
  2026-01-05 13:48 ` Paul Holzinger
  2026-01-10 18:12 ` Stefano Brivio
  3 siblings, 1 reply; 16+ messages in thread
From: David Gibson @ 2026-01-05  4:18 UTC (permalink / raw)
  To: Yumei Huang; +Cc: passt-dev, sbrivio

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

On Mon, Dec 29, 2025 at 05:55:58PM +0800, Yumei Huang wrote:
> This patch introduces a mode where we only forward loopback connections
> and traffic between two namespaces (via the loopback interface, 'lo'),
> without a tap device.
> 
> With this, podman can support forwarding ::1 in custom networks when using
> rootlesskit for forwarding ports.
> 
> In --no-tap mode, --host-lo-to-ns-lo, --no-icmp and --no-ra is automatically
> enabled. Options requiring a tap device (--ns-ifname, --ns-mac-addr,
> --config-net, --outbound-if4/6) are rejected.
> 
> Link: https://bugs.passt.top/show_bug.cgi?id=149
> Signed-off-by: Yumei Huang <yuhuang@redhat.com>

Nice work.  There are some things that need polish, but overall this
looks pretty good to me.  Like Stefano, I'm pleasantly surprised at
how simple it turned out to be.

> ---
>  conf.c  | 56 +++++++++++++++++++++++++++++++++++++++++---------------
>  fwd.c   |  3 +++
>  passt.1 |  5 +++++
>  passt.h |  2 ++
>  pasta.c |  3 +++
>  tap.c   | 11 +++++++----
>  6 files changed, 61 insertions(+), 19 deletions(-)
> 
> diff --git a/conf.c b/conf.c
> index 84ae12b..353d0a5 100644
> --- a/conf.c
> +++ b/conf.c
> @@ -1049,7 +1049,8 @@ pasta_opts:
>  		"  --no-copy-addrs	DEPRECATED:\n"
>  		"			Don't copy all addresses to namespace\n"
>  		"  --ns-mac-addr ADDR	Set MAC address on tap interface\n"
> -		"  --no-splice		Disable inbound socket splicing\n");
> +		"  --no-splice		Disable inbound socket splicing\n"
> +		"  --no-tap		Don't create tap device\n");

I feel like this description can be improved, but I'm not exactly sure
how, yet.

>  
>  	passt_exit(status);
>  }
> @@ -1451,6 +1452,7 @@ void conf(struct ctx *c, int argc, char **argv)
>  		{"no-ndp",	no_argument,		&c->no_ndp,	1 },
>  		{"no-ra",	no_argument,		&c->no_ra,	1 },
>  		{"no-splice",	no_argument,		&c->no_splice,	1 },
> +		{"no-tap",	no_argument,		&c->no_tap,	1 },
>  		{"freebind",	no_argument,		&c->freebind,	1 },
>  		{"no-map-gw",	no_argument,		&no_map_gw,	1 },
>  		{"ipv4-only",	no_argument,		NULL,		'4' },
> @@ -1947,8 +1949,11 @@ void conf(struct ctx *c, int argc, char **argv)
>  		}
>  	} while (name != -1);
>  
> -	if (c->mode != MODE_PASTA)
> +	if (c->mode != MODE_PASTA) {
>  		c->no_splice = 1;
> +		if (c->no_tap)
> +			die("--no-tap is for pasta mode only");
> +	}
>  
>  	if (c->mode == MODE_PASTA && !c->pasta_conf_ns) {
>  		if (copy_routes_opt)
> @@ -1957,6 +1962,25 @@ void conf(struct ctx *c, int argc, char **argv)
>  			die("--no-copy-addrs needs --config-net");
>  	}
>  
> +	if (c->mode == MODE_PASTA && c->no_tap) {
> +		if (c->no_splice)
> +			die("--no-tap is incompatible with --no-splice");
> +		if (*c->ip4.ifname_out || *c->ip6.ifname_out)
> +			die("--no-tap is incompatible with --outbound-if4/6");
> +		if (*c->pasta_ifn)
> +			die("--no-tap is incompatible with --ns-ifname");
> +		if (*c->guest_mac)
> +			die("--no-tap is incompatible with --ns-mac-addr");

These all make sense.  It might also make sense to exclude the -i
option - setting a template interface also makes no sense in --no-tap
mode.

> +		if (c->pasta_conf_ns)
> +			die("--no-tap is incompatible with --config-net");

I don't think this is right.  We still can and should bring up 'lo' in
the --no-tap case.

> +		c->host_lo_to_ns_lo = 1;
> +		c->no_icmp = 1;
> +		c->no_ra = 1;
> +		c->no_dns = 1;
> +		c->no_dns_search = 1;

The reasoning for the last two items is a bit unclear to me.  IIUC,
no_dns and no_dns_search aren't so much about "support" for DNS itself
but for advertising DNS settings via DHCP.  Since DHCP will be
unsupported, so are these as a consequence.  Is that right?

> +	}
> +
>  	if (!ifi4 && *c->ip4.ifname_out)
>  		ifi4 = if_nametoindex(c->ip4.ifname_out);
>  
> @@ -1980,9 +2004,9 @@ void conf(struct ctx *c, int argc, char **argv)
>  	log_conf_parsed = true;		/* Stop printing everything */
>  
>  	nl_sock_init(c, false);
> -	if (!v6_only)
> +	if (!v6_only && !c->no_tap)
>  		c->ifi4 = conf_ip4(ifi4, &c->ip4);
> -	if (!v4_only)
> +	if (!v4_only && !c->no_tap)
>  		c->ifi6 = conf_ip6(ifi6, &c->ip6);
>  
>  	if (c->ifi4 && c->mtu < IPV4_MIN_MTU) {
> @@ -1998,30 +2022,32 @@ void conf(struct ctx *c, int argc, char **argv)
>  	    (*c->ip6.ifname_out && !c->ifi6))
>  		die("External interface not usable");
>  
> -	if (!c->ifi4 && !c->ifi6 && !*c->pasta_ifn) {
> +	if (!c->ifi4 && !c->ifi6 && !*c->pasta_ifn && !c->no_tap) {
>  		strncpy(c->pasta_ifn, pasta_default_ifn,
>  			sizeof(c->pasta_ifn) - 1);
>  	}
>  
>  	if (!c->ifi4 && !v6_only) {
> -		info("IPv4: no external interface as template, use local mode");
> -
> -		conf_ip4_local(&c->ip4);
> +		if (!c->no_tap) {
> +			info("IPv4: no external interface as template, use local mode");
> +			conf_ip4_local(&c->ip4);
> +		}
>  		c->ifi4 = -1;
>  	}
>  
>  	if (!c->ifi6 && !v4_only) {
> -		info("IPv6: no external interface as template, use local mode");
> -
> -		conf_ip6_local(&c->ip6);
> +		if (!c->no_tap) {
> +			info("IPv6: no external interface as template, use local mode");
> +			conf_ip6_local(&c->ip6);
> +		}
>  		c->ifi6 = -1;
>  	}
>  
> -	if (c->ifi4 && !no_map_gw &&
> +	if (c->ifi4 > 0 && !no_map_gw &&

This isn't quite right.  ifi4 == -1 now occurs in two cases: local
mode, and --no-tap mode.  Not setting map_host_loopback makes sense
for --no-tap mode, but it's still needed for local mode.

>  	    IN4_IS_ADDR_UNSPECIFIED(&c->ip4.map_host_loopback))
>  		c->ip4.map_host_loopback = c->ip4.guest_gw;
>  
> -	if (c->ifi6 && !no_map_gw &&
> +	if (c->ifi6 > 0 && !no_map_gw &&

Same here.

>  	    IN6_IS_ADDR_UNSPECIFIED(&c->ip6.map_host_loopback))
>  		c->ip6.map_host_loopback = c->ip6.guest_gw;
>  
> @@ -2116,10 +2142,10 @@ void conf(struct ctx *c, int argc, char **argv)
>  			conf_ports(c, name, optarg, &c->udp.fwd_out);
>  	} while (name != -1);
>  
> -	if (!c->ifi4)
> +	if (c->ifi4 <= 0)
>  		c->no_dhcp = 1;
>  
> -	if (!c->ifi6) {
> +	if (c->ifi6 <= 0) {
>  		c->no_ndp = 1;
>  		c->no_dhcpv6 = 1;

And here.  Local mode can still use NDP and DHCP, even though --no-tap
mode can't.  It might be simpler to force no_ndp, no_dhcp etc. along
with no_ra and the rest above.

>  	} else if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addr)) {
> diff --git a/fwd.c b/fwd.c
> index 44a0e10..2f4a89a 100644
> --- a/fwd.c
> +++ b/fwd.c
> @@ -780,6 +780,9 @@ uint8_t fwd_nat_from_host(const struct ctx *c, uint8_t proto,
>  		return PIF_SPLICE;
>  	}
>  
> +	if (c->no_tap)
> +		return PIF_NONE;
> +
>  	if (!nat_inbound(c, &ini->eaddr, &tgt->oaddr)) {
>  		if (inany_v4(&ini->eaddr)) {
>  			if (IN4_IS_ADDR_UNSPECIFIED(&c->ip4.our_tap_addr))
> diff --git a/passt.1 b/passt.1
> index db0d662..2d643f7 100644
> --- a/passt.1
> +++ b/passt.1
> @@ -755,6 +755,11 @@ Default is to let the tap driver build a pseudorandom hardware address.
>  Disable the bypass path for inbound, local traffic. See the section \fBHandling
>  of local traffic in pasta\fR in the \fBNOTES\fR for more details.
>  
> +.TP
> +.BR \-\-no-tap
> +Do not create a tap device in the namespace. In this mode, only local loopback
> +traffic between namespaces is forwarded using splice.

This probably wants some work, because I'm not sure "tap device" and
"splice" are sufficiently clear in this context.


> +
>  .SH EXAMPLES
>  
>  .SS \fBpasta
> diff --git a/passt.h b/passt.h
> index 79d01dd..0c1ec4c 100644
> --- a/passt.h
> +++ b/passt.h
> @@ -200,6 +200,7 @@ struct ip6_ctx {
>   * @no_ndp:		Disable NDP handler altogether
>   * @no_ra:		Disable router advertisements
>   * @no_splice:		Disable socket splicing for inbound traffic
> + * @no_tap:		Do not create tap device
>   * @host_lo_to_ns_lo:	Map host loopback addresses to ns loopback addresses
>   * @freebind:		Allow binding of non-local addresses for forwarding
>   * @low_wmem:		Low probed net.core.wmem_max
> @@ -277,6 +278,7 @@ struct ctx {
>  	int no_ndp;
>  	int no_ra;
>  	int no_splice;
> +	int no_tap;
>  	int host_lo_to_ns_lo;
>  	int freebind;
>  
> diff --git a/pasta.c b/pasta.c
> index 0ddd6b0..3510ec5 100644
> --- a/pasta.c
> +++ b/pasta.c
> @@ -316,6 +316,9 @@ void pasta_ns_conf(struct ctx *c)
>  		die("Couldn't bring up loopback interface in namespace: %s",
>  		    strerror_(-rc));
>  
> +	if (c->no_tap)
> +		return;
> +
>  	/* Get or set MAC in target namespace */
>  	if (MAC_IS_ZERO(c->guest_mac))
>  		nl_link_get_mac(nl_sock_ns, c->pasta_ifi, c->guest_mac);
> diff --git a/tap.c b/tap.c
> index 9d1344b..9b4eedc 100644
> --- a/tap.c
> +++ b/tap.c
> @@ -1491,13 +1491,16 @@ static int tap_ns_tun(void *arg)
>   */
>  static void tap_sock_tun_init(struct ctx *c)
>  {
> -	NS_CALL(tap_ns_tun, c);
> -	if (c->fd_tap == -1)
> -		die("Failed to set up tap device in namespace");
> +	if (!c->no_tap) {
> +		NS_CALL(tap_ns_tun, c);
> +		if (c->fd_tap == -1)
> +			die("Failed to set up tap device in namespace");
> +	}
>  
>  	pasta_ns_conf(c);
>  
> -	tap_start_connection(c);
> +	if (!c->no_tap)
> +		tap_start_connection(c);
>  }
>  
>  /**
> -- 
> 2.49.0
> 

-- 
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] 16+ messages in thread

* Re: [PATCH] conf, pasta: Add --no-tap option
  2026-01-05  4:18 ` David Gibson
@ 2026-01-05  8:53   ` Yumei Huang
  2026-01-10 18:12     ` Stefano Brivio
  0 siblings, 1 reply; 16+ messages in thread
From: Yumei Huang @ 2026-01-05  8:53 UTC (permalink / raw)
  To: David Gibson; +Cc: passt-dev, sbrivio

On Mon, Jan 5, 2026 at 12:18 PM David Gibson
<david@gibson.dropbear.id.au> wrote:
>
> On Mon, Dec 29, 2025 at 05:55:58PM +0800, Yumei Huang wrote:
> > This patch introduces a mode where we only forward loopback connections
> > and traffic between two namespaces (via the loopback interface, 'lo'),
> > without a tap device.
> >
> > With this, podman can support forwarding ::1 in custom networks when using
> > rootlesskit for forwarding ports.
> >
> > In --no-tap mode, --host-lo-to-ns-lo, --no-icmp and --no-ra is automatically
> > enabled. Options requiring a tap device (--ns-ifname, --ns-mac-addr,
> > --config-net, --outbound-if4/6) are rejected.
> >
> > Link: https://bugs.passt.top/show_bug.cgi?id=149
> > Signed-off-by: Yumei Huang <yuhuang@redhat.com>
>
> Nice work.  There are some things that need polish, but overall this
> looks pretty good to me.  Like Stefano, I'm pleasantly surprised at
> how simple it turned out to be.
>
> > ---
> >  conf.c  | 56 +++++++++++++++++++++++++++++++++++++++++---------------
> >  fwd.c   |  3 +++
> >  passt.1 |  5 +++++
> >  passt.h |  2 ++
> >  pasta.c |  3 +++
> >  tap.c   | 11 +++++++----
> >  6 files changed, 61 insertions(+), 19 deletions(-)
> >
> > diff --git a/conf.c b/conf.c
> > index 84ae12b..353d0a5 100644
> > --- a/conf.c
> > +++ b/conf.c
> > @@ -1049,7 +1049,8 @@ pasta_opts:
> >               "  --no-copy-addrs      DEPRECATED:\n"
> >               "                       Don't copy all addresses to namespace\n"
> >               "  --ns-mac-addr ADDR   Set MAC address on tap interface\n"
> > -             "  --no-splice          Disable inbound socket splicing\n");
> > +             "  --no-splice          Disable inbound socket splicing\n"
> > +             "  --no-tap             Don't create tap device\n");
>
> I feel like this description can be improved, but I'm not exactly sure
> how, yet.
>
> >
> >       passt_exit(status);
> >  }
> > @@ -1451,6 +1452,7 @@ void conf(struct ctx *c, int argc, char **argv)
> >               {"no-ndp",      no_argument,            &c->no_ndp,     1 },
> >               {"no-ra",       no_argument,            &c->no_ra,      1 },
> >               {"no-splice",   no_argument,            &c->no_splice,  1 },
> > +             {"no-tap",      no_argument,            &c->no_tap,     1 },
> >               {"freebind",    no_argument,            &c->freebind,   1 },
> >               {"no-map-gw",   no_argument,            &no_map_gw,     1 },
> >               {"ipv4-only",   no_argument,            NULL,           '4' },
> > @@ -1947,8 +1949,11 @@ void conf(struct ctx *c, int argc, char **argv)
> >               }
> >       } while (name != -1);
> >
> > -     if (c->mode != MODE_PASTA)
> > +     if (c->mode != MODE_PASTA) {
> >               c->no_splice = 1;
> > +             if (c->no_tap)
> > +                     die("--no-tap is for pasta mode only");
> > +     }
> >
> >       if (c->mode == MODE_PASTA && !c->pasta_conf_ns) {
> >               if (copy_routes_opt)
> > @@ -1957,6 +1962,25 @@ void conf(struct ctx *c, int argc, char **argv)
> >                       die("--no-copy-addrs needs --config-net");
> >       }
> >
> > +     if (c->mode == MODE_PASTA && c->no_tap) {
> > +             if (c->no_splice)
> > +                     die("--no-tap is incompatible with --no-splice");
> > +             if (*c->ip4.ifname_out || *c->ip6.ifname_out)
> > +                     die("--no-tap is incompatible with --outbound-if4/6");
> > +             if (*c->pasta_ifn)
> > +                     die("--no-tap is incompatible with --ns-ifname");
> > +             if (*c->guest_mac)
> > +                     die("--no-tap is incompatible with --ns-mac-addr");
>
> These all make sense.  It might also make sense to exclude the -i
> option - setting a template interface also makes no sense in --no-tap
> mode.

Sure, I can add an if condition with if4 (as if4=if6 in that case).
>
> > +             if (c->pasta_conf_ns)
> > +                     die("--no-tap is incompatible with --config-net");
>
> I don't think this is right.  We still can and should bring up 'lo' in
> the --no-tap case.

I see your point, but seems c->pasta_conf_ns is only used for tap as
https://passt.top/passt/tree/pasta.c#n328, 'lo' is configured before
that line.
>
> > +             c->host_lo_to_ns_lo = 1;
> > +             c->no_icmp = 1;
> > +             c->no_ra = 1;
> > +             c->no_dns = 1;
> > +             c->no_dns_search = 1;
>
> The reasoning for the last two items is a bit unclear to me.  IIUC,
> no_dns and no_dns_search aren't so much about "support" for DNS itself
> but for advertising DNS settings via DHCP.  Since DHCP will be
> unsupported, so are these as a consequence.  Is that right?

Yeah, I think so. Actually I added c->no_dhcp, c->no_ndp here as well,
then removed them as they are set in later changes(conditions about
c->ifi4/c->ifi6), though they turn out to be not quite right :'\
>
> > +     }
> > +
> >       if (!ifi4 && *c->ip4.ifname_out)
> >               ifi4 = if_nametoindex(c->ip4.ifname_out);
> >
> > @@ -1980,9 +2004,9 @@ void conf(struct ctx *c, int argc, char **argv)
> >       log_conf_parsed = true;         /* Stop printing everything */
> >
> >       nl_sock_init(c, false);
> > -     if (!v6_only)
> > +     if (!v6_only && !c->no_tap)
> >               c->ifi4 = conf_ip4(ifi4, &c->ip4);
> > -     if (!v4_only)
> > +     if (!v4_only && !c->no_tap)
> >               c->ifi6 = conf_ip6(ifi6, &c->ip6);
> >
> >       if (c->ifi4 && c->mtu < IPV4_MIN_MTU) {
> > @@ -1998,30 +2022,32 @@ void conf(struct ctx *c, int argc, char **argv)
> >           (*c->ip6.ifname_out && !c->ifi6))
> >               die("External interface not usable");
> >
> > -     if (!c->ifi4 && !c->ifi6 && !*c->pasta_ifn) {
> > +     if (!c->ifi4 && !c->ifi6 && !*c->pasta_ifn && !c->no_tap) {
> >               strncpy(c->pasta_ifn, pasta_default_ifn,
> >                       sizeof(c->pasta_ifn) - 1);
> >       }
> >
> >       if (!c->ifi4 && !v6_only) {
> > -             info("IPv4: no external interface as template, use local mode");
> > -
> > -             conf_ip4_local(&c->ip4);
> > +             if (!c->no_tap) {
> > +                     info("IPv4: no external interface as template, use local mode");
> > +                     conf_ip4_local(&c->ip4);
> > +             }
> >               c->ifi4 = -1;
> >       }
> >
> >       if (!c->ifi6 && !v4_only) {
> > -             info("IPv6: no external interface as template, use local mode");
> > -
> > -             conf_ip6_local(&c->ip6);
> > +             if (!c->no_tap) {
> > +                     info("IPv6: no external interface as template, use local mode");
> > +                     conf_ip6_local(&c->ip6);
> > +             }
> >               c->ifi6 = -1;
> >       }
> >
> > -     if (c->ifi4 && !no_map_gw &&
> > +     if (c->ifi4 > 0 && !no_map_gw &&
>
> This isn't quite right.  ifi4 == -1 now occurs in two cases: local
> mode, and --no-tap mode.  Not setting map_host_loopback makes sense
> for --no-tap mode, but it's still needed for local mode.

I'm a bit confused by map_host_loopback. I don't quite understand the
use scenario. IIUC, either in --no-tap mode or local mode, guest can
only communicate with host. Then why do we need to set
map_host_loopback? What's the benefit?
>
> >           IN4_IS_ADDR_UNSPECIFIED(&c->ip4.map_host_loopback))
> >               c->ip4.map_host_loopback = c->ip4.guest_gw;
> >
> > -     if (c->ifi6 && !no_map_gw &&
> > +     if (c->ifi6 > 0 && !no_map_gw &&
>
> Same here.
>
> >           IN6_IS_ADDR_UNSPECIFIED(&c->ip6.map_host_loopback))
> >               c->ip6.map_host_loopback = c->ip6.guest_gw;
> >
> > @@ -2116,10 +2142,10 @@ void conf(struct ctx *c, int argc, char **argv)
> >                       conf_ports(c, name, optarg, &c->udp.fwd_out);
> >       } while (name != -1);
> >
> > -     if (!c->ifi4)
> > +     if (c->ifi4 <= 0)
> >               c->no_dhcp = 1;
> >
> > -     if (!c->ifi6) {
> > +     if (c->ifi6 <= 0) {
> >               c->no_ndp = 1;
> >               c->no_dhcpv6 = 1;
>
> And here.  Local mode can still use NDP and DHCP, even though --no-tap
> mode can't.  It might be simpler to force no_ndp, no_dhcp etc. along
> with no_ra and the rest above.

Sure, I will add them.
>
> >       } else if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addr)) {
> > diff --git a/fwd.c b/fwd.c
> > index 44a0e10..2f4a89a 100644
> > --- a/fwd.c
> > +++ b/fwd.c
> > @@ -780,6 +780,9 @@ uint8_t fwd_nat_from_host(const struct ctx *c, uint8_t proto,
> >               return PIF_SPLICE;
> >       }
> >
> > +     if (c->no_tap)
> > +             return PIF_NONE;
> > +
> >       if (!nat_inbound(c, &ini->eaddr, &tgt->oaddr)) {
> >               if (inany_v4(&ini->eaddr)) {
> >                       if (IN4_IS_ADDR_UNSPECIFIED(&c->ip4.our_tap_addr))
> > diff --git a/passt.1 b/passt.1
> > index db0d662..2d643f7 100644
> > --- a/passt.1
> > +++ b/passt.1
> > @@ -755,6 +755,11 @@ Default is to let the tap driver build a pseudorandom hardware address.
> >  Disable the bypass path for inbound, local traffic. See the section \fBHandling
> >  of local traffic in pasta\fR in the \fBNOTES\fR for more details.
> >
> > +.TP
> > +.BR \-\-no-tap
> > +Do not create a tap device in the namespace. In this mode, only local loopback
> > +traffic between namespaces is forwarded using splice.
>
> This probably wants some work, because I'm not sure "tap device" and
> "splice" are sufficiently clear in this context.

Yeah, I will think about that. Thanks.
>
>
> > +
> >  .SH EXAMPLES
> >
> >  .SS \fBpasta
> > diff --git a/passt.h b/passt.h
> > index 79d01dd..0c1ec4c 100644
> > --- a/passt.h
> > +++ b/passt.h
> > @@ -200,6 +200,7 @@ struct ip6_ctx {
> >   * @no_ndp:          Disable NDP handler altogether
> >   * @no_ra:           Disable router advertisements
> >   * @no_splice:               Disable socket splicing for inbound traffic
> > + * @no_tap:          Do not create tap device
> >   * @host_lo_to_ns_lo:        Map host loopback addresses to ns loopback addresses
> >   * @freebind:                Allow binding of non-local addresses for forwarding
> >   * @low_wmem:                Low probed net.core.wmem_max
> > @@ -277,6 +278,7 @@ struct ctx {
> >       int no_ndp;
> >       int no_ra;
> >       int no_splice;
> > +     int no_tap;
> >       int host_lo_to_ns_lo;
> >       int freebind;
> >
> > diff --git a/pasta.c b/pasta.c
> > index 0ddd6b0..3510ec5 100644
> > --- a/pasta.c
> > +++ b/pasta.c
> > @@ -316,6 +316,9 @@ void pasta_ns_conf(struct ctx *c)
> >               die("Couldn't bring up loopback interface in namespace: %s",
> >                   strerror_(-rc));
> >
> > +     if (c->no_tap)
> > +             return;
> > +
> >       /* Get or set MAC in target namespace */
> >       if (MAC_IS_ZERO(c->guest_mac))
> >               nl_link_get_mac(nl_sock_ns, c->pasta_ifi, c->guest_mac);
> > diff --git a/tap.c b/tap.c
> > index 9d1344b..9b4eedc 100644
> > --- a/tap.c
> > +++ b/tap.c
> > @@ -1491,13 +1491,16 @@ static int tap_ns_tun(void *arg)
> >   */
> >  static void tap_sock_tun_init(struct ctx *c)
> >  {
> > -     NS_CALL(tap_ns_tun, c);
> > -     if (c->fd_tap == -1)
> > -             die("Failed to set up tap device in namespace");
> > +     if (!c->no_tap) {
> > +             NS_CALL(tap_ns_tun, c);
> > +             if (c->fd_tap == -1)
> > +                     die("Failed to set up tap device in namespace");
> > +     }
> >
> >       pasta_ns_conf(c);
> >
> > -     tap_start_connection(c);
> > +     if (!c->no_tap)
> > +             tap_start_connection(c);
> >  }
> >
> >  /**
> > --
> > 2.49.0
> >
>
> --
> 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



-- 
Thanks,

Yumei Huang


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

* Re: [PATCH] conf, pasta: Add --no-tap option
  2025-12-29  9:55 [PATCH] conf, pasta: Add --no-tap option Yumei Huang
  2025-12-31 15:07 ` Stefano Brivio
  2026-01-05  4:18 ` David Gibson
@ 2026-01-05 13:48 ` Paul Holzinger
  2026-01-05 21:10   ` Stefano Brivio
  2026-01-10 18:12 ` Stefano Brivio
  3 siblings, 1 reply; 16+ messages in thread
From: Paul Holzinger @ 2026-01-05 13:48 UTC (permalink / raw)
  To: Yumei Huang, passt-dev, sbrivio; +Cc: david

Sorry I was out for a while so I didn't had time to clarify on the bug 
before.

On 29/12/2025 10:55, Yumei Huang wrote:
> This patch introduces a mode where we only forward loopback connections
> and traffic between two namespaces (via the loopback interface, 'lo'),
> without a tap device.
>
> With this, podman can support forwarding ::1 in custom networks when using
> rootlesskit for forwarding ports.

I guess I didn't really communicate my requirements well. When we use 
rootlessport (rootlesskit) today for custom networks we only do so as 
rootless user and it forwards ::1 (by possibly mapping this to v4 inside 
the container) fine.

My main point for this feature was using as root (requires further 
changes to allow pasta running as root). Because as root podman does 
port forwarding via DNAT firewall rules (i.e. custom nftables rules we 
add). The kernel however never added support for DNAT on ::1 meaning 
clients trying to access that are not getting forwarded. The only way to 
support this is using a user space helper. Right now this doesn't work 
and we do not use rootlessport for this either so I was just thinking 
ahead because we do have these users requests who want ::1 to work as root.

For the current rootlessport use case we also must bind all ports as 
given (i.e. also addresses 0.0.0.0 bind address), just forwarding 
loopback to loopback is not what we want or do for security reasons, see 
CVE-2021-20199. And logically it would not really work to have another 
process bind 0.0.0.0 and this pasta helper bind lo on the same port at 
the same time.

The way I am thinking is bind ports as normal, add the no-tap option and 
add two options to give the v4 and v6 namespace (container) side connect 
addresses so we never actually connect to lo. Then we also should have a 
dynamic way to update the connect addresses at runtime which is required 
for podman network connect/disconnect to work which changes the 
addresses inside the namespace, see 
https://github.com/containers/podman/commit/e88d8dbeae2aebd2d816f16a21891764163afcd4.

Overall none of this is a blocker for removing rootlessport. I think our 
plan was and still is to use the dynamic port forwarding logic David is 
working on to replace the rootless custom network port forwarding case 
with that.

>
> In --no-tap mode, --host-lo-to-ns-lo, --no-icmp and --no-ra is automatically
> enabled. Options requiring a tap device (--ns-ifname, --ns-mac-addr,
> --config-net, --outbound-if4/6) are rejected.
>
> Link: https://bugs.passt.top/show_bug.cgi?id=149
> Signed-off-by: Yumei Huang <yuhuang@redhat.com>
> ---
>   conf.c  | 56 +++++++++++++++++++++++++++++++++++++++++---------------
>   fwd.c   |  3 +++
>   passt.1 |  5 +++++
>   passt.h |  2 ++
>   pasta.c |  3 +++
>   tap.c   | 11 +++++++----
>   6 files changed, 61 insertions(+), 19 deletions(-)
>
> diff --git a/conf.c b/conf.c
> index 84ae12b..353d0a5 100644
> --- a/conf.c
> +++ b/conf.c
> @@ -1049,7 +1049,8 @@ pasta_opts:
>   		"  --no-copy-addrs	DEPRECATED:\n"
>   		"			Don't copy all addresses to namespace\n"
>   		"  --ns-mac-addr ADDR	Set MAC address on tap interface\n"
> -		"  --no-splice		Disable inbound socket splicing\n");
> +		"  --no-splice		Disable inbound socket splicing\n"
> +		"  --no-tap		Don't create tap device\n");
>   
>   	passt_exit(status);
>   }
> @@ -1451,6 +1452,7 @@ void conf(struct ctx *c, int argc, char **argv)
>   		{"no-ndp",	no_argument,		&c->no_ndp,	1 },
>   		{"no-ra",	no_argument,		&c->no_ra,	1 },
>   		{"no-splice",	no_argument,		&c->no_splice,	1 },
> +		{"no-tap",	no_argument,		&c->no_tap,	1 },
>   		{"freebind",	no_argument,		&c->freebind,	1 },
>   		{"no-map-gw",	no_argument,		&no_map_gw,	1 },
>   		{"ipv4-only",	no_argument,		NULL,		'4' },
> @@ -1947,8 +1949,11 @@ void conf(struct ctx *c, int argc, char **argv)
>   		}
>   	} while (name != -1);
>   
> -	if (c->mode != MODE_PASTA)
> +	if (c->mode != MODE_PASTA) {
>   		c->no_splice = 1;
> +		if (c->no_tap)
> +			die("--no-tap is for pasta mode only");
> +	}
>   
>   	if (c->mode == MODE_PASTA && !c->pasta_conf_ns) {
>   		if (copy_routes_opt)
> @@ -1957,6 +1962,25 @@ void conf(struct ctx *c, int argc, char **argv)
>   			die("--no-copy-addrs needs --config-net");
>   	}
>   
> +	if (c->mode == MODE_PASTA && c->no_tap) {
> +		if (c->no_splice)
> +			die("--no-tap is incompatible with --no-splice");
> +		if (*c->ip4.ifname_out || *c->ip6.ifname_out)
> +			die("--no-tap is incompatible with --outbound-if4/6");
> +		if (*c->pasta_ifn)
> +			die("--no-tap is incompatible with --ns-ifname");
> +		if (*c->guest_mac)
> +			die("--no-tap is incompatible with --ns-mac-addr");
> +		if (c->pasta_conf_ns)
> +			die("--no-tap is incompatible with --config-net");
> +
> +		c->host_lo_to_ns_lo = 1;
> +		c->no_icmp = 1;
> +		c->no_ra = 1;
> +		c->no_dns = 1;
> +		c->no_dns_search = 1;
> +	}
> +
>   	if (!ifi4 && *c->ip4.ifname_out)
>   		ifi4 = if_nametoindex(c->ip4.ifname_out);
>   
> @@ -1980,9 +2004,9 @@ void conf(struct ctx *c, int argc, char **argv)
>   	log_conf_parsed = true;		/* Stop printing everything */
>   
>   	nl_sock_init(c, false);
> -	if (!v6_only)
> +	if (!v6_only && !c->no_tap)
>   		c->ifi4 = conf_ip4(ifi4, &c->ip4);
> -	if (!v4_only)
> +	if (!v4_only && !c->no_tap)
>   		c->ifi6 = conf_ip6(ifi6, &c->ip6);
>   
>   	if (c->ifi4 && c->mtu < IPV4_MIN_MTU) {
> @@ -1998,30 +2022,32 @@ void conf(struct ctx *c, int argc, char **argv)
>   	    (*c->ip6.ifname_out && !c->ifi6))
>   		die("External interface not usable");
>   
> -	if (!c->ifi4 && !c->ifi6 && !*c->pasta_ifn) {
> +	if (!c->ifi4 && !c->ifi6 && !*c->pasta_ifn && !c->no_tap) {
>   		strncpy(c->pasta_ifn, pasta_default_ifn,
>   			sizeof(c->pasta_ifn) - 1);
>   	}
>   
>   	if (!c->ifi4 && !v6_only) {
> -		info("IPv4: no external interface as template, use local mode");
> -
> -		conf_ip4_local(&c->ip4);
> +		if (!c->no_tap) {
> +			info("IPv4: no external interface as template, use local mode");
> +			conf_ip4_local(&c->ip4);
> +		}
>   		c->ifi4 = -1;
>   	}
>   
>   	if (!c->ifi6 && !v4_only) {
> -		info("IPv6: no external interface as template, use local mode");
> -
> -		conf_ip6_local(&c->ip6);
> +		if (!c->no_tap) {
> +			info("IPv6: no external interface as template, use local mode");
> +			conf_ip6_local(&c->ip6);
> +		}
>   		c->ifi6 = -1;
>   	}
>   
> -	if (c->ifi4 && !no_map_gw &&
> +	if (c->ifi4 > 0 && !no_map_gw &&
>   	    IN4_IS_ADDR_UNSPECIFIED(&c->ip4.map_host_loopback))
>   		c->ip4.map_host_loopback = c->ip4.guest_gw;
>   
> -	if (c->ifi6 && !no_map_gw &&
> +	if (c->ifi6 > 0 && !no_map_gw &&
>   	    IN6_IS_ADDR_UNSPECIFIED(&c->ip6.map_host_loopback))
>   		c->ip6.map_host_loopback = c->ip6.guest_gw;
>   
> @@ -2116,10 +2142,10 @@ void conf(struct ctx *c, int argc, char **argv)
>   			conf_ports(c, name, optarg, &c->udp.fwd_out);
>   	} while (name != -1);
>   
> -	if (!c->ifi4)
> +	if (c->ifi4 <= 0)
>   		c->no_dhcp = 1;
>   
> -	if (!c->ifi6) {
> +	if (c->ifi6 <= 0) {
>   		c->no_ndp = 1;
>   		c->no_dhcpv6 = 1;
>   	} else if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addr)) {
> diff --git a/fwd.c b/fwd.c
> index 44a0e10..2f4a89a 100644
> --- a/fwd.c
> +++ b/fwd.c
> @@ -780,6 +780,9 @@ uint8_t fwd_nat_from_host(const struct ctx *c, uint8_t proto,
>   		return PIF_SPLICE;
>   	}
>   
> +	if (c->no_tap)
> +		return PIF_NONE;
> +
>   	if (!nat_inbound(c, &ini->eaddr, &tgt->oaddr)) {
>   		if (inany_v4(&ini->eaddr)) {
>   			if (IN4_IS_ADDR_UNSPECIFIED(&c->ip4.our_tap_addr))
> diff --git a/passt.1 b/passt.1
> index db0d662..2d643f7 100644
> --- a/passt.1
> +++ b/passt.1
> @@ -755,6 +755,11 @@ Default is to let the tap driver build a pseudorandom hardware address.
>   Disable the bypass path for inbound, local traffic. See the section \fBHandling
>   of local traffic in pasta\fR in the \fBNOTES\fR for more details.
>   
> +.TP
> +.BR \-\-no-tap
> +Do not create a tap device in the namespace. In this mode, only local loopback
> +traffic between namespaces is forwarded using splice.
> +
>   .SH EXAMPLES
>   
>   .SS \fBpasta
> diff --git a/passt.h b/passt.h
> index 79d01dd..0c1ec4c 100644
> --- a/passt.h
> +++ b/passt.h
> @@ -200,6 +200,7 @@ struct ip6_ctx {
>    * @no_ndp:		Disable NDP handler altogether
>    * @no_ra:		Disable router advertisements
>    * @no_splice:		Disable socket splicing for inbound traffic
> + * @no_tap:		Do not create tap device
>    * @host_lo_to_ns_lo:	Map host loopback addresses to ns loopback addresses
>    * @freebind:		Allow binding of non-local addresses for forwarding
>    * @low_wmem:		Low probed net.core.wmem_max
> @@ -277,6 +278,7 @@ struct ctx {
>   	int no_ndp;
>   	int no_ra;
>   	int no_splice;
> +	int no_tap;
>   	int host_lo_to_ns_lo;
>   	int freebind;
>   
> diff --git a/pasta.c b/pasta.c
> index 0ddd6b0..3510ec5 100644
> --- a/pasta.c
> +++ b/pasta.c
> @@ -316,6 +316,9 @@ void pasta_ns_conf(struct ctx *c)
>   		die("Couldn't bring up loopback interface in namespace: %s",
>   		    strerror_(-rc));
>   
> +	if (c->no_tap)
> +		return;
> +
>   	/* Get or set MAC in target namespace */
>   	if (MAC_IS_ZERO(c->guest_mac))
>   		nl_link_get_mac(nl_sock_ns, c->pasta_ifi, c->guest_mac);
> diff --git a/tap.c b/tap.c
> index 9d1344b..9b4eedc 100644
> --- a/tap.c
> +++ b/tap.c
> @@ -1491,13 +1491,16 @@ static int tap_ns_tun(void *arg)
>    */
>   static void tap_sock_tun_init(struct ctx *c)
>   {
> -	NS_CALL(tap_ns_tun, c);
> -	if (c->fd_tap == -1)
> -		die("Failed to set up tap device in namespace");
> +	if (!c->no_tap) {
> +		NS_CALL(tap_ns_tun, c);
> +		if (c->fd_tap == -1)
> +			die("Failed to set up tap device in namespace");
> +	}
>   
>   	pasta_ns_conf(c);
>   
> -	tap_start_connection(c);
> +	if (!c->no_tap)
> +		tap_start_connection(c);
>   }
>   
>   /**

-- 
Paul Holzinger


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

* Re: [PATCH] conf, pasta: Add --no-tap option
  2026-01-05 13:48 ` Paul Holzinger
@ 2026-01-05 21:10   ` Stefano Brivio
  2026-01-07 15:20     ` Paul Holzinger
  0 siblings, 1 reply; 16+ messages in thread
From: Stefano Brivio @ 2026-01-05 21:10 UTC (permalink / raw)
  To: Paul Holzinger; +Cc: Yumei Huang, passt-dev, david

On Mon, 5 Jan 2026 14:48:15 +0100
Paul Holzinger <pholzing@redhat.com> wrote:

> Sorry I was out for a while so I didn't had time to clarify on the bug 
> before.
> 
> On 29/12/2025 10:55, Yumei Huang wrote:
> > This patch introduces a mode where we only forward loopback connections
> > and traffic between two namespaces (via the loopback interface, 'lo'),
> > without a tap device.
> >
> > With this, podman can support forwarding ::1 in custom networks when using
> > rootlesskit for forwarding ports.  
> 
> I guess I didn't really communicate my requirements well.

I guess it's more likely that you actually did, but I mixed up the
association between requirements and use cases, sorry for that.

In any case, good that we need this anyway, just for another use case.
:)

> When we use 
> rootlessport (rootlesskit) today for custom networks we only do so as 
> rootless user and it forwards ::1 (by possibly mapping this to v4 inside 
> the container) fine.

So, wait a moment, is my comment at:

  https://github.com/containers/podman/issues/14491#issuecomment-2898191772

actually wrong? I don't have time right now to test that but from user
reports and some vague memory I thought ::1 forwarding wouldn't work
with custom networks regardless of root or rootless, because
rootlesskit didn't handle that anyway.

> My main point for this feature was using as root (requires further 
> changes to allow pasta running as root).

...which should be entirely on Podman side and it's still on my plate,
by the way:

  https://github.com/containers/podman/issues/17840
  https://pad.passt.top/p/Features_2025#L40

> Because as root podman does 
> port forwarding via DNAT firewall rules (i.e. custom nftables rules we 
> add). The kernel however never added support for DNAT on ::1 meaning 
> clients trying to access that are not getting forwarded. The only way to 
> support this is using a user space helper. Right now this doesn't work 
> and we do not use rootlessport for this either so I was just thinking 
> ahead because we do have these users requests who want ::1 to work as root.
> 
> For the current rootlessport use case we also must bind all ports as 
> given (i.e. also addresses 0.0.0.0 bind address), just forwarding 
> loopback to loopback is not what we want or do for security reasons, see 
> CVE-2021-20199. And logically it would not really work to have another 
> process bind 0.0.0.0 and this pasta helper bind lo on the same port at 
> the same time.
> 
> The way I am thinking is bind ports as normal, add the no-tap option and 
> add two options to give the v4 and v6 namespace (container) side connect 
> addresses so we never actually connect to lo. Then we also should have a 
> dynamic way to update the connect addresses at runtime which is required 
> for podman network connect/disconnect to work which changes the 
> addresses inside the namespace, see 
> https://github.com/containers/podman/commit/e88d8dbeae2aebd2d816f16a21891764163afcd4.
> 
> Overall none of this is a blocker for removing rootlessport. I think our 
> plan was and still is to use the dynamic port forwarding logic David is 
> working on to replace the rootless custom network port forwarding case 
> with that.

Regardless of other requirements that are needed as well to support
forwarding ::1 for root containers (or rootless with --userns=auto),
this feature by itself makes sense as it is and we'll need it as it is,
right?

By the way we routinely get requests for this feature by pasta (and
Podman) users, regardless of any specific Podman integration, so I
think the feature is generic enough as to make sense regardless of your
plan for root containers.

-- 
Stefano


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

* Re: [PATCH] conf, pasta: Add --no-tap option
  2026-01-05 21:10   ` Stefano Brivio
@ 2026-01-07 15:20     ` Paul Holzinger
  2026-01-10 18:12       ` Stefano Brivio
  0 siblings, 1 reply; 16+ messages in thread
From: Paul Holzinger @ 2026-01-07 15:20 UTC (permalink / raw)
  To: Stefano Brivio; +Cc: Yumei Huang, passt-dev, david


On 05/01/2026 22:10, Stefano Brivio wrote:
> On Mon, 5 Jan 2026 14:48:15 +0100
> Paul Holzinger <pholzing@redhat.com> wrote:
>
>> Sorry I was out for a while so I didn't had time to clarify on the bug
>> before.
>>
>> On 29/12/2025 10:55, Yumei Huang wrote:
>>> This patch introduces a mode where we only forward loopback connections
>>> and traffic between two namespaces (via the loopback interface, 'lo'),
>>> without a tap device.
>>>
>>> With this, podman can support forwarding ::1 in custom networks when using
>>> rootlesskit for forwarding ports.
>> I guess I didn't really communicate my requirements well.
> I guess it's more likely that you actually did, but I mixed up the
> association between requirements and use cases, sorry for that.
>
> In any case, good that we need this anyway, just for another use case.
> :)
>
>> When we use
>> rootlessport (rootlesskit) today for custom networks we only do so as
>> rootless user and it forwards ::1 (by possibly mapping this to v4 inside
>> the container) fine.
> So, wait a moment, is my comment at:
>
>    https://github.com/containers/podman/issues/14491#issuecomment-2898191772
>
> actually wrong? I don't have time right now to test that but from user
> reports and some vague memory I thought ::1 forwarding wouldn't work
> with custom networks regardless of root or rootless, because
> rootlesskit didn't handle that anyway.
yes, rootlesskit handles ipv6 just fine, it is just that our 
rootlessport code remaps that to v4 inside the container.
>
>> My main point for this feature was using as root (requires further
>> changes to allow pasta running as root).
> ...which should be entirely on Podman side and it's still on my plate,
> by the way:
>
>    https://github.com/containers/podman/issues/17840
>    https://pad.passt.top/p/Features_2025#L40
I don't see how this can be fixed on the podman side, the network 
namespace of a rootful container (not userns=auto) is owned by the root 
user. If you configure something in there you must have real 
CAP_NET_ADMIN from the host init userns. So pasta must not drop this 
privilege before configuring the netns. And even then with the future 
netlink monitor work we would need to keep that privilege level to 
modify the netns even during runtime?
>
>> Because as root podman does
>> port forwarding via DNAT firewall rules (i.e. custom nftables rules we
>> add). The kernel however never added support for DNAT on ::1 meaning
>> clients trying to access that are not getting forwarded. The only way to
>> support this is using a user space helper. Right now this doesn't work
>> and we do not use rootlessport for this either so I was just thinking
>> ahead because we do have these users requests who want ::1 to work as root.
>>
>> For the current rootlessport use case we also must bind all ports as
>> given (i.e. also addresses 0.0.0.0 bind address), just forwarding
>> loopback to loopback is not what we want or do for security reasons, see
>> CVE-2021-20199. And logically it would not really work to have another
>> process bind 0.0.0.0 and this pasta helper bind lo on the same port at
>> the same time.
>>
>> The way I am thinking is bind ports as normal, add the no-tap option and
>> add two options to give the v4 and v6 namespace (container) side connect
>> addresses so we never actually connect to lo. Then we also should have a
>> dynamic way to update the connect addresses at runtime which is required
>> for podman network connect/disconnect to work which changes the
>> addresses inside the namespace, see
>> https://github.com/containers/podman/commit/e88d8dbeae2aebd2d816f16a21891764163afcd4.
>>
>> Overall none of this is a blocker for removing rootlessport. I think our
>> plan was and still is to use the dynamic port forwarding logic David is
>> working on to replace the rootless custom network port forwarding case
>> with that.
> Regardless of other requirements that are needed as well to support
> forwarding ::1 for root containers (or rootless with --userns=auto),
> this feature by itself makes sense as it is and we'll need it as it is,
> right?
>
> By the way we routinely get requests for this feature by pasta (and
> Podman) users, regardless of any specific Podman integration, so I
> think the feature is generic enough as to make sense regardless of your
> plan for root containers.
>
I am not sure how I would use or integrate a loopback to loopback 
forwarder in podman so I don't think we would need or can use that as is.

I think the use case itself is still interesting and if there are end 
users asking for it sure not objections from me. I guess it could be 
interesting to expose a service without giving it access to the full 
internet and without having to deal with complicated firewall rules, 
i.e. with this we get a container that only could communicate by 
replying to the forwarded ports.

-- 
Paul Holzinger


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

* Re: [PATCH] conf, pasta: Add --no-tap option
  2026-01-07 15:20     ` Paul Holzinger
@ 2026-01-10 18:12       ` Stefano Brivio
  2026-01-12  8:20         ` Yumei Huang
  0 siblings, 1 reply; 16+ messages in thread
From: Stefano Brivio @ 2026-01-10 18:12 UTC (permalink / raw)
  To: Paul Holzinger; +Cc: Yumei Huang, passt-dev, david, Jon Maloy

[Cc'ing Jon for awareness around the part about netlink monitor and
capabilities, four paragraphs down]

On Wed, 7 Jan 2026 16:20:18 +0100
Paul Holzinger <pholzing@redhat.com> wrote:

> On 05/01/2026 22:10, Stefano Brivio wrote:
> > On Mon, 5 Jan 2026 14:48:15 +0100
> > Paul Holzinger <pholzing@redhat.com> wrote:
> >  
> >> Sorry I was out for a while so I didn't had time to clarify on the bug
> >> before.
> >>
> >> On 29/12/2025 10:55, Yumei Huang wrote:  
> >>> This patch introduces a mode where we only forward loopback connections
> >>> and traffic between two namespaces (via the loopback interface, 'lo'),
> >>> without a tap device.
> >>>
> >>> With this, podman can support forwarding ::1 in custom networks when using
> >>> rootlesskit for forwarding ports.  
> >> I guess I didn't really communicate my requirements well.  
> > I guess it's more likely that you actually did, but I mixed up the
> > association between requirements and use cases, sorry for that.
> >
> > In any case, good that we need this anyway, just for another use case.
> > :)
> >  
> >> When we use
> >> rootlessport (rootlesskit) today for custom networks we only do so as
> >> rootless user and it forwards ::1 (by possibly mapping this to v4 inside
> >> the container) fine.  
> > So, wait a moment, is my comment at:
> >
> >    https://github.com/containers/podman/issues/14491#issuecomment-2898191772
> >
> > actually wrong? I don't have time right now to test that but from user
> > reports and some vague memory I thought ::1 forwarding wouldn't work
> > with custom networks regardless of root or rootless, because
> > rootlesskit didn't handle that anyway.  
>
> yes, rootlesskit handles ipv6 just fine, it is just that our 
> rootlessport code remaps that to v4 inside the container.

Actually, at a glance, I don't think that this could be fixed entirely
in the rootlessport implementation, as rootlesskit doesn't seem to look
at the destination address of the original connection at all.

> >> My main point for this feature was using as root (requires further
> >> changes to allow pasta running as root).  
> > ...which should be entirely on Podman side and it's still on my plate,
> > by the way:
> >
> >    https://github.com/containers/podman/issues/17840
> >    https://pad.passt.top/p/Features_2025#L40  
>
> I don't see how this can be fixed on the podman side, the network 
> namespace of a rootful container (not userns=auto) is owned by the root 
> user. If you configure something in there you must have real 
> CAP_NET_ADMIN from the host init userns. So pasta must not drop this 
> privilege before configuring the netns.

Oops, right. My starting point was this change, which is actually
trivial (at least as a test) and something I already tried out, but
then I hit a number of issues in Podman I never really figured out.

So yes, it takes one change in pasta, but the substantial part left for
me to figure out is why Podman didn't just work with it. It's not
necessarily complicated, I spent just a couple of hours on it, so maybe
there's something simple I missed.

> And even then with the future 
> netlink monitor work we would need to keep that privilege level to 
> modify the netns even during runtime?

This just reminded me that, somewhat surprisingly, for netlink
operations, the check on capabilities is not just performed on the
process creating the socket when the socket is created, but also later
*on the sender of the message*.

This is inconsistent with other operations on other types of sockets
where the whole context is checked and assigned at the time of the
creation, and was introduced because of a specific behaviour of Zebra
(the routing daemon) in 2014, see discussion around:

  https://lore.kernel.org/all/87d2g7d9ag.fsf_-_@x220.int.ebiederm.org/#r

and I stumbled upon it a while ago while preparing a seitan demo
replaying nft messages for an unprivileged container:

  https://seitan.rocks/seitan/tree/demo/nft.hjson#n38

So, my blanket answer "we create that socket at the beginning" doesn't
apply here.

However, assuming that this RFC patch from Jon actually works (I haven't
tested it):

  https://archives.passt.top/passt-dev/20251215015441.887736-11-jmaloy@redhat.com/

I would say we're fine with it. Well, there's still the possibility
that it doesn't work if Podman originally detached the network
namespace, I'm not sure.

If it doesn't work, we'll need to retain more capabilities, or even
keep a cloned process around for this kind of stuff. We could also fix
that in the kernel, Zebra doesn't need that quirk anymore.

> >> Because as root podman does
> >> port forwarding via DNAT firewall rules (i.e. custom nftables rules we
> >> add). The kernel however never added support for DNAT on ::1 meaning
> >> clients trying to access that are not getting forwarded. The only way to
> >> support this is using a user space helper. Right now this doesn't work
> >> and we do not use rootlessport for this either so I was just thinking
> >> ahead because we do have these users requests who want ::1 to work as root.
> >>
> >> For the current rootlessport use case we also must bind all ports as
> >> given (i.e. also addresses 0.0.0.0 bind address), just forwarding
> >> loopback to loopback is not what we want or do for security reasons, see
> >> CVE-2021-20199. And logically it would not really work to have another
> >> process bind 0.0.0.0 and this pasta helper bind lo on the same port at
> >> the same time.
> >>
> >> The way I am thinking is bind ports as normal, add the no-tap option and
> >> add two options to give the v4 and v6 namespace (container) side connect
> >> addresses so we never actually connect to lo. Then we also should have a
> >> dynamic way to update the connect addresses at runtime which is required
> >> for podman network connect/disconnect to work which changes the
> >> addresses inside the namespace, see
> >> https://github.com/containers/podman/commit/e88d8dbeae2aebd2d816f16a21891764163afcd4.
> >>
> >> Overall none of this is a blocker for removing rootlessport. I think our
> >> plan was and still is to use the dynamic port forwarding logic David is
> >> working on to replace the rootless custom network port forwarding case
> >> with that.  
> > Regardless of other requirements that are needed as well to support
> > forwarding ::1 for root containers (or rootless with --userns=auto),
> > this feature by itself makes sense as it is and we'll need it as it is,
> > right?
> >
> > By the way we routinely get requests for this feature by pasta (and
> > Podman) users, regardless of any specific Podman integration, so I
> > think the feature is generic enough as to make sense regardless of your
> > plan for root containers.
>
> I am not sure how I would use or integrate a loopback to loopback 
> forwarder in podman so I don't think we would need or can use that as is.

Well, I'm not sure, I just remember that you had in mind some use cases
that could be fixed with this (and even noted them down in the
references from the ticket).

Sorry Yumei, I should have checked more recently, as it looks like this
doesn't currently have as much priority as I thought, at least in
Podman's perspective. In any case it's definitely useful.

By the way, if it's for the root case, we'll still need it the day we
support operation when started as root. If it's to fix up IPv4 / IPv6
loopback mapping in the rootless case, it would be usable right away.

> I think the use case itself is still interesting and if there are end 
> users asking for it sure not objections from me. I guess it could be 
> interesting to expose a service without giving it access to the full 
> internet and without having to deal with complicated firewall rules, 
> i.e. with this we get a container that only could communicate by 
> replying to the forwarded ports.

Right, yes, it might also be one way to implement "isolated" containers
as described in https://bugs.passt.top/show_bug.cgi?id=139 (I still have
to follow up on comments there, and that might take a while, but let me
quickly mention that it has little/nothing to do with local mode).

-- 
Stefano


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

* Re: [PATCH] conf, pasta: Add --no-tap option
  2026-01-05  8:53   ` Yumei Huang
@ 2026-01-10 18:12     ` Stefano Brivio
  2026-01-12  4:26       ` David Gibson
  2026-01-13  9:57       ` Yumei Huang
  0 siblings, 2 replies; 16+ messages in thread
From: Stefano Brivio @ 2026-01-10 18:12 UTC (permalink / raw)
  To: Yumei Huang; +Cc: David Gibson, passt-dev

On Mon, 5 Jan 2026 16:53:49 +0800
Yumei Huang <yuhuang@redhat.com> wrote:

> On Mon, Jan 5, 2026 at 12:18 PM David Gibson
> <david@gibson.dropbear.id.au> wrote:
> >
> > On Mon, Dec 29, 2025 at 05:55:58PM +0800, Yumei Huang wrote:  
> > > This patch introduces a mode where we only forward loopback connections
> > > and traffic between two namespaces (via the loopback interface, 'lo'),
> > > without a tap device.
> > >
> > > With this, podman can support forwarding ::1 in custom networks when using
> > > rootlesskit for forwarding ports.
> > >
> > > In --no-tap mode, --host-lo-to-ns-lo, --no-icmp and --no-ra is automatically
> > > enabled. Options requiring a tap device (--ns-ifname, --ns-mac-addr,
> > > --config-net, --outbound-if4/6) are rejected.
> > >
> > > Link: https://bugs.passt.top/show_bug.cgi?id=149
> > > Signed-off-by: Yumei Huang <yuhuang@redhat.com>  
> >
> > Nice work.  There are some things that need polish, but overall this
> > looks pretty good to me.  Like Stefano, I'm pleasantly surprised at
> > how simple it turned out to be.
> >  
> > > ---
> > >  conf.c  | 56 +++++++++++++++++++++++++++++++++++++++++---------------
> > >  fwd.c   |  3 +++
> > >  passt.1 |  5 +++++
> > >  passt.h |  2 ++
> > >  pasta.c |  3 +++
> > >  tap.c   | 11 +++++++----
> > >  6 files changed, 61 insertions(+), 19 deletions(-)
> > >
> > > diff --git a/conf.c b/conf.c
> > > index 84ae12b..353d0a5 100644
> > > --- a/conf.c
> > > +++ b/conf.c
> > > @@ -1049,7 +1049,8 @@ pasta_opts:
> > >               "  --no-copy-addrs      DEPRECATED:\n"
> > >               "                       Don't copy all addresses to namespace\n"
> > >               "  --ns-mac-addr ADDR   Set MAC address on tap interface\n"
> > > -             "  --no-splice          Disable inbound socket splicing\n");
> > > +             "  --no-splice          Disable inbound socket splicing\n"
> > > +             "  --no-tap             Don't create tap device\n");  
> >
> > I feel like this description can be improved, but I'm not exactly sure
> > how, yet.

A few possible alternatives:

- "Only enable loopback forwarding"

- "Loopback only from/to namespace"

- call it --splice-only, and use one of the descriptions above

- call it --loopback-only, and use one of the descriptions above

> >  
> > >
> > >       passt_exit(status);
> > >  }
> > > @@ -1451,6 +1452,7 @@ void conf(struct ctx *c, int argc, char **argv)
> > >               {"no-ndp",      no_argument,            &c->no_ndp,     1 },
> > >               {"no-ra",       no_argument,            &c->no_ra,      1 },
> > >               {"no-splice",   no_argument,            &c->no_splice,  1 },
> > > +             {"no-tap",      no_argument,            &c->no_tap,     1 },
> > >               {"freebind",    no_argument,            &c->freebind,   1 },
> > >               {"no-map-gw",   no_argument,            &no_map_gw,     1 },
> > >               {"ipv4-only",   no_argument,            NULL,           '4' },
> > > @@ -1947,8 +1949,11 @@ void conf(struct ctx *c, int argc, char **argv)
> > >               }
> > >       } while (name != -1);
> > >
> > > -     if (c->mode != MODE_PASTA)
> > > +     if (c->mode != MODE_PASTA) {
> > >               c->no_splice = 1;
> > > +             if (c->no_tap)
> > > +                     die("--no-tap is for pasta mode only");
> > > +     }
> > >
> > >       if (c->mode == MODE_PASTA && !c->pasta_conf_ns) {
> > >               if (copy_routes_opt)
> > > @@ -1957,6 +1962,25 @@ void conf(struct ctx *c, int argc, char **argv)
> > >                       die("--no-copy-addrs needs --config-net");
> > >       }
> > >
> > > +     if (c->mode == MODE_PASTA && c->no_tap) {
> > > +             if (c->no_splice)
> > > +                     die("--no-tap is incompatible with --no-splice");
> > > +             if (*c->ip4.ifname_out || *c->ip6.ifname_out)
> > > +                     die("--no-tap is incompatible with --outbound-if4/6");
> > > +             if (*c->pasta_ifn)
> > > +                     die("--no-tap is incompatible with --ns-ifname");
> > > +             if (*c->guest_mac)
> > > +                     die("--no-tap is incompatible with --ns-mac-addr");  
> >
> > These all make sense.  It might also make sense to exclude the -i
> > option - setting a template interface also makes no sense in --no-tap
> > mode.  
> 
> Sure, I can add an if condition with if4 (as if4=if6 in that case).
> >  
> > > +             if (c->pasta_conf_ns)
> > > +                     die("--no-tap is incompatible with --config-net");  
> >
> > I don't think this is right.  We still can and should bring up 'lo' in
> > the --no-tap case.  
> 
> I see your point, but seems c->pasta_conf_ns is only used for tap as
> https://passt.top/passt/tree/pasta.c#n328, 'lo' is configured before
> that line.

Right, and the reason is that there are basic bits of functionality
(probing pipe sizes if I recall correctly, or anyway probing for some
kind of capability) that need the loopback interface to be up.

On the other hand, checks we're adding here are kind of fragile because
we'll add other options in the future and probably forget to check
which ones are incompatible, so I would try a slightly different
approach: only check the options that are *obviously* conflicting with
--no-tap.

That is, the main thing "--config-net" does is to "Configure networking
in the namespace", which we still do with "--no-tap".

Now, I see that making sure c->pasta_conf_ns is false saves you checks
elsewhere in the implementation, which is, I think, a good reason to
have this check here.

But in general we don't need to exclude all the possible options that
make no sense with --no-tap. We don't really confuse users if we allow
them (or, at least, some of them).

> > > +             c->host_lo_to_ns_lo = 1;
> > > +             c->no_icmp = 1;
> > > +             c->no_ra = 1;
> > > +             c->no_dns = 1;
> > > +             c->no_dns_search = 1;  
> >
> > The reasoning for the last two items is a bit unclear to me.  IIUC,
> > no_dns and no_dns_search aren't so much about "support" for DNS itself
> > but for advertising DNS settings via DHCP.  Since DHCP will be
> > unsupported, so are these as a consequence.  Is that right?  
> 
> Yeah, I think so. Actually I added c->no_dhcp, c->no_ndp here as well,
> then removed them as they are set in later changes(conditions about
> c->ifi4/c->ifi6), though they turn out to be not quite right :'\

Do we care about them, though? That code won't be reachable anyway,
unless I'm missing something. Or is it to make the output of
conf_print() nicer? In that case I guess it makes sense to go and
disable things.

> > > +     }
> > > +
> > >       if (!ifi4 && *c->ip4.ifname_out)
> > >               ifi4 = if_nametoindex(c->ip4.ifname_out);
> > >
> > > @@ -1980,9 +2004,9 @@ void conf(struct ctx *c, int argc, char **argv)
> > >       log_conf_parsed = true;         /* Stop printing everything */
> > >
> > >       nl_sock_init(c, false);
> > > -     if (!v6_only)
> > > +     if (!v6_only && !c->no_tap)
> > >               c->ifi4 = conf_ip4(ifi4, &c->ip4);
> > > -     if (!v4_only)
> > > +     if (!v4_only && !c->no_tap)
> > >               c->ifi6 = conf_ip6(ifi6, &c->ip6);
> > >
> > >       if (c->ifi4 && c->mtu < IPV4_MIN_MTU) {
> > > @@ -1998,30 +2022,32 @@ void conf(struct ctx *c, int argc, char **argv)
> > >           (*c->ip6.ifname_out && !c->ifi6))
> > >               die("External interface not usable");
> > >
> > > -     if (!c->ifi4 && !c->ifi6 && !*c->pasta_ifn) {
> > > +     if (!c->ifi4 && !c->ifi6 && !*c->pasta_ifn && !c->no_tap) {
> > >               strncpy(c->pasta_ifn, pasta_default_ifn,
> > >                       sizeof(c->pasta_ifn) - 1);
> > >       }
> > >
> > >       if (!c->ifi4 && !v6_only) {
> > > -             info("IPv4: no external interface as template, use local mode");
> > > -
> > > -             conf_ip4_local(&c->ip4);
> > > +             if (!c->no_tap) {
> > > +                     info("IPv4: no external interface as template, use local mode");
> > > +                     conf_ip4_local(&c->ip4);
> > > +             }
> > >               c->ifi4 = -1;
> > >       }
> > >
> > >       if (!c->ifi6 && !v4_only) {
> > > -             info("IPv6: no external interface as template, use local mode");
> > > -
> > > -             conf_ip6_local(&c->ip6);
> > > +             if (!c->no_tap) {
> > > +                     info("IPv6: no external interface as template, use local mode");
> > > +                     conf_ip6_local(&c->ip6);
> > > +             }
> > >               c->ifi6 = -1;
> > >       }
> > >
> > > -     if (c->ifi4 && !no_map_gw &&
> > > +     if (c->ifi4 > 0 && !no_map_gw &&  
> >
> > This isn't quite right.  ifi4 == -1 now occurs in two cases: local
> > mode, and --no-tap mode.  Not setting map_host_loopback makes sense
> > for --no-tap mode, but it's still needed for local mode.  
> 
> I'm a bit confused by map_host_loopback. I don't quite understand the
> use scenario. IIUC, either in --no-tap mode or local mode, guest can
> only communicate with host.

That's not the case for local mode, the guest can communicate with any
other host. Local mode is just about addresses and routes, and the fact
that, when pasta started, there was no template interface.

> Then why do we need to set map_host_loopback? What's the benefit?

Example: guest has 169.254.2.1 (default in local mode), and wants to use
192.0.2.1 to refer to the host, via loopback interface.

> > >           IN4_IS_ADDR_UNSPECIFIED(&c->ip4.map_host_loopback))
> > >               c->ip4.map_host_loopback = c->ip4.guest_gw;
> > >
> > > -     if (c->ifi6 && !no_map_gw &&
> > > +     if (c->ifi6 > 0 && !no_map_gw &&  
> >
> > Same here.
> >  
> > >           IN6_IS_ADDR_UNSPECIFIED(&c->ip6.map_host_loopback))
> > >               c->ip6.map_host_loopback = c->ip6.guest_gw;
> > >
> > > @@ -2116,10 +2142,10 @@ void conf(struct ctx *c, int argc, char **argv)
> > >                       conf_ports(c, name, optarg, &c->udp.fwd_out);
> > >       } while (name != -1);
> > >
> > > -     if (!c->ifi4)
> > > +     if (c->ifi4 <= 0)
> > >               c->no_dhcp = 1;
> > >
> > > -     if (!c->ifi6) {
> > > +     if (c->ifi6 <= 0) {
> > >               c->no_ndp = 1;
> > >               c->no_dhcpv6 = 1;  
> >
> > And here.  Local mode can still use NDP and DHCP, even though --no-tap
> > mode can't.  It might be simpler to force no_ndp, no_dhcp etc. along
> > with no_ra and the rest above.  
> 
> Sure, I will add them.
> >  
> > >       } else if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addr)) {
> > > diff --git a/fwd.c b/fwd.c
> > > index 44a0e10..2f4a89a 100644
> > > --- a/fwd.c
> > > +++ b/fwd.c
> > > @@ -780,6 +780,9 @@ uint8_t fwd_nat_from_host(const struct ctx *c, uint8_t proto,
> > >               return PIF_SPLICE;
> > >       }
> > >
> > > +     if (c->no_tap)
> > > +             return PIF_NONE;
> > > +
> > >       if (!nat_inbound(c, &ini->eaddr, &tgt->oaddr)) {
> > >               if (inany_v4(&ini->eaddr)) {
> > >                       if (IN4_IS_ADDR_UNSPECIFIED(&c->ip4.our_tap_addr))
> > > diff --git a/passt.1 b/passt.1
> > > index db0d662..2d643f7 100644
> > > --- a/passt.1
> > > +++ b/passt.1
> > > @@ -755,6 +755,11 @@ Default is to let the tap driver build a pseudorandom hardware address.
> > >  Disable the bypass path for inbound, local traffic. See the section \fBHandling
> > >  of local traffic in pasta\fR in the \fBNOTES\fR for more details.
> > >
> > > +.TP
> > > +.BR \-\-no-tap
> > > +Do not create a tap device in the namespace. In this mode, only local loopback
> > > +traffic between namespaces is forwarded using splice.  
> >
> > This probably wants some work, because I'm not sure "tap device" and
> > "splice" are sufficiently clear in this context.  
> 
> Yeah, I will think about that. Thanks.
> >
> >  
> > > +
> > >  .SH EXAMPLES
> > >
> > >  .SS \fBpasta
> > > diff --git a/passt.h b/passt.h
> > > index 79d01dd..0c1ec4c 100644
> > > --- a/passt.h
> > > +++ b/passt.h
> > > @@ -200,6 +200,7 @@ struct ip6_ctx {
> > >   * @no_ndp:          Disable NDP handler altogether
> > >   * @no_ra:           Disable router advertisements
> > >   * @no_splice:               Disable socket splicing for inbound traffic
> > > + * @no_tap:          Do not create tap device
> > >   * @host_lo_to_ns_lo:        Map host loopback addresses to ns loopback addresses
> > >   * @freebind:                Allow binding of non-local addresses for forwarding
> > >   * @low_wmem:                Low probed net.core.wmem_max
> > > @@ -277,6 +278,7 @@ struct ctx {
> > >       int no_ndp;
> > >       int no_ra;
> > >       int no_splice;
> > > +     int no_tap;
> > >       int host_lo_to_ns_lo;
> > >       int freebind;
> > >
> > > diff --git a/pasta.c b/pasta.c
> > > index 0ddd6b0..3510ec5 100644
> > > --- a/pasta.c
> > > +++ b/pasta.c
> > > @@ -316,6 +316,9 @@ void pasta_ns_conf(struct ctx *c)
> > >               die("Couldn't bring up loopback interface in namespace: %s",
> > >                   strerror_(-rc));
> > >
> > > +     if (c->no_tap)
> > > +             return;
> > > +
> > >       /* Get or set MAC in target namespace */
> > >       if (MAC_IS_ZERO(c->guest_mac))
> > >               nl_link_get_mac(nl_sock_ns, c->pasta_ifi, c->guest_mac);
> > > diff --git a/tap.c b/tap.c
> > > index 9d1344b..9b4eedc 100644
> > > --- a/tap.c
> > > +++ b/tap.c
> > > @@ -1491,13 +1491,16 @@ static int tap_ns_tun(void *arg)
> > >   */
> > >  static void tap_sock_tun_init(struct ctx *c)
> > >  {
> > > -     NS_CALL(tap_ns_tun, c);
> > > -     if (c->fd_tap == -1)
> > > -             die("Failed to set up tap device in namespace");
> > > +     if (!c->no_tap) {
> > > +             NS_CALL(tap_ns_tun, c);
> > > +             if (c->fd_tap == -1)
> > > +                     die("Failed to set up tap device in namespace");
> > > +     }
> > >
> > >       pasta_ns_conf(c);
> > >
> > > -     tap_start_connection(c);
> > > +     if (!c->no_tap)
> > > +             tap_start_connection(c);
> > >  }
> > >
> > >  /**
> > > --
> > > 2.49.0
> > >  
> >
> > --
> > 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  
> 
> 
> 

-- 
Stefano


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

* Re: [PATCH] conf, pasta: Add --no-tap option
  2025-12-29  9:55 [PATCH] conf, pasta: Add --no-tap option Yumei Huang
                   ` (2 preceding siblings ...)
  2026-01-05 13:48 ` Paul Holzinger
@ 2026-01-10 18:12 ` Stefano Brivio
  2026-01-13 11:20   ` Yumei Huang
  3 siblings, 1 reply; 16+ messages in thread
From: Stefano Brivio @ 2026-01-10 18:12 UTC (permalink / raw)
  To: Yumei Huang; +Cc: passt-dev, david

On Mon, 29 Dec 2025 17:55:58 +0800
Yumei Huang <yuhuang@redhat.com> wrote:

> This patch introduces a mode where we only forward loopback connections
> and traffic between two namespaces (via the loopback interface, 'lo'),
> without a tap device.
> 
> With this, podman can support forwarding ::1 in custom networks when using
> rootlesskit for forwarding ports.
> 
> In --no-tap mode, --host-lo-to-ns-lo, --no-icmp and --no-ra is automatically
> enabled. Options requiring a tap device (--ns-ifname, --ns-mac-addr,
> --config-net, --outbound-if4/6) are rejected.
> 
> Link: https://bugs.passt.top/show_bug.cgi?id=149
> Signed-off-by: Yumei Huang <yuhuang@redhat.com>
> ---
>  conf.c  | 56 +++++++++++++++++++++++++++++++++++++++++---------------
>  fwd.c   |  3 +++
>  passt.1 |  5 +++++
>  passt.h |  2 ++
>  pasta.c |  3 +++
>  tap.c   | 11 +++++++----
>  6 files changed, 61 insertions(+), 19 deletions(-)
> 
> diff --git a/conf.c b/conf.c
> index 84ae12b..353d0a5 100644
> --- a/conf.c
> +++ b/conf.c
> @@ -1049,7 +1049,8 @@ pasta_opts:
>  		"  --no-copy-addrs	DEPRECATED:\n"
>  		"			Don't copy all addresses to namespace\n"
>  		"  --ns-mac-addr ADDR	Set MAC address on tap interface\n"
> -		"  --no-splice		Disable inbound socket splicing\n");
> +		"  --no-splice		Disable inbound socket splicing\n"
> +		"  --no-tap		Don't create tap device\n");
>  
>  	passt_exit(status);
>  }
> @@ -1451,6 +1452,7 @@ void conf(struct ctx *c, int argc, char **argv)
>  		{"no-ndp",	no_argument,		&c->no_ndp,	1 },
>  		{"no-ra",	no_argument,		&c->no_ra,	1 },
>  		{"no-splice",	no_argument,		&c->no_splice,	1 },
> +		{"no-tap",	no_argument,		&c->no_tap,	1 },
>  		{"freebind",	no_argument,		&c->freebind,	1 },
>  		{"no-map-gw",	no_argument,		&no_map_gw,	1 },
>  		{"ipv4-only",	no_argument,		NULL,		'4' },
> @@ -1947,8 +1949,11 @@ void conf(struct ctx *c, int argc, char **argv)
>  		}
>  	} while (name != -1);
>  
> -	if (c->mode != MODE_PASTA)
> +	if (c->mode != MODE_PASTA) {
>  		c->no_splice = 1;
> +		if (c->no_tap)
> +			die("--no-tap is for pasta mode only");
> +	}
>  
>  	if (c->mode == MODE_PASTA && !c->pasta_conf_ns) {
>  		if (copy_routes_opt)
> @@ -1957,6 +1962,25 @@ void conf(struct ctx *c, int argc, char **argv)
>  			die("--no-copy-addrs needs --config-net");
>  	}
>  
> +	if (c->mode == MODE_PASTA && c->no_tap) {
> +		if (c->no_splice)
> +			die("--no-tap is incompatible with --no-splice");

I'm not sure if you need this for other reasons, but as long as it's
called --no-tap, it's not really incompatible with --no-splice.

Maybe users just want to get a disconnected namespace for whatever
reason ('pasta' is shorter to type than 'unshare -rUn').

> +		if (*c->ip4.ifname_out || *c->ip6.ifname_out)
> +			die("--no-tap is incompatible with --outbound-if4/6");
> +		if (*c->pasta_ifn)
> +			die("--no-tap is incompatible with --ns-ifname");
> +		if (*c->guest_mac)
> +			die("--no-tap is incompatible with --ns-mac-addr");
> +		if (c->pasta_conf_ns)
> +			die("--no-tap is incompatible with --config-net");

I guess all these checks are to save some checks later, which looks like
a good reason to have them here.

If not, though, I don't think we *really* need to tell the user that
--ns-ifname will be ignored with --no-tap.

One thing that might confuse users, though, is this:

$ ./pasta --no-tap --mtu 1500 -- ip l
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

or even this:

$ ./pasta --no-tap -a 192.0.2.1 -- ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host proto kernel_lo 
       valid_lft forever preferred_lft forever

but I would rather *not* add conditions and checks for those even if
there's a *slight* potential for confusion, otherwise this becomes
really long. And it's really not worth it, I think.

> +
> +		c->host_lo_to_ns_lo = 1;
> +		c->no_icmp = 1;
> +		c->no_ra = 1;
> +		c->no_dns = 1;
> +		c->no_dns_search = 1;
> +	}
> +
>  	if (!ifi4 && *c->ip4.ifname_out)
>  		ifi4 = if_nametoindex(c->ip4.ifname_out);
>  
> @@ -1980,9 +2004,9 @@ void conf(struct ctx *c, int argc, char **argv)
>  	log_conf_parsed = true;		/* Stop printing everything */
>  
>  	nl_sock_init(c, false);
> -	if (!v6_only)
> +	if (!v6_only && !c->no_tap)
>  		c->ifi4 = conf_ip4(ifi4, &c->ip4);
> -	if (!v4_only)
> +	if (!v4_only && !c->no_tap)
>  		c->ifi6 = conf_ip6(ifi6, &c->ip6);
>  
>  	if (c->ifi4 && c->mtu < IPV4_MIN_MTU) {
> @@ -1998,30 +2022,32 @@ void conf(struct ctx *c, int argc, char **argv)
>  	    (*c->ip6.ifname_out && !c->ifi6))
>  		die("External interface not usable");
>  
> -	if (!c->ifi4 && !c->ifi6 && !*c->pasta_ifn) {
> +	if (!c->ifi4 && !c->ifi6 && !*c->pasta_ifn && !c->no_tap) {

You already checked that !*c->pasta_ifn above.

>  		strncpy(c->pasta_ifn, pasta_default_ifn,
>  			sizeof(c->pasta_ifn) - 1);
>  	}
>  
>  	if (!c->ifi4 && !v6_only) {
> -		info("IPv4: no external interface as template, use local mode");
> -
> -		conf_ip4_local(&c->ip4);
> +		if (!c->no_tap) {
> +			info("IPv4: no external interface as template, use local mode");
> +			conf_ip4_local(&c->ip4);
> +		}
>  		c->ifi4 = -1;
>  	}
>  
>  	if (!c->ifi6 && !v4_only) {
> -		info("IPv6: no external interface as template, use local mode");
> -
> -		conf_ip6_local(&c->ip6);
> +		if (!c->no_tap) {
> +			info("IPv6: no external interface as template, use local mode");
> +			conf_ip6_local(&c->ip6);
> +		}
>  		c->ifi6 = -1;
>  	}
>  
> -	if (c->ifi4 && !no_map_gw &&
> +	if (c->ifi4 > 0 && !no_map_gw &&
>  	    IN4_IS_ADDR_UNSPECIFIED(&c->ip4.map_host_loopback))
>  		c->ip4.map_host_loopback = c->ip4.guest_gw;
>  
> -	if (c->ifi6 && !no_map_gw &&
> +	if (c->ifi6 > 0 && !no_map_gw &&
>  	    IN6_IS_ADDR_UNSPECIFIED(&c->ip6.map_host_loopback))
>  		c->ip6.map_host_loopback = c->ip6.guest_gw;
>  
> @@ -2116,10 +2142,10 @@ void conf(struct ctx *c, int argc, char **argv)
>  			conf_ports(c, name, optarg, &c->udp.fwd_out);
>  	} while (name != -1);
>  
> -	if (!c->ifi4)
> +	if (c->ifi4 <= 0)
>  		c->no_dhcp = 1;
>  
> -	if (!c->ifi6) {
> +	if (c->ifi6 <= 0) {
>  		c->no_ndp = 1;
>  		c->no_dhcpv6 = 1;
>  	} else if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addr)) {
> diff --git a/fwd.c b/fwd.c
> index 44a0e10..2f4a89a 100644
> --- a/fwd.c
> +++ b/fwd.c
> @@ -780,6 +780,9 @@ uint8_t fwd_nat_from_host(const struct ctx *c, uint8_t proto,
>  		return PIF_SPLICE;
>  	}
>  
> +	if (c->no_tap)
> +		return PIF_NONE;
> +
>  	if (!nat_inbound(c, &ini->eaddr, &tgt->oaddr)) {
>  		if (inany_v4(&ini->eaddr)) {
>  			if (IN4_IS_ADDR_UNSPECIFIED(&c->ip4.our_tap_addr))
> diff --git a/passt.1 b/passt.1
> index db0d662..2d643f7 100644
> --- a/passt.1
> +++ b/passt.1
> @@ -755,6 +755,11 @@ Default is to let the tap driver build a pseudorandom hardware address.
>  Disable the bypass path for inbound, local traffic. See the section \fBHandling
>  of local traffic in pasta\fR in the \fBNOTES\fR for more details.
>  
> +.TP
> +.BR \-\-no-tap
> +Do not create a tap device in the namespace. In this mode, only local loopback
> +traffic between namespaces is forwarded using splice.

"Using splice" isn't really clear, and it's not entirely correct in the
case of UDP: there's no splice() system call there, even if we call
some UDP flows "spliced" for analogy with TCP.

Maybe just omit it, say:

  [...] In this mode, \fIpasta\fR only
  forwards loopback traffic between namespaces.

?

> +
>  .SH EXAMPLES
>  
>  .SS \fBpasta
> diff --git a/passt.h b/passt.h
> index 79d01dd..0c1ec4c 100644
> --- a/passt.h
> +++ b/passt.h
> @@ -200,6 +200,7 @@ struct ip6_ctx {
>   * @no_ndp:		Disable NDP handler altogether
>   * @no_ra:		Disable router advertisements
>   * @no_splice:		Disable socket splicing for inbound traffic
> + * @no_tap:		Do not create tap device
>   * @host_lo_to_ns_lo:	Map host loopback addresses to ns loopback addresses
>   * @freebind:		Allow binding of non-local addresses for forwarding
>   * @low_wmem:		Low probed net.core.wmem_max
> @@ -277,6 +278,7 @@ struct ctx {
>  	int no_ndp;
>  	int no_ra;
>  	int no_splice;
> +	int no_tap;
>  	int host_lo_to_ns_lo;
>  	int freebind;
>  
> diff --git a/pasta.c b/pasta.c
> index 0ddd6b0..3510ec5 100644
> --- a/pasta.c
> +++ b/pasta.c
> @@ -316,6 +316,9 @@ void pasta_ns_conf(struct ctx *c)
>  		die("Couldn't bring up loopback interface in namespace: %s",
>  		    strerror_(-rc));
>  
> +	if (c->no_tap)
> +		return;
> +
>  	/* Get or set MAC in target namespace */
>  	if (MAC_IS_ZERO(c->guest_mac))
>  		nl_link_get_mac(nl_sock_ns, c->pasta_ifi, c->guest_mac);
> diff --git a/tap.c b/tap.c
> index 9d1344b..9b4eedc 100644
> --- a/tap.c
> +++ b/tap.c
> @@ -1491,13 +1491,16 @@ static int tap_ns_tun(void *arg)
>   */
>  static void tap_sock_tun_init(struct ctx *c)
>  {
> -	NS_CALL(tap_ns_tun, c);
> -	if (c->fd_tap == -1)
> -		die("Failed to set up tap device in namespace");
> +	if (!c->no_tap) {
> +		NS_CALL(tap_ns_tun, c);
> +		if (c->fd_tap == -1)
> +			die("Failed to set up tap device in namespace");
> +	}
>  
>  	pasta_ns_conf(c);
>  
> -	tap_start_connection(c);
> +	if (!c->no_tap)
> +		tap_start_connection(c);
>  }
>  
>  /**

Other than that, minus pending comments, it all looks good to me.

-- 
Stefano


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

* Re: [PATCH] conf, pasta: Add --no-tap option
  2026-01-10 18:12     ` Stefano Brivio
@ 2026-01-12  4:26       ` David Gibson
  2026-01-13  0:12         ` Stefano Brivio
  2026-01-13  9:57       ` Yumei Huang
  1 sibling, 1 reply; 16+ messages in thread
From: David Gibson @ 2026-01-12  4:26 UTC (permalink / raw)
  To: Stefano Brivio; +Cc: Yumei Huang, passt-dev

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

On Sat, Jan 10, 2026 at 07:12:19PM +0100, Stefano Brivio wrote:
> On Mon, 5 Jan 2026 16:53:49 +0800
> Yumei Huang <yuhuang@redhat.com> wrote:
> 
> > On Mon, Jan 5, 2026 at 12:18 PM David Gibson
> > <david@gibson.dropbear.id.au> wrote:
> > >
> > > On Mon, Dec 29, 2025 at 05:55:58PM +0800, Yumei Huang wrote:  
> > > > This patch introduces a mode where we only forward loopback connections
> > > > and traffic between two namespaces (via the loopback interface, 'lo'),
> > > > without a tap device.
> > > >
> > > > With this, podman can support forwarding ::1 in custom networks when using
> > > > rootlesskit for forwarding ports.
> > > >
> > > > In --no-tap mode, --host-lo-to-ns-lo, --no-icmp and --no-ra is automatically
> > > > enabled. Options requiring a tap device (--ns-ifname, --ns-mac-addr,
> > > > --config-net, --outbound-if4/6) are rejected.
> > > >
> > > > Link: https://bugs.passt.top/show_bug.cgi?id=149
> > > > Signed-off-by: Yumei Huang <yuhuang@redhat.com>  
> > >
> > > Nice work.  There are some things that need polish, but overall this
> > > looks pretty good to me.  Like Stefano, I'm pleasantly surprised at
> > > how simple it turned out to be.
> > >  
> > > > ---
> > > >  conf.c  | 56 +++++++++++++++++++++++++++++++++++++++++---------------
> > > >  fwd.c   |  3 +++
> > > >  passt.1 |  5 +++++
> > > >  passt.h |  2 ++
> > > >  pasta.c |  3 +++
> > > >  tap.c   | 11 +++++++----
> > > >  6 files changed, 61 insertions(+), 19 deletions(-)
> > > >
> > > > diff --git a/conf.c b/conf.c
> > > > index 84ae12b..353d0a5 100644
> > > > --- a/conf.c
> > > > +++ b/conf.c
> > > > @@ -1049,7 +1049,8 @@ pasta_opts:
> > > >               "  --no-copy-addrs      DEPRECATED:\n"
> > > >               "                       Don't copy all addresses to namespace\n"
> > > >               "  --ns-mac-addr ADDR   Set MAC address on tap interface\n"
> > > > -             "  --no-splice          Disable inbound socket splicing\n");
> > > > +             "  --no-splice          Disable inbound socket splicing\n"
> > > > +             "  --no-tap             Don't create tap device\n");  
> > >
> > > I feel like this description can be improved, but I'm not exactly sure
> > > how, yet.
> 
> A few possible alternatives:
> 
> - "Only enable loopback forwarding"

I prefer this one to the one below.

> - "Loopback only from/to namespace"
> 
> - call it --splice-only, and use one of the descriptions above
> 
> - call it --loopback-only, and use one of the descriptions above
> 
> > >  
> > > >
> > > >       passt_exit(status);
> > > >  }
> > > > @@ -1451,6 +1452,7 @@ void conf(struct ctx *c, int argc, char **argv)
> > > >               {"no-ndp",      no_argument,            &c->no_ndp,     1 },
> > > >               {"no-ra",       no_argument,            &c->no_ra,      1 },
> > > >               {"no-splice",   no_argument,            &c->no_splice,  1 },
> > > > +             {"no-tap",      no_argument,            &c->no_tap,     1 },
> > > >               {"freebind",    no_argument,            &c->freebind,   1 },
> > > >               {"no-map-gw",   no_argument,            &no_map_gw,     1 },
> > > >               {"ipv4-only",   no_argument,            NULL,           '4' },
> > > > @@ -1947,8 +1949,11 @@ void conf(struct ctx *c, int argc, char **argv)
> > > >               }
> > > >       } while (name != -1);
> > > >
> > > > -     if (c->mode != MODE_PASTA)
> > > > +     if (c->mode != MODE_PASTA) {
> > > >               c->no_splice = 1;
> > > > +             if (c->no_tap)
> > > > +                     die("--no-tap is for pasta mode only");
> > > > +     }
> > > >
> > > >       if (c->mode == MODE_PASTA && !c->pasta_conf_ns) {
> > > >               if (copy_routes_opt)
> > > > @@ -1957,6 +1962,25 @@ void conf(struct ctx *c, int argc, char **argv)
> > > >                       die("--no-copy-addrs needs --config-net");
> > > >       }
> > > >
> > > > +     if (c->mode == MODE_PASTA && c->no_tap) {
> > > > +             if (c->no_splice)
> > > > +                     die("--no-tap is incompatible with --no-splice");
> > > > +             if (*c->ip4.ifname_out || *c->ip6.ifname_out)
> > > > +                     die("--no-tap is incompatible with --outbound-if4/6");
> > > > +             if (*c->pasta_ifn)
> > > > +                     die("--no-tap is incompatible with --ns-ifname");
> > > > +             if (*c->guest_mac)
> > > > +                     die("--no-tap is incompatible with --ns-mac-addr");  
> > >
> > > These all make sense.  It might also make sense to exclude the -i
> > > option - setting a template interface also makes no sense in --no-tap
> > > mode.  
> > 
> > Sure, I can add an if condition with if4 (as if4=if6 in that case).
> > >  
> > > > +             if (c->pasta_conf_ns)
> > > > +                     die("--no-tap is incompatible with --config-net");  
> > >
> > > I don't think this is right.  We still can and should bring up 'lo' in
> > > the --no-tap case.  
> > 
> > I see your point, but seems c->pasta_conf_ns is only used for tap as
> > https://passt.top/passt/tree/pasta.c#n328, 'lo' is configured before
> > that line.
> 
> Right, and the reason is that there are basic bits of functionality
> (probing pipe sizes if I recall correctly, or anyway probing for some
> kind of capability) that need the loopback interface to be up.

Ah, right.  Drat.  In general I don't like us touching the guest
netlink at all if we don't have --config-net.  Hrm.. now what exactly
needs this.  It's not anything in sock_probe_features() - that runs in
the host ns.  Not pipe sizes, either - that also takes place in the
host ns (and netns is irrelevant to pipes, anyway).   There could well
be something, but I'm not sure what it is.

> On the other hand, checks we're adding here are kind of fragile because
> we'll add other options in the future and probably forget to check
> which ones are incompatible, so I would try a slightly different
> approach: only check the options that are *obviously* conflicting with
> --no-tap.

I agree.

> That is, the main thing "--config-net" does is to "Configure networking
> in the namespace", which we still do with "--no-tap".

Agreed.

> Now, I see that making sure c->pasta_conf_ns is false saves you checks
> elsewhere in the implementation, which is, I think, a good reason to
> have this check here.
> 
> But in general we don't need to exclude all the possible options that
> make no sense with --no-tap. We don't really confuse users if we allow
> them (or, at least, some of them).
> 
> > > > +             c->host_lo_to_ns_lo = 1;
> > > > +             c->no_icmp = 1;
> > > > +             c->no_ra = 1;
> > > > +             c->no_dns = 1;
> > > > +             c->no_dns_search = 1;  
> > >
> > > The reasoning for the last two items is a bit unclear to me.  IIUC,
> > > no_dns and no_dns_search aren't so much about "support" for DNS itself
> > > but for advertising DNS settings via DHCP.  Since DHCP will be
> > > unsupported, so are these as a consequence.  Is that right?  
> > 
> > Yeah, I think so. Actually I added c->no_dhcp, c->no_ndp here as well,
> > then removed them as they are set in later changes(conditions about
> > c->ifi4/c->ifi6), though they turn out to be not quite right :'\
> 
> Do we care about them, though? That code won't be reachable anyway,
> unless I'm missing something. Or is it to make the output of
> conf_print() nicer? In that case I guess it makes sense to go and
> disable things.

I think we want to avoid making conf_print() output misleading, and
this seems a reasonable way of doing it.

-- 
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] 16+ messages in thread

* Re: [PATCH] conf, pasta: Add --no-tap option
  2026-01-10 18:12       ` Stefano Brivio
@ 2026-01-12  8:20         ` Yumei Huang
  0 siblings, 0 replies; 16+ messages in thread
From: Yumei Huang @ 2026-01-12  8:20 UTC (permalink / raw)
  To: Stefano Brivio; +Cc: Paul Holzinger, passt-dev, david, Jon Maloy

On Sun, Jan 11, 2026 at 2:12 AM Stefano Brivio <sbrivio@redhat.com> wrote:
>
> [Cc'ing Jon for awareness around the part about netlink monitor and
> capabilities, four paragraphs down]
>
> On Wed, 7 Jan 2026 16:20:18 +0100
> Paul Holzinger <pholzing@redhat.com> wrote:
>
> > On 05/01/2026 22:10, Stefano Brivio wrote:
> > > On Mon, 5 Jan 2026 14:48:15 +0100
> > > Paul Holzinger <pholzing@redhat.com> wrote:
> > >
> > >> Sorry I was out for a while so I didn't had time to clarify on the bug
> > >> before.
> > >>
> > >> On 29/12/2025 10:55, Yumei Huang wrote:
> > >>> This patch introduces a mode where we only forward loopback connections
> > >>> and traffic between two namespaces (via the loopback interface, 'lo'),
> > >>> without a tap device.
> > >>>
> > >>> With this, podman can support forwarding ::1 in custom networks when using
> > >>> rootlesskit for forwarding ports.
> > >> I guess I didn't really communicate my requirements well.
> > > I guess it's more likely that you actually did, but I mixed up the
> > > association between requirements and use cases, sorry for that.
> > >
> > > In any case, good that we need this anyway, just for another use case.
> > > :)
> > >
> > >> When we use
> > >> rootlessport (rootlesskit) today for custom networks we only do so as
> > >> rootless user and it forwards ::1 (by possibly mapping this to v4 inside
> > >> the container) fine.
> > > So, wait a moment, is my comment at:
> > >
> > >    https://github.com/containers/podman/issues/14491#issuecomment-2898191772
> > >
> > > actually wrong? I don't have time right now to test that but from user
> > > reports and some vague memory I thought ::1 forwarding wouldn't work
> > > with custom networks regardless of root or rootless, because
> > > rootlesskit didn't handle that anyway.
> >
> > yes, rootlesskit handles ipv6 just fine, it is just that our
> > rootlessport code remaps that to v4 inside the container.
>
> Actually, at a glance, I don't think that this could be fixed entirely
> in the rootlessport implementation, as rootlesskit doesn't seem to look
> at the destination address of the original connection at all.
>
> > >> My main point for this feature was using as root (requires further
> > >> changes to allow pasta running as root).
> > > ...which should be entirely on Podman side and it's still on my plate,
> > > by the way:
> > >
> > >    https://github.com/containers/podman/issues/17840
> > >    https://pad.passt.top/p/Features_2025#L40
> >
> > I don't see how this can be fixed on the podman side, the network
> > namespace of a rootful container (not userns=auto) is owned by the root
> > user. If you configure something in there you must have real
> > CAP_NET_ADMIN from the host init userns. So pasta must not drop this
> > privilege before configuring the netns.
>
> Oops, right. My starting point was this change, which is actually
> trivial (at least as a test) and something I already tried out, but
> then I hit a number of issues in Podman I never really figured out.
>
> So yes, it takes one change in pasta, but the substantial part left for
> me to figure out is why Podman didn't just work with it. It's not
> necessarily complicated, I spent just a couple of hours on it, so maybe
> there's something simple I missed.
>
> > And even then with the future
> > netlink monitor work we would need to keep that privilege level to
> > modify the netns even during runtime?
>
> This just reminded me that, somewhat surprisingly, for netlink
> operations, the check on capabilities is not just performed on the
> process creating the socket when the socket is created, but also later
> *on the sender of the message*.
>
> This is inconsistent with other operations on other types of sockets
> where the whole context is checked and assigned at the time of the
> creation, and was introduced because of a specific behaviour of Zebra
> (the routing daemon) in 2014, see discussion around:
>
>   https://lore.kernel.org/all/87d2g7d9ag.fsf_-_@x220.int.ebiederm.org/#r
>
> and I stumbled upon it a while ago while preparing a seitan demo
> replaying nft messages for an unprivileged container:
>
>   https://seitan.rocks/seitan/tree/demo/nft.hjson#n38
>
> So, my blanket answer "we create that socket at the beginning" doesn't
> apply here.
>
> However, assuming that this RFC patch from Jon actually works (I haven't
> tested it):
>
>   https://archives.passt.top/passt-dev/20251215015441.887736-11-jmaloy@redhat.com/
>
> I would say we're fine with it. Well, there's still the possibility
> that it doesn't work if Podman originally detached the network
> namespace, I'm not sure.
>
> If it doesn't work, we'll need to retain more capabilities, or even
> keep a cloned process around for this kind of stuff. We could also fix
> that in the kernel, Zebra doesn't need that quirk anymore.
>
> > >> Because as root podman does
> > >> port forwarding via DNAT firewall rules (i.e. custom nftables rules we
> > >> add). The kernel however never added support for DNAT on ::1 meaning
> > >> clients trying to access that are not getting forwarded. The only way to
> > >> support this is using a user space helper. Right now this doesn't work
> > >> and we do not use rootlessport for this either so I was just thinking
> > >> ahead because we do have these users requests who want ::1 to work as root.
> > >>
> > >> For the current rootlessport use case we also must bind all ports as
> > >> given (i.e. also addresses 0.0.0.0 bind address), just forwarding
> > >> loopback to loopback is not what we want or do for security reasons, see
> > >> CVE-2021-20199. And logically it would not really work to have another
> > >> process bind 0.0.0.0 and this pasta helper bind lo on the same port at
> > >> the same time.
> > >>
> > >> The way I am thinking is bind ports as normal, add the no-tap option and
> > >> add two options to give the v4 and v6 namespace (container) side connect
> > >> addresses so we never actually connect to lo. Then we also should have a
> > >> dynamic way to update the connect addresses at runtime which is required
> > >> for podman network connect/disconnect to work which changes the
> > >> addresses inside the namespace, see
> > >> https://github.com/containers/podman/commit/e88d8dbeae2aebd2d816f16a21891764163afcd4.
> > >>
> > >> Overall none of this is a blocker for removing rootlessport. I think our
> > >> plan was and still is to use the dynamic port forwarding logic David is
> > >> working on to replace the rootless custom network port forwarding case
> > >> with that.
> > > Regardless of other requirements that are needed as well to support
> > > forwarding ::1 for root containers (or rootless with --userns=auto),
> > > this feature by itself makes sense as it is and we'll need it as it is,
> > > right?
> > >
> > > By the way we routinely get requests for this feature by pasta (and
> > > Podman) users, regardless of any specific Podman integration, so I
> > > think the feature is generic enough as to make sense regardless of your
> > > plan for root containers.
> >
> > I am not sure how I would use or integrate a loopback to loopback
> > forwarder in podman so I don't think we would need or can use that as is.
>
> Well, I'm not sure, I just remember that you had in mind some use cases
> that could be fixed with this (and even noted them down in the
> references from the ticket).
>
> Sorry Yumei, I should have checked more recently, as it looks like this
> doesn't currently have as much priority as I thought, at least in
> Podman's perspective. In any case it's definitely useful.

No worries at all :)
>
> By the way, if it's for the root case, we'll still need it the day we
> support operation when started as root. If it's to fix up IPv4 / IPv6
> loopback mapping in the rootless case, it would be usable right away.
>
> > I think the use case itself is still interesting and if there are end
> > users asking for it sure not objections from me. I guess it could be
> > interesting to expose a service without giving it access to the full
> > internet and without having to deal with complicated firewall rules,
> > i.e. with this we get a container that only could communicate by
> > replying to the forwarded ports.
>
> Right, yes, it might also be one way to implement "isolated" containers
> as described in https://bugs.passt.top/show_bug.cgi?id=139 (I still have
> to follow up on comments there, and that might take a while, but let me
> quickly mention that it has little/nothing to do with local mode).
>
> --
> Stefano
>


-- 
Thanks,

Yumei Huang


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

* Re: [PATCH] conf, pasta: Add --no-tap option
  2026-01-12  4:26       ` David Gibson
@ 2026-01-13  0:12         ` Stefano Brivio
  2026-01-13  2:39           ` David Gibson
  0 siblings, 1 reply; 16+ messages in thread
From: Stefano Brivio @ 2026-01-13  0:12 UTC (permalink / raw)
  To: David Gibson; +Cc: Yumei Huang, passt-dev

On Mon, 12 Jan 2026 15:26:14 +1100
David Gibson <david@gibson.dropbear.id.au> wrote:

> On Sat, Jan 10, 2026 at 07:12:19PM +0100, Stefano Brivio wrote:
> > On Mon, 5 Jan 2026 16:53:49 +0800
> > Yumei Huang <yuhuang@redhat.com> wrote:
> >   
> > > On Mon, Jan 5, 2026 at 12:18 PM David Gibson
> > > <david@gibson.dropbear.id.au> wrote:  
> > > >
> > > > On Mon, Dec 29, 2025 at 05:55:58PM +0800, Yumei Huang wrote:    
> > > >
> > > > > +             if (c->pasta_conf_ns)
> > > > > +                     die("--no-tap is incompatible with --config-net");    
> > > >
> > > > I don't think this is right.  We still can and should bring up 'lo' in
> > > > the --no-tap case.    
> > > 
> > > I see your point, but seems c->pasta_conf_ns is only used for tap as
> > > https://passt.top/passt/tree/pasta.c#n328, 'lo' is configured before
> > > that line.  
> > 
> > Right, and the reason is that there are basic bits of functionality
> > (probing pipe sizes if I recall correctly, or anyway probing for some
> > kind of capability) that need the loopback interface to be up.  
> 
> Ah, right.  Drat.  In general I don't like us touching the guest
> netlink at all if we don't have --config-net.  Hrm.. now what exactly
> needs this.  It's not anything in sock_probe_features() - that runs in
> the host ns.  Not pipe sizes, either - that also takes place in the
> host ns (and netns is irrelevant to pipes, anyway).   There could well
> be something, but I'm not sure what it is.

Actually, I tried, and I don't get any trouble (but I think I had some
error when I added that in 2021).

But we implicitly break any outbound forwarding because our listening
sockets will be unreachable (bind() succeeds though). So... I would be
wary of changing that at this point. There might be users relying on
it, and it's otherwise harmless I guess.

-- 
Stefano


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

* Re: [PATCH] conf, pasta: Add --no-tap option
  2026-01-13  0:12         ` Stefano Brivio
@ 2026-01-13  2:39           ` David Gibson
  0 siblings, 0 replies; 16+ messages in thread
From: David Gibson @ 2026-01-13  2:39 UTC (permalink / raw)
  To: Stefano Brivio; +Cc: Yumei Huang, passt-dev

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

On Tue, Jan 13, 2026 at 01:12:09AM +0100, Stefano Brivio wrote:
> On Mon, 12 Jan 2026 15:26:14 +1100
> David Gibson <david@gibson.dropbear.id.au> wrote:
> 
> > On Sat, Jan 10, 2026 at 07:12:19PM +0100, Stefano Brivio wrote:
> > > On Mon, 5 Jan 2026 16:53:49 +0800
> > > Yumei Huang <yuhuang@redhat.com> wrote:
> > >   
> > > > On Mon, Jan 5, 2026 at 12:18 PM David Gibson
> > > > <david@gibson.dropbear.id.au> wrote:  
> > > > >
> > > > > On Mon, Dec 29, 2025 at 05:55:58PM +0800, Yumei Huang wrote:    
> > > > >
> > > > > > +             if (c->pasta_conf_ns)
> > > > > > +                     die("--no-tap is incompatible with --config-net");    
> > > > >
> > > > > I don't think this is right.  We still can and should bring up 'lo' in
> > > > > the --no-tap case.    
> > > > 
> > > > I see your point, but seems c->pasta_conf_ns is only used for tap as
> > > > https://passt.top/passt/tree/pasta.c#n328, 'lo' is configured before
> > > > that line.  
> > > 
> > > Right, and the reason is that there are basic bits of functionality
> > > (probing pipe sizes if I recall correctly, or anyway probing for some
> > > kind of capability) that need the loopback interface to be up.  
> > 
> > Ah, right.  Drat.  In general I don't like us touching the guest
> > netlink at all if we don't have --config-net.  Hrm.. now what exactly
> > needs this.  It's not anything in sock_probe_features() - that runs in
> > the host ns.  Not pipe sizes, either - that also takes place in the
> > host ns (and netns is irrelevant to pipes, anyway).   There could well
> > be something, but I'm not sure what it is.
> 
> Actually, I tried, and I don't get any trouble (but I think I had some
> error when I added that in 2021).

Ok.

> But we implicitly break any outbound forwarding because our listening
> sockets will be unreachable (bind() succeeds though).

Networking doesn't work until you configure networking, that's the
normal state for !--config-net.  I don't see why that should be
different for outbound forwards than anything else.

> So... I would be
> wary of changing that at this point. There might be users relying on
> it, and it's otherwise harmless I guess.

I mean.. probably?  Almost certainly when pasta is creating the ns -
but in that case there's very little reason not to use --config-net
anyway.  The case I'm concerned about is attaching this to an existing
netns: this can alter the existing network config there.

-- 
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] 16+ messages in thread

* Re: [PATCH] conf, pasta: Add --no-tap option
  2026-01-10 18:12     ` Stefano Brivio
  2026-01-12  4:26       ` David Gibson
@ 2026-01-13  9:57       ` Yumei Huang
  1 sibling, 0 replies; 16+ messages in thread
From: Yumei Huang @ 2026-01-13  9:57 UTC (permalink / raw)
  To: Stefano Brivio; +Cc: David Gibson, passt-dev

On Sun, Jan 11, 2026 at 2:12 AM Stefano Brivio <sbrivio@redhat.com> wrote:
>
> On Mon, 5 Jan 2026 16:53:49 +0800
> Yumei Huang <yuhuang@redhat.com> wrote:
>
> > On Mon, Jan 5, 2026 at 12:18 PM David Gibson
> > <david@gibson.dropbear.id.au> wrote:
> > >
> > > On Mon, Dec 29, 2025 at 05:55:58PM +0800, Yumei Huang wrote:
> > > > This patch introduces a mode where we only forward loopback connections
> > > > and traffic between two namespaces (via the loopback interface, 'lo'),
> > > > without a tap device.
> > > >
> > > > With this, podman can support forwarding ::1 in custom networks when using
> > > > rootlesskit for forwarding ports.
> > > >
> > > > In --no-tap mode, --host-lo-to-ns-lo, --no-icmp and --no-ra is automatically
> > > > enabled. Options requiring a tap device (--ns-ifname, --ns-mac-addr,
> > > > --config-net, --outbound-if4/6) are rejected.
> > > >
> > > > Link: https://bugs.passt.top/show_bug.cgi?id=149
> > > > Signed-off-by: Yumei Huang <yuhuang@redhat.com>
> > >
> > > Nice work.  There are some things that need polish, but overall this
> > > looks pretty good to me.  Like Stefano, I'm pleasantly surprised at
> > > how simple it turned out to be.
> > >
> > > > ---
> > > >  conf.c  | 56 +++++++++++++++++++++++++++++++++++++++++---------------
> > > >  fwd.c   |  3 +++
> > > >  passt.1 |  5 +++++
> > > >  passt.h |  2 ++
> > > >  pasta.c |  3 +++
> > > >  tap.c   | 11 +++++++----
> > > >  6 files changed, 61 insertions(+), 19 deletions(-)
> > > >
> > > > diff --git a/conf.c b/conf.c
> > > > index 84ae12b..353d0a5 100644
> > > > --- a/conf.c
> > > > +++ b/conf.c
> > > > @@ -1049,7 +1049,8 @@ pasta_opts:
> > > >               "  --no-copy-addrs      DEPRECATED:\n"
> > > >               "                       Don't copy all addresses to namespace\n"
> > > >               "  --ns-mac-addr ADDR   Set MAC address on tap interface\n"
> > > > -             "  --no-splice          Disable inbound socket splicing\n");
> > > > +             "  --no-splice          Disable inbound socket splicing\n"
> > > > +             "  --no-tap             Don't create tap device\n");
> > >
> > > I feel like this description can be improved, but I'm not exactly sure
> > > how, yet.
>
> A few possible alternatives:
>
> - "Only enable loopback forwarding"

Thanks, I will go with this and --splice-only.
>
> - "Loopback only from/to namespace"
>
> - call it --splice-only, and use one of the descriptions above
>
> - call it --loopback-only, and use one of the descriptions above
>
> > >
> > > >
> > > >       passt_exit(status);
> > > >  }
> > > > @@ -1451,6 +1452,7 @@ void conf(struct ctx *c, int argc, char **argv)
> > > >               {"no-ndp",      no_argument,            &c->no_ndp,     1 },
> > > >               {"no-ra",       no_argument,            &c->no_ra,      1 },
> > > >               {"no-splice",   no_argument,            &c->no_splice,  1 },
> > > > +             {"no-tap",      no_argument,            &c->no_tap,     1 },
> > > >               {"freebind",    no_argument,            &c->freebind,   1 },
> > > >               {"no-map-gw",   no_argument,            &no_map_gw,     1 },
> > > >               {"ipv4-only",   no_argument,            NULL,           '4' },
> > > > @@ -1947,8 +1949,11 @@ void conf(struct ctx *c, int argc, char **argv)
> > > >               }
> > > >       } while (name != -1);
> > > >
> > > > -     if (c->mode != MODE_PASTA)
> > > > +     if (c->mode != MODE_PASTA) {
> > > >               c->no_splice = 1;
> > > > +             if (c->no_tap)
> > > > +                     die("--no-tap is for pasta mode only");
> > > > +     }
> > > >
> > > >       if (c->mode == MODE_PASTA && !c->pasta_conf_ns) {
> > > >               if (copy_routes_opt)
> > > > @@ -1957,6 +1962,25 @@ void conf(struct ctx *c, int argc, char **argv)
> > > >                       die("--no-copy-addrs needs --config-net");
> > > >       }
> > > >
> > > > +     if (c->mode == MODE_PASTA && c->no_tap) {
> > > > +             if (c->no_splice)
> > > > +                     die("--no-tap is incompatible with --no-splice");
> > > > +             if (*c->ip4.ifname_out || *c->ip6.ifname_out)
> > > > +                     die("--no-tap is incompatible with --outbound-if4/6");
> > > > +             if (*c->pasta_ifn)
> > > > +                     die("--no-tap is incompatible with --ns-ifname");
> > > > +             if (*c->guest_mac)
> > > > +                     die("--no-tap is incompatible with --ns-mac-addr");
> > >
> > > These all make sense.  It might also make sense to exclude the -i
> > > option - setting a template interface also makes no sense in --no-tap
> > > mode.
> >
> > Sure, I can add an if condition with if4 (as if4=if6 in that case).
> > >
> > > > +             if (c->pasta_conf_ns)
> > > > +                     die("--no-tap is incompatible with --config-net");
> > >
> > > I don't think this is right.  We still can and should bring up 'lo' in
> > > the --no-tap case.
> >
> > I see your point, but seems c->pasta_conf_ns is only used for tap as
> > https://passt.top/passt/tree/pasta.c#n328, 'lo' is configured before
> > that line.
>
> Right, and the reason is that there are basic bits of functionality
> (probing pipe sizes if I recall correctly, or anyway probing for some
> kind of capability) that need the loopback interface to be up.
>
> On the other hand, checks we're adding here are kind of fragile because
> we'll add other options in the future and probably forget to check
> which ones are incompatible, so I would try a slightly different
> approach: only check the options that are *obviously* conflicting with
> --no-tap.
>
> That is, the main thing "--config-net" does is to "Configure networking
> in the namespace", which we still do with "--no-tap".

I added that because I thought --config-net is for tap only (as lo is
configured always), and there is no tap for this mode. I can remove
it, it still works without it.
>
> Now, I see that making sure c->pasta_conf_ns is false saves you checks
> elsewhere in the implementation, which is, I think, a good reason to
> have this check here.
>
> But in general we don't need to exclude all the possible options that
> make no sense with --no-tap. We don't really confuse users if we allow
> them (or, at least, some of them).

I see. From a tester's perspective, we used to uncover a few bugs by
combining certain options in different ways. I feel it's more
user-friendly to explicitly state what is unsupported and what will be
ignored, although this is not a strong preference. I can update with
only excluding obvious ones.
>
> > > > +             c->host_lo_to_ns_lo = 1;
> > > > +             c->no_icmp = 1;
> > > > +             c->no_ra = 1;
> > > > +             c->no_dns = 1;
> > > > +             c->no_dns_search = 1;
> > >
> > > The reasoning for the last two items is a bit unclear to me.  IIUC,
> > > no_dns and no_dns_search aren't so much about "support" for DNS itself
> > > but for advertising DNS settings via DHCP.  Since DHCP will be
> > > unsupported, so are these as a consequence.  Is that right?
> >
> > Yeah, I think so. Actually I added c->no_dhcp, c->no_ndp here as well,
> > then removed them as they are set in later changes(conditions about
> > c->ifi4/c->ifi6), though they turn out to be not quite right :'\
>
> Do we care about them, though? That code won't be reachable anyway,
> unless I'm missing something. Or is it to make the output of
> conf_print() nicer? In that case I guess it makes sense to go and
> disable things.

Just tried, without c->no_dns we would hit a warning "Couldn't get any
nameserver address" , without c->no_dns_search, the conf_print will
have lines as below,

    0.0021: DNS search list:
    0.0021:     .
    0.0021: DNS search list:
    0.0022:     .

Apart from that, seems there is no difference with or without c->ra,
c->no_dhcp, c->no_ndp.
I guess we only need  c->no_dns and c->no_dns_search here.
>
> > > > +     }
> > > > +
> > > >       if (!ifi4 && *c->ip4.ifname_out)
> > > >               ifi4 = if_nametoindex(c->ip4.ifname_out);
> > > >
> > > > @@ -1980,9 +2004,9 @@ void conf(struct ctx *c, int argc, char **argv)
> > > >       log_conf_parsed = true;         /* Stop printing everything */
> > > >
> > > >       nl_sock_init(c, false);
> > > > -     if (!v6_only)
> > > > +     if (!v6_only && !c->no_tap)
> > > >               c->ifi4 = conf_ip4(ifi4, &c->ip4);
> > > > -     if (!v4_only)
> > > > +     if (!v4_only && !c->no_tap)
> > > >               c->ifi6 = conf_ip6(ifi6, &c->ip6);
> > > >
> > > >       if (c->ifi4 && c->mtu < IPV4_MIN_MTU) {
> > > > @@ -1998,30 +2022,32 @@ void conf(struct ctx *c, int argc, char **argv)
> > > >           (*c->ip6.ifname_out && !c->ifi6))
> > > >               die("External interface not usable");
> > > >
> > > > -     if (!c->ifi4 && !c->ifi6 && !*c->pasta_ifn) {
> > > > +     if (!c->ifi4 && !c->ifi6 && !*c->pasta_ifn && !c->no_tap) {
> > > >               strncpy(c->pasta_ifn, pasta_default_ifn,
> > > >                       sizeof(c->pasta_ifn) - 1);
> > > >       }
> > > >
> > > >       if (!c->ifi4 && !v6_only) {
> > > > -             info("IPv4: no external interface as template, use local mode");
> > > > -
> > > > -             conf_ip4_local(&c->ip4);
> > > > +             if (!c->no_tap) {
> > > > +                     info("IPv4: no external interface as template, use local mode");
> > > > +                     conf_ip4_local(&c->ip4);
> > > > +             }
> > > >               c->ifi4 = -1;
> > > >       }
> > > >
> > > >       if (!c->ifi6 && !v4_only) {
> > > > -             info("IPv6: no external interface as template, use local mode");
> > > > -
> > > > -             conf_ip6_local(&c->ip6);
> > > > +             if (!c->no_tap) {
> > > > +                     info("IPv6: no external interface as template, use local mode");
> > > > +                     conf_ip6_local(&c->ip6);
> > > > +             }
> > > >               c->ifi6 = -1;
> > > >       }
> > > >
> > > > -     if (c->ifi4 && !no_map_gw &&
> > > > +     if (c->ifi4 > 0 && !no_map_gw &&
> > >
> > > This isn't quite right.  ifi4 == -1 now occurs in two cases: local
> > > mode, and --no-tap mode.  Not setting map_host_loopback makes sense
> > > for --no-tap mode, but it's still needed for local mode.
> >
> > I'm a bit confused by map_host_loopback. I don't quite understand the
> > use scenario. IIUC, either in --no-tap mode or local mode, guest can
> > only communicate with host.
>
> That's not the case for local mode, the guest can communicate with any
> other host. Local mode is just about addresses and routes, and the fact
> that, when pasta started, there was no template interface.

Oh, I thought local mode is only for host without network
connectivity. Thanks for the explanation.
>
> > Then why do we need to set map_host_loopback? What's the benefit?
>
> Example: guest has 169.254.2.1 (default in local mode), and wants to use
> 192.0.2.1 to refer to the host, via loopback interface.
>
> > > >           IN4_IS_ADDR_UNSPECIFIED(&c->ip4.map_host_loopback))
> > > >               c->ip4.map_host_loopback = c->ip4.guest_gw;
> > > >
> > > > -     if (c->ifi6 && !no_map_gw &&
> > > > +     if (c->ifi6 > 0 && !no_map_gw &&
> > >
> > > Same here.
> > >
> > > >           IN6_IS_ADDR_UNSPECIFIED(&c->ip6.map_host_loopback))
> > > >               c->ip6.map_host_loopback = c->ip6.guest_gw;
> > > >
> > > > @@ -2116,10 +2142,10 @@ void conf(struct ctx *c, int argc, char **argv)
> > > >                       conf_ports(c, name, optarg, &c->udp.fwd_out);
> > > >       } while (name != -1);
> > > >
> > > > -     if (!c->ifi4)
> > > > +     if (c->ifi4 <= 0)
> > > >               c->no_dhcp = 1;
> > > >
> > > > -     if (!c->ifi6) {
> > > > +     if (c->ifi6 <= 0) {
> > > >               c->no_ndp = 1;
> > > >               c->no_dhcpv6 = 1;
> > >
> > > And here.  Local mode can still use NDP and DHCP, even though --no-tap
> > > mode can't.  It might be simpler to force no_ndp, no_dhcp etc. along
> > > with no_ra and the rest above.
> >
> > Sure, I will add them.
> > >
> > > >       } else if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addr)) {
> > > > diff --git a/fwd.c b/fwd.c
> > > > index 44a0e10..2f4a89a 100644
> > > > --- a/fwd.c
> > > > +++ b/fwd.c
> > > > @@ -780,6 +780,9 @@ uint8_t fwd_nat_from_host(const struct ctx *c, uint8_t proto,
> > > >               return PIF_SPLICE;
> > > >       }
> > > >
> > > > +     if (c->no_tap)
> > > > +             return PIF_NONE;
> > > > +
> > > >       if (!nat_inbound(c, &ini->eaddr, &tgt->oaddr)) {
> > > >               if (inany_v4(&ini->eaddr)) {
> > > >                       if (IN4_IS_ADDR_UNSPECIFIED(&c->ip4.our_tap_addr))
> > > > diff --git a/passt.1 b/passt.1
> > > > index db0d662..2d643f7 100644
> > > > --- a/passt.1
> > > > +++ b/passt.1
> > > > @@ -755,6 +755,11 @@ Default is to let the tap driver build a pseudorandom hardware address.
> > > >  Disable the bypass path for inbound, local traffic. See the section \fBHandling
> > > >  of local traffic in pasta\fR in the \fBNOTES\fR for more details.
> > > >
> > > > +.TP
> > > > +.BR \-\-no-tap
> > > > +Do not create a tap device in the namespace. In this mode, only local loopback
> > > > +traffic between namespaces is forwarded using splice.
> > >
> > > This probably wants some work, because I'm not sure "tap device" and
> > > "splice" are sufficiently clear in this context.
> >
> > Yeah, I will think about that. Thanks.
> > >
> > >
> > > > +
> > > >  .SH EXAMPLES
> > > >
> > > >  .SS \fBpasta
> > > > diff --git a/passt.h b/passt.h
> > > > index 79d01dd..0c1ec4c 100644
> > > > --- a/passt.h
> > > > +++ b/passt.h
> > > > @@ -200,6 +200,7 @@ struct ip6_ctx {
> > > >   * @no_ndp:          Disable NDP handler altogether
> > > >   * @no_ra:           Disable router advertisements
> > > >   * @no_splice:               Disable socket splicing for inbound traffic
> > > > + * @no_tap:          Do not create tap device
> > > >   * @host_lo_to_ns_lo:        Map host loopback addresses to ns loopback addresses
> > > >   * @freebind:                Allow binding of non-local addresses for forwarding
> > > >   * @low_wmem:                Low probed net.core.wmem_max
> > > > @@ -277,6 +278,7 @@ struct ctx {
> > > >       int no_ndp;
> > > >       int no_ra;
> > > >       int no_splice;
> > > > +     int no_tap;
> > > >       int host_lo_to_ns_lo;
> > > >       int freebind;
> > > >
> > > > diff --git a/pasta.c b/pasta.c
> > > > index 0ddd6b0..3510ec5 100644
> > > > --- a/pasta.c
> > > > +++ b/pasta.c
> > > > @@ -316,6 +316,9 @@ void pasta_ns_conf(struct ctx *c)
> > > >               die("Couldn't bring up loopback interface in namespace: %s",
> > > >                   strerror_(-rc));
> > > >
> > > > +     if (c->no_tap)
> > > > +             return;
> > > > +
> > > >       /* Get or set MAC in target namespace */
> > > >       if (MAC_IS_ZERO(c->guest_mac))
> > > >               nl_link_get_mac(nl_sock_ns, c->pasta_ifi, c->guest_mac);
> > > > diff --git a/tap.c b/tap.c
> > > > index 9d1344b..9b4eedc 100644
> > > > --- a/tap.c
> > > > +++ b/tap.c
> > > > @@ -1491,13 +1491,16 @@ static int tap_ns_tun(void *arg)
> > > >   */
> > > >  static void tap_sock_tun_init(struct ctx *c)
> > > >  {
> > > > -     NS_CALL(tap_ns_tun, c);
> > > > -     if (c->fd_tap == -1)
> > > > -             die("Failed to set up tap device in namespace");
> > > > +     if (!c->no_tap) {
> > > > +             NS_CALL(tap_ns_tun, c);
> > > > +             if (c->fd_tap == -1)
> > > > +                     die("Failed to set up tap device in namespace");
> > > > +     }
> > > >
> > > >       pasta_ns_conf(c);
> > > >
> > > > -     tap_start_connection(c);
> > > > +     if (!c->no_tap)
> > > > +             tap_start_connection(c);
> > > >  }
> > > >
> > > >  /**
> > > > --
> > > > 2.49.0
> > > >
> > >
> > > --
> > > 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
> >
> >
> >
>
> --
> Stefano
>


-- 
Thanks,

Yumei Huang


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

* Re: [PATCH] conf, pasta: Add --no-tap option
  2026-01-10 18:12 ` Stefano Brivio
@ 2026-01-13 11:20   ` Yumei Huang
  0 siblings, 0 replies; 16+ messages in thread
From: Yumei Huang @ 2026-01-13 11:20 UTC (permalink / raw)
  To: Stefano Brivio; +Cc: passt-dev, david

On Sun, Jan 11, 2026 at 2:12 AM Stefano Brivio <sbrivio@redhat.com> wrote:
>
> On Mon, 29 Dec 2025 17:55:58 +0800
> Yumei Huang <yuhuang@redhat.com> wrote:
>
> > This patch introduces a mode where we only forward loopback connections
> > and traffic between two namespaces (via the loopback interface, 'lo'),
> > without a tap device.
> >
> > With this, podman can support forwarding ::1 in custom networks when using
> > rootlesskit for forwarding ports.
> >
> > In --no-tap mode, --host-lo-to-ns-lo, --no-icmp and --no-ra is automatically
> > enabled. Options requiring a tap device (--ns-ifname, --ns-mac-addr,
> > --config-net, --outbound-if4/6) are rejected.
> >
> > Link: https://bugs.passt.top/show_bug.cgi?id=149
> > Signed-off-by: Yumei Huang <yuhuang@redhat.com>
> > ---
> >  conf.c  | 56 +++++++++++++++++++++++++++++++++++++++++---------------
> >  fwd.c   |  3 +++
> >  passt.1 |  5 +++++
> >  passt.h |  2 ++
> >  pasta.c |  3 +++
> >  tap.c   | 11 +++++++----
> >  6 files changed, 61 insertions(+), 19 deletions(-)
> >
> > diff --git a/conf.c b/conf.c
> > index 84ae12b..353d0a5 100644
> > --- a/conf.c
> > +++ b/conf.c
> > @@ -1049,7 +1049,8 @@ pasta_opts:
> >               "  --no-copy-addrs      DEPRECATED:\n"
> >               "                       Don't copy all addresses to namespace\n"
> >               "  --ns-mac-addr ADDR   Set MAC address on tap interface\n"
> > -             "  --no-splice          Disable inbound socket splicing\n");
> > +             "  --no-splice          Disable inbound socket splicing\n"
> > +             "  --no-tap             Don't create tap device\n");
> >
> >       passt_exit(status);
> >  }
> > @@ -1451,6 +1452,7 @@ void conf(struct ctx *c, int argc, char **argv)
> >               {"no-ndp",      no_argument,            &c->no_ndp,     1 },
> >               {"no-ra",       no_argument,            &c->no_ra,      1 },
> >               {"no-splice",   no_argument,            &c->no_splice,  1 },
> > +             {"no-tap",      no_argument,            &c->no_tap,     1 },
> >               {"freebind",    no_argument,            &c->freebind,   1 },
> >               {"no-map-gw",   no_argument,            &no_map_gw,     1 },
> >               {"ipv4-only",   no_argument,            NULL,           '4' },
> > @@ -1947,8 +1949,11 @@ void conf(struct ctx *c, int argc, char **argv)
> >               }
> >       } while (name != -1);
> >
> > -     if (c->mode != MODE_PASTA)
> > +     if (c->mode != MODE_PASTA) {
> >               c->no_splice = 1;
> > +             if (c->no_tap)
> > +                     die("--no-tap is for pasta mode only");
> > +     }
> >
> >       if (c->mode == MODE_PASTA && !c->pasta_conf_ns) {
> >               if (copy_routes_opt)
> > @@ -1957,6 +1962,25 @@ void conf(struct ctx *c, int argc, char **argv)
> >                       die("--no-copy-addrs needs --config-net");
> >       }
> >
> > +     if (c->mode == MODE_PASTA && c->no_tap) {
> > +             if (c->no_splice)
> > +                     die("--no-tap is incompatible with --no-splice");
>
> I'm not sure if you need this for other reasons, but as long as it's
> called --no-tap, it's not really incompatible with --no-splice.

I will update it to --splice-only

>
> Maybe users just want to get a disconnected namespace for whatever
> reason ('pasta' is shorter to type than 'unshare -rUn').
>
> > +             if (*c->ip4.ifname_out || *c->ip6.ifname_out)
> > +                     die("--no-tap is incompatible with --outbound-if4/6");
> > +             if (*c->pasta_ifn)
> > +                     die("--no-tap is incompatible with --ns-ifname");
> > +             if (*c->guest_mac)
> > +                     die("--no-tap is incompatible with --ns-mac-addr");
> > +             if (c->pasta_conf_ns)
> > +                     die("--no-tap is incompatible with --config-net");
>
> I guess all these checks are to save some checks later, which looks like
> a good reason to have them here.
>
> If not, though, I don't think we *really* need to tell the user that
> --ns-ifname will be ignored with --no-tap.
>
> One thing that might confuse users, though, is this:
>
> $ ./pasta --no-tap --mtu 1500 -- ip l
> 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
>     link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
>
> or even this:
>
> $ ./pasta --no-tap -a 192.0.2.1 -- ip a
> 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
>     link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
>     inet 127.0.0.1/8 scope host lo
>        valid_lft forever preferred_lft forever
>     inet6 ::1/128 scope host proto kernel_lo
>        valid_lft forever preferred_lft forever
>
> but I would rather *not* add conditions and checks for those even if
> there's a *slight* potential for confusion, otherwise this becomes
> really long. And it's really not worth it, I think.

Then I guess we only need the c->no_splice check, right?
>
> > +
> > +             c->host_lo_to_ns_lo = 1;
> > +             c->no_icmp = 1;
> > +             c->no_ra = 1;
> > +             c->no_dns = 1;
> > +             c->no_dns_search = 1;
> > +     }
> > +
> >       if (!ifi4 && *c->ip4.ifname_out)
> >               ifi4 = if_nametoindex(c->ip4.ifname_out);
> >
> > @@ -1980,9 +2004,9 @@ void conf(struct ctx *c, int argc, char **argv)
> >       log_conf_parsed = true;         /* Stop printing everything */
> >
> >       nl_sock_init(c, false);
> > -     if (!v6_only)
> > +     if (!v6_only && !c->no_tap)
> >               c->ifi4 = conf_ip4(ifi4, &c->ip4);
> > -     if (!v4_only)
> > +     if (!v4_only && !c->no_tap)
> >               c->ifi6 = conf_ip6(ifi6, &c->ip6);
> >
> >       if (c->ifi4 && c->mtu < IPV4_MIN_MTU) {
> > @@ -1998,30 +2022,32 @@ void conf(struct ctx *c, int argc, char **argv)
> >           (*c->ip6.ifname_out && !c->ifi6))
> >               die("External interface not usable");
> >
> > -     if (!c->ifi4 && !c->ifi6 && !*c->pasta_ifn) {
> > +     if (!c->ifi4 && !c->ifi6 && !*c->pasta_ifn && !c->no_tap) {
>
> You already checked that !*c->pasta_ifn above.

I guess the check above (aka. if (*c->pasta_ifn && c->no_tap)) doesn't
affect this one? If c->pasta_ifn is assigned, we won't come to the
check  !c->no_tap here. Otherwise, we do need to check !c->no_tap.
>
> >               strncpy(c->pasta_ifn, pasta_default_ifn,
> >                       sizeof(c->pasta_ifn) - 1);
> >       }
> >
> >       if (!c->ifi4 && !v6_only) {
> > -             info("IPv4: no external interface as template, use local mode");
> > -
> > -             conf_ip4_local(&c->ip4);
> > +             if (!c->no_tap) {
> > +                     info("IPv4: no external interface as template, use local mode");
> > +                     conf_ip4_local(&c->ip4);
> > +             }
> >               c->ifi4 = -1;
> >       }
> >
> >       if (!c->ifi6 && !v4_only) {
> > -             info("IPv6: no external interface as template, use local mode");
> > -
> > -             conf_ip6_local(&c->ip6);
> > +             if (!c->no_tap) {
> > +                     info("IPv6: no external interface as template, use local mode");
> > +                     conf_ip6_local(&c->ip6);
> > +             }
> >               c->ifi6 = -1;
> >       }
> >
> > -     if (c->ifi4 && !no_map_gw &&
> > +     if (c->ifi4 > 0 && !no_map_gw &&
> >           IN4_IS_ADDR_UNSPECIFIED(&c->ip4.map_host_loopback))
> >               c->ip4.map_host_loopback = c->ip4.guest_gw;
> >
> > -     if (c->ifi6 && !no_map_gw &&
> > +     if (c->ifi6 > 0 && !no_map_gw &&
> >           IN6_IS_ADDR_UNSPECIFIED(&c->ip6.map_host_loopback))
> >               c->ip6.map_host_loopback = c->ip6.guest_gw;
> >
> > @@ -2116,10 +2142,10 @@ void conf(struct ctx *c, int argc, char **argv)
> >                       conf_ports(c, name, optarg, &c->udp.fwd_out);
> >       } while (name != -1);
> >
> > -     if (!c->ifi4)
> > +     if (c->ifi4 <= 0)
> >               c->no_dhcp = 1;
> >
> > -     if (!c->ifi6) {
> > +     if (c->ifi6 <= 0) {
> >               c->no_ndp = 1;
> >               c->no_dhcpv6 = 1;
> >       } else if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addr)) {
> > diff --git a/fwd.c b/fwd.c
> > index 44a0e10..2f4a89a 100644
> > --- a/fwd.c
> > +++ b/fwd.c
> > @@ -780,6 +780,9 @@ uint8_t fwd_nat_from_host(const struct ctx *c, uint8_t proto,
> >               return PIF_SPLICE;
> >       }
> >
> > +     if (c->no_tap)
> > +             return PIF_NONE;
> > +
> >       if (!nat_inbound(c, &ini->eaddr, &tgt->oaddr)) {
> >               if (inany_v4(&ini->eaddr)) {
> >                       if (IN4_IS_ADDR_UNSPECIFIED(&c->ip4.our_tap_addr))
> > diff --git a/passt.1 b/passt.1
> > index db0d662..2d643f7 100644
> > --- a/passt.1
> > +++ b/passt.1
> > @@ -755,6 +755,11 @@ Default is to let the tap driver build a pseudorandom hardware address.
> >  Disable the bypass path for inbound, local traffic. See the section \fBHandling
> >  of local traffic in pasta\fR in the \fBNOTES\fR for more details.
> >
> > +.TP
> > +.BR \-\-no-tap
> > +Do not create a tap device in the namespace. In this mode, only local loopback
> > +traffic between namespaces is forwarded using splice.
>
> "Using splice" isn't really clear, and it's not entirely correct in the
> case of UDP: there's no splice() system call there, even if we call
> some UDP flows "spliced" for analogy with TCP.
>
> Maybe just omit it, say:
>
>   [...] In this mode, \fIpasta\fR only
>   forwards loopback traffic between namespaces.

Do you think we still need "Do not create a tap device in the
namespace" after updating it to --splice-only?

>
> ?
>
> > +
> >  .SH EXAMPLES
> >
> >  .SS \fBpasta
> > diff --git a/passt.h b/passt.h
> > index 79d01dd..0c1ec4c 100644
> > --- a/passt.h
> > +++ b/passt.h
> > @@ -200,6 +200,7 @@ struct ip6_ctx {
> >   * @no_ndp:          Disable NDP handler altogether
> >   * @no_ra:           Disable router advertisements
> >   * @no_splice:               Disable socket splicing for inbound traffic
> > + * @no_tap:          Do not create tap device
> >   * @host_lo_to_ns_lo:        Map host loopback addresses to ns loopback addresses
> >   * @freebind:                Allow binding of non-local addresses for forwarding
> >   * @low_wmem:                Low probed net.core.wmem_max
> > @@ -277,6 +278,7 @@ struct ctx {
> >       int no_ndp;
> >       int no_ra;
> >       int no_splice;
> > +     int no_tap;
> >       int host_lo_to_ns_lo;
> >       int freebind;
> >
> > diff --git a/pasta.c b/pasta.c
> > index 0ddd6b0..3510ec5 100644
> > --- a/pasta.c
> > +++ b/pasta.c
> > @@ -316,6 +316,9 @@ void pasta_ns_conf(struct ctx *c)
> >               die("Couldn't bring up loopback interface in namespace: %s",
> >                   strerror_(-rc));
> >
> > +     if (c->no_tap)
> > +             return;
> > +
> >       /* Get or set MAC in target namespace */
> >       if (MAC_IS_ZERO(c->guest_mac))
> >               nl_link_get_mac(nl_sock_ns, c->pasta_ifi, c->guest_mac);
> > diff --git a/tap.c b/tap.c
> > index 9d1344b..9b4eedc 100644
> > --- a/tap.c
> > +++ b/tap.c
> > @@ -1491,13 +1491,16 @@ static int tap_ns_tun(void *arg)
> >   */
> >  static void tap_sock_tun_init(struct ctx *c)
> >  {
> > -     NS_CALL(tap_ns_tun, c);
> > -     if (c->fd_tap == -1)
> > -             die("Failed to set up tap device in namespace");
> > +     if (!c->no_tap) {
> > +             NS_CALL(tap_ns_tun, c);
> > +             if (c->fd_tap == -1)
> > +                     die("Failed to set up tap device in namespace");
> > +     }
> >
> >       pasta_ns_conf(c);
> >
> > -     tap_start_connection(c);
> > +     if (!c->no_tap)
> > +             tap_start_connection(c);
> >  }
> >
> >  /**
>
> Other than that, minus pending comments, it all looks good to me.

Thank for the review and comments!
>
> --
> Stefano
>


-- 
Thanks,

Yumei Huang


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

end of thread, other threads:[~2026-01-13 11:21 UTC | newest]

Thread overview: 16+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-12-29  9:55 [PATCH] conf, pasta: Add --no-tap option Yumei Huang
2025-12-31 15:07 ` Stefano Brivio
2026-01-05  4:18 ` David Gibson
2026-01-05  8:53   ` Yumei Huang
2026-01-10 18:12     ` Stefano Brivio
2026-01-12  4:26       ` David Gibson
2026-01-13  0:12         ` Stefano Brivio
2026-01-13  2:39           ` David Gibson
2026-01-13  9:57       ` Yumei Huang
2026-01-05 13:48 ` Paul Holzinger
2026-01-05 21:10   ` Stefano Brivio
2026-01-07 15:20     ` Paul Holzinger
2026-01-10 18:12       ` Stefano Brivio
2026-01-12  8:20         ` Yumei Huang
2026-01-10 18:12 ` Stefano Brivio
2026-01-13 11:20   ` Yumei Huang

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