github.com/netdata/go.d.plugin@v0.58.1/modules/dnsmasq/collect.go (about) 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 (d *Dnsmasq) collect() (map[string]int64, error) { 14 r, err := d.queryCacheStatistics() 15 if err != nil { 16 return nil, err 17 } 18 19 ms := make(map[string]int64) 20 if err = d.collectResponse(ms, r); err != nil { 21 return nil, err 22 } 23 24 return ms, nil 25 } 26 27 func (d *Dnsmasq) collectResponse(ms map[string]int64, resp *dns.Msg) error { 28 /* 29 ;; flags: qr aa rd ra; QUERY: 7, ANSWER: 7, AUTHORITY: 0, ADDITIONAL: 0 30 31 ;; QUESTION SECTION: 32 ;cachesize.bind. CH TXT 33 ;insertions.bind. CH TXT 34 ;evictions.bind. CH TXT 35 ;hits.bind. CH TXT 36 ;misses.bind. CH TXT 37 ;auth.bind. CH TXT 38 ;servers.bind. CH TXT 39 40 ;; ANSWER SECTION: 41 cachesize.bind. 0 CH TXT "150" 42 insertions.bind. 0 CH TXT "1" 43 evictions.bind. 0 CH TXT "0" 44 hits.bind. 0 CH TXT "176" 45 misses.bind. 0 CH TXT "4" 46 auth.bind. 0 CH TXT "0" 47 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" 48 */ 49 for _, a := range resp.Answer { 50 txt, ok := a.(*dns.TXT) 51 if !ok { 52 continue 53 } 54 55 idx := strings.IndexByte(txt.Hdr.Name, '.') 56 if idx == -1 { 57 continue 58 } 59 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) 66 } 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) 70 } 71 failedQueries, err := strconv.ParseFloat(parts[2], 64) 72 if err != nil { 73 return fmt.Errorf("parse '%s' (%s): %v", txt.Hdr.Name, entry, err) 74 } 75 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) 86 } 87 88 ms[name] = int64(v) 89 } 90 } 91 return nil 92 } 93 94 func (d *Dnsmasq) queryCacheStatistics() (*dns.Msg, error) { 95 msg := &dns.Msg{ 96 MsgHdr: dns.MsgHdr{ 97 Id: dns.Id(), 98 RecursionDesired: true, 99 }, 100 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}, 111 }, 112 } 113 114 r, _, err := d.dnsClient.Exchange(msg, d.Address) 115 if err != nil { 116 return nil, err 117 } 118 if r == nil { 119 return nil, fmt.Errorf("'%s' returned an empty response", d.Address) 120 } 121 if r.Rcode != dns.RcodeSuccess { 122 s := dns.RcodeToString[r.Rcode] 123 return nil, fmt.Errorf("'%s' returned '%s' (%d) response code", d.Address, s, r.Rcode) 124 } 125 return r, nil 126 }