github.com/rootless-containers/rootlesskit/v2@v2.3.4/pkg/network/vpnkit/vpnkit.go (about) 1 package vpnkit 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "io" 8 "net" 9 "os" 10 "os/exec" 11 "path/filepath" 12 "strconv" 13 "sync" 14 "syscall" 15 "time" 16 17 "github.com/containernetworking/plugins/pkg/ns" 18 "github.com/google/uuid" 19 "github.com/moby/vpnkit/go/pkg/vmnet" 20 21 "github.com/sirupsen/logrus" 22 "github.com/songgao/water" 23 24 "github.com/rootless-containers/rootlesskit/v2/pkg/api" 25 "github.com/rootless-containers/rootlesskit/v2/pkg/common" 26 "github.com/rootless-containers/rootlesskit/v2/pkg/messages" 27 "github.com/rootless-containers/rootlesskit/v2/pkg/network" 28 ) 29 30 func NewParentDriver(binary string, mtu int, ifname string, disableHostLoopback bool) network.ParentDriver { 31 if binary == "" { 32 panic("got empty vpnkit binary") 33 } 34 if mtu < 0 { 35 panic("got negative mtu") 36 } 37 if mtu == 0 { 38 mtu = 1500 39 } 40 if mtu != 1500 { 41 logrus.Warnf("vpnkit is known to have issues with non-1500 MTU (current: %d), see https://github.com/rootless-containers/rootlesskit/issues/6#issuecomment-403531453", mtu) 42 // NOTE: iperf3 stops working with MTU >= 16425 43 } 44 if ifname == "" { 45 ifname = "tap0" 46 } 47 return &parentDriver{ 48 binary: binary, 49 mtu: mtu, 50 ifname: ifname, 51 disableHostLoopback: disableHostLoopback, 52 } 53 } 54 55 const ( 56 DriverName = "vpnkit" 57 opaqueMAC = "vpnkit.mac" 58 opaqueSocket = "vpnkit.socket" 59 opaqueUUID = "vpnkit.uuid" 60 ) 61 62 type parentDriver struct { 63 binary string 64 mtu int 65 ifname string 66 disableHostLoopback bool 67 infoMu sync.RWMutex 68 info func() *api.NetworkDriverInfo 69 } 70 71 func (d *parentDriver) Info(ctx context.Context) (*api.NetworkDriverInfo, error) { 72 d.infoMu.RLock() 73 infoFn := d.info 74 d.infoMu.RUnlock() 75 if infoFn == nil { 76 return &api.NetworkDriverInfo{ 77 Driver: DriverName, 78 }, nil 79 } 80 81 return infoFn(), nil 82 } 83 84 func (d *parentDriver) MTU() int { 85 return d.mtu 86 } 87 88 func (d *parentDriver) ConfigureNetwork(childPID int, stateDir, detachedNetNSPath string) (*messages.ParentInitNetworkDriverCompleted, func() error, error) { 89 var cleanups []func() error 90 vpnkitSocket := filepath.Join(stateDir, "vpnkit-ethernet.sock") 91 vpnkitCtx, vpnkitCancel := context.WithCancel(context.Background()) 92 vpnkitCmd := exec.CommandContext(vpnkitCtx, d.binary, "--ethernet", vpnkitSocket, "--mtu", strconv.Itoa(d.mtu)) 93 if d.disableHostLoopback { 94 vpnkitCmd.Args = append(vpnkitCmd.Args, "--host-ip", "0.0.0.0") 95 } 96 vpnkitCmd.SysProcAttr = &syscall.SysProcAttr{ 97 Pdeathsig: syscall.SIGKILL, 98 } 99 cleanups = append(cleanups, func() error { 100 logrus.Debugf("killing vpnkit") 101 vpnkitCancel() 102 wErr := vpnkitCmd.Wait() 103 logrus.Debugf("killed vpnkit: %v", wErr) 104 return nil 105 }) 106 if err := vpnkitCmd.Start(); err != nil { 107 return nil, common.Seq(cleanups), fmt.Errorf("executing %v: %w", vpnkitCmd, err) 108 } 109 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 110 cleanups = append(cleanups, func() error { cancel(); return nil }) 111 vmnet, err := waitForVPNKit(ctx, vpnkitSocket) 112 if err != nil { 113 return nil, common.Seq(cleanups), fmt.Errorf("connecting to %s: %w", vpnkitSocket, err) 114 } 115 cleanups = append(cleanups, func() error { return vmnet.Close() }) 116 vifUUID := uuid.New() 117 logrus.Debugf("connecting to VPNKit vmnet at %s as %s", vpnkitSocket, vifUUID) 118 // No context.WithTimeout..? 119 vif, err := vmnet.ConnectVif(vifUUID) 120 if err != nil { 121 return nil, common.Seq(cleanups), fmt.Errorf("connecting to %s with uuid %s: %w", vpnkitSocket, vifUUID, err) 122 } 123 logrus.Debugf("connected to VPNKit vmnet") 124 // TODO: support configuration 125 netmsg := messages.ParentInitNetworkDriverCompleted{ 126 Dev: d.ifname, 127 IP: vif.IP.String(), 128 Netmask: 24, 129 Gateway: "192.168.65.1", 130 DNS: []string{"192.168.65.1"}, 131 MTU: d.mtu, 132 NetworkDriverOpaque: map[string]string{ 133 opaqueMAC: vif.ClientMAC.String(), 134 opaqueSocket: vpnkitSocket, 135 opaqueUUID: vifUUID.String(), 136 }, 137 } 138 d.infoMu.Lock() 139 d.info = func() *api.NetworkDriverInfo { 140 return &api.NetworkDriverInfo{ 141 Driver: DriverName, 142 DNS: []net.IP{net.ParseIP(netmsg.DNS[0])}, 143 ChildIP: net.ParseIP(netmsg.IP), 144 DynamicChildIP: false, 145 } 146 } 147 d.infoMu.Unlock() 148 return &netmsg, common.Seq(cleanups), nil 149 } 150 151 func waitForVPNKit(ctx context.Context, socket string) (*vmnet.Vmnet, error) { 152 retried := 0 153 for { 154 vmnet, err := vmnet.New(ctx, socket) 155 if err == nil { 156 return vmnet, nil 157 } 158 sleepTime := (retried % 100) * 10 * int(time.Microsecond) 159 select { 160 case <-ctx.Done(): 161 return nil, fmt.Errorf("last error: %v: %w", err, ctx.Err()) 162 case <-time.After(time.Duration(sleepTime)): 163 } 164 retried++ 165 } 166 } 167 168 func NewChildDriver() network.ChildDriver { 169 return &childDriver{} 170 } 171 172 type childDriver struct { 173 } 174 175 func (d *childDriver) ChildDriverInfo() (*network.ChildDriverInfo, error) { 176 return &network.ChildDriverInfo{ 177 ConfiguresInterface: false, 178 }, nil 179 } 180 181 func (d *childDriver) ConfigureNetworkChild(netmsg *messages.ParentInitNetworkDriverCompleted, detachedNetNSPath string) (tap string, err error) { 182 tapName := netmsg.Dev 183 if tapName == "" { 184 return "", errors.New("no dev is set") 185 } 186 macStr := netmsg.NetworkDriverOpaque[opaqueMAC] 187 socket := netmsg.NetworkDriverOpaque[opaqueSocket] 188 uuidStr := netmsg.NetworkDriverOpaque[opaqueUUID] 189 if macStr == "" { 190 return "", errors.New("no VPNKit MAC is set") 191 } 192 if socket == "" { 193 return "", errors.New("no VPNKit socket is set") 194 } 195 if uuidStr == "" { 196 return "", errors.New("no VPNKit UUID is set") 197 } 198 return startVPNKitRoutines(context.TODO(), tapName, macStr, socket, uuidStr, detachedNetNSPath) 199 } 200 201 func startVPNKitRoutines(ctx context.Context, tapName, macStr, socket, uuidStr, detachedNetNSPath string) (string, error) { 202 var tap *water.Interface 203 fn := func(_ ns.NetNS) error { 204 cmds := [][]string{ 205 {"ip", "tuntap", "add", "name", tapName, "mode", "tap"}, 206 {"ip", "link", "set", tapName, "address", macStr}, 207 // IP stuff and MTU are configured in activateTap() in pkg/child/child.go 208 } 209 if err := common.Execs(os.Stderr, os.Environ(), cmds); err != nil { 210 return fmt.Errorf("executing %v: %w", cmds, err) 211 } 212 var err error 213 tap, err = water.New( 214 water.Config{ 215 DeviceType: water.TAP, 216 PlatformSpecificParams: water.PlatformSpecificParams{ 217 Name: tapName, 218 }, 219 }) 220 if err != nil { 221 return fmt.Errorf("creating tap %s: %w", tapName, err) 222 } 223 return nil 224 } 225 if detachedNetNSPath == "" { 226 if err := fn(nil); err != nil { 227 return "", err 228 } 229 } else { 230 if err := ns.WithNetNSPath(detachedNetNSPath, fn); err != nil { 231 return "", err 232 } 233 } 234 if tap.Name() != tapName { 235 return "", fmt.Errorf("expected %q, got %q", tapName, tap.Name()) 236 } 237 vmnet, err := vmnet.New(ctx, socket) 238 if err != nil { 239 return "", err 240 } 241 vifUUID, err := uuid.Parse(uuidStr) 242 if err != nil { 243 return "", err 244 } 245 vif, err := vmnet.ConnectVif(vifUUID) 246 if err != nil { 247 return "", err 248 } 249 go tap2vif(vif, tap) 250 go vif2tap(tap, vif) 251 return tapName, nil 252 } 253 254 func tap2vif(vif *vmnet.Vif, r io.Reader) { 255 b := make([]byte, 65536) 256 for { 257 n, err := r.Read(b) 258 if err != nil { 259 if errors.Is(err, io.EOF) { 260 return 261 } 262 panic(fmt.Errorf("tap2vif: read: %w", err)) 263 } 264 if err := vif.Write(b[:n]); err != nil { 265 if errors.Is(err, io.EOF) { 266 return 267 } 268 panic(fmt.Errorf("tap2vif: write: %w", err)) 269 } 270 } 271 } 272 273 func vif2tap(w io.Writer, vif *vmnet.Vif) { 274 for { 275 b, err := vif.Read() 276 if err != nil { 277 if errors.Is(err, io.EOF) { 278 return 279 } 280 panic(fmt.Errorf("vif2tap: read: %w", err)) 281 } 282 if _, err := w.Write(b); err != nil { 283 if errors.Is(err, io.EOF) { 284 return 285 } 286 287 panic(fmt.Errorf("vif2tap: write: %w", err)) 288 } 289 } 290 }