2024-07-21 07:57:47 +00:00
|
|
|
package dns
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
2024-08-18 04:34:09 +00:00
|
|
|
"fmt"
|
2024-07-21 07:57:47 +00:00
|
|
|
"github.com/miekg/dns"
|
|
|
|
log "github.com/sirupsen/logrus"
|
2024-08-18 04:34:09 +00:00
|
|
|
"github.com/xvzc/SpoofDPI/dns/addrselect"
|
2024-07-21 07:57:47 +00:00
|
|
|
"github.com/xvzc/SpoofDPI/util"
|
2024-08-18 04:34:09 +00:00
|
|
|
"net"
|
|
|
|
"net/netip"
|
|
|
|
"strconv"
|
|
|
|
"sync"
|
|
|
|
"time"
|
2024-07-21 07:57:47 +00:00
|
|
|
)
|
|
|
|
|
2024-08-18 04:34:09 +00:00
|
|
|
type client interface {
|
|
|
|
Resolve(ctx context.Context, host string) ([]net.IPAddr, error)
|
|
|
|
String() string
|
2024-07-21 07:57:47 +00:00
|
|
|
}
|
|
|
|
|
2024-08-18 04:34:09 +00:00
|
|
|
type Resolver struct {
|
|
|
|
host string
|
|
|
|
port string
|
|
|
|
enableDoh bool
|
|
|
|
systemClient client
|
|
|
|
customClient client
|
2024-07-21 07:57:47 +00:00
|
|
|
}
|
|
|
|
|
2024-08-18 04:34:09 +00:00
|
|
|
func NewResolver(config *util.Config) *Resolver {
|
|
|
|
addr := *config.DnsAddr
|
|
|
|
port := strconv.Itoa(*config.DnsPort)
|
|
|
|
server := net.JoinHostPort(addr, port)
|
2024-07-21 07:57:47 +00:00
|
|
|
|
2024-08-18 04:34:09 +00:00
|
|
|
var systemClient client
|
|
|
|
if config.AllowedPatterns != nil {
|
|
|
|
systemClient = NewSystemClient()
|
2024-07-21 07:57:47 +00:00
|
|
|
}
|
|
|
|
|
2024-08-18 04:34:09 +00:00
|
|
|
var customClient client
|
|
|
|
if *config.EnableDoh {
|
|
|
|
customClient = NewDoHClient(addr)
|
|
|
|
} else {
|
|
|
|
customClient = NewCustomClient(server)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &Resolver{
|
|
|
|
host: *config.DnsAddr,
|
|
|
|
port: port,
|
|
|
|
enableDoh: *config.EnableDoh,
|
|
|
|
systemClient: systemClient,
|
|
|
|
customClient: customClient,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *Resolver) Lookup(domain string, useSystemDns bool) (string, error) {
|
|
|
|
if _, err := parseAddr(domain); err == nil {
|
|
|
|
return domain, nil
|
2024-08-06 08:48:18 +00:00
|
|
|
}
|
|
|
|
|
2024-08-18 04:34:09 +00:00
|
|
|
clt := d.getClient(useSystemDns)
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
log.Debugf("[DNS] resolving %s using %s", domain, clt)
|
|
|
|
t := time.Now()
|
|
|
|
addrs, err := clt.Resolve(ctx, domain)
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("%s: %w", clt, err)
|
2024-07-21 07:57:47 +00:00
|
|
|
}
|
2024-08-18 04:34:09 +00:00
|
|
|
lookupTime := time.Since(t).Milliseconds()
|
2024-07-21 07:57:47 +00:00
|
|
|
|
2024-08-18 04:34:09 +00:00
|
|
|
addr := addrs[0].String()
|
|
|
|
log.Debugf("[DNS] resolved %s to %s in %d ms", domain, addr, lookupTime)
|
|
|
|
return addr, nil
|
2024-08-06 08:48:18 +00:00
|
|
|
}
|
|
|
|
|
2024-08-18 04:34:09 +00:00
|
|
|
func (d *Resolver) getClient(useSystemDns bool) client {
|
|
|
|
if useSystemDns {
|
|
|
|
return d.systemClient
|
|
|
|
} else {
|
|
|
|
return d.customClient
|
|
|
|
}
|
|
|
|
}
|
2024-08-06 08:48:18 +00:00
|
|
|
|
2024-08-18 04:34:09 +00:00
|
|
|
type SystemClient struct {
|
|
|
|
client *net.Resolver
|
|
|
|
}
|
2024-07-21 07:57:47 +00:00
|
|
|
|
2024-08-18 04:34:09 +00:00
|
|
|
func NewSystemClient() *SystemClient {
|
|
|
|
return &SystemClient{
|
|
|
|
client: &net.Resolver{PreferGo: true},
|
|
|
|
}
|
|
|
|
}
|
2024-07-21 07:57:47 +00:00
|
|
|
|
2024-08-18 04:34:09 +00:00
|
|
|
func (c *SystemClient) String() string {
|
|
|
|
return "SystemClient"
|
|
|
|
}
|
2024-07-21 07:57:47 +00:00
|
|
|
|
2024-08-18 04:34:09 +00:00
|
|
|
func (c *SystemClient) Resolve(ctx context.Context, host string) ([]net.IPAddr, error) {
|
|
|
|
addrs, err := c.client.LookupIPAddr(ctx, host)
|
2024-07-21 07:57:47 +00:00
|
|
|
if err != nil {
|
2024-08-18 04:34:09 +00:00
|
|
|
return []net.IPAddr{}, err
|
2024-07-21 07:57:47 +00:00
|
|
|
}
|
2024-08-18 04:34:09 +00:00
|
|
|
return addrs, nil
|
|
|
|
}
|
2024-07-21 07:57:47 +00:00
|
|
|
|
2024-08-18 04:34:09 +00:00
|
|
|
type sendMsgFunc = func(ctx context.Context, msg *dns.Msg) (*dns.Msg, error)
|
2024-07-21 07:57:47 +00:00
|
|
|
|
2024-08-18 04:34:09 +00:00
|
|
|
type customDNSResult struct {
|
|
|
|
msg *dns.Msg
|
|
|
|
err error
|
|
|
|
}
|
2024-08-06 08:48:18 +00:00
|
|
|
|
2024-08-18 04:34:09 +00:00
|
|
|
type CustomClient struct {
|
|
|
|
server string
|
|
|
|
sendMsgFn sendMsgFunc
|
2024-08-06 08:48:18 +00:00
|
|
|
}
|
|
|
|
|
2024-08-18 04:34:09 +00:00
|
|
|
func (c *CustomClient) Resolve(ctx context.Context, host string) ([]net.IPAddr, error) {
|
|
|
|
queryTypes := []uint16{dns.TypeAAAA, dns.TypeA}
|
|
|
|
resultCh := c.makeLookups(ctx, host, queryTypes)
|
|
|
|
|
|
|
|
addrs, err := c.processResults(ctx, resultCh)
|
|
|
|
return addrs, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *CustomClient) makeLookups(ctx context.Context, host string, queryTypes []uint16) <-chan *customDNSResult {
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
resCh := make(chan *customDNSResult)
|
|
|
|
|
|
|
|
lookup := func(qType uint16) {
|
|
|
|
defer wg.Done()
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
return
|
|
|
|
case resCh <- c.makeLookup(ctx, host, qType):
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, queryType := range queryTypes {
|
|
|
|
wg.Add(1)
|
|
|
|
go lookup(queryType)
|
2024-08-06 08:48:18 +00:00
|
|
|
}
|
2024-08-18 04:34:09 +00:00
|
|
|
go func() {
|
|
|
|
wg.Wait()
|
|
|
|
close(resCh)
|
|
|
|
}()
|
|
|
|
return resCh
|
|
|
|
}
|
2024-08-06 08:48:18 +00:00
|
|
|
|
2024-08-18 04:34:09 +00:00
|
|
|
func (c *CustomClient) makeLookup(ctx context.Context, host string, queryType uint16) *customDNSResult {
|
|
|
|
msg := c.newMsg(host, queryType)
|
|
|
|
resp, err := c.sendMsg(ctx, msg)
|
|
|
|
if err != nil {
|
|
|
|
queryName := recordTypeIDToName(queryType)
|
|
|
|
err = fmt.Errorf("resolving %s, query type %s: %w", host, queryName, err)
|
|
|
|
return &customDNSResult{err: err}
|
2024-08-06 08:48:18 +00:00
|
|
|
}
|
2024-08-18 04:34:09 +00:00
|
|
|
return &customDNSResult{msg: resp}
|
|
|
|
}
|
2024-08-06 08:48:18 +00:00
|
|
|
|
2024-08-18 04:34:09 +00:00
|
|
|
func (c *CustomClient) newMsg(host string, qType uint16) *dns.Msg {
|
|
|
|
msg := new(dns.Msg)
|
|
|
|
msg.SetQuestion(dns.Fqdn(host), qType)
|
|
|
|
return msg
|
2024-07-21 07:57:47 +00:00
|
|
|
}
|
|
|
|
|
2024-08-18 04:34:09 +00:00
|
|
|
func (c *CustomClient) sendMsg(ctx context.Context, msg *dns.Msg) (*dns.Msg, error) {
|
|
|
|
resp, err := c.sendMsgFn(ctx, msg)
|
|
|
|
return resp, err
|
|
|
|
}
|
2024-08-12 15:33:31 +00:00
|
|
|
|
2024-08-18 04:34:09 +00:00
|
|
|
func (c *CustomClient) processResults(ctx context.Context, resCh <-chan *customDNSResult) ([]net.IPAddr, error) {
|
|
|
|
var errs []error
|
|
|
|
var addrs []net.IPAddr
|
2024-08-12 22:37:20 +00:00
|
|
|
|
2024-08-18 04:34:09 +00:00
|
|
|
for result := range resCh {
|
|
|
|
if result.err != nil {
|
|
|
|
errs = append(errs, result.err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
resultAddrs := parseAddrsFromMsg(result.msg)
|
|
|
|
addrs = append(addrs, resultAddrs...)
|
|
|
|
}
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
return nil, errors.New("cancelled")
|
|
|
|
default:
|
|
|
|
if len(addrs) == 0 {
|
|
|
|
return addrs, errors.Join(errs...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sortAddrs(addrs)
|
|
|
|
return addrs, nil
|
|
|
|
}
|
2024-08-12 22:37:20 +00:00
|
|
|
|
2024-08-18 04:34:09 +00:00
|
|
|
func (c *CustomClient) String() string {
|
|
|
|
return fmt.Sprintf("CustomClient for %s", c.server)
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewCustomClient(server string) *CustomClient {
|
|
|
|
clt := &dns.Client{}
|
|
|
|
return &CustomClient{
|
|
|
|
server: server,
|
|
|
|
sendMsgFn: func(ctx context.Context, msg *dns.Msg) (*dns.Msg, error) {
|
|
|
|
resp, _, err := clt.Exchange(msg, server)
|
|
|
|
return resp, err
|
|
|
|
},
|
2024-07-21 07:57:47 +00:00
|
|
|
}
|
2024-08-18 04:34:09 +00:00
|
|
|
}
|
2024-07-21 07:57:47 +00:00
|
|
|
|
2024-08-18 04:34:09 +00:00
|
|
|
func NewDoHClient(host string) *CustomClient {
|
|
|
|
server := net.JoinHostPort(host, "443")
|
|
|
|
clt := getDOHClient(server)
|
|
|
|
return &CustomClient{
|
|
|
|
server: server,
|
|
|
|
sendMsgFn: func(ctx context.Context, msg *dns.Msg) (*dns.Msg, error) {
|
|
|
|
return clt.dohExchange(ctx, msg)
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func recordTypeIDToName(id uint16) string {
|
|
|
|
switch id {
|
|
|
|
case 1:
|
|
|
|
return "A"
|
|
|
|
case 28:
|
|
|
|
return "AAAA"
|
|
|
|
}
|
|
|
|
return strconv.FormatUint(uint64(id), 10)
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseAddrsFromMsg(msg *dns.Msg) []net.IPAddr {
|
|
|
|
var addrs []net.IPAddr
|
|
|
|
|
|
|
|
for _, record := range msg.Answer {
|
|
|
|
switch ipRecord := record.(type) {
|
|
|
|
case *dns.A:
|
|
|
|
addrs = append(addrs, net.IPAddr{IP: ipRecord.A})
|
|
|
|
case *dns.AAAA:
|
|
|
|
addrs = append(addrs, net.IPAddr{IP: ipRecord.AAAA})
|
2024-08-12 22:37:20 +00:00
|
|
|
}
|
|
|
|
}
|
2024-08-18 04:34:09 +00:00
|
|
|
return addrs
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseAddr(addr string) (net.IP, error) {
|
|
|
|
parsed, err := netip.ParseAddr(addr)
|
|
|
|
if err != nil {
|
|
|
|
return net.IP{}, fmt.Errorf("parsing %s as an IP address: %w", addr, err)
|
|
|
|
}
|
|
|
|
return parsed.AsSlice(), nil
|
|
|
|
}
|
2024-08-12 22:37:20 +00:00
|
|
|
|
2024-08-18 04:34:09 +00:00
|
|
|
func sortAddrs(addrs []net.IPAddr) {
|
|
|
|
addrselect.SortByRFC6724(addrs)
|
2024-07-21 07:57:47 +00:00
|
|
|
}
|