Browse code

d/libn/i/nftables: cgo nftables without `nft` cmd

When the daemon is linked against libnftables it programs the kernel
without invoking the `nft` command. Allow the nftables firewall backend
to be enabled when libnftables is used, irrespective of whether `nft` is
installed on the host.

Update the bridge network driver to clean up stale nftables tables in
iptables mode without depending on the `nft` command.

Signed-off-by: Cory Snider <csnider@mirantis.com>

Cory Snider authored on 2026/06/11 03:08:49
Showing 4 changed files
... ...
@@ -3,9 +3,8 @@
3 3
 package nftabler
4 4
 
5 5
 import (
6
-	"bytes"
7 6
 	"context"
8
-	"os/exec"
7
+	"fmt"
9 8
 
10 9
 	"github.com/containerd/log"
11 10
 	"github.com/moby/moby/v2/daemon/libnetwork/drivers/bridge/internal/firewaller"
... ...
@@ -25,14 +24,10 @@ func Cleanup(ctx context.Context, config firewaller.Config) {
25 25
 }
26 26
 
27 27
 func tryCleanup(ctx context.Context, family nftables.Family, label string) {
28
-	cmd := exec.CommandContext(ctx, "nft", "delete", "table", string(family), dockerTable)
29
-	out, err := cmd.CombinedOutput()
28
+	err := nftables.RunCmd(ctx, fmt.Appendf(nil, "delete table %s %s", family, dockerTable))
30 29
 	if err != nil {
31 30
 		// May not exist ("Error: Could not process rule: No such file or directory")
32
-		log.G(ctx).WithFields(log.Fields{
33
-			"error":  err,
34
-			"output": string(bytes.TrimRight(out, "\n ^")), // remove "^^^^^" added in nft's error message.
35
-		}).Info("Deleting nftables " + label + " rules")
31
+		log.G(ctx).WithError(err).Info("Deleting nftables " + label + " rules")
36 32
 		return
37 33
 	}
38 34
 
... ...
@@ -20,6 +20,10 @@ import (
20 20
 // #include <nftables/libnftables.h>
21 21
 import "C"
22 22
 
23
+func preflight() error {
24
+	return nil
25
+}
26
+
23 27
 type nftCtx C.struct_nft_ctx
24 28
 
25 29
 // Apply calls libnftables to execute the nftables commands in nftCmd.
... ...
@@ -20,17 +20,37 @@ type nftCtx struct{}
20 20
 var lookPathNSEnter = sync.OnceValues(func() (string, error) {
21 21
 	return exec.LookPath("nsenter")
22 22
 })
23
+var lookPathNft = sync.OnceValues(func() (string, error) {
24
+	p, err := exec.LookPath("nft")
25
+	if err != nil {
26
+		log.G(context.Background()).WithError(err).Warnf("Failed to find nft tool")
27
+		return "", fmt.Errorf("failed to find nft tool: %w", err)
28
+	}
29
+	return p, nil
30
+})
31
+
32
+func preflight() error {
33
+	_, err := lookPathNft()
34
+	return err
35
+}
23 36
 
24 37
 func newNftCtx() (*nftCtx, error) {
25
-	return *nftCtx{}, nil
38
+	_, err := lookPathNft()
39
+	if err != nil {
40
+		return nil, err
41
+	}
42
+	return &nftCtx{}, nil
26 43
 }
27 44
 
28 45
 func (*nftCtx) Apply(ctx context.Context, nftCmd []byte) error {
29 46
 	ctx, span := otel.Tracer("").Start(ctx, spanPrefix+".nftApply.exec")
30 47
 	defer span.End()
31 48
 
32
-	cmdPath := nftPath
33
-	cmdArgs := []string{nftPath, "-f", "-"}
49
+	cmdPath, err := lookPathNft()
50
+	if err != nil {
51
+		return err
52
+	}
53
+	cmdArgs := []string{cmdPath, "-f", "-"}
34 54
 	detachedNetNS, err := rootless.DetachedNetNS()
35 55
 	if err != nil {
36 56
 		return fmt.Errorf("could not check for detached netns: %w", err)
... ...
@@ -49,7 +49,6 @@ import (
49 49
 	"errors"
50 50
 	"fmt"
51 51
 	"iter"
52
-	"os/exec"
53 52
 	"runtime"
54 53
 	"slices"
55 54
 	"strconv"
... ...
@@ -64,16 +63,15 @@ import (
64 64
 const spanPrefix = "libnetwork.internal.nftables"
65 65
 
66 66
 var (
67
-	// nftPath is the path of the "nft" tool, set by [Enable] and left empty if the tool
68
-	// is not present - in which case, nftables is disabled.
69
-	nftPath string
67
+	// enabled is set by [Enable].
68
+	enabled bool
70 69
 	// Error returned by Enable if nftables could not be initialised.
71 70
 	nftEnableError error
72 71
 	// incrementalUpdateTempl is a parsed text/template, used to apply incremental updates.
73 72
 	incrementalUpdateTempl *template.Template
74 73
 	// reloadTempl is a parsed text/template, used to apply a whole table.
75 74
 	reloadTempl *template.Template
76
-	// enableOnce is used by [Enable] to avoid checking the path for "nft" more than once.
75
+	// enableOnce is used by [Enable] to avoid parsing the templates more than once.
77 76
 	enableOnce sync.Once
78 77
 )
79 78
 
... ...
@@ -211,10 +209,10 @@ func (t Typeof) VMap() MapTypeof {
211 211
 // Enable tries once to initialise nftables.
212 212
 func Enable() error {
213 213
 	enableOnce.Do(func() {
214
-		path, err := exec.LookPath("nft")
214
+		err := preflight()
215 215
 		if err != nil {
216
-			log.G(context.Background()).WithError(err).Warnf("Failed to find nft tool")
217
-			nftEnableError = fmt.Errorf("failed to find nft tool: %w", err)
216
+			log.G(context.Background()).WithError(err).Warnf("Failed to initialize nftables")
217
+			nftEnableError = err
218 218
 			return
219 219
 		}
220 220
 		if err := parseTemplate(); err != nil {
... ...
@@ -222,24 +220,38 @@ func Enable() error {
222 222
 			nftEnableError = fmt.Errorf("internal error while initialising nftables: %w", err)
223 223
 			return
224 224
 		}
225
-		nftPath = path
225
+		enabled = true
226 226
 	})
227 227
 	return nftEnableError
228 228
 }
229 229
 
230
-// Enabled returns true if the "nft" tool is available and [Enable] has been called.
230
+// Enabled returns true if [Enable] has been called and nftables was initialized successfully.
231 231
 func Enabled() bool {
232
-	return nftPath != ""
232
+	return enabled
233 233
 }
234 234
 
235 235
 // Disable undoes Enable. Intended for unit testing.
236 236
 func Disable() {
237
-	nftPath = ""
237
+	enabled = false
238 238
 	incrementalUpdateTempl = nil
239 239
 	reloadTempl = nil
240 240
 	enableOnce = sync.Once{}
241 241
 }
242 242
 
243
+// RunCmd runs arbitrary nftables ruleset commands, like `nft -f`, irrespective
244
+// of whether nftables is [Enabled].
245
+//
246
+// Most users of this package should use [Table] and [Modifier] to manage their
247
+// nftables ruleset.
248
+func RunCmd(ctx context.Context, nftCmd []byte) error {
249
+	h, err := newNftCtx()
250
+	if err != nil {
251
+		return err
252
+	}
253
+	defer h.Close()
254
+	return h.Apply(ctx, nftCmd)
255
+}
256
+
243 257
 //////////////////////////////
244 258
 // Tables
245 259