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  }