package birdwatcher // Parsers and helpers import ( "encoding/json" "fmt" "log" "sort" "strconv" "strings" "time" "github.com/alice-lg/alice-lg/pkg/api" "github.com/alice-lg/alice-lg/pkg/decoders" "github.com/alice-lg/alice-lg/pkg/pools" ) // Convert server time string to time func parseServerTime(value interface{}, layout, timezone string) (time.Time, error) { svalue, ok := value.(string) if !ok { return time.Time{}, nil } loc, err := time.LoadLocation(timezone) if err != nil { return time.Time{}, err } t, err := time.ParseInLocation(layout, svalue, loc) if err != nil { return time.Time{}, err } return t.UTC(), nil } // Make api status from response: // The api status is always included in a birdwatcher response func parseAPIStatus(bird ClientResponse, config Config) (*api.Meta, error) { birdAPI, ok := bird["api"].(map[string]interface{}) if !ok { // Try to retrieve the real error from server birdErr, ok := bird["error"].(string) if !ok { // Unknown error return nil, fmt.Errorf("invalid API response received from server") } return nil, fmt.Errorf(birdErr) } // Parse TTL ttl, err := parseServerTime( bird["ttl"], config.ServerTime, config.Timezone, ) if err != nil { return nil, err } // Parse Cache Status cacheStatus, _ := parseCacheStatus(birdAPI, config) status := &api.Meta{ Version: birdAPI["Version"].(string), ResultFromCache: birdAPI["result_from_cache"].(bool), TTL: ttl, CacheStatus: cacheStatus, } return status, nil } // Parse cache status from api response func parseCacheStatus( cacheStatus map[string]interface{}, config Config, ) (api.CacheStatus, error) { cache, ok := cacheStatus["cache_status"].(map[string]interface{}) if !ok { return api.CacheStatus{}, fmt.Errorf("invalid Cache Status") } cachedAt, ok := cache["cached_at"].(map[string]interface{}) if !ok { return api.CacheStatus{}, fmt.Errorf("invalid Cache Status") } cachedAtTime, err := parseServerTime( cachedAt["date"], config.ServerTime, config.Timezone) if err != nil { return api.CacheStatus{}, err } status := api.CacheStatus{ CachedAt: cachedAtTime, // We ommit OrigTTL for now... } return status, nil } // Parse birdwatcher status func parseBirdwatcherStatus(bird ClientResponse, config Config) (api.Status, error) { birdStatus := bird["status"].(map[string]interface{}) // Get special fields serverTime, _ := parseServerTime( birdStatus["current_server"], config.ServerTimeShort, config.Timezone, ) lastReboot, _ := parseServerTime( birdStatus["last_reboot"], config.ServerTimeShort, config.Timezone, ) if !config.ShowLastReboot { lastReboot = time.Time{} } lastReconfig, _ := parseServerTime( birdStatus["last_reconfig"], config.ServerTimeExt, config.Timezone, ) // Make status response status := api.Status{ ServerTime: serverTime, LastReboot: lastReboot, LastReconfig: lastReconfig, Backend: "bird", Version: decoders.String(birdStatus["version"], "unknown"), Message: decoders.String(birdStatus["message"], "unknown"), RouterID: decoders.String(birdStatus["router_id"], "unknown"), } return status, nil } // Parse neighbor uptime func parseRelativeServerTime(uptime interface{}, config Config) time.Duration { serverTime, _ := parseServerTime(uptime, config.ServerTimeShort, config.Timezone) return time.Since(serverTime) } // Parse neighbors response func parseNeighbors(bird ClientResponse, config Config) (api.Neighbors, error) { rsID := config.ID neighbors := api.Neighbors{} protocols := bird["protocols"].(map[string]interface{}) // Iterate over protocols map: for protocolID, proto := range protocols { protocol := proto.(map[string]interface{}) routes := protocol["routes"].(map[string]interface{}) uptime := parseRelativeServerTime(protocol["state_changed"], config) lastError := decoders.String(protocol["last_error"], "") routesReceived := float64(0) if routes != nil { if _, ok := routes["imported"]; ok { routesReceived = routesReceived + routes["imported"].(float64) } if _, ok := routes["filtered"]; ok { routesReceived = routesReceived + routes["filtered"].(float64) } } neighbor := &api.Neighbor{ ID: protocolID, Address: decoders.String(protocol["neighbor_address"], "error"), ASN: decoders.Int(protocol["neighbor_as"], 0), State: strings.ToLower( decoders.String(protocol["state"], "unknown")), Description: decoders.String(protocol["description"], "no description"), RoutesReceived: decoders.Int(routesReceived, 0), RoutesAccepted: decoders.Int(routes["imported"], 0), RoutesFiltered: decoders.Int(routes["filtered"], 0), RoutesExported: decoders.Int(routes["exported"], 0), //TODO protocol_exported? RoutesPreferred: decoders.Int(routes["preferred"], 0), Uptime: uptime, LastError: lastError, RouteServerID: rsID, Details: protocol, } neighbors = append(neighbors, neighbor) } sort.Sort(neighbors) return neighbors, nil } // Parse neighbors response func parseNeighborsShort(bird ClientResponse, config Config) (api.NeighborsStatus, error) { neighbors := api.NeighborsStatus{} protocols := bird["protocols"].(map[string]interface{}) // Iterate over protocols map: for protocolID, proto := range protocols { protocol := proto.(map[string]interface{}) uptime := parseRelativeServerTime(protocol["since"], config) neighbor := &api.NeighborStatus{ ID: protocolID, State: decoders.String(protocol["state"], "unknown"), Since: uptime, } neighbors = append(neighbors, neighbor) } sort.Sort(neighbors) return neighbors, nil } // Parse route bgp info func parseRouteBgpInfo(data interface{}) *api.BGPInfo { gwpool := pools.Gateways4 // Let's see bgpData, ok := data.(map[string]interface{}) if !ok { // Info is missing return &api.BGPInfo{} } asPath := decoders.IntList(bgpData["as_path"]) communities := parseBgpCommunities(bgpData["communities"]) largeCommunities := parseBgpCommunities(bgpData["large_communities"]) extCommunities := parseExtBgpCommunities(bgpData["ext_communities"]) localPref, _ := strconv.Atoi(decoders.String(bgpData["local_pref"], "0")) med, _ := strconv.Atoi(decoders.String(bgpData["med"], "0")) bgp := &api.BGPInfo{ Origin: pools.Origins.Acquire( decoders.String(bgpData["origin"], "unknown")), AsPath: pools.ASPaths.Acquire(asPath), NextHop: gwpool.Acquire( decoders.String(bgpData["next_hop"], "unknown")), LocalPref: localPref, Med: med, Communities: communities, ExtCommunities: extCommunities, LargeCommunities: largeCommunities, } return bgp } // Extract bgp communities from response func parseBgpCommunities(data interface{}) []api.Community { communities := []api.Community{} ldata, ok := data.([]interface{}) if !ok { // We don't have any return []api.Community{} } for _, c := range ldata { cdata := c.([]interface{}) community := api.Community{} for _, cinfo := range cdata { community = append(community, int(cinfo.(float64))) } communities = append(communities, community) } return communities } // Extract extended communtieis func parseExtBgpCommunities(data interface{}) []api.ExtCommunity { communities := []api.ExtCommunity{} ldata, ok := data.([]interface{}) if !ok { // We don't have any return communities } for _, c := range ldata { cdata := c.([]interface{}) if len(cdata) != 3 { log.Println("Ignoring malformed ext community:", cdata) continue } communities = append(communities, api.ExtCommunity{ cdata[0], cdata[1], cdata[2], }) } return communities } // Parse partial route func parseRouteData( rdata map[string]interface{}, config Config, keepDetails bool, ) *api.Route { gwpool := pools.Gateways4 // Let's see netpool := pools.Networks4 // same... age := parseRelativeServerTime(rdata["age"], config) rtype := decoders.StringList(rdata["type"]) bgpInfo := parseRouteBgpInfo(rdata["bgp"]) // Precompute details as raw json message var details json.RawMessage = nil if keepDetails { detailsJSON, err := json.Marshal(rdata) if err != nil { log.Println("error while encoding details:", err) } details = json.RawMessage(detailsJSON) } gateway := decoders.String(rdata["gateway"], "unknown gateway") learntFrom := decoders.String(rdata["learnt_from"], "") if learntFrom == "" { learntFrom = gateway } route := &api.Route{ ID: decoders.String(rdata["network"], "unknown"), NeighborID: pools.Neighbors.Acquire( decoders.String(rdata["from_protocol"], "unknown neighbor")), Network: netpool.Acquire( decoders.String(rdata["network"], "unknown net")), Interface: pools.Interfaces.Acquire( decoders.String(rdata["interface"], "unknown interface")), Metric: decoders.Int(rdata["metric"], -1), Primary: decoders.Bool(rdata["primary"], false), LearntFrom: gwpool.Acquire(learntFrom), Gateway: gwpool.Acquire(gateway), Age: age, Type: pools.Types.Acquire(rtype), BGP: bgpInfo, Details: &details, } return route } // Parse partial routes response func parseRoutesData( birdRoutes []interface{}, config Config, keepDetails bool, ) api.Routes { routes := api.Routes{} for _, data := range birdRoutes { rdata := data.(map[string]interface{}) route := parseRouteData(rdata, config, keepDetails) routes = append(routes, route) } return routes } // Parse routes response func parseRoutes( bird ClientResponse, config Config, keepDetails bool, ) (api.Routes, error) { birdRoutes, ok := bird["routes"].([]interface{}) if !ok { return api.Routes{}, fmt.Errorf("routes response missing") } routes := parseRoutesData(birdRoutes, config, keepDetails) // Sort routes sort.Sort(routes) return routes, nil } /* Linter says parseRoutesDump is dead code. So for now this is removed... func parseRoutesDump(bird ClientResponse, config Config) (*api.RoutesResponse, error) { result := &api.RoutesResponse{} apiStatus, err := parseAPIStatus(bird, config) if err != nil { return result, err } result.Meta = apiStatus // Fetch imported routes importedRoutes, ok := bird["imported"].([]interface{}) if !ok { return result, fmt.Errorf("imported routes missing") } // Sort routes by network for faster querying imported := parseRoutesData(importedRoutes, config) sort.Sort(imported) result.Imported = imported // Fetch filtered routes filteredRoutes, ok := bird["filtered"].([]interface{}) if !ok { return result, fmt.Errorf("filtered routes missing") } filtered := parseRoutesData(filteredRoutes, config) sort.Sort(filtered) result.Filtered = filtered return result, nil } */