1
0
Fork 0
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:
Knut Ahlers 2015-12-14 17:13:21 +01:00
commit bb35ac980a
8 changed files with 629 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
dns_check

4
.gobuilder.yml Normal file
View file

@ -0,0 +1,4 @@
build_matrix:
general:
ldflags:
- "-X main.version $(git describe --tags)"

8
Makefile Normal file
View 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
View 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
View 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
View 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
View 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
View 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