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

bgpv1: Add GetRoutes method to Router interface and generic Path type #26803

Merged
merged 2 commits into from
Jul 14, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
94 changes: 94 additions & 0 deletions pkg/bgpv1/gobgp/conversions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Cilium

package gobgp

import (
"errors"
"fmt"
"time"

gobgp "github.com/osrg/gobgp/v3/api"
"github.com/osrg/gobgp/v3/pkg/apiutil"
"github.com/osrg/gobgp/v3/pkg/packet/bgp"
"google.golang.org/protobuf/types/known/timestamppb"

"github.com/cilium/cilium/pkg/bgpv1/types"
)

// ToGoBGPPath converts the Agent Path type to the GoBGP Path type
func ToGoBGPPath(p *types.Path) (*gobgp.Path, error) {
nlri, err := apiutil.MarshalNLRI(p.NLRI)
if err != nil {
return nil, fmt.Errorf("failed to convert NLRI: %w", err)
}

pattrs, err := apiutil.MarshalPathAttributes(p.PathAttributes)
if err != nil {
return nil, fmt.Errorf("failed to convert PathAttribute: %w", err)
}

// ageTimestamp is Path's creation time stamp.
// It is calculated by subtraction of the AgeNanoseconds from the current timestamp.
ageTimestamp := timestamppb.New(time.Now().Add(time.Duration(-1 * p.AgeNanoseconds)))

family := &gobgp.Family{
Afi: gobgp.Family_Afi(p.NLRI.AFI()),
Safi: gobgp.Family_Safi(p.NLRI.SAFI()),
}

return &gobgp.Path{
Nlri: nlri,
Pattrs: pattrs,
Age: ageTimestamp,
Best: p.Best,
Family: family,
}, nil
}

// ToAgentPath converts the GoBGP Path type to the Agent Path type
func ToAgentPath(p *gobgp.Path) (*types.Path, error) {
family := bgp.AfiSafiToRouteFamily(uint16(p.Family.Afi), uint8(p.Family.Safi))

nlri, err := apiutil.UnmarshalNLRI(family, p.Nlri)
if err != nil {
return nil, fmt.Errorf("failed to convert Nlri: %w", err)
}

pattrs, err := apiutil.UnmarshalPathAttributes(p.Pattrs)
if err != nil {
return nil, fmt.Errorf("failed to convert Pattrs: %w", err)
}

// ageNano is time since the Path was created in nanoseconds.
// It is calculated by difference in time from age timestamp till now.
ageNano := int64(time.Now().Sub(p.Age.AsTime()))

return &types.Path{
NLRI: nlri,
PathAttributes: pattrs,
AgeNanoseconds: ageNano,
Best: p.Best,
}, nil
}

// ToAgentPaths converts slice of the GoBGP Path type to slice of the Agent Path type
func ToAgentPaths(paths []*gobgp.Path) ([]*types.Path, error) {
errs := []error{}
ps := []*types.Path{}

for _, path := range paths {
p, err := ToAgentPath(path)
if err != nil {
errs = append(errs, err)
continue
}
ps = append(ps, p)
}

if len(errs) != 0 {
return nil, errors.Join(errs...)
}

return ps, nil
}
57 changes: 57 additions & 0 deletions pkg/bgpv1/gobgp/conversions_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Cilium

package gobgp

import (
"context"
"testing"

gobgp "github.com/osrg/gobgp/v3/api"
"github.com/osrg/gobgp/v3/pkg/server"
"github.com/stretchr/testify/require"
)

// Test common Path type conversions appearing in the codebase
func TestPathConversions(t *testing.T) {
for _, tt := range commonPaths {
t.Run(tt.name, func(t *testing.T) {
s := server.NewBgpServer()
go s.Serve()

err := s.StartBgp(context.TODO(), &gobgp.StartBgpRequest{
Global: &gobgp.Global{
Asn: 65000,
RouterId: "127.0.0.1",
ListenPort: -1,
},
})
require.NoError(t, err)

t.Cleanup(func() {
s.Stop()
})

path, err := ToGoBGPPath(&tt.path)
require.NoError(t, err)

res, err := s.AddPath(context.TODO(), &gobgp.AddPathRequest{
Path: path,
})
require.NoError(t, err)
require.NotZero(t, res.Uuid)

req := &gobgp.ListPathRequest{
Family: path.Family,
}
err = s.ListPath(context.TODO(), req, func(destination *gobgp.Destination) {
paths, err := ToAgentPaths(destination.Paths)
require.NoError(t, err)
require.NotZero(t, paths)
require.EqualValues(t, tt.path.NLRI, paths[0].NLRI)
require.EqualValues(t, tt.path.PathAttributes, paths[0].PathAttributes)
})
require.NoError(t, err)
})
}
}
60 changes: 60 additions & 0 deletions pkg/bgpv1/gobgp/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,3 +220,63 @@ func toAgentSafi(s gobgp.Family_Safi) types.Safi {
return types.SafiUnknown
}
}

func toGoBGPTableType(t types.TableType) (gobgp.TableType, error) {
switch t {
case types.TableTypeLocRIB:
return gobgp.TableType_LOCAL, nil
case types.TableTypeAdjRIBIn:
return gobgp.TableType_ADJ_IN, nil
case types.TableTypeAdjRIBOut:
return gobgp.TableType_ADJ_OUT, nil
default:
return gobgp.TableType_LOCAL, fmt.Errorf("unknown table type %d", t)
}
}

// GetRoutes retrieves routes from the RIB of underlying router
func (g *GoBGPServer) GetRoutes(ctx context.Context, r *types.GetRoutesRequest) (*types.GetRoutesResponse, error) {
errs := []error{}
var routes []*types.Route

fn := func(destination *gobgp.Destination) {
paths, err := ToAgentPaths(destination.Paths)
if err != nil {
errs = append(errs, err)
return
}
routes = append(routes, &types.Route{
Prefix: destination.Prefix,
Paths: paths,
})
}

tt, err := toGoBGPTableType(r.TableType)
if err != nil {
return nil, fmt.Errorf("invalid table type: %w", err)
}

family := &gobgp.Family{
Afi: gobgp.Family_Afi(r.Family.Afi),
Safi: gobgp.Family_Safi(r.Family.Safi),
}

var neighbor string
if r.Neighbor.IsValid() {
neighbor = r.Neighbor.String()
}

req := &gobgp.ListPathRequest{
TableType: tt,
Family: family,
Name: neighbor,
}

if err := g.server.ListPath(ctx, req, fn); err != nil {
return nil, err
}

return &types.GetRoutesResponse{
Routes: routes,
}, nil
}
90 changes: 90 additions & 0 deletions pkg/bgpv1/gobgp/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/cilium/cilium/pkg/logging"
"github.com/cilium/cilium/pkg/logging/logfields"

"github.com/osrg/gobgp/v3/pkg/packet/bgp"
"github.com/stretchr/testify/require"
"k8s.io/utils/pointer"
)
Expand Down Expand Up @@ -368,3 +369,92 @@ func findMatchingPeer(t *testing.T, peers []*models.BgpPeer, n *v2alpha1api.Cili
}
return nil
}

func TestGetRoutes(t *testing.T) {
testSC, err := NewGoBGPServerWithConfig(context.Background(), log, types.ServerParameters{
Global: types.BGPGlobal{
ASN: 65000,
RouterID: "127.0.0.1",
ListenPort: -1,
},
}, &agent.ControlPlaneState{})
require.NoError(t, err)

t.Cleanup(func() {
testSC.Stop()
})

err = testSC.AddNeighbor(context.TODO(), types.NeighborRequest{
Neighbor: neighbor64125,
VR: &v2alpha1api.CiliumBGPVirtualRouter{},
})
require.NoError(t, err)

_, err = testSC.AdvertisePath(context.TODO(), types.PathRequest{
Advert: types.Advertisement{
Prefix: netip.MustParsePrefix("10.0.0.0/24"),
},
})
require.NoError(t, err)

_, err = testSC.AdvertisePath(context.TODO(), types.PathRequest{
Advert: types.Advertisement{
Prefix: netip.MustParsePrefix("fd00::/64"),
},
})
require.NoError(t, err)

// test IPv4 address family
res, err := testSC.GetRoutes(context.TODO(), &types.GetRoutesRequest{
TableType: types.TableTypeLocRIB,
Family: types.Family{
Afi: types.AfiIPv4,
Safi: types.SafiUnicast,
},
})
require.NoError(t, err)
require.Equal(t, 1, len(res.Routes))
require.Equal(t, 1, len(res.Routes[0].Paths))
require.Equal(t, uint16(bgp.AFI_IP), res.Routes[0].Paths[0].NLRI.AFI())
require.Equal(t, uint8(bgp.SAFI_UNICAST), res.Routes[0].Paths[0].NLRI.SAFI())
require.IsType(t, &bgp.IPAddrPrefix{}, res.Routes[0].Paths[0].NLRI)

// test IPv6 address family
res, err = testSC.GetRoutes(context.TODO(), &types.GetRoutesRequest{
TableType: types.TableTypeLocRIB,
Family: types.Family{
Afi: types.AfiIPv6,
Safi: types.SafiUnicast,
},
})
require.NoError(t, err)
require.Equal(t, 1, len(res.Routes))
require.Equal(t, 1, len(res.Routes[0].Paths))
require.Equal(t, uint16(bgp.AFI_IP6), res.Routes[0].Paths[0].NLRI.AFI())
require.Equal(t, uint8(bgp.SAFI_UNICAST), res.Routes[0].Paths[0].NLRI.SAFI())
require.IsType(t, &bgp.IPv6AddrPrefix{}, res.Routes[0].Paths[0].NLRI)

// test adj-rib-out
res, err = testSC.GetRoutes(context.TODO(), &types.GetRoutesRequest{
TableType: types.TableTypeAdjRIBOut,
Family: types.Family{
Afi: types.AfiIPv4,
Safi: types.SafiUnicast,
},
Neighbor: netip.MustParsePrefix(neighbor64125.PeerAddress).Addr(),
})
require.NoError(t, err)
require.Equal(t, 0, len(res.Routes)) // adj-rib is empty as there is no actual peering up

// test adj-rib-in
res, err = testSC.GetRoutes(context.TODO(), &types.GetRoutesRequest{
TableType: types.TableTypeAdjRIBIn,
Family: types.Family{
Afi: types.AfiIPv6,
Safi: types.SafiUnicast,
},
Neighbor: netip.MustParsePrefix(neighbor64125.PeerAddress).Addr(),
})
require.NoError(t, err)
require.Equal(t, 0, len(res.Routes)) // adj-rib is empty as there is no actual peering up
}
45 changes: 45 additions & 0 deletions pkg/bgpv1/gobgp/test_fixtures.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Cilium

package gobgp

import (
"github.com/osrg/gobgp/v3/pkg/packet/bgp"

"github.com/cilium/cilium/pkg/bgpv1/types"
)

var (
prefixV4 = bgp.NewIPAddrPrefix(24, "10.0.0.0")
prefixV6 = bgp.NewIPv6AddrPrefix(64, "fd00::")
originAttribute = bgp.NewPathAttributeOrigin(0)
nextHopAttribute = bgp.NewPathAttributeNextHop("0.0.0.0")
mpReachNLRIAttribute = bgp.NewPathAttributeMpReachNLRI("::", []bgp.AddrPrefixInterface{prefixV6})

// common path structure appearing in the agent code
commonPaths = []struct {
name string
path types.Path
}{
{
name: "IPv4 unicast advertisement",
path: types.Path{
NLRI: prefixV4,
PathAttributes: []bgp.PathAttributeInterface{
originAttribute,
nextHopAttribute,
},
},
},
{
name: "IPv6 unicast advertisement",
path: types.Path{
NLRI: prefixV6,
PathAttributes: []bgp.PathAttributeInterface{
originAttribute,
mpReachNLRIAttribute,
},
},
},
}
)