go.d dnsmasq: query metrics individually to handle v2.90+ SERVFAIL (#18376)
Ilya Mashchenko committed
Aug 19, 2024 at 18:28 UTC
bacd83a36570e9f8a2a99c8ba72fb67e7d0b3d5f
1 file changed
+65
-52
src/go/plugin/go.d/modules/dnsmasq/collect.go
+65
-52
@@ -11,20 +11,16 @@ import (
11
)
12
13
func (d *Dnsmasq) collect() (map[string]int64, error) {
14
- r, err := d.queryCacheStatistics()
15
- if err != nil {
16
- return nil, err
17
- }
14
+ mx := make(map[string]int64)
15
19
- ms := make(map[string]int64)
20
- if err = d.collectResponse(ms, r); err != nil {
16
+ if err := d.collectCacheStatistics(mx); err != nil {
17
return nil, err
18
}
19
24
- return ms, nil
20
+ return mx, nil
21
}
22
27
-func (d *Dnsmasq) collectResponse(ms map[string]int64, resp *dns.Msg) error {
23
+func (d *Dnsmasq) collectCacheStatistics(mx map[string]int64) error {
24
/*
25
;; flags: qr aa rd ra; QUERY: 7, ANSWER: 7, AUTHORITY: 0, ADDITIONAL: 0
26
@@ -46,68 +42,82 @@ func (d *Dnsmasq) collectResponse(ms map[string]int64, resp *dns.Msg) error {
42
auth.bind. 0 CH TXT "0"
43
servers.bind. 0 CH TXT "10.0.0.1#53 0 0" "1.1.1.1#53 4 3" "1.0.0.1#53 3 0"
44
*/
49
- for _, a := range resp.Answer {
50
- txt, ok := a.(*dns.TXT)
51
- if !ok {
52
- continue
53
- }
45
55
- idx := strings.IndexByte(txt.Hdr.Name, '.')
56
- if idx == -1 {
57
- continue
46
+ questions := []string{
47
+ "servers.bind.",
48
+ "cachesize.bind.",
49
+ "insertions.bind.",
50
+ "evictions.bind.",
51
+ "hits.bind.",
52
+ "misses.bind.",
53
+ // auth.bind query is only supported if dnsmasq has been built to support running as an authoritative name server
54
+ // See https://github.com/netdata/netdata/issues/13766
55
+ //"auth.bind.",
56
+ }
57
+
58
+ for _, q := range questions {
59
+ resp, err := d.query(q)
60
+ if err != nil {
61
+ return err
62
}
63
60
- switch name := txt.Hdr.Name[:idx]; name {
61
- case "servers":
62
- for _, entry := range txt.Txt {
63
- parts := strings.Fields(entry)
64
- if len(parts) != 3 {
65
- return fmt.Errorf("parse %s (%s): unexpected format", txt.Hdr.Name, entry)
64
+ for _, a := range resp.Answer {
65
+ txt, ok := a.(*dns.TXT)
66
+ if !ok {
67
+ continue
68
+ }
69
+
70
+ idx := strings.IndexByte(txt.Hdr.Name, '.')
71
+ if idx == -1 {
72
+ continue
73
+ }
74
+
75
+ name := txt.Hdr.Name[:idx]
76
+
77
+ switch name {
78
+ case "servers":
79
+ for _, entry := range txt.Txt {
80
+ parts := strings.Fields(entry)
81
+ if len(parts) != 3 {
82
+ return fmt.Errorf("parse %s (%s): unexpected format", txt.Hdr.Name, entry)
83
+ }
84
+ queries, err := strconv.ParseFloat(parts[1], 64)
85
+ if err != nil {
86
+ return fmt.Errorf("parse '%s' (%s): %v", txt.Hdr.Name, entry, err)
87
+ }
88
+ failedQueries, err := strconv.ParseFloat(parts[2], 64)
89
+ if err != nil {
90
+ return fmt.Errorf("parse '%s' (%s): %v", txt.Hdr.Name, entry, err)
91
+ }
92
+
93
+ mx["queries"] += int64(queries)
94
+ mx["failed_queries"] += int64(failedQueries)
95
}
67
- queries, err := strconv.ParseFloat(parts[1], 64)
68
- if err != nil {
69
- return fmt.Errorf("parse '%s' (%s): %v", txt.Hdr.Name, entry, err)
96
+ case "cachesize", "insertions", "evictions", "hits", "misses", "auth":
97
+ if len(txt.Txt) != 1 {
98
+ return fmt.Errorf("parse '%s' (%v): unexpected format", txt.Hdr.Name, txt.Txt)
99
}
71
- failedQueries, err := strconv.ParseFloat(parts[2], 64)
100
+ v, err := strconv.ParseFloat(txt.Txt[0], 64)
101
if err != nil {
73
- return fmt.Errorf("parse '%s' (%s): %v", txt.Hdr.Name, entry, err)
102
+ return fmt.Errorf("parse '%s' (%s): %v", txt.Hdr.Name, txt.Txt[0], err)
103
}
104
76
- ms["queries"] += int64(queries)
77
- ms["failed_queries"] += int64(failedQueries)
78
- }
79
- case "cachesize", "insertions", "evictions", "hits", "misses", "auth":
80
- if len(txt.Txt) != 1 {
81
- return fmt.Errorf("parse '%s' (%v): unexpected format", txt.Hdr.Name, txt.Txt)
82
- }
83
- v, err := strconv.ParseFloat(txt.Txt[0], 64)
84
- if err != nil {
85
- return fmt.Errorf("parse '%s' (%s): %v", txt.Hdr.Name, txt.Txt[0], err)
105
+ mx[name] = int64(v)
106
}
87
-
88
- ms[name] = int64(v)
107
}
108
}
109
+
110
return nil
111
}
112
94
-func (d *Dnsmasq) queryCacheStatistics() (*dns.Msg, error) {
113
+func (d *Dnsmasq) query(question string) (*dns.Msg, error) {
114
msg := &dns.Msg{
115
MsgHdr: dns.MsgHdr{
116
Id: dns.Id(),
117
RecursionDesired: true,
118
},
119
Question: []dns.Question{
101
- {Name: "cachesize.bind.", Qtype: dns.TypeTXT, Qclass: dns.ClassCHAOS},
102
- {Name: "insertions.bind.", Qtype: dns.TypeTXT, Qclass: dns.ClassCHAOS},
103
- {Name: "evictions.bind.", Qtype: dns.TypeTXT, Qclass: dns.ClassCHAOS},
104
- {Name: "hits.bind.", Qtype: dns.TypeTXT, Qclass: dns.ClassCHAOS},
105
- {Name: "misses.bind.", Qtype: dns.TypeTXT, Qclass: dns.ClassCHAOS},
106
- // TODO: collect auth.bind if available
107
- // auth.bind query is only supported if dnsmasq has been built
108
- // to support running as an authoritative name server. See https://github.com/netdata/netdata/issues/13766
109
- //{Name: "auth.bind.", Qtype: dns.TypeTXT, Qclass: dns.ClassCHAOS},
110
- {Name: "servers.bind.", Qtype: dns.TypeTXT, Qclass: dns.ClassCHAOS},
120
+ {Name: question, Qtype: dns.TypeTXT, Qclass: dns.ClassCHAOS},
121
},
122
}
123
@@ -115,12 +125,15 @@ func (d *Dnsmasq) queryCacheStatistics() (*dns.Msg, error) {
125
if err != nil {
126
return nil, err
127
}
128
+
129
if r == nil {
119
- return nil, fmt.Errorf("'%s' returned an empty response", d.Address)
130
+ return nil, fmt.Errorf("'%s' question '%s', returned an empty response", d.Address, question)
131
}
132
+
133
if r.Rcode != dns.RcodeSuccess {
134
s := dns.RcodeToString[r.Rcode]
123
- return nil, fmt.Errorf("'%s' returned '%s' (%d) response code", d.Address, s, r.Rcode)
135
+ return nil, fmt.Errorf("'%s' question '%s' returned '%s' (%d) response code", d.Address, question, s, r.Rcode)
136
}
137
+
138
return r, nil
139
}