Все проверки выполнены успешно
CI / test (push) Successful in 2m32s
Docker / Build and publish worker image (push) Successful in 20m9s
Treat resolver transport failures as inconclusive checks so they do not open outage alerts.
237 строки
5.6 KiB
Go
237 строки
5.6 KiB
Go
// Source: https://github.com/bortzmeyer/check-soa/blob/master/check-soa.go
|
|
// 2-Clause BSD License: Copyright (c) 2012, Stephane Bortzmeyer All rights reserved.
|
|
// A simple program to have rapidly an idea of the health of a DNS
|
|
// zone. It queries each name server of the zone for the SOA record and
|
|
// displays the value of the serial number for each server.
|
|
//
|
|
// Stephane Bortzmeyer <bortzmeyer@nic.fr>
|
|
// Heavily modified for RSMon
|
|
|
|
package cdns
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/miekg/dns"
|
|
"github.com/weppos/publicsuffix-go/publicsuffix"
|
|
|
|
"rocketgit.ru/rsmon/worker/app/models"
|
|
)
|
|
|
|
const (
|
|
stateOK = "OK"
|
|
stateERR = "ERR"
|
|
stateWARN = "WARN"
|
|
)
|
|
|
|
var localhost net.IP
|
|
|
|
func init() {
|
|
localhost = net.ParseIP("127.0.0.1")
|
|
}
|
|
|
|
// Perform checks if domain is resolvable via it's DNS servers
|
|
func Perform(c *models.Check) *Result {
|
|
result := &Result{}
|
|
result.State = "FAIL"
|
|
host := c.Monitor.Host
|
|
|
|
if host == "" {
|
|
result.State = "FAIL"
|
|
result.Error = errors.New("empty host name")
|
|
return result
|
|
}
|
|
|
|
start := time.Now()
|
|
// log.Println("run dns:", host)
|
|
if host == "localhost" || strings.HasPrefix(host, "localhost:") {
|
|
result.Warnings = append(result.Warnings, "DNS check not possible for localhost, please disable")
|
|
return result
|
|
}
|
|
addr := net.ParseIP(host)
|
|
if addr != nil {
|
|
result.Warnings = append(result.Warnings, "DNS check not possible for ip address, please disable")
|
|
return result
|
|
}
|
|
|
|
zname, err := publicsuffix.Domain(host)
|
|
if err != nil {
|
|
result.Warnings = append(result.Warnings, "Failed to get public suffix: "+err.Error())
|
|
zname = host
|
|
}
|
|
// if zname != host {
|
|
// result.Infos = append(result.Infos, "not top level domain, running NS check for "+zname)
|
|
// }
|
|
|
|
zone := dns.Fqdn(zname)
|
|
nsChan := make(chan DNSreply)
|
|
// log.Println(zone)
|
|
go localQuery(nsChan, zone, dns.TypeNS)
|
|
nsResult := <-nsChan
|
|
if nsResult.r == nil {
|
|
// All recursive resolvers were unreachable, so this worker could not
|
|
// establish whether the domain is healthy. Do not open an outage event.
|
|
result.State = "FAIL"
|
|
result.Error = fmt.Errorf("cannot retrieve the list of name servers for %s: %s", zone, nsResult.err)
|
|
return result
|
|
}
|
|
if nsResult.r.Rcode == dns.RcodeNameError {
|
|
result.State = stateERR
|
|
result.Error = fmt.Errorf("no such domain %s", zone)
|
|
return result
|
|
}
|
|
// spew.Dump(nsResult)
|
|
|
|
nslist := make(map[string]nameServer, 0)
|
|
for i := range nsResult.r.Answer {
|
|
ans := nsResult.r.Answer[i]
|
|
if ns, ok := ans.(*dns.NS); ok {
|
|
name := ns.Ns
|
|
nslist[name] = nameServer{name: name, ips: make([]string, MaxAddresses)}
|
|
}
|
|
}
|
|
|
|
// spew.Dump(nslist)
|
|
|
|
numNS, numNSaddr, success, results := masterTask(zone, nslist)
|
|
if success {
|
|
result.State = stateOK
|
|
} else {
|
|
result.State = stateERR
|
|
}
|
|
|
|
if numNS == 0 {
|
|
result.State = stateERR
|
|
result.Error = fmt.Errorf("no NS records for zone \"%s\"", zone)
|
|
return result
|
|
}
|
|
if numNSaddr == 0 {
|
|
result.State = stateERR
|
|
result.Error = fmt.Errorf("no IP addresses for name servers of %s", zone)
|
|
return result
|
|
}
|
|
|
|
gallOK := true
|
|
ganyOK := false
|
|
failedNS := []string{}
|
|
|
|
lzone := dns.Fqdn(host)
|
|
|
|
for _, rzt := range results { //nolint:gocritic // range copy is acceptable here
|
|
// spew.Dump(rzt)
|
|
|
|
allOK := true
|
|
anyOK := false
|
|
ns := NSServer{Name: rzt.name}
|
|
for i := 0; i < len(rzt.ips); i++ {
|
|
ip := NSIP{
|
|
ResponseTime: rzt.rtts[i],
|
|
IP: rzt.ips[i],
|
|
}
|
|
if rzt.success[i] {
|
|
anyOK = true
|
|
ganyOK = true
|
|
ip.State = stateOK
|
|
ip.Serial = rzt.serial[i]
|
|
} else {
|
|
allOK = false
|
|
gallOK = false
|
|
ip.State = stateERR
|
|
ip.Error = errors.New(rzt.errMsg[i])
|
|
failedNS = append(failedNS, rzt.name)
|
|
// spew.Dump(rzt)
|
|
}
|
|
ns.NSIPs = append(ns.NSIPs, ip)
|
|
|
|
if result.State == stateOK {
|
|
// spew.Dump(ns)
|
|
// log.Println("fetching records for", lzone, "from", ns.NSIPs[0].IP)
|
|
ns.Response, err = FetchARecords(lzone, ns.NSIPs[0].IP)
|
|
if err != nil {
|
|
ns.State = stateERR
|
|
ns.Error = err
|
|
gallOK = false
|
|
failedNS = append(failedNS, rzt.name+"/"+ns.NSIPs[0].IP)
|
|
// log.Println("failed:", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(rzt.ips) == 0 {
|
|
ns.State = stateERR
|
|
ns.Error = errors.New(rzt.globalErrMsg)
|
|
failedNS = append(failedNS, rzt.name+"/no ip for dns server")
|
|
gallOK = false
|
|
} else {
|
|
if allOK {
|
|
ns.State = stateOK
|
|
} else {
|
|
if anyOK {
|
|
ns.State = "WARN"
|
|
// log.Println("failed NS")
|
|
// spew.Dump(ns)
|
|
if ns.Error == nil {
|
|
ns.Error = errors.New("some servers failed")
|
|
}
|
|
} else {
|
|
ns.State = stateERR
|
|
if ns.Error == nil {
|
|
ns.Error = errors.New("all servers failed")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
result.NSServers = append(result.NSServers, ns)
|
|
}
|
|
|
|
if gallOK {
|
|
result.State = stateOK
|
|
} else {
|
|
if ganyOK {
|
|
result.State = stateWARN
|
|
result.Warnings = append(result.Warnings, "some servers failed: "+strings.Join(failedNS, ","))
|
|
} else {
|
|
result.State = stateERR
|
|
if result.Error == nil {
|
|
result.Error = errors.New("all servers failed")
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, ni := range result.NSServers {
|
|
// log.Println(ni.Name)
|
|
for _, r := range ni.Response {
|
|
ip := r.Value.Inet
|
|
if ip.Equal(localhost) {
|
|
result.State = stateERR
|
|
err := "resolves to localhost/127.0.0.1"
|
|
if result.Error == nil {
|
|
result.Error = errors.New(err)
|
|
} else if result.Error.Error() != err {
|
|
result.Warnings = append(result.Warnings, err)
|
|
}
|
|
}
|
|
// log.Println(r.Name, r.Kind, r.Value)
|
|
}
|
|
}
|
|
|
|
_, maxt := result.Times()
|
|
if maxt > 2*time.Second {
|
|
if result.State == stateOK {
|
|
result.State = stateWARN
|
|
}
|
|
result.Warnings = append(result.Warnings, "slow")
|
|
}
|
|
|
|
result.Duration = time.Since(start)
|
|
|
|
// spew.Dump(result)
|
|
|
|
return result
|
|
}
|