mirror of
https://github.com/Luzifer/dns_check.git
synced 2024-12-22 10:51:18 +00:00
Initital version
This commit is contained in:
commit
bb35ac980a
8 changed files with 629 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
dns_check
|
4
.gobuilder.yml
Normal file
4
.gobuilder.yml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
build_matrix:
|
||||||
|
general:
|
||||||
|
ldflags:
|
||||||
|
- "-X main.version $(git describe --tags)"
|
8
Makefile
Normal file
8
Makefile
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
default:
|
||||||
|
|
||||||
|
compile:
|
||||||
|
go generate
|
||||||
|
go build -ldflags "-X main.version=$(shell git describe --tags || git rev-parse --short HEAD)" .
|
||||||
|
|
||||||
|
bindata:
|
||||||
|
go-bindata nameservers.yaml
|
60
README.md
Normal file
60
README.md
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
# Luzifer / dns\_check
|
||||||
|
|
||||||
|
dns\_check is a small utility to check major DNS services for records of a FQDN without having to query them one-by-one.
|
||||||
|
|
||||||
|
Use cases:
|
||||||
|
|
||||||
|
- Check whether the IP of your domain is consistent on all services
|
||||||
|
- Check whether your DNS change is already live for users of those services
|
||||||
|
- Have an automated check which tells you when something is not right
|
||||||
|
|
||||||
|
You can see the current table of nameservers oncluded on the build by browsing the [nameservers.yaml](nameservers.yaml) file.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# ./dns_check --help
|
||||||
|
Usage of ./dns_check:
|
||||||
|
-a, --assert=[]: Exit with exit code 2 when these DNS entries were not found
|
||||||
|
--assert-threshold=100: If used with -a fail when not at least N percent of the nameservers had the expected result
|
||||||
|
-f, --full-scan[=false]: Scan all nameservers included in this build
|
||||||
|
-q, --quiet[=false]: Do not communicate by text, use only exit codes
|
||||||
|
-s, --short[=true]: Use short notation (only when using assertion)
|
||||||
|
--version[=false]: Print version and exit
|
||||||
|
```
|
||||||
|
|
||||||
|
Use case: I know the IP of my domain and want to check whether all services report that IP
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# ./dns_check -a "188.40.126.69" A luzifer.io
|
||||||
|
[Level3] (209.244.0.3:53) ✓
|
||||||
|
[Level3] (209.244.0.4:53) ✓
|
||||||
|
[Verisign] (64.6.64.6:53) ✓
|
||||||
|
[Verisign] (64.6.65.6:53) ✓
|
||||||
|
[Google] (8.8.8.8:53) ✓
|
||||||
|
[Google] (8.8.4.4:53) ✓
|
||||||
|
[OpenDNS Home] (208.67.222.222:53) ✓
|
||||||
|
[OpenDNS Home] (208.67.220.220:53) ✓
|
||||||
|
```
|
||||||
|
|
||||||
|
Use case: Just tell me the IP of any domain
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# ./dns_check A luzifer.io
|
||||||
|
[Google] (8.8.8.8:53)
|
||||||
|
- 188.40.126.69
|
||||||
|
[Google] (8.8.4.4:53)
|
||||||
|
- 188.40.126.69
|
||||||
|
[Level3] (209.244.0.3:53)
|
||||||
|
- 188.40.126.69
|
||||||
|
[Level3] (209.244.0.4:53)
|
||||||
|
- 188.40.126.69
|
||||||
|
[Verisign] (64.6.64.6:53)
|
||||||
|
- 188.40.126.69
|
||||||
|
[Verisign] (64.6.65.6:53)
|
||||||
|
- 188.40.126.69
|
||||||
|
[OpenDNS Home] (208.67.222.222:53)
|
||||||
|
- 188.40.126.69
|
||||||
|
[OpenDNS Home] (208.67.220.220:53)
|
||||||
|
- 188.40.126.69
|
||||||
|
```
|
235
bindata.go
Normal file
235
bindata.go
Normal file
|
@ -0,0 +1,235 @@
|
||||||
|
// Code generated by go-bindata.
|
||||||
|
// sources:
|
||||||
|
// nameservers.yaml
|
||||||
|
// DO NOT EDIT!
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func bindataRead(data []byte, name string) ([]byte, error) {
|
||||||
|
gz, err := gzip.NewReader(bytes.NewBuffer(data))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Read %q: %v", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
_, err = io.Copy(&buf, gz)
|
||||||
|
clErr := gz.Close()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Read %q: %v", name, err)
|
||||||
|
}
|
||||||
|
if clErr != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type asset struct {
|
||||||
|
bytes []byte
|
||||||
|
info os.FileInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
type bindataFileInfo struct {
|
||||||
|
name string
|
||||||
|
size int64
|
||||||
|
mode os.FileMode
|
||||||
|
modTime time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fi bindataFileInfo) Name() string {
|
||||||
|
return fi.name
|
||||||
|
}
|
||||||
|
func (fi bindataFileInfo) Size() int64 {
|
||||||
|
return fi.size
|
||||||
|
}
|
||||||
|
func (fi bindataFileInfo) Mode() os.FileMode {
|
||||||
|
return fi.mode
|
||||||
|
}
|
||||||
|
func (fi bindataFileInfo) ModTime() time.Time {
|
||||||
|
return fi.modTime
|
||||||
|
}
|
||||||
|
func (fi bindataFileInfo) IsDir() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
func (fi bindataFileInfo) Sys() interface{} {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _nameserversYaml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x64\x53\x4b\x6f\x9b\x40\x10\xbe\xfb\x57\x20\xf5\xcc\xc0\x3e\x78\xe5\x66\xb9\x6d\x52\x29\x4a\x0f\xb6\x52\xf5\x14\x11\x18\x3b\x28\xb0\x8b\x96\xc5\x6a\xff\x7d\x67\xcd\x6b\xad\x2a\xb3\x04\x3e\xcf\xeb\xfb\x66\x36\x0c\xc3\x5d\xa5\x0d\xbe\xf5\x46\x5f\x9b\x1a\xcd\xf0\xb0\x0b\x82\x30\x78\xc6\x2b\xb6\xe2\xf6\xfa\x8a\xa6\x19\x9a\x8b\xba\x7d\x3c\x6a\x7d\x69\xf1\xf6\xfa\xb3\x47\xf5\xf5\xe5\x18\x3c\xe9\x0e\x77\xfd\xf8\xde\x36\xd5\x9b\x2a\x3b\x1c\xd0\x5c\xe7\x3c\x5f\x82\xe7\x66\xb0\x41\xa5\xbb\xbe\x69\xb1\x0e\xce\x46\x77\xc1\x87\xb5\xfd\x43\x14\xf5\xd5\x30\xf6\xbd\x36\x16\xca\x77\x3d\x5a\x20\xa7\x48\xd7\x91\x6d\xfa\xc1\x9a\xa6\xfa\x1c\xa2\x32\x3a\x1b\xc4\x70\x4a\x1d\xd6\x6a\x08\xe7\xd4\xf0\x61\x3b\xca\x3e\xf5\xe8\xea\xb8\x76\x78\x5c\x00\x97\x12\x62\x10\x0f\x89\xf8\x0f\x94\x13\xb8\x90\x59\xa2\x52\x09\x29\xb8\xc7\x16\x33\x41\xc9\x02\x4d\x8c\x17\xff\x1c\x6e\x7f\x9b\xb7\xfb\x94\x4b\x76\x92\x03\x7e\xed\x4f\x87\xa7\xd5\x5d\x02\x8f\x63\x48\x0b\xc8\x63\x2f\x66\x42\x33\xea\x6a\x46\x0f\xba\xd3\xb5\x0e\x8e\x58\x8d\x06\x5d\x9a\xad\x1e\x4f\x21\x49\xe9\xe9\x97\xe4\x31\xb1\xca\xe8\xdf\x04\xfa\x93\xd8\xe4\xc8\x21\x25\x17\xce\xdd\xf1\x15\x99\xf1\xd8\x9d\xb5\xed\x60\x5f\x5f\x4b\x65\xcb\xcb\x9a\x80\x51\x55\x96\x48\xd7\x25\xdb\xc2\x57\x94\x2d\xe8\x0b\x8d\x50\x2b\x62\xa0\x14\x56\xf6\x58\x9e\xb7\x0c\x05\xd1\x4e\x80\x11\x03\xe6\xb1\x5f\xe1\x6c\x85\x1f\x69\xce\xea\x84\x65\xe7\x33\x67\xc0\x59\x0e\x8c\x15\x74\xee\x27\x9a\x13\x5c\xd0\x11\xf3\xa4\x5d\x51\x2f\x92\x15\x09\xc8\x14\x44\x41\xe6\x97\x5d\x50\xe9\xc9\xf6\xf2\xe3\xb0\x84\x25\x44\x94\xa5\xf4\x2b\x09\xe3\xa9\x3d\xc3\x5c\x50\x3b\x73\x1f\xc7\xae\x34\xf6\xb5\xe9\xd1\xf8\x6a\x67\x34\xa8\x98\xec\x5e\x6b\x87\x32\xb2\x59\xe9\xbf\xeb\xe6\x71\x4a\xca\x5c\x43\x09\x99\x17\xb3\xc0\xce\x26\xf8\x3b\xa9\xe3\xd1\x13\x34\x3d\x0a\xa2\x01\x64\x72\x8b\xf3\xd0\x6c\x42\xf7\xad\x45\xa3\x4a\x7b\xb7\x4f\x37\xdd\x62\x92\x56\x72\xc8\xfc\xb5\x20\x7a\x89\x00\x96\x0a\x48\x66\x55\x7f\x97\xaa\xc6\x3f\xe0\x05\x67\x99\x93\xfe\x6e\xfd\x17\x68\xe6\x57\xa1\x1a\x46\x73\x36\x0d\xdd\x56\xa8\x3f\xd7\x59\xd2\x2d\x14\x02\xa4\xa0\xbd\xd9\x62\x0b\x6a\x83\xc6\xc1\xe8\x2a\xd0\x99\xf0\xa7\xd1\xd0\xe5\x2f\x15\x06\xdf\x5a\x5a\x27\x7a\x5f\x8b\x4b\xc8\x39\x48\x67\x93\x6b\x3f\x2a\x7b\xd8\x9f\x56\x66\xb4\x18\xee\x9e\xcd\x5a\xef\xfe\x05\x00\x00\xff\xff\xa7\x60\x1b\x39\xda\x04\x00\x00")
|
||||||
|
|
||||||
|
func nameserversYamlBytes() ([]byte, error) {
|
||||||
|
return bindataRead(
|
||||||
|
_nameserversYaml,
|
||||||
|
"nameservers.yaml",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func nameserversYaml() (*asset, error) {
|
||||||
|
bytes, err := nameserversYamlBytes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
info := bindataFileInfo{name: "nameservers.yaml", size: 1242, mode: os.FileMode(420), modTime: time.Unix(1450103270, 0)}
|
||||||
|
a := &asset{bytes: bytes, info: info}
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Asset loads and returns the asset for the given name.
|
||||||
|
// It returns an error if the asset could not be found or
|
||||||
|
// could not be loaded.
|
||||||
|
func Asset(name string) ([]byte, error) {
|
||||||
|
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||||
|
if f, ok := _bindata[cannonicalName]; ok {
|
||||||
|
a, err := f()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
|
||||||
|
}
|
||||||
|
return a.bytes, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("Asset %s not found", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustAsset is like Asset but panics when Asset would return an error.
|
||||||
|
// It simplifies safe initialization of global variables.
|
||||||
|
func MustAsset(name string) []byte {
|
||||||
|
a, err := Asset(name)
|
||||||
|
if err != nil {
|
||||||
|
panic("asset: Asset(" + name + "): " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssetInfo loads and returns the asset info for the given name.
|
||||||
|
// It returns an error if the asset could not be found or
|
||||||
|
// could not be loaded.
|
||||||
|
func AssetInfo(name string) (os.FileInfo, error) {
|
||||||
|
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||||
|
if f, ok := _bindata[cannonicalName]; ok {
|
||||||
|
a, err := f()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
|
||||||
|
}
|
||||||
|
return a.info, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("AssetInfo %s not found", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssetNames returns the names of the assets.
|
||||||
|
func AssetNames() []string {
|
||||||
|
names := make([]string, 0, len(_bindata))
|
||||||
|
for name := range _bindata {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
|
||||||
|
// _bindata is a table, holding each asset generator, mapped to its name.
|
||||||
|
var _bindata = map[string]func() (*asset, error){
|
||||||
|
"nameservers.yaml": nameserversYaml,
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssetDir returns the file names below a certain
|
||||||
|
// directory embedded in the file by go-bindata.
|
||||||
|
// For example if you run go-bindata on data/... and data contains the
|
||||||
|
// following hierarchy:
|
||||||
|
// data/
|
||||||
|
// foo.txt
|
||||||
|
// img/
|
||||||
|
// a.png
|
||||||
|
// b.png
|
||||||
|
// then AssetDir("data") would return []string{"foo.txt", "img"}
|
||||||
|
// AssetDir("data/img") would return []string{"a.png", "b.png"}
|
||||||
|
// AssetDir("foo.txt") and AssetDir("notexist") would return an error
|
||||||
|
// AssetDir("") will return []string{"data"}.
|
||||||
|
func AssetDir(name string) ([]string, error) {
|
||||||
|
node := _bintree
|
||||||
|
if len(name) != 0 {
|
||||||
|
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||||
|
pathList := strings.Split(cannonicalName, "/")
|
||||||
|
for _, p := range pathList {
|
||||||
|
node = node.Children[p]
|
||||||
|
if node == nil {
|
||||||
|
return nil, fmt.Errorf("Asset %s not found", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if node.Func != nil {
|
||||||
|
return nil, fmt.Errorf("Asset %s not found", name)
|
||||||
|
}
|
||||||
|
rv := make([]string, 0, len(node.Children))
|
||||||
|
for childName := range node.Children {
|
||||||
|
rv = append(rv, childName)
|
||||||
|
}
|
||||||
|
return rv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type bintree struct {
|
||||||
|
Func func() (*asset, error)
|
||||||
|
Children map[string]*bintree
|
||||||
|
}
|
||||||
|
var _bintree = &bintree{nil, map[string]*bintree{
|
||||||
|
"nameservers.yaml": &bintree{nameserversYaml, map[string]*bintree{}},
|
||||||
|
}}
|
||||||
|
|
||||||
|
// RestoreAsset restores an asset under the given directory
|
||||||
|
func RestoreAsset(dir, name string) error {
|
||||||
|
data, err := Asset(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
info, err := AssetInfo(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RestoreAssets restores an asset under the given directory recursively
|
||||||
|
func RestoreAssets(dir, name string) error {
|
||||||
|
children, err := AssetDir(name)
|
||||||
|
// File
|
||||||
|
if err != nil {
|
||||||
|
return RestoreAsset(dir, name)
|
||||||
|
}
|
||||||
|
// Dir
|
||||||
|
for _, child := range children {
|
||||||
|
err = RestoreAssets(dir, filepath.Join(name, child))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func _filePath(dir, name string) string {
|
||||||
|
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||||
|
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
|
||||||
|
}
|
||||||
|
|
75
dns.go
Normal file
75
dns.go
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getDNSQueryResponse(queryType, fqdn, dnsServer string) ([]string, error) {
|
||||||
|
qt, ok := dns.StringToType[queryType]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("Query type '%s' is an unknown type.", queryType)
|
||||||
|
}
|
||||||
|
|
||||||
|
m := new(dns.Msg)
|
||||||
|
m.SetQuestion(dns.Fqdn(fqdn), qt)
|
||||||
|
|
||||||
|
in, err := dns.Exchange(m, dnsServer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
responses := []string{}
|
||||||
|
|
||||||
|
switch dns.RcodeToString[in.Rcode] {
|
||||||
|
// TODO: Catch more error codes (https://github.com/miekg/dns/blob/master/msg.go#L127)
|
||||||
|
case "NXDOMAIN":
|
||||||
|
return nil, fmt.Errorf("Domain was not found. (NXDOMAIN)")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, a := range in.Answer {
|
||||||
|
r, err := formatDNSAnswer(a)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, rp := range r {
|
||||||
|
responses = append(responses, rp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(responses)
|
||||||
|
|
||||||
|
return responses, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatDNSAnswer(a interface{}) (r []string, err error) {
|
||||||
|
switch a.(type) {
|
||||||
|
case *dns.A:
|
||||||
|
r = []string{a.(*dns.A).A.String()}
|
||||||
|
case *dns.AAAA:
|
||||||
|
r = []string{a.(*dns.AAAA).AAAA.String()}
|
||||||
|
case *dns.CNAME:
|
||||||
|
r = []string{a.(*dns.CNAME).Target}
|
||||||
|
case *dns.MX:
|
||||||
|
r = []string{fmt.Sprintf("%d %s", a.(*dns.MX).Preference, a.(*dns.MX).Mx)}
|
||||||
|
case *dns.NS:
|
||||||
|
r = []string{a.(*dns.NS).Ns}
|
||||||
|
case *dns.PTR:
|
||||||
|
r = []string{a.(*dns.PTR).Ptr}
|
||||||
|
case *dns.TXT:
|
||||||
|
r = a.(*dns.TXT).Txt
|
||||||
|
case *dns.SRV:
|
||||||
|
r = []string{fmt.Sprintf("%d %d %d %s",
|
||||||
|
a.(*dns.SRV).Priority,
|
||||||
|
a.(*dns.SRV).Weight,
|
||||||
|
a.(*dns.SRV).Port,
|
||||||
|
a.(*dns.SRV).Target,
|
||||||
|
)}
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("Got an unexpected answer type: %s", a.(dns.RR).String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
182
main.go
Normal file
182
main.go
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
//go:generate make bindata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
|
"github.com/Luzifer/rconfig"
|
||||||
|
"github.com/fatih/color"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
cfg = struct {
|
||||||
|
FullScan bool `flag:"full-scan,f" default:"false" description:"Scan all nameservers included in this build"`
|
||||||
|
Assert []string `flag:"assert,a" default:"" description:"Exit with exit code 2 when these DNS entries were not found"`
|
||||||
|
AssertPercentage float64 `flag:"assert-threshold" default:"100.0" description:"If used with -a fail when not at least N percent of the nameservers had the expected result"`
|
||||||
|
Quiet bool `flag:"quiet,q" default:"false" description:"Do not communicate by text, use only exit codes"`
|
||||||
|
Short bool `flag:"short,s" default:"true" description:"Use short notation (only when using assertion)"`
|
||||||
|
Version bool `flag:"version" default:"false" description:"Print version and exit"`
|
||||||
|
}{}
|
||||||
|
nameserverDirectory = struct {
|
||||||
|
CoreProviders []string `yaml:"core_providers"`
|
||||||
|
PublicNameservers map[string][]string `yaml:"public_nameservers"`
|
||||||
|
}{}
|
||||||
|
version = "dev"
|
||||||
|
|
||||||
|
// Color output helpers
|
||||||
|
red = color.New(color.FgRed).SprintfFunc()
|
||||||
|
green = color.New(color.FgGreen).SprintFunc()
|
||||||
|
yellow = color.New(color.FgYellow).SprintFunc()
|
||||||
|
providerOut = color.New(color.FgWhite).Add(color.BgBlue).SprintfFunc()
|
||||||
|
serverOut = color.New(color.FgWhite).SprintfFunc()
|
||||||
|
)
|
||||||
|
|
||||||
|
type checkResult struct {
|
||||||
|
Provider string
|
||||||
|
Server string
|
||||||
|
Results []string
|
||||||
|
QueryError error
|
||||||
|
AssertSucceeded bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c checkResult) Print() {
|
||||||
|
if c.QueryError != nil {
|
||||||
|
fmt.Printf("%s %s %s\n",
|
||||||
|
providerOut("[%s]", c.Provider),
|
||||||
|
serverOut("(%s)", c.Server),
|
||||||
|
red("Error: %s", c.QueryError),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var result string
|
||||||
|
if len(cfg.Assert) > 0 {
|
||||||
|
if c.AssertSucceeded {
|
||||||
|
result = green("\u2713")
|
||||||
|
} else {
|
||||||
|
result = red("\u2717")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
srvBuf := bytes.NewBuffer([]byte{})
|
||||||
|
fmt.Fprintf(srvBuf, "%s %s %s\n",
|
||||||
|
providerOut("[%s]", c.Provider),
|
||||||
|
serverOut("(%s)", c.Server),
|
||||||
|
result,
|
||||||
|
)
|
||||||
|
if !cfg.Short {
|
||||||
|
for _, r := range c.Results {
|
||||||
|
fmt.Fprintf(srvBuf, " %s %s\n", yellow("-"), r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Print(srvBuf.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if err := rconfig.Parse(&cfg); err != nil {
|
||||||
|
log.Fatalf("Unable to parse arguments: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Version {
|
||||||
|
fmt.Printf("dns_check version %s\n", version)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if reflect.DeepEqual(cfg.Assert, []string{""}) {
|
||||||
|
cfg.Short = false
|
||||||
|
cfg.Assert = []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := loadNameservers(); err != nil {
|
||||||
|
log.Fatalf("Unable to load nameserver list, probably your build is defect: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
args := rconfig.Args()
|
||||||
|
if len(args) != 3 {
|
||||||
|
fmt.Println("Usage: dns_check <type> <query>")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
queryType := args[1]
|
||||||
|
queryFQDN := args[2]
|
||||||
|
|
||||||
|
// Correct ordering is required for DeepEqual
|
||||||
|
sort.Strings(cfg.Assert)
|
||||||
|
|
||||||
|
wg := make(chan bool, 10)
|
||||||
|
results := []*checkResult{}
|
||||||
|
for provider, servers := range nameserverDirectory.PublicNameservers {
|
||||||
|
if !cfg.FullScan && !isCoreProvider(provider) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, server := range servers {
|
||||||
|
wg <- true
|
||||||
|
r := &checkResult{
|
||||||
|
Provider: provider,
|
||||||
|
Server: server,
|
||||||
|
}
|
||||||
|
results = append(results, r)
|
||||||
|
go checkProviderServer(wg, queryType, queryFQDN, provider, server, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for len(wg) > 0 {
|
||||||
|
time.Sleep(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var failCount int
|
||||||
|
for _, r := range results {
|
||||||
|
if !r.AssertSucceeded {
|
||||||
|
failCount++
|
||||||
|
}
|
||||||
|
if !cfg.Quiet {
|
||||||
|
r.Print()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (1.0-float64(failCount)/float64(len(results)))*100 < cfg.AssertPercentage {
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkProviderServer(wg chan bool, queryType, queryFQDN, provider, server string, r *checkResult) {
|
||||||
|
r.Results, r.QueryError = getDNSQueryResponse(queryType, queryFQDN, server)
|
||||||
|
|
||||||
|
if len(cfg.Assert) > 0 {
|
||||||
|
r.AssertSucceeded = reflect.DeepEqual(r.Results, cfg.Assert)
|
||||||
|
} else {
|
||||||
|
r.AssertSucceeded = true
|
||||||
|
}
|
||||||
|
|
||||||
|
<-wg
|
||||||
|
}
|
||||||
|
|
||||||
|
func isCoreProvider(s string) bool {
|
||||||
|
for _, v := range nameserverDirectory.CoreProviders {
|
||||||
|
if v == s {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadNameservers() error {
|
||||||
|
data, err := Asset("nameservers.yaml")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return yaml.Unmarshal(data, &nameserverDirectory)
|
||||||
|
}
|
64
nameservers.yaml
Normal file
64
nameservers.yaml
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
---
|
||||||
|
core_providers:
|
||||||
|
- Level3
|
||||||
|
- Verisign
|
||||||
|
- Google
|
||||||
|
- OpenDNS Home
|
||||||
|
public_nameservers:
|
||||||
|
# List compiled from http://pcsupport.about.com/od/tipstricks/a/free-public-dns-servers.htm
|
||||||
|
Level3:
|
||||||
|
- 209.244.0.3:53
|
||||||
|
- 209.244.0.4:53
|
||||||
|
Verisign:
|
||||||
|
- 64.6.64.6:53
|
||||||
|
- 64.6.65.6:53
|
||||||
|
Google:
|
||||||
|
- 8.8.8.8:53
|
||||||
|
- 8.8.4.4:53
|
||||||
|
DNS.WATCH:
|
||||||
|
- 84.200.69.80:53
|
||||||
|
- 84.200.70.40:53
|
||||||
|
Comodo Secure DNS:
|
||||||
|
- 8.26.56.26:53
|
||||||
|
- 8.20.247.20:53
|
||||||
|
OpenDNS Home:
|
||||||
|
- 208.67.222.222:53
|
||||||
|
- 208.67.220.220:53
|
||||||
|
DNS Advantage:
|
||||||
|
- 156.154.70.1:53
|
||||||
|
- 156.154.71.1:53
|
||||||
|
Norton ConnectSafe:
|
||||||
|
- 199.85.126.10:53
|
||||||
|
- 199.85.127.10:53
|
||||||
|
GreenTeamDNS:
|
||||||
|
- 81.218.119.11:53
|
||||||
|
- 209.88.198.133:53
|
||||||
|
SafeDNS:
|
||||||
|
- 195.46.39.39:53
|
||||||
|
- 195.46.39.40:53
|
||||||
|
OpenNIC:
|
||||||
|
- 50.116.40.226:53
|
||||||
|
- 50.116.23.211:53
|
||||||
|
SmartViper:
|
||||||
|
- 208.76.50.50:53
|
||||||
|
- 208.76.51.51:53
|
||||||
|
Dyn:
|
||||||
|
- 216.146.35.35:53
|
||||||
|
- 216.146.36.36:53
|
||||||
|
FreeDNS:
|
||||||
|
- 37.235.1.174:53
|
||||||
|
- 37.235.1.177:53
|
||||||
|
Alternate DNS:
|
||||||
|
- 198.101.242.72:53
|
||||||
|
- 23.253.163.53:53
|
||||||
|
Yandex.DNS:
|
||||||
|
- 77.88.8.8:53
|
||||||
|
- 77.88.8.1:53
|
||||||
|
censurfridns.dk:
|
||||||
|
- 89.233.43.71:53
|
||||||
|
- 91.239.100.100:53
|
||||||
|
Hurricane Electric:
|
||||||
|
- 74.82.42.42:53
|
||||||
|
puntCAT:
|
||||||
|
- 109.69.8.51:53
|
||||||
|
|
Loading…
Reference in a new issue