This doesn't add a protocol specific parser, but does allow basic information about babel imported and exported routes etc. to be exposed.
332 lines
6.5 KiB
Go
332 lines
6.5 KiB
Go
package parser
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"fmt"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/czerwonk/bird_exporter/protocol"
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
var (
|
|
protocolRegex *regexp.Regexp
|
|
descriptionRegex *regexp.Regexp
|
|
routeRegex *regexp.Regexp
|
|
uptimeRegex *regexp.Regexp
|
|
routeChangeRegex *regexp.Regexp
|
|
filterRegex *regexp.Regexp
|
|
channelRegex *regexp.Regexp
|
|
)
|
|
|
|
type context struct {
|
|
current *protocol.Protocol
|
|
line string
|
|
handled bool
|
|
protocols []*protocol.Protocol
|
|
ipVersion string
|
|
}
|
|
|
|
func init() {
|
|
protocolRegex = regexp.MustCompile(`^(?:1002\-)?([^\s]+)\s+(MRT|BGP|BFD|OSPF|RPKI|RIP|RAdv|Pipe|Perf|Direct|Babel|Device|Kernel|Static)\s+([^\s]+)\s+([^\s]+)\s+(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}|[^\s]+)(?:\s+(.*?))?$`)
|
|
descriptionRegex = regexp.MustCompile(`Description:\s+(.*)`)
|
|
routeRegex = regexp.MustCompile(`^\s+Routes:\s+(\d+) imported, (?:(\d+) filtered, )?(\d+) exported(?:, (\d+) preferred)?`)
|
|
uptimeRegex = regexp.MustCompile(`^(?:((\d+):(\d{2}):(\d{2}))|(\d+)|(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}))$`)
|
|
routeChangeRegex = regexp.MustCompile(`(Import|Export) (updates|withdraws):\s+(\d+|---)\s+(\d+|---)\s+(\d+|---)\s+(\d+|---)\s+(\d+|---)\s*`)
|
|
filterRegex = regexp.MustCompile(`(Input|Output) filter:\s+(.*)`)
|
|
channelRegex = regexp.MustCompile(`Channel ipv(4|6)`)
|
|
}
|
|
|
|
// ParseProtocols parses bird output and returns protocol.Protocol structs
|
|
func ParseProtocols(data []byte, ipVersion string) []*protocol.Protocol {
|
|
reader := bytes.NewReader(data)
|
|
scanner := bufio.NewScanner(reader)
|
|
|
|
c := &context{protocols: make([]*protocol.Protocol, 0), ipVersion: ipVersion}
|
|
|
|
var handlers = []func(*context){
|
|
handleEmptyLine,
|
|
parseLineForProtocol,
|
|
parseLineForDescription,
|
|
parseLineForChannel,
|
|
parseLineForRoutes,
|
|
parseLineForRouteChanges,
|
|
parseLineForFilterName,
|
|
}
|
|
|
|
for scanner.Scan() {
|
|
c.line = strings.TrimRight(scanner.Text(), " ")
|
|
c.handled = false
|
|
|
|
for _, h := range handlers {
|
|
if !c.handled {
|
|
h(c)
|
|
}
|
|
}
|
|
}
|
|
|
|
return c.protocols
|
|
}
|
|
|
|
func handleEmptyLine(c *context) {
|
|
if c.line != "" {
|
|
return
|
|
}
|
|
|
|
c.current = nil
|
|
c.handled = true
|
|
}
|
|
|
|
func parseLineForProtocol(c *context) {
|
|
match := protocolRegex.FindStringSubmatch(c.line)
|
|
|
|
if match == nil {
|
|
return
|
|
}
|
|
|
|
proto := parseProto(match[2])
|
|
ut := parseUptime(match[5])
|
|
|
|
c.current = protocol.NewProtocol(match[1], proto, c.ipVersion, ut)
|
|
c.current.Up = parseState(match[4])
|
|
|
|
fillAttributes(c.current, match)
|
|
|
|
c.protocols = append(c.protocols, c.current)
|
|
c.handled = true
|
|
}
|
|
|
|
func parseLineForDescription(c *context) {
|
|
match := descriptionRegex.FindStringSubmatch(c.line)
|
|
|
|
if match == nil {
|
|
return
|
|
}
|
|
|
|
if len(match) <= 1 {
|
|
return
|
|
}
|
|
|
|
c.current.Description = strings.Join(match[1:], " ")
|
|
}
|
|
|
|
func parseProto(val string) int {
|
|
switch val {
|
|
case "BGP":
|
|
return protocol.BGP
|
|
case "OSPF":
|
|
return protocol.OSPF
|
|
case "Direct":
|
|
return protocol.Direct
|
|
case "Kernel":
|
|
return protocol.Kernel
|
|
case "Static":
|
|
return protocol.Static
|
|
case "Babel":
|
|
return protocol.Babel
|
|
}
|
|
|
|
return protocol.PROTO_UNKNOWN
|
|
}
|
|
|
|
func parseState(state string) int {
|
|
if state == "up" {
|
|
return 1
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
func parseUptime(value string) int {
|
|
match := uptimeRegex.FindStringSubmatch(value)
|
|
|
|
if match == nil {
|
|
return 0
|
|
}
|
|
|
|
if len(match[1]) > 0 {
|
|
return parseUptimeForDuration(match)
|
|
}
|
|
|
|
if len(match[5]) > 0 {
|
|
return parseUptimeForTimestamp(value)
|
|
}
|
|
|
|
return parseUptimeForIso(value)
|
|
}
|
|
|
|
func parseUptimeForIso(s string) int {
|
|
start, err := time.ParseInLocation("2006-01-02 15:04:05", s, time.Local)
|
|
if err != nil {
|
|
log.Errorln(err)
|
|
return 0
|
|
}
|
|
|
|
return int(time.Since(start).Seconds())
|
|
}
|
|
|
|
func parseUptimeForDuration(duration []string) int {
|
|
h := parseInt(duration[2])
|
|
m := parseInt(duration[3])
|
|
s := parseInt(duration[4])
|
|
str := fmt.Sprintf("%dh%dm%ds", h, m, s)
|
|
|
|
d, err := time.ParseDuration(str)
|
|
if err != nil {
|
|
log.Errorln(err)
|
|
return 0
|
|
}
|
|
|
|
return int(d.Seconds())
|
|
}
|
|
|
|
func parseUptimeForTimestamp(timestamp string) int {
|
|
since := parseInt(timestamp)
|
|
|
|
s := time.Unix(since, 0)
|
|
d := time.Since(s)
|
|
return int(d.Seconds())
|
|
}
|
|
|
|
func parseLineForChannel(c *context) {
|
|
if c.ipVersion != "" || c.current == nil {
|
|
return
|
|
}
|
|
|
|
channel := channelRegex.FindStringSubmatch(c.line)
|
|
if channel == nil {
|
|
return
|
|
}
|
|
|
|
if len(c.current.IPVersion) == 0 {
|
|
c.current.IPVersion = channel[1]
|
|
} else {
|
|
c.current = &protocol.Protocol{
|
|
Name: c.current.Name,
|
|
Proto: c.current.Proto,
|
|
Up: c.current.Up,
|
|
Uptime: c.current.Uptime,
|
|
IPVersion: channel[1],
|
|
}
|
|
c.protocols = append(c.protocols, c.current)
|
|
}
|
|
|
|
c.handled = true
|
|
}
|
|
|
|
func parseLineForRoutes(c *context) {
|
|
if c.current == nil {
|
|
return
|
|
}
|
|
|
|
match := routeRegex.FindStringSubmatch(c.line)
|
|
if match == nil {
|
|
return
|
|
}
|
|
|
|
c.current.Imported, _ = strconv.ParseInt(match[1], 10, 64)
|
|
c.current.Exported, _ = strconv.ParseInt(match[3], 10, 64)
|
|
|
|
if len(match[2]) > 0 {
|
|
c.current.Filtered, _ = strconv.ParseInt(match[2], 10, 64)
|
|
}
|
|
|
|
if len(match[4]) > 0 {
|
|
c.current.Preferred, _ = strconv.ParseInt(match[4], 10, 64)
|
|
}
|
|
|
|
c.handled = true
|
|
}
|
|
|
|
func parseLineForRouteChanges(c *context) {
|
|
if c.current == nil {
|
|
return
|
|
}
|
|
|
|
match := routeChangeRegex.FindStringSubmatch(c.line)
|
|
if match == nil {
|
|
return
|
|
}
|
|
|
|
x := getRouteChangeCount(match, c.current)
|
|
x.Received = parseRouteChangeValue(match[3])
|
|
x.Rejected = parseRouteChangeValue(match[4])
|
|
x.Filtered = parseRouteChangeValue(match[5])
|
|
x.Ignored = parseRouteChangeValue(match[6])
|
|
x.Accepted = parseRouteChangeValue(match[7])
|
|
|
|
c.handled = true
|
|
}
|
|
|
|
func getRouteChangeCount(values []string, p *protocol.Protocol) *protocol.RouteChangeCount {
|
|
if values[1] == "Import" {
|
|
if values[2] == "updates" {
|
|
return &p.ImportUpdates
|
|
}
|
|
|
|
return &p.ImportWithdraws
|
|
}
|
|
|
|
if values[2] == "updates" {
|
|
return &p.ExportUpdates
|
|
}
|
|
|
|
return &p.ExportWithdraws
|
|
}
|
|
|
|
func parseRouteChangeValue(value string) int64 {
|
|
if value == "---" {
|
|
return 0
|
|
}
|
|
|
|
return parseInt(value)
|
|
}
|
|
|
|
func parseLineForFilterName(c *context) {
|
|
if c.current == nil {
|
|
return
|
|
}
|
|
|
|
match := filterRegex.FindStringSubmatch(c.line)
|
|
if match == nil {
|
|
return
|
|
}
|
|
|
|
if match[1] == "Input" {
|
|
c.current.ImportFilter = match[2]
|
|
} else {
|
|
c.current.ExportFilter = match[2]
|
|
}
|
|
|
|
c.handled = true
|
|
}
|
|
|
|
func parseInt(value string) int64 {
|
|
i, err := strconv.ParseInt(value, 10, 64)
|
|
|
|
if err != nil {
|
|
log.Errorln(err)
|
|
return 0
|
|
}
|
|
|
|
return i
|
|
}
|
|
|
|
func fillAttributes(p *protocol.Protocol, m []string) {
|
|
if p.Proto == protocol.OSPF {
|
|
p.Attributes["running"] = float64(parseOspfRunning(m[6]))
|
|
}
|
|
}
|
|
|
|
func parseOspfRunning(state string) int {
|
|
if state == "Running" {
|
|
return 1
|
|
}
|
|
|
|
return 0
|
|
}
|