mirror of
https://github.com/cloudnativelabs/kube-router.git
synced 2026-02-26 12:31:04 +01:00
202 lines
5.7 KiB
Go
202 lines
5.7 KiB
Go
package routing
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"fmt"
|
|
"net"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/cloudnativelabs/kube-router/v2/pkg/bgp"
|
|
"github.com/cloudnativelabs/kube-router/v2/pkg/utils"
|
|
gobgpapi "github.com/osrg/gobgp/v4/api"
|
|
"github.com/osrg/gobgp/v4/pkg/apiutil"
|
|
|
|
v1core "k8s.io/api/core/v1"
|
|
"k8s.io/klog/v2"
|
|
)
|
|
|
|
// Used for processing Annotations that may contain multiple items
|
|
// Pass this the string and the delimiter
|
|
//
|
|
//nolint:unparam // while delimiter is always "," for now it provides flexibility to leave the function this way
|
|
func stringToSlice(s, d string) []string {
|
|
ss := make([]string, 0)
|
|
if strings.Contains(s, d) {
|
|
ss = strings.Split(s, d)
|
|
} else {
|
|
ss = append(ss, s)
|
|
}
|
|
return ss
|
|
}
|
|
|
|
func stringSliceToIPs(s []string) ([]net.IP, error) {
|
|
ips := make([]net.IP, 0)
|
|
for _, ipString := range s {
|
|
ip := net.ParseIP(ipString)
|
|
if ip == nil {
|
|
return nil, fmt.Errorf("could not parse \"%s\" as an IP", ipString)
|
|
}
|
|
ips = append(ips, ip)
|
|
}
|
|
return ips, nil
|
|
}
|
|
|
|
func stringSliceToIPNets(s []string) ([]net.IPNet, error) {
|
|
ipNets := make([]net.IPNet, 0)
|
|
for _, ipNetString := range s {
|
|
ip, ipNet, err := net.ParseCIDR(strings.TrimSpace(ipNetString))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not parse \"%s\" as an CIDR", ipNetString)
|
|
}
|
|
if ip == nil {
|
|
return nil, fmt.Errorf("could not parse \"%s\" as an IP", ipNetString)
|
|
}
|
|
ipNets = append(ipNets, *ipNet)
|
|
}
|
|
return ipNets, nil
|
|
}
|
|
|
|
func stringSliceToUInt32(s []string) ([]uint32, error) {
|
|
ints := make([]uint32, 0)
|
|
for _, intString := range s {
|
|
newInt, err := strconv.ParseUint(intString, 0, 32)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not parse \"%s\" as an integer", intString)
|
|
}
|
|
ints = append(ints, uint32(newInt))
|
|
}
|
|
return ints, nil
|
|
}
|
|
|
|
func stringSliceB64Decode(s []string) ([]string, error) {
|
|
ss := make([]string, 0)
|
|
for _, b64String := range s {
|
|
decoded, err := base64.StdEncoding.DecodeString(b64String)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not parse \"%s\" as a base64 encoded string",
|
|
b64String)
|
|
}
|
|
ss = append(ss, string(decoded))
|
|
}
|
|
return ss, nil
|
|
}
|
|
|
|
func statementsEqualByName(a, b []*gobgpapi.Statement) bool {
|
|
// First a is in the outer loop ensuring that all members of a are in b
|
|
for _, st1 := range a {
|
|
st1Found := false
|
|
for _, st2 := range b {
|
|
if st1.Name == st2.Name {
|
|
st1Found = true
|
|
}
|
|
}
|
|
if !st1Found {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// Second b is in the outer loop ensuring that all members of b are in a
|
|
for _, st1 := range b {
|
|
st1Found := false
|
|
for _, st2 := range a {
|
|
if st1.Name == st2.Name {
|
|
st1Found = true
|
|
}
|
|
}
|
|
if !st1Found {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// If we've made it through both loops then we know that the statements arrays are equal
|
|
return true
|
|
}
|
|
|
|
// getPodCIDRsFromAllNodeSources gets the pod CIDRs for all available sources on a given node in a specific order. The
|
|
// order of preference is:
|
|
// 1. From the kube-router.io/pod-cidr annotation (preserves backwards compatibility)
|
|
// 2. From the kube-router.io/pod-cidrs annotation (allows the user to specify multiple CIDRs for a given node which
|
|
// seems to be closer aligned to how upstream is moving)
|
|
// 3. From the node's spec definition in node.Spec.PodCIDRs
|
|
func getPodCIDRsFromAllNodeSources(node *v1core.Node) (podCIDRs []string) {
|
|
// Prefer kube-router.io/pod-cidr as a matter of keeping backwards compatibility with previous functionality
|
|
podCIDR := node.GetAnnotations()["kube-router.io/pod-cidr"]
|
|
if podCIDR != "" {
|
|
_, _, err := net.ParseCIDR(podCIDR)
|
|
if err != nil {
|
|
klog.Warningf("couldn't parse CIDR %s from kube-router.io/pod-cidr annotation, skipping...", podCIDR)
|
|
} else {
|
|
podCIDRs = append(podCIDRs, podCIDR)
|
|
return podCIDRs
|
|
}
|
|
}
|
|
|
|
// Then attempt to find the annotation kube-router.io/pod-cidrs and prefer those second
|
|
cidrsAnnotation := node.GetAnnotations()["kube-router.io/pod-cidrs"]
|
|
if cidrsAnnotation != "" {
|
|
// this should contain comma separated CIDRs, any CIDRs which fail to parse we will emit a warning log for
|
|
// and skip it
|
|
cidrsAnnotArray := strings.Split(cidrsAnnotation, ",")
|
|
for _, cidr := range cidrsAnnotArray {
|
|
_, _, err := net.ParseCIDR(cidr)
|
|
if err != nil {
|
|
klog.Warningf("couldn't parse CIDR %s from kube-router.io/pod-cidrs annotation, skipping...",
|
|
cidr)
|
|
continue
|
|
}
|
|
podCIDRs = append(podCIDRs, cidr)
|
|
}
|
|
return podCIDRs
|
|
}
|
|
|
|
// Finally, if all else fails, use the PodCIDRs on the node spec
|
|
return node.Spec.PodCIDRs
|
|
}
|
|
|
|
// getBGPPathForVIP creates a BGP path for advertising or withdrawing a VIP (service IP).
|
|
// It automatically determines whether the VIP is IPv4 or IPv6, finds the appropriate
|
|
// next-hop from the node, and constructs a /32 or /128 CIDR path.
|
|
func getBGPPathForVIP(vip string, krNode utils.NodeIPAware, isWithdrawal bool) (*apiutil.Path, error) {
|
|
ip := net.ParseIP(vip)
|
|
if ip == nil {
|
|
return nil, fmt.Errorf("could not parse VIP: %s", vip)
|
|
}
|
|
|
|
var nextHop string
|
|
var subnet int32
|
|
|
|
// Determine IP version and set appropriate parameters
|
|
switch {
|
|
case ip.To4() != nil:
|
|
subnet = 32
|
|
nhIP := krNode.FindBestIPv4NodeAddress()
|
|
if nhIP == nil {
|
|
return nil, fmt.Errorf("could not find an IPv4 address on node to set as nexthop for vip: %s", vip)
|
|
}
|
|
nextHop = nhIP.String()
|
|
case ip.To16() != nil:
|
|
subnet = 128
|
|
nhIP := krNode.FindBestIPv6NodeAddress()
|
|
if nhIP == nil {
|
|
return nil, fmt.Errorf("could not find an IPv6 address on node to set as nexthop for vip: %s", vip)
|
|
}
|
|
nextHop = nhIP.String()
|
|
}
|
|
|
|
// Construct CIDR (e.g., "10.96.0.1/32" or "2001:db8::1/128")
|
|
cidr := fmt.Sprintf("%s/%d", vip, subnet)
|
|
|
|
// Use builder to create the path
|
|
builder, err := bgp.NewPathBuilder(cidr, nextHop)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Mark as withdrawal if requested
|
|
if isWithdrawal {
|
|
return builder.WithWithdrawal().Build()
|
|
}
|
|
return builder.Build()
|
|
}
|