1 package probers
2
3 import (
4 "fmt"
5 "net"
6 "strconv"
7 "strings"
8
9 "github.com/letsencrypt/boulder/observer/probers"
10 "github.com/letsencrypt/boulder/strictyaml"
11 "github.com/miekg/dns"
12 "github.com/prometheus/client_golang/prometheus"
13 )
14
15 var (
16 validQTypes = map[string]uint16{"A": 1, "TXT": 16, "AAAA": 28, "CAA": 257}
17 )
18
19
20 type DNSConf struct {
21 Proto string `yaml:"protocol"`
22 Server string `yaml:"server"`
23 Recurse bool `yaml:"recurse"`
24 QName string `yaml:"query_name"`
25 QType string `yaml:"query_type"`
26 }
27
28
29 func (c DNSConf) Kind() string {
30 return "DNS"
31 }
32
33
34 func (c DNSConf) UnmarshalSettings(settings []byte) (probers.Configurer, error) {
35 var conf DNSConf
36 err := strictyaml.Unmarshal(settings, &conf)
37 if err != nil {
38 return nil, err
39 }
40 return conf, nil
41 }
42
43 func (c DNSConf) validateServer() error {
44 server := strings.Trim(strings.ToLower(c.Server), " ")
45
46 host, port, err := net.SplitHostPort(server)
47 if err != nil || port == "" {
48 return fmt.Errorf(
49 "invalid `server`, %q, could not be split: %s", c.Server, err)
50 }
51
52 portNum, err := strconv.Atoi(port)
53 if err != nil {
54 return fmt.Errorf(
55 "invalid `server`, %q, port must be a number", c.Server)
56 }
57 if portNum <= 0 || portNum > 65535 {
58 return fmt.Errorf(
59 "invalid `server`, %q, port number must be one in [1-65535]", c.Server)
60 }
61
62 IPv6 := net.ParseIP(host).To16()
63 IPv4 := net.ParseIP(host).To4()
64 FQDN := dns.IsFqdn(dns.Fqdn(host))
65 if IPv6 == nil && IPv4 == nil && !FQDN {
66 return fmt.Errorf(
67 "invalid `server`, %q, is not an FQDN or IPv4 / IPv6 address", c.Server)
68 }
69 return nil
70 }
71
72 func (c DNSConf) validateProto() error {
73 validProtos := []string{"udp", "tcp"}
74 proto := strings.Trim(strings.ToLower(c.Proto), " ")
75 for _, i := range validProtos {
76 if proto == i {
77 return nil
78 }
79 }
80 return fmt.Errorf(
81 "invalid `protocol`, got: %q, expected one in: %s", c.Proto, validProtos)
82 }
83
84 func (c DNSConf) validateQType() error {
85 validQTypes = map[string]uint16{"A": 1, "TXT": 16, "AAAA": 28, "CAA": 257}
86 qtype := strings.Trim(strings.ToUpper(c.QType), " ")
87 q := make([]string, 0, len(validQTypes))
88 for i := range validQTypes {
89 q = append(q, i)
90 if qtype == i {
91 return nil
92 }
93 }
94 return fmt.Errorf(
95 "invalid `query_type`, got: %q, expected one in %s", c.QType, q)
96 }
97
98
99
100
101 func (c DNSConf) MakeProber(_ map[string]prometheus.Collector) (probers.Prober, error) {
102
103 if !dns.IsFqdn(dns.Fqdn(c.QName)) {
104 return nil, fmt.Errorf(
105 "invalid `query_name`, %q is not an fqdn", c.QName)
106 }
107
108
109 err := c.validateServer()
110 if err != nil {
111 return nil, err
112 }
113
114
115 err = c.validateProto()
116 if err != nil {
117 return nil, err
118 }
119
120
121 err = c.validateQType()
122 if err != nil {
123 return nil, err
124 }
125
126 return DNSProbe{
127 proto: strings.Trim(strings.ToLower(c.Proto), " "),
128 recurse: c.Recurse,
129 qname: c.QName,
130 server: c.Server,
131 qtype: validQTypes[strings.Trim(strings.ToUpper(c.QType), " ")],
132 }, nil
133 }
134
135
136 func (c DNSConf) Instrument() map[string]prometheus.Collector {
137 return nil
138 }
139
140
141
142 func init() {
143 probers.Register(DNSConf{})
144 }
145
View as plain text