github.com/cilium/cilium@v1.16.2/pkg/datapath/linux/ipsec.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package linux
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"log/slog"
    10  	"net"
    11  	"os"
    12  
    13  	"github.com/vishvananda/netlink"
    14  	"golang.org/x/sys/unix"
    15  
    16  	"github.com/cilium/cilium/pkg/datapath/linux/ipsec"
    17  	"github.com/cilium/cilium/pkg/datapath/linux/linux_defaults"
    18  	"github.com/cilium/cilium/pkg/datapath/linux/route"
    19  	"github.com/cilium/cilium/pkg/logging/logfields"
    20  	"github.com/cilium/cilium/pkg/metrics"
    21  	nodeTypes "github.com/cilium/cilium/pkg/node/types"
    22  	"github.com/cilium/cilium/pkg/option"
    23  )
    24  
    25  var (
    26  	exactMatchMask = net.IPv4Mask(255, 255, 255, 255)
    27  )
    28  
    29  // getDefaultEncryptionInterface() is needed to find the interface used when
    30  // populating neighbor table and doing arpRequest. For most configurations
    31  // there is only a single interface so choosing [0] works by choosing the only
    32  // interface. However EKS, uses multiple interfaces, but fortunately for us
    33  // in EKS any interface would work so pick the [0] index here as well.
    34  func (n *linuxNodeHandler) getDefaultEncryptionInterface() string {
    35  	if option.Config.TunnelingEnabled() {
    36  		return n.datapathConfig.TunnelDevice
    37  	}
    38  	devices := n.nodeConfig.Devices
    39  	if len(devices) > 0 {
    40  		return devices[0].Name
    41  	}
    42  	if len(option.Config.EncryptInterface) > 0 {
    43  		return option.Config.EncryptInterface[0]
    44  	}
    45  	return ""
    46  }
    47  
    48  func (n *linuxNodeHandler) getLinkLocalIP(family int) (net.IP, error) {
    49  	iface := n.getDefaultEncryptionInterface()
    50  	link, err := netlink.LinkByName(iface)
    51  	if err != nil {
    52  		return nil, err
    53  	}
    54  	addr, err := netlink.AddrList(link, family)
    55  	if err != nil {
    56  		return nil, err
    57  	}
    58  	return addr[0].IPNet.IP, nil
    59  }
    60  
    61  func (n *linuxNodeHandler) getV4LinkLocalIP() (net.IP, error) {
    62  	return n.getLinkLocalIP(netlink.FAMILY_V4)
    63  }
    64  
    65  func (n *linuxNodeHandler) getV6LinkLocalIP() (net.IP, error) {
    66  	return n.getLinkLocalIP(netlink.FAMILY_V6)
    67  }
    68  
    69  func upsertIPsecLog(log *slog.Logger, err error, spec string, loc, rem *net.IPNet, spi uint8, nodeID uint16) error {
    70  	scopedLog := log.With(
    71  		logfields.Reason, spec,
    72  		logfields.SPI, spi,
    73  		logfields.LocalIP, loc,
    74  		logfields.RemoteIP, rem,
    75  		logfields.NodeID, fmt.Sprintf("0x%x", nodeID),
    76  	)
    77  	if err != nil {
    78  		scopedLog.Error("IPsec enable failed", logfields.Error, err)
    79  		return fmt.Errorf("failed to enable ipsec with %s using local IP %s, rem %s, spi %d: %w",
    80  			spec,
    81  			loc.String(),
    82  			rem.String(),
    83  			spi, err)
    84  	} else {
    85  		scopedLog.Debug("IPsec enable succeeded")
    86  	}
    87  	return nil
    88  }
    89  
    90  func (n *linuxNodeHandler) registerIpsecMetricOnce() {
    91  	n.ipsecMetricOnce.Do(func() {
    92  		if err := metrics.Register(n.ipsecMetricCollector); err != nil {
    93  			n.log.Error("IPSec metrics registration failed. No metrics will be reported!",
    94  				logfields.Error, err,
    95  			)
    96  		}
    97  	})
    98  }
    99  
   100  func (n *linuxNodeHandler) enableSubnetIPsec(v4CIDR, v6CIDR []*net.IPNet) error {
   101  	errs := n.replaceHostRules()
   102  	for _, cidr := range v4CIDR {
   103  		if !option.Config.EnableEndpointRoutes {
   104  			if err := n.replaceNodeIPSecInRoute(cidr); err != nil {
   105  				errs = errors.Join(errs, fmt.Errorf("failed to replace ipsec IN (%q): %w", cidr.IP, err))
   106  			}
   107  		}
   108  		if err := n.replaceNodeIPSecOutRoute(cidr); err != nil {
   109  			errs = errors.Join(errs, fmt.Errorf("failed to replace ipsec OUT (%q): %w", cidr.IP, err))
   110  		}
   111  	}
   112  
   113  	for _, cidr := range v6CIDR {
   114  		if err := n.replaceNodeIPSecInRoute(cidr); err != nil {
   115  			errs = errors.Join(errs, fmt.Errorf("failed to replace ipsec IN (%q): %w", cidr.IP, err))
   116  		}
   117  
   118  		if err := n.replaceNodeIPSecOutRoute(cidr); err != nil {
   119  			errs = errors.Join(errs, fmt.Errorf("failed to replace ipsec OUT (%q): %w", cidr.IP, err))
   120  		}
   121  	}
   122  	return errs
   123  }
   124  
   125  func (n *linuxNodeHandler) enableIPsec(oldNode, newNode *nodeTypes.Node, nodeID uint16) error {
   126  	var errs error
   127  	if newNode.IsLocal() {
   128  		if err := n.replaceHostRules(); err != nil {
   129  			errs = fmt.Errorf("failed to replace host rules: %w", err)
   130  		}
   131  	}
   132  
   133  	if oldNode != nil && oldNode.BootID != newNode.BootID {
   134  		n.ipsecUpdateNeeded[newNode.Identity()] = true
   135  	}
   136  	_, updateExisting := n.ipsecUpdateNeeded[newNode.Identity()]
   137  	statesUpdated := true
   138  
   139  	// In endpoint routes mode we use the stack to route packets after
   140  	// the packet is decrypted so set skb->mark to zero from XFRM stack
   141  	// to avoid confusion in netfilters and conntrack that may be using
   142  	// the mark fields. This uses XFRM_OUTPUT_MARK added in 4.14 kernels.
   143  	zeroMark := option.Config.EnableEndpointRoutes
   144  
   145  	if n.nodeConfig.EnableIPv4 && (newNode.IPv4AllocCIDR != nil || n.subnetEncryption()) {
   146  		update, err := n.enableIPsecIPv4(newNode, nodeID, zeroMark, updateExisting)
   147  		statesUpdated = statesUpdated && update
   148  		errs = errors.Join(errs, err)
   149  	}
   150  	if n.nodeConfig.EnableIPv6 && (newNode.IPv6AllocCIDR != nil || n.subnetEncryption()) {
   151  		update, err := n.enableIPsecIPv6(newNode, nodeID, zeroMark, updateExisting)
   152  		statesUpdated = statesUpdated && update
   153  		errs = errors.Join(errs, err)
   154  	}
   155  
   156  	if updateExisting && statesUpdated {
   157  		delete(n.ipsecUpdateNeeded, newNode.Identity())
   158  	}
   159  
   160  	return errs
   161  }
   162  
   163  func (n *linuxNodeHandler) enableIPsecIPv4(newNode *nodeTypes.Node, nodeID uint16, zeroMark, updateExisting bool) (bool, error) {
   164  	statesUpdated := true
   165  	var spi uint8
   166  	var errs error
   167  
   168  	wildcardIP := net.ParseIP(wildcardIPv4)
   169  	wildcardCIDR := &net.IPNet{IP: wildcardIP, Mask: net.IPv4Mask(0, 0, 0, 0)}
   170  
   171  	err := ipsec.IPsecDefaultDropPolicy(n.log, false)
   172  	errs = errors.Join(errs, upsertIPsecLog(n.log, err, "default-drop IPv4", wildcardCIDR, wildcardCIDR, spi, 0))
   173  
   174  	if newNode.IsLocal() {
   175  		if n.subnetEncryption() {
   176  			// FIXME: Remove the following four lines in Cilium v1.16
   177  			if localCIDR := n.nodeConfig.AllocCIDRIPv4; localCIDR != nil {
   178  				// This removes a bogus route that Cilium installed prior to v1.15
   179  				_ = route.Delete(n.createNodeIPSecInRoute(localCIDR.IPNet))
   180  			}
   181  		} else {
   182  			localCIDR := n.nodeConfig.AllocCIDRIPv4.IPNet
   183  			errs = errors.Join(errs, n.replaceNodeIPSecInRoute(localCIDR))
   184  		}
   185  	} else {
   186  		// A node update that doesn't contain a BootID will cause the creation
   187  		// of non-matching XFRM IN and OUT states across the cluster as the
   188  		// BootID is used to generate per-node key pairs. Non-matching XFRM
   189  		// states will result in XfrmInStateProtoError, causing packet drops.
   190  		// An empty BootID should thus be treated as an error, and Cilium
   191  		// should not attempt to derive per-node keys from it.
   192  		if newNode.BootID == "" {
   193  			n.log.Debug("Unable to enable IPsec for node with empty BootID", logfields.Node, newNode.Name)
   194  			return false, errs
   195  		}
   196  
   197  		remoteCiliumInternalIP := newNode.GetCiliumInternalIP(false)
   198  		if remoteCiliumInternalIP == nil {
   199  			return false, errs
   200  		}
   201  		remoteIP := remoteCiliumInternalIP
   202  
   203  		localCiliumInternalIP := n.nodeConfig.CiliumInternalIPv4
   204  		localIP := localCiliumInternalIP
   205  
   206  		if n.subnetEncryption() {
   207  			localNodeInternalIP, err := n.getV4LinkLocalIP()
   208  			if err != nil {
   209  				n.log.Error("Failed to get local IPv4 for IPsec configuration", logfields.Error, err)
   210  				errs = errors.Join(errs, fmt.Errorf("failed to get local ipv4 for ipsec link: %w", err))
   211  			}
   212  			remoteNodeInternalIP := newNode.GetNodeIP(false)
   213  
   214  			// Check if we should use the NodeInternalIPs instead of the
   215  			// CiliumInternalIPs for the IPsec encapsulation.
   216  			if !option.Config.UseCiliumInternalIPForIPsec {
   217  				localIP = localNodeInternalIP
   218  				remoteIP = remoteNodeInternalIP
   219  			}
   220  
   221  			for _, cidr := range n.nodeConfig.IPv4PodSubnets {
   222  				spi, err = ipsec.UpsertIPsecEndpoint(n.log, wildcardCIDR, cidr, localIP, remoteIP, nodeID, newNode.BootID, ipsec.IPSecDirOut, zeroMark, updateExisting, ipsec.DefaultReqID)
   223  				errs = errors.Join(errs, upsertIPsecLog(n.log, err, "out IPv4", wildcardCIDR, cidr, spi, nodeID))
   224  				if err != nil {
   225  					statesUpdated = false
   226  				}
   227  
   228  				/* Insert wildcard policy rules for traffic skipping back through host */
   229  				if err = ipsec.IpSecReplacePolicyFwd(cidr, localIP, ipsec.DefaultReqID); err != nil {
   230  					n.log.Warn("egress unable to replace policy fwd", logfields.Error, err)
   231  				}
   232  
   233  				spi, err = ipsec.UpsertIPsecEndpoint(n.log, wildcardCIDR, cidr, localCiliumInternalIP, remoteCiliumInternalIP, nodeID, newNode.BootID, ipsec.IPSecDirIn, zeroMark, updateExisting, ipsec.DefaultReqID)
   234  				errs = errors.Join(errs, upsertIPsecLog(n.log, err, "in CiliumInternalIPv4", wildcardCIDR, cidr, spi, nodeID))
   235  				if err != nil {
   236  					statesUpdated = false
   237  				}
   238  
   239  				spi, err = ipsec.UpsertIPsecEndpoint(n.log, wildcardCIDR, cidr, localNodeInternalIP, remoteNodeInternalIP, nodeID, newNode.BootID, ipsec.IPSecDirIn, zeroMark, updateExisting, ipsec.DefaultReqID)
   240  				errs = errors.Join(errs, upsertIPsecLog(n.log, err, "in NodeInternalIPv4", wildcardCIDR, cidr, spi, nodeID))
   241  				if err != nil {
   242  					statesUpdated = false
   243  				}
   244  			}
   245  		} else {
   246  			localCIDR := n.nodeConfig.AllocCIDRIPv4.IPNet
   247  			remoteCIDR := newNode.IPv4AllocCIDR.IPNet
   248  			if err := n.replaceNodeIPSecOutRoute(remoteCIDR); err != nil {
   249  				errs = errors.Join(errs, fmt.Errorf("failed to replace ipsec OUT (%q): %w", remoteCIDR.IP, err))
   250  			}
   251  			spi, err = ipsec.UpsertIPsecEndpoint(n.log, wildcardCIDR, remoteCIDR, localIP, remoteIP, nodeID, newNode.BootID, ipsec.IPSecDirOut, false, updateExisting, ipsec.DefaultReqID)
   252  			errs = errors.Join(errs, upsertIPsecLog(n.log, err, "out IPv4", wildcardCIDR, remoteCIDR, spi, nodeID))
   253  			if err != nil {
   254  				statesUpdated = false
   255  			}
   256  
   257  			/* Insert wildcard policy rules for traffic skipping back through host */
   258  			if err = ipsec.IpSecReplacePolicyFwd(wildcardCIDR, localIP, ipsec.DefaultReqID); err != nil {
   259  				n.log.Warn("egress unable to replace policy fwd", logfields.Error, err)
   260  			}
   261  
   262  			spi, err = ipsec.UpsertIPsecEndpoint(n.log, localCIDR, wildcardCIDR, localIP, remoteIP, nodeID, newNode.BootID, ipsec.IPSecDirIn, false, updateExisting, ipsec.DefaultReqID)
   263  			errs = errors.Join(errs, upsertIPsecLog(n.log, err, "in IPv4", localCIDR, wildcardCIDR, spi, nodeID))
   264  			if err != nil {
   265  				statesUpdated = false
   266  			}
   267  
   268  			// In Encrypt Overlay mode, outermost header is ESP tunnel.
   269  			// Packet format : [IP|ESP|IP|VxLAN|<payload>]
   270  			// ESP tunnel src/dst addresses are underlay IPs of the node (NodeInternalIP).
   271  			// VxLAN tunnel src/dst addresses are also underlay IPs of the node (NodeInternalIP).
   272  			if n.nodeConfig.EnableIPSecEncryptedOverlay {
   273  				localUnderlayIP := n.nodeConfig.NodeIPv4
   274  				if localUnderlayIP == nil {
   275  					n.log.Warn("unable to enable encrypted overlay IPsec, nil local internal IP")
   276  					return false, errs
   277  				}
   278  				remoteUnderlayIP := newNode.GetNodeIP(false)
   279  				if remoteUnderlayIP == nil {
   280  					n.log.Warn("unable to enable encrypted overlay IPsec, nil remote internal IP for node", logfields.Node, newNode.Name)
   281  					return false, errs
   282  				}
   283  
   284  				localOverlayIPExactMatch := &net.IPNet{IP: localUnderlayIP, Mask: exactMatchMask}
   285  				remoteOverlayIPExactMatch := &net.IPNet{IP: remoteUnderlayIP, Mask: exactMatchMask}
   286  
   287  				spi, err = ipsec.UpsertIPsecEndpoint(n.log, localOverlayIPExactMatch, remoteOverlayIPExactMatch, localUnderlayIP, remoteUnderlayIP, nodeID, newNode.BootID, ipsec.IPSecDirOut, false, updateExisting, ipsec.EncryptedOverlayReqID)
   288  				errs = errors.Join(errs, upsertIPsecLog(n.log, err, "overlay out IPv4", localOverlayIPExactMatch, remoteOverlayIPExactMatch, spi, nodeID))
   289  				if err != nil {
   290  					statesUpdated = false
   291  				}
   292  
   293  				spi, err = ipsec.UpsertIPsecEndpoint(n.log, localOverlayIPExactMatch, remoteOverlayIPExactMatch, localUnderlayIP, remoteUnderlayIP, nodeID, newNode.BootID, ipsec.IPSecDirIn, false, updateExisting, ipsec.EncryptedOverlayReqID)
   294  				errs = errors.Join(errs, upsertIPsecLog(n.log, err, "overlay in IPv4", localOverlayIPExactMatch, remoteOverlayIPExactMatch, spi, nodeID))
   295  				if err != nil {
   296  					statesUpdated = false
   297  				}
   298  			}
   299  		}
   300  	}
   301  	return statesUpdated, errs
   302  }
   303  
   304  func (n *linuxNodeHandler) enableIPsecIPv6(newNode *nodeTypes.Node, nodeID uint16, zeroMark, updateExisting bool) (bool, error) {
   305  	statesUpdated := true
   306  	var errs error
   307  	var spi uint8
   308  
   309  	wildcardIP := net.ParseIP(wildcardIPv6)
   310  	wildcardCIDR := &net.IPNet{IP: wildcardIP, Mask: net.CIDRMask(0, 128)}
   311  
   312  	err := ipsec.IPsecDefaultDropPolicy(n.log, true)
   313  	if err != nil {
   314  		errs = errors.Join(errs, fmt.Errorf("failed to create default drop policy IPv6: %w", err))
   315  	}
   316  	errs = errors.Join(errs, upsertIPsecLog(n.log, err, "default-drop IPv6", wildcardCIDR, wildcardCIDR, spi, 0))
   317  
   318  	if newNode.IsLocal() {
   319  		if n.subnetEncryption() {
   320  			// FIXME: Remove the following four lines in Cilium v1.16
   321  			if localCIDR := n.nodeConfig.AllocCIDRIPv6; localCIDR != nil {
   322  				// This removes a bogus route that Cilium installed prior to v1.15
   323  				_ = route.Delete(n.createNodeIPSecInRoute(localCIDR.IPNet))
   324  			}
   325  		} else {
   326  			localCIDR := n.nodeConfig.AllocCIDRIPv6.IPNet
   327  			errs = errors.Join(errs, n.replaceNodeIPSecInRoute(localCIDR))
   328  		}
   329  	} else {
   330  		// A node update that doesn't contain a BootID will cause the creation
   331  		// of non-matching XFRM IN and OUT states across the cluster as the
   332  		// BootID is used to generate per-node key pairs. Non-matching XFRM
   333  		// states will result in XfrmInStateProtoError, causing packet drops.
   334  		// An empty BootID should thus be treated as an error, and Cilium
   335  		// should not attempt to derive per-node keys from it.
   336  		if newNode.BootID == "" {
   337  			n.log.Debug("Unable to enable IPsec for node with empty BootID", logfields.Node, newNode.Name)
   338  			return false, errs
   339  		}
   340  
   341  		remoteCiliumInternalIP := newNode.GetCiliumInternalIP(true)
   342  		if remoteCiliumInternalIP == nil {
   343  			return false, errs
   344  		}
   345  		remoteIP := remoteCiliumInternalIP
   346  
   347  		localCiliumInternalIP := n.nodeConfig.CiliumInternalIPv6
   348  		localIP := localCiliumInternalIP
   349  
   350  		if n.subnetEncryption() {
   351  			localNodeInternalIP, err := n.getV6LinkLocalIP()
   352  			if err != nil {
   353  				n.log.Error("Failed to get local IPv6 for IPsec configuration", logfields.Error, err)
   354  				errs = errors.Join(errs, fmt.Errorf("failed to get local ipv6 for ipsec link: %w", err))
   355  			}
   356  			remoteNodeInternalIP := newNode.GetNodeIP(true)
   357  
   358  			// Check if we should use the NodeInternalIPs instead of the
   359  			// CiliumInternalIPs for the IPsec encapsulation.
   360  			if !option.Config.UseCiliumInternalIPForIPsec {
   361  				localIP = localNodeInternalIP
   362  				remoteIP = remoteNodeInternalIP
   363  			}
   364  
   365  			for _, cidr := range n.nodeConfig.IPv6PodSubnets {
   366  				spi, err = ipsec.UpsertIPsecEndpoint(n.log, wildcardCIDR, cidr, localIP, remoteIP, nodeID, newNode.BootID, ipsec.IPSecDirOut, zeroMark, updateExisting, ipsec.DefaultReqID)
   367  				errs = errors.Join(errs, upsertIPsecLog(n.log, err, "out IPv6", wildcardCIDR, cidr, spi, nodeID))
   368  				if err != nil {
   369  					statesUpdated = false
   370  				}
   371  
   372  				spi, err = ipsec.UpsertIPsecEndpoint(n.log, wildcardCIDR, cidr, localCiliumInternalIP, remoteCiliumInternalIP, nodeID, newNode.BootID, ipsec.IPSecDirIn, zeroMark, updateExisting, ipsec.DefaultReqID)
   373  				errs = errors.Join(errs, upsertIPsecLog(n.log, err, "in CiliumInternalIPv6", wildcardCIDR, cidr, spi, nodeID))
   374  				if err != nil {
   375  					statesUpdated = false
   376  				}
   377  
   378  				spi, err = ipsec.UpsertIPsecEndpoint(n.log, wildcardCIDR, cidr, localNodeInternalIP, remoteNodeInternalIP, nodeID, newNode.BootID, ipsec.IPSecDirIn, zeroMark, updateExisting, ipsec.DefaultReqID)
   379  				errs = errors.Join(errs, upsertIPsecLog(n.log, err, "in NodeInternalIPv6", wildcardCIDR, cidr, spi, nodeID))
   380  				if err != nil {
   381  					statesUpdated = false
   382  				}
   383  			}
   384  		} else {
   385  			localCIDR := n.nodeConfig.AllocCIDRIPv6.IPNet
   386  			remoteCIDR := newNode.IPv6AllocCIDR.IPNet
   387  			if err := n.replaceNodeIPSecOutRoute(remoteCIDR); err != nil {
   388  				errs = errors.Join(errs, fmt.Errorf("failed to replace ipsec OUT (%q): %w", remoteCIDR.IP, err))
   389  			}
   390  			spi, err = ipsec.UpsertIPsecEndpoint(n.log, wildcardCIDR, remoteCIDR, localIP, remoteIP, nodeID, newNode.BootID, ipsec.IPSecDirOut, false, updateExisting, ipsec.DefaultReqID)
   391  			errs = errors.Join(errs, upsertIPsecLog(n.log, err, "out IPv6", wildcardCIDR, remoteCIDR, spi, nodeID))
   392  			if err != nil {
   393  				statesUpdated = false
   394  			}
   395  
   396  			spi, err = ipsec.UpsertIPsecEndpoint(n.log, localCIDR, wildcardCIDR, localIP, remoteIP, nodeID, newNode.BootID, ipsec.IPSecDirIn, false, updateExisting, ipsec.DefaultReqID)
   397  			errs = errors.Join(errs, upsertIPsecLog(n.log, err, "in IPv6", localCIDR, wildcardCIDR, spi, nodeID))
   398  			if err != nil {
   399  				statesUpdated = false
   400  			}
   401  		}
   402  	}
   403  	return statesUpdated, errs
   404  }
   405  
   406  func (n *linuxNodeHandler) subnetEncryption() bool {
   407  	return len(n.nodeConfig.IPv4PodSubnets) > 0 || len(n.nodeConfig.IPv6PodSubnets) > 0
   408  }
   409  
   410  func (n *linuxNodeHandler) removeEncryptRules() error {
   411  	rule := route.Rule{
   412  		Priority: 1,
   413  		Mask:     linux_defaults.RouteMarkMask,
   414  		Table:    linux_defaults.RouteTableIPSec,
   415  		Protocol: linux_defaults.RTProto,
   416  	}
   417  
   418  	rule.Mark = linux_defaults.RouteMarkDecrypt
   419  	if err := route.DeleteRule(netlink.FAMILY_V4, rule); err != nil {
   420  		if !os.IsNotExist(err) {
   421  			return fmt.Errorf("delete previous IPv4 decrypt rule failed: %w", err)
   422  		}
   423  	}
   424  
   425  	rule.Mark = linux_defaults.RouteMarkEncrypt
   426  	if err := route.DeleteRule(netlink.FAMILY_V4, rule); err != nil {
   427  		if !os.IsNotExist(err) {
   428  			return fmt.Errorf("delete previous IPv4 encrypt rule failed: %w", err)
   429  		}
   430  	}
   431  
   432  	if err := route.DeleteRouteTable(linux_defaults.RouteTableIPSec, netlink.FAMILY_V4); err != nil {
   433  		n.log.Warn("Deletion of IPSec routes failed", logfields.Error, err)
   434  	}
   435  
   436  	rule.Mark = linux_defaults.RouteMarkDecrypt
   437  	if err := route.DeleteRule(netlink.FAMILY_V6, rule); err != nil {
   438  		if !os.IsNotExist(err) && !errors.Is(err, unix.EAFNOSUPPORT) {
   439  			return fmt.Errorf("delete previous IPv6 decrypt rule failed: %w", err)
   440  		}
   441  	}
   442  
   443  	rule.Mark = linux_defaults.RouteMarkEncrypt
   444  	if err := route.DeleteRule(netlink.FAMILY_V6, rule); err != nil {
   445  		if !os.IsNotExist(err) && !errors.Is(err, unix.EAFNOSUPPORT) {
   446  			return fmt.Errorf("delete previous IPv6 encrypt rule failed: %w", err)
   447  		}
   448  	}
   449  	return nil
   450  }
   451  
   452  func (n *linuxNodeHandler) createNodeIPSecInRoute(ip *net.IPNet) route.Route {
   453  	device := n.getDefaultEncryptionInterface()
   454  	return route.Route{
   455  		Nexthop: nil,
   456  		Device:  device,
   457  		Prefix:  *ip,
   458  		Table:   linux_defaults.RouteTableIPSec,
   459  		Proto:   linux_defaults.RTProto,
   460  		Type:    route.RTN_LOCAL,
   461  	}
   462  }
   463  
   464  func (n *linuxNodeHandler) createNodeIPSecOutRoute(ip *net.IPNet) route.Route {
   465  	return route.Route{
   466  		Nexthop: nil,
   467  		Device:  n.datapathConfig.HostDevice,
   468  		Prefix:  *ip,
   469  		Table:   linux_defaults.RouteTableIPSec,
   470  		MTU:     n.nodeConfig.RoutePostEncryptMTU,
   471  		Proto:   linux_defaults.RTProto,
   472  	}
   473  }
   474  
   475  func (n *linuxNodeHandler) isValidIP(ip *net.IPNet) bool {
   476  	if ip.IP.To4() != nil {
   477  		if !n.nodeConfig.EnableIPv4 {
   478  			return false
   479  		}
   480  	} else {
   481  		if !n.nodeConfig.EnableIPv6 {
   482  			return false
   483  		}
   484  	}
   485  
   486  	return true
   487  }
   488  
   489  // replaceNodeIPSecOutRoute replace the out IPSec route in the host routing
   490  // table with the new route. If no route exists the route is installed on the
   491  // host. The caller must ensure that the CIDR passed in must be non-nil.
   492  func (n *linuxNodeHandler) replaceNodeIPSecOutRoute(ip *net.IPNet) error {
   493  	if !n.isValidIP(ip) {
   494  		return nil
   495  	}
   496  
   497  	if err := route.Upsert(n.createNodeIPSecOutRoute(ip)); err != nil {
   498  		n.log.Error("Unable to replace the IPSec route OUT the host routing table",
   499  			logfields.Error, err,
   500  			logfields.CIDR, ip,
   501  		)
   502  		return err
   503  	}
   504  	return nil
   505  }
   506  
   507  // The caller must ensure that the CIDR passed in must be non-nil.
   508  func (n *linuxNodeHandler) deleteNodeIPSecOutRoute(ip *net.IPNet) error {
   509  	if !n.isValidIP(ip) {
   510  		return nil
   511  	}
   512  
   513  	if err := route.Delete(n.createNodeIPSecOutRoute(ip)); err != nil {
   514  		n.log.Error("Unable to delete the IPsec route OUT from the host routing table",
   515  			logfields.Error, err,
   516  			logfields.CIDR, ip,
   517  		)
   518  		return fmt.Errorf("failed to delete ipsec host route out: %w", err)
   519  	}
   520  	return nil
   521  }
   522  
   523  // replaceNodeIPSecoInRoute replace the in IPSec routes in the host routing
   524  // table with the new route. If no route exists the route is installed on the
   525  // host. The caller must ensure that the CIDR passed in must be non-nil.
   526  func (n *linuxNodeHandler) replaceNodeIPSecInRoute(ip *net.IPNet) error {
   527  	if !n.isValidIP(ip) {
   528  		return nil
   529  	}
   530  
   531  	if err := route.Upsert(n.createNodeIPSecInRoute(ip)); err != nil {
   532  		n.log.Error("Unable to replace the IPSec route IN the host routing table",
   533  			logfields.Error, err,
   534  			logfields.CIDR, ip,
   535  		)
   536  		return fmt.Errorf("failed to replace ipsec host route IN: %w", err)
   537  	}
   538  	return nil
   539  }
   540  
   541  func (n *linuxNodeHandler) deleteIPsec(oldNode *nodeTypes.Node) error {
   542  	var errs error
   543  	scopedLog := n.log.With(logfields.NodeName, oldNode.Name)
   544  	scopedLog.Debug("Removing IPsec configuration for node")
   545  
   546  	nodeID := n.getNodeIDForNode(oldNode)
   547  	if nodeID == 0 {
   548  		scopedLog.Warn("No node ID found for node.")
   549  	} else {
   550  		errs = errors.Join(errs, ipsec.DeleteIPsecEndpoint(n.log, nodeID))
   551  	}
   552  
   553  	if n.nodeConfig.EnableIPv4 && oldNode.IPv4AllocCIDR != nil {
   554  		old4RouteNet := &net.IPNet{IP: oldNode.IPv4AllocCIDR.IP, Mask: oldNode.IPv4AllocCIDR.Mask}
   555  		// This is only needed in IPAM modes where we install one route per
   556  		// remote pod CIDR.
   557  		if !n.subnetEncryption() {
   558  			errs = errors.Join(errs, n.deleteNodeIPSecOutRoute(old4RouteNet))
   559  		}
   560  	}
   561  
   562  	if n.nodeConfig.EnableIPv6 && oldNode.IPv6AllocCIDR != nil {
   563  		old6RouteNet := &net.IPNet{IP: oldNode.IPv6AllocCIDR.IP, Mask: oldNode.IPv6AllocCIDR.Mask}
   564  		// See IPv4 case above.
   565  		if !n.subnetEncryption() {
   566  			errs = errors.Join(errs, n.deleteNodeIPSecOutRoute(old6RouteNet))
   567  		}
   568  	}
   569  
   570  	delete(n.ipsecUpdateNeeded, oldNode.Identity())
   571  
   572  	return errs
   573  }