1
0
Fork 0
mirror of https://github.com/alice-lg/birdwatcher.git synced 2025-03-30 00:00:17 +01:00
birdwatcher/bird/parser.go
2018-01-18 13:00:34 +01:00

568 lines
15 KiB
Go

package bird
import (
"bufio"
"io"
"regexp"
"strconv"
"strings"
)
var (
ParserConf ParserConfig
regex struct {
lineSeperator *regexp.Regexp
status struct {
startLine *regexp.Regexp
routerID *regexp.Regexp
currentServer *regexp.Regexp
lastReboot *regexp.Regexp
lastReconfig *regexp.Regexp
}
bgp struct {
channel *regexp.Regexp
protocol *regexp.Regexp
numericValue *regexp.Regexp
routes *regexp.Regexp
stringValue *regexp.Regexp
importUpdates *regexp.Regexp
importWithdraws *regexp.Regexp
exportUpdates *regexp.Regexp
exportWithdraws *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
origin *regexp.Regexp
prefixBird2 *regexp.Regexp
gatewayBird2 *regexp.Regexp
}
}
)
type Parsed map[string]interface{}
func init() {
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.bgp.channel = regexp.MustCompile("Channel ipv([46])")
regex.bgp.protocol = 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+)?.*$`)
regex.bgp.numericValue = regexp.MustCompile(`^\s+([^:]+):\s+([\d]+)\s*$`)
regex.bgp.routes = regexp.MustCompile(`^\s+Routes:\s+(.*)`)
regex.bgp.stringValue = regexp.MustCompile(`^\s+([^:]+):\s+(.+)\s*$`)
regex.bgp.importUpdates = regexp.MustCompile(`^\s+Import updates:\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s*$`)
regex.bgp.importWithdraws = regexp.MustCompile(`^\s+Import withdraws:\s+(\d+)\s+(\d+)\s+\-\-\-\s+(\d+)\s+(\d+)\s*$`)
regex.bgp.exportUpdates = regexp.MustCompile(`^\s+Export updates:\s+(\d+)\s+(\d+)\s+(\d+)\s+\-\-\-\s+(\d+)\s*$`)
regex.bgp.exportWithdraws = regexp.MustCompile(`^\s+Export withdraws:\s+(\d+)(\s+\-\-\-){2}\s+(\d+)\s*$`)
regex.routes.startDefinition = 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}\).*`)
regex.routes.second = regexp.MustCompile(`^\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}\).*$`)
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.origin = regexp.MustCompile(`\([^\(]*\)\s*`)
regex.routes.prefixBird2 = regexp.MustCompile(`^([0-9a-f\.\:\/]+)?\s+unicast\s+\[([\w\.:]+)\s+([0-9\-\:\s]+)(?:\s+from\s+([0-9a-f\.\:\/]+))?\]\s+(?:(\*)\s+)?\((\d+)(?:\/\d+)?(?:\/[^\)]*)?\).*$`)
regex.routes.gatewayBird2 = regexp.MustCompile(`^\s+via\s+([0-9a-f\.\:]+)\s+on\s+([\w\.]+)\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 parseProtocols(reader io.Reader) Parsed {
res := Parsed{}
protocols := []string{}
proto := ""
lines := newLineIterator(reader, false)
for lines.next() {
line := lines.string()
if emptyString(line) {
if !emptyString(proto) {
protocols = append(protocols, proto)
}
proto = ""
} else {
proto += (line + "\n")
}
}
res["protocols"] = protocols
return 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)
res[groups[2]] = groups[1]
}
}
return Parsed{"symbols": res}
}
func parseRoutes(reader io.Reader) Parsed {
res := Parsed{}
routes := []Parsed{}
route := Parsed{}
lines := newLineIterator(reader, true)
for lines.next() {
line := lines.string()
if specialLine(line) {
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) {
bgp := Parsed{}
if tmp, ok := route["bgp"]; ok {
if val, ok := tmp.(Parsed); ok {
bgp = val
}
}
parseRoutesBgp(line, bgp)
route["bgp"] = bgp
}
}
if len(route) > 0 {
routes = append(routes, route)
}
res["routes"] = routes
return res
}
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] == "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 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 parseBgp(lines string) Parsed {
res := Parsed{}
routeChanges := Parsed{}
handlers := []func(string) bool{
func(l string) bool { return parseBgpProtocol(l, res) },
func(l string) bool { return parseBgpRouteLine(l, res) },
func(l string) bool { return parseBgpImportUpdates(l, routeChanges) },
func(l string) bool { return parseBgpImportWithdraws(l, routeChanges) },
func(l string) bool { return parseBgpExportUpdates(l, routeChanges) },
func(l string) bool { return parseBgpExportWithdraws(l, routeChanges) },
func(l string) bool { return parseBgpNumberValuesRx(l, res) },
func(l string) bool { return parseBgpStringValuesRx(l, res) },
}
ipVersion := ""
reader := strings.NewReader(lines)
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
line := scanner.Text()
if m := regex.bgp.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"] = 0
routes["filtered"] = 0
routes["exported"] = 0
routes["preferred"] = 0
res["routes"] = routes
}
return res
}
func parseLine(line string, handlers []func(string) bool) {
for _, h := range handlers {
if h(line) {
return
}
}
}
func parseBgpProtocol(line string, res Parsed) bool {
groups := regex.bgp.protocol.FindStringSubmatch(line)
if groups == nil {
return false
}
res["protocol"] = groups[1]
res["bird_protocol"] = "BGP"
res["table"] = groups[2]
res["state"] = groups[3]
res["state_changed"] = groups[4]
res["connection"] = groups[5]
return true
}
func parseBgpRouteLine(line string, res Parsed) bool {
groups := regex.bgp.routes.FindStringSubmatch(line)
if groups == nil {
return false
}
routes := parseBgpRoutes(groups[1])
res["routes"] = routes
return true
}
func parseBgpImportUpdates(line string, res Parsed) bool {
groups := regex.bgp.importUpdates.FindStringSubmatch(line)
if groups == nil {
return false
}
updates := Parsed{}
updates["received"] = parseInt(groups[1])
updates["rejected"] = parseInt(groups[2])
updates["filtered"] = parseInt(groups[3])
updates["ignored"] = parseInt(groups[4])
updates["accepted"] = parseInt(groups[5])
res["import_updates"] = updates
return true
}
func parseBgpImportWithdraws(line string, res Parsed) bool {
groups := regex.bgp.importWithdraws.FindStringSubmatch(line)
if groups == nil {
return false
}
updates := Parsed{}
updates["received"] = parseInt(groups[1])
updates["rejected"] = parseInt(groups[2])
updates["filtered"] = parseInt(groups[3])
updates["accepted"] = parseInt(groups[4])
res["import_withdraws"] = updates
return true
}
func parseBgpExportUpdates(line string, res Parsed) bool {
groups := regex.bgp.exportUpdates.FindStringSubmatch(line)
if groups == nil {
return false
}
updates := Parsed{}
updates["received"] = parseInt(groups[1])
updates["rejected"] = parseInt(groups[2])
updates["ignored"] = parseInt(groups[3])
updates["accepted"] = parseInt(groups[4])
res["export_updates"] = updates
return true
}
func parseBgpExportWithdraws(line string, res Parsed) bool {
groups := regex.bgp.exportWithdraws.FindStringSubmatch(line)
if groups == nil {
return false
}
updates := Parsed{}
updates["received"] = parseInt(groups[1])
updates["accepted"] = parseInt(groups[3])
res["export_withdraws"] = updates
return true
}
func parseBgpNumberValuesRx(line string, res Parsed) bool {
groups := regex.bgp.numericValue.FindStringSubmatch(line)
if groups == nil {
return false
}
key := treatKey(groups[1])
res[key] = parseInt(groups[2])
return true
}
func parseBgpStringValuesRx(line string, res Parsed) bool {
groups := regex.bgp.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 0
}
return val
}
func parseBgpRoutes(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
}