diff --git a/bird/bird.go b/bird/bird.go index 867fe99..cf18015 100644 --- a/bird/bird.go +++ b/bird/bird.go @@ -28,7 +28,7 @@ var Cache = struct { var NilParse Parsed = (Parsed)(nil) var BirdError Parsed = Parsed{"error": "bird unreachable"} -func isSpecial(ret Parsed) bool { +func IsSpecial(ret Parsed) bool { return reflect.DeepEqual(ret, NilParse) || reflect.DeepEqual(ret, BirdError) } @@ -132,10 +132,15 @@ func RunAndParse(cmd string, parser func(io.Reader) Parsed) (Parsed, bool) { } func Status() (Parsed, bool) { - birdStatus, ok := RunAndParse("status", parseStatus) - if isSpecial(birdStatus) { - return birdStatus, ok + birdStatus, from_cache := RunAndParse("status", parseStatus) + if IsSpecial(birdStatus) { + return birdStatus, from_cache } + + if from_cache { + return birdStatus, from_cache + } + status := birdStatus["status"].(Parsed) // Last Reconfig Timestamp source: @@ -164,7 +169,7 @@ func Status() (Parsed, bool) { birdStatus["status"] = status - return birdStatus, ok + return birdStatus, from_cache } func Protocols() (Parsed, bool) { @@ -173,7 +178,7 @@ func Protocols() (Parsed, bool) { func ProtocolsBgp() (Parsed, bool) { protocols, from_cache := Protocols() - if isSpecial(protocols) { + if IsSpecial(protocols) { return protocols, from_cache } @@ -195,7 +200,7 @@ func Symbols() (Parsed, bool) { } func RoutesPrefixed(prefix string) (Parsed, bool) { - cmd := routeQueryForChannel("route all") + cmd := routeQueryForChannel("route " + prefix + " all") return RunAndParse(cmd, parseRoutes) } @@ -206,7 +211,12 @@ func RoutesProto(protocol string) (Parsed, bool) { func RoutesProtoCount(protocol string) (Parsed, bool) { cmd := routeQueryForChannel("route protocol "+protocol) + " count" - return RunAndParse(cmd, parseRoutes) + return RunAndParse(cmd, parseRoutesCount) +} + +func RoutesProtoPrimaryCount(protocol string) (Parsed, bool) { + cmd := routeQueryForChannel("route primary protocol "+protocol) + " count" + return RunAndParse(cmd, parseRoutesCount) } func RoutesFiltered(protocol string) (Parsed, bool) { @@ -294,10 +304,8 @@ func RoutesDumpPerPeerTable() (Parsed, bool) { protocols := protocolsRes["protocols"].(Parsed) for protocol, details := range protocols { - details, ok := details.(Parsed) - if !ok { - continue - } + details := details.(Parsed) + counters, ok := details["routes"].(Parsed) if !ok { continue @@ -307,7 +315,10 @@ func RoutesDumpPerPeerTable() (Parsed, bool) { continue // nothing to do here. } // Lookup filtered routes - pfilteredRes, _ := RoutesFiltered(protocol) + pfilteredRes, from_cache := RoutesFiltered(protocol) + if reflect.DeepEqual(pfilteredRes, BirdError) { + return pfilteredRes, from_cache + } pfiltered, ok := pfilteredRes["routes"].([]Parsed) if !ok { diff --git a/bird/parser.go b/bird/parser.go index 408c8db..0f2ccc2 100644 --- a/bird/parser.go +++ b/bird/parser.go @@ -37,15 +37,16 @@ var ( countRx *regexp.Regexp } routes struct { - startDefinition *regexp.Regexp - second *regexp.Regexp - routeType *regexp.Regexp - bgp *regexp.Regexp - community *regexp.Regexp - largeCommunity *regexp.Regexp - origin *regexp.Regexp - prefixBird2 *regexp.Regexp - gatewayBird2 *regexp.Regexp + startDefinition *regexp.Regexp + second *regexp.Regexp + routeType *regexp.Regexp + bgp *regexp.Regexp + community *regexp.Regexp + largeCommunity *regexp.Regexp + extendedCommunity *regexp.Regexp + origin *regexp.Regexp + prefixBird2 *regexp.Regexp + gatewayBird2 *regexp.Regexp } } ) @@ -76,6 +77,7 @@ func init() { regex.routes.bgp = regexp.MustCompile(`^\s+BGP.(\w+):\s+(.+)\s*$`) regex.routes.community = regexp.MustCompile(`^\((\d+),\s*(\d+)\)`) regex.routes.largeCommunity = regexp.MustCompile(`^\((\d+),\s*(\d+),\s*(\d+)\)`) + regex.routes.extendedCommunity = regexp.MustCompile(`^\(([^,]+),\s*(\d+),\s*(\d+)\)`) regex.routes.origin = regexp.MustCompile(`\([^\(]*\)\s*`) regex.routes.prefixBird2 = regexp.MustCompile(`^([0-9a-f\.\:\/]+)?\s+unicast\s+\[([\w\.:]+)\s+([0-9\-\:\s]+)(?:\s+from\s+([0-9a-f\.\:\/]+))?\]\s+(?:(\*)\s+)?\((\d+)(?:\/\d+)?(?:\/[^\)]*)?\).*$`) regex.routes.gatewayBird2 = regexp.MustCompile(`^\s+via\s+([0-9a-f\.\:]+)\s+on\s+([\w\.]+)\s*$`) @@ -167,7 +169,12 @@ func parseSymbols(reader io.Reader) Parsed { if regex.symbols.keyRx.MatchString(line) { groups := regex.symbols.keyRx.FindStringSubmatch(line) - res[groups[2]] = groups[1] + + if _, ok := res[groups[2]]; !ok { + res[groups[2]] = []string{} + } + + res[groups[2]] = append(res[groups[2]].([]string), groups[1]) } } @@ -395,6 +402,8 @@ func parseRoutesBgp(line string, bgp Parsed) { parseRoutesCommunities(groups, bgp) } else if groups[1] == "large_community" { parseRoutesLargeCommunities(groups, bgp) + } else if groups[1] == "ext_community" { + parseRoutesExtendedCommunities(groups, bgp) } else if groups[1] == "as_path" { bgp["as_path"] = strings.Split(groups[2], " ") } else { @@ -431,6 +440,19 @@ func parseRoutesLargeCommunities(groups []string, res Parsed) { res["large_communities"] = communities } +func parseRoutesExtendedCommunities(groups []string, res Parsed) { + communities := []interface{}{} + for _, community := range regex.routes.origin.FindAllString(groups[2], -1) { + if regex.routes.extendedCommunity.MatchString(community) { + communityGroups := regex.routes.extendedCommunity.FindStringSubmatch(community) + communities = append(communities, []interface{}{communityGroups[1], parseInt(communityGroups[2]), parseInt(communityGroups[3])}) + } + } + + res["ext_communities"] = communities +} + + func parseRoutesCount(reader io.Reader) Parsed { res := Parsed{} @@ -491,10 +513,10 @@ func parseProtocol(lines string) Parsed { if _, ok := res["routes"]; !ok { routes := Parsed{} - routes["accepted"] = 0 - routes["filtered"] = 0 - routes["exported"] = 0 - routes["preferred"] = 0 + routes["accepted"] = int64(0) + routes["filtered"] = int64(0) + routes["exported"] = int64(0) + routes["preferred"] = int64(0) res["routes"] = routes } @@ -597,7 +619,7 @@ func treatKey(key string) string { func parseInt(from string) int64 { val, err := strconv.ParseInt(from, 10, 64) if err != nil { - return 0 + return int64(0) } return val diff --git a/bird/parser_test.go b/bird/parser_test.go index b16744f..81d5bc2 100644 --- a/bird/parser_test.go +++ b/bird/parser_test.go @@ -53,14 +53,7 @@ func TestParseProtocolBgp(t *testing.T) { p := parseProtocols(f) log.Printf("%# v", pretty.Formatter(p)) - lines := p["protocols"].([]string) - - protocols := []Parsed{} - - for _, v := range lines { - p2 := parseProtocol(v) - protocols = append(protocols, p2) - } + protocols := p["protocols"].(Parsed) if len(protocols) != 3 { //log.Printf("%# v", pretty.Formatter(protocols)) @@ -68,7 +61,6 @@ func TestParseProtocolBgp(t *testing.T) { } fmt.Println(protocols) - } func TestParseRoutesAllIpv4Bird1(t *testing.T) { @@ -108,6 +100,9 @@ func runTestForIpv4WithFile(file string, t *testing.T) { {9033, 65666, 12}, {9033, 65666, 9}, }, + extendedCommunities: []interface{}{ + []interface{}{"rt", int64(48858), int64(50)}, + }, metric: 100, localPref: "100", protocol: "ID8503_AS1340", @@ -126,6 +121,11 @@ func runTestForIpv4WithFile(file string, t *testing.T) { {9033, 65666, 12}, {9033, 65666, 9}, }, + extendedCommunities: []interface{}{ + []interface{}{"ro", int64(21414), int64(52001)}, + []interface{}{"ro", int64(21414), int64(52004)}, + []interface{}{"ro", int64(21414), int64(64515)}, + }, metric: 100, localPref: "100", protocol: "ID8497_AS1339", @@ -144,6 +144,11 @@ func runTestForIpv4WithFile(file string, t *testing.T) { {9033, 65666, 12}, {9033, 65666, 9}, }, + extendedCommunities: []interface{}{ + []interface{}{"ro", int64(21414), int64(52001)}, + []interface{}{"ro", int64(21414), int64(52004)}, + []interface{}{"ro", int64(21414), int64(64515)}, + }, metric: 100, localPref: "100", protocol: "ID8503_AS1340", @@ -162,6 +167,9 @@ func runTestForIpv4WithFile(file string, t *testing.T) { {9033, 65666, 12}, {9033, 65666, 9}, }, + extendedCommunities: []interface{}{ + []interface{}{"rt", int64(48858), int64(50)}, + }, metric: 100, localPref: "100", protocol: "ID8503_AS1340", @@ -207,6 +215,11 @@ func runTestForIpv6WithFile(file string, t *testing.T) { {48821, 0, 2000}, {48821, 0, 2100}, }, + extendedCommunities: []interface{}{ + []interface{}{"ro", int64(21414), int64(52001)}, + []interface{}{"ro", int64(21414), int64(52004)}, + []interface{}{"ro", int64(21414), int64(64515)}, + }, metric: 100, localPref: "500", primary: true, @@ -225,6 +238,11 @@ func runTestForIpv6WithFile(file string, t *testing.T) { {48821, 0, 3000}, {48821, 0, 3100}, }, + extendedCommunities: []interface{}{ + []interface{}{"ro", int64(21414), int64(52001)}, + []interface{}{"ro", int64(21414), int64(52004)}, + []interface{}{"ro", int64(21414), int64(64515)}, + }, localPref: "100", metric: 100, primary: false, @@ -243,6 +261,9 @@ func runTestForIpv6WithFile(file string, t *testing.T) { {48821, 0, 2000}, {48821, 0, 2100}, }, + extendedCommunities: []interface{}{ + []interface{}{"unknown 0x4300", int64(0), int64(1)}, + }, metric: 100, localPref: "5000", primary: true, @@ -288,6 +309,12 @@ func assertRouteIsEqual(expected expectedRoute, actual Parsed, name string, t *t if largeCommunity := value(bgp, "large_communities", name, t).([][]int64); !reflect.DeepEqual(largeCommunity, expected.largeCommunities) { t.Fatal(name, ": Expected large_community to be:", expected.largeCommunities, "not", largeCommunity) } + + if extendedCommunity, ok := bgp["ext_communities"]; ok { + if !reflect.DeepEqual(extendedCommunity.([]interface{}), expected.extendedCommunities) { + t.Fatal(name, ": Expected ext_community to be:", expected.extendedCommunities, "not", extendedCommunity) + } + } } func value(parsed Parsed, key, name string, t *testing.T) interface{} { @@ -300,14 +327,15 @@ func value(parsed Parsed, key, name string, t *testing.T) interface{} { } type expectedRoute struct { - network string - gateway string - asPath []string - community [][]int64 - largeCommunities [][]int64 - metric int64 - protocol string - primary bool - localPref string - iface string + network string + gateway string + asPath []string + community [][]int64 + largeCommunities [][]int64 + extendedCommunities []interface{} + metric int64 + protocol string + primary bool + localPref string + iface string } diff --git a/birdwatcher.go b/birdwatcher.go index 1571031..6685243 100644 --- a/birdwatcher.go +++ b/birdwatcher.go @@ -65,6 +65,9 @@ func makeRouter(config endpoints.ServerConfig) *httprouter.Router { if isModuleEnabled("routes_count_table", whitelist) { r.GET("/routes/count/table/:table", endpoints.Endpoint(endpoints.TableCount)) } + if isModuleEnabled("routes_count_primary", whitelist) { + r.GET("/routes/count/primary/:protocol", endpoints.Endpoint(endpoints.ProtoPrimaryCount)) + } if isModuleEnabled("routes_filtered", whitelist) { r.GET("/routes/filtered/:protocol", endpoints.Endpoint(endpoints.RoutesFiltered)) } @@ -156,7 +159,9 @@ func main() { // Configuration bird.ClientConf = birdConf bird.StatusConf = conf.Status + bird.RateLimitConf.Lock() bird.RateLimitConf.Conf = conf.Ratelimit + bird.RateLimitConf.Unlock() bird.ParserConf = conf.Parser endpoints.Conf = conf.Server diff --git a/endpoints/routes.go b/endpoints/routes.go index 5aadcb5..1dee4b3 100644 --- a/endpoints/routes.go +++ b/endpoints/routes.go @@ -58,8 +58,16 @@ func ProtoCount(r *http.Request, ps httprouter.Params) (bird.Parsed, bool) { return bird.RoutesProtoCount(protocol) } +func ProtoPrimaryCount(r *http.Request, ps httprouter.Params) (bird.Parsed, bool) { + protocol, err := ValidateProtocolParam(ps.ByName("protocol")) + if err != nil { + return bird.Parsed{"error": fmt.Sprintf("%s", err)}, false + } + return bird.RoutesProtoPrimaryCount(protocol) +} + func TableCount(r *http.Request, ps httprouter.Params) (bird.Parsed, bool) { - return bird.RoutesTable(ps.ByName("table")) + return bird.RoutesTableCount(ps.ByName("table")) } func RouteNet(r *http.Request, ps httprouter.Params) (bird.Parsed, bool) { diff --git a/endpoints/symbols.go b/endpoints/symbols.go index 6a8d41d..df9245b 100644 --- a/endpoints/symbols.go +++ b/endpoints/symbols.go @@ -13,10 +13,16 @@ func Symbols(r *http.Request, ps httprouter.Params) (bird.Parsed, bool) { func SymbolTables(r *http.Request, ps httprouter.Params) (bird.Parsed, bool) { val, from_cache := bird.Symbols() - return bird.Parsed{"symbols": val["routing table"]}, from_cache + if bird.IsSpecial(val) { + return val, from_cache + } + return bird.Parsed{"symbols": val["symbols"].(bird.Parsed)["routing table"]}, from_cache } func SymbolProtocols(r *http.Request, ps httprouter.Params) (bird.Parsed, bool) { val, from_cache := bird.Symbols() - return bird.Parsed{"symbols": val["protocols"]}, from_cache + if bird.IsSpecial(val) { + return val, from_cache + } + return bird.Parsed{"symbols": val["symbols"].(bird.Parsed)["protocol"]}, from_cache } diff --git a/etc/ecix/birdwatcher.conf b/etc/ecix/birdwatcher.conf index eb727f5..a1cc871 100644 --- a/etc/ecix/birdwatcher.conf +++ b/etc/ecix/birdwatcher.conf @@ -18,6 +18,7 @@ allow_from = [] # routes_table # routes_count_protocol # routes_count_table +# routes_count_primary # routes_filtered # routes_prefixed # routes_noexport diff --git a/test/routes_bird1_ipv4.sample b/test/routes_bird1_ipv4.sample index 69f34bf..8689cce 100644 --- a/test/routes_bird1_ipv4.sample +++ b/test/routes_bird1_ipv4.sample @@ -7,6 +7,7 @@ BIRD 1.6.3 ready. BGP.local_pref: 100 BGP.community: (65011,3) (9033,3251) BGP.large_community: (9033, 65666, 12) (9033, 65666, 9) + BGP.ext_community: (rt, 48858, 50) 200.0.0.0/24 via 1.2.3.15 on eno7 [ID8497_AS1339 2017-06-21 08:17:31] * (100) [AS1339i] Type: BGP unicast univ BGP.origin: IGP @@ -15,6 +16,7 @@ BIRD 1.6.3 ready. BGP.local_pref: 100 BGP.community: (65011,40) (9033,3251) BGP.large_community: (9033, 65666, 12) (9033, 65666, 9) + BGP.ext_community: (ro, 21414, 52001) (ro, 21414, 52004) (ro, 21414, 64515) via 1.2.3.16 on eno8 [ID8503_AS1340 2017-06-21 08:17:33] (100) [AS1340i] Type: BGP unicast univ BGP.origin: IGP @@ -23,6 +25,7 @@ BIRD 1.6.3 ready. BGP.local_pref: 100 BGP.community: (65011,3) (9033,3251) BGP.large_community: (9033, 65666, 12) (9033, 65666, 9) + BGP.ext_community: (ro, 21414, 52001) (ro, 21414, 52004) (ro, 21414, 64515) 16.0.0.0/24 via 1.2.3.16 on eno7 [ID8503_AS1340 2017-06-21 08:17:33] * (100) [AS1340i] Type: BGP unicast univ BGP.origin: IGP @@ -31,3 +34,4 @@ BIRD 1.6.3 ready. BGP.local_pref: 100 BGP.community: (65011,3) (9033,3251) BGP.large_community: (9033, 65666, 12) (9033, 65666, 9) + BGP.ext_community: (rt, 48858, 50) diff --git a/test/routes_bird1_ipv6.sample b/test/routes_bird1_ipv6.sample index c9722d0..1f52769 100644 --- a/test/routes_bird1_ipv6.sample +++ b/test/routes_bird1_ipv6.sample @@ -8,6 +8,7 @@ BIRD 1.6.3 ready. BGP.local_pref: 500 BGP.community: (9033,3001) (65000,680) BGP.large_community: (48821, 0, 2000) (48821, 0, 2100) + BGP.ext_community: (ro, 21414, 52001) (ro, 21414, 52004) (ro, 21414, 64515) via fe80:ffff:ffff::2 on eth3 [upstream2 2018-01-14 14:33:52] (100) [AS15169i] Type: BGP unicast univ BGP.origin: IGP @@ -17,6 +18,7 @@ BIRD 1.6.3 ready. BGP.local_pref: 100 BGP.community: (50629,200) (50629,201) BGP.large_community: (48821, 0, 3000) (48821, 0, 3100) + BGP.ext_community: (ro, 21414, 52001) (ro, 21414, 52004) (ro, 21414, 64515) 2001:678:1e0::/48 via fe80:ffff:ffff::2 on eth2 [upstream2 2018-01-14 15:04:17 from 2001:678:1e0::2] * (100) [AS202739i] Type: BGP unicast univ BGP.origin: IGP @@ -24,4 +26,5 @@ BIRD 1.6.3 ready. BGP.next_hop: 2001:678:1e0::2 BGP.local_pref: 5000 BGP.community: (48821,2000) (48821,2100) - BGP.large_community: (48821, 0, 2000) (48821, 0, 2100) \ No newline at end of file + BGP.large_community: (48821, 0, 2000) (48821, 0, 2100) + BGP.ext_community: (unknown 0x4300, 0, 1) diff --git a/test/routes_bird2_ipv4.sample b/test/routes_bird2_ipv4.sample index 7a34f1a..8a9c552 100644 --- a/test/routes_bird2_ipv4.sample +++ b/test/routes_bird2_ipv4.sample @@ -8,6 +8,7 @@ BIRD 1.6.3 ready. BGP.local_pref: 100 BGP.community: (65011,3) (9033,3251) BGP.large_community: (9033, 65666, 12) (9033, 65666, 9) + BGP.ext_community: (rt, 48858, 50) 200.0.0.0/24 unicast [ID8497_AS1339 2017-06-21 08:17:31] * (100) [AS1339i] via 1.2.3.15 on eno7 Type: BGP univ @@ -17,6 +18,7 @@ BIRD 1.6.3 ready. BGP.local_pref: 100 BGP.community: (65011,40) (9033,3251) BGP.large_community: (9033, 65666, 12) (9033, 65666, 9) + BGP.ext_community: (ro, 21414, 52001) (ro, 21414, 52004) (ro, 21414, 64515) unicast [ID8503_AS1340 2017-06-21 08:17:33] (100/?) [AS1340i] via 1.2.3.16 on eno8 Type: BGP univ @@ -26,6 +28,7 @@ BIRD 1.6.3 ready. BGP.local_pref: 100 BGP.community: (65011,3) (9033,3251) BGP.large_community: (9033, 65666, 12) (9033, 65666, 9) + BGP.ext_community: (ro, 21414, 52001) (ro, 21414, 52004) (ro, 21414, 64515) 16.0.0.0/24 unicast [ID8503_AS1340 2017-06-21 08:17:33] * (100) [AS1340i] via 1.2.3.16 on eno7 Type: BGP univ @@ -35,3 +38,4 @@ BIRD 1.6.3 ready. BGP.local_pref: 100 BGP.community: (65011,3) (9033,3251) BGP.large_community: (9033, 65666, 12) (9033, 65666, 9) + BGP.ext_community: (rt, 48858, 50) diff --git a/test/routes_bird2_ipv6.sample b/test/routes_bird2_ipv6.sample index b66ce80..e6c864c 100644 --- a/test/routes_bird2_ipv6.sample +++ b/test/routes_bird2_ipv6.sample @@ -9,6 +9,7 @@ BIRD 2.0.0 ready. BGP.local_pref: 500 BGP.community: (9033,3001) (65000,680) BGP.large_community: (48821, 0, 2000) (48821, 0, 2100) + BGP.ext_community: (ro, 21414, 52001) (ro, 21414, 52004) (ro, 21414, 64515) unicast [upstream2 2018-01-14 13:07:26 from fe80:ffff:ffff::2] (100/?) [AS15169i] via fe80:ffff:ffff::2 on eth3 Type: BGP univ @@ -19,6 +20,7 @@ BIRD 2.0.0 ready. BGP.local_pref: 100 BGP.community: (50629,200) (50629,201) BGP.large_community: (48821, 0, 3000) (48821, 0, 3100) + BGP.ext_community: (ro, 21414, 52001) (ro, 21414, 52004) (ro, 21414, 64515) 2001:678:1e0::/48 unicast [upstream2 2018-01-15 20:31:39 from fe80:ffff:ffff::2] * (100/?) [i] via fe80:ffff:ffff::2 on eth2 Type: BGP univ @@ -27,4 +29,5 @@ BIRD 2.0.0 ready. BGP.next_hop: 2001:678:1e0::2 BGP.local_pref: 5000 BGP.community: (48821,2000) (48821,2100) - BGP.large_community: (48821, 0, 2000) (48821, 0, 2100) \ No newline at end of file + BGP.large_community: (48821, 0, 2000) (48821, 0, 2100) + BGP.ext_community: (unknown 0x4300, 0, 1)