67 lines
1.7 KiB
Go
67 lines
1.7 KiB
Go
package dns
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"net/netip"
|
|
|
|
"github.com/qdm12/dns/v2/pkg/provider"
|
|
)
|
|
|
|
// Client is a simple plaintext UDP DNS client, to be used for healthchecks.
|
|
// Note the client connects to a DNS server only over UDP on port 53,
|
|
// because we don't want to use DoT or DoH and impact the TCP connections
|
|
// when running a healthcheck.
|
|
type Client struct {
|
|
serverAddrs []netip.AddrPort
|
|
dnsIPIndex int
|
|
}
|
|
|
|
func New() *Client {
|
|
return &Client{
|
|
serverAddrs: concatAddrPorts([][]netip.AddrPort{
|
|
provider.Cloudflare().Plain.IPv4,
|
|
provider.Google().Plain.IPv4,
|
|
provider.Quad9().Plain.IPv4,
|
|
provider.OpenDNS().Plain.IPv4,
|
|
provider.LibreDNS().Plain.IPv4,
|
|
provider.Quadrant().Plain.IPv4,
|
|
provider.CiraProtected().Plain.IPv4,
|
|
}),
|
|
}
|
|
}
|
|
|
|
func concatAddrPorts(addrs [][]netip.AddrPort) []netip.AddrPort {
|
|
var result []netip.AddrPort
|
|
for _, addrList := range addrs {
|
|
result = append(result, addrList...)
|
|
}
|
|
return result
|
|
}
|
|
|
|
var ErrLookupNoIPs = errors.New("no IPs found from DNS lookup")
|
|
|
|
func (c *Client) Check(ctx context.Context) error {
|
|
dnsAddr := c.serverAddrs[c.dnsIPIndex].String()
|
|
resolver := &net.Resolver{
|
|
PreferGo: true,
|
|
Dial: func(ctx context.Context, _, _ string) (net.Conn, error) {
|
|
dialer := net.Dialer{}
|
|
return dialer.DialContext(ctx, "udp", dnsAddr)
|
|
},
|
|
}
|
|
ips, err := resolver.LookupIP(ctx, "ip", "github.com")
|
|
switch {
|
|
case err != nil:
|
|
c.dnsIPIndex = (c.dnsIPIndex + 1) % len(c.serverAddrs)
|
|
return fmt.Errorf("with DNS server %s: %w", dnsAddr, err)
|
|
case len(ips) == 0:
|
|
c.dnsIPIndex = (c.dnsIPIndex + 1) % len(c.serverAddrs)
|
|
return fmt.Errorf("with DNS server %s: %w", dnsAddr, ErrLookupNoIPs)
|
|
default:
|
|
return nil
|
|
}
|
|
}
|