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>
| ... | ... |
@@ -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,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 |
|