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 }