mirror of
https://github.com/moby/moby.git
synced 2026-08-03 22:51:03 +00:00
Table.Close() had a value receiver, so setting t.t = nil only modified the callee's copy of the handle. The caller's Table was left looking valid: t, _ := nftables.NewTable(...) t.Close() t.IsValid() // true Worse, Close() only dropped the nftables handle, and nftApply() opens a new one whenever it finds none. So Apply() and Reload() on a closed table silently reopened a handle and carried on updating the ruleset. At the root of it, a Table looked like a plain value but behaved like a reference, and nothing stopped it from being copied. So embed table in Table by value and hand out *Table instead. The Table/table split is still needed - table's fields have to be exported for text/template - but reference semantics are now visible at every call site, and they're enforced: because table contains a sync.Mutex, "go vet" reports both a copy of a Table and a method or function that takes one by value, so the shape of this bug is no longer expressible. Close() therefore can't invalidate the table by clearing a pointer, it has to record the state. Add a closed flag, and refuse to open a new nftables handle for a table that's been closed. Apply() checked neither for a closed table nor for a nil *Table, which would have panicked. It now reports an error, checking the closed state with applyLock held so that it can't race with Close(), and before the in-memory table is touched so that a rejected update isn't recorded as applied. The invalid table is now a nil *Table rather than a zero-value Table, which also removes the need for consumers to return an empty Table alongside an error. That made it obvious that the nftabler was leaking the table it had just created when it gave up on setting up IPv6, so close it. Signed-off-by: Cory Snider <csnider@mirantis.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
143 lines
3.6 KiB
Go
143 lines
3.6 KiB
Go
//go:build linux
|
|
|
|
package overlay
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
|
|
"github.com/containerd/log"
|
|
"github.com/moby/moby/v2/daemon/libnetwork/drivers/overlay/overlayutils"
|
|
"github.com/moby/moby/v2/daemon/libnetwork/internal/nftables"
|
|
)
|
|
|
|
const (
|
|
nftOverlayTable = "docker-overlay"
|
|
nftEncOutChainName = "enc-out"
|
|
nftEncInChainName = "enc-in"
|
|
nftEncVNSetName = "encrypted-vnis"
|
|
nftEncVNIExpr = "@th,96,24"
|
|
)
|
|
|
|
// ensureOverlayEncNftTable returns the overlay encryption nft table, running one-time setup on first use.
|
|
func (d *driver) ensureOverlayEncNftTable(ctx context.Context) (*nftables.Table, error) {
|
|
d.overlayEncNftInitMu.Lock()
|
|
defer d.overlayEncNftInitMu.Unlock()
|
|
if d.overlayEncNftTable.IsValid() {
|
|
return d.overlayEncNftTable, nil
|
|
}
|
|
|
|
v6, err := d.isIPv6Transport()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fam := nftables.IPv4
|
|
if v6 {
|
|
fam = nftables.IPv6
|
|
}
|
|
t, err := nftables.NewTable(fam, nftOverlayTable)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
tm := nftables.Modifier{}
|
|
tm.Create(nftables.Set{
|
|
Name: nftEncVNSetName,
|
|
ElementType: nftables.Typeof(nftEncVNIExpr),
|
|
})
|
|
tm.Create(nftables.BaseChain{
|
|
Name: nftEncOutChainName,
|
|
ChainType: nftables.BaseChainTypeRoute,
|
|
Hook: nftables.BaseChainHookOutput,
|
|
Priority: nftables.BaseChainPriorityMangle,
|
|
Policy: nftables.BaseChainPolicyAccept,
|
|
})
|
|
tm.Create(nftables.BaseChain{
|
|
Name: nftEncInChainName,
|
|
ChainType: nftables.BaseChainTypeFilter,
|
|
Hook: nftables.BaseChainHookInput,
|
|
Priority: nftables.BaseChainPriorityRaw,
|
|
Policy: nftables.BaseChainPolicyAccept,
|
|
})
|
|
|
|
port := strconv.FormatUint(uint64(overlayutils.VXLANUDPPort()), 10)
|
|
tm.Create(nftables.Rule{
|
|
Chain: nftEncOutChainName,
|
|
Rule: []string{
|
|
"udp dport", port,
|
|
nftEncVNIExpr,
|
|
"@" + nftEncVNSetName,
|
|
"counter",
|
|
"meta mark set", fmt.Sprintf("0x%x", mark),
|
|
},
|
|
})
|
|
tm.Create(nftables.Rule{
|
|
Chain: nftEncInChainName,
|
|
Rule: []string{
|
|
"meta secpath missing",
|
|
"udp dport", port,
|
|
nftEncVNIExpr,
|
|
"@" + nftEncVNSetName,
|
|
"counter",
|
|
"drop",
|
|
},
|
|
})
|
|
|
|
if err := t.Apply(ctx, tm); err != nil {
|
|
_ = t.Close()
|
|
return nil, err
|
|
}
|
|
|
|
d.overlayEncNftTable = t
|
|
return t, nil
|
|
}
|
|
|
|
func (d *driver) programOverlayEncVNINft(ctx context.Context, vni uint32, encrypted bool) error {
|
|
// Attempt to clean up stale iptables rules from an old incarnation of
|
|
// the daemon which could clash with the nftables ruleset.
|
|
cleanupErr := errors.Join(d.programInput(vni, false), d.programMangle(vni, false))
|
|
if cleanupErr != nil {
|
|
log.G(ctx).WithError(cleanupErr).Infof("Failed to clean up stale iptables rules for VNI %d", vni)
|
|
}
|
|
|
|
t, err := d.ensureOverlayEncNftTable(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
tm := nftables.Modifier{}
|
|
se := nftables.SetElement{
|
|
SetName: nftEncVNSetName,
|
|
Element: fmt.Sprintf("0x%06x", vni&0xffffff),
|
|
Idempotent: true,
|
|
}
|
|
if encrypted {
|
|
tm.Create(se)
|
|
} else {
|
|
tm.Delete(se)
|
|
}
|
|
return t.Apply(ctx, tm)
|
|
}
|
|
|
|
// cleanupNft deletes all nftables rules created by the driver. It's intended to
|
|
// be used during startup, to clean up rules created by an old incarnation of
|
|
// the daemon after switching to a different firewall backend.
|
|
func (d *driver) cleanupNft(ctx context.Context) {
|
|
v6, err := d.isIPv6Transport()
|
|
if err != nil {
|
|
log.G(ctx).WithError(err).Error("Deleting overlay encryption nftables rules")
|
|
return
|
|
}
|
|
fam := nftables.IPv4
|
|
if v6 {
|
|
fam = nftables.IPv6
|
|
}
|
|
if err := nftables.RunCmd(ctx, fmt.Appendf(nil, "delete table %s %s", fam, nftOverlayTable)); err != nil {
|
|
log.G(ctx).WithError(err).Info("Deleting overlay encryption nftables rules")
|
|
return
|
|
}
|
|
log.G(ctx).Info("Deleted overlay encryption nftables rules")
|
|
}
|