github.com/rootless-containers/rootlesskit/v2@v2.3.4/pkg/network/pasta/pasta.go (about) 1 package pasta 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "io" 8 "net" 9 "os/exec" 10 "strconv" 11 "sync" 12 13 "github.com/sirupsen/logrus" 14 15 "github.com/rootless-containers/rootlesskit/v2/pkg/api" 16 "github.com/rootless-containers/rootlesskit/v2/pkg/common" 17 "github.com/rootless-containers/rootlesskit/v2/pkg/messages" 18 "github.com/rootless-containers/rootlesskit/v2/pkg/network" 19 "github.com/rootless-containers/rootlesskit/v2/pkg/network/iputils" 20 ) 21 22 type Features struct { 23 // Has `--host-lo-to-ns-lo` (introduced in passt 2024_10_30.ee7d0b6) 24 // https://passt.top/passt/commit/?id=b4dace8f462b346ae2135af1f8d681a99a849a5f 25 HasHostLoToNsLo bool 26 } 27 28 func DetectFeatures(binary string) (*Features, error) { 29 if binary == "" { 30 return nil, errors.New("got empty pasta binary") 31 } 32 realBinary, err := exec.LookPath(binary) 33 if err != nil { 34 return nil, fmt.Errorf("pasta binary %q is not installed: %w", binary, err) 35 } 36 cmd := exec.Command(realBinary, "--version") 37 b, err := cmd.CombinedOutput() 38 if err != nil { 39 return nil, fmt.Errorf(`command "%s --version" failed, make sure pasta is installed: %q: %w`, 40 realBinary, string(b), err) 41 } 42 f := Features{ 43 HasHostLoToNsLo: false, 44 } 45 cmd = exec.Command(realBinary, "--host-lo-to-ns-lo", "--version") 46 if cmd.Run() == nil { 47 f.HasHostLoToNsLo = true 48 } 49 return &f, nil 50 } 51 52 // NewParentDriver instantiates new parent driver. 53 func NewParentDriver(logWriter io.Writer, binary string, mtu int, ipnet *net.IPNet, ifname string, 54 disableHostLoopback, enableIPv6, implicitPortForwarding bool) (network.ParentDriver, error) { 55 if binary == "" { 56 return nil, errors.New("got empty slirp4netns binary") 57 } 58 if mtu < 0 { 59 return nil, errors.New("got negative mtu") 60 } 61 if mtu == 0 { 62 mtu = 65520 63 } 64 65 if ipnet == nil { 66 var err error 67 _, ipnet, err = net.ParseCIDR("10.0.2.0/24") 68 if err != nil { 69 return nil, err 70 } 71 } 72 73 if ifname == "" { 74 ifname = "tap0" 75 } 76 77 feat, err := DetectFeatures(binary) 78 if err != nil { 79 return nil, err 80 } 81 82 return &parentDriver{ 83 logWriter: logWriter, 84 binary: binary, 85 mtu: mtu, 86 ipnet: ipnet, 87 disableHostLoopback: disableHostLoopback, 88 enableIPv6: enableIPv6, 89 ifname: ifname, 90 implicitPortForwarding: implicitPortForwarding, 91 feat: feat, 92 }, nil 93 } 94 95 type parentDriver struct { 96 logWriter io.Writer 97 binary string 98 mtu int 99 ipnet *net.IPNet 100 disableHostLoopback bool 101 enableIPv6 bool 102 ifname string 103 infoMu sync.RWMutex 104 implicitPortForwarding bool 105 info func() *api.NetworkDriverInfo 106 feat *Features 107 } 108 109 const DriverName = "pasta" 110 111 func (d *parentDriver) Info(ctx context.Context) (*api.NetworkDriverInfo, error) { 112 d.infoMu.RLock() 113 infoFn := d.info 114 d.infoMu.RUnlock() 115 if infoFn == nil { 116 return &api.NetworkDriverInfo{ 117 Driver: DriverName, 118 }, nil 119 } 120 121 return infoFn(), nil 122 } 123 124 func (d *parentDriver) MTU() int { 125 return d.mtu 126 } 127 128 func (d *parentDriver) ConfigureNetwork(childPID int, stateDir, detachedNetNSPath string) (*messages.ParentInitNetworkDriverCompleted, func() error, error) { 129 tap := d.ifname 130 var cleanups []func() error 131 132 address, err := iputils.AddIPInt(d.ipnet.IP, 100) 133 if err != nil { 134 return nil, common.Seq(cleanups), err 135 } 136 netmask, _ := d.ipnet.Mask.Size() 137 gateway, err := iputils.AddIPInt(d.ipnet.IP, 2) 138 if err != nil { 139 return nil, common.Seq(cleanups), err 140 } 141 dns, err := iputils.AddIPInt(d.ipnet.IP, 3) 142 if err != nil { 143 return nil, common.Seq(cleanups), err 144 } 145 146 opts := []string{ 147 "--stderr", 148 "--ns-ifname=" + d.ifname, 149 "--mtu=" + strconv.Itoa(d.mtu), 150 "--config-net", 151 "--address=" + address.String(), 152 "--netmask=" + strconv.Itoa(netmask), 153 "--gateway=" + gateway.String(), 154 "--dns-forward=" + dns.String(), 155 } 156 if d.disableHostLoopback { 157 opts = append(opts, "--no-map-gw") 158 } 159 if !d.enableIPv6 { 160 opts = append(opts, "--ipv4-only") 161 } 162 if d.implicitPortForwarding { 163 opts = append(opts, "--tcp-ports=auto", 164 "--udp-ports=auto") 165 } else { 166 opts = append(opts, "--tcp-ports=none", 167 "--udp-ports=none") 168 } 169 if d.feat != nil { 170 if d.feat.HasHostLoToNsLo { 171 // Needed to keep `docker run -p 127.0.0.1:8080:80` functional with 172 // passt >= 2024_10_30.ee7d0b6 173 // 174 // https://github.com/rootless-containers/rootlesskit/pull/482#issuecomment-2591798590 175 opts = append(opts, "--host-lo-to-ns-lo") 176 } 177 } 178 if detachedNetNSPath == "" { 179 opts = append(opts, strconv.Itoa(childPID)) 180 } else { 181 opts = append(opts, 182 fmt.Sprintf("--userns=/proc/%d/ns/user", childPID), 183 "--netns="+detachedNetNSPath) 184 } 185 186 // FIXME: Doesn't work with: 187 // - passt-0.0~git20230627.289301b-1 (Ubuntu 23.10) 188 // - passt-0.0~git20240220.1e6f92b-1 (Ubuntu 24.04) 189 // see https://bugs.launchpad.net/ubuntu/+source/passt/+bug/2077158 190 // 191 // Workaround: set the kernel.apparmor_restrict_unprivileged_userns 192 // sysctl to 0, or (preferred) add the AppArmor profile from upstream, 193 // or from Debian packages, or from Ubuntu > 24.10. 194 cmd := exec.Command(d.binary, opts...) 195 logrus.Debugf("Executing %v", cmd.Args) 196 out, err := cmd.CombinedOutput() 197 if err != nil { 198 exitErr := &exec.ExitError{} 199 if errors.As(err, &exitErr) { 200 return nil, common.Seq(cleanups), 201 fmt.Errorf("pasta failed with exit code %d:\n%s", 202 exitErr.ExitCode(), string(out)) 203 } 204 return nil, common.Seq(cleanups), fmt.Errorf("executing %v: %w", cmd, err) 205 } 206 207 netmsg := messages.ParentInitNetworkDriverCompleted{ 208 Dev: tap, 209 MTU: d.mtu, 210 } 211 netmsg.IP = address.String() 212 netmsg.Netmask = netmask 213 netmsg.Gateway = gateway.String() 214 netmsg.DNS = []string{dns.String()} 215 216 d.infoMu.Lock() 217 d.info = func() *api.NetworkDriverInfo { 218 return &api.NetworkDriverInfo{ 219 Driver: DriverName, 220 DNS: []net.IP{net.ParseIP(netmsg.DNS[0])}, 221 ChildIP: net.ParseIP(netmsg.IP), 222 DynamicChildIP: false, 223 } 224 } 225 d.infoMu.Unlock() 226 return &netmsg, common.Seq(cleanups), nil 227 } 228 229 func NewChildDriver() network.ChildDriver { 230 return &childDriver{} 231 } 232 233 type childDriver struct { 234 } 235 236 func (d *childDriver) ChildDriverInfo() (*network.ChildDriverInfo, error) { 237 return &network.ChildDriverInfo{ 238 ConfiguresInterface: true, 239 }, nil 240 } 241 242 func (d *childDriver) ConfigureNetworkChild(netmsg *messages.ParentInitNetworkDriverCompleted, detachedNetNSPath string) (string, error) { 243 // NOP 244 return netmsg.Dev, nil 245 }