package containerd import ( "bytes" "context" "fmt" "os" "os/exec" "path/filepath" "runtime" "strings" "github.com/containerd/containerd/v2/client" gocni "github.com/containerd/go-cni" ) const ( defaultCNIConfDir = "/etc/cni/net.d" defaultCNIBinDir = "/opt/cni/bin" ) // SetupNetwork attaches CNI networking to a running task. func SetupNetwork(ctx context.Context, task client.Task, containerID string) error { if task == nil { return ErrInvalidArgument } if containerID == "" { containerID = task.ID() } if containerID == "" { return ErrInvalidArgument } pid := task.Pid() if pid == 0 { return fmt.Errorf("task pid not available for %s", containerID) } if runtime.GOOS == "darwin" { return setupNetworkWithCLI(ctx, containerID, pid) } if _, err := os.Stat(defaultCNIConfDir); err != nil { return fmt.Errorf("cni config dir missing: %s: %w", defaultCNIConfDir, err) } if _, err := os.Stat(defaultCNIBinDir); err != nil { return fmt.Errorf("cni bin dir missing: %s: %w", defaultCNIBinDir, err) } netnsPath := filepath.Join("/proc", fmt.Sprint(pid), "ns", "net") if _, err := os.Stat(netnsPath); err != nil { return fmt.Errorf("netns not found: %s: %w", netnsPath, err) } cni, err := gocni.New( gocni.WithPluginDir([]string{defaultCNIBinDir}), gocni.WithPluginConfDir(defaultCNIConfDir), ) if err != nil { return err } if err := cni.Load(gocni.WithLoNetwork, gocni.WithDefaultConf); err != nil { return err } _, err = cni.Setup(ctx, containerID, netnsPath) if err == nil { return nil } if !isDuplicateAllocationError(err) { return err } if rmErr := cni.Remove(ctx, containerID, netnsPath); rmErr != nil { return rmErr } _, err = cni.Setup(ctx, containerID, netnsPath) return err } func setupNetworkWithCLI(ctx context.Context, containerID string, pid uint32) error { args := []string{ "shell", "--tty=false", "default", "--", "sudo", "-n", "memoh-cli", "cni-setup", "--id", containerID, "--pid", fmt.Sprint(pid), "--conf-dir", defaultCNIConfDir, "--bin-dir", defaultCNIBinDir, } cmd := exec.CommandContext(ctx, "limactl", args...) var stderr bytes.Buffer cmd.Stderr = &stderr if err := cmd.Run(); err != nil { if stderr.Len() > 0 { cniErr := fmt.Errorf("cni cli failed: %s", strings.TrimSpace(stderr.String())) if !isDuplicateAllocationError(cniErr) { return cniErr } } else if !isDuplicateAllocationError(err) { return err } if rmErr := removeNetworkWithCLI(ctx, containerID, pid); rmErr != nil { return rmErr } cmd = exec.CommandContext(ctx, "limactl", args...) stderr.Reset() cmd.Stderr = &stderr if err := cmd.Run(); err != nil { if stderr.Len() > 0 { return fmt.Errorf("cni cli failed: %s", strings.TrimSpace(stderr.String())) } return err } } return nil } // RemoveNetwork detaches CNI networking for a running task. func RemoveNetwork(ctx context.Context, task client.Task, containerID string) error { if task == nil { return ErrInvalidArgument } if containerID == "" { containerID = task.ID() } if containerID == "" { return ErrInvalidArgument } pid := task.Pid() if pid == 0 { return fmt.Errorf("task pid not available for %s", containerID) } if runtime.GOOS == "darwin" { return removeNetworkWithCLI(ctx, containerID, pid) } if _, err := os.Stat(defaultCNIConfDir); err != nil { return fmt.Errorf("cni config dir missing: %s: %w", defaultCNIConfDir, err) } if _, err := os.Stat(defaultCNIBinDir); err != nil { return fmt.Errorf("cni bin dir missing: %s: %w", defaultCNIBinDir, err) } netnsPath := filepath.Join("/proc", fmt.Sprint(pid), "ns", "net") if _, err := os.Stat(netnsPath); err != nil { return fmt.Errorf("netns not found: %s: %w", netnsPath, err) } cni, err := gocni.New( gocni.WithPluginDir([]string{defaultCNIBinDir}), gocni.WithPluginConfDir(defaultCNIConfDir), ) if err != nil { return err } if err := cni.Load(gocni.WithLoNetwork, gocni.WithDefaultConf); err != nil { return err } return cni.Remove(ctx, containerID, netnsPath) } func removeNetworkWithCLI(ctx context.Context, containerID string, pid uint32) error { args := []string{ "shell", "--tty=false", "default", "--", "sudo", "-n", "memoh-cli", "cni-remove", "--id", containerID, "--pid", fmt.Sprint(pid), "--conf-dir", defaultCNIConfDir, "--bin-dir", defaultCNIBinDir, } cmd := exec.CommandContext(ctx, "limactl", args...) var stderr bytes.Buffer cmd.Stderr = &stderr if err := cmd.Run(); err != nil { if stderr.Len() > 0 { return fmt.Errorf("cni cli failed: %s", strings.TrimSpace(stderr.String())) } return err } return nil } func isDuplicateAllocationError(err error) bool { if err == nil { return false } return strings.Contains(err.Error(), "duplicate allocation") }