1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
|
// Go equivalent of the "DNS & BIND" book check-soa program.
// Created by Stephane Bortzmeyer.
package main
import (
"errors"
"fmt"
"github.com/miekg/dns"
"os"
"strings"
"time"
)
const (
TIMEOUT time.Duration = 5 // seconds
)
var (
localm *dns.Msg
localc *dns.Client
conf *dns.ClientConfig
)
func localQuery(qname string, qtype uint16) (*dns.Msg, error) {
localm.SetQuestion(qname, qtype)
for i := range conf.Servers {
server := conf.Servers[i]
r, _, err := localc.Exchange(localm, server+":"+conf.Port)
if r == nil || r.Rcode == dns.RcodeNameError || r.Rcode == dns.RcodeSuccess {
return r, err
}
}
return nil, errors.New("No name server to answer the question")
}
func main() {
var err error
if len(os.Args) != 2 {
fmt.Printf("%s ZONE\n", os.Args[0])
os.Exit(1)
}
conf, err = dns.ClientConfigFromFile("/etc/resolv.conf")
if conf == nil {
fmt.Printf("Cannot initialize the local resolver: %s\n", err)
os.Exit(1)
}
localm = new(dns.Msg)
localm.RecursionDesired = true
localm.Question = make([]dns.Question, 1)
localc = new(dns.Client)
localc.ReadTimeout = TIMEOUT * 1e9
r, err := localQuery(dns.Fqdn(os.Args[1]), dns.TypeNS)
if r == nil {
fmt.Printf("Cannot retrieve the list of name servers for %s: %s\n", dns.Fqdn(os.Args[1]), err)
os.Exit(1)
}
if r.Rcode == dns.RcodeNameError {
fmt.Printf("No such domain %s\n", dns.Fqdn(os.Args[1]))
os.Exit(1)
}
m := new(dns.Msg)
m.RecursionDesired = false
m.Question = make([]dns.Question, 1)
c := new(dns.Client)
c.ReadTimeout = TIMEOUT * 1e9
success := true
numNS := 0
for _, ans := range r.Answer {
switch ans.(type) {
case *dns.NS:
nameserver := ans.(*dns.NS).Ns
numNS += 1
ips := make([]string, 0)
fmt.Printf("%s : ", nameserver)
ra, err := localQuery(nameserver, dns.TypeA)
if ra == nil {
fmt.Printf("Error getting the IPv4 address of %s: %s\n", nameserver, err)
os.Exit(1)
}
if ra.Rcode != dns.RcodeSuccess {
fmt.Printf("Error getting the IPv4 address of %s: %s\n", nameserver, dns.RcodeToString[ra.Rcode])
os.Exit(1)
}
for _, ansa := range ra.Answer {
switch ansa.(type) {
case *dns.A:
ips = append(ips, ansa.(*dns.A).A.String())
}
}
raaaa, err := localQuery(nameserver, dns.TypeAAAA)
if raaaa == nil {
fmt.Printf("Error getting the IPv6 address of %s: %s\n", nameserver, err)
os.Exit(1)
}
if raaaa.Rcode != dns.RcodeSuccess {
fmt.Printf("Error getting the IPv6 address of %s: %s\n", nameserver, dns.RcodeToString[raaaa.Rcode])
os.Exit(1)
}
for _, ansaaaa := range raaaa.Answer {
switch ansaaaa.(type) {
case *dns.AAAA:
ips = append(ips, ansaaaa.(*dns.AAAA).AAAA.String())
}
}
if len(ips) == 0 {
success = false
fmt.Printf("No IP address for this server")
}
for _, ip := range ips {
m.Question[0] = dns.Question{dns.Fqdn(os.Args[1]), dns.TypeSOA, dns.ClassINET}
nsAddressPort := ""
if strings.ContainsAny(":", ip) {
// IPv6 address
nsAddressPort = "[" + ip + "]:53"
} else {
nsAddressPort = ip + ":53"
}
soa, _, err := c.Exchange(m, nsAddressPort)
// TODO: retry if timeout? Otherwise, one lost UDP packet and it is the end
if soa == nil {
success = false
fmt.Printf("%s (%s) ", ip, err)
goto Next
}
if soa.Rcode != dns.RcodeSuccess {
success = false
fmt.Printf("%s (%s) ", ips, dns.RcodeToString[soa.Rcode])
goto Next
}
if len(soa.Answer) == 0 { // May happen if the server is a recursor, not authoritative, since we query with RD=0
success = false
fmt.Printf("%s (0 answer) ", ip)
goto Next
}
rsoa := soa.Answer[0]
switch rsoa.(type) {
case *dns.SOA:
if soa.Authoritative {
// TODO: test if all name servers have the same serial ?
fmt.Printf("%s (%d) ", ips, rsoa.(*dns.SOA).Serial)
} else {
success = false
fmt.Printf("%s (not authoritative) ", ips)
}
}
}
Next:
fmt.Printf("\n")
}
}
if numNS == 0 {
fmt.Printf("No NS records for \"%s\". It is probably a CNAME to a domain but not a zone\n", dns.Fqdn(os.Args[1]))
os.Exit(1)
}
if success {
os.Exit(0)
}
os.Exit(1)
}
|