Skip to content

Commit c866150

Browse files
committed
Implement ListSessions of BGP API
VRF filtering and flaps + routes imported in the stats are still missing here, but listing sessions does work.
1 parent bfe8627 commit c866150

File tree

3 files changed

+345
-1
lines changed

3 files changed

+345
-1
lines changed

protocols/bgp/metrics/bgp_peer_metrics.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package metrics
22

33
import (
4+
"fmt"
45
"time"
56

67
bnet "github.com/bio-routing/bio-rd/net"
8+
"github.com/bio-routing/bio-rd/protocols/bgp/api"
79
)
810

911
const (
@@ -48,3 +50,25 @@ type BGPPeerMetrics struct {
4850
// AddressFamilies provides metrics on AFI/SAFI level
4951
AddressFamilies []*BGPAddressFamilyMetrics
5052
}
53+
54+
// GetStateAsProto returns the state of this peer to be used by the BGP API
55+
func (m *BGPPeerMetrics) GetStateAsProto() api.Session_State {
56+
switch m.State {
57+
case StateDown:
58+
return api.Session_Active // substitution
59+
case StateIdle:
60+
return api.Session_Idle
61+
case StateConnect:
62+
return api.Session_Connect
63+
case StateActive:
64+
return api.Session_Active
65+
case StateOpenSent:
66+
return api.Session_OpenSent
67+
case StateOpenConfirm:
68+
return api.Session_OpenConfirmed
69+
case StateEstablished:
70+
return api.Session_Established
71+
default:
72+
panic(fmt.Sprintf("Unknown state: %v", m.State))
73+
}
74+
}

protocols/bgp/server/bgp_api.go

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import (
55
"fmt"
66

77
"github.com/bio-routing/bio-rd/protocols/bgp/api"
8+
"github.com/bio-routing/bio-rd/protocols/bgp/metrics"
89
"github.com/bio-routing/bio-rd/route"
10+
"github.com/pkg/errors"
911

1012
bnet "github.com/bio-routing/bio-rd/net"
1113
routeapi "github.com/bio-routing/bio-rd/route/api"
@@ -22,8 +24,69 @@ func NewBGPAPIServer(s BGPServer) *BGPAPIServer {
2224
}
2325
}
2426

27+
// ListSessions lists all sessions the BGP server currently has
2528
func (s *BGPAPIServer) ListSessions(ctx context.Context, in *api.ListSessionsRequest) (*api.ListSessionsResponse, error) {
26-
return nil, fmt.Errorf("Not implemented yet")
29+
bgpMetrics, err := s.srv.Metrics()
30+
if err != nil {
31+
return nil, errors.Wrap(err, "Could not get peer metrics")
32+
}
33+
34+
sessions := make([]*api.Session, 0)
35+
for _, peerIP := range s.srv.GetPeers() {
36+
peer := s.srv.GetPeerConfig(peerIP)
37+
if in.Filter != nil {
38+
if in.Filter.NeighborIp != nil {
39+
filterNeighbor := bnet.IPFromProtoIP(in.Filter.NeighborIp)
40+
if *filterNeighbor != *peerIP {
41+
continue
42+
}
43+
}
44+
}
45+
46+
// find metrics for peer
47+
var peerMetrics *metrics.BGPPeerMetrics
48+
for _, peerMetricsEntry := range bgpMetrics.Peers {
49+
if *peerMetricsEntry.IP == *peer.PeerAddress {
50+
peerMetrics = peerMetricsEntry
51+
break
52+
}
53+
}
54+
if peerMetrics == nil {
55+
return nil, fmt.Errorf("Could not find metrics for neighbor %s", peer.PeerAddress)
56+
}
57+
58+
estSince := peerMetrics.Since.Unix()
59+
if estSince < 0 {
60+
// time not set, peer probably not up
61+
estSince = 0
62+
}
63+
var routesReceived, routesSent uint64
64+
for _, afiPeerMetrics := range peerMetrics.AddressFamilies {
65+
routesReceived += afiPeerMetrics.RoutesReceived
66+
routesSent += afiPeerMetrics.RoutesSent
67+
}
68+
69+
session := &api.Session{
70+
LocalAddress: peer.LocalAddress.ToProto(),
71+
NeighborAddress: peer.PeerAddress.ToProto(),
72+
LocalAsn: peer.LocalAS,
73+
PeerAsn: peer.PeerAS,
74+
Status: peerMetrics.GetStateAsProto(),
75+
Stats: &api.SessionStats{
76+
MessagesIn: peerMetrics.UpdatesReceived,
77+
MessagesOut: peerMetrics.UpdatesSent,
78+
RoutesReceived: routesReceived,
79+
RoutesExported: routesSent,
80+
},
81+
EstablishedSince: uint64(estSince),
82+
}
83+
sessions = append(sessions, session)
84+
}
85+
86+
resp := &api.ListSessionsResponse{
87+
Sessions: sessions,
88+
}
89+
return resp, nil
2790
}
2891

2992
// DumpRIBIn dumps the RIB in of a peer for a given AFI/SAFI

protocols/bgp/server/bgp_api_test.go

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/bio-routing/bio-rd/routingtable/adjRIBIn"
1717
"github.com/bio-routing/bio-rd/routingtable/adjRIBOut"
1818
"github.com/bio-routing/bio-rd/routingtable/filter"
19+
"github.com/bio-routing/bio-rd/routingtable/vrf"
1920
"github.com/stretchr/testify/assert"
2021
"google.golang.org/grpc"
2122
"google.golang.org/grpc/test/bufconn"
@@ -358,3 +359,259 @@ func TestDumpRIBInOut(t *testing.T) {
358359
assert.Equal(t, expected, results, test.name)
359360
}
360361
}
362+
363+
func TestListSessions(t *testing.T) {
364+
vrf, _ := vrf.New("inet.0", 0)
365+
//establishedTime := time.Now()
366+
367+
tests := []struct {
368+
name string
369+
apisrv *BGPAPIServer
370+
req *api.ListSessionsRequest
371+
expected *api.ListSessionsResponse
372+
wantFail bool
373+
}{
374+
{
375+
name: "Simple ListSessions, without filter",
376+
apisrv: &BGPAPIServer{
377+
srv: &bgpServer{
378+
peers: &peerManager{
379+
peers: map[bnet.IP]*peer{
380+
bnet.IPv4FromOctets(10, 0, 0, 0): {
381+
config: &PeerConfig{
382+
PeerAS: 65100,
383+
LocalAS: 65000,
384+
LocalAddress: bnet.IPv4FromOctets(172, 0, 0, 0).Ptr(),
385+
PeerAddress: bnet.IPv4FromOctets(10, 0, 0, 0).Ptr(),
386+
},
387+
peerASN: 65100,
388+
localASN: 65000,
389+
addr: bnet.IPv4FromOctets(10, 0, 0, 0).Ptr(),
390+
vrf: vrf,
391+
},
392+
},
393+
},
394+
},
395+
},
396+
req: &api.ListSessionsRequest{},
397+
expected: &api.ListSessionsResponse{
398+
Sessions: []*api.Session{
399+
{
400+
LocalAddress: bnet.IPv4FromOctets(172, 0, 0, 0).ToProto(),
401+
NeighborAddress: bnet.IPv4FromOctets(10, 0, 0, 0).ToProto(),
402+
PeerAsn: 65100,
403+
LocalAsn: 65000,
404+
Status: api.Session_Active,
405+
Stats: &api.SessionStats{},
406+
},
407+
},
408+
},
409+
wantFail: false,
410+
},
411+
{
412+
name: "ListSessions with two peers without filter",
413+
apisrv: &BGPAPIServer{
414+
srv: &bgpServer{
415+
peers: &peerManager{
416+
peers: map[bnet.IP]*peer{
417+
bnet.IPv4FromOctets(10, 0, 0, 0): {
418+
config: &PeerConfig{
419+
PeerAS: 65100,
420+
LocalAS: 65000,
421+
LocalAddress: bnet.IPv4FromOctets(172, 0, 0, 0).Ptr(),
422+
PeerAddress: bnet.IPv4FromOctets(10, 0, 0, 0).Ptr(),
423+
},
424+
peerASN: 65100,
425+
localASN: 65000,
426+
addr: bnet.IPv4FromOctets(10, 0, 0, 0).Ptr(),
427+
vrf: vrf,
428+
},
429+
bnet.IPv4FromOctets(192, 168, 0, 0): {
430+
config: &PeerConfig{
431+
PeerAS: 64999,
432+
LocalAS: 65000,
433+
LocalAddress: bnet.IPv4FromOctets(172, 0, 0, 0).Ptr(),
434+
PeerAddress: bnet.IPv4FromOctets(192, 168, 0, 0).Ptr(),
435+
},
436+
peerASN: 64999,
437+
localASN: 65000,
438+
addr: bnet.IPv4FromOctets(192, 168, 0, 0).Ptr(),
439+
vrf: vrf,
440+
},
441+
},
442+
},
443+
},
444+
},
445+
req: &api.ListSessionsRequest{},
446+
expected: &api.ListSessionsResponse{
447+
Sessions: []*api.Session{
448+
{
449+
LocalAddress: bnet.IPv4FromOctets(172, 0, 0, 0).ToProto(),
450+
NeighborAddress: bnet.IPv4FromOctets(10, 0, 0, 0).ToProto(),
451+
PeerAsn: 65100,
452+
LocalAsn: 65000,
453+
Status: api.Session_Active,
454+
Stats: &api.SessionStats{},
455+
},
456+
{
457+
LocalAddress: bnet.IPv4FromOctets(172, 0, 0, 0).ToProto(),
458+
NeighborAddress: bnet.IPv4FromOctets(192, 168, 0, 0).ToProto(),
459+
PeerAsn: 64999,
460+
LocalAsn: 65000,
461+
Status: api.Session_Active,
462+
Stats: &api.SessionStats{},
463+
},
464+
},
465+
},
466+
wantFail: false,
467+
},
468+
{
469+
name: "ListSession with two peers and filter",
470+
apisrv: &BGPAPIServer{
471+
srv: &bgpServer{
472+
peers: &peerManager{
473+
peers: map[bnet.IP]*peer{
474+
bnet.IPv4FromOctets(10, 0, 0, 0): {
475+
config: &PeerConfig{
476+
PeerAS: 65100,
477+
LocalAS: 65000,
478+
LocalAddress: bnet.IPv4FromOctets(172, 0, 0, 0).Ptr(),
479+
PeerAddress: bnet.IPv4FromOctets(10, 0, 0, 0).Ptr(),
480+
},
481+
peerASN: 65100,
482+
localASN: 65000,
483+
addr: bnet.IPv4FromOctets(10, 0, 0, 0).Ptr(),
484+
vrf: vrf,
485+
},
486+
bnet.IPv4FromOctets(192, 168, 0, 0): {
487+
config: &PeerConfig{
488+
PeerAS: 64999,
489+
LocalAS: 65000,
490+
LocalAddress: bnet.IPv4FromOctets(172, 0, 0, 0).Ptr(),
491+
PeerAddress: bnet.IPv4FromOctets(192, 168, 0, 0).Ptr(),
492+
},
493+
peerASN: 64999,
494+
localASN: 65000,
495+
addr: bnet.IPv4FromOctets(192, 168, 0, 0).Ptr(),
496+
vrf: vrf,
497+
},
498+
},
499+
},
500+
},
501+
},
502+
req: &api.ListSessionsRequest{
503+
Filter: &api.SessionFilter{
504+
NeighborIp: bnet.IPv4FromOctets(10, 0, 0, 0).ToProto(),
505+
},
506+
},
507+
expected: &api.ListSessionsResponse{
508+
Sessions: []*api.Session{
509+
{
510+
LocalAddress: bnet.IPv4FromOctets(172, 0, 0, 0).ToProto(),
511+
NeighborAddress: bnet.IPv4FromOctets(10, 0, 0, 0).ToProto(),
512+
PeerAsn: 65100,
513+
LocalAsn: 65000,
514+
Status: api.Session_Active,
515+
Stats: &api.SessionStats{},
516+
},
517+
},
518+
},
519+
wantFail: false,
520+
},
521+
{
522+
name: "ListSession with routes for stats",
523+
apisrv: &BGPAPIServer{
524+
srv: &bgpServer{
525+
peers: &peerManager{
526+
peers: map[bnet.IP]*peer{
527+
bnet.IPv4FromOctets(10, 0, 0, 0): {
528+
ipv4: &peerAddressFamily{},
529+
ipv6: &peerAddressFamily{},
530+
fsms: []*FSM{
531+
0: {
532+
ribsInitialized: true,
533+
ipv4Unicast: &fsmAddressFamily{
534+
adjRIBIn: &routingtable.RTMockClient{FakeRouteCount: 3},
535+
adjRIBOut: &routingtable.RTMockClient{FakeRouteCount: 2},
536+
},
537+
ipv6Unicast: &fsmAddressFamily{
538+
adjRIBIn: &routingtable.RTMockClient{FakeRouteCount: 10},
539+
adjRIBOut: &routingtable.RTMockClient{FakeRouteCount: 12},
540+
},
541+
counters: fsmCounters{
542+
updatesReceived: 23,
543+
updatesSent: 42,
544+
},
545+
},
546+
},
547+
config: &PeerConfig{
548+
PeerAS: 65100,
549+
LocalAS: 65000,
550+
LocalAddress: bnet.IPv4FromOctets(172, 0, 0, 0).Ptr(),
551+
PeerAddress: bnet.IPv4FromOctets(10, 0, 0, 0).Ptr(),
552+
},
553+
peerASN: 65100,
554+
localASN: 65000,
555+
addr: bnet.IPv4FromOctets(10, 0, 0, 0).Ptr(),
556+
vrf: vrf,
557+
},
558+
},
559+
},
560+
},
561+
},
562+
req: &api.ListSessionsRequest{},
563+
expected: &api.ListSessionsResponse{
564+
Sessions: []*api.Session{
565+
{
566+
LocalAddress: bnet.IPv4FromOctets(172, 0, 0, 0).ToProto(),
567+
NeighborAddress: bnet.IPv4FromOctets(10, 0, 0, 0).ToProto(),
568+
PeerAsn: 65100,
569+
LocalAsn: 65000,
570+
Status: api.Session_Active,
571+
Stats: &api.SessionStats{
572+
RoutesReceived: 13,
573+
RoutesExported: 14,
574+
MessagesIn: 23,
575+
MessagesOut: 42,
576+
},
577+
},
578+
},
579+
},
580+
wantFail: false,
581+
},
582+
}
583+
584+
for _, test := range tests {
585+
testSrv := test.apisrv.srv.(*bgpServer)
586+
testSrv.metrics = &metricsService{testSrv}
587+
bufSize := 1024 * 1024
588+
lis := bufconn.Listen(bufSize)
589+
s := grpc.NewServer()
590+
api.RegisterBgpServiceServer(s, test.apisrv)
591+
go func() {
592+
if err := s.Serve(lis); err != nil {
593+
log.Fatalf("Server exited with error: %v", err)
594+
}
595+
}()
596+
597+
ctx := context.Background()
598+
conn, err := grpc.DialContext(ctx, "bufnet", grpc.WithDialer(func(string, time.Duration) (net.Conn, error) {
599+
return lis.Dial()
600+
}), grpc.WithInsecure())
601+
if err != nil {
602+
t.Fatalf("Failed to dial bufnet: %v", err)
603+
}
604+
defer conn.Close()
605+
606+
client := api.NewBgpServiceClient(conn)
607+
neighborResp, err := client.ListSessions(ctx, test.req)
608+
if err != nil {
609+
t.Fatalf("ListSessions call failed: %v", err)
610+
}
611+
assert.Equal(t, test.expected, neighborResp)
612+
}
613+
614+
// As tests seem to share state we need to clean up the vrf here
615+
vrf.Unregister()
616+
vrf.Dispose()
617+
}

0 commit comments

Comments
 (0)