Dns query input plugin

This commit is contained in:
Marcin Jasion 2016-02-13 19:00:42 +01:00 committed by Cameron Sparr
parent a18f535f21
commit 636dc27ead
5 changed files with 323 additions and 0 deletions

1
Godeps
View File

@ -50,3 +50,4 @@ gopkg.in/dancannon/gorethink.v1 6f088135ff288deb9d5546f4c71919207f891a70
gopkg.in/fatih/pool.v2 cba550ebf9bce999a02e963296d4bc7a486cb715
gopkg.in/mgo.v2 03c9f3ee4c14c8e51ee521a6a7d0425658dd6f64
gopkg.in/yaml.v2 f7716cbe52baa25d2e9b0d0da546fcf909fc16b4
github.com/miekg/dns e0d84d97e59bcb6561eae269c4e94d25b66822cb

View File

@ -6,6 +6,7 @@ import (
_ "github.com/influxdata/telegraf/plugins/inputs/bcache"
_ "github.com/influxdata/telegraf/plugins/inputs/couchdb"
_ "github.com/influxdata/telegraf/plugins/inputs/disque"
_ "github.com/influxdata/telegraf/plugins/inputs/dns"
_ "github.com/influxdata/telegraf/plugins/inputs/docker"
_ "github.com/influxdata/telegraf/plugins/inputs/dovecot"
_ "github.com/influxdata/telegraf/plugins/inputs/elasticsearch"

View File

@ -0,0 +1,51 @@
# DNS Input Plugin
The DNS plugin gathers dns query times in miliseconds - like [Dig](https://en.wikipedia.org/wiki/Dig_\(command\))
### Configuration:
```
# Sample Config:
[[inputs.dns]]
### Domains or subdomains to query
domains = ["mjasion.pl"] # required
### servers to query
servers = ["8.8.8.8"] # required
### Query record type. Posible values: A, CNAME, MX, TXT, NS. Default is "A"
recordType = "A" # optional
### Dns server port. 53 is default
port = 53 # optional
### Query timeout in seconds. Default is 2 seconds
timeout = 2 # optional
```
For querying more than one record type make:
```
[[inputs.dns]]
domains = ["mjasion.pl"]
servers = ["8.8.8.8", "8.8.4.4"]
recordType = "A"
[[inputs.dns]]
domains = ["mjasion.pl"]
servers = ["8.8.8.8", "8.8.4.4"]
recordType = "MX"
```
### Tags:
- server
- domain
- recordType
### Example output:
```
./telegraf -config telegraf.conf -test -input-filter dns -test
> dns,domain=mjasion.pl,recordType=A,server=8.8.8.8 value=25.236181 1455452083165126877
```

143
plugins/inputs/dns/dns.go Normal file
View File

@ -0,0 +1,143 @@
package dns
import (
"errors"
"fmt"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/inputs"
"github.com/miekg/dns"
"net"
"strconv"
"time"
)
type Dns struct {
// Domains or subdomains to query
Domains []string
// Server to query
Servers []string
// Record type
RecordType string
// DNS server port number
Port int
// Dns query timeout in seconds. 0 means no timeout
Timeout int
}
var sampleConfig = `
### Domains or subdomains to query
domains = ["mjasion.pl"] # required
### servers to query
servers = ["8.8.8.8"] # required
### Query record type. Posible values: A, CNAME, MX, TXT, NS. Default is "A"
recordType = "A" # optional
### Dns server port. 53 is default
port = 53 # optional
### Query timeout in seconds. Default is 2 seconds
timeout = 2 # optional
`
func (d *Dns) SampleConfig() string {
return sampleConfig
}
func (d *Dns) Description() string {
return "Query given DNS server and gives statistics"
}
func (d *Dns) Gather(acc telegraf.Accumulator) error {
d.setDefaultValues()
for _, domain := range d.Domains {
for _, server := range d.Servers {
dnsQueryTime, err := d.getDnsQueryTime(domain, server)
if err != nil {
return err
}
tags := map[string]string{
"server": server,
"domain": domain,
"recordType": d.RecordType,
}
acc.Add("dns", dnsQueryTime, tags)
}
}
return nil
}
func (d *Dns) setDefaultValues() {
if len(d.RecordType) == 0 {
d.RecordType = "A"
}
if d.Port == 0 {
d.Port = 53
}
if d.Timeout == 0 {
d.Timeout = 2
}
}
func (d *Dns) getDnsQueryTime(domain string, server string) (float64, error) {
dnsQueryTime := float64(0)
c := new(dns.Client)
c.ReadTimeout = time.Duration(d.Timeout) * time.Second
m := new(dns.Msg)
recordType, err := d.parseRecordType()
if err != nil {
return dnsQueryTime, err
}
m.SetQuestion(dns.Fqdn(domain), recordType)
m.RecursionDesired = true
start_time := time.Now()
r, _, err := c.Exchange(m, net.JoinHostPort(server, strconv.Itoa(d.Port)))
queryDuration := time.Since(start_time)
if err != nil {
return dnsQueryTime, err
}
if r.Rcode != dns.RcodeSuccess {
return dnsQueryTime, errors.New(fmt.Sprintf("Invalid answer name %s after %s query for %s\n", domain, d.RecordType, domain))
}
dnsQueryTime = float64(queryDuration.Nanoseconds()) / 1e6
return dnsQueryTime, nil
}
func (d *Dns) parseRecordType() (uint16, error) {
var recordType uint16
var error error
switch d.RecordType {
case "A":
recordType = dns.TypeA
case "CNAME":
recordType = dns.TypeCNAME
case "MX":
recordType = dns.TypeMX
case "NS":
recordType = dns.TypeNS
case "TXT":
recordType = dns.TypeTXT
default:
error = errors.New(fmt.Sprintf("Record type %s not recognized", d.RecordType))
}
return recordType, error
}
func init() {
inputs.Add("dns", func() telegraf.Input {
return &Dns{}
})
}

View File

@ -0,0 +1,127 @@
package dns
import (
"github.com/influxdata/telegraf/testutil"
"github.com/miekg/dns"
"github.com/stretchr/testify/assert"
"testing"
"time"
)
var servers = []string{"8.8.8.8"}
var domains = []string{"mjasion.pl"}
func TestGathering(t *testing.T) {
var dnsConfig = Dns{
Servers: servers,
Domains: domains,
}
var acc testutil.Accumulator
dnsConfig.Gather(&acc)
metric, _ := acc.Get("dns")
queryTime, _ := metric.Fields["value"].(float64)
assert.NotEqual(t, 0, queryTime)
}
func TestGatheringMxRecord(t *testing.T) {
var dnsConfig = Dns{
Servers: servers,
Domains: domains,
}
var acc testutil.Accumulator
dnsConfig.RecordType = "MX"
dnsConfig.Gather(&acc)
metric, _ := acc.Get("dns")
queryTime, _ := metric.Fields["value"].(float64)
assert.NotEqual(t, 0, queryTime)
}
func TestMetricContainsServerAndDomainAndRecordTypeTags(t *testing.T) {
var dnsConfig = Dns{
Servers: servers,
Domains: domains,
}
var acc testutil.Accumulator
tags := map[string]string{
"server": "8.8.8.8",
"domain": "mjasion.pl",
"recordType": "A",
}
fields := map[string]interface{}{}
dnsConfig.Gather(&acc)
metric, _ := acc.Get("dns")
queryTime, _ := metric.Fields["value"].(float64)
fields["value"] = queryTime
acc.AssertContainsTaggedFields(t, "dns", fields, tags)
}
func TestGatheringTimeout(t *testing.T) {
var dnsConfig = Dns{
Servers: servers,
Domains: domains,
}
var acc testutil.Accumulator
dnsConfig.Port = 60054
dnsConfig.Timeout = 1
var err error
channel := make(chan error, 1)
go func() {
channel <- dnsConfig.Gather(&acc)
}()
select {
case res := <-channel:
err = res
case <-time.After(time.Second * 2):
err = nil
}
assert.Error(t, err)
assert.Contains(t, err.Error(), "i/o timeout")
}
func TestSettingDefaultValues(t *testing.T) {
dnsConfig := Dns{}
dnsConfig.setDefaultValues()
assert.Equal(t, "A", dnsConfig.RecordType, "Default record type not equal 'A'")
assert.Equal(t, 53, dnsConfig.Port, "Default port number not equal 53")
assert.Equal(t, 2, dnsConfig.Timeout, "Default timeout not equal 2")
}
func TestRecordTypeParser(t *testing.T) {
var dnsConfig = Dns{}
var recordType uint16
var err error
dnsConfig.RecordType = "A"
recordType, err = dnsConfig.parseRecordType()
assert.Equal(t, dns.TypeA, recordType)
dnsConfig.RecordType = "CNAME"
recordType, err = dnsConfig.parseRecordType()
assert.Equal(t, dns.TypeCNAME, recordType)
dnsConfig.RecordType = "MX"
recordType, err = dnsConfig.parseRecordType()
assert.Equal(t, dns.TypeMX, recordType)
dnsConfig.RecordType = "NS"
recordType, err = dnsConfig.parseRecordType()
assert.Equal(t, dns.TypeNS, recordType)
dnsConfig.RecordType = "TXT"
recordType, err = dnsConfig.parseRecordType()
assert.Equal(t, dns.TypeTXT, recordType)
dnsConfig.RecordType = "nil"
recordType, err = dnsConfig.parseRecordType()
assert.Error(t, err)
}