Files
Memoh/internal/containerd/network.go
T
2026-02-09 08:47:18 +08:00

198 lines
4.7 KiB
Go

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")
}