package routing import ( "encoding/base64" "fmt" "net" "strconv" "strings" gobgpapi "github.com/osrg/gobgp/v3/api" 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 } // getBGPRouteInfoForVIP attempt to automatically find the subnet, BGP AFI/SAFI Family, and nexthop for a given VIP // based upon whether it is an IPv4 address or an IPv6 address. Returns slash notation subnet as uint32 suitable for // sending to GoBGP and an error if it is unable to determine the subnet automatically func (nrc *NetworkRoutingController) getBGPRouteInfoForVIP(vip string) (subnet uint32, nh string, afiFamily gobgpapi.Family_Afi, err error) { ip := net.ParseIP(vip) if ip == nil { err = fmt.Errorf("could not parse VIP: %s", vip) return } if ip.To4() != nil { subnet = 32 afiFamily = gobgpapi.Family_AFI_IP nhIP := nrc.krNode.FindBestIPv4NodeAddress() if nhIP == nil { err = fmt.Errorf("could not find an IPv4 address on node to set as nexthop for vip: %s", vip) } nh = nhIP.String() return } if ip.To16() != nil { subnet = 128 afiFamily = gobgpapi.Family_AFI_IP6 nhIP := nrc.krNode.FindBestIPv6NodeAddress() if nhIP == nil { err = fmt.Errorf("could not find an IPv6 address on node to set as nexthop for vip: %s", vip) } nh = nhIP.String() return } err = fmt.Errorf("could not convert IP to IPv4 or IPv6, unable to find subnet for: %s", vip) return }