diff --git a/collector/collector.go b/collector/collector.go index 4ee01b9..b4403e2 100644 --- a/collector/collector.go +++ b/collector/collector.go @@ -134,6 +134,13 @@ func WithTLS(insecure bool) Option { } } +// WithIpsec enables ipsec metrics +func WithIpsec() Option { + return func(c *collector) { + c.collectors = append(c.collectors, newIpsecCollector()) + } +} + // Option applies options to collector type Option func(*collector) diff --git a/collector/ipsec_collector.go b/collector/ipsec_collector.go new file mode 100644 index 0000000..27a9e5c --- /dev/null +++ b/collector/ipsec_collector.go @@ -0,0 +1,110 @@ +package collector + +import ( + "strconv" + "strings" + + "github.com/prometheus/client_golang/prometheus" + log "github.com/sirupsen/logrus" + "gopkg.in/routeros.v2/proto" +) + +type ipsecCollector struct { + props []string + descriptions map[string]*prometheus.Desc +} + +func newIpsecCollector() routerOSCollector { + c := &ipsecCollector{} + c.init() + return c +} + +func (c *ipsecCollector) init() { + c.props = []string{"src-address", "dst-address", "ph2-state", "invalid", "active", "comment"} + + labelNames := []string{"devicename", "srcdst", "comment"} + c.descriptions = make(map[string]*prometheus.Desc) + for _, p := range c.props[1:] { + c.descriptions[p] = descriptionForPropertyName("ipsec", p, labelNames) + } +} + +func (c *ipsecCollector) describe(ch chan<- *prometheus.Desc) { + for _, d := range c.descriptions { + ch <- d + } +} + +func (c *ipsecCollector) collect(ctx *collectorContext) error { + stats, err := c.fetch(ctx) + if err != nil { + return err + } + + for _, re := range stats { + c.collectForStat(re, ctx) + } + + return nil +} + +func (c *ipsecCollector) fetch(ctx *collectorContext) ([]*proto.Sentence, error) { + reply, err := ctx.client.Run("/ip/ipsec/policy/print", "?disabled=false", "?dynamic=false", "=.proplist="+strings.Join(c.props, ",")) + if err != nil { + log.WithFields(log.Fields{ + "device": ctx.device.Name, + "error": err, + }).Error("error fetching interface metrics") + return nil, err + } + + return reply.Re, nil +} + +func (c *ipsecCollector) collectForStat(re *proto.Sentence, ctx *collectorContext) { + srcdst := re.Map["src-address"] + "-" + re.Map["dst-address"] + comment := re.Map["comment"] + + for _, p := range c.props[2:] { + c.collectMetricForProperty(p, srcdst, comment, re, ctx) + } +} + +func (c *ipsecCollector) collectMetricForProperty(property, srcdst, comment string, re *proto.Sentence, ctx *collectorContext) { + desc := c.descriptions[property] + if value := re.Map[property]; value != "" { + var v float64 + var err error + v, err = strconv.ParseFloat(value, 64) + + switch property { + case "ph2-state": + if value == "established" { + v, err = 1, nil + } else { + v, err = 0, nil + } + case "active", "invalid": + if value == "true" { + v, err = 1, nil + } else { + v, err = 0, nil + } + case "comment": + return + } + + if err != nil { + log.WithFields(log.Fields{ + "device": ctx.device.Name, + "srcdst": srcdst, + "property": property, + "value": value, + "error": err, + }).Error("error parsing ipsec metric value") + return + } + ctx.ch <- prometheus.MustNewConstMetric(desc, prometheus.CounterValue, v, ctx.device.Name, srcdst, comment) + } +} diff --git a/config/config.go b/config/config.go index 6395947..819cadf 100644 --- a/config/config.go +++ b/config/config.go @@ -20,6 +20,7 @@ type Config struct { WlanSTA bool `yaml:"wlansta,omitempty"` WlanIF bool `yaml:"wlanif,omitempty"` Monitor bool `yaml:"monitor,omitempty"` + Ipsec bool `yaml:"ipsec,omitempty"` } `yaml:"features,omitempty"` } diff --git a/config/config.test.yml b/config/config.test.yml index 30016cf..36c6286 100644 --- a/config/config.test.yml +++ b/config/config.test.yml @@ -17,4 +17,5 @@ features: pools: true optics: true wlansta: true - wlanif: true \ No newline at end of file + wlanif: true + ipsec: true diff --git a/config/config_test.go b/config/config_test.go index 2071e00..e23cc9f 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -27,6 +27,7 @@ func TestShouldParse(t *testing.T) { assertFeature("Optics", c.Features.Optics, t) assertFeature("WlanSTA", c.Features.WlanSTA, t) assertFeature("WlanIF", c.Features.WlanIF, t) + assertFeature("Ipsec", c.Features.Ipsec, t) } func loadTestFile(t *testing.T) []byte { diff --git a/main.go b/main.go index 787ada6..b646a61 100644 --- a/main.go +++ b/main.go @@ -43,6 +43,7 @@ var ( withWlanSTA = flag.Bool("with-wlansta", false, "retrieves connected wlan station metrics") withWlanIF = flag.Bool("with-wlanif", false, "retrieves wlan interface metrics") withMonitor = flag.Bool("with-monitor", false, "retrieves ethernet interface monitor info") + withIpsec = flag.Bool("with-ipsec", false, "retrieves ipsec metrics") cfg *config.Config @@ -208,6 +209,10 @@ func collectorOptions() []collector.Option { } + if *withIpsec || cfg.Features.Ipsec { + opts = append(opts, collector.WithIpsec()) + } + if *timeout != collector.DefaultTimeout { opts = append(opts, collector.WithTimeout(*timeout)) }