2016-11-10 14:16:44 +01:00
|
|
|
package bird
|
|
|
|
|
|
|
|
import (
|
2017-05-02 12:36:35 +02:00
|
|
|
"reflect"
|
2016-11-10 15:11:32 +01:00
|
|
|
"strings"
|
2016-11-11 17:47:51 +01:00
|
|
|
"sync"
|
|
|
|
"time"
|
2017-05-02 12:36:35 +02:00
|
|
|
|
|
|
|
"os/exec"
|
2016-11-10 14:16:44 +01:00
|
|
|
)
|
|
|
|
|
2016-12-02 17:07:30 +01:00
|
|
|
var ClientConf BirdConfig
|
|
|
|
var StatusConf StatusConfig
|
2016-12-13 11:01:28 +01:00
|
|
|
var RateLimitConf struct {
|
2016-12-13 11:58:08 +01:00
|
|
|
sync.RWMutex
|
2016-12-13 11:01:28 +01:00
|
|
|
Conf RateLimitConfig
|
|
|
|
}
|
2016-11-25 15:50:59 +01:00
|
|
|
|
2016-11-11 17:47:51 +01:00
|
|
|
var Cache = struct {
|
|
|
|
sync.RWMutex
|
|
|
|
m map[string]Parsed
|
2016-11-11 17:33:33 +01:00
|
|
|
}{m: make(map[string]Parsed)}
|
|
|
|
|
2017-05-02 12:36:35 +02:00
|
|
|
var NilParse Parsed = (Parsed)(nil)
|
|
|
|
var BirdError Parsed = Parsed{"error": "bird unreachable"}
|
|
|
|
|
|
|
|
func isSpecial(ret Parsed) bool {
|
|
|
|
return reflect.DeepEqual(ret, NilParse) || reflect.DeepEqual(ret, BirdError)
|
|
|
|
}
|
|
|
|
|
2016-11-11 17:33:33 +01:00
|
|
|
func fromCache(key string) (Parsed, bool) {
|
2016-11-11 17:47:51 +01:00
|
|
|
Cache.RLock()
|
|
|
|
val, ok := Cache.m[key]
|
|
|
|
Cache.RUnlock()
|
2016-12-21 12:16:29 +01:00
|
|
|
if !ok {
|
2017-05-02 12:36:35 +02:00
|
|
|
return NilParse, false
|
2016-12-21 12:16:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
ttl, correct := val["ttl"].(time.Time)
|
|
|
|
if !correct || ttl.Before(time.Now()) {
|
2017-05-02 12:36:35 +02:00
|
|
|
return NilParse, false
|
2016-12-21 12:16:29 +01:00
|
|
|
}
|
|
|
|
|
2016-11-11 17:47:51 +01:00
|
|
|
return val, ok
|
2016-11-11 17:33:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func toCache(key string, val Parsed) {
|
2016-11-11 17:47:51 +01:00
|
|
|
val["ttl"] = time.Now().Add(5 * time.Minute)
|
|
|
|
Cache.Lock()
|
|
|
|
Cache.m[key] = val
|
|
|
|
Cache.Unlock()
|
2016-11-11 17:33:33 +01:00
|
|
|
}
|
|
|
|
|
2016-11-10 14:16:44 +01:00
|
|
|
func Run(args string) ([]byte, error) {
|
2016-11-10 16:06:38 +01:00
|
|
|
args = "show " + args
|
2016-11-10 15:11:32 +01:00
|
|
|
argsList := strings.Split(args, " ")
|
2016-12-02 17:07:30 +01:00
|
|
|
return exec.Command(ClientConf.BirdCmd, argsList...).Output()
|
2016-11-10 14:16:44 +01:00
|
|
|
}
|
|
|
|
|
2016-11-25 15:50:59 +01:00
|
|
|
func InstallRateLimitReset() {
|
|
|
|
go func() {
|
|
|
|
c := time.Tick(time.Second)
|
|
|
|
|
|
|
|
for _ = range c {
|
2016-12-13 11:01:28 +01:00
|
|
|
RateLimitConf.Lock()
|
2016-12-13 14:04:14 +01:00
|
|
|
RateLimitConf.Conf.Reqs = RateLimitConf.Conf.Max
|
2016-12-13 11:01:28 +01:00
|
|
|
RateLimitConf.Unlock()
|
2016-11-25 15:50:59 +01:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
func checkRateLimit() bool {
|
2016-12-13 11:58:08 +01:00
|
|
|
RateLimitConf.RLock()
|
|
|
|
check := !RateLimitConf.Conf.Enabled
|
|
|
|
RateLimitConf.RUnlock()
|
|
|
|
if check {
|
2016-12-13 10:49:18 +01:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2016-12-13 11:58:08 +01:00
|
|
|
RateLimitConf.RLock()
|
2016-12-13 14:04:14 +01:00
|
|
|
check = RateLimitConf.Conf.Reqs < 1
|
2016-12-13 11:58:08 +01:00
|
|
|
RateLimitConf.RUnlock()
|
|
|
|
if check {
|
2016-11-25 15:50:59 +01:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2016-12-13 11:01:28 +01:00
|
|
|
RateLimitConf.Lock()
|
2016-12-13 14:04:14 +01:00
|
|
|
RateLimitConf.Conf.Reqs -= 1
|
2016-12-13 11:01:28 +01:00
|
|
|
RateLimitConf.Unlock()
|
2016-11-25 15:50:59 +01:00
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2016-11-11 17:33:33 +01:00
|
|
|
func RunAndParse(cmd string, parser func([]byte) Parsed) (Parsed, bool) {
|
2016-11-11 17:47:51 +01:00
|
|
|
if val, ok := fromCache(cmd); ok {
|
|
|
|
return val, true
|
|
|
|
}
|
2016-11-11 17:33:33 +01:00
|
|
|
|
2016-11-25 15:50:59 +01:00
|
|
|
if !checkRateLimit() {
|
2017-05-02 12:36:35 +02:00
|
|
|
return NilParse, false
|
2016-11-25 15:50:59 +01:00
|
|
|
}
|
|
|
|
|
2016-11-10 15:11:32 +01:00
|
|
|
out, err := Run(cmd)
|
2016-11-10 14:16:44 +01:00
|
|
|
|
2016-11-10 15:11:32 +01:00
|
|
|
if err != nil {
|
|
|
|
// ignore errors for now
|
2017-05-02 12:36:35 +02:00
|
|
|
return BirdError, false
|
2016-11-10 15:11:32 +01:00
|
|
|
}
|
2016-11-10 14:16:44 +01:00
|
|
|
|
2016-11-11 17:33:33 +01:00
|
|
|
parsed := parser(out)
|
2016-11-11 17:47:51 +01:00
|
|
|
toCache(cmd, parsed)
|
|
|
|
return parsed, false
|
2016-11-10 14:16:44 +01:00
|
|
|
}
|
|
|
|
|
2016-11-11 17:33:33 +01:00
|
|
|
func Status() (Parsed, bool) {
|
2016-12-02 17:07:30 +01:00
|
|
|
birdStatus, ok := RunAndParse("status", parseStatus)
|
2017-05-02 12:36:35 +02:00
|
|
|
if isSpecial(birdStatus) {
|
2017-01-02 15:16:08 +01:00
|
|
|
return birdStatus, ok
|
|
|
|
}
|
2016-12-05 14:54:13 +01:00
|
|
|
status := birdStatus["status"].(Parsed)
|
2016-12-02 17:07:30 +01:00
|
|
|
|
|
|
|
// Last Reconfig Timestamp source:
|
|
|
|
var lastReconfig string
|
|
|
|
switch StatusConf.ReconfigTimestampSource {
|
|
|
|
case "bird":
|
2016-12-05 14:33:21 +01:00
|
|
|
lastReconfig = status["last_reconfig"].(string)
|
2016-12-02 17:07:30 +01:00
|
|
|
break
|
|
|
|
case "config_modified":
|
|
|
|
lastReconfig = lastReconfigTimestampFromFileStat(
|
|
|
|
ClientConf.ConfigFilename,
|
|
|
|
)
|
|
|
|
case "config_regex":
|
|
|
|
lastReconfig = lastReconfigTimestampFromFileContent(
|
|
|
|
ClientConf.ConfigFilename,
|
|
|
|
StatusConf.ReconfigTimestampMatch,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2016-12-05 14:33:21 +01:00
|
|
|
status["last_reconfig"] = lastReconfig
|
2016-12-02 17:07:30 +01:00
|
|
|
|
|
|
|
// Filter fields
|
2016-12-02 17:11:57 +01:00
|
|
|
for _, field := range StatusConf.FilterFields {
|
2016-12-05 14:33:21 +01:00
|
|
|
status[field] = nil
|
2016-12-02 17:11:57 +01:00
|
|
|
}
|
2016-12-02 17:07:30 +01:00
|
|
|
|
2016-12-05 14:33:21 +01:00
|
|
|
birdStatus["status"] = status
|
|
|
|
|
2016-12-02 17:07:30 +01:00
|
|
|
return birdStatus, ok
|
2016-11-10 14:16:44 +01:00
|
|
|
}
|
|
|
|
|
2016-11-11 17:33:33 +01:00
|
|
|
func Protocols() (Parsed, bool) {
|
2016-11-10 15:11:32 +01:00
|
|
|
return RunAndParse("protocols all", parseProtocols)
|
2016-11-10 14:16:44 +01:00
|
|
|
}
|
|
|
|
|
2016-11-11 17:33:33 +01:00
|
|
|
func ProtocolsBgp() (Parsed, bool) {
|
|
|
|
p, from_cache := Protocols()
|
2017-05-02 12:36:35 +02:00
|
|
|
if isSpecial(p) {
|
2017-01-02 15:16:08 +01:00
|
|
|
return p, from_cache
|
|
|
|
}
|
2016-11-11 17:47:51 +01:00
|
|
|
protocols := p["protocols"].([]string)
|
2016-11-10 18:43:29 +01:00
|
|
|
|
2016-11-11 13:32:27 +01:00
|
|
|
bgpProto := Parsed{}
|
2016-11-10 18:43:29 +01:00
|
|
|
|
2016-11-11 13:32:27 +01:00
|
|
|
for _, v := range protocols {
|
|
|
|
if strings.Contains(v, " BGP ") {
|
|
|
|
key := strings.Split(v, " ")[0]
|
|
|
|
bgpProto[key] = parseBgp(v)
|
|
|
|
}
|
|
|
|
}
|
2016-11-10 18:43:29 +01:00
|
|
|
|
2017-02-16 17:25:35 +01:00
|
|
|
return Parsed{"protocols": bgpProto, "ttl": p["ttl"]}, from_cache
|
2016-11-10 18:43:29 +01:00
|
|
|
}
|
|
|
|
|
2016-11-11 17:33:33 +01:00
|
|
|
func Symbols() (Parsed, bool) {
|
2016-11-10 15:11:32 +01:00
|
|
|
return RunAndParse("symbols", parseSymbols)
|
2016-11-10 14:16:44 +01:00
|
|
|
}
|
|
|
|
|
2016-12-08 11:09:25 +01:00
|
|
|
func RoutesPrefixed(prefix string) (Parsed, bool) {
|
2017-03-27 17:15:53 +02:00
|
|
|
return RunAndParse("route all "+prefix, parseRoutes)
|
2016-12-08 11:09:25 +01:00
|
|
|
}
|
|
|
|
|
2016-11-11 17:33:33 +01:00
|
|
|
func RoutesProto(protocol string) (Parsed, bool) {
|
2016-11-24 17:59:19 +01:00
|
|
|
return RunAndParse("route protocol '"+protocol+"' all",
|
2016-11-10 15:11:32 +01:00
|
|
|
parseRoutes)
|
2016-11-10 14:16:44 +01:00
|
|
|
}
|
|
|
|
|
2016-11-11 17:33:33 +01:00
|
|
|
func RoutesProtoCount(protocol string) (Parsed, bool) {
|
2016-11-24 17:59:19 +01:00
|
|
|
return RunAndParse("route protocol '"+protocol+"' count",
|
2016-11-10 15:11:32 +01:00
|
|
|
parseRoutesCount)
|
2016-11-10 14:16:44 +01:00
|
|
|
}
|
|
|
|
|
2016-12-06 17:20:27 +01:00
|
|
|
func RoutesFiltered(protocol string) (Parsed, bool) {
|
2017-02-22 16:09:54 +01:00
|
|
|
return RunAndParse("route filtered protocol '"+protocol+"' all", parseRoutes)
|
2016-12-06 17:20:27 +01:00
|
|
|
}
|
|
|
|
|
2016-11-11 17:33:33 +01:00
|
|
|
func RoutesExport(protocol string) (Parsed, bool) {
|
2016-11-24 17:59:19 +01:00
|
|
|
return RunAndParse("route export '"+protocol+"' all",
|
2016-11-10 15:11:32 +01:00
|
|
|
parseRoutes)
|
2016-11-10 14:16:44 +01:00
|
|
|
}
|
|
|
|
|
2017-04-07 11:11:43 +02:00
|
|
|
func RoutesNoExport(protocol string) (Parsed, bool) {
|
2017-05-29 09:50:20 +02:00
|
|
|
|
|
|
|
// In case we have a multi table setup, we have to query
|
|
|
|
// the pipe protocol.
|
2017-07-14 15:48:46 +02:00
|
|
|
if ParserConf.PerPeerTables {
|
2017-05-29 09:50:20 +02:00
|
|
|
// Replace prefix
|
2017-07-14 15:48:46 +02:00
|
|
|
protocol = TemplateExpand(
|
2017-07-14 15:50:15 +02:00
|
|
|
ParserConf.PeerProtocolMatch,
|
2017-07-14 15:48:46 +02:00
|
|
|
ParserConf.PipeProtocolTemplate,
|
|
|
|
protocol,
|
|
|
|
)
|
2017-05-29 09:50:20 +02:00
|
|
|
}
|
|
|
|
|
2017-04-07 11:11:43 +02:00
|
|
|
return RunAndParse("route noexport '"+protocol+"' all",
|
|
|
|
parseRoutes)
|
|
|
|
}
|
|
|
|
|
2016-11-11 17:33:33 +01:00
|
|
|
func RoutesExportCount(protocol string) (Parsed, bool) {
|
2016-11-24 17:59:19 +01:00
|
|
|
return RunAndParse("route export '"+protocol+"' count",
|
2016-11-10 15:11:32 +01:00
|
|
|
parseRoutesCount)
|
2016-11-10 14:16:44 +01:00
|
|
|
}
|
|
|
|
|
2016-11-11 17:33:33 +01:00
|
|
|
func RoutesTable(table string) (Parsed, bool) {
|
2016-11-24 17:59:19 +01:00
|
|
|
return RunAndParse("route table '"+table+"' all",
|
2016-11-10 15:11:32 +01:00
|
|
|
parseRoutes)
|
2016-11-10 14:16:44 +01:00
|
|
|
}
|
|
|
|
|
2016-11-11 17:33:33 +01:00
|
|
|
func RoutesTableCount(table string) (Parsed, bool) {
|
2016-11-24 17:59:19 +01:00
|
|
|
return RunAndParse("route table '"+table+"' count",
|
2016-11-10 15:11:32 +01:00
|
|
|
parseRoutesCount)
|
2016-11-10 14:16:44 +01:00
|
|
|
}
|
|
|
|
|
2016-11-11 17:33:33 +01:00
|
|
|
func RoutesLookupTable(net string, table string) (Parsed, bool) {
|
2016-11-24 17:59:19 +01:00
|
|
|
return RunAndParse("route for '"+net+"' table '"+table+"' all",
|
2016-11-10 15:11:32 +01:00
|
|
|
parseRoutes)
|
2016-11-10 14:16:44 +01:00
|
|
|
}
|
|
|
|
|
2016-11-11 17:33:33 +01:00
|
|
|
func RoutesLookupProtocol(net string, protocol string) (Parsed, bool) {
|
2016-11-24 17:59:19 +01:00
|
|
|
return RunAndParse("route for '"+net+"' protocol '"+protocol+"' all",
|
2016-11-10 15:11:32 +01:00
|
|
|
parseRoutes)
|
2016-11-10 14:16:44 +01:00
|
|
|
}
|
2017-02-15 12:20:55 +01:00
|
|
|
|
|
|
|
func RoutesPeer(peer string) (Parsed, bool) {
|
|
|
|
return RunAndParse("route export '"+peer+"'", parseRoutes)
|
|
|
|
}
|
2017-06-22 15:06:54 +02:00
|
|
|
|
|
|
|
func RoutesDump() (Parsed, bool) {
|
|
|
|
if ParserConf.PerPeerTables {
|
|
|
|
return RoutesDumpPerPeerTable()
|
|
|
|
}
|
|
|
|
return RoutesDumpSingleTable()
|
|
|
|
}
|
|
|
|
|
|
|
|
func RoutesDumpSingleTable() (Parsed, bool) {
|
2017-06-22 18:48:22 +02:00
|
|
|
importedRes, cached := RunAndParse("route all", parseRoutes)
|
2017-06-22 15:06:54 +02:00
|
|
|
filteredRes, _ := RunAndParse("route filtered all", parseRoutes)
|
|
|
|
|
2017-06-22 18:48:22 +02:00
|
|
|
imported := importedRes["routes"]
|
2017-06-22 15:06:54 +02:00
|
|
|
filtered := filteredRes["routes"]
|
|
|
|
|
|
|
|
result := Parsed{
|
2017-06-22 18:48:22 +02:00
|
|
|
"imported": imported,
|
2017-06-22 15:06:54 +02:00
|
|
|
"filtered": filtered,
|
|
|
|
}
|
|
|
|
return result, cached
|
|
|
|
}
|
|
|
|
|
|
|
|
func RoutesDumpPerPeerTable() (Parsed, bool) {
|
2017-06-22 18:48:22 +02:00
|
|
|
importedRes, cached := RunAndParse("route all", parseRoutes)
|
|
|
|
imported := importedRes["routes"]
|
2017-06-22 15:06:54 +02:00
|
|
|
filtered := []Parsed{}
|
|
|
|
|
|
|
|
// Get protocols with filtered routes
|
2017-06-22 16:08:37 +02:00
|
|
|
protocolsRes, _ := ProtocolsBgp()
|
|
|
|
protocols := protocolsRes["protocols"].(Parsed)
|
|
|
|
|
2017-06-22 15:06:54 +02:00
|
|
|
for protocol, details := range protocols {
|
|
|
|
details, ok := details.(Parsed)
|
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
2017-06-22 16:08:37 +02:00
|
|
|
counters, ok := details["routes"].(Parsed)
|
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
2017-07-13 12:16:53 +02:00
|
|
|
filterCount := counters["filtered"]
|
2017-06-22 15:06:54 +02:00
|
|
|
if filterCount == 0 {
|
|
|
|
continue // nothing to do here.
|
|
|
|
}
|
|
|
|
// Lookup filtered routes
|
|
|
|
pfilteredRes, _ := RunAndParse(
|
|
|
|
"route filtered protocol '"+protocol+"' all",
|
|
|
|
parseRoutes)
|
|
|
|
|
|
|
|
pfiltered, ok := pfilteredRes["routes"].([]Parsed)
|
|
|
|
if !ok {
|
|
|
|
continue // something went wrong...
|
|
|
|
}
|
|
|
|
|
|
|
|
filtered = append(filtered, pfiltered...)
|
|
|
|
}
|
|
|
|
|
|
|
|
result := Parsed{
|
2017-06-22 19:01:05 +02:00
|
|
|
"imported": imported,
|
2017-06-22 15:06:54 +02:00
|
|
|
"filtered": filtered,
|
|
|
|
}
|
|
|
|
return result, cached
|
|
|
|
}
|