Skip to content

Commit

Permalink
Support using MX records for DNS discovery (prometheus#10099)
Browse files Browse the repository at this point in the history
It's currently possible to use blackbox_exporter to probe MX records
themselves. However it's not possible to do an end-to-end test, like is
possible with SRV records. This makes it possible to use MX records as a
source of hostnames in the same way as SRV records.

Signed-off-by: David Leadbeater <dgl@dgl.cx>
  • Loading branch information
dgl authored and B-Marine committed Aug 25, 2022
1 parent b124c49 commit 225eab7
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 5 deletions.
16 changes: 14 additions & 2 deletions discovery/dns/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ const (
dnsSrvRecordPrefix = model.MetaLabelPrefix + "dns_srv_record_"
dnsSrvRecordTargetLabel = dnsSrvRecordPrefix + "target"
dnsSrvRecordPortLabel = dnsSrvRecordPrefix + "port"
dnsMxRecordPrefix = model.MetaLabelPrefix + "dns_mx_record_"
dnsMxRecordTargetLabel = dnsMxRecordPrefix + "target"

// Constants for instrumentation.
namespace = "prometheus"
Expand Down Expand Up @@ -100,7 +102,7 @@ func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
}
switch strings.ToUpper(c.Type) {
case "SRV":
case "A", "AAAA":
case "A", "AAAA", "MX":
if c.Port == 0 {
return errors.New("a port is required in DNS-SD configs for all record types except SRV")
}
Expand Down Expand Up @@ -136,6 +138,8 @@ func NewDiscovery(conf SDConfig, logger log.Logger) *Discovery {
qtype = dns.TypeAAAA
case "SRV":
qtype = dns.TypeSRV
case "MX":
qtype = dns.TypeMX
}
d := &Discovery{
names: conf.Names,
Expand Down Expand Up @@ -195,7 +199,7 @@ func (d *Discovery) refreshOne(ctx context.Context, name string, ch chan<- *targ
}

for _, record := range response.Answer {
var target, dnsSrvRecordTarget, dnsSrvRecordPort model.LabelValue
var target, dnsSrvRecordTarget, dnsSrvRecordPort, dnsMxRecordTarget model.LabelValue

switch addr := record.(type) {
case *dns.SRV:
Expand All @@ -206,6 +210,13 @@ func (d *Discovery) refreshOne(ctx context.Context, name string, ch chan<- *targ
addr.Target = strings.TrimRight(addr.Target, ".")

target = hostPort(addr.Target, int(addr.Port))
case *dns.MX:
dnsMxRecordTarget = model.LabelValue(addr.Mx)

// Remove the final dot from rooted DNS names to make them look more usual.
addr.Mx = strings.TrimRight(addr.Mx, ".")

target = hostPort(addr.Mx, d.port)
case *dns.A:
target = hostPort(addr.A.String(), d.port)
case *dns.AAAA:
Expand All @@ -222,6 +233,7 @@ func (d *Discovery) refreshOne(ctx context.Context, name string, ch chan<- *targ
dnsNameLabel: model.LabelValue(name),
dnsSrvRecordTargetLabel: dnsSrvRecordTarget,
dnsSrvRecordPortLabel: dnsSrvRecordPort,
dnsMxRecordTargetLabel: dnsMxRecordTarget,
})
}

Expand Down
44 changes: 44 additions & 0 deletions discovery/dns/dns_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ func TestDNS(t *testing.T) {
"__meta_dns_name": "web.example.com.",
"__meta_dns_srv_record_target": "",
"__meta_dns_srv_record_port": "",
"__meta_dns_mx_record_target": "",
},
},
},
Expand Down Expand Up @@ -110,6 +111,7 @@ func TestDNS(t *testing.T) {
"__meta_dns_name": "web.example.com.",
"__meta_dns_srv_record_target": "",
"__meta_dns_srv_record_port": "",
"__meta_dns_mx_record_target": "",
},
},
},
Expand Down Expand Up @@ -140,12 +142,14 @@ func TestDNS(t *testing.T) {
"__meta_dns_name": "_mysql._tcp.db.example.com.",
"__meta_dns_srv_record_target": "db1.example.com.",
"__meta_dns_srv_record_port": "3306",
"__meta_dns_mx_record_target": "",
},
{
"__address__": "db2.example.com:3306",
"__meta_dns_name": "_mysql._tcp.db.example.com.",
"__meta_dns_srv_record_target": "db2.example.com.",
"__meta_dns_srv_record_port": "3306",
"__meta_dns_mx_record_target": "",
},
},
},
Expand Down Expand Up @@ -175,6 +179,7 @@ func TestDNS(t *testing.T) {
"__meta_dns_name": "_mysql._tcp.db.example.com.",
"__meta_dns_srv_record_target": "db1.example.com.",
"__meta_dns_srv_record_port": "3306",
"__meta_dns_mx_record_target": "",
},
},
},
Expand All @@ -195,6 +200,45 @@ func TestDNS(t *testing.T) {
},
},
},
{
name: "MX record query",
config: SDConfig{
Names: []string{"example.com."},
Type: "MX",
Port: 25,
RefreshInterval: model.Duration(time.Minute),
},
lookup: func(name string, qtype uint16, logger log.Logger) (*dns.Msg, error) {
return &dns.Msg{
Answer: []dns.RR{
&dns.MX{Preference: 0, Mx: "smtp1.example.com."},
&dns.MX{Preference: 10, Mx: "smtp2.example.com."},
},
},
nil
},
expected: []*targetgroup.Group{
{
Source: "example.com.",
Targets: []model.LabelSet{
{
"__address__": "smtp1.example.com:25",
"__meta_dns_name": "example.com.",
"__meta_dns_srv_record_target": "",
"__meta_dns_srv_record_port": "",
"__meta_dns_mx_record_target": "smtp1.example.com.",
},
{
"__address__": "smtp2.example.com:25",
"__meta_dns_name": "example.com.",
"__meta_dns_srv_record_target": "",
"__meta_dns_srv_record_port": "",
"__meta_dns_mx_record_target": "smtp2.example.com.",
},
},
},
},
},
}

for _, tc := range testCases {
Expand Down
7 changes: 4 additions & 3 deletions docs/configuration/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -965,22 +965,23 @@ A DNS-based service discovery configuration allows specifying a set of DNS
domain names which are periodically queried to discover a list of targets. The
DNS servers to be contacted are read from `/etc/resolv.conf`.

This service discovery method only supports basic DNS A, AAAA and SRV record
queries, but not the advanced DNS-SD approach specified in
This service discovery method only supports basic DNS A, AAAA, MX and SRV
record queries, but not the advanced DNS-SD approach specified in
[RFC6763](https://tools.ietf.org/html/rfc6763).

The following meta labels are available on targets during [relabeling](#relabel_config):

* `__meta_dns_name`: the record name that produced the discovered target.
* `__meta_dns_srv_record_target`: the target field of the SRV record
* `__meta_dns_srv_record_port`: the port field of the SRV record
* `__meta_dns_mx_record_target`: the target field of the MX record

```yaml
# A list of DNS domain names to be queried.
names:
[ - <string> ]

# The type of DNS query to perform. One of SRV, A, or AAAA.
# The type of DNS query to perform. One of SRV, A, AAAA or MX.
[ type: <string> | default = 'SRV' ]

# The port number used if the query type is not SRV.
Expand Down

0 comments on commit 225eab7

Please sign in to comment.