152 lines
3.5 KiB
Go
152 lines
3.5 KiB
Go
package checker
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/miekg/dns"
|
|
)
|
|
|
|
const dnsTimeout = 5 * time.Second
|
|
|
|
// FallbackResolver is the resolver used when /etc/resolv.conf is missing or
|
|
// empty.
|
|
var FallbackResolver = net.JoinHostPort("1.1.1.1", "53")
|
|
|
|
func systemResolver() string {
|
|
cfg, err := dns.ClientConfigFromFile("/etc/resolv.conf")
|
|
if err != nil || len(cfg.Servers) == 0 {
|
|
return FallbackResolver
|
|
}
|
|
return net.JoinHostPort(cfg.Servers[0], cfg.Port)
|
|
}
|
|
|
|
func dnsExchange(ctx context.Context, server string, q dns.Question) (*dns.Msg, error) {
|
|
client := dns.Client{Timeout: dnsTimeout}
|
|
m := new(dns.Msg)
|
|
m.Id = dns.Id()
|
|
m.Question = []dns.Question{q}
|
|
m.RecursionDesired = true
|
|
m.SetEdns0(4096, true)
|
|
|
|
if deadline, ok := ctx.Deadline(); ok {
|
|
if d := time.Until(deadline); d > 0 && d < client.Timeout {
|
|
client.Timeout = d
|
|
}
|
|
}
|
|
|
|
r, _, err := client.Exchange(m, server)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if r != nil && r.Truncated {
|
|
if ctx.Err() != nil {
|
|
return r, nil
|
|
}
|
|
tcpClient := dns.Client{Net: "tcp", Timeout: client.Timeout}
|
|
if r2, _, err2 := tcpClient.Exchange(m, server); err2 == nil && r2 != nil {
|
|
return r2, nil
|
|
}
|
|
}
|
|
return r, nil
|
|
}
|
|
|
|
func lowerFQDN(name string) string {
|
|
return strings.ToLower(dns.Fqdn(name))
|
|
}
|
|
|
|
func isReverseArpa(name string) bool {
|
|
n := lowerFQDN(name)
|
|
return strings.HasSuffix(n, ".in-addr.arpa.") || n == "in-addr.arpa." ||
|
|
strings.HasSuffix(n, ".ip6.arpa.") || n == "ip6.arpa."
|
|
}
|
|
|
|
func isIPv6Arpa(name string) bool {
|
|
n := lowerFQDN(name)
|
|
return strings.HasSuffix(n, ".ip6.arpa.") || n == "ip6.arpa."
|
|
}
|
|
|
|
// reverseNameToIP returns nil for partial zone apexes (e.g. covering only part of an octet).
|
|
func reverseNameToIP(name string) net.IP {
|
|
n := strings.ToLower(strings.TrimSuffix(dns.Fqdn(name), "."))
|
|
|
|
switch {
|
|
case strings.HasSuffix(n, ".in-addr.arpa"):
|
|
labels := strings.Split(strings.TrimSuffix(n, ".in-addr.arpa"), ".")
|
|
if len(labels) != 4 {
|
|
return nil
|
|
}
|
|
out := make([]string, 4)
|
|
for i, l := range labels {
|
|
if _, err := strconv.Atoi(l); err != nil {
|
|
return nil
|
|
}
|
|
out[3-i] = l
|
|
}
|
|
return net.ParseIP(strings.Join(out, "."))
|
|
|
|
case strings.HasSuffix(n, ".ip6.arpa"):
|
|
labels := strings.Split(strings.TrimSuffix(n, ".ip6.arpa"), ".")
|
|
if len(labels) != 32 {
|
|
return nil
|
|
}
|
|
var sb strings.Builder
|
|
for i := len(labels) - 1; i >= 0; i-- {
|
|
if len(labels[i]) != 1 {
|
|
return nil
|
|
}
|
|
sb.WriteString(labels[i])
|
|
if i > 0 && (len(labels)-i)%4 == 0 {
|
|
sb.WriteByte(':')
|
|
}
|
|
}
|
|
return net.ParseIP(sb.String())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func resolveForward(ctx context.Context, name string) ([]ForwardAddress, string) {
|
|
resolver := systemResolver()
|
|
var out []ForwardAddress
|
|
var lastErr string
|
|
anySuccess := false
|
|
|
|
for _, qt := range []uint16{dns.TypeA, dns.TypeAAAA} {
|
|
if ctx.Err() != nil {
|
|
break
|
|
}
|
|
q := dns.Question{Name: dns.Fqdn(name), Qtype: qt, Qclass: dns.ClassINET}
|
|
r, err := dnsExchange(ctx, resolver, q)
|
|
if err != nil {
|
|
lastErr = err.Error()
|
|
continue
|
|
}
|
|
anySuccess = true
|
|
if r == nil {
|
|
continue
|
|
}
|
|
for _, rr := range r.Answer {
|
|
switch v := rr.(type) {
|
|
case *dns.A:
|
|
out = append(out, ForwardAddress{Type: "A", Address: v.A.String(), TTL: v.Hdr.Ttl})
|
|
case *dns.AAAA:
|
|
out = append(out, ForwardAddress{Type: "AAAA", Address: v.AAAA.String(), TTL: v.Hdr.Ttl})
|
|
}
|
|
}
|
|
}
|
|
if len(out) > 0 || anySuccess {
|
|
return out, ""
|
|
}
|
|
return out, lastErr
|
|
}
|
|
|
|
func ipEqual(addr string, ip net.IP) bool {
|
|
parsed := net.ParseIP(addr)
|
|
if parsed == nil {
|
|
return false
|
|
}
|
|
return parsed.Equal(ip)
|
|
}
|