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=