* [PATCH] conf, pasta: Add --no-tap option
@ 2025-12-29 9:55 Yumei Huang
2025-12-31 15:07 ` Stefano Brivio
` (2 more replies)
0 siblings, 3 replies; 6+ 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] 6+ 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
2 siblings, 0 replies; 6+ 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] 6+ 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
2 siblings, 1 reply; 6+ 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] 6+ 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
0 siblings, 0 replies; 6+ 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] 6+ 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
2 siblings, 1 reply; 6+ 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] 6+ 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
0 siblings, 0 replies; 6+ 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] 6+ messages in thread
end of thread, other threads:[~2026-01-05 21:11 UTC | newest]
Thread overview: 6+ 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-05 13:48 ` Paul Holzinger
2026-01-05 21:10 ` Stefano Brivio
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).