187 lines
5.4 KiB
Go

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
}