//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 nftables.Table{}, err
}
fam := nftables.IPv4
if v6 {
fam = nftables.IPv6
}
t, err := nftables.NewTable(fam, nftOverlayTable)
if err != nil {
return nftables.Table{}, 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 nftables.Table{}, 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")
}