Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More injectRoute() Preparation Refactors #1745

Merged
merged 6 commits into from
Oct 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions pkg/bgp/id.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package bgp

import (
"encoding/binary"
"errors"
"fmt"
"hash/fnv"
"net"
"strconv"
"strings"

"github.com/cloudnativelabs/kube-router/v2/pkg/utils"
gobgp "github.com/osrg/gobgp/v3/pkg/packet/bgp"
)

const (
CommunityMaxSize = 32
CommunityMaxPartSize = 16
)

// GenerateRouterID will generate a router ID based upon the user's configuration (or lack there of) and the node's
// primary IP address if the user has not specified. If the user has configured the router ID as "generate" then we
// will generate a router ID based upon fnv hashing the node's primary IP address.
func GenerateRouterID(nodeIPAware utils.NodeIPAware, configRouterID string) (string, error) {
switch {
case configRouterID == "generate":
h := fnv.New32a()
h.Write(nodeIPAware.GetPrimaryNodeIP())
hs := h.Sum32()
gip := make(net.IP, 4)
binary.BigEndian.PutUint32(gip, hs)
return gip.String(), nil
case configRouterID != "":
return configRouterID, nil
}

if nodeIPAware.GetPrimaryNodeIP().To4() == nil {
return "", errors.New("router-id must be specified when primary node IP is an IPv6 address")
}
return configRouterID, nil
}

// 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, CommunityMaxSize)
if err == nil {
return nil
}

elem1, elem2, found := strings.Cut(arg, ":")
if found {
if _, err := strconv.ParseUint(elem1, 10, CommunityMaxPartSize); err == nil {
if _, err = strconv.ParseUint(elem2, 10, CommunityMaxPartSize); err == nil {
return nil
}
}
}
for _, v := range gobgp.WellKnownCommunityNameMap {
if arg == v {
return nil
}
}
return fmt.Errorf("failed to parse %s as community", arg)
}
39 changes: 39 additions & 0 deletions pkg/bgp/id_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package bgp

import (
"testing"

"github.com/stretchr/testify/assert"
)

func Test_ValidateCommunity(t *testing.T) {
t.Run("BGP community specified as a 32-bit integer should pass validation", func(t *testing.T) {
assert.Nil(t, ValidateCommunity("4294967041"))
assert.Nil(t, ValidateCommunity("4294967295"))
})
t.Run("BGP community specified as 2 16-bit integers should pass validation", func(t *testing.T) {
assert.Nil(t, ValidateCommunity("65535:65281"))
assert.Nil(t, ValidateCommunity("65535:65535"))
})
t.Run("Well known BGP communities passed as a string should pass validation", func(t *testing.T) {
assert.Nil(t, ValidateCommunity("no-export"))
assert.Nil(t, ValidateCommunity("internet"))
assert.Nil(t, ValidateCommunity("planned-shut"))
assert.Nil(t, ValidateCommunity("accept-own"))
assert.Nil(t, ValidateCommunity("blackhole"))
assert.Nil(t, ValidateCommunity("no-advertise"))
assert.Nil(t, ValidateCommunity("no-peer"))
})
t.Run("BGP community that is greater than 32-bit integer should fail validation", func(t *testing.T) {
assert.Error(t, ValidateCommunity("4294967296"))
})
t.Run("BGP community that is greater than 2 16-bit integers should fail validation", func(t *testing.T) {
assert.Error(t, ValidateCommunity("65536:65535"))
assert.Error(t, ValidateCommunity("65535:65536"))
assert.Error(t, ValidateCommunity("65536:65536"))
})
t.Run("BGP community that is not a number should fail validation", func(t *testing.T) {
assert.Error(t, ValidateCommunity("0xFFFFFFFF"))
assert.Error(t, ValidateCommunity("community"))
})
}
63 changes: 63 additions & 0 deletions pkg/bgp/parse.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package bgp

import (
"fmt"
"net"

gobgpapi "github.com/osrg/gobgp/v3/api"
"github.com/vishvananda/netlink"
)

// ParseNextHop 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 ParseNextHop(path *gobgpapi.Path) (net.IP, error) {
for _, pAttr := range path.GetPattrs() {
unmarshalNew, err := pAttr.UnmarshalNew()
if err != nil {
return nil, fmt.Errorf("failed to unmarshal path attribute: %s", err)
}
switch t := unmarshalNew.(type) {
case *gobgpapi.NextHopAttribute:
// This is the primary way that we receive NextHops and happens when both the client and the server exchange
// next hops on the same IP family that they negotiated BGP on
nextHopIP := net.ParseIP(t.NextHop)
if nextHopIP != nil && (nextHopIP.To4() != nil || nextHopIP.To16() != nil) {
return nextHopIP, nil
}
return nil, fmt.Errorf("invalid nextHop address: %s", t.NextHop)
case *gobgpapi.MpReachNLRIAttribute:
// in the case where the server and the client are exchanging next-hops that don't relate to their primary
// IP family, we get MpReachNLRIAttribute instead of NextHopAttributes
// TODO: here we only take the first next hop, at some point in the future it would probably be best to
// consider multiple next hops
nextHopIP := net.ParseIP(t.NextHops[0])
if nextHopIP != nil && (nextHopIP.To4() != nil || nextHopIP.To16() != nil) {
return nextHopIP, nil
}
return nil, fmt.Errorf("invalid nextHop address: %s", t.NextHops[0])
}
}
return nil, fmt.Errorf("could not parse next hop received from GoBGP for path: %s", path)
}

// ParsePath 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 ParsePath(path *gobgpapi.Path) (*net.IPNet, net.IP, error) {
nextHop, err := ParseNextHop(path)
if err != nil {
return nil, nil, err
}

nlri := path.GetNlri()
var prefix gobgpapi.IPAddressPrefix
err = nlri.UnmarshalTo(&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
}
Loading