package checker import ( "context" "fmt" "net" "sync" "time" sdk "git.happydns.org/checker-sdk-go/checker" ) func init() { Register(&quad9Source{ secure: newDNSResolver("9.9.9.9:53"), unsecured: newDNSResolver("9.9.9.10:53"), }) } type quad9Source struct { secure *net.Resolver unsecured *net.Resolver } func newDNSResolver(addr string) *net.Resolver { return &net.Resolver{ PreferGo: true, Dial: func(ctx context.Context, network, _ string) (net.Conn, error) { d := &net.Dialer{Timeout: 5 * time.Second} return d.DialContext(ctx, "udp", addr) }, } } func (*quad9Source) ID() string { return "quad9" } func (*quad9Source) Name() string { return "Quad9 secure DNS" } func (*quad9Source) Options() SourceOptions { return SourceOptions{ User: []sdk.CheckerOptionField{ { Id: "enable_quad9", Type: "bool", Label: "Use Quad9 secure DNS check", Description: "Compare Quad9's secure resolver (9.9.9.9) against its unsecured peer (9.9.9.10). A domain that resolves on the unsecured but returns NXDOMAIN on the secure resolver is blocked by Quad9's threat intelligence.", Default: true, }, }, } } func (s *quad9Source) Query(ctx context.Context, domain, registered string, opts sdk.CheckerOptions) []SourceResult { if !sdk.GetBoolOption(opts, "enable_quad9", true) || registered == "" { return disabledResult(s.ID(), s.Name()) } res := SourceResult{ SourceID: s.ID(), SourceName: s.Name(), Enabled: true, LookupURL: "https://www.quad9.net/result/?domain=" + registered, } var ( secureAddrs, unsecuredAddrs []net.IPAddr secureErr, unsecuredErr error wg sync.WaitGroup ) queryCtx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() wg.Add(2) go func() { defer wg.Done() secureAddrs, secureErr = s.secure.LookupIPAddr(queryCtx, registered) }() go func() { defer wg.Done() unsecuredAddrs, unsecuredErr = s.unsecured.LookupIPAddr(queryCtx, registered) }() wg.Wait() // Suppress the unused variable warning — secureAddrs is only used as an // existence check; the actual IPs are not surfaced. _ = secureAddrs // If the unsecured resolver can't resolve the domain either, it simply // doesn't exist — not a Quad9 block. if unsecuredErr != nil || len(unsecuredAddrs) == 0 { if unsecuredErr != nil { if dnsErr, ok := unsecuredErr.(*net.DNSError); !ok || !dnsErr.IsNotFound { res.Error = "unsecured resolver: " + unsecuredErr.Error() } } return []SourceResult{res} } // Domain resolves on unsecured. Check whether the secure resolver blocked it. if dnsErr, ok := secureErr.(*net.DNSError); ok && dnsErr.IsNotFound { res.Reasons = []string{"Blocked by Quad9 threat intelligence"} res.Evidence = append(res.Evidence, Evidence{ Label: "Secure resolver (9.9.9.9)", Value: "NXDOMAIN", }) } else if secureErr != nil { res.Error = "secure resolver: " + secureErr.Error() } return []SourceResult{res} } func (*quad9Source) Evaluate(r SourceResult) (bool, string) { return evidenceEval(r, SeverityCrit) } func (*quad9Source) Diagnose(res SourceResult) Diagnosis { return Diagnosis{ Severity: SeverityCrit, Title: "Blocked by Quad9 secure DNS resolver", Detail: fmt.Sprintf( "This domain is on Quad9's threat intelligence blocklist: the secure resolver (9.9.9.9) returns NXDOMAIN while the unsecured peer (9.9.9.10) resolves normally. Quad9 aggregates feeds from 18+ threat intel partners (abuse.ch, Bambenek, CINS, DShield, etc.). Visitors whose ISP or device uses Quad9 cannot reach this domain. Submit a false-positive report at the Quad9 contact page if you believe the listing is incorrect.", ), Fix: "https://www.quad9.net/support/contact/", FixIsURL: true, LookupURL: res.LookupURL, } }