Skip to content

Commit 2d09c79

Browse files
authored
Merge pull request #28478 from Honny1/pesto-support
Rootless bridge: preserve source IPs via pesto/pasta
2 parents 23cc1a3 + baf8fa6 commit 2d09c79

29 files changed

Lines changed: 762 additions & 207 deletions

docs/source/markdown/options/network.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ Valid _mode_ values are:
3535

3636
Any other options will be passed through to netavark without validation. This can be useful to pass arguments to netavark plugins.
3737

38+
For rootless bridge networks, port forwarding uses `rootlessport` by default. Setting `rootless_port_forwarder="pasta"` in the `[network]` section of **[containers.conf(5)](https://github.com/containers/container-libs/blob/main/common/docs/containers.conf.5.md)** switches to pasta's kernel-level forwarding (via `pesto`), which preserves the original client source IP address inside the container. This option is experimental and its behavior is subject to change.
39+
3840
For example, to set a static IPv4 address and a static mac address, use `--network bridge:ip=10.88.0.10,mac=44:33:22:11:00:99`.
3941

4042
- _\<network name or ID\>_**[:OPTIONS,...]**: Connect to a user-defined network; this is the network name or ID from a network created by **[podman network create](podman-network-create.1.md)**. It is possible to specify the same options described under the bridge mode above. Use the **--network** option multiple times to specify additional networks. \

docs/source/markdown/options/publish.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,11 @@ Use **podman port** to see the actual mapping: `podman port $CONTAINER $CONTAINE
2828

2929
Port publishing is only supported for containers utilizing their own network namespace
3030
through `bridge` networks, or the `pasta` network mode.
31+
32+
For rootless bridge networks, port forwarding uses `rootlessport` by default, which
33+
is a userspace proxy that does not preserve client source IPs. Setting
34+
`rootless_port_forwarder="pasta"` in the `[network]` section of
35+
**[containers.conf(5)](https://github.com/containers/container-libs/blob/main/common/docs/containers.conf.5.md)**
36+
switches to pasta's kernel-level forwarding via `pesto`, preserving the original
37+
client IP address inside the container. This option is experimental and its
38+
behavior is subject to change.

docs/source/markdown/podman-info.1.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ host:
9696
spec: 1.0.0
9797
+SYSTEMD +SELINUX +APPARMOR +CAP +SECCOMP +EBPF +CRIU +YAJL
9898
os: linux
99+
rootlessPortForwarder: rootlessport
99100
pasta:
100101
executable: /usr/bin/passt
101102
package: passt-0^20221116.gace074c-1.fc34.x86_64
@@ -237,6 +238,7 @@ $ podman info --format json
237238
"version": "crun version 1.0\ncommit: 139dc6971e2f1d931af520188763e984d6cdfbf8\nspec: 1.0.0\n+SYSTEMD +SELINUX +APPARMOR +CAP +SECCOMP +EBPF +CRIU +YAJL"
238239
},
239240
"os": "linux",
241+
"rootlessPortForwarder": "rootlessport",
240242
"remoteSocket": {
241243
"path": "/run/user/3267/podman/podman.sock"
242244
},

docs/tutorials/basic_networking.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,14 @@ The user experience of rootless netavark is very akin to a rootful netavark, exc
8080
there is no default network configuration provided. You simply need to create a
8181
network, and the one will be created as a bridge network.
8282

83+
By default, rootless bridge networks use `rootlessport` for port forwarding, which
84+
is a userspace proxy that does not preserve client source IPs. To preserve source
85+
IPs, set `rootless_port_forwarder="pasta"` in the `[network]` section of
86+
`containers.conf`. This uses `pesto` to configure pasta's kernel-level forwarding,
87+
so containers see the real client IP address instead of the bridge gateway.
88+
This option is experimental and its behavior is subject to change.
89+
It requires a version of passt (`>= passt-0^20260507.g1afd4ed`) that ships the `pesto` binary.
90+
8391
```
8492
$ podman network create
8593
```

go.mod

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,9 @@ require (
6565
github.com/vishvananda/netlink v1.3.1
6666
go.etcd.io/bbolt v1.4.3
6767
go.podman.io/buildah v1.42.1-0.20260501153811-377cf64e213b
68-
go.podman.io/common v0.67.2-0.20260515151312-e2c14667a598
69-
go.podman.io/image/v5 v5.39.3-0.20260515151312-e2c14667a598
70-
go.podman.io/storage v1.62.1-0.20260515151312-e2c14667a598
68+
go.podman.io/common v0.67.2-0.20260519201413-7e9ee2072844
69+
go.podman.io/image/v5 v5.39.3-0.20260519201413-7e9ee2072844
70+
go.podman.io/storage v1.63.1-0.20260519201413-7e9ee2072844
7171
golang.org/x/crypto v0.51.0
7272
golang.org/x/net v0.54.0
7373
golang.org/x/sync v0.20.0

go.sum

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,8 @@ github.com/disiqueira/gotree/v3 v3.0.2 h1:ik5iuLQQoufZBNPY518dXhiO5056hyNBIK9lWh
9898
github.com/disiqueira/gotree/v3 v3.0.2/go.mod h1:ZuyjE4+mUQZlbpkI24AmruZKhg3VHEgPLDY8Qk+uUu8=
9999
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
100100
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
101-
github.com/docker/cli v29.5.0+incompatible h1:FPUvKJoKpeP4Njz8NrQdeUN8o247P7ndTiILtaP5/l4=
102-
github.com/docker/cli v29.5.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
101+
github.com/docker/cli v29.5.1+incompatible h1:NiufLAJoRcPauFoBNYthfuM4REFwM8H2h9xnLABNHGs=
102+
github.com/docker/cli v29.5.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
103103
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
104104
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
105105
github.com/docker/docker-credential-helpers v0.9.7 h1:jaPIxEIDz5bQeghNAdzz0ETwMMnM4vzjZlxz3pWP4JA=
@@ -431,12 +431,12 @@ go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09
431431
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
432432
go.podman.io/buildah v1.42.1-0.20260501153811-377cf64e213b h1:i8ntFzITajbJA3ojnA0ZdpbC+I+ccweZvZaGIhQb4i8=
433433
go.podman.io/buildah v1.42.1-0.20260501153811-377cf64e213b/go.mod h1:hPvgsjBU09C+15fKoIZJvKvNaxR+c0QvMg/n4NgBS7A=
434-
go.podman.io/common v0.67.2-0.20260515151312-e2c14667a598 h1:siUEt+Q/qN+1TI4/dem6kaI4x4hnanj0CPKIYLAA5x8=
435-
go.podman.io/common v0.67.2-0.20260515151312-e2c14667a598/go.mod h1:Zw3/xJw/PY5FERQykTNIWFAJzL9ism2LaWFrmvFlBjo=
436-
go.podman.io/image/v5 v5.39.3-0.20260515151312-e2c14667a598 h1:nHqODDlrwo33mxXf5qH7b7rjcc2ZVmR8xSTdvAApOrg=
437-
go.podman.io/image/v5 v5.39.3-0.20260515151312-e2c14667a598/go.mod h1:PehFFa+D1wGt+GL5PCm0BupwarWcFFnOpZyQkF0eGdw=
438-
go.podman.io/storage v1.62.1-0.20260515151312-e2c14667a598 h1:HtvnEpmKdzedYRDIBFIrVJummv3WnflKYEhK+3twZs0=
439-
go.podman.io/storage v1.62.1-0.20260515151312-e2c14667a598/go.mod h1:z4Z9K+7GhKjWL/Y1O17+4f8a1KGijVeC9hr3tymhSOs=
434+
go.podman.io/common v0.67.2-0.20260519201413-7e9ee2072844 h1:Un2Wz6Ni/QmkVC528gXOVvVKAM/vOi/c7kLK8gWvmvI=
435+
go.podman.io/common v0.67.2-0.20260519201413-7e9ee2072844/go.mod h1:bhfqGXJ/cMC6CYubcmENInEQlemgClO0ea9+e1Mz/8k=
436+
go.podman.io/image/v5 v5.39.3-0.20260519201413-7e9ee2072844 h1:wbh4wP38Ba13JjvHtNS2RZrI+L2/n2pIhQY36dUaebw=
437+
go.podman.io/image/v5 v5.39.3-0.20260519201413-7e9ee2072844/go.mod h1:Jg91tpyyYgFAQLG1YjWbaZs4NHRzLWpDgCZyiOvDZyY=
438+
go.podman.io/storage v1.63.1-0.20260519201413-7e9ee2072844 h1:s3bBIQVRqZ4uusgEMaVmUfXXphj4tEsg7BwiQB4COkQ=
439+
go.podman.io/storage v1.63.1-0.20260519201413-7e9ee2072844/go.mod h1:z4Z9K+7GhKjWL/Y1O17+4f8a1KGijVeC9hr3tymhSOs=
440440
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
441441
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
442442
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=

libpod/define/info.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,11 @@ type HostInfo struct {
5353
// RemoteSocket returns the UNIX domain socket the Podman service is listening on
5454
RemoteSocket *RemoteSocket `json:"remoteSocket,omitempty"`
5555
// RootlessNetworkCmd returns the default rootless network command (pasta)
56-
RootlessNetworkCmd string `json:"rootlessNetworkCmd"`
57-
RuntimeInfo map[string]any `json:"runtimeInfo,omitempty"`
56+
RootlessNetworkCmd string `json:"rootlessNetworkCmd"`
57+
// RootlessPortForwarder returns the port forwarding mechanism for rootless
58+
// bridge networks: "rootlessport" (default) or "pasta" (experimental)
59+
RootlessPortForwarder string `json:"rootlessPortForwarder"`
60+
RuntimeInfo map[string]any `json:"runtimeInfo,omitempty"`
5861
// ServiceIsRemote is true when the podman/libpod service is remote to the client
5962
ServiceIsRemote bool `json:"serviceIsRemote"`
6063
Security SecurityInfo `json:"security"`

libpod/info.go

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -108,26 +108,27 @@ func (r *Runtime) hostInfo() (*define.HostInfo, error) {
108108
}
109109

110110
info := define.HostInfo{
111-
Arch: runtime.GOARCH,
112-
BuildahVersion: buildah.Version,
113-
DatabaseBackend: r.state.Name(),
114-
Linkmode: linkmode.Linkmode(),
115-
CPUs: runtime.NumCPU(),
116-
CPUUtilization: cpuUtil,
117-
Distribution: hostDistributionInfo,
118-
LogDriver: r.config.Containers.LogDriver,
119-
EventLogger: r.eventer.String(),
120-
FreeLocks: locksFree,
121-
Hostname: host,
122-
Kernel: kv,
123-
MemFree: mi.MemFree,
124-
MemTotal: mi.MemTotal,
125-
NetworkBackend: r.config.Network.NetworkBackend,
126-
NetworkBackendInfo: r.network.NetworkInfo(),
127-
OS: runtime.GOOS,
128-
RootlessNetworkCmd: r.config.Network.DefaultRootlessNetworkCmd,
129-
SwapFree: mi.SwapFree,
130-
SwapTotal: mi.SwapTotal,
111+
Arch: runtime.GOARCH,
112+
BuildahVersion: buildah.Version,
113+
DatabaseBackend: r.state.Name(),
114+
Linkmode: linkmode.Linkmode(),
115+
CPUs: runtime.NumCPU(),
116+
CPUUtilization: cpuUtil,
117+
Distribution: hostDistributionInfo,
118+
LogDriver: r.config.Containers.LogDriver,
119+
EventLogger: r.eventer.String(),
120+
FreeLocks: locksFree,
121+
Hostname: host,
122+
Kernel: kv,
123+
MemFree: mi.MemFree,
124+
MemTotal: mi.MemTotal,
125+
NetworkBackend: r.config.Network.NetworkBackend,
126+
NetworkBackendInfo: r.network.NetworkInfo(),
127+
OS: runtime.GOOS,
128+
RootlessNetworkCmd: r.config.Network.DefaultRootlessNetworkCmd,
129+
RootlessPortForwarder: r.config.Network.RootlessPortForwarder,
130+
SwapFree: mi.SwapFree,
131+
SwapTotal: mi.SwapTotal,
131132
}
132133
platform := parse.DefaultPlatform()
133134
pArr := strings.Split(platform, "/")

libpod/networking_common.go

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,27 @@ func (r *Runtime) teardownNetwork(ctr *Container) error {
110110
return err
111111
}
112112

113-
if !ctr.config.NetMode.IsPasta() && len(networks) > 0 {
114-
netOpts := ctr.getNetworkOptions(networks)
115-
return r.teardownNetworkBackend(ctr.state.NetNS, netOpts)
113+
if len(networks) == 0 {
114+
return nil
116115
}
117-
return nil
116+
117+
// --net=pasta: per-container pasta cleans up when it exits, nothing to tear down.
118+
if ctr.config.NetMode.IsPasta() {
119+
return nil
120+
}
121+
122+
// Pasta forwarding mode: remove port forwarding rules (via pesto) before
123+
// netavark tears down bridge/nftables so pasta stops forwarding first.
124+
// Rootlessport mode: no explicit teardown needed (exits with conmon).
125+
if rootless.IsRootless() && ctr.config.NetMode.IsBridge() && len(ctr.config.PortMappings) > 0 &&
126+
r.config.Network.RootlessPortForwarder == config.RootlessPortForwarderPasta {
127+
if err := r.teardownRootlessPortMappingViaPesto(ctr); err != nil {
128+
logrus.Warnf("pesto port cleanup failed for container %s: %v", ctr.ID(), err)
129+
}
130+
}
131+
132+
netOpts := ctr.getNetworkOptions(networks)
133+
return r.teardownNetworkBackend(ctr.state.NetNS, netOpts)
118134
}
119135

120136
// isBridgeNetMode checks if the given network mode is bridge.
@@ -439,7 +455,7 @@ func (c *Container) NetworkDisconnect(nameOrID, netName string, _ bool) error {
439455

440456
// Reload ports when there are still connected networks, maybe we removed the network interface with the child ip.
441457
// Reloading without connected networks does not make sense, so we can skip this step.
442-
if rootless.IsRootless() && len(networkStatus) > 0 {
458+
if rootless.IsRootless() && c.runtime.config.Network.RootlessPortForwarder == config.RootlessPortForwarderRootlessport && len(networkStatus) > 0 {
443459
if err := c.reloadRootlessRLKPortMapping(); err != nil {
444460
return err
445461
}
@@ -595,7 +611,7 @@ func (c *Container) NetworkConnect(nameOrID, netName string, netOpts types.PerNe
595611

596612
// The first network needs a port reload to set the correct child ip for the rootlessport process.
597613
// Adding a second network does not require a port reload because the child ip is still valid.
598-
if rootless.IsRootless() && len(networks) == 0 {
614+
if rootless.IsRootless() && c.runtime.config.Network.RootlessPortForwarder == config.RootlessPortForwarderRootlessport && len(networks) == 0 {
599615
if err := c.reloadRootlessRLKPortMapping(); err != nil {
600616
return err
601617
}

libpod/networking_freebsd.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,3 +224,7 @@ func (c *Container) inspectJoinedNetworkNS(_ string) (q types.StatusBlock, retEr
224224
func (c *Container) reloadRootlessRLKPortMapping() error {
225225
return errors.New("unsupported (*Container).reloadRootlessRLKPortMapping")
226226
}
227+
228+
func (r *Runtime) teardownRootlessPortMappingViaPesto(_ *Container) error {
229+
return errors.New("unsupported teardownRootlessPortMappingViaPesto on FreeBSD")
230+
}

0 commit comments

Comments
 (0)