335 lines
7.9 KiB
Go
335 lines
7.9 KiB
Go
package birdwatcher
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
|
|
"github.com/alice-lg/alice-lg/pkg/api"
|
|
)
|
|
|
|
// SingleTableBirdwatcher is an Alice Source
|
|
type SingleTableBirdwatcher struct {
|
|
GenericBirdwatcher
|
|
}
|
|
|
|
func (src *SingleTableBirdwatcher) fetchReceivedRoutes(
|
|
ctx context.Context,
|
|
neighborID string,
|
|
) (*api.Meta, api.Routes, error) {
|
|
res, err := src.client.GetEndpoint(ctx, "/routes/protocol/"+neighborID)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
defer res.Body.Close()
|
|
|
|
meta, routes, err := parseRoutesResponseStream(res.Body, src.config)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return meta, routes, nil
|
|
}
|
|
|
|
func (src *SingleTableBirdwatcher) fetchFilteredRoutes(
|
|
ctx context.Context,
|
|
neighborID string,
|
|
) (*api.Meta, api.Routes, error) {
|
|
res, err := src.client.GetEndpoint(ctx, "/routes/filtered/"+neighborID)
|
|
if err != nil {
|
|
log.Println("WARNING Could not retrieve filtered routes:", err)
|
|
log.Println("Is the 'routes_filtered' module active in birdwatcher?")
|
|
return nil, nil, err
|
|
}
|
|
defer res.Body.Close()
|
|
|
|
meta, routes, err := parseRoutesResponseStream(res.Body, src.config)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return meta, routes, nil
|
|
}
|
|
|
|
func (src *SingleTableBirdwatcher) fetchNotExportedRoutes(
|
|
ctx context.Context,
|
|
neighborID string,
|
|
) (*api.Meta, api.Routes, error) {
|
|
res, err := src.client.GetEndpoint(ctx, "/routes/noexport/"+neighborID)
|
|
if err != nil {
|
|
log.Println("WARNING Could not retrieve routes not exported:", err)
|
|
log.Println("Is the 'routes_noexport' module active in birdwatcher?")
|
|
return nil, nil, err
|
|
}
|
|
defer res.Body.Close()
|
|
|
|
meta, routes, err := parseRoutesResponseStream(res.Body, src.config)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return meta, routes, nil
|
|
}
|
|
|
|
// RoutesRequired is a specialized request to fetch:
|
|
//
|
|
// - RoutesExported and
|
|
// - RoutesFiltered
|
|
//
|
|
// from Birdwatcher. As the not exported routes can be very many
|
|
// these are optional and can be loaded on demand using the
|
|
// RoutesNotExported() API.
|
|
//
|
|
// A route deduplication is applied.
|
|
func (src *SingleTableBirdwatcher) fetchRequiredRoutes(
|
|
ctx context.Context,
|
|
neighborID string,
|
|
) (*api.RoutesResponse, error) {
|
|
// Allow only one concurrent request for this neighbor
|
|
// to our backend server.
|
|
src.routesFetchMutex.Lock(neighborID)
|
|
defer src.routesFetchMutex.Unlock(neighborID)
|
|
|
|
// Check if we have a cache hit
|
|
response := src.routesRequiredCache.Get(neighborID)
|
|
if response != nil {
|
|
return response, nil
|
|
}
|
|
|
|
// First: get routes received
|
|
apiStatus, receivedRoutes, err := src.fetchReceivedRoutes(ctx, neighborID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Second: get routes filtered
|
|
_, filteredRoutes, err := src.fetchFilteredRoutes(ctx, neighborID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Perform route deduplication
|
|
importedRoutes := api.Routes{}
|
|
if len(receivedRoutes) > 0 {
|
|
peer := receivedRoutes[0].Gateway
|
|
learntFrom := receivedRoutes[0].LearntFrom
|
|
|
|
filteredRoutes = src.filterRoutesByPeerOrLearntFrom(filteredRoutes, peer, learntFrom)
|
|
importedRoutes = src.filterRoutesByDuplicates(receivedRoutes, filteredRoutes)
|
|
}
|
|
|
|
response = &api.RoutesResponse{
|
|
Response: api.Response{
|
|
Meta: apiStatus,
|
|
},
|
|
Imported: importedRoutes,
|
|
Filtered: filteredRoutes,
|
|
}
|
|
|
|
// Cache result
|
|
src.routesRequiredCache.Set(neighborID, response)
|
|
|
|
return response, nil
|
|
}
|
|
|
|
// Neighbors get neighbors from protocols
|
|
func (src *SingleTableBirdwatcher) Neighbors(
|
|
ctx context.Context,
|
|
) (*api.NeighborsResponse, error) {
|
|
// Check if we hit the cache
|
|
response := src.neighborsCache.Get()
|
|
if response != nil {
|
|
return response, nil
|
|
}
|
|
|
|
// Query birdwatcher
|
|
bird, err := src.client.GetJSON(ctx, "/protocols/bgp")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Use api status from first request
|
|
apiStatus, err := parseAPIStatus(bird, src.config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Parse the neighbors
|
|
neighbors, err := parseNeighbors(bird, src.config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
response = &api.NeighborsResponse{
|
|
Response: api.Response{
|
|
Meta: apiStatus,
|
|
},
|
|
Neighbors: neighbors,
|
|
}
|
|
|
|
// Cache result
|
|
src.neighborsCache.Set(response)
|
|
return response, nil // dereference for now
|
|
}
|
|
|
|
// NeighborsSummary is for now an alias of Neighbors
|
|
func (src *SingleTableBirdwatcher) NeighborsSummary(
|
|
ctx context.Context,
|
|
) (*api.NeighborsResponse, error) {
|
|
return src.Neighbors(ctx)
|
|
}
|
|
|
|
// Routes gets filtered and exported routes
|
|
func (src *SingleTableBirdwatcher) Routes(
|
|
ctx context.Context,
|
|
neighborID string,
|
|
) (*api.RoutesResponse, error) {
|
|
response := &api.RoutesResponse{}
|
|
|
|
// Fetch required routes first (received and filtered)
|
|
required, err := src.fetchRequiredRoutes(ctx, neighborID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Optional: NoExport
|
|
_, notExported, err := src.fetchNotExportedRoutes(ctx, neighborID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
response.Meta = required.Meta
|
|
response.Imported = required.Imported
|
|
response.Filtered = required.Filtered
|
|
response.NotExported = notExported
|
|
|
|
return response, nil
|
|
}
|
|
|
|
// RoutesReceived gets all received routes
|
|
func (src *SingleTableBirdwatcher) RoutesReceived(
|
|
ctx context.Context,
|
|
neighborID string,
|
|
) (*api.RoutesResponse, error) {
|
|
response := &api.RoutesResponse{}
|
|
|
|
// Check if we hit the cache
|
|
cachedRoutes := src.routesRequiredCache.Get(neighborID)
|
|
if cachedRoutes != nil {
|
|
response.Meta = cachedRoutes.Meta
|
|
response.Imported = cachedRoutes.Imported
|
|
return response, nil
|
|
}
|
|
|
|
// Fetch required routes first (received and filtered)
|
|
// However: Store in separate cache for faster access
|
|
routes, err := src.fetchRequiredRoutes(ctx, neighborID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
response.Meta = routes.Meta
|
|
response.Imported = routes.Imported
|
|
|
|
return response, nil
|
|
}
|
|
|
|
// RoutesFiltered gets all filtered routes
|
|
func (src *SingleTableBirdwatcher) RoutesFiltered(
|
|
ctx context.Context,
|
|
neighborID string,
|
|
) (*api.RoutesResponse, error) {
|
|
response := &api.RoutesResponse{}
|
|
|
|
// Check if we hit the cache
|
|
cachedRoutes := src.routesRequiredCache.Get(neighborID)
|
|
if cachedRoutes != nil {
|
|
response.Meta = cachedRoutes.Meta
|
|
response.Filtered = cachedRoutes.Filtered
|
|
return response, nil
|
|
}
|
|
|
|
// Fetch required routes first (received and filtered)
|
|
// However: Store in separate cache for faster access
|
|
routes, err := src.fetchRequiredRoutes(ctx, neighborID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
response.Meta = routes.Meta
|
|
response.Filtered = routes.Filtered
|
|
|
|
return response, nil
|
|
}
|
|
|
|
// RoutesNotExported get all not exported routes
|
|
func (src *SingleTableBirdwatcher) RoutesNotExported(
|
|
ctx context.Context,
|
|
neighborID string,
|
|
) (*api.RoutesResponse, error) {
|
|
// Check if we hit the cache
|
|
response := src.routesNotExportedCache.Get(neighborID)
|
|
if response != nil {
|
|
return response, nil
|
|
}
|
|
|
|
// Fetch not exported routes
|
|
apiStatus, routes, err := src.fetchNotExportedRoutes(ctx, neighborID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
response = &api.RoutesResponse{
|
|
Response: api.Response{
|
|
Meta: apiStatus,
|
|
},
|
|
NotExported: routes,
|
|
}
|
|
|
|
// Cache result
|
|
src.routesNotExportedCache.Set(neighborID, response)
|
|
|
|
return response, nil
|
|
}
|
|
|
|
// AllRoutes retrieves a route dump
|
|
func (src *SingleTableBirdwatcher) AllRoutes(
|
|
ctx context.Context,
|
|
) (*api.RoutesResponse, error) {
|
|
// First fetch all routes from the master table
|
|
mainTable := src.GenericBirdwatcher.config.MainTable
|
|
|
|
// Routes received
|
|
res, err := src.client.GetEndpoint(ctx, "/routes/table/"+mainTable)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer res.Body.Close()
|
|
|
|
meta, birdImported, err := parseRoutesResponseStream(res.Body, src.config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Routes filtered
|
|
res, err = src.client.GetEndpoint(ctx, "/routes/table/"+mainTable+"/filtered")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer res.Body.Close()
|
|
|
|
_, birdFiltered, err := parseRoutesResponseStream(res.Body, src.config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
response := &api.RoutesResponse{
|
|
Response: api.Response{
|
|
Meta: meta,
|
|
},
|
|
Imported: birdImported,
|
|
Filtered: birdFiltered,
|
|
}
|
|
|
|
return response, nil
|
|
}
|