github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/libpod/networking_linux.go (about) 1 // +build linux 2 3 package libpod 4 5 import ( 6 "bytes" 7 "crypto/rand" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "net" 12 "os" 13 "os/exec" 14 "path/filepath" 15 "strings" 16 "syscall" 17 "time" 18 19 cnitypes "github.com/containernetworking/cni/pkg/types/current" 20 "github.com/containernetworking/plugins/pkg/ns" 21 "github.com/containers/libpod/libpod/define" 22 "github.com/containers/libpod/pkg/errorhandling" 23 "github.com/containers/libpod/pkg/netns" 24 "github.com/containers/libpod/pkg/rootless" 25 "github.com/containers/libpod/pkg/rootlessport" 26 "github.com/cri-o/ocicni/pkg/ocicni" 27 "github.com/pkg/errors" 28 "github.com/sirupsen/logrus" 29 "github.com/vishvananda/netlink" 30 "golang.org/x/sys/unix" 31 ) 32 33 // Get an OCICNI network config 34 func (r *Runtime) getPodNetwork(id, name, nsPath string, networks []string, ports []ocicni.PortMapping, staticIP net.IP, staticMAC net.HardwareAddr) ocicni.PodNetwork { 35 var networkKey string 36 if len(networks) > 0 { 37 // This is inconsistent for >1 network, but it's probably the 38 // best we can do. 39 networkKey = networks[0] 40 } else { 41 networkKey = r.netPlugin.GetDefaultNetworkName() 42 } 43 network := ocicni.PodNetwork{ 44 Name: name, 45 Namespace: name, // TODO is there something else we should put here? We don't know about Kube namespaces 46 ID: id, 47 NetNS: nsPath, 48 RuntimeConfig: map[string]ocicni.RuntimeConfig{ 49 networkKey: {PortMappings: ports}, 50 }, 51 } 52 53 // If we have extra networks, add them 54 if len(networks) > 0 { 55 network.Networks = make([]ocicni.NetAttachment, len(networks)) 56 for i, netName := range networks { 57 network.Networks[i].Name = netName 58 } 59 } 60 61 if staticIP != nil || staticMAC != nil { 62 // For static IP or MAC, we need to populate networks even if 63 // it's just the default. 64 if len(networks) == 0 { 65 // If len(networks) == 0 this is guaranteed to be the 66 // default network. 67 network.Networks = []ocicni.NetAttachment{{Name: networkKey}} 68 } 69 var rt ocicni.RuntimeConfig = ocicni.RuntimeConfig{PortMappings: ports} 70 if staticIP != nil { 71 rt.IP = staticIP.String() 72 } 73 if staticMAC != nil { 74 rt.MAC = staticMAC.String() 75 } 76 network.RuntimeConfig = map[string]ocicni.RuntimeConfig{ 77 networkKey: rt, 78 } 79 } 80 81 return network 82 } 83 84 // Create and configure a new network namespace for a container 85 func (r *Runtime) configureNetNS(ctr *Container, ctrNS ns.NetNS) ([]*cnitypes.Result, error) { 86 var requestedIP net.IP 87 if ctr.requestedIP != nil { 88 requestedIP = ctr.requestedIP 89 // cancel request for a specific IP in case the container is reused later 90 ctr.requestedIP = nil 91 } else { 92 requestedIP = ctr.config.StaticIP 93 } 94 95 var requestedMAC net.HardwareAddr 96 if ctr.requestedMAC != nil { 97 requestedMAC = ctr.requestedMAC 98 // cancel request for a specific MAC in case the container is reused later 99 ctr.requestedMAC = nil 100 } else { 101 requestedMAC = ctr.config.StaticMAC 102 } 103 104 podNetwork := r.getPodNetwork(ctr.ID(), ctr.Name(), ctrNS.Path(), ctr.config.Networks, ctr.config.PortMappings, requestedIP, requestedMAC) 105 106 results, err := r.netPlugin.SetUpPod(podNetwork) 107 if err != nil { 108 return nil, errors.Wrapf(err, "error configuring network namespace for container %s", ctr.ID()) 109 } 110 defer func() { 111 if err != nil { 112 if err2 := r.netPlugin.TearDownPod(podNetwork); err2 != nil { 113 logrus.Errorf("Error tearing down partially created network namespace for container %s: %v", ctr.ID(), err2) 114 } 115 } 116 }() 117 118 networkStatus := make([]*cnitypes.Result, 0) 119 for idx, r := range results { 120 logrus.Debugf("[%d] CNI result: %v", idx, r.Result) 121 resultCurrent, err := cnitypes.GetResult(r.Result) 122 if err != nil { 123 return nil, errors.Wrapf(err, "error parsing CNI plugin result %q: %v", r.Result, err) 124 } 125 networkStatus = append(networkStatus, resultCurrent) 126 } 127 128 return networkStatus, nil 129 } 130 131 // Create and configure a new network namespace for a container 132 func (r *Runtime) createNetNS(ctr *Container) (n ns.NetNS, q []*cnitypes.Result, err error) { 133 ctrNS, err := netns.NewNS() 134 if err != nil { 135 return nil, nil, errors.Wrapf(err, "error creating network namespace for container %s", ctr.ID()) 136 } 137 defer func() { 138 if err != nil { 139 if err2 := netns.UnmountNS(ctrNS); err2 != nil { 140 logrus.Errorf("Error unmounting partially created network namespace for container %s: %v", ctr.ID(), err2) 141 } 142 if err2 := ctrNS.Close(); err2 != nil { 143 logrus.Errorf("Error closing partially created network namespace for container %s: %v", ctr.ID(), err2) 144 } 145 } 146 }() 147 148 logrus.Debugf("Made network namespace at %s for container %s", ctrNS.Path(), ctr.ID()) 149 150 networkStatus := []*cnitypes.Result{} 151 if !rootless.IsRootless() && !ctr.config.NetMode.IsSlirp4netns() { 152 networkStatus, err = r.configureNetNS(ctr, ctrNS) 153 } 154 return ctrNS, networkStatus, err 155 } 156 157 type slirpFeatures struct { 158 HasDisableHostLoopback bool 159 HasMTU bool 160 HasEnableSandbox bool 161 HasEnableSeccomp bool 162 } 163 164 func checkSlirpFlags(path string) (*slirpFeatures, error) { 165 cmd := exec.Command(path, "--help") 166 out, err := cmd.CombinedOutput() 167 if err != nil { 168 return nil, errors.Wrapf(err, "slirp4netns %q", out) 169 } 170 return &slirpFeatures{ 171 HasDisableHostLoopback: strings.Contains(string(out), "--disable-host-loopback"), 172 HasMTU: strings.Contains(string(out), "--mtu"), 173 HasEnableSandbox: strings.Contains(string(out), "--enable-sandbox"), 174 HasEnableSeccomp: strings.Contains(string(out), "--enable-seccomp"), 175 }, nil 176 } 177 178 // Configure the network namespace for a rootless container 179 func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) { 180 path := r.config.Engine.NetworkCmdPath 181 182 if path == "" { 183 var err error 184 path, err = exec.LookPath("slirp4netns") 185 if err != nil { 186 logrus.Errorf("could not find slirp4netns, the network namespace won't be configured: %v", err) 187 return nil 188 } 189 } 190 191 syncR, syncW, err := os.Pipe() 192 if err != nil { 193 return errors.Wrapf(err, "failed to open pipe") 194 } 195 defer errorhandling.CloseQuiet(syncR) 196 defer errorhandling.CloseQuiet(syncW) 197 198 havePortMapping := len(ctr.Config().PortMappings) > 0 199 logPath := filepath.Join(ctr.runtime.config.Engine.TmpDir, fmt.Sprintf("slirp4netns-%s.log", ctr.config.ID)) 200 201 cmdArgs := []string{} 202 slirpFeatures, err := checkSlirpFlags(path) 203 if err != nil { 204 return errors.Wrapf(err, "error checking slirp4netns binary %s: %q", path, err) 205 } 206 if slirpFeatures.HasDisableHostLoopback { 207 cmdArgs = append(cmdArgs, "--disable-host-loopback") 208 } 209 if slirpFeatures.HasMTU { 210 cmdArgs = append(cmdArgs, "--mtu", "65520") 211 } 212 if slirpFeatures.HasEnableSandbox { 213 cmdArgs = append(cmdArgs, "--enable-sandbox") 214 } 215 if slirpFeatures.HasEnableSeccomp { 216 cmdArgs = append(cmdArgs, "--enable-seccomp") 217 } 218 219 // the slirp4netns arguments being passed are describes as follows: 220 // from the slirp4netns documentation: https://github.com/rootless-containers/slirp4netns 221 // -c, --configure Brings up the tap interface 222 // -e, --exit-fd=FD specify the FD for terminating slirp4netns 223 // -r, --ready-fd=FD specify the FD to write to when the initialization steps are finished 224 cmdArgs = append(cmdArgs, "-c", "-e", "3", "-r", "4") 225 netnsPath := "" 226 if !ctr.config.PostConfigureNetNS { 227 ctr.rootlessSlirpSyncR, ctr.rootlessSlirpSyncW, err = os.Pipe() 228 if err != nil { 229 return errors.Wrapf(err, "failed to create rootless network sync pipe") 230 } 231 netnsPath = ctr.state.NetNS.Path() 232 cmdArgs = append(cmdArgs, "--netns-type=path", netnsPath, "tap0") 233 } else { 234 defer errorhandling.CloseQuiet(ctr.rootlessSlirpSyncR) 235 defer errorhandling.CloseQuiet(ctr.rootlessSlirpSyncW) 236 netnsPath = fmt.Sprintf("/proc/%d/ns/net", ctr.state.PID) 237 // we don't use --netns-path here (unavailable for slirp4netns < v0.4) 238 cmdArgs = append(cmdArgs, fmt.Sprintf("%d", ctr.state.PID), "tap0") 239 } 240 241 cmd := exec.Command(path, cmdArgs...) 242 logrus.Debugf("slirp4netns command: %s", strings.Join(cmd.Args, " ")) 243 cmd.SysProcAttr = &syscall.SysProcAttr{ 244 Setpgid: true, 245 } 246 247 // workaround for https://github.com/rootless-containers/slirp4netns/pull/153 248 if slirpFeatures.HasEnableSandbox { 249 cmd.SysProcAttr.Cloneflags = syscall.CLONE_NEWNS 250 cmd.SysProcAttr.Unshareflags = syscall.CLONE_NEWNS 251 } 252 253 // Leak one end of the pipe in slirp4netns, the other will be sent to conmon 254 cmd.ExtraFiles = append(cmd.ExtraFiles, ctr.rootlessSlirpSyncR, syncW) 255 256 logFile, err := os.Create(logPath) 257 if err != nil { 258 return errors.Wrapf(err, "failed to open slirp4netns log file %s", logPath) 259 } 260 defer logFile.Close() 261 // Unlink immediately the file so we won't need to worry about cleaning it up later. 262 // It is still accessible through the open fd logFile. 263 if err := os.Remove(logPath); err != nil { 264 return errors.Wrapf(err, "delete file %s", logPath) 265 } 266 cmd.Stdout = logFile 267 cmd.Stderr = logFile 268 if err := cmd.Start(); err != nil { 269 return errors.Wrapf(err, "failed to start slirp4netns process") 270 } 271 defer func() { 272 if err := cmd.Process.Release(); err != nil { 273 logrus.Errorf("unable to release command process: %q", err) 274 } 275 }() 276 277 if err := waitForSync(syncR, cmd, logFile, 1*time.Second); err != nil { 278 return err 279 } 280 281 if havePortMapping { 282 return r.setupRootlessPortMapping(ctr, netnsPath) 283 } 284 return nil 285 } 286 287 func waitForSync(syncR *os.File, cmd *exec.Cmd, logFile io.ReadSeeker, timeout time.Duration) error { 288 prog := filepath.Base(cmd.Path) 289 if len(cmd.Args) > 0 { 290 prog = cmd.Args[0] 291 } 292 b := make([]byte, 16) 293 for { 294 if err := syncR.SetDeadline(time.Now().Add(timeout)); err != nil { 295 return errors.Wrapf(err, "error setting %s pipe timeout", prog) 296 } 297 // FIXME: return err as soon as proc exits, without waiting for timeout 298 if _, err := syncR.Read(b); err == nil { 299 break 300 } else { 301 if os.IsTimeout(err) { 302 // Check if the process is still running. 303 var status syscall.WaitStatus 304 pid, err := syscall.Wait4(cmd.Process.Pid, &status, syscall.WNOHANG, nil) 305 if err != nil { 306 return errors.Wrapf(err, "failed to read %s process status", prog) 307 } 308 if pid != cmd.Process.Pid { 309 continue 310 } 311 if status.Exited() { 312 // Seek at the beginning of the file and read all its content 313 if _, err := logFile.Seek(0, 0); err != nil { 314 logrus.Errorf("could not seek log file: %q", err) 315 } 316 logContent, err := ioutil.ReadAll(logFile) 317 if err != nil { 318 return errors.Wrapf(err, "%s failed", prog) 319 } 320 return errors.Errorf("%s failed: %q", prog, logContent) 321 } 322 if status.Signaled() { 323 return errors.Errorf("%s killed by signal", prog) 324 } 325 continue 326 } 327 return errors.Wrapf(err, "failed to read from %s sync pipe", prog) 328 } 329 } 330 return nil 331 } 332 333 func (r *Runtime) setupRootlessPortMapping(ctr *Container, netnsPath string) (err error) { 334 syncR, syncW, err := os.Pipe() 335 if err != nil { 336 return errors.Wrapf(err, "failed to open pipe") 337 } 338 defer errorhandling.CloseQuiet(syncR) 339 defer errorhandling.CloseQuiet(syncW) 340 341 logPath := filepath.Join(ctr.runtime.config.Engine.TmpDir, fmt.Sprintf("rootlessport-%s.log", ctr.config.ID)) 342 logFile, err := os.Create(logPath) 343 if err != nil { 344 return errors.Wrapf(err, "failed to open rootlessport log file %s", logPath) 345 } 346 defer logFile.Close() 347 // Unlink immediately the file so we won't need to worry about cleaning it up later. 348 // It is still accessible through the open fd logFile. 349 if err := os.Remove(logPath); err != nil { 350 return errors.Wrapf(err, "delete file %s", logPath) 351 } 352 353 if !ctr.config.PostConfigureNetNS { 354 ctr.rootlessPortSyncR, ctr.rootlessPortSyncW, err = os.Pipe() 355 if err != nil { 356 return errors.Wrapf(err, "failed to create rootless port sync pipe") 357 } 358 } 359 360 cfg := rootlessport.Config{ 361 Mappings: ctr.config.PortMappings, 362 NetNSPath: netnsPath, 363 ExitFD: 3, 364 ReadyFD: 4, 365 TmpDir: ctr.runtime.config.Engine.TmpDir, 366 } 367 cfgJSON, err := json.Marshal(cfg) 368 if err != nil { 369 return err 370 } 371 cfgR := bytes.NewReader(cfgJSON) 372 var stdout bytes.Buffer 373 cmd := exec.Command(fmt.Sprintf("/proc/%d/exe", os.Getpid())) 374 cmd.Args = []string{rootlessport.ReexecKey} 375 // Leak one end of the pipe in rootlessport process, the other will be sent to conmon 376 377 if ctr.rootlessPortSyncR != nil { 378 defer errorhandling.CloseQuiet(ctr.rootlessPortSyncR) 379 } 380 381 cmd.ExtraFiles = append(cmd.ExtraFiles, ctr.rootlessPortSyncR, syncW) 382 cmd.Stdin = cfgR 383 // stdout is for human-readable error, stderr is for debug log 384 cmd.Stdout = &stdout 385 cmd.Stderr = io.MultiWriter(logFile, &logrusDebugWriter{"rootlessport: "}) 386 cmd.SysProcAttr = &syscall.SysProcAttr{ 387 Setpgid: true, 388 } 389 if err := cmd.Start(); err != nil { 390 return errors.Wrapf(err, "failed to start rootlessport process") 391 } 392 defer func() { 393 if err := cmd.Process.Release(); err != nil { 394 logrus.Errorf("unable to release rootlessport process: %q", err) 395 } 396 }() 397 if err := waitForSync(syncR, cmd, logFile, 3*time.Second); err != nil { 398 stdoutStr := stdout.String() 399 if stdoutStr != "" { 400 // err contains full debug log and too verbose, so return stdoutStr 401 logrus.Debug(err) 402 return errors.Errorf("failed to expose ports via rootlessport: %q", stdoutStr) 403 } 404 return err 405 } 406 logrus.Debug("rootlessport is ready") 407 return nil 408 } 409 410 // Configure the network namespace using the container process 411 func (r *Runtime) setupNetNS(ctr *Container) (err error) { 412 nsProcess := fmt.Sprintf("/proc/%d/ns/net", ctr.state.PID) 413 414 b := make([]byte, 16) 415 416 if _, err := rand.Reader.Read(b); err != nil { 417 return errors.Wrapf(err, "failed to generate random netns name") 418 } 419 420 nsPath := fmt.Sprintf("/var/run/netns/cni-%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]) 421 422 if err := os.MkdirAll(filepath.Dir(nsPath), 0711); err != nil { 423 return errors.Wrapf(err, "cannot create %s", filepath.Dir(nsPath)) 424 } 425 426 mountPointFd, err := os.Create(nsPath) 427 if err != nil { 428 return errors.Wrapf(err, "cannot open %s", nsPath) 429 } 430 if err := mountPointFd.Close(); err != nil { 431 return err 432 } 433 434 if err := unix.Mount(nsProcess, nsPath, "none", unix.MS_BIND, ""); err != nil { 435 return errors.Wrapf(err, "cannot mount %s", nsPath) 436 } 437 438 netNS, err := ns.GetNS(nsPath) 439 if err != nil { 440 return err 441 } 442 networkStatus, err := r.configureNetNS(ctr, netNS) 443 444 // Assign NetNS attributes to container 445 ctr.state.NetNS = netNS 446 ctr.state.NetworkStatus = networkStatus 447 return err 448 } 449 450 // Join an existing network namespace 451 func joinNetNS(path string) (ns.NetNS, error) { 452 netNS, err := ns.GetNS(path) 453 if err != nil { 454 return nil, errors.Wrapf(err, "error retrieving network namespace at %s", path) 455 } 456 457 return netNS, nil 458 } 459 460 // Close a network namespace. 461 // Differs from teardownNetNS() in that it will not attempt to undo the setup of 462 // the namespace, but will instead only close the open file descriptor 463 func (r *Runtime) closeNetNS(ctr *Container) error { 464 if ctr.state.NetNS == nil { 465 // The container has no network namespace, we're set 466 return nil 467 } 468 469 if err := ctr.state.NetNS.Close(); err != nil { 470 return errors.Wrapf(err, "error closing network namespace for container %s", ctr.ID()) 471 } 472 473 ctr.state.NetNS = nil 474 475 return nil 476 } 477 478 // Tear down a network namespace, undoing all state associated with it. 479 func (r *Runtime) teardownNetNS(ctr *Container) error { 480 if ctr.state.NetNS == nil { 481 // The container has no network namespace, we're set 482 return nil 483 } 484 485 logrus.Debugf("Tearing down network namespace at %s for container %s", ctr.state.NetNS.Path(), ctr.ID()) 486 487 // rootless containers do not use the CNI plugin 488 if !rootless.IsRootless() && !ctr.config.NetMode.IsSlirp4netns() { 489 var requestedIP net.IP 490 if ctr.requestedIP != nil { 491 requestedIP = ctr.requestedIP 492 // cancel request for a specific IP in case the container is reused later 493 ctr.requestedIP = nil 494 } else { 495 requestedIP = ctr.config.StaticIP 496 } 497 498 var requestedMAC net.HardwareAddr 499 if ctr.requestedMAC != nil { 500 requestedMAC = ctr.requestedMAC 501 // cancel request for a specific MAC in case the container is reused later 502 ctr.requestedMAC = nil 503 } else { 504 requestedMAC = ctr.config.StaticMAC 505 } 506 507 podNetwork := r.getPodNetwork(ctr.ID(), ctr.Name(), ctr.state.NetNS.Path(), ctr.config.Networks, ctr.config.PortMappings, requestedIP, requestedMAC) 508 509 if err := r.netPlugin.TearDownPod(podNetwork); err != nil { 510 return errors.Wrapf(err, "error tearing down CNI namespace configuration for container %s", ctr.ID()) 511 } 512 } 513 514 // First unmount the namespace 515 if err := netns.UnmountNS(ctr.state.NetNS); err != nil { 516 return errors.Wrapf(err, "error unmounting network namespace for container %s", ctr.ID()) 517 } 518 519 // Now close the open file descriptor 520 if err := ctr.state.NetNS.Close(); err != nil { 521 return errors.Wrapf(err, "error closing network namespace for container %s", ctr.ID()) 522 } 523 524 ctr.state.NetNS = nil 525 526 return nil 527 } 528 529 func getContainerNetNS(ctr *Container) (string, error) { 530 if ctr.state.NetNS != nil { 531 return ctr.state.NetNS.Path(), nil 532 } 533 if ctr.config.NetNsCtr != "" { 534 c, err := ctr.runtime.GetContainer(ctr.config.NetNsCtr) 535 if err != nil { 536 return "", err 537 } 538 if err = c.syncContainer(); err != nil { 539 return "", err 540 } 541 return c.state.NetNS.Path(), nil 542 } 543 return "", nil 544 } 545 546 func getContainerNetIO(ctr *Container) (*netlink.LinkStatistics, error) { 547 var netStats *netlink.LinkStatistics 548 // rootless v2 cannot seem to resolve its network connection to 549 // collect statistics. For now, we allow stats to at least run 550 // by returning nil 551 if rootless.IsRootless() { 552 return netStats, nil 553 } 554 netNSPath, netPathErr := getContainerNetNS(ctr) 555 if netPathErr != nil { 556 return nil, netPathErr 557 } 558 if netNSPath == "" { 559 // If netNSPath is empty, it was set as none, and no netNS was set up 560 // this is a valid state and thus return no error, nor any statistics 561 return nil, nil 562 } 563 err := ns.WithNetNSPath(netNSPath, func(_ ns.NetNS) error { 564 link, err := netlink.LinkByName(ocicni.DefaultInterfaceName) 565 if err != nil { 566 return err 567 } 568 netStats = link.Attrs().Statistics 569 return nil 570 }) 571 return netStats, err 572 } 573 574 // Produce an InspectNetworkSettings containing information on the container 575 // network. 576 func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, error) { 577 settings := new(define.InspectNetworkSettings) 578 settings.Ports = []ocicni.PortMapping{} 579 if c.config.PortMappings != nil { 580 // TODO: This may not be safe. 581 settings.Ports = c.config.PortMappings 582 } 583 584 // We can't do more if the network is down. 585 if c.state.NetNS == nil { 586 return settings, nil 587 } 588 589 // Set network namespace path 590 settings.SandboxKey = c.state.NetNS.Path() 591 592 // If this is empty, we're probably slirp4netns 593 if len(c.state.NetworkStatus) == 0 { 594 return settings, nil 595 } 596 597 // If we have CNI networks - handle that here 598 if len(c.config.Networks) > 0 { 599 if len(c.config.Networks) != len(c.state.NetworkStatus) { 600 return nil, errors.Wrapf(define.ErrInternal, "network inspection mismatch: asked to join %d CNI networks but have information on %d networks", len(c.config.Networks), len(c.state.NetworkStatus)) 601 } 602 603 settings.Networks = make(map[string]*define.InspectAdditionalNetwork) 604 605 // CNI results should be in the same order as the list of 606 // networks we pass into CNI. 607 for index, name := range c.config.Networks { 608 cniResult := c.state.NetworkStatus[index] 609 addedNet := new(define.InspectAdditionalNetwork) 610 addedNet.NetworkID = name 611 612 basicConfig, err := resultToBasicNetworkConfig(cniResult) 613 if err != nil { 614 return nil, err 615 } 616 addedNet.InspectBasicNetworkConfig = basicConfig 617 618 settings.Networks[name] = addedNet 619 } 620 621 return settings, nil 622 } 623 624 // If not joining networks, we should have at most 1 result 625 if len(c.state.NetworkStatus) > 1 { 626 return nil, errors.Wrapf(define.ErrInternal, "should have at most 1 CNI result if not joining networks, instead got %d", len(c.state.NetworkStatus)) 627 } 628 629 if len(c.state.NetworkStatus) == 1 { 630 basicConfig, err := resultToBasicNetworkConfig(c.state.NetworkStatus[0]) 631 if err != nil { 632 return nil, err 633 } 634 635 settings.InspectBasicNetworkConfig = basicConfig 636 } 637 638 return settings, nil 639 } 640 641 // resultToBasicNetworkConfig produces an InspectBasicNetworkConfig from a CNI 642 // result 643 func resultToBasicNetworkConfig(result *cnitypes.Result) (define.InspectBasicNetworkConfig, error) { 644 config := define.InspectBasicNetworkConfig{} 645 646 for _, ctrIP := range result.IPs { 647 size, _ := ctrIP.Address.Mask.Size() 648 switch { 649 case ctrIP.Version == "4" && config.IPAddress == "": 650 config.IPAddress = ctrIP.Address.IP.String() 651 config.IPPrefixLen = size 652 config.Gateway = ctrIP.Gateway.String() 653 if ctrIP.Interface != nil && *ctrIP.Interface < len(result.Interfaces) && *ctrIP.Interface > 0 { 654 config.MacAddress = result.Interfaces[*ctrIP.Interface].Mac 655 } 656 case ctrIP.Version == "4" && config.IPAddress != "": 657 config.SecondaryIPAddresses = append(config.SecondaryIPAddresses, ctrIP.Address.String()) 658 if ctrIP.Interface != nil && *ctrIP.Interface < len(result.Interfaces) && *ctrIP.Interface > 0 { 659 config.AdditionalMacAddresses = append(config.AdditionalMacAddresses, result.Interfaces[*ctrIP.Interface].Mac) 660 } 661 case ctrIP.Version == "6" && config.IPAddress == "": 662 config.GlobalIPv6Address = ctrIP.Address.IP.String() 663 config.GlobalIPv6PrefixLen = size 664 config.IPv6Gateway = ctrIP.Gateway.String() 665 case ctrIP.Version == "6" && config.IPAddress != "": 666 config.SecondaryIPv6Addresses = append(config.SecondaryIPv6Addresses, ctrIP.Address.String()) 667 default: 668 return config, errors.Wrapf(define.ErrInternal, "unrecognized IP version %q", ctrIP.Version) 669 } 670 } 671 672 return config, nil 673 } 674 675 // This is a horrible hack, necessary because CNI does not properly clean up 676 // after itself on an unclean reboot. Return what we're pretty sure is the path 677 // to CNI's internal files (it's not really exposed to us). 678 func getCNINetworksDir() (string, error) { 679 return "/var/lib/cni/networks", nil 680 } 681 682 type logrusDebugWriter struct { 683 prefix string 684 } 685 686 func (w *logrusDebugWriter) Write(p []byte) (int, error) { 687 logrus.Debugf("%s%s", w.prefix, string(p)) 688 return len(p), nil 689 }