2017-05-16 14:10:19 +02:00
|
|
|
package birdwatcher
|
|
|
|
|
|
|
|
import (
|
2018-06-19 10:02:16 +02:00
|
|
|
"github.com/alice-lg/alice-lg/backend/api"
|
2018-01-31 15:17:20 +01:00
|
|
|
|
|
|
|
"log"
|
2018-03-16 15:23:52 +01:00
|
|
|
"sort"
|
2017-05-16 14:10:19 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
type Birdwatcher struct {
|
2017-05-18 18:10:55 +02:00
|
|
|
config Config
|
2017-05-19 17:40:22 +02:00
|
|
|
client *Client
|
2017-05-16 14:10:19 +02:00
|
|
|
}
|
|
|
|
|
2017-05-18 18:10:55 +02:00
|
|
|
func NewBirdwatcher(config Config) *Birdwatcher {
|
2017-05-19 17:40:22 +02:00
|
|
|
client := NewClient(config.Api)
|
|
|
|
|
2017-05-16 14:10:19 +02:00
|
|
|
birdwatcher := &Birdwatcher{
|
|
|
|
config: config,
|
2017-05-19 17:40:22 +02:00
|
|
|
client: client,
|
2017-05-16 14:10:19 +02:00
|
|
|
}
|
|
|
|
return birdwatcher
|
|
|
|
}
|
|
|
|
|
2017-05-18 18:10:55 +02:00
|
|
|
func (self *Birdwatcher) Status() (api.StatusResponse, error) {
|
2017-05-19 17:40:22 +02:00
|
|
|
bird, err := self.client.GetJson("/status")
|
|
|
|
if err != nil {
|
2017-05-22 11:09:22 +02:00
|
|
|
return api.StatusResponse{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
apiStatus, err := parseApiStatus(bird, self.config)
|
|
|
|
if err != nil {
|
|
|
|
return api.StatusResponse{}, err
|
2017-05-19 17:40:22 +02:00
|
|
|
}
|
|
|
|
|
2017-05-23 15:40:06 +02:00
|
|
|
birdStatus, err := parseBirdwatcherStatus(bird, self.config)
|
|
|
|
if err != nil {
|
|
|
|
return api.StatusResponse{}, err
|
2017-05-19 17:40:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
response := api.StatusResponse{
|
2017-05-19 17:56:01 +02:00
|
|
|
Api: apiStatus,
|
2017-05-23 15:40:06 +02:00
|
|
|
Status: birdStatus,
|
2017-05-19 17:40:22 +02:00
|
|
|
}
|
2017-05-18 18:10:55 +02:00
|
|
|
|
2017-05-19 17:40:22 +02:00
|
|
|
return response, nil
|
2017-05-16 14:10:19 +02:00
|
|
|
}
|
|
|
|
|
2017-05-22 12:47:55 +02:00
|
|
|
// Get bird BGP protocols
|
2017-05-18 18:10:55 +02:00
|
|
|
func (self *Birdwatcher) Neighbours() (api.NeighboursResponse, error) {
|
2017-05-22 11:09:22 +02:00
|
|
|
bird, err := self.client.GetJson("/protocols/bgp")
|
|
|
|
if err != nil {
|
|
|
|
return api.NeighboursResponse{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
apiStatus, err := parseApiStatus(bird, self.config)
|
|
|
|
if err != nil {
|
|
|
|
return api.NeighboursResponse{}, err
|
|
|
|
}
|
|
|
|
|
2017-05-22 11:47:10 +02:00
|
|
|
neighbours, err := parseNeighbours(bird, self.config)
|
2017-05-22 11:09:22 +02:00
|
|
|
if err != nil {
|
|
|
|
return api.NeighboursResponse{}, err
|
|
|
|
}
|
2017-05-18 18:10:55 +02:00
|
|
|
|
2017-05-22 11:09:22 +02:00
|
|
|
return api.NeighboursResponse{
|
|
|
|
Api: apiStatus,
|
|
|
|
Neighbours: neighbours,
|
|
|
|
}, nil
|
2017-05-16 14:10:19 +02:00
|
|
|
}
|
|
|
|
|
2017-05-22 12:47:55 +02:00
|
|
|
// Get filtered and exported routes
|
2017-05-18 18:10:55 +02:00
|
|
|
func (self *Birdwatcher) Routes(neighbourId string) (api.RoutesResponse, error) {
|
2017-05-22 12:47:55 +02:00
|
|
|
// Exported
|
|
|
|
bird, err := self.client.GetJson("/routes/protocol/" + neighbourId)
|
|
|
|
if err != nil {
|
|
|
|
return api.RoutesResponse{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Use api status from first request
|
|
|
|
apiStatus, err := parseApiStatus(bird, self.config)
|
|
|
|
if err != nil {
|
|
|
|
return api.RoutesResponse{}, err
|
|
|
|
}
|
|
|
|
|
2017-05-23 15:25:15 +02:00
|
|
|
imported, err := parseRoutes(bird, self.config)
|
2017-05-22 12:47:55 +02:00
|
|
|
if err != nil {
|
|
|
|
return api.RoutesResponse{}, err
|
|
|
|
}
|
|
|
|
|
2018-03-16 15:23:52 +01:00
|
|
|
gateway := ""
|
2018-04-30 12:38:48 +02:00
|
|
|
learnt_from := ""
|
2018-03-16 15:23:52 +01:00
|
|
|
if len(imported) > 0 { // infer next_hop ip address from imported[0]
|
2018-04-30 12:38:48 +02:00
|
|
|
gateway = imported[0].Gateway //TODO: change mechanism to infer gateway when state becomes available elsewhere.
|
|
|
|
learnt_from = mustString(imported[0].Details["learnt_from"], "") // also take learnt_from address into account if present.
|
|
|
|
// ^ learnt_from is regularly present on routes for remote-triggered blackholing.
|
2018-03-16 15:23:52 +01:00
|
|
|
}
|
|
|
|
|
2018-01-31 15:17:20 +01:00
|
|
|
// Optional: Filtered
|
|
|
|
bird, _ = self.client.GetJson("/routes/filtered/" + neighbourId)
|
2017-05-22 13:26:37 +02:00
|
|
|
filtered, err := parseRoutes(bird, self.config)
|
|
|
|
if err != nil {
|
2018-01-31 15:17:20 +01:00
|
|
|
log.Println("WARNING Could not retrieve filtered routes:", err)
|
|
|
|
log.Println("Is the 'routes_filtered' module active in birdwatcher?")
|
2018-03-16 15:23:52 +01:00
|
|
|
} else { // we got a filtered routes response => perform routes deduplication
|
|
|
|
result_filtered := make(api.Routes, 0, len(filtered))
|
|
|
|
result_imported := make(api.Routes, 0, len(imported))
|
|
|
|
|
|
|
|
importedMap := make(map[string]api.Route) // for O(1) access
|
|
|
|
for _, route := range imported {
|
|
|
|
importedMap[route.Id] = route
|
|
|
|
}
|
|
|
|
// choose routes with next_hop == gateway of this neighbour
|
|
|
|
for _, route := range filtered {
|
2018-04-30 12:38:48 +02:00
|
|
|
if (route.Gateway == gateway) || (route.Gateway == learnt_from) {
|
2018-03-16 15:23:52 +01:00
|
|
|
result_filtered = append(result_filtered, route)
|
|
|
|
delete(importedMap, route.Id) // remove routes that are filtered on pipe
|
2018-04-27 16:50:36 +02:00
|
|
|
} else if len(imported) == 0 { // in case there are just filtered routes
|
|
|
|
result_filtered = append(result_filtered, route)
|
2018-03-16 15:23:52 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
sort.Sort(result_filtered)
|
|
|
|
filtered = result_filtered
|
|
|
|
// map to slice
|
|
|
|
for _, route := range importedMap {
|
|
|
|
result_imported = append(result_imported, route)
|
|
|
|
}
|
|
|
|
sort.Sort(result_imported)
|
|
|
|
imported = result_imported
|
2017-05-22 13:26:37 +02:00
|
|
|
}
|
2017-05-18 18:10:55 +02:00
|
|
|
|
2017-05-23 15:25:15 +02:00
|
|
|
// Optional: NoExport
|
|
|
|
bird, _ = self.client.GetJson("/routes/noexport/" + neighbourId)
|
|
|
|
noexport, err := parseRoutes(bird, self.config)
|
2018-01-31 15:19:53 +01:00
|
|
|
if err != nil {
|
|
|
|
log.Println("WARNING Could not retrieve routes not exported:", err)
|
|
|
|
log.Println("Is the 'routes_noexport' module active in birdwatcher?")
|
2018-03-16 15:23:52 +01:00
|
|
|
} else {
|
|
|
|
result_noexport := make([]api.Route, 0, len(noexport))
|
|
|
|
// choose routes with next_hop == gateway of this neighbour
|
|
|
|
for _, route := range noexport {
|
2018-04-30 12:38:48 +02:00
|
|
|
if (route.Gateway == gateway) || (route.Gateway == learnt_from) {
|
2018-03-16 15:23:52 +01:00
|
|
|
result_noexport = append(result_noexport, route)
|
2018-04-27 16:50:36 +02:00
|
|
|
} else if len(imported) == 0 { // in case there are just filtered routes
|
|
|
|
result_noexport = append(result_noexport, route)
|
2018-03-16 15:23:52 +01:00
|
|
|
}
|
|
|
|
}
|
2018-01-31 15:19:53 +01:00
|
|
|
}
|
2017-05-23 15:25:15 +02:00
|
|
|
|
2017-05-22 12:47:55 +02:00
|
|
|
return api.RoutesResponse{
|
2017-05-23 15:25:15 +02:00
|
|
|
Api: apiStatus,
|
|
|
|
Imported: imported,
|
|
|
|
Filtered: filtered,
|
|
|
|
NotExported: noexport,
|
2017-05-22 12:47:55 +02:00
|
|
|
}, nil
|
2017-05-16 14:10:19 +02:00
|
|
|
}
|
2017-05-23 13:58:58 +02:00
|
|
|
|
|
|
|
// Make routes lookup
|
2017-06-29 14:57:08 +02:00
|
|
|
func (self *Birdwatcher) LookupPrefix(prefix string) (api.RoutesLookupResponse, error) {
|
2017-06-19 15:27:11 +02:00
|
|
|
// Get RS info
|
|
|
|
rs := api.Routeserver{
|
|
|
|
Id: self.config.Id,
|
|
|
|
Name: self.config.Name,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Query prefix on RS
|
|
|
|
bird, err := self.client.GetJson("/routes/prefix?prefix=" + prefix)
|
|
|
|
if err != nil {
|
2017-06-29 14:57:08 +02:00
|
|
|
return api.RoutesLookupResponse{}, err
|
2017-06-19 15:27:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Parse API status
|
|
|
|
apiStatus, err := parseApiStatus(bird, self.config)
|
|
|
|
if err != nil {
|
2017-06-29 14:57:08 +02:00
|
|
|
return api.RoutesLookupResponse{}, err
|
2017-06-19 15:27:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Parse routes
|
|
|
|
routes, err := parseRoutes(bird, self.config)
|
|
|
|
|
|
|
|
// Add corresponding neighbour and source rs to result
|
|
|
|
results := []api.LookupRoute{}
|
|
|
|
for _, src := range routes {
|
|
|
|
// Okay. This is actually really hacky.
|
|
|
|
// A less bruteforce approach would be highly appreciated
|
|
|
|
route := api.LookupRoute{
|
|
|
|
Id: src.Id,
|
|
|
|
|
|
|
|
Routeserver: rs,
|
|
|
|
|
|
|
|
NeighbourId: src.NeighbourId,
|
|
|
|
|
|
|
|
Network: src.Network,
|
|
|
|
Interface: src.Interface,
|
|
|
|
Gateway: src.Gateway,
|
|
|
|
Metric: src.Metric,
|
|
|
|
Bgp: src.Bgp,
|
|
|
|
Age: src.Age,
|
|
|
|
Type: src.Type,
|
|
|
|
|
|
|
|
Details: src.Details,
|
|
|
|
}
|
|
|
|
results = append(results, route)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make result
|
2017-06-29 14:57:08 +02:00
|
|
|
response := api.RoutesLookupResponse{
|
2017-06-19 15:27:11 +02:00
|
|
|
Api: apiStatus,
|
|
|
|
Routes: results,
|
|
|
|
}
|
|
|
|
return response, nil
|
2017-05-23 13:58:58 +02:00
|
|
|
}
|
2017-06-22 21:04:41 +02:00
|
|
|
|
|
|
|
func (self *Birdwatcher) AllRoutes() (api.RoutesResponse, error) {
|
|
|
|
bird, err := self.client.GetJson("/routes/dump")
|
|
|
|
if err != nil {
|
|
|
|
return api.RoutesResponse{}, err
|
|
|
|
}
|
2017-06-23 11:12:01 +02:00
|
|
|
result, err := parseRoutesDump(bird, self.config)
|
|
|
|
return result, err
|
2017-06-22 21:04:41 +02:00
|
|
|
}
|