On Wed, Jan 14, 2026 at 6:00 PM Stefano Brivio
On Wed, 14 Jan 2026 15:28:31 +0800 Yumei Huang
wrote: On Wed, Jan 14, 2026 at 2:31 PM Yumei Huang
wrote: On Wed, Jan 14, 2026 at 7:34 AM Stefano Brivio
wrote: On Tue, 13 Jan 2026 19:20:47 +0800 Yumei Huang
wrote: On Sun, Jan 11, 2026 at 2:12 AM Stefano Brivio
wrote: On Mon, 29 Dec 2025 17:55:58 +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
> --- > 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:
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:
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?
...maybe? About *needing*, yes, I guess so, but if other checks save more checks later, I would keep 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; > + } > + > 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.
Right, but you don't care about resetting c->pasta_ifn to the default value if !c->no_tap, because in that case you know that c->pasta_ifn wasn't set, so you can happily override it.
I just realized that you probably meant when c->no_tap is set. Actually it would affect conf_print, info("Namespace interface: %s", c->pasta_ifn). But I will add a condition about c->splice_only before this line, so yes, it doesn't matter whether reset it or not. I will remove the check in v2.
I'm not sure I fully understand it. If !c->no_tap, the condition is the same as before without this patch, which is to not reset it if it's specified in cmd line. We won't know if c->pasta_ifn is set until this check, do we?
Let's assume !c->ifi4 && !c->ifi6. Then we have 2 variables and 2^2 possible cases:
1. !*c->pasta_ifn && !c->no_tap: we need to override c->pasta_ifn
2. !*c->pasta_ifn && c->no_tap: we don't need to override c->pasta_ifn, *but it's harmless if we do*
This will lead conf_print to print "Namespace interface: tap0" which is not correct. But I plan to add a check with c->no_tap in conf_print, so it won't be a problem.
3. *c->pasta_ifn && !c->no_tap: we must not override c->pasta_ifn
4. *c->pasta_ifn && c->no_tap: we must not override c->pasta_ifn
Now, if we make 1. and 2. the same and decide to override c->pasta_ifn also in case 2. (when it's not necessary, but harmless), 1. and 2. as well as 3. and 4. are pairwise the same, so you don't strictly need to add a condition on c->no_tap, I think.
On the other hand... if it's obvious just to me, maybe it's actually simpler to keep the check. :) I realise that my observation is not as clear as I initially thought.
-- Stefano
-- Thanks, Yumei Huang