Skip to content

Commit fdf91cd

Browse files
committed
Allow creation of a rootless netns backed by Pasta
This makes the code for setting up rootless network namespaces dependent on what the default rootless network provider is, and allows Pasta to be used for traffic forwarding on the rootless netns. This also switches the default rootless network provider to Pasta Signed-off-by: Matt Heon <mheon@redhat.com>
1 parent c4de0a3 commit fdf91cd

File tree

6 files changed

+78
-21
lines changed

6 files changed

+78
-21
lines changed

docs/containers.conf.5.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -442,10 +442,10 @@ default_subnet_pools = [
442442
]
443443
```
444444

445-
**default_rootless_network_cmd**="slirp4netns"
445+
**default_rootless_network_cmd**="pasta"
446446

447447
Configure which rootless network program to use by default. Valid options are
448-
`slirp4netns` (default) and `pasta`.
448+
`slirp4netns` and `pasta` (default).
449449

450450
**network_config_dir**="/etc/cni/net.d/"
451451

@@ -770,7 +770,7 @@ Number of times to retry pulling/pushing images in case of failure.
770770

771771
**retry_delay** = ""
772772

773-
Delay between retries in case pulling/pushing image fails. If set, container engines will retry at the set interval, otherwise they delay 2 seconds and then exponentially back off.
773+
Delay between retries in case pulling/pushing image fails. If set, container engines will retry at the set interval, otherwise they delay 2 seconds and then exponentially back off.
774774

775775
**runtime**=""
776776

libnetwork/internal/rootlessnetns/netns_linux.go

Lines changed: 68 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ import (
88
"path/filepath"
99
"strconv"
1010
"strings"
11-
"syscall"
1211

1312
"github.com/containernetworking/plugins/pkg/ns"
13+
"github.com/containers/common/libnetwork/pasta"
1414
"github.com/containers/common/libnetwork/resolvconf"
1515
"github.com/containers/common/libnetwork/slirp4netns"
1616
"github.com/containers/common/pkg/config"
@@ -31,8 +31,8 @@ const (
3131
// refCountFile file name for the ref count file
3232
refCountFile = "ref-count"
3333

34-
// rootlessNetNsSilrp4netnsPidFile is the name of the rootless netns slirp4netns pid file
35-
rootlessNetNsSilrp4netnsPidFile = "rootless-netns-slirp4netns.pid"
34+
// rootlessNetNsConnPidFile is the name of the rootless netns slirp4netns/pasta pid file
35+
rootlessNetNsConnPidFile = "rootless-netns-conn.pid"
3636

3737
// persistentCNIDir is the directory where the CNI files are stored
3838
persistentCNIDir = "/var/lib/cni"
@@ -113,7 +113,14 @@ func (n *Netns) getOrCreateNetns() (ns.NetNS, bool, error) {
113113
if err != nil {
114114
return nil, false, wrapError("create netns", err)
115115
}
116-
err = n.setupSlirp4netns(nsPath)
116+
switch strings.ToLower(n.config.Network.DefaultRootlessNetworkCmd) {
117+
case "", slirp4netns.BinaryName:
118+
err = n.setupSlirp4netns(nsPath)
119+
case pasta.BinaryName:
120+
err = n.setupPasta(nsPath)
121+
default:
122+
err = fmt.Errorf("invalid rootless network command %q", n.config.Network.DefaultRootlessNetworkCmd)
123+
}
117124
return netns, true, err
118125
}
119126

@@ -133,8 +140,8 @@ func (n *Netns) cleanup() error {
133140
if err := netns.UnmountNS(nsPath); err != nil {
134141
multiErr = multierror.Append(multiErr, err)
135142
}
136-
if err := n.cleanupSlirp4netns(); err != nil {
137-
multiErr = multierror.Append(multiErr, wrapError("kill slirp4netns", err))
143+
if err := n.cleanupRootlessNetns(); err != nil {
144+
multiErr = multierror.Append(multiErr, wrapError("kill network process", err))
138145
}
139146
if err := os.RemoveAll(n.dir); err != nil {
140147
multiErr = multierror.Append(multiErr, wrapError("remove rootless netns dir", err))
@@ -143,6 +150,53 @@ func (n *Netns) cleanup() error {
143150
return multiErr.ErrorOrNil()
144151
}
145152

153+
func (n *Netns) setupPasta(nsPath string) error {
154+
pidPath := n.getPath(rootlessNetNsConnPidFile)
155+
156+
pastaOpts := pasta.SetupOptions{
157+
Config: n.config,
158+
Netns: nsPath,
159+
ExtraOptions: []string{"--pid", pidPath},
160+
}
161+
if err := pasta.Setup(&pastaOpts); err != nil {
162+
return fmt.Errorf("setting up Pasta: %w", err)
163+
}
164+
165+
if systemd.RunsOnSystemd() {
166+
// Treat these as fatal - if pasta failed to write a PID file something is probably wrong.
167+
pidfile, err := os.ReadFile(pidPath)
168+
if err != nil {
169+
return fmt.Errorf("unable to open pasta PID file: %w", err)
170+
}
171+
pid, err := strconv.Atoi(strings.TrimSpace(string(pidfile)))
172+
if err != nil {
173+
return fmt.Errorf("unable to decode pasta PID: %w", err)
174+
}
175+
176+
if err := systemd.MoveRootlessNetnsSlirpProcessToUserSlice(pid); err != nil {
177+
// only log this, it is not fatal but can lead to issues when running podman inside systemd units
178+
logrus.Errorf("failed to move the rootless netns pasta process to the systemd user.slice: %v", err)
179+
}
180+
}
181+
182+
if err := resolvconf.New(&resolvconf.Params{
183+
Path: n.getPath(resolvConfName),
184+
// fake the netns since we want to filter localhost
185+
Namespaces: []specs.LinuxNamespace{
186+
{Type: specs.NetworkNamespace},
187+
},
188+
// TODO: Need a way to determine if there is a valid v6 address on any
189+
// external interface of the system.
190+
IPv6Enabled: false,
191+
KeepHostServers: true,
192+
Nameservers: []string{},
193+
}); err != nil {
194+
return wrapError("create resolv.conf", err)
195+
}
196+
197+
return nil
198+
}
199+
146200
func (n *Netns) setupSlirp4netns(nsPath string) error {
147201
res, err := slirp4netns.Setup(&slirp4netns.SetupOptions{
148202
Config: n.config,
@@ -155,7 +209,7 @@ func (n *Netns) setupSlirp4netns(nsPath string) error {
155209
// create pid file for the slirp4netns process
156210
// this is need to kill the process in the cleanup
157211
pid := strconv.Itoa(res.Pid)
158-
err = os.WriteFile(n.getPath(rootlessNetNsSilrp4netnsPidFile), []byte(pid), 0o600)
212+
err = os.WriteFile(n.getPath(rootlessNetNsConnPidFile), []byte(pid), 0o600)
159213
if err != nil {
160214
return wrapError("write slirp4netns pid file", err)
161215
}
@@ -190,15 +244,18 @@ func (n *Netns) setupSlirp4netns(nsPath string) error {
190244
return nil
191245
}
192246

193-
func (n *Netns) cleanupSlirp4netns() error {
194-
pidFile := n.getPath(rootlessNetNsSilrp4netnsPidFile)
247+
func (n *Netns) cleanupRootlessNetns() error {
248+
pidFile := n.getPath(rootlessNetNsConnPidFile)
195249
b, err := os.ReadFile(pidFile)
196250
if err == nil {
197251
var i int
198-
i, err = strconv.Atoi(string(b))
252+
i, err = strconv.Atoi(strings.TrimSpace(string(b)))
199253
if err == nil {
200254
// kill the slirp process so we do not leak it
201-
err = syscall.Kill(i, syscall.SIGTERM)
255+
err = unix.Kill(i, unix.SIGTERM)
256+
if err == unix.ESRCH {
257+
err = nil
258+
}
202259
}
203260
}
204261
return err

pkg/config/config_local_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,12 +151,12 @@ var _ = Describe("Config Local", func() {
151151
// Given
152152
config, err := NewConfig("")
153153
gomega.Expect(err).To(gomega.BeNil())
154-
gomega.Expect(config.Network.DefaultRootlessNetworkCmd).To(gomega.Equal("slirp4netns"))
154+
gomega.Expect(config.Network.DefaultRootlessNetworkCmd).To(gomega.Equal("pasta"))
155155
// When
156156
config2, err := NewConfig("testdata/containers_default.conf")
157157
// Then
158158
gomega.Expect(err).To(gomega.BeNil())
159-
gomega.Expect(config2.Network.DefaultRootlessNetworkCmd).To(gomega.Equal("pasta"))
159+
gomega.Expect(config2.Network.DefaultRootlessNetworkCmd).To(gomega.Equal("slirp4netns"))
160160
})
161161

162162
It("should fail on invalid device mode", func() {

pkg/config/containers.conf

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -384,9 +384,9 @@ default_sysctls = [
384384

385385

386386
# Configure which rootless network program to use by default. Valid options are
387-
# `slirp4netns` (default) and `pasta`.
387+
# `slirp4netns` and `pasta` (default).
388388
#
389-
#default_rootless_network_cmd = "slirp4netns"
389+
#default_rootless_network_cmd = "pasta"
390390

391391
# Path to the directory where network configuration files are located.
392392
# For the CNI backend the default is "/etc/cni/net.d" as root
@@ -662,7 +662,7 @@ default_sysctls = [
662662

663663
# Delay between retries in case pulling/pushing image fails.
664664
# If set, container engines will retry at the set interval,
665-
# otherwise they delay 2 seconds and then exponentially back off.
665+
# otherwise they delay 2 seconds and then exponentially back off.
666666
#
667667
#retry_delay = "2s"
668668

pkg/config/default.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ func defaultConfig() (*Config, error) {
257257
DefaultNetwork: "podman",
258258
DefaultSubnet: DefaultSubnet,
259259
DefaultSubnetPools: DefaultSubnetPools,
260-
DefaultRootlessNetworkCmd: "slirp4netns",
260+
DefaultRootlessNetworkCmd: "pasta",
261261
DNSBindPort: 0,
262262
CNIPluginDirs: attributedstring.NewSlice(DefaultCNIPluginDirs),
263263
NetavarkPluginDirs: attributedstring.NewSlice(DefaultNetavarkPluginDirs),

pkg/config/testdata/containers_default.conf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ network_config_dir = "/etc/cni/net.d/"
122122

123123
default_subnet_pools = [{"base" = "10.89.0.0/16", "size" = 24}, {"base" = "10.90.0.0/15", "size" = 24}]
124124

125-
default_rootless_network_cmd = "pasta"
125+
default_rootless_network_cmd = "slirp4netns"
126126

127127
# firewall driver to be used by default
128128
firewall_driver = "none"

0 commit comments

Comments
 (0)