package microvm import ( "bytes" "context" _ "embed" "fmt" "log/slog" "os" "os/exec" "text/template" "tangled.org/core/spindle/netguard" ) var ( blockedNamespaceRoutes = netguard.BlockedRoutes blockedNamespaceNets = netguard.BlockedNets ) //go:embed netns_wrapper.sh.tmpl var netnsWrapperTemplate string type netnsWrapperData struct { TapName string BlockedRoutes []string } func writeNetnsWrapper(path string, dev bool) error { tmpl, err := template.New("netns-wrapper").Parse(netnsWrapperTemplate) if err != nil { return fmt.Errorf("parse qemu network namespace wrapper template: %w", err) } var script bytes.Buffer var routes []string if !dev { routes = blockedNamespaceRoutes } err = tmpl.Execute(&script, netnsWrapperData{ TapName: netnsTapName, BlockedRoutes: routes, }) if err != nil { return fmt.Errorf("render qemu network namespace wrapper template: %w", err) } if err := os.WriteFile(path, script.Bytes(), 0o700); err != nil { return fmt.Errorf("write qemu network namespace wrapper: %w", err) } return nil } type slirpNamespace struct { spec ImageSpec pidFile string dev bool } func (n *slirpNamespace) Start(ctx context.Context, logFile *os.File, logger *slog.Logger) (*exec.Cmd, *os.File, error) { pid, err := waitForPIDFile(ctx, n.pidFile) if err != nil { return nil, nil, err } exitR, exitW, err := os.Pipe() if err != nil { return nil, nil, fmt.Errorf("create slirp4netns exit pipe: %w", err) } defer exitR.Close() // always close our read end; child gets it via ExtraFiles dup var ok bool defer func() { if !ok { _ = exitW.Close() } }() slirpPath, err := exec.LookPath("slirp4netns") if err != nil { return nil, nil, fmt.Errorf("slirp4netns command not found in PATH: %w", err) } args := slirpArgs(n.dev, pid) cmd := exec.CommandContext(ctx, slirpPath, args...) cmd.ExtraFiles = []*os.File{exitR} cmd.Stdout = logFile cmd.Stderr = logFile if err := cmd.Start(); err != nil { return nil, nil, fmt.Errorf("start slirp4netns: %w", err) } logger.Info("started slirp4netns network namespace", "pid", pid, "cidr", outerSlirpCIDR, "tap", netnsTapName) ok = true return cmd, exitW, nil } func slirpArgs(dev bool, pid string) []string { args := []string{ "--configure", "--mtu=" + netnsMTU, } if !dev { args = append(args, "--disable-host-loopback") } args = append(args, "--disable-dns", "--enable-seccomp", "--exit-fd=3", "--cidr="+outerSlirpCIDR, pid, netnsTapName, ) return args }