From d723fdfe383357721c7c4a9185dd442ca9bb3f72 Mon Sep 17 00:00:00 2001 From: wolmi Date: Tue, 4 Feb 2020 04:03:45 +0100 Subject: [PATCH] Added support to auto discovery with SRV DNS registry (#60) * Added support to auto discovery witih SRV The yaml configuration has been modified to suport srv parameters. Now the name is taken form the router identity to allow dynamic discovery of devices based on SRV registry. * Corrected format * feat(collector): Added support to use custom DNS * feat(collector): get default dns server from resolv.conf * Remove getIdentity on the static config devices --- README.md | 18 ++++++++++ collector/collector.go | 74 +++++++++++++++++++++++++++++++++++++++++- config/config.go | 18 +++++++--- go.mod | 1 + go.sum | 15 +++++++++ 5 files changed, 121 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index aef9f3d..37239b2 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,19 @@ devices: port: 8999 user: prometheus2 password: password_to_second_router + - name: routers_srv_dns + srv: + record: _mikrotik._udp.example.com + user: prometheus + password: password_to_all_dns_routers + - name: routers_srv_custom_dns + srv: + record: _mikrotik2._udp.example.com + dns: + address: 1.1.1.1 + port: 53 + user: prometheus + password: password_to_all_dns_routers features: bgp: true @@ -64,6 +77,11 @@ features: optics: true ``` +If you add a devices with the `srv` parameter instead of `address` the exporter will perform a DNS query +to obtain the SRV record and discover the devices dynamically. Also, you can specify a DNS server to use +on the query. + + ###### example output ``` diff --git a/collector/collector.go b/collector/collector.go index 9139a8d..6a409ce 100644 --- a/collector/collector.go +++ b/collector/collector.go @@ -8,11 +8,15 @@ import ( "fmt" "io" "net" + "os" + "strconv" + "strings" "sync" "time" "mikrotik-exporter/config" + "github.com/miekg/dns" "github.com/prometheus/client_golang/prometheus" log "github.com/sirupsen/logrus" routeros "gopkg.in/routeros.v2" @@ -22,6 +26,7 @@ const ( namespace = "mikrotik" apiPort = "8728" apiPortTLS = "8729" + dnsPort = 53 // DefaultTimeout defines the default timeout when connecting to a router DefaultTimeout = 5 * time.Second @@ -194,9 +199,52 @@ func (c *collector) Describe(ch chan<- *prometheus.Desc) { // Collect implements the prometheus.Collector interface. func (c *collector) Collect(ch chan<- prometheus.Metric) { wg := sync.WaitGroup{} - wg.Add(len(c.devices)) + + var realDevices []config.Device for _, dev := range c.devices { + if (config.SrvRecord{}) != dev.Srv { + log.WithFields(log.Fields{ + "SRV": dev.Srv.Record, + }).Info("SRV configuration detected") + conf, _ := dns.ClientConfigFromFile("/etc/resolv.conf") + dnsServer := net.JoinHostPort(conf.Servers[0], strconv.Itoa(dnsPort)) + if (config.DnsServer{}) != dev.Srv.Dns { + dnsServer = net.JoinHostPort(dev.Srv.Dns.Address, strconv.Itoa(dev.Srv.Dns.Port)) + log.WithFields(log.Fields{ + "DnsServer": dnsServer, + }).Info("Custom DNS config detected") + } + dnsMsg := new(dns.Msg) + dnsCli := new(dns.Client) + + dnsMsg.RecursionDesired = true + dnsMsg.SetQuestion(dns.Fqdn(dev.Srv.Record), dns.TypeSRV) + r, _, err := dnsCli.Exchange(dnsMsg, dnsServer) + + if err != nil { + os.Exit(1) + } + + for _, k := range r.Answer { + if s, ok := k.(*dns.SRV); ok { + d := config.Device{} + d.Name = strings.TrimRight(s.Target, ".") + d.Address = strings.TrimRight(s.Target, ".") + d.User = dev.User + d.Password = dev.Password + c.getIdentity(&d) + realDevices = append(realDevices, d) + } + } + } else { + realDevices = append(realDevices, dev) + } + } + + wg.Add(len(realDevices)) + + for _, dev := range realDevices { go func(d config.Device) { c.collectForDevice(d, ch) wg.Done() @@ -206,6 +254,30 @@ func (c *collector) Collect(ch chan<- prometheus.Metric) { wg.Wait() } +func (c *collector) getIdentity(d *config.Device) error { + cl, err := c.connect(d) + if err != nil { + log.WithFields(log.Fields{ + "device": d.Name, + "error": err, + }).Error("error dialing device") + return err + } + defer cl.Close() + reply, err := cl.Run("/system/identity/print") + if err != nil { + log.WithFields(log.Fields{ + "device": d.Name, + "error": err, + }).Error("error fetching ethernet interfaces") + return err + } + for _, id := range reply.Re { + d.Name = id.Map["name"] + } + return nil +} + func (c *collector) collectForDevice(d config.Device, ch chan<- prometheus.Metric) { begin := time.Now() diff --git a/config/config.go b/config/config.go index 12f60f3..981a439 100644 --- a/config/config.go +++ b/config/config.go @@ -29,13 +29,23 @@ type Config struct { // Device represents a target device type Device struct { - Name string `yaml:"name"` - Address string `yaml:"address"` - User string `yaml:"user"` - Password string `yaml:"password"` + Name string `yaml:"name"` + Address string `yaml:"address,omitempty"` + Srv SrvRecord `yaml:"srv,omitempty"` + User string `yaml:"user"` + Password string `yaml:"password"` Port string `yaml:"port"` } +type SrvRecord struct { + Record string `yaml:"record"` + Dns DnsServer `yaml:"dns,omitempty"` +} +type DnsServer struct { + Address string `yaml:"address"` + Port int `yaml:"port"` +} + // Load reads YAML from reader and unmashals in Config func Load(r io.Reader) (*Config, error) { b, err := ioutil.ReadAll(r) diff --git a/go.mod b/go.mod index 4735b2b..45c33dc 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module mikrotik-exporter go 1.13 require ( + github.com/miekg/dns v1.1.22 github.com/prometheus/client_golang v1.2.1 github.com/prometheus/common v0.7.0 github.com/sirupsen/logrus v1.4.2 diff --git a/go.sum b/go.sum index b6c230e..54fe2aa 100644 --- a/go.sum +++ b/go.sum @@ -32,6 +32,8 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.1.22 h1:Jm64b3bO9kP43ddLjL2EY3Io6bmy1qGb9Xxz6TqS6rc= +github.com/miekg/dns v1.1.22/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= @@ -66,17 +68,30 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392 h1:ACG4HJsFiNMf47Y4PeRoebLNy/2lXT9EtprMuTFWt1M= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY= golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=