Support using MX records for DNS discovery (#10099)
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>
This commit is contained in:
parent
a7f19b5775
commit
d677ec489e
|
@ -40,6 +40,8 @@ const (
|
||||||
dnsSrvRecordPrefix = model.MetaLabelPrefix + "dns_srv_record_"
|
dnsSrvRecordPrefix = model.MetaLabelPrefix + "dns_srv_record_"
|
||||||
dnsSrvRecordTargetLabel = dnsSrvRecordPrefix + "target"
|
dnsSrvRecordTargetLabel = dnsSrvRecordPrefix + "target"
|
||||||
dnsSrvRecordPortLabel = dnsSrvRecordPrefix + "port"
|
dnsSrvRecordPortLabel = dnsSrvRecordPrefix + "port"
|
||||||
|
dnsMxRecordPrefix = model.MetaLabelPrefix + "dns_mx_record_"
|
||||||
|
dnsMxRecordTargetLabel = dnsMxRecordPrefix + "target"
|
||||||
|
|
||||||
// Constants for instrumentation.
|
// Constants for instrumentation.
|
||||||
namespace = "prometheus"
|
namespace = "prometheus"
|
||||||
|
@ -100,7 +102,7 @@ func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||||
}
|
}
|
||||||
switch strings.ToUpper(c.Type) {
|
switch strings.ToUpper(c.Type) {
|
||||||
case "SRV":
|
case "SRV":
|
||||||
case "A", "AAAA":
|
case "A", "AAAA", "MX":
|
||||||
if c.Port == 0 {
|
if c.Port == 0 {
|
||||||
return errors.New("a port is required in DNS-SD configs for all record types except SRV")
|
return errors.New("a port is required in DNS-SD configs for all record types except SRV")
|
||||||
}
|
}
|
||||||
|
@ -136,6 +138,8 @@ func NewDiscovery(conf SDConfig, logger log.Logger) *Discovery {
|
||||||
qtype = dns.TypeAAAA
|
qtype = dns.TypeAAAA
|
||||||
case "SRV":
|
case "SRV":
|
||||||
qtype = dns.TypeSRV
|
qtype = dns.TypeSRV
|
||||||
|
case "MX":
|
||||||
|
qtype = dns.TypeMX
|
||||||
}
|
}
|
||||||
d := &Discovery{
|
d := &Discovery{
|
||||||
names: conf.Names,
|
names: conf.Names,
|
||||||
|
@ -195,7 +199,7 @@ func (d *Discovery) refreshOne(ctx context.Context, name string, ch chan<- *targ
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, record := range response.Answer {
|
for _, record := range response.Answer {
|
||||||
var target, dnsSrvRecordTarget, dnsSrvRecordPort model.LabelValue
|
var target, dnsSrvRecordTarget, dnsSrvRecordPort, dnsMxRecordTarget model.LabelValue
|
||||||
|
|
||||||
switch addr := record.(type) {
|
switch addr := record.(type) {
|
||||||
case *dns.SRV:
|
case *dns.SRV:
|
||||||
|
@ -206,6 +210,13 @@ func (d *Discovery) refreshOne(ctx context.Context, name string, ch chan<- *targ
|
||||||
addr.Target = strings.TrimRight(addr.Target, ".")
|
addr.Target = strings.TrimRight(addr.Target, ".")
|
||||||
|
|
||||||
target = hostPort(addr.Target, int(addr.Port))
|
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:
|
case *dns.A:
|
||||||
target = hostPort(addr.A.String(), d.port)
|
target = hostPort(addr.A.String(), d.port)
|
||||||
case *dns.AAAA:
|
case *dns.AAAA:
|
||||||
|
@ -222,6 +233,7 @@ func (d *Discovery) refreshOne(ctx context.Context, name string, ch chan<- *targ
|
||||||
dnsNameLabel: model.LabelValue(name),
|
dnsNameLabel: model.LabelValue(name),
|
||||||
dnsSrvRecordTargetLabel: dnsSrvRecordTarget,
|
dnsSrvRecordTargetLabel: dnsSrvRecordTarget,
|
||||||
dnsSrvRecordPortLabel: dnsSrvRecordPort,
|
dnsSrvRecordPortLabel: dnsSrvRecordPort,
|
||||||
|
dnsMxRecordTargetLabel: dnsMxRecordTarget,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -80,6 +80,7 @@ func TestDNS(t *testing.T) {
|
||||||
"__meta_dns_name": "web.example.com.",
|
"__meta_dns_name": "web.example.com.",
|
||||||
"__meta_dns_srv_record_target": "",
|
"__meta_dns_srv_record_target": "",
|
||||||
"__meta_dns_srv_record_port": "",
|
"__meta_dns_srv_record_port": "",
|
||||||
|
"__meta_dns_mx_record_target": "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -110,6 +111,7 @@ func TestDNS(t *testing.T) {
|
||||||
"__meta_dns_name": "web.example.com.",
|
"__meta_dns_name": "web.example.com.",
|
||||||
"__meta_dns_srv_record_target": "",
|
"__meta_dns_srv_record_target": "",
|
||||||
"__meta_dns_srv_record_port": "",
|
"__meta_dns_srv_record_port": "",
|
||||||
|
"__meta_dns_mx_record_target": "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -140,12 +142,14 @@ func TestDNS(t *testing.T) {
|
||||||
"__meta_dns_name": "_mysql._tcp.db.example.com.",
|
"__meta_dns_name": "_mysql._tcp.db.example.com.",
|
||||||
"__meta_dns_srv_record_target": "db1.example.com.",
|
"__meta_dns_srv_record_target": "db1.example.com.",
|
||||||
"__meta_dns_srv_record_port": "3306",
|
"__meta_dns_srv_record_port": "3306",
|
||||||
|
"__meta_dns_mx_record_target": "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"__address__": "db2.example.com:3306",
|
"__address__": "db2.example.com:3306",
|
||||||
"__meta_dns_name": "_mysql._tcp.db.example.com.",
|
"__meta_dns_name": "_mysql._tcp.db.example.com.",
|
||||||
"__meta_dns_srv_record_target": "db2.example.com.",
|
"__meta_dns_srv_record_target": "db2.example.com.",
|
||||||
"__meta_dns_srv_record_port": "3306",
|
"__meta_dns_srv_record_port": "3306",
|
||||||
|
"__meta_dns_mx_record_target": "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -175,6 +179,7 @@ func TestDNS(t *testing.T) {
|
||||||
"__meta_dns_name": "_mysql._tcp.db.example.com.",
|
"__meta_dns_name": "_mysql._tcp.db.example.com.",
|
||||||
"__meta_dns_srv_record_target": "db1.example.com.",
|
"__meta_dns_srv_record_target": "db1.example.com.",
|
||||||
"__meta_dns_srv_record_port": "3306",
|
"__meta_dns_srv_record_port": "3306",
|
||||||
|
"__meta_dns_mx_record_target": "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -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 {
|
for _, tc := range testCases {
|
||||||
|
|
|
@ -961,8 +961,8 @@ 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
|
domain names which are periodically queried to discover a list of targets. The
|
||||||
DNS servers to be contacted are read from `/etc/resolv.conf`.
|
DNS servers to be contacted are read from `/etc/resolv.conf`.
|
||||||
|
|
||||||
This service discovery method only supports basic DNS A, AAAA and SRV record
|
This service discovery method only supports basic DNS A, AAAA, MX and SRV
|
||||||
queries, but not the advanced DNS-SD approach specified in
|
record queries, but not the advanced DNS-SD approach specified in
|
||||||
[RFC6763](https://tools.ietf.org/html/rfc6763).
|
[RFC6763](https://tools.ietf.org/html/rfc6763).
|
||||||
|
|
||||||
The following meta labels are available on targets during [relabeling](#relabel_config):
|
The following meta labels are available on targets during [relabeling](#relabel_config):
|
||||||
|
@ -970,13 +970,14 @@ The following meta labels are available on targets during [relabeling](#relabel_
|
||||||
* `__meta_dns_name`: the record name that produced the discovered target.
|
* `__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_target`: the target field of the SRV record
|
||||||
* `__meta_dns_srv_record_port`: the port 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
|
```yaml
|
||||||
# A list of DNS domain names to be queried.
|
# A list of DNS domain names to be queried.
|
||||||
names:
|
names:
|
||||||
[ - <string> ]
|
[ - <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' ]
|
[ type: <string> | default = 'SRV' ]
|
||||||
|
|
||||||
# The port number used if the query type is not SRV.
|
# The port number used if the query type is not SRV.
|
||||||
|
|
Loading…
Reference in New Issue