[dns] Allow to specify query Class (#635)
authorBaptiste Courtois <baptiste.courtois@gmail.com>
Sun, 14 Jun 2020 10:35:33 +0000 (12:35 +0200)
committerGitHub <noreply@github.com>
Sun, 14 Jun 2020 10:35:33 +0000 (11:35 +0100)
* [dns] validate query_class and query_type config values
This allows to catch simple config errors earlier.

Signed-off-by: Baptiste Courtois <b.courtois@criteo.com>
CONFIGURATION.md
config/config.go
config/config_test.go
config/testdata/invalid-dns-class.yml [new file with mode: 0644]
config/testdata/invalid-dns-type.yml [new file with mode: 0644]
prober/dns.go
prober/dns_test.go

index ec40685836bd7fe106a7dfc6576d01b48e6567a8..0ab0d21c8fbe357414f148aa92f42e261bb697b8 100644 (file)
@@ -156,6 +156,7 @@ tls_config:
 query_name: <string>
 
 [ query_type: <string> | default = "ANY" ]
+[ query_class: <string> | default = "IN" ]
 
 # List of valid response codes.
 valid_rcodes:
index 148de8441f09e762b57149fcf9e710016df6f4da..3fddb3c52a3e31f587f498e6e00ab8846a519e89 100644 (file)
@@ -23,6 +23,7 @@ import (
 
        yaml "gopkg.in/yaml.v3"
 
+       "github.com/miekg/dns"
        "github.com/prometheus/client_golang/prometheus"
        "github.com/prometheus/common/config"
 )
@@ -175,6 +176,7 @@ type DNSProbe struct {
        IPProtocolFallback bool           `yaml:"ip_protocol_fallback,omitempty"`
        SourceIPAddress    string         `yaml:"source_ip_address,omitempty"`
        TransportProtocol  string         `yaml:"transport_protocol,omitempty"`
+       QueryClass         string         `yaml:"query_class,omitempty"` // Defaults to IN.
        QueryName          string         `yaml:"query_name,omitempty"`
        QueryType          string         `yaml:"query_type,omitempty"`   // Defaults to ANY.
        ValidRcodes        []string       `yaml:"valid_rcodes,omitempty"` // Defaults to NOERROR.
@@ -232,6 +234,17 @@ func (s *DNSProbe) UnmarshalYAML(unmarshal func(interface{}) error) error {
        if s.QueryName == "" {
                return errors.New("query name must be set for DNS module")
        }
+       if s.QueryClass != "" {
+               if _, ok := dns.StringToClass[s.QueryClass]; !ok {
+                       return fmt.Errorf("query class '%s' is not valid", s.QueryClass)
+               }
+       }
+       if s.QueryType != "" {
+               if _, ok := dns.StringToType[s.QueryType]; !ok {
+                       return fmt.Errorf("query type '%s' is not valid", s.QueryType)
+               }
+       }
+
        return nil
 }
 
index 4fc45298ffc54f82849438d7757a2d5d9c69a8ed..21c81360692ec4485983c648d1c142e2eb37d712 100644 (file)
@@ -51,6 +51,14 @@ func TestLoadBadConfigs(t *testing.T) {
                        ConfigFile:    "testdata/invalid-dns-module.yml",
                        ExpectedError: "error parsing config file: query name must be set for DNS module",
                },
+               {
+                       ConfigFile:    "testdata/invalid-dns-class.yml",
+                       ExpectedError: "error parsing config file: query class 'X' is not valid",
+               },
+               {
+                       ConfigFile:    "testdata/invalid-dns-type.yml",
+                       ExpectedError: "error parsing config file: query type 'X' is not valid",
+               },
                {
                        ConfigFile:    "testdata/invalid-http-header-match.yml",
                        ExpectedError: "error parsing config file: regexp must be set for HTTP header matchers",
diff --git a/config/testdata/invalid-dns-class.yml b/config/testdata/invalid-dns-class.yml
new file mode 100644 (file)
index 0000000..2893b31
--- /dev/null
@@ -0,0 +1,8 @@
+modules:
+  dns_test:
+    prober: dns
+    timeout: 5s
+    dns:
+      query_name: example.com
+      query_class: X
+      query_type: A
diff --git a/config/testdata/invalid-dns-type.yml b/config/testdata/invalid-dns-type.yml
new file mode 100644 (file)
index 0000000..6c01c44
--- /dev/null
@@ -0,0 +1,8 @@
+modules:
+  dns_test:
+    prober: dns
+    timeout: 5s
+    dns:
+      query_name: example.com
+      query_class: CH
+      query_type: X
index 9c26c9bbfc992900a326a391e8bef64140bbb559..edb5b28203222bc2aeadc825b9e25cc73de429ac 100644 (file)
@@ -141,6 +141,16 @@ func ProbeDNS(ctx context.Context, target string, module config.Module, registry
        registry.MustRegister(probeDNSAuthorityRRSGauge)
        registry.MustRegister(probeDNSAdditionalRRSGauge)
 
+       qc := uint16(dns.ClassINET)
+       if module.DNS.QueryClass != "" {
+               var ok bool
+               qc, ok = dns.StringToClass[module.DNS.QueryClass]
+               if !ok {
+                       level.Error(logger).Log("msg", "Invalid query class", "Class seen", module.DNS.QueryClass, "Existing classes", dns.ClassToString)
+                       return false
+               }
+       }
+
        qt := dns.TypeANY
        if module.DNS.QueryType != "" {
                var ok bool
@@ -200,9 +210,12 @@ func ProbeDNS(ctx context.Context, target string, module config.Module, registry
        }
 
        msg := new(dns.Msg)
-       msg.SetQuestion(dns.Fqdn(module.DNS.QueryName), qt)
+       msg.Id = dns.Id()
+       msg.RecursionDesired = true
+       msg.Question = make([]dns.Question, 1)
+       msg.Question[0] = dns.Question{dns.Fqdn(module.DNS.QueryName), qt, qc}
 
-       level.Info(logger).Log("msg", "Making DNS query", "target", target, "dial_protocol", dialProtocol, "query", module.DNS.QueryName, "type", qt)
+       level.Info(logger).Log("msg", "Making DNS query", "target", target, "dial_protocol", dialProtocol, "query", module.DNS.QueryName, "type", qt, "class", qc)
        timeoutDeadline, _ := ctx.Deadline()
        client.Timeout = time.Until(timeoutDeadline)
        response, _, err := client.Exchange(msg, target)
index b4f0472c81e0b9e553e2cc2c19fc8a484b510904..75dc7c33aa22abfb69056c3a9681bd1c33dd0759 100644 (file)
@@ -181,7 +181,12 @@ func authoritativeDNSHandler(w dns.ResponseWriter, r *dns.Msg) {
                        panic(err)
                }
                m.Answer = append(m.Answer, a)
-
+       } else if r.Question[0].Qclass == dns.ClassCHAOS && r.Question[0].Qtype == dns.TypeTXT {
+               txt, err := dns.NewRR("example.com. 3600 CH TXT \"goCHAOS\"")
+               if err != nil {
+                       panic(err)
+               }
+               m.Answer = append(m.Answer, txt)
        } else {
                a, err := dns.NewRR("example.com. 3600 IN A 127.0.0.1")
                if err != nil {
@@ -243,7 +248,21 @@ func TestAuthoritativeDNSResponse(t *testing.T) {
                                QueryName:          "example.com",
                                QueryType:          "SOA",
                        }, true,
-               }, {
+               },
+               {
+                       config.DNSProbe{
+                               IPProtocol:         "ip4",
+                               IPProtocolFallback: true,
+                               QueryClass:         "CH",
+                               QueryName:          "example.com",
+                               QueryType:          "TXT",
+                               ValidateAnswer: config.DNSRRValidator{
+                                       FailIfMatchesRegexp:    []string{".*IN.*"},
+                                       FailIfNotMatchesRegexp: []string{".*CH.*"},
+                               },
+                       }, true,
+               },
+               {
                        config.DNSProbe{
                                IPProtocol:         "ip4",
                                IPProtocolFallback: true,