Use peterzen/goresolver as resolver

This commit is contained in:
nemunaire 2025-11-17 11:00:36 +07:00
commit 6ae2c2463d
3 changed files with 140 additions and 14 deletions

2
go.mod
View file

@ -47,6 +47,7 @@ require (
github.com/mailru/easyjson v0.9.1 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.32 // indirect
github.com/miekg/dns v1.1.4 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
@ -55,6 +56,7 @@ require (
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/peterzen/goresolver v1.0.2 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/quic-go/quic-go v0.56.0 // indirect
github.com/redis/go-redis/v9 v9.16.0 // indirect

8
go.sum
View file

@ -120,6 +120,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/miekg/dns v1.1.4 h1:rCMZsU2ScVSYcAsOXgmC6+AKOK+6pmQTOcw03nfwYV0=
github.com/miekg/dns v1.1.4/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -154,6 +156,8 @@ github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
github.com/peterzen/goresolver v1.0.2 h1:UxRxk835Onz7Go4oPUsOptSmBlIvN/yJ2kv3Srr3hw4=
github.com/peterzen/goresolver v1.0.2/go.mod h1:LrWRiOeCYApgvR2OhpipNOeaE1yGfI+QQjpF0riJC8M=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
@ -198,6 +202,7 @@ go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg=
golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
golang.org/x/crypto v0.0.0-20190222235706-ffb98f73852f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@ -207,6 +212,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
@ -216,12 +222,14 @@ golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222171317-cd391775e71e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View file

@ -23,7 +23,12 @@ package analyzer
import (
"context"
"fmt"
"net"
"strings"
"github.com/miekg/dns"
"github.com/peterzen/goresolver"
)
// DNSResolver defines the interface for DNS resolution operations.
@ -45,36 +50,147 @@ type DNSResolver interface {
LookupHost(ctx context.Context, host string) ([]string, error)
}
// StandardDNSResolver is the default DNS resolver implementation that uses net.Resolver.
// StandardDNSResolver is the default DNS resolver implementation that uses goresolver with DNSSEC validation.
type StandardDNSResolver struct {
resolver *net.Resolver
resolver *goresolver.Resolver
}
// NewStandardDNSResolver creates a new StandardDNSResolver with default settings.
// NewStandardDNSResolver creates a new StandardDNSResolver with DNSSEC validation support.
func NewStandardDNSResolver() DNSResolver {
// Pass /etc/resolv.conf to load default DNS configuration
resolver, err := goresolver.NewResolver("/etc/resolv.conf")
if err != nil {
panic(fmt.Sprintf("failed to initialize goresolver: %v", err))
}
return &StandardDNSResolver{
resolver: &net.Resolver{
PreferGo: true,
},
resolver: resolver,
}
}
// LookupMX implements DNSResolver.LookupMX using net.Resolver.
// LookupMX implements DNSResolver.LookupMX using goresolver with DNSSEC validation.
func (r *StandardDNSResolver) LookupMX(ctx context.Context, name string) ([]*net.MX, error) {
return r.resolver.LookupMX(ctx, name)
// Ensure the name ends with a dot for DNS queries
queryName := name
if !strings.HasSuffix(queryName, ".") {
queryName = queryName + "."
}
rrs, err := r.resolver.StrictNSQuery(queryName, dns.TypeMX)
if err != nil {
return nil, err
}
mxRecords := make([]*net.MX, 0, len(rrs))
for _, rr := range rrs {
if mx, ok := rr.(*dns.MX); ok {
mxRecords = append(mxRecords, &net.MX{
Host: strings.TrimSuffix(mx.Mx, "."),
Pref: mx.Preference,
})
}
}
if len(mxRecords) == 0 {
return nil, fmt.Errorf("no MX records found for %s", name)
}
return mxRecords, nil
}
// LookupTXT implements DNSResolver.LookupTXT using net.Resolver.
// LookupTXT implements DNSResolver.LookupTXT using goresolver with DNSSEC validation.
func (r *StandardDNSResolver) LookupTXT(ctx context.Context, name string) ([]string, error) {
return r.resolver.LookupTXT(ctx, name)
// Ensure the name ends with a dot for DNS queries
queryName := name
if !strings.HasSuffix(queryName, ".") {
queryName = queryName + "."
}
rrs, err := r.resolver.StrictNSQuery(queryName, dns.TypeTXT)
if err != nil {
return nil, err
}
txtRecords := make([]string, 0, len(rrs))
for _, rr := range rrs {
if txt, ok := rr.(*dns.TXT); ok {
// Join all TXT strings (a single TXT record can have multiple strings)
txtRecords = append(txtRecords, strings.Join(txt.Txt, ""))
}
}
if len(txtRecords) == 0 {
return nil, fmt.Errorf("no TXT records found for %s", name)
}
return txtRecords, nil
}
// LookupAddr implements DNSResolver.LookupAddr using net.Resolver.
// LookupAddr implements DNSResolver.LookupAddr using goresolver with DNSSEC validation.
func (r *StandardDNSResolver) LookupAddr(ctx context.Context, addr string) ([]string, error) {
return r.resolver.LookupAddr(ctx, addr)
// Convert IP address to reverse DNS name (e.g., 1.0.0.127.in-addr.arpa.)
arpa, err := dns.ReverseAddr(addr)
if err != nil {
return nil, fmt.Errorf("invalid IP address: %w", err)
}
rrs, err := r.resolver.StrictNSQuery(arpa, dns.TypePTR)
if err != nil {
return nil, err
}
ptrRecords := make([]string, 0, len(rrs))
for _, rr := range rrs {
if ptr, ok := rr.(*dns.PTR); ok {
ptrRecords = append(ptrRecords, strings.TrimSuffix(ptr.Ptr, "."))
}
}
if len(ptrRecords) == 0 {
return nil, fmt.Errorf("no PTR records found for %s", addr)
}
return ptrRecords, nil
}
// LookupHost implements DNSResolver.LookupHost using net.Resolver.
// LookupHost implements DNSResolver.LookupHost using goresolver with DNSSEC validation.
func (r *StandardDNSResolver) LookupHost(ctx context.Context, host string) ([]string, error) {
return r.resolver.LookupHost(ctx, host)
// Ensure the host ends with a dot for DNS queries
queryName := host
if !strings.HasSuffix(queryName, ".") {
queryName = queryName + "."
}
var allAddrs []string
// Query A records (IPv4)
rrsA, errA := r.resolver.StrictNSQuery(queryName, dns.TypeA)
if errA == nil {
for _, rr := range rrsA {
if a, ok := rr.(*dns.A); ok {
allAddrs = append(allAddrs, a.A.String())
}
}
}
// Query AAAA records (IPv6)
rrsAAAA, errAAAA := r.resolver.StrictNSQuery(queryName, dns.TypeAAAA)
if errAAAA == nil {
for _, rr := range rrsAAAA {
if aaaa, ok := rr.(*dns.AAAA); ok {
allAddrs = append(allAddrs, aaaa.AAAA.String())
}
}
}
// Return error only if both queries failed
if errA != nil && errAAAA != nil {
return nil, fmt.Errorf("failed to resolve host: IPv4 error: %v, IPv6 error: %v", errA, errAAAA)
}
if len(allAddrs) == 0 {
return nil, fmt.Errorf("no A or AAAA records found for %s", host)
}
return allAddrs, nil
}