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 }