1
0
Fork 0

Adding upstream version 1.34.4.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-24 07:26:29 +02:00
parent e393c3af3f
commit 4978089aab
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
4963 changed files with 677545 additions and 0 deletions

View file

@ -0,0 +1,93 @@
# DNS Query Input Plugin
This plugin gathers information about DNS queries such as response time and
result codes.
⭐ Telegraf v1.4.0
🏷️ system, network
💻 all
## Global configuration options <!-- @/docs/includes/plugin_config.md -->
In addition to the plugin-specific configuration settings, plugins support
additional global and plugin configuration settings. These settings are used to
modify metrics, tags, and field or create aliases and configure ordering, etc.
See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
[CONFIGURATION.md]: ../../../docs/CONFIGURATION.md#plugins
## Configuration
```toml @sample.conf
# Query given DNS server and gives statistics
[[inputs.dns_query]]
## servers to query
servers = ["8.8.8.8"]
## Network is the network protocol name.
# network = "udp"
## Domains or subdomains to query.
# domains = ["."]
## Query record type.
## Possible values: A, AAAA, CNAME, MX, NS, PTR, TXT, SOA, SPF, SRV.
# record_type = "A"
## Dns server port.
# port = 53
## Query timeout
# timeout = "2s"
## Include the specified additional properties in the resulting metric.
## The following values are supported:
## "first_ip" -- return IP of the first A and AAAA answer
## "all_ips" -- return IPs of all A and AAAA answers
# include_fields = []
```
## Metrics
- dns_query
- tags:
- server
- domain
- record_type
- result
- rcode
- fields:
- query_time_ms (float)
- result_code (int, success = 0, timeout = 1, error = 2)
- rcode_value (int)
## Rcode Descriptions
|rcode_value|rcode|Description|
|---|-----------|-----------------------------------|
|0 | NoError | No Error |
|1 | FormErr | Format Error |
|2 | ServFail | Server Failure |
|3 | NXDomain | Non-Existent Domain |
|4 | NotImp | Not Implemented |
|5 | Refused | Query Refused |
|6 | YXDomain | Name Exists when it should not |
|7 | YXRRSet | RR Set Exists when it should not |
|8 | NXRRSet | RR Set that should exist does not |
|9 | NotAuth | Server Not Authoritative for zone |
|10 | NotZone | Name not contained in zone |
|16 | BADSIG | TSIG Signature Failure |
|16 | BADVERS | Bad OPT Version |
|17 | BADKEY | Key not recognized |
|18 | BADTIME | Signature out of time window |
|19 | BADMODE | Bad TKEY Mode |
|20 | BADNAME | Duplicate key name |
|21 | BADALG | Algorithm not supported |
|22 | BADTRUNC | Bad Truncation |
|23 | BADCOOKIE | Bad/missing Server Cookie |
## Example Output
```text
dns_query,domain=google.com,rcode=NOERROR,record_type=A,result=success,server=127.0.0.1 rcode_value=0i,result_code=0i,query_time_ms=0.13746 1550020750001000000
```

View file

@ -0,0 +1,254 @@
//go:generate ../../../tools/readme_config_includer/generator
package dns_query
import (
_ "embed"
"errors"
"fmt"
"net"
"slices"
"strconv"
"sync"
"time"
"github.com/miekg/dns"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/plugins/inputs"
)
//go:embed sample.conf
var sampleConfig string
var ignoredErrors = []string{
"NXDOMAIN",
}
type resultType uint64
const (
successResult resultType = iota
timeoutResult
errorResult
)
type DNSQuery struct {
Domains []string `toml:"domains"`
Network string `toml:"network"`
Servers []string `toml:"servers"`
RecordType string `toml:"record_type"`
Port int `toml:"port"`
Timeout config.Duration `toml:"timeout"`
IncludeFields []string `toml:"include_fields"`
fieldEnabled map[string]bool
}
func (*DNSQuery) SampleConfig() string {
return sampleConfig
}
func (d *DNSQuery) Init() error {
// Convert the included fields into a lookup-table
d.fieldEnabled = make(map[string]bool, len(d.IncludeFields))
for _, f := range d.IncludeFields {
switch f {
case "first_ip", "all_ips":
default:
return fmt.Errorf("invalid field %q included", f)
}
d.fieldEnabled[f] = true
}
// Set defaults
if d.Network == "" {
d.Network = "udp"
}
if d.RecordType == "" {
d.RecordType = "NS"
}
if len(d.Domains) == 0 {
d.Domains = []string{"."}
d.RecordType = "NS"
}
if d.Port < 1 {
d.Port = 53
}
return nil
}
func (d *DNSQuery) Gather(acc telegraf.Accumulator) error {
var wg sync.WaitGroup
for _, domain := range d.Domains {
for _, server := range d.Servers {
wg.Add(1)
go func(domain, server string) {
defer wg.Done()
fields, tags, err := d.query(domain, server)
if err != nil && !slices.Contains(ignoredErrors, tags["rcode"]) {
var opErr *net.OpError
if !errors.As(err, &opErr) || !opErr.Timeout() {
acc.AddError(err)
}
}
acc.AddFields("dns_query", fields, tags)
}(domain, server)
}
}
wg.Wait()
return nil
}
func (d *DNSQuery) query(domain, server string) (map[string]interface{}, map[string]string, error) {
tags := map[string]string{
"server": server,
"domain": domain,
"record_type": d.RecordType,
"result": "error",
}
fields := map[string]interface{}{
"query_time_ms": float64(0),
"result_code": uint64(errorResult),
}
c := dns.Client{
ReadTimeout: time.Duration(d.Timeout),
Net: d.Network,
}
recordType, err := d.parseRecordType()
if err != nil {
return fields, tags, err
}
var msg dns.Msg
msg.SetQuestion(dns.Fqdn(domain), recordType)
msg.RecursionDesired = true
addr := net.JoinHostPort(server, strconv.Itoa(d.Port))
r, rtt, err := c.Exchange(&msg, addr)
if err != nil {
var opErr *net.OpError
if errors.As(err, &opErr) && opErr.Timeout() {
tags["result"] = "timeout"
fields["result_code"] = uint64(timeoutResult)
return fields, tags, err
}
return fields, tags, err
}
// Fill valid fields
tags["rcode"] = dns.RcodeToString[r.Rcode]
fields["rcode_value"] = r.Rcode
fields["query_time_ms"] = float64(rtt.Nanoseconds()) / 1e6
// Handle the failure case
if r.Rcode != dns.RcodeSuccess {
return fields, tags, fmt.Errorf("invalid answer (%s) from %s after %s query for %s", dns.RcodeToString[r.Rcode], server, d.RecordType, domain)
}
// Success
tags["result"] = "success"
fields["result_code"] = uint64(successResult)
// Fill out custom fields for specific record types
for _, record := range r.Answer {
switch x := record.(type) {
case *dns.A:
fields["name"] = x.Hdr.Name
case *dns.AAAA:
fields["name"] = x.Hdr.Name
case *dns.CNAME:
fields["name"] = x.Hdr.Name
case *dns.MX:
fields["name"] = x.Hdr.Name
fields["preference"] = x.Preference
case *dns.SOA:
fields["expire"] = x.Expire
fields["minttl"] = x.Minttl
fields["name"] = x.Hdr.Name
fields["refresh"] = x.Refresh
fields["retry"] = x.Retry
fields["serial"] = x.Serial
}
}
if d.fieldEnabled["first_ip"] {
for _, record := range r.Answer {
if ip, found := extractIP(record); found {
fields["ip"] = ip
break
}
}
}
if d.fieldEnabled["all_ips"] {
for i, record := range r.Answer {
if ip, found := extractIP(record); found {
fields["ip_"+strconv.Itoa(i)] = ip
}
}
}
return fields, tags, nil
}
func (d *DNSQuery) parseRecordType() (uint16, error) {
var recordType uint16
var err error
switch d.RecordType {
case "A":
recordType = dns.TypeA
case "AAAA":
recordType = dns.TypeAAAA
case "ANY":
recordType = dns.TypeANY
case "CNAME":
recordType = dns.TypeCNAME
case "MX":
recordType = dns.TypeMX
case "NS":
recordType = dns.TypeNS
case "PTR":
recordType = dns.TypePTR
case "SOA":
recordType = dns.TypeSOA
case "SPF":
recordType = dns.TypeSPF
case "SRV":
recordType = dns.TypeSRV
case "TXT":
recordType = dns.TypeTXT
default:
err = fmt.Errorf("record type %s not recognized", d.RecordType)
}
return recordType, err
}
func extractIP(record dns.RR) (string, bool) {
if r, ok := record.(*dns.A); ok {
return r.A.String(), true
}
if r, ok := record.(*dns.AAAA); ok {
return r.AAAA.String(), true
}
return "", false
}
func init() {
inputs.Add("dns_query", func() telegraf.Input {
return &DNSQuery{
Timeout: config.Duration(2 * time.Second),
}
})
}

View file

@ -0,0 +1,284 @@
package dns_query
import (
"testing"
"time"
"github.com/miekg/dns"
"github.com/stretchr/testify/require"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/metric"
"github.com/influxdata/telegraf/testutil"
)
var (
servers = []string{"8.8.8.8"}
domains = []string{"google.com"}
)
func TestGathering(t *testing.T) {
if testing.Short() {
t.Skip("Skipping network-dependent test in short mode.")
}
dnsConfig := DNSQuery{
Servers: servers,
Domains: domains,
Timeout: config.Duration(2 * time.Second),
}
var acc testutil.Accumulator
require.NoError(t, dnsConfig.Init())
require.NoError(t, acc.GatherError(dnsConfig.Gather))
m, ok := acc.Get("dns_query")
require.True(t, ok)
queryTime, ok := m.Fields["query_time_ms"].(float64)
require.True(t, ok)
require.NotEqual(t, float64(0), queryTime)
}
func TestGatherInvalid(t *testing.T) {
if testing.Short() {
t.Skip("Skipping network-dependent test in short mode.")
}
dnsConfig := DNSQuery{
Servers: servers,
Domains: []string{"qwerty123.example.com"},
Timeout: config.Duration(1 * time.Second),
}
var acc testutil.Accumulator
require.NoError(t, dnsConfig.Init())
require.NoError(t, dnsConfig.Gather(&acc))
require.Empty(t, acc.Errors)
}
func TestGatheringMxRecord(t *testing.T) {
if testing.Short() {
t.Skip("Skipping network-dependent test in short mode.")
}
dnsConfig := DNSQuery{
Servers: servers,
Domains: domains,
RecordType: "MX",
Timeout: config.Duration(2 * time.Second),
}
var acc testutil.Accumulator
require.NoError(t, dnsConfig.Init())
require.NoError(t, acc.GatherError(dnsConfig.Gather))
m, ok := acc.Get("dns_query")
require.True(t, ok)
queryTime, ok := m.Fields["query_time_ms"].(float64)
require.True(t, ok)
require.NotEqual(t, float64(0), queryTime)
preference, ok := m.Fields["preference"].(uint16)
require.True(t, ok)
require.NotEqual(t, 0, preference)
}
func TestGatheringRootDomain(t *testing.T) {
if testing.Short() {
t.Skip("Skipping network-dependent test in short mode.")
}
dnsConfig := DNSQuery{
Servers: servers,
Domains: []string{"."},
RecordType: "MX",
Timeout: config.Duration(2 * time.Second),
}
require.NoError(t, dnsConfig.Init())
var acc testutil.Accumulator
require.NoError(t, acc.GatherError(dnsConfig.Gather))
m, ok := acc.Get("dns_query")
require.True(t, ok)
queryTime, ok := m.Fields["query_time_ms"].(float64)
require.True(t, ok)
expected := []telegraf.Metric{
metric.New(
"dns_query",
map[string]string{
"server": "8.8.8.8",
"domain": ".",
"record_type": "MX",
"rcode": "NOERROR",
"result": "success",
},
map[string]interface{}{
"rcode_value": 0,
"result_code": uint64(0),
"query_time_ms": queryTime,
},
time.Unix(0, 0),
),
}
testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime())
}
func TestMetricContainsServerAndDomainAndRecordTypeTags(t *testing.T) {
if testing.Short() {
t.Skip("Skipping network-dependent test in short mode.")
}
dnsConfig := DNSQuery{
Servers: servers,
Domains: domains,
Timeout: config.Duration(2 * time.Second),
}
require.NoError(t, dnsConfig.Init())
var acc testutil.Accumulator
require.NoError(t, acc.GatherError(dnsConfig.Gather))
m, ok := acc.Get("dns_query")
require.True(t, ok)
queryTime, ok := m.Fields["query_time_ms"].(float64)
require.True(t, ok)
expected := []telegraf.Metric{
metric.New(
"dns_query",
map[string]string{
"server": "8.8.8.8",
"domain": "google.com",
"record_type": "NS",
"rcode": "NOERROR",
"result": "success",
},
map[string]interface{}{
"rcode_value": 0,
"result_code": uint64(0),
"query_time_ms": queryTime,
},
time.Unix(0, 0),
),
}
testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime())
}
func TestGatheringTimeout(t *testing.T) {
if testing.Short() {
t.Skip("Skipping network-dependent test in short mode.")
}
dnsConfig := DNSQuery{
Servers: servers,
Domains: domains,
Timeout: config.Duration(1 * time.Second),
Port: 60054,
}
require.NoError(t, dnsConfig.Init())
var acc testutil.Accumulator
channel := make(chan error, 1)
go func() {
channel <- acc.GatherError(dnsConfig.Gather)
}()
select {
case err := <-channel:
require.NoError(t, err)
case <-time.After(time.Second * 2):
require.Fail(t, "DNS query did not timeout")
}
}
func TestSettingDefaultValues(t *testing.T) {
dnsConfig := DNSQuery{
Timeout: config.Duration(2 * time.Second),
}
require.NoError(t, dnsConfig.Init())
require.Equal(t, []string{"."}, dnsConfig.Domains, "Default domain not equal \".\"")
require.Equal(t, "NS", dnsConfig.RecordType, "Default record type not equal 'NS'")
require.Equal(t, 53, dnsConfig.Port, "Default port number not equal 53")
require.Equal(t, config.Duration(2*time.Second), dnsConfig.Timeout, "Default timeout not equal 2s")
dnsConfig = DNSQuery{
Domains: []string{"."},
Timeout: config.Duration(2 * time.Second),
}
require.NoError(t, dnsConfig.Init())
require.Equal(t, "NS", dnsConfig.RecordType, "Default record type not equal 'NS'")
}
func TestRecordTypeParser(t *testing.T) {
tests := []struct {
record string
expected uint16
}{
{
record: "A",
expected: dns.TypeA,
},
{
record: "AAAA",
expected: dns.TypeAAAA,
},
{
record: "ANY",
expected: dns.TypeANY,
},
{
record: "CNAME",
expected: dns.TypeCNAME,
},
{
record: "MX",
expected: dns.TypeMX,
},
{
record: "NS",
expected: dns.TypeNS,
},
{
record: "PTR",
expected: dns.TypePTR,
},
{
record: "SOA",
expected: dns.TypeSOA,
},
{
record: "SPF",
expected: dns.TypeSPF,
},
{
record: "SRV",
expected: dns.TypeSRV,
},
{
record: "TXT",
expected: dns.TypeTXT,
},
}
for _, tt := range tests {
t.Run(tt.record, func(t *testing.T) {
plugin := DNSQuery{
Timeout: config.Duration(2 * time.Second),
Domains: []string{"example.com"},
RecordType: tt.record,
}
require.NoError(t, plugin.Init())
recordType, err := plugin.parseRecordType()
require.NoError(t, err)
require.Equal(t, tt.expected, recordType)
})
}
}
func TestRecordTypeParserError(t *testing.T) {
plugin := DNSQuery{
Timeout: config.Duration(2 * time.Second),
RecordType: "nil",
}
_, err := plugin.parseRecordType()
require.Error(t, err)
}

View file

@ -0,0 +1,26 @@
# Query given DNS server and gives statistics
[[inputs.dns_query]]
## servers to query
servers = ["8.8.8.8"]
## Network is the network protocol name.
# network = "udp"
## Domains or subdomains to query.
# domains = ["."]
## Query record type.
## Possible values: A, AAAA, CNAME, MX, NS, PTR, TXT, SOA, SPF, SRV.
# record_type = "A"
## Dns server port.
# port = 53
## Query timeout
# timeout = "2s"
## Include the specified additional properties in the resulting metric.
## The following values are supported:
## "first_ip" -- return IP of the first A and AAAA answer
## "all_ips" -- return IPs of all A and AAAA answers
# include_fields = []