//go:build !cgo || static_build || !libnftables package nftables import ( "context" "fmt" "io" "os/exec" "strings" "sync" "github.com/containerd/log" "github.com/moby/moby/v2/daemon/internal/rootless" "go.opentelemetry.io/otel" ) type nftCtx struct{} var lookPathNSEnter = sync.OnceValues(func() (string, error) { return exec.LookPath("nsenter") }) var lookPathNft = sync.OnceValues(func() (string, error) { p, err := exec.LookPath("nft") if err != nil { log.G(context.Background()).WithError(err).Warnf("Failed to find nft tool") return "", fmt.Errorf("failed to find nft tool: %w", err) } return p, nil }) func preflight() error { _, err := lookPathNft() return err } func newNftCtx() (*nftCtx, error) { _, err := lookPathNft() if err != nil { return nil, err } return &nftCtx{}, nil } func (*nftCtx) Apply(ctx context.Context, nftCmd []byte) error { ctx, span := otel.Tracer("").Start(ctx, spanPrefix+".nftApply.exec") defer span.End() cmdPath, err := lookPathNft() if err != nil { return err } cmdArgs := []string{cmdPath, "-f", "-"} detachedNetNS, err := rootless.DetachedNetNS() if err != nil { return fmt.Errorf("could not check for detached netns: %w", err) } if detachedNetNS != "" && !rootless.InSandboxNS() { nsenterPath, err := lookPathNSEnter() if err != nil { return fmt.Errorf("nsenter not found: %w", err) } cmdPath = nsenterPath cmdArgs = append([]string{nsenterPath, "-n" + detachedNetNS, "-F", "--"}, cmdArgs...) } cmd := exec.CommandContext(ctx, cmdPath, cmdArgs[1:]...) stdinPipe, err := cmd.StdinPipe() if err != nil { return fmt.Errorf("getting stdin pipe for nft: %w", err) } stdoutPipe, err := cmd.StdoutPipe() if err != nil { return fmt.Errorf("getting stdout pipe for nft: %w", err) } stderrPipe, err := cmd.StderrPipe() if err != nil { return fmt.Errorf("getting stderr pipe for nft: %w", err) } if err := cmd.Start(); err != nil { return fmt.Errorf("starting nft: %w", err) } if _, err := stdinPipe.Write(nftCmd); err != nil { return fmt.Errorf("sending nft commands: %w", err) } if err := stdinPipe.Close(); err != nil { return fmt.Errorf("closing nft input pipe: %w", err) } stdoutBuf := strings.Builder{} if _, err := io.Copy(&stdoutBuf, stdoutPipe); err != nil { return fmt.Errorf("reading stdout of nft: %w", err) } stdout := stdoutBuf.String() stderrBuf := strings.Builder{} if _, err := io.Copy(&stderrBuf, stderrPipe); err != nil { return fmt.Errorf("reading stderr of nft: %w", err) } stderr := stderrBuf.String() err = cmd.Wait() if err != nil { return fmt.Errorf("running nft: %s %w", stderr, err) } log.G(ctx).WithFields(log.Fields{"stdout": stdout, "stderr": stderr}).Debug("nftables: updated") return nil } func (*nftCtx) Close() { }