mirror of
https://github.com/moby/moby.git
synced 2026-08-03 22:51:03 +00:00
Port the firewall ruleset for encrypted overlay networks to nftables. Maximize compatibility with the most distros by only using nftables features that are widely available. Use the deprecated 'meta secpath exists' expression instead of the more modern 'meta ipsec exists'. Extract the VNI from VXLAN packets using the more widely available '@th' raw payload expressions instead of '@ih' or 'vxlan vni' expressions. Signed-off-by: Cory Snider <csnider@mirantis.com>
717 lines
17 KiB
Go
717 lines
17 KiB
Go
//go:build linux
|
|
|
|
package overlay
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"hash/fnv"
|
|
"net"
|
|
"net/netip"
|
|
"strconv"
|
|
"syscall"
|
|
|
|
"github.com/containerd/log"
|
|
"github.com/moby/moby/v2/daemon/libnetwork/discoverapi"
|
|
"github.com/moby/moby/v2/daemon/libnetwork/drivers/overlay/overlayutils"
|
|
"github.com/moby/moby/v2/daemon/libnetwork/internal/nftables"
|
|
"github.com/moby/moby/v2/daemon/libnetwork/iptables"
|
|
"github.com/moby/moby/v2/daemon/libnetwork/ns"
|
|
"github.com/moby/moby/v2/daemon/libnetwork/types"
|
|
"github.com/vishvananda/netlink"
|
|
)
|
|
|
|
/*
|
|
Encrypted overlay networks use IPsec in transport mode to encrypt and
|
|
authenticate the VXLAN UDP datagrams. This driver implements a bespoke control
|
|
plane which negotiates the security parameters for each peer-to-peer tunnel.
|
|
|
|
IPsec Terminology
|
|
|
|
- ESP: IPSec Encapsulating Security Payload
|
|
- SPI: Security Parameter Index
|
|
- ICV: Integrity Check Value
|
|
- SA: Security Association https://en.wikipedia.org/wiki/IPsec#Security_association
|
|
|
|
|
|
Developer documentation for Linux IPsec is rather sparse online. The following
|
|
slide deck provides a decent overview.
|
|
https://libreswan.org/wiki/images/e/e0/Netdev-0x12-ipsec-flow.pdf
|
|
|
|
The Linux IPsec stack is part of XFRM, the netlink packet transformation
|
|
interface.
|
|
https://man7.org/linux/man-pages/man8/ip-xfrm.8.html
|
|
*/
|
|
|
|
const (
|
|
// Value used to mark outgoing packets which should have our IPsec
|
|
// processing applied. It is also used as a label to identify XFRM
|
|
// states (Security Associations) and policies (Security Policies)
|
|
// programmed by us so we know which ones we can clean up without
|
|
// disrupting other VPN connections on the system.
|
|
mark = 0xD0C4E3
|
|
|
|
pktExpansion = 26 // SPI(4) + SeqN(4) + IV(8) + PadLength(1) + NextHeader(1) + ICV(8)
|
|
)
|
|
|
|
const (
|
|
forward = iota + 1
|
|
reverse
|
|
bidir
|
|
)
|
|
|
|
// Mark value for matching packets which should have our IPsec security policy
|
|
// applied.
|
|
var spMark = netlink.XfrmMark{Value: mark, Mask: 0xffffffff}
|
|
|
|
type key struct {
|
|
value []byte
|
|
tag uint32
|
|
}
|
|
|
|
func (k *key) String() string {
|
|
if k != nil {
|
|
return fmt.Sprintf("(key: %s, tag: 0x%x)", hex.EncodeToString(k.value)[0:5], k.tag)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// Security Parameter Indices for the IPsec flows between local node and a
|
|
// remote peer, which identify the Security Associations (XFRM states) to be
|
|
// applied when encrypting and decrypting packets.
|
|
type spi struct {
|
|
forward int
|
|
reverse int
|
|
}
|
|
|
|
func (s *spi) String() string {
|
|
return fmt.Sprintf("SPI(FWD: 0x%x, REV: 0x%x)", uint32(s.forward), uint32(s.reverse))
|
|
}
|
|
|
|
type encrNode struct {
|
|
spi []spi
|
|
count int
|
|
}
|
|
|
|
// encrMap is a map of node IP addresses to their encryption parameters.
|
|
//
|
|
// Like all Go maps, it is not safe for concurrent use.
|
|
type encrMap map[netip.Addr]encrNode
|
|
|
|
func (e encrMap) String() string {
|
|
b := new(bytes.Buffer)
|
|
for k, v := range e {
|
|
b.WriteString("\n")
|
|
b.WriteString(k.String())
|
|
b.WriteString(":")
|
|
b.WriteString("[")
|
|
for _, s := range v.spi {
|
|
b.WriteString(s.String())
|
|
b.WriteString(",")
|
|
}
|
|
b.WriteString("]")
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
// setupEncryption programs the encryption parameters for secure communication
|
|
// between the local node and a remote node.
|
|
func (d *driver) setupEncryption(remoteIP netip.Addr) error {
|
|
log.G(context.TODO()).Debugf("setupEncryption(%s)", remoteIP)
|
|
|
|
d.encrMu.Lock()
|
|
defer d.encrMu.Unlock()
|
|
if len(d.keys) == 0 {
|
|
return types.ForbiddenErrorf("encryption key is not present")
|
|
}
|
|
d.mu.Lock()
|
|
localIP, advIP := d.bindAddress, d.advertiseAddress
|
|
d.mu.Unlock()
|
|
log.G(context.TODO()).Debugf("Programming encryption between %s and %s", localIP, remoteIP)
|
|
|
|
indices := make([]spi, 0, len(d.keys))
|
|
|
|
for i, k := range d.keys {
|
|
spis := spi{buildSPI(advIP, remoteIP, k.tag), buildSPI(remoteIP, advIP, k.tag)}
|
|
dir := reverse
|
|
if i == 0 {
|
|
dir = bidir
|
|
}
|
|
fSA, rSA, err := programSA(localIP.AsSlice(), remoteIP.AsSlice(), spis, k, dir, true)
|
|
if err != nil {
|
|
log.G(context.TODO()).Warn(err)
|
|
}
|
|
indices = append(indices, spis)
|
|
if i != 0 {
|
|
continue
|
|
}
|
|
err = programSP(fSA, rSA, true)
|
|
if err != nil {
|
|
log.G(context.TODO()).Warn(err)
|
|
}
|
|
}
|
|
|
|
node := d.secMap[remoteIP]
|
|
node.spi = indices
|
|
node.count++
|
|
d.secMap[remoteIP] = node
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *driver) removeEncryption(remoteIP netip.Addr) error {
|
|
log.G(context.TODO()).Debugf("removeEncryption(%s)", remoteIP)
|
|
|
|
d.encrMu.Lock()
|
|
defer d.encrMu.Unlock()
|
|
|
|
var spi []spi
|
|
node := d.secMap[remoteIP]
|
|
if node.count == 1 {
|
|
delete(d.secMap, remoteIP)
|
|
spi = node.spi
|
|
} else {
|
|
node.count--
|
|
d.secMap[remoteIP] = node
|
|
}
|
|
|
|
for i, idxs := range spi {
|
|
dir := reverse
|
|
if i == 0 {
|
|
dir = bidir
|
|
}
|
|
fSA, rSA, err := programSA(d.bindAddress.AsSlice(), remoteIP.AsSlice(), idxs, nil, dir, false)
|
|
if err != nil {
|
|
log.G(context.TODO()).Warn(err)
|
|
}
|
|
if i != 0 {
|
|
continue
|
|
}
|
|
err = programSP(fSA, rSA, false)
|
|
if err != nil {
|
|
log.G(context.TODO()).Warn(err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *driver) transportIPTable() (*iptables.IPTable, error) {
|
|
v6, err := d.isIPv6Transport()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
version := iptables.IPv4
|
|
if v6 {
|
|
version = iptables.IPv6
|
|
}
|
|
return iptables.GetIptable(version), nil
|
|
}
|
|
|
|
func (d *driver) programMangle(vni uint32, add bool) error {
|
|
var (
|
|
m = strconv.FormatUint(mark, 10)
|
|
chain = "OUTPUT"
|
|
rule = append(matchVXLAN(overlayutils.VXLANUDPPort(), vni), "-j", "MARK", "--set-mark", m)
|
|
a = iptables.Append
|
|
action = "install"
|
|
)
|
|
|
|
iptable, err := d.transportIPTable()
|
|
if err != nil {
|
|
// Fail closed if unsure. Better safe than cleartext.
|
|
return err
|
|
}
|
|
|
|
if !add {
|
|
a = iptables.Delete
|
|
action = "remove"
|
|
}
|
|
|
|
if err := iptable.ProgramRule(iptables.Mangle, chain, a, rule); err != nil {
|
|
return fmt.Errorf("could not %s mangle rule: %w", action, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *driver) programInput(vni uint32, add bool) error {
|
|
var (
|
|
plainVxlan = matchVXLAN(overlayutils.VXLANUDPPort(), vni)
|
|
chain = "INPUT"
|
|
msg = "add"
|
|
)
|
|
|
|
rule := func(policy, jump string) []string {
|
|
args := append([]string{"-m", "policy", "--dir", "in", "--pol", policy}, plainVxlan...)
|
|
return append(args, "-j", jump)
|
|
}
|
|
|
|
iptable, err := d.transportIPTable()
|
|
if err != nil {
|
|
// Fail closed if unsure. Better safe than cleartext.
|
|
return err
|
|
}
|
|
|
|
if !add {
|
|
msg = "remove"
|
|
}
|
|
|
|
action := func(a iptables.Action) iptables.Action {
|
|
if !add {
|
|
return iptables.Delete
|
|
}
|
|
return a
|
|
}
|
|
|
|
// Drop incoming VXLAN datagrams for the VNI which were received in cleartext.
|
|
// Insert at the top of the chain so the packets are dropped even if an
|
|
// administrator-configured rule exists which would otherwise unconditionally
|
|
// accept incoming VXLAN traffic.
|
|
if err := iptable.ProgramRule(iptables.Filter, chain, action(iptables.Insert), rule("none", "DROP")); err != nil {
|
|
return fmt.Errorf("could not %s input drop rule: %w", msg, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *driver) programOverlayEncryptionFirewall(ctx context.Context, vni uint32, encrypted bool) error {
|
|
if nftables.Enabled() {
|
|
return d.programOverlayEncVNINft(ctx, vni, encrypted)
|
|
}
|
|
|
|
mangleErr := d.programMangle(vni, encrypted)
|
|
if mangleErr != nil && encrypted {
|
|
return mangleErr
|
|
}
|
|
err := d.programInput(vni, encrypted)
|
|
if err != nil && encrypted {
|
|
return errors.Join(err, d.programMangle(vni, false))
|
|
}
|
|
return errors.Join(mangleErr, err)
|
|
}
|
|
|
|
func programSA(localIP, remoteIP net.IP, spi spi, k *key, dir int, add bool) (fSA *netlink.XfrmState, rSA *netlink.XfrmState, lastErr error) {
|
|
var (
|
|
action = "Removing"
|
|
xfrmProgram = ns.NlHandle().XfrmStateDel
|
|
)
|
|
|
|
if add {
|
|
action = "Adding"
|
|
xfrmProgram = ns.NlHandle().XfrmStateAdd
|
|
}
|
|
|
|
if dir&reverse > 0 {
|
|
rSA = &netlink.XfrmState{
|
|
Src: remoteIP,
|
|
Dst: localIP,
|
|
Proto: netlink.XFRM_PROTO_ESP,
|
|
Spi: spi.reverse,
|
|
Mode: netlink.XFRM_MODE_TRANSPORT,
|
|
Reqid: mark,
|
|
}
|
|
if add {
|
|
rSA.Aead = buildAeadAlgo(k, spi.reverse)
|
|
}
|
|
|
|
exists, err := saExists(rSA)
|
|
if err != nil {
|
|
lastErr = err
|
|
exists = !add
|
|
}
|
|
|
|
if add != exists {
|
|
log.G(context.TODO()).Debugf("%s: rSA{%s}", action, rSA)
|
|
if err := xfrmProgram(rSA); err != nil {
|
|
log.G(context.TODO()).Warnf("Failed %s rSA{%s}: %v", action, rSA, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
if dir&forward > 0 {
|
|
fSA = &netlink.XfrmState{
|
|
Src: localIP,
|
|
Dst: remoteIP,
|
|
Proto: netlink.XFRM_PROTO_ESP,
|
|
Spi: spi.forward,
|
|
Mode: netlink.XFRM_MODE_TRANSPORT,
|
|
Reqid: mark,
|
|
}
|
|
if add {
|
|
fSA.Aead = buildAeadAlgo(k, spi.forward)
|
|
}
|
|
|
|
exists, err := saExists(fSA)
|
|
if err != nil {
|
|
lastErr = err
|
|
exists = !add
|
|
}
|
|
|
|
if add != exists {
|
|
log.G(context.TODO()).Debugf("%s fSA{%s}", action, fSA)
|
|
if err := xfrmProgram(fSA); err != nil {
|
|
log.G(context.TODO()).Warnf("Failed %s fSA{%s}: %v.", action, fSA, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
return fSA, rSA, lastErr
|
|
}
|
|
|
|
// getMinimalIP returns the address in its shortest form
|
|
// If ip contains an IPv4-mapped IPv6 address, the 4-octet form of the IPv4 address will be returned.
|
|
// Otherwise ip is returned unchanged.
|
|
func getMinimalIP(ip net.IP) net.IP {
|
|
if ip != nil && ip.To4() != nil {
|
|
return ip.To4()
|
|
}
|
|
return ip
|
|
}
|
|
|
|
func programSP(fSA *netlink.XfrmState, rSA *netlink.XfrmState, add bool) error {
|
|
action := "Removing"
|
|
xfrmProgram := ns.NlHandle().XfrmPolicyDel
|
|
if add {
|
|
action = "Adding"
|
|
xfrmProgram = ns.NlHandle().XfrmPolicyAdd
|
|
}
|
|
|
|
// Create a congruent cidr
|
|
s := getMinimalIP(fSA.Src)
|
|
d := getMinimalIP(fSA.Dst)
|
|
fullMask := net.CIDRMask(8*len(s), 8*len(s))
|
|
|
|
fPol := &netlink.XfrmPolicy{
|
|
Src: &net.IPNet{IP: s, Mask: fullMask},
|
|
Dst: &net.IPNet{IP: d, Mask: fullMask},
|
|
Dir: netlink.XFRM_DIR_OUT,
|
|
Proto: syscall.IPPROTO_UDP,
|
|
DstPort: int(overlayutils.VXLANUDPPort()),
|
|
Mark: &spMark,
|
|
Tmpls: []netlink.XfrmPolicyTmpl{
|
|
{
|
|
Src: fSA.Src,
|
|
Dst: fSA.Dst,
|
|
Proto: netlink.XFRM_PROTO_ESP,
|
|
Mode: netlink.XFRM_MODE_TRANSPORT,
|
|
Spi: fSA.Spi,
|
|
Reqid: mark,
|
|
},
|
|
},
|
|
}
|
|
|
|
exists, err := spExists(fPol)
|
|
if err != nil {
|
|
exists = !add
|
|
}
|
|
|
|
if add != exists {
|
|
log.G(context.TODO()).Debugf("%s fSP{%s}", action, fPol)
|
|
if err := xfrmProgram(fPol); err != nil {
|
|
log.G(context.TODO()).Warnf("%s fSP{%s}: %v", action, fPol, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func saExists(sa *netlink.XfrmState) (bool, error) {
|
|
_, err := ns.NlHandle().XfrmStateGet(sa)
|
|
switch {
|
|
case err == nil:
|
|
return true, nil
|
|
case errors.Is(err, syscall.ESRCH):
|
|
return false, nil
|
|
default:
|
|
err = fmt.Errorf("Error while checking for SA existence: %v", err)
|
|
log.G(context.TODO()).Warn(err)
|
|
return false, err
|
|
}
|
|
}
|
|
|
|
func spExists(sp *netlink.XfrmPolicy) (bool, error) {
|
|
_, err := ns.NlHandle().XfrmPolicyGet(sp)
|
|
switch {
|
|
case err == nil:
|
|
return true, nil
|
|
case errors.Is(err, syscall.ENOENT):
|
|
return false, nil
|
|
default:
|
|
err = fmt.Errorf("Error while checking for SP existence: %v", err)
|
|
log.G(context.TODO()).Warn(err)
|
|
return false, err
|
|
}
|
|
}
|
|
|
|
func buildSPI(src, dst netip.Addr, st uint32) int {
|
|
h := fnv.New32a()
|
|
v := src.As16()
|
|
h.Write(v[:])
|
|
binary.Write(h, binary.BigEndian, st)
|
|
v = dst.As16()
|
|
h.Write(v[:])
|
|
return int(h.Sum32())
|
|
}
|
|
|
|
func buildAeadAlgo(k *key, s int) *netlink.XfrmStateAlgo {
|
|
salt := make([]byte, 4)
|
|
binary.BigEndian.PutUint32(salt, uint32(s))
|
|
return &netlink.XfrmStateAlgo{
|
|
Name: "rfc4106(gcm(aes))",
|
|
Key: append(k.value, salt...),
|
|
ICVLen: 64,
|
|
}
|
|
}
|
|
|
|
func (d *driver) setKeys(ctx context.Context, encrData discoverapi.DriverEncryptionConfig) error {
|
|
keys := make([]*key, 0, len(encrData.Keys))
|
|
for i := 0; i < len(encrData.Keys); i++ {
|
|
keys = append(keys, &key{
|
|
value: encrData.Keys[i],
|
|
tag: uint32(encrData.Tags[i]),
|
|
})
|
|
}
|
|
d.encrMu.Lock()
|
|
defer d.encrMu.Unlock()
|
|
|
|
// Remove any stale policy, state
|
|
clearEncryptionStates()
|
|
// Accept the encryption keys and clear any stale encryption map
|
|
d.secMap = encrMap{}
|
|
d.keys = keys
|
|
|
|
log.G(ctx).WithFields(log.Fields{
|
|
"driver": "overlay",
|
|
"keys": d.keys,
|
|
}).Debug("Set initial encryption keys")
|
|
return nil
|
|
}
|
|
|
|
// updateKeys allows to add a new key and/or change the primary key and/or prune an existing key
|
|
// The primary key is the key used in transmission and will go in first position in the list.
|
|
func (d *driver) updateKeys(ctx context.Context, encrData discoverapi.DriverEncryptionUpdate) error {
|
|
var newKey, primaryKey, pruneKey *key
|
|
if encrData.Key != nil {
|
|
newKey = &key{
|
|
value: encrData.Key,
|
|
tag: uint32(encrData.Tag),
|
|
}
|
|
}
|
|
if encrData.Primary != nil {
|
|
primaryKey = &key{
|
|
value: encrData.Primary,
|
|
tag: uint32(encrData.PrimaryTag),
|
|
}
|
|
}
|
|
if encrData.Prune != nil {
|
|
pruneKey = &key{
|
|
value: encrData.Prune,
|
|
tag: uint32(encrData.PruneTag),
|
|
}
|
|
}
|
|
|
|
d.encrMu.Lock()
|
|
defer d.encrMu.Unlock()
|
|
|
|
log.G(ctx).WithFields(log.Fields{
|
|
"driver": "overlay",
|
|
"current": d.keys,
|
|
"new": newKey,
|
|
"primary": primaryKey,
|
|
"prune": pruneKey,
|
|
}).Debug("Updating encryption keys")
|
|
|
|
var (
|
|
newIdx = -1
|
|
priIdx = -1
|
|
delIdx = -1
|
|
lIP = d.bindAddress
|
|
aIP = d.advertiseAddress
|
|
)
|
|
|
|
// add new
|
|
if newKey != nil {
|
|
d.keys = append(d.keys, newKey)
|
|
newIdx += len(d.keys)
|
|
}
|
|
for i, k := range d.keys {
|
|
if primaryKey != nil && k.tag == primaryKey.tag {
|
|
priIdx = i
|
|
}
|
|
if pruneKey != nil && k.tag == pruneKey.tag {
|
|
delIdx = i
|
|
}
|
|
}
|
|
|
|
if (newKey != nil && newIdx == -1) ||
|
|
(primaryKey != nil && priIdx == -1) ||
|
|
(pruneKey != nil && delIdx == -1) {
|
|
return types.InvalidParameterErrorf("cannot find proper key indices while processing key update:"+
|
|
"(newIdx,priIdx,delIdx):(%d, %d, %d)", newIdx, priIdx, delIdx)
|
|
}
|
|
|
|
if priIdx != -1 && priIdx == delIdx {
|
|
return types.InvalidParameterErrorf("attempting to both make a key (index %d) primary and delete it", priIdx)
|
|
}
|
|
|
|
for rIP, node := range d.secMap {
|
|
idxs := updateNodeKey(lIP, aIP, rIP, node.spi, d.keys, newIdx, priIdx, delIdx)
|
|
if idxs != nil {
|
|
d.secMap[rIP] = encrNode{idxs, node.count}
|
|
}
|
|
}
|
|
|
|
// swap primary
|
|
if priIdx != -1 {
|
|
d.keys[0], d.keys[priIdx] = d.keys[priIdx], d.keys[0]
|
|
}
|
|
// prune
|
|
if delIdx != -1 {
|
|
if delIdx == 0 {
|
|
delIdx = priIdx
|
|
}
|
|
d.keys = append(d.keys[:delIdx], d.keys[delIdx+1:]...)
|
|
}
|
|
|
|
log.G(ctx).WithFields(log.Fields{
|
|
"driver": "overlay",
|
|
"keys": d.keys,
|
|
}).Debug("Updated encryption keys")
|
|
return nil
|
|
}
|
|
|
|
/********************************************************
|
|
* Steady state: rSA0, rSA1, rSA2, fSA1, fSP1
|
|
* Rotation --> -rSA0, +rSA3, +fSA2, +fSP2/-fSP1, -fSA1
|
|
* Steady state: rSA1, rSA2, rSA3, fSA2, fSP2
|
|
*********************************************************/
|
|
|
|
// Spis and keys are sorted in such away the one in position 0 is the primary
|
|
func updateNodeKey(lIP, aIP, rIP netip.Addr, idxs []spi, curKeys []*key, newIdx, priIdx, delIdx int) []spi {
|
|
log.G(context.TODO()).Debugf("Updating keys for node: %s (%d,%d,%d)", rIP, newIdx, priIdx, delIdx)
|
|
|
|
spis := idxs
|
|
log.G(context.TODO()).Debugf("Current: %v", spis)
|
|
|
|
// add new
|
|
if newIdx != -1 {
|
|
spis = append(spis, spi{
|
|
forward: buildSPI(aIP, rIP, curKeys[newIdx].tag),
|
|
reverse: buildSPI(rIP, aIP, curKeys[newIdx].tag),
|
|
})
|
|
}
|
|
|
|
if delIdx != -1 {
|
|
// -rSA0
|
|
programSA(lIP.AsSlice(), rIP.AsSlice(), spis[delIdx], nil, reverse, false)
|
|
}
|
|
|
|
if newIdx > -1 {
|
|
// +rSA2
|
|
programSA(lIP.AsSlice(), rIP.AsSlice(), spis[newIdx], curKeys[newIdx], reverse, true)
|
|
}
|
|
|
|
if priIdx > 0 {
|
|
// +fSA2
|
|
fSA2, _, _ := programSA(lIP.AsSlice(), rIP.AsSlice(), spis[priIdx], curKeys[priIdx], forward, true)
|
|
|
|
// +fSP2, -fSP1
|
|
s := getMinimalIP(fSA2.Src)
|
|
d := getMinimalIP(fSA2.Dst)
|
|
fullMask := net.CIDRMask(8*len(s), 8*len(s))
|
|
|
|
fSP1 := &netlink.XfrmPolicy{
|
|
Src: &net.IPNet{IP: s, Mask: fullMask},
|
|
Dst: &net.IPNet{IP: d, Mask: fullMask},
|
|
Dir: netlink.XFRM_DIR_OUT,
|
|
Proto: syscall.IPPROTO_UDP,
|
|
DstPort: int(overlayutils.VXLANUDPPort()),
|
|
Mark: &spMark,
|
|
Tmpls: []netlink.XfrmPolicyTmpl{
|
|
{
|
|
Src: fSA2.Src,
|
|
Dst: fSA2.Dst,
|
|
Proto: netlink.XFRM_PROTO_ESP,
|
|
Mode: netlink.XFRM_MODE_TRANSPORT,
|
|
Spi: fSA2.Spi,
|
|
Reqid: mark,
|
|
},
|
|
},
|
|
}
|
|
log.G(context.TODO()).Debugf("Updating fSP{%s}", fSP1)
|
|
if err := ns.NlHandle().XfrmPolicyUpdate(fSP1); err != nil {
|
|
log.G(context.TODO()).Warnf("Failed to update fSP{%s}: %v", fSP1, err)
|
|
}
|
|
|
|
// -fSA1
|
|
programSA(lIP.AsSlice(), rIP.AsSlice(), spis[0], nil, forward, false)
|
|
}
|
|
|
|
// swap
|
|
if priIdx > 0 {
|
|
swp := spis[0]
|
|
spis[0] = spis[priIdx]
|
|
spis[priIdx] = swp
|
|
}
|
|
// prune
|
|
if delIdx != -1 {
|
|
if delIdx == 0 {
|
|
delIdx = priIdx
|
|
}
|
|
spis = append(spis[:delIdx], spis[delIdx+1:]...)
|
|
}
|
|
|
|
log.G(context.TODO()).Debugf("Updated: %v", spis)
|
|
|
|
return spis
|
|
}
|
|
|
|
func (n *network) maxMTU() int {
|
|
mtu := 1500
|
|
if n.mtu != 0 {
|
|
mtu = n.mtu
|
|
}
|
|
mtu -= vxlanEncap
|
|
if n.secure {
|
|
// In case of encryption account for the
|
|
// esp packet expansion and padding
|
|
mtu -= pktExpansion
|
|
mtu -= (mtu % 4)
|
|
}
|
|
return mtu
|
|
}
|
|
|
|
func clearEncryptionStates() {
|
|
nlh := ns.NlHandle()
|
|
spList, err := nlh.XfrmPolicyList(netlink.FAMILY_ALL)
|
|
if err != nil {
|
|
log.G(context.TODO()).Warnf("Failed to retrieve SP list for cleanup: %v", err)
|
|
}
|
|
saList, err := nlh.XfrmStateList(netlink.FAMILY_ALL)
|
|
if err != nil {
|
|
log.G(context.TODO()).Warnf("Failed to retrieve SA list for cleanup: %v", err)
|
|
}
|
|
for _, sp := range spList {
|
|
if sp.Mark != nil && sp.Mark.Value == spMark.Value {
|
|
if err := nlh.XfrmPolicyDel(&sp); err != nil {
|
|
log.G(context.TODO()).Warnf("Failed to delete stale SP %s: %v", sp, err)
|
|
continue
|
|
}
|
|
log.G(context.TODO()).Debugf("Removed stale SP: %s", sp)
|
|
}
|
|
}
|
|
for _, sa := range saList {
|
|
if sa.Reqid == mark {
|
|
if err := nlh.XfrmStateDel(&sa); err != nil {
|
|
log.G(context.TODO()).Warnf("Failed to delete stale SA %s: %v", sa, err)
|
|
continue
|
|
}
|
|
log.G(context.TODO()).Debugf("Removed stale SA: %s", sa)
|
|
}
|
|
}
|
|
}
|