2016-02-21 17:43:24 +00:00
|
|
|
package dns_query
|
2016-02-13 18:00:42 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"net"
|
|
|
|
"strconv"
|
2018-09-28 00:26:36 +00:00
|
|
|
"sync"
|
2016-02-13 18:00:42 +00:00
|
|
|
"time"
|
2016-07-19 13:03:28 +00:00
|
|
|
|
2017-07-21 23:56:08 +00:00
|
|
|
"github.com/miekg/dns"
|
|
|
|
|
2016-07-19 13:03:28 +00:00
|
|
|
"github.com/influxdata/telegraf"
|
|
|
|
"github.com/influxdata/telegraf/plugins/inputs"
|
2016-02-13 18:00:42 +00:00
|
|
|
)
|
|
|
|
|
2018-05-08 01:18:01 +00:00
|
|
|
type ResultType uint64
|
|
|
|
|
|
|
|
const (
|
|
|
|
Success ResultType = 0
|
|
|
|
Timeout = 1
|
|
|
|
Error = 2
|
|
|
|
)
|
|
|
|
|
2016-02-21 17:43:24 +00:00
|
|
|
type DnsQuery struct {
|
2016-02-13 18:00:42 +00:00
|
|
|
// Domains or subdomains to query
|
|
|
|
Domains []string
|
|
|
|
|
2017-11-01 00:00:06 +00:00
|
|
|
// Network protocol name
|
2017-07-21 23:56:08 +00:00
|
|
|
Network string
|
|
|
|
|
2016-02-13 18:00:42 +00:00
|
|
|
// Server to query
|
|
|
|
Servers []string
|
|
|
|
|
|
|
|
// Record type
|
2016-02-15 15:10:04 +00:00
|
|
|
RecordType string `toml:"record_type"`
|
2016-02-13 18:00:42 +00:00
|
|
|
|
|
|
|
// DNS server port number
|
|
|
|
Port int
|
|
|
|
|
|
|
|
// Dns query timeout in seconds. 0 means no timeout
|
|
|
|
Timeout int
|
|
|
|
}
|
|
|
|
|
|
|
|
var sampleConfig = `
|
2016-02-21 17:43:24 +00:00
|
|
|
## servers to query
|
2017-07-21 23:56:08 +00:00
|
|
|
servers = ["8.8.8.8"]
|
2016-02-13 18:00:42 +00:00
|
|
|
|
2017-07-21 23:56:08 +00:00
|
|
|
## Network is the network protocol name.
|
|
|
|
# network = "udp"
|
2016-02-21 17:43:24 +00:00
|
|
|
|
2017-07-21 23:56:08 +00:00
|
|
|
## Domains or subdomains to query.
|
|
|
|
# domains = ["."]
|
|
|
|
|
|
|
|
## Query record type.
|
2016-03-31 23:50:24 +00:00
|
|
|
## Posible values: A, AAAA, CNAME, MX, NS, PTR, TXT, SOA, SPF, SRV.
|
2017-07-21 23:56:08 +00:00
|
|
|
# record_type = "A"
|
2016-02-13 18:00:42 +00:00
|
|
|
|
2017-07-21 23:56:08 +00:00
|
|
|
## Dns server port.
|
|
|
|
# port = 53
|
2016-02-13 18:00:42 +00:00
|
|
|
|
2017-07-21 23:56:08 +00:00
|
|
|
## Query timeout in seconds.
|
|
|
|
# timeout = 2
|
2016-02-13 18:00:42 +00:00
|
|
|
`
|
|
|
|
|
2016-02-21 17:43:24 +00:00
|
|
|
func (d *DnsQuery) SampleConfig() string {
|
2016-02-13 18:00:42 +00:00
|
|
|
return sampleConfig
|
|
|
|
}
|
|
|
|
|
2016-02-21 17:43:24 +00:00
|
|
|
func (d *DnsQuery) Description() string {
|
2016-02-13 18:00:42 +00:00
|
|
|
return "Query given DNS server and gives statistics"
|
|
|
|
}
|
2016-02-21 17:43:24 +00:00
|
|
|
func (d *DnsQuery) Gather(acc telegraf.Accumulator) error {
|
2018-09-28 00:26:36 +00:00
|
|
|
var wg sync.WaitGroup
|
2016-02-13 18:00:42 +00:00
|
|
|
d.setDefaultValues()
|
2016-07-19 13:03:28 +00:00
|
|
|
|
2016-02-13 18:00:42 +00:00
|
|
|
for _, domain := range d.Domains {
|
|
|
|
for _, server := range d.Servers {
|
2018-09-28 00:26:36 +00:00
|
|
|
wg.Add(1)
|
|
|
|
go func(domain, server string) {
|
|
|
|
fields := make(map[string]interface{}, 2)
|
|
|
|
tags := map[string]string{
|
|
|
|
"server": server,
|
|
|
|
"domain": domain,
|
|
|
|
"record_type": d.RecordType,
|
|
|
|
}
|
|
|
|
|
|
|
|
dnsQueryTime, err := d.getDnsQueryTime(domain, server)
|
|
|
|
if err == nil {
|
|
|
|
setResult(Success, fields, tags)
|
|
|
|
fields["query_time_ms"] = dnsQueryTime
|
|
|
|
} else if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() {
|
|
|
|
setResult(Timeout, fields, tags)
|
|
|
|
} else if err != nil {
|
|
|
|
setResult(Error, fields, tags)
|
|
|
|
acc.AddError(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
acc.AddFields("dns_query", fields, tags)
|
2018-10-11 01:29:33 +00:00
|
|
|
|
|
|
|
wg.Done()
|
2018-09-28 00:26:36 +00:00
|
|
|
}(domain, server)
|
2016-02-13 18:00:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-28 00:26:36 +00:00
|
|
|
wg.Wait()
|
2017-04-24 18:13:26 +00:00
|
|
|
return nil
|
2016-02-13 18:00:42 +00:00
|
|
|
}
|
|
|
|
|
2016-02-21 17:43:24 +00:00
|
|
|
func (d *DnsQuery) setDefaultValues() {
|
2017-07-21 23:56:08 +00:00
|
|
|
if d.Network == "" {
|
|
|
|
d.Network = "udp"
|
|
|
|
}
|
|
|
|
|
2016-02-13 18:00:42 +00:00
|
|
|
if len(d.RecordType) == 0 {
|
2016-02-21 17:43:24 +00:00
|
|
|
d.RecordType = "NS"
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(d.Domains) == 0 {
|
|
|
|
d.Domains = []string{"."}
|
|
|
|
d.RecordType = "NS"
|
2016-02-13 18:00:42 +00:00
|
|
|
}
|
2016-02-21 17:43:24 +00:00
|
|
|
|
2016-02-13 18:00:42 +00:00
|
|
|
if d.Port == 0 {
|
|
|
|
d.Port = 53
|
|
|
|
}
|
2016-02-21 17:43:24 +00:00
|
|
|
|
2016-02-13 18:00:42 +00:00
|
|
|
if d.Timeout == 0 {
|
|
|
|
d.Timeout = 2
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-21 17:43:24 +00:00
|
|
|
func (d *DnsQuery) getDnsQueryTime(domain string, server string) (float64, error) {
|
2016-02-13 18:00:42 +00:00
|
|
|
dnsQueryTime := float64(0)
|
|
|
|
|
|
|
|
c := new(dns.Client)
|
|
|
|
c.ReadTimeout = time.Duration(d.Timeout) * time.Second
|
2017-07-21 23:56:08 +00:00
|
|
|
c.Net = d.Network
|
2016-02-13 18:00:42 +00:00
|
|
|
|
|
|
|
m := new(dns.Msg)
|
|
|
|
recordType, err := d.parseRecordType()
|
|
|
|
if err != nil {
|
|
|
|
return dnsQueryTime, err
|
|
|
|
}
|
|
|
|
m.SetQuestion(dns.Fqdn(domain), recordType)
|
|
|
|
m.RecursionDesired = true
|
|
|
|
|
2016-02-15 15:10:04 +00:00
|
|
|
r, rtt, err := c.Exchange(m, net.JoinHostPort(server, strconv.Itoa(d.Port)))
|
2016-02-13 18:00:42 +00:00
|
|
|
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))
|
|
|
|
}
|
2016-02-15 15:10:04 +00:00
|
|
|
dnsQueryTime = float64(rtt.Nanoseconds()) / 1e6
|
2016-02-13 18:00:42 +00:00
|
|
|
return dnsQueryTime, nil
|
|
|
|
}
|
|
|
|
|
2016-02-21 17:43:24 +00:00
|
|
|
func (d *DnsQuery) parseRecordType() (uint16, error) {
|
2016-02-13 18:00:42 +00:00
|
|
|
var recordType uint16
|
|
|
|
var error error
|
|
|
|
|
|
|
|
switch d.RecordType {
|
|
|
|
case "A":
|
|
|
|
recordType = dns.TypeA
|
2016-02-15 15:10:04 +00:00
|
|
|
case "AAAA":
|
|
|
|
recordType = dns.TypeAAAA
|
|
|
|
case "ANY":
|
|
|
|
recordType = dns.TypeANY
|
2016-02-13 18:00:42 +00:00
|
|
|
case "CNAME":
|
|
|
|
recordType = dns.TypeCNAME
|
|
|
|
case "MX":
|
|
|
|
recordType = dns.TypeMX
|
|
|
|
case "NS":
|
|
|
|
recordType = dns.TypeNS
|
2016-02-21 17:43:24 +00:00
|
|
|
case "PTR":
|
|
|
|
recordType = dns.TypePTR
|
|
|
|
case "SOA":
|
|
|
|
recordType = dns.TypeSOA
|
|
|
|
case "SPF":
|
|
|
|
recordType = dns.TypeSPF
|
|
|
|
case "SRV":
|
|
|
|
recordType = dns.TypeSRV
|
2016-02-13 18:00:42 +00:00
|
|
|
case "TXT":
|
|
|
|
recordType = dns.TypeTXT
|
|
|
|
default:
|
|
|
|
error = errors.New(fmt.Sprintf("Record type %s not recognized", d.RecordType))
|
|
|
|
}
|
|
|
|
|
|
|
|
return recordType, error
|
|
|
|
}
|
|
|
|
|
2018-05-08 01:18:01 +00:00
|
|
|
func setResult(result ResultType, fields map[string]interface{}, tags map[string]string) {
|
|
|
|
var tag string
|
|
|
|
switch result {
|
|
|
|
case Success:
|
|
|
|
tag = "success"
|
|
|
|
case Timeout:
|
|
|
|
tag = "timeout"
|
|
|
|
case Error:
|
|
|
|
tag = "error"
|
|
|
|
}
|
|
|
|
|
|
|
|
tags["result"] = tag
|
|
|
|
fields["result_code"] = uint64(result)
|
|
|
|
}
|
|
|
|
|
2016-02-13 18:00:42 +00:00
|
|
|
func init() {
|
2016-02-15 15:10:04 +00:00
|
|
|
inputs.Add("dns_query", func() telegraf.Input {
|
2016-02-21 17:43:24 +00:00
|
|
|
return &DnsQuery{}
|
2016-02-13 18:00:42 +00:00
|
|
|
})
|
|
|
|
}
|