From e9dd8feb4fdd0a6c991f52894ba704d45624b28d Mon Sep 17 00:00:00 2001 From: Stephane Bortzmeyer Date: Sat, 3 Nov 2012 23:14:11 +0100 Subject: [PATCH] Go equivalent of the "DNS & BIND" book check-soa program --- ex/check-soa/check-soa.go | 172 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 ex/check-soa/check-soa.go diff --git a/ex/check-soa/check-soa.go b/ex/check-soa/check-soa.go new file mode 100644 index 00000000..9abd2e97 --- /dev/null +++ b/ex/check-soa/check-soa.go @@ -0,0 +1,172 @@ +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) (r *dns.Msg, err error) { + localm.Question[0] = dns.Question{qname, qtype, dns.ClassINET} + for serverIndex := range conf.Servers { + server := conf.Servers[serverIndex] + r, err := localc.Exchange(localm, server+":"+conf.Port) + if r == nil { + return r, err + } + if r.Rcode == dns.RcodeNameError { + return r, err + } + if 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) + } + zone := os.Args[1] + if zone[len(zone)-1] != '.' { + zone += "." + } + 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.MsgHdr.RecursionDesired = true + localm.Question = make([]dns.Question, 1) + localc = new(dns.Client) + localc.ReadTimeout = TIMEOUT * 1e9 + r, err := localQuery(zone, dns.TypeNS) + if r == nil { + fmt.Printf("Cannot retrieve the list of name servers for %s: %s\n", zone, err) + os.Exit(1) + } + if r.Rcode == dns.RcodeNameError { + fmt.Printf("No such domain %s\n", zone) + os.Exit(1) + } + m := new(dns.Msg) + m.MsgHdr.RecursionDesired = false + m.Question = make([]dns.Question, 1) + c := new(dns.Client) + c.ReadTimeout = TIMEOUT * 1e9 + success := true + numNS := 0 + for i := range r.Answer { + ans := r.Answer[i] + switch ans.(type) { + case *dns.RR_NS: + nameserver := ans.(*dns.RR_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.Rcode_str[ra.Rcode]) + os.Exit(1) + } + for j := range ra.Answer { + ansa := ra.Answer[j] + switch ansa.(type) { + case *dns.RR_A: + ips = append(ips, ansa.(*dns.RR_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.Rcode_str[raaaa.Rcode]) + os.Exit(1) + } + for j := range raaaa.Answer { + ansaaaa := raaaa.Answer[j] + switch ansaaaa.(type) { + case *dns.RR_AAAA: + ips = append(ips, ansaaaa.(*dns.RR_AAAA).AAAA.String()) + } + } + if len(ips) == 0 { + success = false + fmt.Printf("No IP address for this server") + } + for j := range ips { + m.Question[0] = dns.Question{zone, dns.TypeSOA, dns.ClassINET} + nsAddressPort := "" + if strings.ContainsAny(":", ips[j]) { + /* IPv6 address */ + nsAddressPort = "[" + ips[j] + "]:53" + } else { + nsAddressPort = ips[j] + ":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) ", ips[j], err) + } else { + if soa.Rcode != dns.RcodeSuccess { + success = false + fmt.Printf("%s (%s) ", ips[j], dns.Rcode_str[soa.Rcode]) + } else { + 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) ", ips[j]) + } else { + rsoa := soa.Answer[0] + switch rsoa.(type) { + case *dns.RR_SOA: + if soa.MsgHdr.Authoritative { + /* TODO: test if all name servers have the same serial ? */ + fmt.Printf("%s (%d) ", ips[j], rsoa.(*dns.RR_SOA).Serial) + } else { + success = false + fmt.Printf("%s (not authoritative) ", ips[j]) + } + } + } + } + } + } + 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", zone) + os.Exit(1) + } + if success { + os.Exit(0) + } else { + os.Exit(1) + } +}