diff --git a/protocols/bgp/metrics/bgp_peer_metrics.go b/protocols/bgp/metrics/bgp_peer_metrics.go index 8cad4d3aa..2eebe9e28 100644 --- a/protocols/bgp/metrics/bgp_peer_metrics.go +++ b/protocols/bgp/metrics/bgp_peer_metrics.go @@ -1,9 +1,11 @@ package metrics import ( + "fmt" "time" bnet "github.com/bio-routing/bio-rd/net" + "github.com/bio-routing/bio-rd/protocols/bgp/api" ) const ( @@ -48,3 +50,25 @@ type BGPPeerMetrics struct { // AddressFamilies provides metrics on AFI/SAFI level AddressFamilies []*BGPAddressFamilyMetrics } + +// GetStateAsProto returns the state of this peer to be used by the BGP API +func (m *BGPPeerMetrics) GetStateAsProto() api.Session_State { + switch m.State { + case StateDown: + return api.Session_Active // substitution + case StateIdle: + return api.Session_Idle + case StateConnect: + return api.Session_Connect + case StateActive: + return api.Session_Active + case StateOpenSent: + return api.Session_OpenSent + case StateOpenConfirm: + return api.Session_OpenConfirmed + case StateEstablished: + return api.Session_Established + default: + panic(fmt.Sprintf("Unknown state: %v", m.State)) + } +} diff --git a/protocols/bgp/server/bgp_api.go b/protocols/bgp/server/bgp_api.go index 242c7fb02..645f3b853 100644 --- a/protocols/bgp/server/bgp_api.go +++ b/protocols/bgp/server/bgp_api.go @@ -5,7 +5,9 @@ import ( "fmt" "github.com/bio-routing/bio-rd/protocols/bgp/api" + "github.com/bio-routing/bio-rd/protocols/bgp/metrics" "github.com/bio-routing/bio-rd/route" + "github.com/pkg/errors" bnet "github.com/bio-routing/bio-rd/net" routeapi "github.com/bio-routing/bio-rd/route/api" @@ -22,8 +24,69 @@ func NewBGPAPIServer(s BGPServer) *BGPAPIServer { } } +// ListSessions lists all sessions the BGP server currently has func (s *BGPAPIServer) ListSessions(ctx context.Context, in *api.ListSessionsRequest) (*api.ListSessionsResponse, error) { - return nil, fmt.Errorf("Not implemented yet") + bgpMetrics, err := s.srv.Metrics() + if err != nil { + return nil, errors.Wrap(err, "Could not get peer metrics") + } + + sessions := make([]*api.Session, 0) + for _, peerIP := range s.srv.GetPeers() { + peer := s.srv.GetPeerConfig(peerIP) + if in.Filter != nil { + if in.Filter.NeighborIp != nil { + filterNeighbor := bnet.IPFromProtoIP(in.Filter.NeighborIp) + if *filterNeighbor != *peerIP { + continue + } + } + } + + // find metrics for peer + var peerMetrics *metrics.BGPPeerMetrics + for _, peerMetricsEntry := range bgpMetrics.Peers { + if *peerMetricsEntry.IP == *peer.PeerAddress { + peerMetrics = peerMetricsEntry + break + } + } + if peerMetrics == nil { + return nil, fmt.Errorf("Could not find metrics for neighbor %s", peer.PeerAddress) + } + + estSince := peerMetrics.Since.Unix() + if estSince < 0 { + // time not set, peer probably not up + estSince = 0 + } + var routesReceived, routesSent uint64 + for _, afiPeerMetrics := range peerMetrics.AddressFamilies { + routesReceived += afiPeerMetrics.RoutesReceived + routesSent += afiPeerMetrics.RoutesSent + } + + session := &api.Session{ + LocalAddress: peer.LocalAddress.ToProto(), + NeighborAddress: peer.PeerAddress.ToProto(), + LocalAsn: peer.LocalAS, + PeerAsn: peer.PeerAS, + Status: peerMetrics.GetStateAsProto(), + Stats: &api.SessionStats{ + MessagesIn: peerMetrics.UpdatesReceived, + MessagesOut: peerMetrics.UpdatesSent, + RoutesReceived: routesReceived, + RoutesExported: routesSent, + }, + EstablishedSince: uint64(estSince), + } + sessions = append(sessions, session) + } + + resp := &api.ListSessionsResponse{ + Sessions: sessions, + } + return resp, nil } // DumpRIBIn dumps the RIB in of a peer for a given AFI/SAFI diff --git a/protocols/bgp/server/bgp_api_test.go b/protocols/bgp/server/bgp_api_test.go index 5ae69c0e9..e847a2dfc 100644 --- a/protocols/bgp/server/bgp_api_test.go +++ b/protocols/bgp/server/bgp_api_test.go @@ -16,6 +16,7 @@ import ( "github.com/bio-routing/bio-rd/routingtable/adjRIBIn" "github.com/bio-routing/bio-rd/routingtable/adjRIBOut" "github.com/bio-routing/bio-rd/routingtable/filter" + "github.com/bio-routing/bio-rd/routingtable/vrf" "github.com/stretchr/testify/assert" "google.golang.org/grpc" "google.golang.org/grpc/test/bufconn" @@ -358,3 +359,259 @@ func TestDumpRIBInOut(t *testing.T) { assert.Equal(t, expected, results, test.name) } } + +func TestListSessions(t *testing.T) { + vrf, _ := vrf.New("inet.0", 0) + //establishedTime := time.Now() + + tests := []struct { + name string + apisrv *BGPAPIServer + req *api.ListSessionsRequest + expected *api.ListSessionsResponse + wantFail bool + }{ + { + name: "Simple ListSessions, without filter", + apisrv: &BGPAPIServer{ + srv: &bgpServer{ + peers: &peerManager{ + peers: map[bnet.IP]*peer{ + bnet.IPv4FromOctets(10, 0, 0, 0): { + config: &PeerConfig{ + PeerAS: 65100, + LocalAS: 65000, + LocalAddress: bnet.IPv4FromOctets(172, 0, 0, 0).Ptr(), + PeerAddress: bnet.IPv4FromOctets(10, 0, 0, 0).Ptr(), + }, + peerASN: 65100, + localASN: 65000, + addr: bnet.IPv4FromOctets(10, 0, 0, 0).Ptr(), + vrf: vrf, + }, + }, + }, + }, + }, + req: &api.ListSessionsRequest{}, + expected: &api.ListSessionsResponse{ + Sessions: []*api.Session{ + { + LocalAddress: bnet.IPv4FromOctets(172, 0, 0, 0).ToProto(), + NeighborAddress: bnet.IPv4FromOctets(10, 0, 0, 0).ToProto(), + PeerAsn: 65100, + LocalAsn: 65000, + Status: api.Session_Active, + Stats: &api.SessionStats{}, + }, + }, + }, + wantFail: false, + }, + { + name: "ListSessions with two peers without filter", + apisrv: &BGPAPIServer{ + srv: &bgpServer{ + peers: &peerManager{ + peers: map[bnet.IP]*peer{ + bnet.IPv4FromOctets(10, 0, 0, 0): { + config: &PeerConfig{ + PeerAS: 65100, + LocalAS: 65000, + LocalAddress: bnet.IPv4FromOctets(172, 0, 0, 0).Ptr(), + PeerAddress: bnet.IPv4FromOctets(10, 0, 0, 0).Ptr(), + }, + peerASN: 65100, + localASN: 65000, + addr: bnet.IPv4FromOctets(10, 0, 0, 0).Ptr(), + vrf: vrf, + }, + bnet.IPv4FromOctets(192, 168, 0, 0): { + config: &PeerConfig{ + PeerAS: 64999, + LocalAS: 65000, + LocalAddress: bnet.IPv4FromOctets(172, 0, 0, 0).Ptr(), + PeerAddress: bnet.IPv4FromOctets(192, 168, 0, 0).Ptr(), + }, + peerASN: 64999, + localASN: 65000, + addr: bnet.IPv4FromOctets(192, 168, 0, 0).Ptr(), + vrf: vrf, + }, + }, + }, + }, + }, + req: &api.ListSessionsRequest{}, + expected: &api.ListSessionsResponse{ + Sessions: []*api.Session{ + { + LocalAddress: bnet.IPv4FromOctets(172, 0, 0, 0).ToProto(), + NeighborAddress: bnet.IPv4FromOctets(10, 0, 0, 0).ToProto(), + PeerAsn: 65100, + LocalAsn: 65000, + Status: api.Session_Active, + Stats: &api.SessionStats{}, + }, + { + LocalAddress: bnet.IPv4FromOctets(172, 0, 0, 0).ToProto(), + NeighborAddress: bnet.IPv4FromOctets(192, 168, 0, 0).ToProto(), + PeerAsn: 64999, + LocalAsn: 65000, + Status: api.Session_Active, + Stats: &api.SessionStats{}, + }, + }, + }, + wantFail: false, + }, + { + name: "ListSession with two peers and filter", + apisrv: &BGPAPIServer{ + srv: &bgpServer{ + peers: &peerManager{ + peers: map[bnet.IP]*peer{ + bnet.IPv4FromOctets(10, 0, 0, 0): { + config: &PeerConfig{ + PeerAS: 65100, + LocalAS: 65000, + LocalAddress: bnet.IPv4FromOctets(172, 0, 0, 0).Ptr(), + PeerAddress: bnet.IPv4FromOctets(10, 0, 0, 0).Ptr(), + }, + peerASN: 65100, + localASN: 65000, + addr: bnet.IPv4FromOctets(10, 0, 0, 0).Ptr(), + vrf: vrf, + }, + bnet.IPv4FromOctets(192, 168, 0, 0): { + config: &PeerConfig{ + PeerAS: 64999, + LocalAS: 65000, + LocalAddress: bnet.IPv4FromOctets(172, 0, 0, 0).Ptr(), + PeerAddress: bnet.IPv4FromOctets(192, 168, 0, 0).Ptr(), + }, + peerASN: 64999, + localASN: 65000, + addr: bnet.IPv4FromOctets(192, 168, 0, 0).Ptr(), + vrf: vrf, + }, + }, + }, + }, + }, + req: &api.ListSessionsRequest{ + Filter: &api.SessionFilter{ + NeighborIp: bnet.IPv4FromOctets(10, 0, 0, 0).ToProto(), + }, + }, + expected: &api.ListSessionsResponse{ + Sessions: []*api.Session{ + { + LocalAddress: bnet.IPv4FromOctets(172, 0, 0, 0).ToProto(), + NeighborAddress: bnet.IPv4FromOctets(10, 0, 0, 0).ToProto(), + PeerAsn: 65100, + LocalAsn: 65000, + Status: api.Session_Active, + Stats: &api.SessionStats{}, + }, + }, + }, + wantFail: false, + }, + { + name: "ListSession with routes for stats", + apisrv: &BGPAPIServer{ + srv: &bgpServer{ + peers: &peerManager{ + peers: map[bnet.IP]*peer{ + bnet.IPv4FromOctets(10, 0, 0, 0): { + ipv4: &peerAddressFamily{}, + ipv6: &peerAddressFamily{}, + fsms: []*FSM{ + 0: { + ribsInitialized: true, + ipv4Unicast: &fsmAddressFamily{ + adjRIBIn: &routingtable.RTMockClient{FakeRouteCount: 3}, + adjRIBOut: &routingtable.RTMockClient{FakeRouteCount: 2}, + }, + ipv6Unicast: &fsmAddressFamily{ + adjRIBIn: &routingtable.RTMockClient{FakeRouteCount: 10}, + adjRIBOut: &routingtable.RTMockClient{FakeRouteCount: 12}, + }, + counters: fsmCounters{ + updatesReceived: 23, + updatesSent: 42, + }, + }, + }, + config: &PeerConfig{ + PeerAS: 65100, + LocalAS: 65000, + LocalAddress: bnet.IPv4FromOctets(172, 0, 0, 0).Ptr(), + PeerAddress: bnet.IPv4FromOctets(10, 0, 0, 0).Ptr(), + }, + peerASN: 65100, + localASN: 65000, + addr: bnet.IPv4FromOctets(10, 0, 0, 0).Ptr(), + vrf: vrf, + }, + }, + }, + }, + }, + req: &api.ListSessionsRequest{}, + expected: &api.ListSessionsResponse{ + Sessions: []*api.Session{ + { + LocalAddress: bnet.IPv4FromOctets(172, 0, 0, 0).ToProto(), + NeighborAddress: bnet.IPv4FromOctets(10, 0, 0, 0).ToProto(), + PeerAsn: 65100, + LocalAsn: 65000, + Status: api.Session_Active, + Stats: &api.SessionStats{ + RoutesReceived: 13, + RoutesExported: 14, + MessagesIn: 23, + MessagesOut: 42, + }, + }, + }, + }, + wantFail: false, + }, + } + + for _, test := range tests { + testSrv := test.apisrv.srv.(*bgpServer) + testSrv.metrics = &metricsService{testSrv} + bufSize := 1024 * 1024 + lis := bufconn.Listen(bufSize) + s := grpc.NewServer() + api.RegisterBgpServiceServer(s, test.apisrv) + go func() { + if err := s.Serve(lis); err != nil { + log.Fatalf("Server exited with error: %v", err) + } + }() + + ctx := context.Background() + conn, err := grpc.DialContext(ctx, "bufnet", grpc.WithDialer(func(string, time.Duration) (net.Conn, error) { + return lis.Dial() + }), grpc.WithInsecure()) + if err != nil { + t.Fatalf("Failed to dial bufnet: %v", err) + } + defer conn.Close() + + client := api.NewBgpServiceClient(conn) + neighborResp, err := client.ListSessions(ctx, test.req) + if err != nil { + t.Fatalf("ListSessions call failed: %v", err) + } + assert.Equal(t, test.expected, neighborResp) + } + + // As tests seem to share state we need to clean up the vrf here + vrf.Unregister() + vrf.Dispose() +}