diff --git a/Makefile b/Makefile index bf75803..98ac2de 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,3 @@ - # # Ecix Birdseye Makefile # @@ -99,5 +98,3 @@ clean: rm -f $(PROG)-osx-$(ARCH) rm -f $(PROG)-linux-$(ARCH) rm -rf $(DIST) - - diff --git a/README.md b/README.md index 8255a70..463c6a4 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,6 @@ Barry O'Donovan's [birds-eye](https://github.com/inex/birds-eye-design/) to [the BIRD routing daemon](http://bird.network.cz/). -## Installation - -You will need to have go installed to build the package. -Running `go get github.com/ecix/birdwatcher` will give you -a binary. You might need to cross-compile it for your -bird-running servive (`GOARCH` and `GOOS` are your friends). - ## Why The [INEX implementation](https://github.com/inex/birdseye) of @@ -20,6 +13,48 @@ in a routeserver setting. By using Go, we are able to work with regular binaries, which means deployment and maintenance might be more convenient. +Our version also has a few more capabilities, as you will +discover when looking at [the modules section](https://github.com/ecix/birdwatcher/blob/master/etc/ecix/birdwatcher.conf) +of the config. + +## Installation + +You will need to have go installed to build the package. +Running `go get github.com/ecix/birdwatcher` will give you +a binary. You might need to cross-compile it for your +bird-running servive (`GOARCH` and `GOOS` are your friends). + +We provide a Makefile for more advanced compilation/configuration. +Running `make linux` will create a Linux executable (by default for +`amd64`, but that is configurable by providing the `ARCH` argument +to the Makefile). + +### Building an RPM + +Building RPMs is supported through [fpm](https://github.com/jordansissel/fpm). +If you have `fpm` installed locally, you can run `make rpm` +to create a RPM in the folder `RPMS`. If you have a remote +build server with `fpm` installed, you can build and fetch +an RPM with `make remote_rpm BUILD_SERVER=` +(requires SSH access). + +### Deployment + +If you want to deploy `birdwatcher` on a system that uses +RPMs, you should be able to install it after following the +instructions on [building an RPM](#building-an-rpm). + +We do not currently support other deployment methods. + +## Configuration + +An example config with sane defaults is provided in +[etc/ecix/birdwatcher.conf](https://github.com/ecix/birdwatcher/blob/master/etc/ecix/birdwatcher.conf). +You should be able to use it out of the box. If you need +to change it, it is well-commented and hopefully intuitive. +If you do not know how to configure it, please consider opening +[an issue](https://github.com/ecix/birdwatcher/issues/new). + ## How In the background `birdwatcher` runs the `birdc` client, sends diff --git a/VERSION b/VERSION index bd8bf88..84298f9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.7.0 +1.7.8 diff --git a/bird/bird.go b/bird/bird.go index 3168a47..6b4c0a4 100644 --- a/bird/bird.go +++ b/bird/bird.go @@ -23,6 +23,15 @@ func fromCache(key string) (Parsed, bool) { Cache.RLock() val, ok := Cache.m[key] Cache.RUnlock() + if !ok { + return nil, false + } + + ttl, correct := val["ttl"].(time.Time) + if !correct || ttl.Before(time.Now()) { + return nil, false + } + return val, ok } @@ -96,6 +105,9 @@ func RunAndParse(cmd string, parser func([]byte) Parsed) (Parsed, bool) { func Status() (Parsed, bool) { birdStatus, ok := RunAndParse("status", parseStatus) + if birdStatus == nil { + return birdStatus, ok + } status := birdStatus["status"].(Parsed) // Last Reconfig Timestamp source: @@ -133,6 +145,9 @@ func Protocols() (Parsed, bool) { func ProtocolsBgp() (Parsed, bool) { p, from_cache := Protocols() + if p == nil { + return p, from_cache + } protocols := p["protocols"].([]string) bgpProto := Parsed{} @@ -144,7 +159,8 @@ func ProtocolsBgp() (Parsed, bool) { } } - return Parsed{"protocols": bgpProto}, from_cache + + return Parsed{"protocols": bgpProto, "ttl": p["ttl"]}, from_cache } func Symbols() (Parsed, bool) { @@ -198,3 +214,7 @@ func RoutesLookupProtocol(net string, protocol string) (Parsed, bool) { return RunAndParse("route for '"+net+"' protocol '"+protocol+"' all", parseRoutes) } + +func RoutesPeer(peer string) (Parsed, bool) { + return RunAndParse("route export '"+peer+"'", parseRoutes) +} diff --git a/bird/config.go b/bird/config.go index 89bf0d8..e3efb07 100644 --- a/bird/config.go +++ b/bird/config.go @@ -16,7 +16,7 @@ type BirdConfig struct { } type RateLimitConfig struct { - Reqs int `toml:"requests_per_minute"` + Reqs int Max int `toml:"requests_per_minute"` Enabled bool } diff --git a/bird/parser.go b/bird/parser.go index 8a1fdde..2173cbe 100644 --- a/bird/parser.go +++ b/bird/parser.go @@ -46,9 +46,9 @@ func parseStatus(input []byte) Parsed { start_line_rx := regexp.MustCompile(`^BIRD\s([0-9\.]+)\s*$`) router_id_rx := regexp.MustCompile(`^Router\sID\sis\s([0-9\.]+)\s*$`) - current_server_rx := regexp.MustCompile(`^Current\sserver\stime\sis\s([0-9\-]+)\s([0-9\:]+)\s*$`) - last_reboot_rx := regexp.MustCompile(`^Last\sreboot\son\s([0-9\-]+)\s([0-9\:]+)\s*$`) - last_reconfig_rx := regexp.MustCompile(`^Last\sreconfiguration\son\s([0-9\-]+)\s([0-9\:]+)\s*$`) + current_server_rx := regexp.MustCompile(`^Current\sserver\stime\sis\s([0-9\-]+\s[0-9\:]+)\s*$`) + last_reboot_rx := regexp.MustCompile(`^Last\sreboot\son\s([0-9\-]+\s[0-9\:]+)\s*$`) + last_reconfig_rx := regexp.MustCompile(`^Last\sreconfiguration\son\s([0-9\-]+\s[0-9\:]+)\s*$`) for _, line := range lines { if start_line_rx.MatchString(line) { @@ -128,10 +128,11 @@ func parseRoutes(input []byte) Parsed { route := Parsed{} start_def_rx := regexp.MustCompile(`^([0-9a-f\.\:\/]+)\s+via\s+([0-9a-f\.\:]+)\s+on\s+(\w+)\s+\[([\w\.:]+)\s+([0-9\-\:\s]+)(?:\s+from\s+([0-9a-f\.\:\/]+)){0,1}\]\s+(?:(\*)\s+){0,1}\((\d+)(?:\/\d+){0,1}\).*`) - second_rx := regexp.MustCompile(`^\s+via\s+([0-9a-f\.\:]+)\s+on\s+(\w+)\s+\[(\w+)\s+([0-9\-\:]+)(?:\s+from\s+([0-9a-f\.\:\/]+)){0,1}\]\s+(?:(\*)\s+){0,1}\((\d+)(?:\/\d+){0,1}\).*$`) + second_rx := regexp.MustCompile(`^\s+via\s+([0-9a-f\.\:]+)\s+on\s+([\w+)\s+\[([\w\.]+)\s+([0-9\-\:\s]+)(?:\s+from\s+([0-9a-f\.\:\/]+)){0,1}\]\s+(?:(\*)\s+){0,1}\((\d+)(?:\/\d+){0,1}\).*$`) type_rx := regexp.MustCompile(`^\s+Type:\s+(.*)\s*$`) bgp_rx := regexp.MustCompile(`^\s+BGP.(\w+):\s+(.+)\s*$`) community_rx := regexp.MustCompile(`^\((\d+),(\d+)\)`) + large_community_rx := regexp.MustCompile(`^\((\d+),(\d+),(\d+)\)`) for _, line := range lines { if specialLine(line) || (len(route) == 0 && emptyLine(line)) { continue @@ -186,6 +187,18 @@ func parseRoutes(input []byte) Parsed { } } bgp["communities"] = communities + } else if groups[1] == "large_community" { + communities := [][]int64{} + for _, community := range strings.Split(groups[2], " ") { + if large_community_rx.MatchString(community) { + com_groups := large_community_rx.FindStringSubmatch(community) + maj := parseInt(com_groups[1]) + min := parseInt(com_groups[2]) + pat := parseInt(com_groups[3]) + communities = append(communities, []int64{maj, min, pat}) + } + } + bgp["large_communities"] = communities } else if groups[1] == "as_path" { bgp["as_path"] = strings.Split(groups[2], " ") } else { diff --git a/bird/status.go b/bird/status.go index fa38a34..609b357 100644 --- a/bird/status.go +++ b/bird/status.go @@ -24,8 +24,6 @@ func lastReconfigTimestampFromFileStat(filename string) string { func lastReconfigTimestampFromFileContent(filename string, regex string) string { rx := regexp.MustCompile(regex) - fmt.Println("Using regex:", regex) - // Read config file linewise file, err := os.Open(filename) if err != nil { diff --git a/birdwatcher.go b/birdwatcher.go index ac55877..7554f50 100644 --- a/birdwatcher.go +++ b/birdwatcher.go @@ -12,6 +12,9 @@ import ( "github.com/julienschmidt/httprouter" ) +//go:generate versionize +var VERSION = "1.7.8" + func isModuleEnabled(module string, modulesEnabled []string) bool { for _, enabled := range modulesEnabled { if enabled == module { @@ -26,6 +29,7 @@ func makeRouter(config endpoints.ServerConfig) *httprouter.Router { r := httprouter.New() if isModuleEnabled("status", whitelist) { + r.GET("/version", endpoints.Version(VERSION)) r.GET("/status", endpoints.Endpoint(endpoints.Status)) } if isModuleEnabled("protocols", whitelist) { @@ -65,6 +69,9 @@ func makeRouter(config endpoints.ServerConfig) *httprouter.Router { r.GET("/route/net/:net", endpoints.Endpoint(endpoints.RouteNet)) r.GET("/route/net/:net/table/:table", endpoints.Endpoint(endpoints.RouteNetTable)) } + if isModuleEnabled("routes_peer", whitelist) { + r.GET("/routes/peer", endpoints.Endpoint(endpoints.RoutesPeer)) + } return r } @@ -93,6 +100,7 @@ func main() { bird6 := flag.Bool("6", false, "Use bird6 instead of bird") flag.Parse() + endpoints.VERSION = VERSION bird.InstallRateLimitReset() // Load configurations conf, err := LoadConfigs([]string{ diff --git a/endpoints/endpoint.go b/endpoints/endpoint.go index 587bb9d..dfe07b7 100644 --- a/endpoints/endpoint.go +++ b/endpoints/endpoint.go @@ -70,3 +70,10 @@ func Endpoint(wrapped endpoint) httprouter.Handle { w.Write(js) } } + +func Version(version string) httprouter.Handle { + return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + w.Header().Set("Content-Type", "text/plain") + w.Write([]byte(version)) + } +} diff --git a/endpoints/routes.go b/endpoints/routes.go index 36debdc..0ceff26 100644 --- a/endpoints/routes.go +++ b/endpoints/routes.go @@ -61,3 +61,17 @@ func RouteNet(r *http.Request, ps httprouter.Params) (bird.Parsed, bool) { func RouteNetTable(r *http.Request, ps httprouter.Params) (bird.Parsed, bool) { return bird.RoutesLookupTable(ps.ByName("net"), ps.ByName("table")) } + +func RoutesPeer(r *http.Request, ps httprouter.Params) (bird.Parsed, bool) { + qs := r.URL.Query() + peerl := qs["peer"] + if len(peerl) != 1 { + return bird.Parsed{"error": "need a peer as single query parameter"}, false + } + + peer, err := ValidateProtocolParam(peerl[0]) + if err != nil { + return bird.Parsed{"error": fmt.Sprintf("%s", err)}, false + } + return bird.RoutesPeer(peer) +} diff --git a/endpoints/utils.go b/endpoints/utils.go index 1bf3b41..904c4ed 100644 --- a/endpoints/utils.go +++ b/endpoints/utils.go @@ -17,10 +17,13 @@ type APIInfo struct { CacheStatus CacheStatus `json:"cache_status"` } +// go generate does not work in subdirectories. Beautious. +var VERSION string + func GetApiInfo(from_cache bool) *APIInfo { ai := &APIInfo{} - ai.Version = "1.0" + ai.Version = VERSION ai.ResultFromCache = from_cache return ai diff --git a/etc/ecix/birdwatcher.conf b/etc/ecix/birdwatcher.conf index 5a5e20c..0f184e7 100644 --- a/etc/ecix/birdwatcher.conf +++ b/etc/ecix/birdwatcher.conf @@ -3,10 +3,10 @@ # [server] -# Restrict access to certain IPs. Leave empty to allow from all. +# Restrict access to certain IPs. Leave empty to allow from all. allow_from = [] -# All modules: +# All modules: # status # protocols # protocols_bgp @@ -21,7 +21,7 @@ allow_from = [] # routes_filtered # routes_prefixed # -modules_enabled = ["status", "protocols_bgp", "routes_protocol"] +modules_enabled = ["status", "protocols_bgp", "routes_protocol", "routes_peer"] [status] #