mirror of
https://github.com/alice-lg/birdwatcher.git
synced 2025-03-09 00:00:05 +01:00
Merge branch 'master' into rate-limit
* master: (35 commits) added prefixes allow rpm dir to exist added filtered routes added proto counts to validated methods rebuild rpm fix charset validator on utf-8 rebuild rpm whitelist routes added filtering for protocol params rebuild rpm another day, another regex fix. rebuild rpm ipv6 compat regex fixed cast, removed debug output fixed example path use nested map rebuild rpm fixed fieldname and ipv6 regex rebuild rpm added filtered fields ...
This commit is contained in:
commit
88b01e3c83
22 changed files with 572 additions and 39 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -5,3 +5,5 @@ birdwatcher
|
|||
birdwatcher-*
|
||||
|
||||
DIST/
|
||||
|
||||
*.local.*
|
||||
|
|
27
Makefile
27
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
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
1.0.0
|
||||
1.5.1
|
||||
|
|
44
bird/bird.go
44
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)
|
||||
|
|
16
bird/config.go
Normal file
16
bird/config.go
Normal file
|
@ -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"`
|
||||
}
|
|
@ -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*$`)
|
||||
|
|
49
bird/status.go
Normal file
49
bird/status.go
Normal file
|
@ -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 ""
|
||||
}
|
23
bird/status_test.go
Normal file
23
bird/status_test.go
Normal file
|
@ -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)
|
||||
}
|
123
birdwatcher.go
123
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))
|
||||
}
|
||||
|
|
55
config.go
Normal file
55
config.go
Normal file
|
@ -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
|
||||
}
|
17
config_test.go
Normal file
17
config_test.go
Normal file
|
@ -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)
|
||||
}
|
7
endpoints/config.go
Normal file
7
endpoints/config.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
package endpoints
|
||||
|
||||
// Endpoints / Server configuration
|
||||
type ServerConfig struct {
|
||||
AllowFrom []string `toml:"allow_from"`
|
||||
ModulesEnabled []string `toml:"modules_enabled"`
|
||||
}
|
|
@ -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)
|
||||
|
|
55
endpoints/filter.go
Normal file
55
endpoints/filter.go
Normal file
|
@ -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.:/")
|
||||
}
|
39
endpoints/filter_test.go
Normal file
39
endpoints/filter_test.go
Normal file
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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) {
|
||||
|
|
51
etc/ecix/birdwatcher.conf
Normal file
51
etc/ecix/birdwatcher.conf
Normal file
|
@ -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"
|
||||
|
||||
|
11
install/systemd/birdwatcher4.service
Normal file
11
install/systemd/birdwatcher4.service
Normal file
|
@ -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
|
11
install/systemd/birdwatcher6.service
Normal file
11
install/systemd/birdwatcher6.service
Normal file
|
@ -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
|
|
@ -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'
|
||||
|
|
@ -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'
|
||||
|
Loading…
Add table
Reference in a new issue