Skip to content

Commit 8752655

Browse files
authored
Merge pull request mackerelio#704 from wafuwafu13/dns-check-plugin
Add check-dns plugin
2 parents 11fb390 + ad9935a commit 8752655

File tree

9 files changed

+340
-0
lines changed

9 files changed

+340
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Documentation for each plugin is located in its respective sub directory.
1010
* [check-aws-sqs-queue-size](./check-aws-sqs-queue-size/README.md)
1111
* [check-cert-file](./check-cert-file/README.md)
1212
* [check-disk](./check-disk/README.md)
13+
* [check-dns](./check-dns/README.md)
1314
* [check-elasticsearch](./check-elasticsearch/README.md)
1415
* [check-file-age](./check-file-age/README.md)
1516
* [check-file-size](./check-file-size/README.md)

check-dns/README.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# check-dns
2+
3+
## Description
4+
5+
Monitor DNS response.
6+
7+
## Synopsis
8+
```
9+
check-dns -H example.com -s 8.8.8.8
10+
```
11+
12+
## Installation
13+
14+
First, build this program.
15+
16+
```
17+
go get github.com/mackerelio/go-check-plugins
18+
cd $(go env GOPATH)/src/github.com/mackerelio/go-check-plugins/check-dns
19+
go install
20+
```
21+
22+
Or you can use this program by installing the official Mackerel package. See [Using the official check plugin pack for check monitoring - Mackerel Docs](https://mackerel.io/docs/entry/howto/mackerel-check-plugins).
23+
24+
25+
Next, you can execute this program :-)
26+
27+
```
28+
check-dns -H example.com -s 8.8.8.8
29+
```
30+
31+
32+
## Setting for mackerel-agent
33+
34+
If there are no problems in the execution result, add a setting in mackerel-agent.conf .
35+
36+
```
37+
[plugin.checks.fileage-sample]
38+
command = ["check-dns", "-H", "example.com", "-s", "8.8.8.8"]
39+
```
40+
41+
## Usage
42+
### Options
43+
44+
```
45+
-H, --host= The name or address you want to query
46+
-s, --server= DNS server you want to use for the lookup
47+
-p, --port= Port number you want to use (default: 53)
48+
-q, --querytype= DNS record query type where TYPE =(A, AAAA, SRV, TXT, MX, ANY) (default: A)
49+
-c, --queryclass= DNS record class type where TYPE =(IN, CS, CH, HS, NONE, ANY) (default: IN)
50+
--norec Set not recursive mode
51+
```
52+
53+
## For more information
54+
55+
Please execute `check-dns -h` and you can get command line options.

check-dns/lib/check_dns.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package checkdns
2+
3+
import (
4+
"fmt"
5+
"net"
6+
"os"
7+
"strconv"
8+
"strings"
9+
10+
"github.com/jessevdk/go-flags"
11+
"github.com/mackerelio/checkers"
12+
"github.com/miekg/dns"
13+
)
14+
15+
type dnsOpts struct {
16+
Host string `short:"H" long:"host" required:"true" description:"The name or address you want to query"`
17+
Server string `short:"s" long:"server" description:"DNS server you want to use for the lookup"`
18+
Port int `short:"p" long:"port" default:"53" description:"Port number you want to use"`
19+
QueryType string `short:"q" long:"querytype" default:"A" description:"DNS record query type where TYPE =(A, AAAA, SRV, TXT, MX, ANY)"`
20+
QueryClass string `short:"c" long:"queryclass" default:"IN" description:"DNS record class type where TYPE =(IN, CS, CH, HS, NONE, ANY)"`
21+
Norec bool `long:"norec" description:"Set not recursive mode"`
22+
}
23+
24+
// Do the plugin
25+
func Do() {
26+
opts, err := parseArgs(os.Args[1:])
27+
if err != nil {
28+
os.Exit(1)
29+
}
30+
ckr := opts.run()
31+
ckr.Name = "DNS"
32+
ckr.Exit()
33+
}
34+
35+
func parseArgs(args []string) (*dnsOpts, error) {
36+
opts := &dnsOpts{}
37+
_, err := flags.ParseArgs(opts, args)
38+
return opts, err
39+
}
40+
41+
func (opts *dnsOpts) run() *checkers.Checker {
42+
var nameserver string
43+
var err error
44+
if opts.Server != "" {
45+
nameserver = opts.Server
46+
} else {
47+
nameserver, err = adapterAddress()
48+
if err != nil {
49+
return checkers.Critical(err.Error())
50+
}
51+
}
52+
nameserver = net.JoinHostPort(nameserver, strconv.Itoa(opts.Port))
53+
54+
queryType, ok := dns.StringToType[strings.ToUpper(opts.QueryType)]
55+
if !ok {
56+
return checkers.Critical(fmt.Sprintf("%s is invalid queryType", opts.QueryType))
57+
}
58+
queryClass, ok := dns.StringToClass[strings.ToUpper(opts.QueryClass)]
59+
if !ok {
60+
return checkers.Critical(fmt.Sprintf("%s is invalid queryClass", opts.QueryClass))
61+
}
62+
63+
c := new(dns.Client)
64+
m := &dns.Msg{
65+
MsgHdr: dns.MsgHdr{
66+
RecursionDesired: !opts.Norec,
67+
Opcode: dns.OpcodeQuery,
68+
},
69+
Question: []dns.Question{{Name: dns.Fqdn(opts.Host), Qtype: queryType, Qclass: uint16(queryClass)}},
70+
}
71+
m.Id = dns.Id()
72+
73+
r, _, err := c.Exchange(m, nameserver)
74+
if err != nil {
75+
return checkers.Critical(err.Error())
76+
}
77+
78+
checkSt := checkers.OK
79+
if r.MsgHdr.Rcode != dns.RcodeSuccess {
80+
checkSt = checkers.CRITICAL
81+
}
82+
msg := fmt.Sprintf("HEADER-> %s\n", r.MsgHdr.String())
83+
for _, answer := range r.Answer {
84+
msg += fmt.Sprintf("ANSWER-> %s\n", answer)
85+
}
86+
87+
return checkers.NewChecker(checkSt, msg)
88+
}

check-dns/lib/check_dns_test.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package checkdns
2+
3+
import (
4+
"net"
5+
"os"
6+
"strings"
7+
"testing"
8+
9+
"github.com/mackerelio/checkers"
10+
"github.com/stretchr/testify/assert"
11+
)
12+
13+
func TestNameServer(t *testing.T) {
14+
nameserver, err := adapterAddress()
15+
if err != nil {
16+
t.Errorf(err.Error())
17+
}
18+
t.Logf(nameserver)
19+
address := net.ParseIP(nameserver)
20+
if address == nil {
21+
t.Errorf("nameserver is invalid IP")
22+
}
23+
}
24+
25+
func TestCheckDns(t *testing.T) {
26+
tests := []struct {
27+
args []string
28+
want_status checkers.Status
29+
want_msg []string
30+
}{
31+
{
32+
[]string{"-H", "example.com"},
33+
checkers.OK,
34+
[]string{"status: NOERROR"},
35+
},
36+
{
37+
[]string{"-H", "example.com", "--norec"},
38+
checkers.OK,
39+
[]string{"status: NOERROR"},
40+
},
41+
{
42+
[]string{"-H", "exampleeeee.com"},
43+
checkers.CRITICAL,
44+
[]string{"status: NXDOMAIN"},
45+
},
46+
{
47+
[]string{"-H", "example.com", "-s", "8.8.8.8"},
48+
checkers.OK,
49+
[]string{"status: NOERROR"},
50+
},
51+
{
52+
[]string{"-H", "exampleeeee.com", "-s", "8.8.8.8"},
53+
checkers.CRITICAL,
54+
[]string{"status: NXDOMAIN"},
55+
},
56+
{
57+
[]string{"-H", "example.com", "-s", "8.8.8"},
58+
checkers.CRITICAL,
59+
[]string{""},
60+
},
61+
{
62+
[]string{"-H", "example.com", "-s", "8.8.8.8", "-q", "AAAA"},
63+
checkers.OK,
64+
[]string{"status: NOERROR", "AAAA"},
65+
},
66+
{
67+
[]string{"-H", "example.com", "-s", "8.8.8.8", "-q", "AAA"},
68+
checkers.CRITICAL,
69+
[]string{"AAA is invalid queryType"},
70+
},
71+
{
72+
[]string{"-H", "example.com", "-s", "8.8.8.8", "-c", "IN"},
73+
checkers.OK,
74+
[]string{"status: NOERROR"},
75+
},
76+
{
77+
[]string{"-H", "example.com", "-s", "8.8.8.8", "-c", "INN"},
78+
checkers.CRITICAL,
79+
[]string{"INN is invalid queryClass"},
80+
},
81+
}
82+
83+
for i, tt := range tests {
84+
t.Logf("=== Start #%d", i)
85+
opts, err := parseArgs(tt.args)
86+
if err != nil {
87+
t.Fatal(err)
88+
}
89+
// when runs without setting server in CI, status will be REFUSED
90+
if opts.Server == "" && os.Getenv("CI") == "true" {
91+
continue
92+
}
93+
ckr := opts.run()
94+
95+
assert.Equal(t, tt.want_status, ckr.Status)
96+
97+
for _, want := range tt.want_msg {
98+
if !strings.Contains(ckr.Message, want) {
99+
t.Errorf("%s is not incleded in message", want)
100+
}
101+
}
102+
}
103+
}

check-dns/lib/nameserver.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//go:build !windows
2+
3+
package checkdns
4+
5+
import (
6+
"fmt"
7+
"github.com/miekg/dns"
8+
"net"
9+
)
10+
11+
func adapterAddress() (string, error) {
12+
conf, err := dns.ClientConfigFromFile("/etc/resolv.conf")
13+
if err != nil {
14+
return "", err
15+
}
16+
nameserver := conf.Servers[0]
17+
// ref: https://github.com/miekg/exdns/blob/d851fa434ad51cb84500b3e18b8aa7d3bead2c51/q/q.go#L148-L153
18+
// if the nameserver is from /etc/resolv.conf the [ and ] are already
19+
// added, thereby breaking net.ParseIP. Check for this and don't
20+
// fully qualify such a name
21+
if nameserver[0] == '[' && nameserver[len(nameserver)-1] == ']' {
22+
nameserver = nameserver[1 : len(nameserver)-1]
23+
}
24+
// ref: https://github.com/miekg/exdns/blob/d851fa434ad51cb84500b3e18b8aa7d3bead2c51/q/q.go#L154-L158
25+
if net.ParseIP(nameserver) == nil {
26+
nameserver = dns.Fqdn(nameserver)
27+
}
28+
if net.ParseIP(nameserver) == nil {
29+
return "", fmt.Errorf("invalid nameserver: %s", nameserver)
30+
}
31+
return nameserver, nil
32+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//go:build windows
2+
3+
package checkdns
4+
5+
import (
6+
"fmt"
7+
"github.com/miekg/dns"
8+
"golang.org/x/sys/windows"
9+
"net"
10+
"os"
11+
"syscall"
12+
"unsafe"
13+
)
14+
15+
// ref: https://go.dev/src/net/interface_windows.go
16+
func adapterAddress() (string, error) {
17+
var b []byte
18+
l := uint32(15000) // recommended initial size
19+
for {
20+
b = make([]byte, l)
21+
err := windows.GetAdaptersAddresses(syscall.AF_UNSPEC, windows.GAA_FLAG_INCLUDE_PREFIX, 0, (*windows.IpAdapterAddresses)(unsafe.Pointer(&b[0])), &l)
22+
if err == nil {
23+
if l == 0 {
24+
return "", nil
25+
}
26+
break
27+
}
28+
if err.(syscall.Errno) != syscall.ERROR_BUFFER_OVERFLOW {
29+
return "", os.NewSyscallError("getadaptersaddresses", err)
30+
}
31+
if l <= uint32(len(b)) {
32+
return "", os.NewSyscallError("getadaptersaddresses", err)
33+
}
34+
}
35+
var aas []*windows.IpAdapterAddresses
36+
for aa := (*windows.IpAdapterAddresses)(unsafe.Pointer(&b[0])); aa != nil; aa = aa.Next {
37+
aas = append(aas, aa)
38+
}
39+
nameserver := aas[0].FirstDnsServerAddress.Address.IP().String()
40+
// ref: https://github.com/miekg/exdns/blob/d851fa434ad51cb84500b3e18b8aa7d3bead2c51/q/q.go#L154-L158
41+
if net.ParseIP(nameserver) == nil {
42+
nameserver = dns.Fqdn(nameserver)
43+
}
44+
if net.ParseIP(nameserver) == nil {
45+
return "", fmt.Errorf("invalid nameserver: %s", nameserver)
46+
}
47+
return nameserver, nil
48+
}

check-dns/main.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package main
2+
3+
import "github.com/mackerelio/go-check-plugins/check-dns/lib"
4+
5+
func main() {
6+
checkdns.Do()
7+
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ require (
4646
github.com/gogo/protobuf v1.3.2 // indirect
4747
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
4848
github.com/jmespath/go-jmespath v0.4.0 // indirect
49+
github.com/miekg/dns v1.1.50 // indirect
4950
github.com/moby/sys/mount v0.3.3 // indirect
5051
github.com/moby/sys/mountinfo v0.6.2 // indirect
5152
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect

go.sum

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,8 @@ github.com/mattn/go-zglob v0.0.4/go.mod h1:MxxjyoXXnMxfIpxTK2GAkw1w8glPsQILx3N5w
506506
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
507507
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
508508
github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY=
509+
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
510+
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
509511
github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
510512
github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4=
511513
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
@@ -829,6 +831,7 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY
829831
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
830832
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
831833
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
834+
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
832835
golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
833836
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
834837
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
@@ -922,6 +925,7 @@ golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7w
922925
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
923926
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
924927
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
928+
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
925929
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
926930
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
927931
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -998,6 +1002,7 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY
9981002
golang.org/x/tools v0.0.0-20200916195026-c9a70fc28ce3/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
9991003
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
10001004
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
1005+
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
10011006
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
10021007
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
10031008
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

0 commit comments

Comments
 (0)