github.com/fumiama/terasu@v0.0.0-20240507144117-547a591149c0/dns/dns.go (about)

     1  package dns
     2  
     3  import (
     4  	"context"
     5  	"crypto/tls"
     6  	"errors"
     7  	"net"
     8  	"strings"
     9  	"sync"
    10  	"time"
    11  
    12  	"github.com/fumiama/terasu"
    13  	"github.com/fumiama/terasu/ip"
    14  )
    15  
    16  var (
    17  	ErrNoDNSAvailable = errors.New("no dns available")
    18  )
    19  
    20  var defaultDialer = net.Dialer{
    21  	Timeout: time.Second * 4,
    22  }
    23  
    24  func SetTimeout(t time.Duration) {
    25  	defaultDialer.Timeout = t
    26  }
    27  
    28  type dnsstat struct {
    29  	a string
    30  	e bool
    31  }
    32  
    33  type DNSList struct {
    34  	sync.RWMutex
    35  	m map[string][]*dnsstat
    36  	b map[string][]string
    37  }
    38  
    39  type DNSConfig struct {
    40  	Servers   map[string][]string `yaml:"Servers"`   // Servers map[dot.com]ip:ports
    41  	Fallbacks map[string][]string `yaml:"Fallbacks"` // Fallbacks map[domain]ips
    42  }
    43  
    44  // hasrecord no lock, use under lock
    45  func hasrecord(lst []*dnsstat, a string) bool {
    46  	for _, addr := range lst {
    47  		if addr.a == a {
    48  			return true
    49  		}
    50  	}
    51  	return false
    52  }
    53  
    54  // hasrecord no lock, use under lock
    55  func hasfallback(lst []string, a string) bool {
    56  	for _, addr := range lst {
    57  		if addr == a {
    58  			return true
    59  		}
    60  	}
    61  	return false
    62  }
    63  
    64  func (ds *DNSList) Add(c *DNSConfig) {
    65  	ds.Lock()
    66  	defer ds.Unlock()
    67  	addList := map[string][]*dnsstat{}
    68  	for host, addrs := range c.Servers {
    69  		for _, addr := range addrs {
    70  			if !hasrecord(ds.m[host], addr) && !hasrecord(addList[host], addr) {
    71  				addList[host] = append(addList[host], &dnsstat{addr, true})
    72  			}
    73  		}
    74  	}
    75  	for host, addrs := range addList {
    76  		ds.m[host] = append(ds.m[host], addrs...)
    77  	}
    78  	addListFallback := map[string][]string{}
    79  	for host, addrs := range c.Fallbacks {
    80  		for _, addr := range addrs {
    81  			if !hasfallback(ds.b[host], addr) && !hasfallback(addListFallback[host], addr) {
    82  				addListFallback[host] = append(addListFallback[host], addr)
    83  			}
    84  		}
    85  	}
    86  	for host, addrs := range addListFallback {
    87  		ds.b[host] = append(ds.b[host], addrs...)
    88  	}
    89  }
    90  
    91  func (ds *DNSList) LookupHostFallback(ctx context.Context, host string) ([]string, error) {
    92  	ds.RLock()
    93  	defer ds.RUnlock()
    94  	// try to use DoH first
    95  	for _, addrs := range ds.m {
    96  		for _, addr := range addrs {
    97  			if !addr.e || !strings.HasPrefix(addr.a, "https://") { // disabled or is not DoH
    98  				continue
    99  			}
   100  			jr, err := lookupdoh(addr.a, host)
   101  			if err == nil {
   102  				hosts := jr.hosts()
   103  				if len(hosts) > 0 {
   104  					return hosts, nil
   105  				}
   106  			}
   107  			addr.e = false // no need to acquire write lock
   108  		}
   109  	}
   110  	if addrs, ok := ds.b[host]; ok {
   111  		return addrs, nil
   112  	}
   113  	return net.DefaultResolver.LookupHost(ctx, host)
   114  }
   115  
   116  func (ds *DNSList) DialContext(ctx context.Context, dialer *net.Dialer, firstFragmentLen uint8) (tlsConn *tls.Conn, err error) {
   117  	err = ErrNoDNSAvailable
   118  
   119  	if dialer == nil {
   120  		dialer = &defaultDialer
   121  	}
   122  
   123  	ds.RLock()
   124  	defer ds.RUnlock()
   125  
   126  	if dialer.Timeout != 0 {
   127  		var cancel context.CancelFunc
   128  		ctx, cancel = context.WithTimeout(ctx, dialer.Timeout)
   129  		defer cancel()
   130  	}
   131  
   132  	if !dialer.Deadline.IsZero() {
   133  		var cancel context.CancelFunc
   134  		ctx, cancel = context.WithDeadline(ctx, dialer.Deadline)
   135  		defer cancel()
   136  	}
   137  
   138  	var conn net.Conn
   139  	for host, addrs := range ds.m {
   140  		for _, addr := range addrs {
   141  			if !addr.e || strings.HasPrefix(addr.a, "https://") { // disabled or is DoH
   142  				continue
   143  			}
   144  			conn, err = dialer.DialContext(ctx, "tcp", addr.a)
   145  			if err != nil {
   146  				addr.e = false // no need to acquire write lock
   147  				continue
   148  			}
   149  			tlsConn = tls.Client(conn, &tls.Config{ServerName: host})
   150  			err = terasu.Use(tlsConn).HandshakeContext(ctx, firstFragmentLen)
   151  			if err == nil {
   152  				return
   153  			}
   154  			_ = tlsConn.Close()
   155  			addr.e = false // no need to acquire write lock
   156  		}
   157  	}
   158  	return
   159  }
   160  
   161  var IPv6Servers = DNSList{
   162  	m: map[string][]*dnsstat{
   163  		"dot.sb": {
   164  			{"[2a09::]:853", true},
   165  			{"[2a11::]:853", true},
   166  			{"https://doh.sb/dns-query", true},
   167  		},
   168  		"dns.google": {
   169  			{"[2001:4860:4860::8888]:853", true},
   170  			{"[2001:4860:4860::8844]:853", true},
   171  			{"https://dns.google/resolve", true},
   172  			{"https://[2001:4860:4860::8888]/resolve", true},
   173  			{"https://[2001:4860:4860::8844]/resolve", true},
   174  		},
   175  		"cloudflare-dns.com": {
   176  			{"[2606:4700:4700::1111]:853", true},
   177  			{"[2606:4700:4700::1001]:853", true},
   178  			{"https://cloudflare-dns.com/dns-query", true},
   179  			{"https://[2606:4700:4700::1111]/dns-query", true},
   180  			{"https://[2606:4700:4700::1001]/dns-query", true},
   181  		},
   182  		"dns.opendns.com": {
   183  			{"[2620:119:35::35]:853", true},
   184  			{"[2620:119:53::53]:853", true},
   185  		},
   186  		"dns10.quad9.net": {
   187  			{"[2620:fe::10]:853", true},
   188  			{"[2620:fe::fe:10]:853", true},
   189  		},
   190  	},
   191  	b: map[string][]string{},
   192  }
   193  
   194  var IPv4Servers = DNSList{
   195  	m: map[string][]*dnsstat{
   196  		"dot.sb": {
   197  			{"185.222.222.222:853", true},
   198  			{"45.11.45.11:853", true},
   199  			{"https://doh.sb/dns-query", true},
   200  		},
   201  		"dns.google": {
   202  			{"8.8.8.8:853", true},
   203  			{"8.8.4.4:853", true},
   204  			{"https://dns.google/resolve", true},
   205  			{"https://8.8.8.8/resolve", true},
   206  			{"https://8.8.4.4/resolve", true},
   207  		},
   208  		"cloudflare-dns.com": {
   209  			{"1.1.1.1:853", true},
   210  			{"1.0.0.1:853", true},
   211  			{"https://cloudflare-dns.com/dns-query", true},
   212  			{"https://1.1.1.1/dns-query", true},
   213  			{"https://1.0.0.1/dns-query", true},
   214  		},
   215  		"dns.opendns.com": {
   216  			{"208.67.222.222:853", true},
   217  			{"208.67.220.220:853", true},
   218  		},
   219  		"dns10.quad9.net": {
   220  			{"9.9.9.10:853", true},
   221  			{"149.112.112.10:853", true},
   222  		},
   223  	},
   224  	b: map[string][]string{},
   225  }
   226  
   227  var DefaultResolver = &net.Resolver{
   228  	PreferGo: true,
   229  	Dial: func(ctx context.Context, _, _ string) (net.Conn, error) {
   230  		if ip.IsIPv6Available.Get() {
   231  			return IPv6Servers.DialContext(ctx, nil, terasu.DefaultFirstFragmentLen)
   232  		}
   233  		return IPv4Servers.DialContext(ctx, nil, terasu.DefaultFirstFragmentLen)
   234  	},
   235  }