1
0
Fork 0
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:
hellerve 2016-12-13 10:29:17 +01:00
commit 88b01e3c83
22 changed files with 572 additions and 39 deletions

2
.gitignore vendored
View file

@ -5,3 +5,5 @@ birdwatcher
birdwatcher-*
DIST/
*.local.*

View file

@ -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

View file

@ -1 +1 @@
1.0.0
1.5.1

View file

@ -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
View 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"`
}

View file

@ -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
View 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
View 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)
}

View file

@ -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
View 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
View 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
View file

@ -0,0 +1,7 @@
package endpoints
// Endpoints / Server configuration
type ServerConfig struct {
AllowFrom []string `toml:"allow_from"`
ModulesEnabled []string `toml:"modules_enabled"`
}

View file

@ -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
View 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
View 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")
}
}
}

View file

@ -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
View 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"

View 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

View 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

View file

@ -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'

View file

@ -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'