summaryrefslogtreecommitdiffstats
path: root/src/go/collectors/go.d.plugin/modules/dnsmasq/collect.go
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/go/collectors/go.d.plugin/modules/dnsmasq/collect.go126
1 files changed, 126 insertions, 0 deletions
diff --git a/src/go/collectors/go.d.plugin/modules/dnsmasq/collect.go b/src/go/collectors/go.d.plugin/modules/dnsmasq/collect.go
new file mode 100644
index 000000000..2561688d7
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/dnsmasq/collect.go
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package dnsmasq
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+
+ "github.com/miekg/dns"
+)
+
+func (d *Dnsmasq) collect() (map[string]int64, error) {
+ r, err := d.queryCacheStatistics()
+ if err != nil {
+ return nil, err
+ }
+
+ ms := make(map[string]int64)
+ if err = d.collectResponse(ms, r); err != nil {
+ return nil, err
+ }
+
+ return ms, nil
+}
+
+func (d *Dnsmasq) collectResponse(ms map[string]int64, resp *dns.Msg) error {
+ /*
+ ;; flags: qr aa rd ra; QUERY: 7, ANSWER: 7, AUTHORITY: 0, ADDITIONAL: 0
+
+ ;; QUESTION SECTION:
+ ;cachesize.bind. CH TXT
+ ;insertions.bind. CH TXT
+ ;evictions.bind. CH TXT
+ ;hits.bind. CH TXT
+ ;misses.bind. CH TXT
+ ;auth.bind. CH TXT
+ ;servers.bind. CH TXT
+
+ ;; ANSWER SECTION:
+ cachesize.bind. 0 CH TXT "150"
+ insertions.bind. 0 CH TXT "1"
+ evictions.bind. 0 CH TXT "0"
+ hits.bind. 0 CH TXT "176"
+ misses.bind. 0 CH TXT "4"
+ auth.bind. 0 CH TXT "0"
+ 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"
+ */
+ for _, a := range resp.Answer {
+ txt, ok := a.(*dns.TXT)
+ if !ok {
+ continue
+ }
+
+ idx := strings.IndexByte(txt.Hdr.Name, '.')
+ if idx == -1 {
+ continue
+ }
+
+ switch name := txt.Hdr.Name[:idx]; name {
+ case "servers":
+ for _, entry := range txt.Txt {
+ parts := strings.Fields(entry)
+ if len(parts) != 3 {
+ return fmt.Errorf("parse %s (%s): unexpected format", txt.Hdr.Name, entry)
+ }
+ queries, err := strconv.ParseFloat(parts[1], 64)
+ if err != nil {
+ return fmt.Errorf("parse '%s' (%s): %v", txt.Hdr.Name, entry, err)
+ }
+ failedQueries, err := strconv.ParseFloat(parts[2], 64)
+ if err != nil {
+ return fmt.Errorf("parse '%s' (%s): %v", txt.Hdr.Name, entry, err)
+ }
+
+ ms["queries"] += int64(queries)
+ ms["failed_queries"] += int64(failedQueries)
+ }
+ case "cachesize", "insertions", "evictions", "hits", "misses", "auth":
+ if len(txt.Txt) != 1 {
+ return fmt.Errorf("parse '%s' (%v): unexpected format", txt.Hdr.Name, txt.Txt)
+ }
+ v, err := strconv.ParseFloat(txt.Txt[0], 64)
+ if err != nil {
+ return fmt.Errorf("parse '%s' (%s): %v", txt.Hdr.Name, txt.Txt[0], err)
+ }
+
+ ms[name] = int64(v)
+ }
+ }
+ return nil
+}
+
+func (d *Dnsmasq) queryCacheStatistics() (*dns.Msg, error) {
+ msg := &dns.Msg{
+ MsgHdr: dns.MsgHdr{
+ Id: dns.Id(),
+ RecursionDesired: true,
+ },
+ Question: []dns.Question{
+ {Name: "cachesize.bind.", Qtype: dns.TypeTXT, Qclass: dns.ClassCHAOS},
+ {Name: "insertions.bind.", Qtype: dns.TypeTXT, Qclass: dns.ClassCHAOS},
+ {Name: "evictions.bind.", Qtype: dns.TypeTXT, Qclass: dns.ClassCHAOS},
+ {Name: "hits.bind.", Qtype: dns.TypeTXT, Qclass: dns.ClassCHAOS},
+ {Name: "misses.bind.", Qtype: dns.TypeTXT, Qclass: dns.ClassCHAOS},
+ // TODO: collect auth.bind if available
+ // auth.bind query is only supported if dnsmasq has been built
+ // to support running as an authoritative name server. See https://github.com/netdata/netdata/issues/13766
+ //{Name: "auth.bind.", Qtype: dns.TypeTXT, Qclass: dns.ClassCHAOS},
+ {Name: "servers.bind.", Qtype: dns.TypeTXT, Qclass: dns.ClassCHAOS},
+ },
+ }
+
+ r, _, err := d.dnsClient.Exchange(msg, d.Address)
+ if err != nil {
+ return nil, err
+ }
+ if r == nil {
+ return nil, fmt.Errorf("'%s' returned an empty response", d.Address)
+ }
+ if r.Rcode != dns.RcodeSuccess {
+ s := dns.RcodeToString[r.Rcode]
+ return nil, fmt.Errorf("'%s' returned '%s' (%d) response code", d.Address, s, r.Rcode)
+ }
+ return r, nil
+}