2021-09-11 16:20:07 -05:00

219 lines
6.4 KiB
Go

package routing
import (
"encoding/base64"
"errors"
"fmt"
"net"
"regexp"
"strconv"
"strings"
"github.com/golang/protobuf/ptypes"
gobgpapi "github.com/osrg/gobgp/api"
"github.com/osrg/gobgp/pkg/packet/bgp"
"github.com/vishvananda/netlink/nl"
"k8s.io/klog/v2"
"github.com/vishvananda/netlink"
)
// Used for processing Annotations that may contain multiple items
// Pass this the string and the delimiter
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 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 ipv4IsEnabled() bool {
l, err := net.Listen("tcp4", "")
if err != nil {
return false
}
_ = l.Close()
return true
}
func ipv6IsEnabled() bool {
// If ipv6 is disabled with;
//
// sysctl -w net.ipv6.conf.all.disable_ipv6=1
//
// It is still possible to listen on the any-address "::". So this
// function tries the loopback address "::1" which must be present
// if ipv6 is enabled.
l, err := net.Listen("tcp6", "[::1]:0")
if err != nil {
return false
}
_ = l.Close()
return true
}
func getNodeSubnet(nodeIP net.IP) (net.IPNet, string, error) {
links, err := netlink.LinkList()
if err != nil {
return net.IPNet{}, "", errors.New("failed to get list of links")
}
for _, link := range links {
addresses, err := netlink.AddrList(link, netlink.FAMILY_ALL)
if err != nil {
return net.IPNet{}, "", errors.New("failed to get list of addr")
}
for _, addr := range addresses {
if addr.IPNet.IP.Equal(nodeIP) {
return *addr.IPNet, link.Attrs().Name, nil
}
}
}
return net.IPNet{}, "", errors.New("failed to find interface with specified node ip")
}
// generateTunnelName will generate a name for a tunnel interface given a node IP
// for example, if the node IP is 10.0.0.1 the tunnel interface will be named tun-10001
// Since linux restricts interface names to 15 characters, if length of a node IP
// is greater than 12 (after removing "."), then the interface name is tunXYZ
// as opposed to tun-XYZ
func generateTunnelName(nodeIP string) string {
hash := strings.ReplaceAll(nodeIP, ".", "")
// nolint:gomnd // this number becomes less obvious when made a constant
if len(hash) < 12 {
return "tun-" + hash
}
return "tun" + hash
}
// validateCommunity takes in a string and attempts to parse a BGP community out of it in a way that is similar to
// gobgp (internal/pkg/table/policy.go:ParseCommunity()). If it is not able to parse the community information it
// returns an error.
func validateCommunity(arg string) error {
_, err := strconv.ParseUint(arg, 10, bgpCommunityMaxSize)
if err == nil {
return nil
}
_regexpCommunity := regexp.MustCompile(`(\d+):(\d+)`)
elems := _regexpCommunity.FindStringSubmatch(arg)
if len(elems) == 3 {
if _, err := strconv.ParseUint(elems[1], 10, bgpCommunityMaxPartSize); err == nil {
if _, err = strconv.ParseUint(elems[2], 10, bgpCommunityMaxPartSize); err == nil {
return nil
}
}
}
for _, v := range bgp.WellKnownCommunityNameMap {
if arg == v {
return nil
}
}
return fmt.Errorf("failed to parse %s as community", arg)
}
// parseBGPNextHop takes in a GoBGP Path and parses out the destination's next hop from its attributes. If it
// can't parse a next hop IP from the GoBGP Path, it returns an error.
func parseBGPNextHop(path *gobgpapi.Path) (net.IP, error) {
for _, pAttr := range path.GetPattrs() {
var value ptypes.DynamicAny
if err := ptypes.UnmarshalAny(pAttr, &value); err != nil {
return nil, fmt.Errorf("failed to unmarshal path attribute: %s", err)
}
// nolint:gocritic // We can't change this to an if condition because it is a .(type) expression
switch a := value.Message.(type) {
case *gobgpapi.NextHopAttribute:
nextHop := net.ParseIP(a.NextHop).To4()
if nextHop == nil {
if nextHop = net.ParseIP(a.NextHop).To16(); nextHop == nil {
return nil, fmt.Errorf("invalid nextHop address: %s", a.NextHop)
}
}
return nextHop, nil
}
}
return nil, fmt.Errorf("could not parse next hop received from GoBGP for path: %s", path)
}
// parseBGPPath takes in a GoBGP Path and parses out the destination subnet and the next hop from its attributes.
// If successful, it will return the destination of the BGP path as a subnet form and the next hop. If it
// can't parse the destination or the next hop IP, it returns an error.
func parseBGPPath(path *gobgpapi.Path) (*net.IPNet, net.IP, error) {
nextHop, err := parseBGPNextHop(path)
if err != nil {
return nil, nil, err
}
nlri := path.GetNlri()
var prefix gobgpapi.IPAddressPrefix
err = ptypes.UnmarshalAny(nlri, &prefix)
if err != nil {
return nil, nil, fmt.Errorf("invalid nlri in advertised path")
}
dstSubnet, err := netlink.ParseIPNet(prefix.Prefix + "/" + fmt.Sprint(prefix.PrefixLen))
if err != nil {
return nil, nil, fmt.Errorf("couldn't parse IP subnet from nlri advertised path")
}
return dstSubnet, nextHop, nil
}
// deleteRoutesByDestination attempts to safely find all routes based upon its destination subnet and delete them
func deleteRoutesByDestination(destinationSubnet *net.IPNet) error {
routes, err := netlink.RouteListFiltered(nl.FAMILY_ALL, &netlink.Route{
Dst: destinationSubnet, Protocol: zebraRouteOriginator,
}, netlink.RT_FILTER_DST|netlink.RT_FILTER_PROTOCOL)
if err != nil {
return fmt.Errorf("failed to get routes from netlink: %v", err)
}
for i, r := range routes {
klog.V(2).Infof("Found route to remove: %s", r.String())
if err = netlink.RouteDel(&routes[i]); err != nil {
return fmt.Errorf("failed to remove route due to %v", err)
}
}
return nil
}