mirror of
https://github.com/alice-lg/birdwatcher.git
synced 2025-03-09 00:00:05 +01:00
705 lines
18 KiB
Go
705 lines
18 KiB
Go
package bird
|
|
|
|
import (
|
|
"bufio"
|
|
"io"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
// WorkerPoolSize is the number of go routines used to parse routing tables concurrently
|
|
var WorkerPoolSize = 8
|
|
|
|
var (
|
|
ParserConf ParserConfig
|
|
regex struct {
|
|
status struct {
|
|
startLine *regexp.Regexp
|
|
routerID *regexp.Regexp
|
|
currentServer *regexp.Regexp
|
|
lastReboot *regexp.Regexp
|
|
lastReconfig *regexp.Regexp
|
|
}
|
|
protocol struct {
|
|
channel *regexp.Regexp
|
|
protocol *regexp.Regexp
|
|
numericValue *regexp.Regexp
|
|
routes *regexp.Regexp
|
|
stringValue *regexp.Regexp
|
|
routeChanges *regexp.Regexp
|
|
short *regexp.Regexp
|
|
}
|
|
symbols struct {
|
|
keyRx *regexp.Regexp
|
|
}
|
|
routeCount struct {
|
|
countRx *regexp.Regexp
|
|
}
|
|
routes struct {
|
|
startDefinition *regexp.Regexp
|
|
second *regexp.Regexp
|
|
routeType *regexp.Regexp
|
|
bgp *regexp.Regexp
|
|
community *regexp.Regexp
|
|
largeCommunity *regexp.Regexp
|
|
extendedCommunity *regexp.Regexp
|
|
origin *regexp.Regexp
|
|
prefixBird2 *regexp.Regexp
|
|
gatewayBird2 *regexp.Regexp
|
|
interfaceBird2 *regexp.Regexp
|
|
}
|
|
}
|
|
)
|
|
|
|
type Parsed map[string]interface{}
|
|
|
|
func init() {
|
|
const re_ifname = `[^/\s]+`
|
|
const re_ip = `[0-9a-f\.\:]+`
|
|
const re_prefix = `[0-9a-f\.\:\/]+`
|
|
|
|
regex.status.startLine = regexp.MustCompile(`^BIRD\s([0-9\.]+)\s*$`)
|
|
regex.status.routerID = regexp.MustCompile(`^Router\sID\sis\s([0-9\.]+)\s*$`)
|
|
regex.status.currentServer = regexp.MustCompile(`^Current\sserver\stime\sis\s([0-9\-]+\s[0-9\:]+)\s*$`)
|
|
regex.status.lastReboot = regexp.MustCompile(`^Last\sreboot\son\s([0-9\-]+\s[0-9\:]+)\s*$`)
|
|
regex.status.lastReconfig = regexp.MustCompile(`^Last\sreconfiguration\son\s([0-9\-]+\s[0-9\:]+)\s*$`)
|
|
|
|
regex.symbols.keyRx = regexp.MustCompile(`^([^\s]+)\s+(.+)\s*$`)
|
|
|
|
regex.routeCount.countRx = regexp.MustCompile(`^(\d+)\s+of\s+(\d+)\s+routes.*$`)
|
|
|
|
regex.protocol.channel = regexp.MustCompile("Channel ipv([46])")
|
|
// regex.protocol.protocol = regexp.MustCompile(`^(?:1002\-)?([^\s]+)\s+(BGP|RPKI|Pipe|BFD|Direct|Device|Kernel)\s+([^\s]+)\s+([^\s]+)\s+(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}|[^\s]+)(?:\s+(.*?)\s*)?$`)
|
|
regex.protocol.protocol = regexp.MustCompile(`^(?:1002\-)?([^\s]+)\s+(\w+)\s+([^\s]+)\s+([^\s]+)\s+(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}|[^\s]+)(?:\s+(.*?)\s*)?$`)
|
|
regex.protocol.numericValue = regexp.MustCompile(`^\s+([^:]+):\s+([\d]+)\s*$`)
|
|
regex.protocol.routes = regexp.MustCompile(`^\s+Routes:\s+(.*)`)
|
|
regex.protocol.stringValue = regexp.MustCompile(`^\s+([^:]+):\s+(.+)\s*$`)
|
|
regex.protocol.routeChanges = regexp.MustCompile(`(Import|Export) (updates|withdraws):\s+(\d+|---)\s+(\d+|---)\s+(\d+|---)\s+(\d+|---)\s+(\d+|---)\s*$`)
|
|
|
|
regex.routes.startDefinition = regexp.MustCompile(`^(` + re_prefix + `)\s+via\s+(` + re_ip + `)\s+on\s+(` + re_ifname + `)\s+\[([\w\.:]+)\s+([0-9\-\:\s]+)(?:\s+from\s+(` + re_prefix + `)){0,1}\]\s+(?:(\*)\s+){0,1}\((\d+)(?:\/\d+){0,1}|\?\).*`)
|
|
regex.protocol.short = regexp.MustCompile(`^(?:1002\-)?([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([0-9\-]+\s+[0-9\:]+?|[0-9\-]+)\s+(.*?)\s*?$`)
|
|
regex.routes.second = regexp.MustCompile(`^\s+via\s+(` + re_ip + `)\s+on\s+(` + re_ifname + `)\s+\[([\w\.:]+)\s+([0-9\-\:\s]+)(?:\s+from\s+(` + re_prefix + `)){0,1}\]\s+(?:(\*)\s+){0,1}\((\d+)(?:\/\d+){0,1}\).*$`)
|
|
regex.routes.routeType = regexp.MustCompile(`^\s+Type:\s+(.*)\s*$`)
|
|
regex.routes.bgp = regexp.MustCompile(`^\s+BGP.(\w+):\s+(.+)\s*$`)
|
|
regex.routes.community = regexp.MustCompile(`^\((\d+),\s*(\d+)\)`)
|
|
regex.routes.largeCommunity = regexp.MustCompile(`^\((\d+),\s*(\d+),\s*(\d+)\)`)
|
|
regex.routes.extendedCommunity = regexp.MustCompile(`^\(([^,]+),\s*([^,]+),\s*([^,]+)\)`)
|
|
regex.routes.origin = regexp.MustCompile(`\([^\(]*\)\s*`)
|
|
regex.routes.prefixBird2 = regexp.MustCompile(`^(` + re_prefix + `)?\s+unicast\s+\[([\w\.:]+)\s+([0-9\-\:\s]+)(?:\s+from\s+(` + re_prefix + `))?\]\s+(?:(\*)\s+)?\((\d+)(?:\/\d+)?(?:\/[^\)]*)?\).*$`)
|
|
regex.routes.gatewayBird2 = regexp.MustCompile(`^\s+via\s+(` + re_ip + `)\s+on\s+(` + re_ifname + `)\s*$`)
|
|
regex.routes.interfaceBird2 = regexp.MustCompile(`^\s+dev\s+(` + re_ifname + `)\s*$`)
|
|
}
|
|
|
|
func dirtyContains(l []string, e string) bool {
|
|
for _, c := range l {
|
|
if c == e {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func emptyString(s string) bool {
|
|
return len(strings.TrimSpace(s)) == 0
|
|
}
|
|
|
|
func specialLine(line string) bool {
|
|
return (strings.HasPrefix(line, "BIRD") || strings.HasPrefix(line, "Access restricted"))
|
|
}
|
|
|
|
func parseStatus(reader io.Reader) Parsed {
|
|
res := Parsed{}
|
|
|
|
lines := newLineIterator(reader, true)
|
|
for lines.next() {
|
|
line := lines.string()
|
|
|
|
if regex.status.startLine.MatchString(line) {
|
|
res["version"] = regex.status.startLine.FindStringSubmatch(line)[1]
|
|
} else if regex.status.routerID.MatchString(line) {
|
|
res["router_id"] = regex.status.routerID.FindStringSubmatch(line)[1]
|
|
} else if regex.status.currentServer.MatchString(line) {
|
|
res["current_server"] = regex.status.currentServer.FindStringSubmatch(line)[1]
|
|
} else if regex.status.lastReboot.MatchString(line) {
|
|
res["last_reboot"] = regex.status.lastReboot.FindStringSubmatch(line)[1]
|
|
} else if regex.status.lastReconfig.MatchString(line) {
|
|
res["last_reconfig"] = regex.status.lastReconfig.FindStringSubmatch(line)[1]
|
|
} else {
|
|
res["message"] = line
|
|
}
|
|
}
|
|
|
|
for k := range res {
|
|
if dirtyContains(ParserConf.FilterFields, k) {
|
|
res[k] = nil
|
|
}
|
|
}
|
|
|
|
return Parsed{"status": res}
|
|
}
|
|
|
|
func parseProtocolsShort(reader io.Reader) Parsed {
|
|
res := Parsed{}
|
|
|
|
lines := newLineIterator(reader, false)
|
|
for lines.next() {
|
|
line := lines.string()
|
|
|
|
if specialLine(line) {
|
|
continue
|
|
}
|
|
|
|
if regex.protocol.short.MatchString(line) {
|
|
// The header is skipped, because the regular expression does not
|
|
// match if the "since" field does not contain digits
|
|
matches := regex.protocol.short.FindStringSubmatch(line)
|
|
|
|
res[matches[1]] = Parsed{
|
|
"proto": matches[2],
|
|
"table": matches[3],
|
|
"state": matches[4],
|
|
"since": matches[5],
|
|
"info": matches[6],
|
|
}
|
|
}
|
|
}
|
|
|
|
return Parsed{"protocols": res}
|
|
}
|
|
|
|
func parseProtocols(reader io.Reader) Parsed {
|
|
res := Parsed{}
|
|
|
|
proto := ""
|
|
|
|
lines := newLineIterator(reader, false)
|
|
for lines.next() {
|
|
line := lines.string()
|
|
|
|
if emptyString(line) {
|
|
if !emptyString(proto) {
|
|
parsed := parseProtocol(proto)
|
|
|
|
res[parsed["protocol"].(string)] = parsed
|
|
}
|
|
proto = ""
|
|
} else {
|
|
proto += (line + "\n")
|
|
}
|
|
}
|
|
|
|
return Parsed{"protocols": res}
|
|
}
|
|
|
|
func parseSymbols(reader io.Reader) Parsed {
|
|
res := Parsed{}
|
|
|
|
lines := newLineIterator(reader, true)
|
|
for lines.next() {
|
|
line := lines.string()
|
|
|
|
if specialLine(line) {
|
|
continue
|
|
}
|
|
|
|
if regex.symbols.keyRx.MatchString(line) {
|
|
groups := regex.symbols.keyRx.FindStringSubmatch(line)
|
|
|
|
if _, ok := res[groups[2]]; !ok {
|
|
res[groups[2]] = []string{}
|
|
}
|
|
|
|
res[groups[2]] = append(res[groups[2]].([]string), groups[1])
|
|
}
|
|
}
|
|
|
|
return Parsed{"symbols": res}
|
|
}
|
|
|
|
type blockJob struct {
|
|
lines []string
|
|
position int
|
|
}
|
|
|
|
type blockParsed struct {
|
|
items []Parsed
|
|
position int
|
|
}
|
|
|
|
func parseRoutes(reader io.Reader) Parsed {
|
|
jobs := make(chan blockJob)
|
|
out := startRouteWorkers(jobs)
|
|
|
|
res := startRouteConsumer(out)
|
|
defer close(res)
|
|
|
|
pos := 0
|
|
block := []string{}
|
|
lines := newLineIterator(reader, true)
|
|
|
|
for lines.next() {
|
|
line := lines.string()
|
|
|
|
if line[0] != 32 && line[0] != 9 && len(block) > 0 {
|
|
jobs <- blockJob{block, pos}
|
|
pos++
|
|
block = []string{}
|
|
}
|
|
|
|
block = append(block, line)
|
|
}
|
|
|
|
if len(block) > 0 {
|
|
jobs <- blockJob{block, pos}
|
|
}
|
|
|
|
close(jobs)
|
|
|
|
return <-res
|
|
}
|
|
|
|
func startRouteWorkers(jobs chan blockJob) chan blockParsed {
|
|
out := make(chan blockParsed)
|
|
|
|
wg := &sync.WaitGroup{}
|
|
wg.Add(WorkerPoolSize)
|
|
go func() {
|
|
for i := 0; i < WorkerPoolSize; i++ {
|
|
go workerForRouteBlockParsing(jobs, out, wg)
|
|
}
|
|
wg.Wait()
|
|
close(out)
|
|
}()
|
|
|
|
return out
|
|
}
|
|
|
|
func startRouteConsumer(out <-chan blockParsed) chan Parsed {
|
|
res := make(chan Parsed)
|
|
|
|
go func() {
|
|
byBlock := map[int][]Parsed{}
|
|
count := 0
|
|
for r := range out {
|
|
count++
|
|
byBlock[r.position] = r.items
|
|
}
|
|
res <- Parsed{"routes": sortedSliceForRouteBlocks(byBlock, count)}
|
|
}()
|
|
|
|
return res
|
|
}
|
|
|
|
func sortedSliceForRouteBlocks(byBlock map[int][]Parsed, numBlocks int) []Parsed {
|
|
res := []Parsed{}
|
|
|
|
for i := 0; i < numBlocks; i++ {
|
|
routes, ok := byBlock[i]
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
res = append(res, routes...)
|
|
}
|
|
|
|
return res
|
|
}
|
|
|
|
func workerForRouteBlockParsing(jobs <-chan blockJob, out chan<- blockParsed, wg *sync.WaitGroup) {
|
|
for j := range jobs {
|
|
parseRouteLines(j.lines, j.position, out)
|
|
}
|
|
wg.Done()
|
|
}
|
|
|
|
func parseRouteLines(lines []string, position int, ch chan<- blockParsed) {
|
|
route := Parsed{}
|
|
routes := []Parsed{}
|
|
|
|
for i := 0; i < len(lines); {
|
|
line := lines[i]
|
|
|
|
if specialLine(line) {
|
|
i++
|
|
continue
|
|
}
|
|
|
|
if regex.routes.prefixBird2.MatchString(line) {
|
|
formerPrefix := ""
|
|
if len(route) > 0 {
|
|
routes = append(routes, route)
|
|
|
|
formerPrefix = route["network"].(string)
|
|
route = Parsed{}
|
|
}
|
|
|
|
parseMainRouteDetailBird2(regex.routes.prefixBird2.FindStringSubmatch(line), route, formerPrefix)
|
|
} else if regex.routes.startDefinition.MatchString(line) {
|
|
if len(route) > 0 {
|
|
routes = append(routes, route)
|
|
route = Parsed{}
|
|
}
|
|
|
|
parseMainRouteDetail(regex.routes.startDefinition.FindStringSubmatch(line), route)
|
|
} else if regex.routes.gatewayBird2.MatchString(line) {
|
|
parseRoutesGatewayBird2(regex.routes.gatewayBird2.FindStringSubmatch(line), route)
|
|
} else if regex.routes.second.MatchString(line) {
|
|
routes = append(routes, route)
|
|
|
|
route = parseRoutesSecond(line, route)
|
|
} else if regex.routes.routeType.MatchString(line) {
|
|
submatch := regex.routes.routeType.FindStringSubmatch(line)[1]
|
|
route["type"] = strings.Split(submatch, " ")
|
|
} else if regex.routes.bgp.MatchString(line) {
|
|
// BIRD has a static buffer to hold information which is sent to the client (birdc)
|
|
// If there is more information to be sent to the client than the buffer can hold,
|
|
// the output is split into multiple lines and the continuation of the previous
|
|
// line is indicated by 2 tab characters at the beginning of the next line
|
|
joinLines := func() {
|
|
for c := i + 1; c < len(lines); c++ {
|
|
if strings.HasPrefix(lines[c], "\x09\x09") {
|
|
line += lines[c][2:]
|
|
i++
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// The aforementioned behaviour was only observed for the *community fields
|
|
if strings.HasPrefix(line, "\x09BGP.community") ||
|
|
strings.HasPrefix(line, "\x09BGP.large_community") ||
|
|
strings.HasPrefix(line, "\x09BGP.ext_community") {
|
|
joinLines()
|
|
}
|
|
|
|
bgp := Parsed{}
|
|
if tmp, ok := route["bgp"]; ok {
|
|
if val, ok := tmp.(Parsed); ok {
|
|
bgp = val
|
|
}
|
|
}
|
|
|
|
parseRoutesBgp(line, bgp)
|
|
route["bgp"] = bgp
|
|
}
|
|
|
|
i++
|
|
}
|
|
|
|
if len(route) > 0 {
|
|
routes = append(routes, route)
|
|
}
|
|
|
|
ch <- blockParsed{routes, position}
|
|
}
|
|
|
|
func parseMainRouteDetail(groups []string, route Parsed) {
|
|
route["network"] = groups[1]
|
|
route["gateway"] = groups[2]
|
|
route["interface"] = groups[3]
|
|
route["from_protocol"] = groups[4]
|
|
route["age"] = groups[5]
|
|
route["learnt_from"] = groups[6]
|
|
route["primary"] = groups[7] == "*"
|
|
route["metric"] = parseInt(groups[8])
|
|
|
|
for k := range route {
|
|
if dirtyContains(ParserConf.FilterFields, k) {
|
|
route[k] = nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func parseMainRouteDetailBird2(groups []string, route Parsed, formerPrefix string) {
|
|
if len(groups[1]) > 0 {
|
|
route["network"] = groups[1]
|
|
} else {
|
|
route["network"] = formerPrefix
|
|
}
|
|
|
|
route["from_protocol"] = groups[2]
|
|
route["age"] = groups[3]
|
|
route["learnt_from"] = groups[4]
|
|
route["primary"] = groups[5] == "*"
|
|
route["metric"] = parseInt(groups[6])
|
|
|
|
for k := range route {
|
|
if dirtyContains(ParserConf.FilterFields, k) {
|
|
route[k] = nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func parseRoutesGatewayBird2(groups []string, route Parsed) {
|
|
route["gateway"] = groups[1]
|
|
route["interface"] = groups[2]
|
|
}
|
|
|
|
func parseRoutesSecond(line string, route Parsed) Parsed {
|
|
tmp, ok := route["network"]
|
|
if !ok {
|
|
return route
|
|
}
|
|
|
|
var network string
|
|
if network, ok = tmp.(string); !ok {
|
|
return route
|
|
}
|
|
|
|
route = Parsed{}
|
|
groups := regex.routes.second.FindStringSubmatch(line)
|
|
first, groups := groups[0], groups[1:]
|
|
groups = append([]string{network}, groups...)
|
|
groups = append([]string{first}, groups...)
|
|
|
|
parseMainRouteDetail(groups, route)
|
|
return route
|
|
}
|
|
|
|
func parseRoutesBgp(line string, bgp Parsed) {
|
|
groups := regex.routes.bgp.FindStringSubmatch(line)
|
|
|
|
if groups[1] == "community" {
|
|
parseRoutesCommunities(groups, bgp)
|
|
} else if groups[1] == "large_community" {
|
|
parseRoutesLargeCommunities(groups, bgp)
|
|
} else if groups[1] == "ext_community" {
|
|
parseRoutesExtendedCommunities(groups, bgp)
|
|
} else if groups[1] == "as_path" {
|
|
bgp["as_path"] = strings.Split(groups[2], " ")
|
|
} else {
|
|
bgp[groups[1]] = groups[2]
|
|
}
|
|
}
|
|
|
|
func parseRoutesCommunities(groups []string, res Parsed) {
|
|
communities := [][]int64{}
|
|
for _, community := range regex.routes.origin.FindAllString(groups[2], -1) {
|
|
if regex.routes.community.MatchString(community) {
|
|
communityGroups := regex.routes.community.FindStringSubmatch(community)
|
|
maj := parseInt(communityGroups[1])
|
|
min := parseInt(communityGroups[2])
|
|
communities = append(communities, []int64{maj, min})
|
|
}
|
|
}
|
|
|
|
res["communities"] = communities
|
|
}
|
|
|
|
func parseRoutesLargeCommunities(groups []string, res Parsed) {
|
|
communities := [][]int64{}
|
|
for _, community := range regex.routes.origin.FindAllString(groups[2], -1) {
|
|
if regex.routes.largeCommunity.MatchString(community) {
|
|
communityGroups := regex.routes.largeCommunity.FindStringSubmatch(community)
|
|
maj := parseInt(communityGroups[1])
|
|
min := parseInt(communityGroups[2])
|
|
pat := parseInt(communityGroups[3])
|
|
communities = append(communities, []int64{maj, min, pat})
|
|
}
|
|
}
|
|
|
|
res["large_communities"] = communities
|
|
}
|
|
|
|
func parseRoutesExtendedCommunities(groups []string, res Parsed) {
|
|
communities := []interface{}{}
|
|
for _, community := range regex.routes.origin.FindAllString(groups[2], -1) {
|
|
if regex.routes.extendedCommunity.MatchString(community) {
|
|
communityGroups := regex.routes.extendedCommunity.FindStringSubmatch(community)
|
|
communities = append(communities, []interface{}{communityGroups[1], communityGroups[2], communityGroups[3]})
|
|
}
|
|
}
|
|
|
|
res["ext_communities"] = communities
|
|
}
|
|
|
|
func parseRoutesCount(reader io.Reader) Parsed {
|
|
res := Parsed{}
|
|
|
|
lines := newLineIterator(reader, true)
|
|
for lines.next() {
|
|
line := lines.string()
|
|
|
|
if specialLine(line) {
|
|
continue
|
|
}
|
|
|
|
if regex.routeCount.countRx.MatchString(line) {
|
|
count := regex.routeCount.countRx.FindStringSubmatch(line)[1]
|
|
res["routes"] = parseInt(count)
|
|
}
|
|
}
|
|
|
|
return res
|
|
}
|
|
|
|
func isCorrectChannel(currentIPVersion string) bool {
|
|
if len(currentIPVersion) == 0 {
|
|
return true
|
|
}
|
|
|
|
return currentIPVersion == IPVersion
|
|
}
|
|
|
|
func parseProtocol(lines string) Parsed {
|
|
res := Parsed{}
|
|
routeChanges := Parsed{}
|
|
|
|
handlers := []func(string) bool{
|
|
func(l string) bool { return parseProtocolHeader(l, res) },
|
|
func(l string) bool { return parseProtocolRouteLine(l, res) },
|
|
func(l string) bool { return parseProtocolRouteChanges(l, routeChanges) },
|
|
func(l string) bool { return parseProtocolNumberValuesRx(l, res) },
|
|
func(l string) bool { return parseProtocolStringValuesRx(l, res) },
|
|
}
|
|
|
|
ipVersion := ""
|
|
|
|
reader := strings.NewReader(lines)
|
|
scanner := bufio.NewScanner(reader)
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
|
|
if m := regex.protocol.channel.FindStringSubmatch(line); len(m) > 0 {
|
|
ipVersion = m[1]
|
|
}
|
|
|
|
if isCorrectChannel(ipVersion) {
|
|
parseLine(line, handlers)
|
|
}
|
|
}
|
|
|
|
res["route_changes"] = routeChanges
|
|
|
|
if _, ok := res["routes"]; !ok {
|
|
routes := Parsed{}
|
|
routes["accepted"] = int64(0)
|
|
routes["filtered"] = int64(0)
|
|
routes["imported"] = int64(0)
|
|
routes["exported"] = int64(0)
|
|
routes["preferred"] = int64(0)
|
|
|
|
res["routes"] = routes
|
|
}
|
|
|
|
return res
|
|
}
|
|
|
|
func parseLine(line string, handlers []func(string) bool) {
|
|
for _, h := range handlers {
|
|
if h(line) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func parseProtocolHeader(line string, res Parsed) bool {
|
|
groups := regex.protocol.protocol.FindStringSubmatch(line)
|
|
if groups == nil {
|
|
return false
|
|
}
|
|
|
|
res["protocol"] = groups[1]
|
|
res["bird_protocol"] = groups[2]
|
|
res["table"] = groups[3]
|
|
res["state"] = groups[4]
|
|
res["state_changed"] = groups[5]
|
|
res["connection"] = groups[6] // TODO eliminate
|
|
if groups[2] == "Pipe" {
|
|
res["peer_table"] = groups[6][3:]
|
|
}
|
|
return true
|
|
}
|
|
|
|
func parseProtocolRouteLine(line string, res Parsed) bool {
|
|
groups := regex.protocol.routes.FindStringSubmatch(line)
|
|
if groups == nil {
|
|
return false
|
|
}
|
|
|
|
routes := parseProtocolRoutes(groups[1])
|
|
res["routes"] = routes
|
|
return true
|
|
}
|
|
|
|
func setChangeCount(name string, value string, res Parsed) {
|
|
if value == "---" { // field not available for protocol
|
|
return
|
|
}
|
|
res[name] = parseInt(value)
|
|
}
|
|
|
|
func parseProtocolRouteChanges(line string, res Parsed) bool {
|
|
groups := regex.protocol.routeChanges.FindStringSubmatch(line)
|
|
if groups == nil {
|
|
return false
|
|
}
|
|
|
|
updates := Parsed{}
|
|
setChangeCount("received", groups[3], updates)
|
|
setChangeCount("rejected", groups[4], updates)
|
|
setChangeCount("filtered", groups[5], updates)
|
|
setChangeCount("ignored", groups[6], updates)
|
|
setChangeCount("accepted", groups[7], updates)
|
|
|
|
key := strings.ToLower(groups[1]) + "_" + groups[2]
|
|
res[key] = updates
|
|
return true
|
|
}
|
|
|
|
func parseProtocolNumberValuesRx(line string, res Parsed) bool {
|
|
groups := regex.protocol.numericValue.FindStringSubmatch(line)
|
|
if groups == nil {
|
|
return false
|
|
}
|
|
|
|
key := treatKey(groups[1])
|
|
res[key] = parseInt(groups[2])
|
|
return true
|
|
}
|
|
|
|
func parseProtocolStringValuesRx(line string, res Parsed) bool {
|
|
groups := regex.protocol.stringValue.FindStringSubmatch(line)
|
|
if groups == nil {
|
|
return false
|
|
}
|
|
|
|
key := treatKey(groups[1])
|
|
res[key] = groups[2]
|
|
return true
|
|
}
|
|
|
|
// Will snake_case a value like that:
|
|
// I am a Weird stRiNg -> i_am_a_weird_string
|
|
func treatKey(key string) string {
|
|
spaces := regexp.MustCompile(`\s+`)
|
|
key = spaces.ReplaceAllString(key, "_")
|
|
return strings.ToLower(key)
|
|
}
|
|
|
|
func parseInt(from string) int64 {
|
|
val, err := strconv.ParseInt(from, 10, 64)
|
|
if err != nil {
|
|
return int64(0)
|
|
}
|
|
|
|
return val
|
|
}
|
|
|
|
func parseProtocolRoutes(input string) Parsed {
|
|
routes := Parsed{}
|
|
|
|
// Input: 1 imported, 0 filtered, 2 exported, 1 preferred
|
|
tokens := strings.Split(input, ",")
|
|
for _, token := range tokens {
|
|
token = strings.TrimSpace(token)
|
|
content := strings.Split(token, " ")
|
|
routes[content[1]] = parseInt(content[0])
|
|
}
|
|
|
|
return routes
|
|
}
|