diff --git a/.gitignore b/.gitignore index 251ae28..c16085f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ birdwatcher birdwatcher-* DIST/ + +*.local.* diff --git a/Makefile b/Makefile index c3315c6..bf75803 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,8 @@ VERSION=$(APP_VERSION)_$(shell git rev-parse --short HEAD) BUILD_SERVER='' +SYSTEM_INIT=systemd + DIST=DIST/ REMOTE_DIST=$(PROG)-$(DIST) @@ -44,10 +46,22 @@ endif dist: clean linux mkdir -p $(DIST)opt/ecix/birdwatcher/bin - mkdir -p $(DIST)etc/init + mkdir -p $(DIST)etc/ecix + +ifeq ($(SYSTEM_INIT), systemd) + # Installing systemd services + mkdir -p $(DIST)usr/lib/systemd/system/ + cp install/systemd/* $(DIST)usr/lib/systemd/system/. +else + # Installing upstart configuration + mkdir -p $(DIST)/etc/init/ + cp install/upstart/init/* $(DIST)etc/init/. +endif + # Copy config and startup script - cp etc/init/* DIST/etc/init/. + cp etc/ecix/* DIST/etc/ecix/. + rm -f DIST/etc/ecix/*.local.* # Copy bin cp $(PROG)-linux-$(ARCH) DIST/opt/ecix/birdwatcher/bin/. @@ -56,22 +70,25 @@ dist: clean linux rpm: dist # Clear tmp failed build (if any) - rm -f $(RPM) - rm -fr $(LOCAL_RPMS) - mkdir $(LOCAL_RPMS) + mkdir -p $(LOCAL_RPMS) # Create RPM from dist fpm -s dir -t rpm -n $(PROG) -v $(VERSION) -C $(DIST) \ + --config-files /etc/ecix/birdwatcher.conf \ opt/ etc/ mv $(RPM) $(LOCAL_RPMS) remote_rpm: build_server dist + + mkdir -p $(LOCAL_RPMS) + # Copy distribution to build server ssh $(BUILD_SERVER) -- rm -rf $(REMOTE_DIST) scp -r $(DIST) $(BUILD_SERVER):$(REMOTE_DIST) ssh $(BUILD_SERVER) -- fpm -s dir -t rpm -n $(PROG) -v $(VERSION) -C $(REMOTE_DIST) \ + --config-files /etc/ecix/birdwatcher.conf \ opt/ etc/ # Get rpm from server diff --git a/VERSION b/VERSION index 3eefcb9..26ca594 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.0 +1.5.1 diff --git a/bird/bird.go b/bird/bird.go index af0dc60..3feeeb3 100644 --- a/bird/bird.go +++ b/bird/bird.go @@ -7,7 +7,8 @@ import ( "time" ) -var BirdCmd string +var ClientConf BirdConfig +var StatusConf StatusConfig var rateLimit = 0 var MAX_RATE = 5 @@ -34,7 +35,7 @@ func toCache(key string, val Parsed) { func Run(args string) ([]byte, error) { args = "show " + args argsList := strings.Split(args, " ") - return exec.Command(BirdCmd, argsList...).Output() + return exec.Command(ClientConf.BirdCmd, argsList...).Output() } func InstallRateLimitReset() { @@ -79,7 +80,36 @@ func RunAndParse(cmd string, parser func([]byte) Parsed) (Parsed, bool) { } func Status() (Parsed, bool) { - return RunAndParse("status", parseStatus) + birdStatus, ok := RunAndParse("status", parseStatus) + status := birdStatus["status"].(Parsed) + + // Last Reconfig Timestamp source: + var lastReconfig string + switch StatusConf.ReconfigTimestampSource { + case "bird": + lastReconfig = status["last_reconfig"].(string) + break + case "config_modified": + lastReconfig = lastReconfigTimestampFromFileStat( + ClientConf.ConfigFilename, + ) + case "config_regex": + lastReconfig = lastReconfigTimestampFromFileContent( + ClientConf.ConfigFilename, + StatusConf.ReconfigTimestampMatch, + ) + } + + status["last_reconfig"] = lastReconfig + + // Filter fields + for _, field := range StatusConf.FilterFields { + status[field] = nil + } + + birdStatus["status"] = status + + return birdStatus, ok } func Protocols() (Parsed, bool) { @@ -106,6 +136,10 @@ func Symbols() (Parsed, bool) { return RunAndParse("symbols", parseSymbols) } +func RoutesPrefixed(prefix string) (Parsed, bool) { + return RunAndParse("route all '"+prefix+"'", parseRoutes) +} + func RoutesProto(protocol string) (Parsed, bool) { return RunAndParse("route protocol '"+protocol+"' all", parseRoutes) @@ -116,6 +150,10 @@ func RoutesProtoCount(protocol string) (Parsed, bool) { parseRoutesCount) } +func RoutesFiltered(protocol string) (Parsed, bool) { + return RunAndParse("route protocol '"+protocol+"' filtered", parseRoutes) +} + func RoutesExport(protocol string) (Parsed, bool) { return RunAndParse("route export '"+protocol+"' all", parseRoutes) diff --git a/bird/config.go b/bird/config.go new file mode 100644 index 0000000..b5c6788 --- /dev/null +++ b/bird/config.go @@ -0,0 +1,16 @@ +package bird + +// Birdwatcher Birdc Configuration + +type StatusConfig struct { + ReconfigTimestampSource string `toml:"reconfig_timestamp_source"` + ReconfigTimestampMatch string `toml:"reconfig_timestamp_match"` + + FilterFields []string `toml:"filter_fields"` +} + +type BirdConfig struct { + Listen string + ConfigFilename string `toml:"config"` + BirdCmd string `toml:"birdc"` +} diff --git a/bird/parser.go b/bird/parser.go index 908f8a1..8a1fdde 100644 --- a/bird/parser.go +++ b/bird/parser.go @@ -127,10 +127,10 @@ func parseRoutes(input []byte) Parsed { routes := []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+from\s+([0-9a-f\.\:\/]+)){0,1}\]\s+(?:(\*)\s+){0,1}\((\d+)(?:\/\d+){0,1}\).*$`) + 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}\).*$`) type_rx := regexp.MustCompile(`^\s+Type:\s+(.*)\s*$`) - bgp_rx := regexp.MustCompile(`^\s+BGP.(\w+):\s+(\w+)\s*$`) + bgp_rx := regexp.MustCompile(`^\s+BGP.(\w+):\s+(.+)\s*$`) community_rx := regexp.MustCompile(`^\((\d+),(\d+)\)`) for _, line := range lines { if specialLine(line) || (len(route) == 0 && emptyLine(line)) { @@ -186,6 +186,8 @@ func parseRoutes(input []byte) Parsed { } } bgp["communities"] = communities + } else if groups[1] == "as_path" { + bgp["as_path"] = strings.Split(groups[2], " ") } else { bgp[groups[1]] = groups[2] } @@ -242,7 +244,7 @@ func parseBgp(input string) Parsed { lines := getLinesFromString(input) route_changes := Parsed{} - bgp_rx := regexp.MustCompile(`^([\w\.]+)\s+BGP\s+(\w+)\s+(\w+)\s+([0-9]{4}-[0-9]{2}-[0-9]{2}\s+[0-9]{2}:[0-9]{2}:[0-9]{2})\s*(\w+)?.*$`) + bgp_rx := regexp.MustCompile(`^([\w\.:]+)\s+BGP\s+(\w+)\s+(\w+)\s+([0-9]{4}-[0-9]{2}-[0-9]{2}\s+[0-9]{2}:[0-9]{2}:[0-9]{2})\s*(\w+)?.*$`) num_val_rx := regexp.MustCompile(`^\s+([^:]+):\s+([\d]+)\s*$`) str_val_rx := regexp.MustCompile(`^\s+([^:]+):\s+(.+)\s*$`) routes_rx := regexp.MustCompile(`^\s+Routes:\s+(\d+)\s+imported,\s+(\d+)\s+filtered,\s+(\d+)\s+exported,\s+(\d+)\s+preferred\s*$`) diff --git a/bird/status.go b/bird/status.go new file mode 100644 index 0000000..fa38a34 --- /dev/null +++ b/bird/status.go @@ -0,0 +1,49 @@ +package bird + +import ( + "bufio" + "fmt" + "os" + "regexp" +) + +// Get last reconfig timestamp from file modification date +func lastReconfigTimestampFromFileStat(filename string) string { + info, err := os.Stat(filename) + if err != nil { + return fmt.Sprintf("Could not fetch file modified timestamp: %s", err) + } + + modTime := info.ModTime().UTC() + buf, _ := modTime.MarshalJSON() + + return string(buf) +} + +// Parse config file linewise, find matching line and extract date +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 { + return fmt.Sprintf("Could not read: %s", err) + } + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + matches := rx.FindStringSubmatch(scanner.Text()) + if len(matches) > 0 { + return matches[1] + } + } + + if err := scanner.Err(); err != nil { + return fmt.Sprintf("Error reading config: %s", err) + } + + return "" +} diff --git a/bird/status_test.go b/bird/status_test.go new file mode 100644 index 0000000..da98c52 --- /dev/null +++ b/bird/status_test.go @@ -0,0 +1,23 @@ +package bird + +// Created: 2016-12-01 14:15:00 + +import ( + "testing" +) + +func TestReconfigTimestampFromStat(t *testing.T) { + + // Just get the modification date of this file + ts := lastReconfigTimestampFromFileStat("./status_test.go") + t.Log(ts) + + ts = lastReconfigTimestampFromFileStat("./___i_do_not_exist___") + t.Log(ts) +} + +func TestReconfigTimestampFromContent(t *testing.T) { + + ts := lastReconfigTimestampFromFileContent("./status_test.go", "// Created: (.*)") + t.Log(ts) +} diff --git a/birdwatcher.go b/birdwatcher.go index 8e8b0f4..5d783fb 100644 --- a/birdwatcher.go +++ b/birdwatcher.go @@ -8,40 +8,117 @@ import ( "github.com/ecix/birdwatcher/bird" "github.com/ecix/birdwatcher/endpoints" + "github.com/julienschmidt/httprouter" ) -func makeRouter() *httprouter.Router { +func isModuleEnabled(module string, modulesEnabled []string) bool { + for _, enabled := range modulesEnabled { + if enabled == module { + return true + } + } + return false +} + +func makeRouter(config endpoints.ServerConfig) *httprouter.Router { + whitelist := config.ModulesEnabled + r := httprouter.New() - r.GET("/status", endpoints.Endpoint(endpoints.Status)) - r.GET("/protocols/bgp", endpoints.Endpoint(endpoints.Bgp)) - r.GET("/symbols", endpoints.Endpoint(endpoints.Symbols)) - r.GET("/symbols/tables", endpoints.Endpoint(endpoints.SymbolTables)) - r.GET("/symbols/protocols", endpoints.Endpoint(endpoints.SymbolProtocols)) - r.GET("/routes/protocol/:protocol", endpoints.Endpoint(endpoints.ProtoRoutes)) - r.GET("/routes/table/:table", endpoints.Endpoint(endpoints.TableRoutes)) - r.GET("/routes/count/protocol/:protocol", endpoints.Endpoint(endpoints.ProtoCount)) - r.GET("/routes/count/table/:table", endpoints.Endpoint(endpoints.TableCount)) - r.GET("/route/net/:net", endpoints.Endpoint(endpoints.RouteNet)) - r.GET("/route/net/:net/table/:table", endpoints.Endpoint(endpoints.RouteNetTable)) - r.GET("/protocols", endpoints.Endpoint(endpoints.Protocols)) + if isModuleEnabled("status", whitelist) { + r.GET("/status", endpoints.Endpoint(endpoints.Status)) + } + if isModuleEnabled("protocols", whitelist) { + r.GET("/protocols", endpoints.Endpoint(endpoints.Protocols)) + } + if isModuleEnabled("protocols_bgp", whitelist) { + r.GET("/protocols/bgp", endpoints.Endpoint(endpoints.Bgp)) + } + if isModuleEnabled("symbols", whitelist) { + r.GET("/symbols", endpoints.Endpoint(endpoints.Symbols)) + } + if isModuleEnabled("symbols_tables", whitelist) { + r.GET("/symbols/tables", endpoints.Endpoint(endpoints.SymbolTables)) + } + if isModuleEnabled("symbols_protocols", whitelist) { + r.GET("/symbols/protocols", endpoints.Endpoint(endpoints.SymbolProtocols)) + } + if isModuleEnabled("routes_protocol", whitelist) { + r.GET("/routes/protocol/:protocol", endpoints.Endpoint(endpoints.ProtoRoutes)) + } + if isModuleEnabled("routes_table", whitelist) { + r.GET("/routes/table/:table", endpoints.Endpoint(endpoints.TableRoutes)) + } + if isModuleEnabled("routes_count_protocol", whitelist) { + r.GET("/routes/count/protocol/:protocol", endpoints.Endpoint(endpoints.ProtoCount)) + } + if isModuleEnabled("routes_count_table", whitelist) { + r.GET("/routes/count/table/:table", endpoints.Endpoint(endpoints.TableCount)) + } + if isModuleEnabled("routes_filtered", whitelist) { + r.GET("routes/filtered/:protocol", endpoints.Endpoint(endpoints.RoutesFiltered)) + } + if isModuleEnabled("routes_prefixed", whitelist) { + r.GET("routes/prefix/:prefix", endpoints.Endpoint(endpoints.RoutesPrefixed)) + } + if isModuleEnabled("route_net", whitelist) { + r.GET("/route/net/:net", endpoints.Endpoint(endpoints.RouteNet)) + r.GET("/route/net/:net/table/:table", endpoints.Endpoint(endpoints.RouteNetTable)) + } return r } +// Print service information like, listen address, +// access restrictions and configuration flags +func PrintServiceInfo(conf *Config, birdConf bird.BirdConfig) { + // General Info + log.Println("Starting Birdwatcher") + log.Println(" Using:", birdConf.BirdCmd) + log.Println(" Listen:", birdConf.Listen) + + // Endpoint Info + if len(conf.Server.AllowFrom) == 0 { + log.Println(" AllowFrom: ALL") + } else { + log.Println(" AllowFrom:", strings.Join(conf.Server.AllowFrom, ", ")) + } + + log.Println(" ModulesEnabled:") + for _, m := range conf.Server.ModulesEnabled { + log.Println(" -", m) + } +} + func main() { - port := flag.String("port", - "29184", - "The port the birdwatcher should run on") - birdc := flag.String("birdc", - "birdc", - "The birdc command to use (for IPv6, use birdc6)") + bird6 := flag.Bool("6", false, "Use bird6 instead of bird") flag.Parse() - bird.BirdCmd = *birdc bird.InstallRateLimitReset() + // Load configurations + conf, err := LoadConfigs([]string{ + "./etc/ecix/birdwatcher.conf", + "/etc/ecix/birdwatcher.conf", + "./etc/ecix/birdwatcher.local.conf", + }) - r := makeRouter() + if err != nil { + log.Fatal("Loading birdwatcher configuration failed:", err) + } - realPort := strings.Join([]string{":", *port}, "") - log.Fatal(http.ListenAndServe(realPort, r)) + // Get config according to flags + birdConf := conf.Bird + if *bird6 { + birdConf = conf.Bird6 + } + + PrintServiceInfo(conf, birdConf) + + // Configuration + bird.ClientConf = birdConf + bird.StatusConf = conf.Status + endpoints.Conf = conf.Server + + // Make server + r := makeRouter(conf.Server) + log.Fatal(http.ListenAndServe(birdConf.Listen, r)) } diff --git a/config.go b/config.go new file mode 100644 index 0000000..6127f0f --- /dev/null +++ b/config.go @@ -0,0 +1,55 @@ +package main + +// Birdwatcher Configuration + +import ( + "fmt" + + "github.com/BurntSushi/toml" + "github.com/imdario/mergo" + + "github.com/ecix/birdwatcher/bird" + "github.com/ecix/birdwatcher/endpoints" +) + +type Config struct { + Server endpoints.ServerConfig + + Status bird.StatusConfig + Bird bird.BirdConfig + Bird6 bird.BirdConfig +} + +// Try to load configfiles as specified in the files +// list. For example: +// +// ./etc/birdwatcher/birdwatcher.conf +// /etc/birdwatcher/birdwatcher.conf +// ./etc/birdwatcher/birdwatcher.local.conf +// +// +func LoadConfigs(configFiles []string) (*Config, error) { + config := &Config{} + hasConfig := false + var confError error + + for _, filename := range configFiles { + tmp := &Config{} + _, err := toml.DecodeFile(filename, tmp) + if err != nil { + continue + } else { + hasConfig = true + // Merge configs + if err := mergo.Merge(config, tmp); err != nil { + return nil, err + } + } + } + + if !hasConfig { + confError = fmt.Errorf("Could not load any config file") + } + + return config, confError +} diff --git a/config_test.go b/config_test.go new file mode 100644 index 0000000..adf7376 --- /dev/null +++ b/config_test.go @@ -0,0 +1,17 @@ +package main + +import ( + "testing" +) + +func TestLoadConfigs(t *testing.T) { + t.Log("Loading configs") + res, err := LoadConfigs([]string{ + "./etc/ecix/birdwatcher.conf", + "/etc/ecix/birdwatcher.conf", + "./etc/ecix/birdwatcher.local.conf", + }) + + t.Log(res) + t.Log(err) +} diff --git a/endpoints/config.go b/endpoints/config.go new file mode 100644 index 0000000..a924c87 --- /dev/null +++ b/endpoints/config.go @@ -0,0 +1,7 @@ +package endpoints + +// Endpoints / Server configuration +type ServerConfig struct { + AllowFrom []string `toml:"allow_from"` + ModulesEnabled []string `toml:"modules_enabled"` +} diff --git a/endpoints/endpoint.go b/endpoints/endpoint.go index 73c078f..f760e7d 100644 --- a/endpoints/endpoint.go +++ b/endpoints/endpoint.go @@ -1,6 +1,10 @@ package endpoints import ( + "fmt" + "log" + "strings" + "encoding/json" "net/http" @@ -8,10 +12,43 @@ import ( "github.com/julienschmidt/httprouter" ) +var Conf ServerConfig + +func CheckAccess(req *http.Request) error { + if len(Conf.AllowFrom) == 0 { + return nil // AllowFrom ALL + } + + // Extract IP + tokens := strings.Split(req.RemoteAddr, ":") + ip := strings.Join(tokens[:len(tokens)-1], ":") + ip = strings.Replace(ip, "[", "", -1) + ip = strings.Replace(ip, "]", "", -1) + + // Check Access + for _, allowed := range Conf.AllowFrom { + if ip == allowed { + return nil + } + } + + // Log this request + log.Println("Rejecting access from:", ip) + + return fmt.Errorf("%s is not allowed to access this service.", ip) +} + func Endpoint(wrapped func(httprouter.Params) (bird.Parsed, bool)) httprouter.Handle { return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + + // Access Control + if err := CheckAccess(r); err != nil { + http.Error(w, err.Error(), http.StatusForbidden) + return + } + res := make(map[string]interface{}) ret, from_cache := wrapped(ps) diff --git a/endpoints/filter.go b/endpoints/filter.go new file mode 100644 index 0000000..6386cb3 --- /dev/null +++ b/endpoints/filter.go @@ -0,0 +1,55 @@ +package endpoints + +import ( + "fmt" +) + +/* + * Parameter / Request Validation + */ + +// Check if the value is not longer than a given length +func ValidateLength(value string, maxLength int) error { + if len(value) > maxLength { + return fmt.Errorf("Provided param value is too long.") + } + return nil +} + +func ValidateCharset(value string, alphabet string) error { + for _, check := range value { + ok := false + for _, char := range alphabet { + if char == check { + ok = true + break + } + } + if !ok { + return fmt.Errorf("Invalid character in param value") + } + } + return nil +} + +func ValidateLengthAndCharset(value string, maxLength int, alphabet string) (string, error) { + // Check length + if err := ValidateLength(value, maxLength); err != nil { + return "", err + } + + // Check input + if err := ValidateCharset(value, alphabet); err != nil { + return "", err + } + + return value, nil +} + +func ValidateProtocolParam(value string) (string, error) { + return ValidateLengthAndCharset(value, 80, "ID_AS:.abcdef1234567890") +} + +func ValidatePrefixParam(value string) (string, error) { + return ValidateLengthAndCharset(value, 80, "1234567890abcdef.:/") +} diff --git a/endpoints/filter_test.go b/endpoints/filter_test.go new file mode 100644 index 0000000..b57f10a --- /dev/null +++ b/endpoints/filter_test.go @@ -0,0 +1,39 @@ +package endpoints + +import ( + "testing" +) + +func TestValidateProtocol(t *testing.T) { + + validProtocols := []string{ + "ID421_AS11171_123.8.127.19", + "ID429_AS12240_2222:7af8:8:05:01:30bb:0:1", + "AI421_AS11171_123..8..127..19", + } + + invalidProtocols := []string{ + "ID421_AS11171_123.8.127.lö19", + "Test123", + "ThisValueIsTooLong12345678901234567890123456789012345678901234567890123456789012345678901234567890", + } + + // Valid protocol values + for _, param := range validProtocols { + t.Log("Testing valid protocol:", param) + _, err := ValidateProtocolParam(param) + if err != nil { + t.Error(param, "should be a valid protocol param") + } + } + + // Invalid protocol values + for _, param := range invalidProtocols { + t.Log("Testing invalid protocol:", param) + _, err := ValidateProtocolParam(param) + if err == nil { + t.Error(param, "should be an invalid protocol param") + } + } + +} diff --git a/endpoints/routes.go b/endpoints/routes.go index 961d9b1..c8a95a4 100644 --- a/endpoints/routes.go +++ b/endpoints/routes.go @@ -1,12 +1,34 @@ package endpoints import ( + "fmt" + "github.com/ecix/birdwatcher/bird" "github.com/julienschmidt/httprouter" ) func ProtoRoutes(ps httprouter.Params) (bird.Parsed, bool) { - return bird.RoutesProto(ps.ByName("protocol")) + protocol, err := ValidateProtocolParam(ps.ByName("protocol")) + if err != nil { + return bird.Parsed{"error": fmt.Sprintf("%s", err)}, false + } + return bird.RoutesProto(protocol) +} + +func RoutesFiltered(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.RoutesFiltered(protocol) +} + +func RoutesPrefixed(ps httprouter.Params) (bird.Parsed, bool) { + prefix, err := ValidatePrefixParam(ps.ByName("prefix")) + if err != nil { + return bird.Parsed{"error": fmt.Sprintf("%s", err)}, false + } + return bird.RoutesPrefixed(prefix) } func TableRoutes(ps httprouter.Params) (bird.Parsed, bool) { @@ -14,7 +36,11 @@ func TableRoutes(ps httprouter.Params) (bird.Parsed, bool) { } func ProtoCount(ps httprouter.Params) (bird.Parsed, bool) { - return bird.RoutesProtoCount(ps.ByName("protocol")) + protocol, err := ValidateProtocolParam(ps.ByName("protocol")) + if err != nil { + return bird.Parsed{"error": fmt.Sprintf("%s", err)}, false + } + return bird.RoutesProtoCount(protocol) } func TableCount(ps httprouter.Params) (bird.Parsed, bool) { diff --git a/etc/ecix/birdwatcher.conf b/etc/ecix/birdwatcher.conf new file mode 100644 index 0000000..93cc840 --- /dev/null +++ b/etc/ecix/birdwatcher.conf @@ -0,0 +1,51 @@ + + +# +# Birdwatcher Configuration +# + +[server] +# Restrict access to certain IPs. Leave empty to allow from all. +allow_from = [] + +# All modules: +# status +# protocols +# protocols_bgp +# symbols +# symbols_tables +# symbols_protocols +# routes_protocol +# routes_table +# routes_count_protocol +# routes_count_table +# route_net +# routes_filtered +# routes_prefixed +# +modules_enabled = ["status", "protocols_bgp", "routes_protocol"] + +[status] +# +# Where to get the reconfigure timestamp from: +# Available sources: bird, config_regex, config_modified +# +reconfig_timestamp_source = "bird" +reconfig_timestamp_match = "# Created: (.*)" + +# Remove fields e.g. last_reboot +filter_fields = [] + + +[bird] +listen = "0.0.0.0:29188" +config = "/etc/bird.conf" +birdc = "birdc" + + +[bird6] +listen = "0.0.0.0:29189" +config = "/etc/bird6.conf" +birdc = "birdc6" + + diff --git a/install/systemd/birdwatcher4.service b/install/systemd/birdwatcher4.service new file mode 100644 index 0000000..a4b6563 --- /dev/null +++ b/install/systemd/birdwatcher4.service @@ -0,0 +1,11 @@ +[Unit] +Description=BIRDwatcher IPv4 +Wants=network.target +After=network.target + +[Service] +Type=simple +ExecStart=/opt/ecix/birdwatcher/bin/birdwatcher-linux-amd64 + +[Install] +WantedBy=multi-user.target diff --git a/install/systemd/birdwatcher6.service b/install/systemd/birdwatcher6.service new file mode 100644 index 0000000..940d53e --- /dev/null +++ b/install/systemd/birdwatcher6.service @@ -0,0 +1,11 @@ +[Unit] +Description=BIRDwatcher IPv6 +Wants=network.target +After=network.target + +[Service] +Type=simple +ExecStart=/opt/ecix/birdwatcher/bin/birdwatcher-linux-amd64 -6 + +[Install] +WantedBy=multi-user.target diff --git a/etc/init/bird4watcher.conf b/install/upstart/init/bird4watcher.conf similarity index 61% rename from etc/init/bird4watcher.conf rename to install/upstart/init/bird4watcher.conf index 0293fff..a8834a6 100644 --- a/etc/init/bird4watcher.conf +++ b/install/upstart/init/bird4watcher.conf @@ -10,5 +10,5 @@ respawn limit 20 10 start on starting birdwatcher stop on stopping birdwatcher -exec /opt/ecix/birdwatcher/bin/birdwatcher-linux-amd64 -birdc bird -port 29184 2>&1 | logger -i -t 'BIRD4 WATCHER' +exec /opt/ecix/birdwatcher/bin/birdwatcher-linux-amd64 2>&1 | logger -i -t 'BIRD4 WATCHER' diff --git a/etc/init/bird6watcher.conf b/install/upstart/init/bird6watcher.conf similarity index 61% rename from etc/init/bird6watcher.conf rename to install/upstart/init/bird6watcher.conf index 642c095..7cada64 100644 --- a/etc/init/bird6watcher.conf +++ b/install/upstart/init/bird6watcher.conf @@ -10,5 +10,5 @@ respawn limit 20 10 start on starting birdwatcher stop on stopping birdwatcher -exec /opt/ecix/birdwatcher/bin/birdwatcher-linux-amd64 -birdc bird6 -port 29185 2>&1 | logger -i -t 'BIRD6 WATCHER' +exec /opt/ecix/birdwatcher/bin/birdwatcher-linux-amd64 -6 2>&1 | logger -i -t 'BIRD6 WATCHER' diff --git a/etc/init/birdwatcher.conf b/install/upstart/init/birdwatcher.conf similarity index 100% rename from etc/init/birdwatcher.conf rename to install/upstart/init/birdwatcher.conf