master
go 139 lines 3.22 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package dnsmasq
4
5 import (
6 "fmt"
7 "strconv"
8 "strings"
9
10 "github.com/miekg/dns"
11 )
12
13 func (c *Collector) collect() (map[string]int64, error) {
14 mx := make(map[string]int64)
15
16 if err := c.collectCacheStatistics(mx); err != nil {
17 return nil, err
18 }
19
20 return mx, nil
21 }
22
23 func (c *Collector) collectCacheStatistics(mx map[string]int64) error {
24 /*
25 ;; flags: qr aa rd ra; QUERY: 7, ANSWER: 7, AUTHORITY: 0, ADDITIONAL: 0
26
27 ;; QUESTION SECTION:
28 ;cachesize.bind. CH TXT
29 ;insertions.bind. CH TXT
30 ;evictions.bind. CH TXT
31 ;hits.bind. CH TXT
32 ;misses.bind. CH TXT
33 ;auth.bind. CH TXT
34 ;servers.bind. CH TXT
35
36 ;; ANSWER SECTION:
37 cachesize.bind. 0 CH TXT "150"
38 insertions.bind. 0 CH TXT "1"
39 evictions.bind. 0 CH TXT "0"
40 hits.bind. 0 CH TXT "176"
41 misses.bind. 0 CH TXT "4"
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 */
45
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 := c.query(q)
60 if err != nil {
61 return err
62 }
63
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 }
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 }
100 v, err := strconv.ParseFloat(txt.Txt[0], 64)
101 if err != nil {
102 return fmt.Errorf("parse '%s' (%s): %v", txt.Hdr.Name, txt.Txt[0], err)
103 }
104
105 mx[name] = int64(v)
106 }
107 }
108 }
109
110 return nil
111 }
112
113 func (c *Collector) 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{
120 {Name: question, Qtype: dns.TypeTXT, Qclass: dns.ClassCHAOS},
121 },
122 }
123
124 r, _, err := c.dnsClient.Exchange(msg, c.Address)
125 if err != nil {
126 return nil, err
127 }
128
129 if r == nil {
130 return nil, fmt.Errorf("'%s' question '%s', returned an empty response", c.Address, question)
131 }
132
133 if r.Rcode != dns.RcodeSuccess {
134 s := dns.RcodeToString[r.Rcode]
135 return nil, fmt.Errorf("'%s' question '%s' returned '%s' (%d) response code", c.Address, question, s, r.Rcode)
136 }
137
138 return r, nil
139 }