Feature: add default-nameserver and outbound interface
This commit is contained in:
@ -2,6 +2,7 @@ package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
|
||||
@ -10,7 +11,9 @@ import (
|
||||
|
||||
type client struct {
|
||||
*D.Client
|
||||
Address string
|
||||
r *Resolver
|
||||
addr string
|
||||
host string
|
||||
}
|
||||
|
||||
func (c *client) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
||||
@ -18,7 +21,22 @@ func (c *client) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
||||
}
|
||||
|
||||
func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
||||
c.Client.Dialer = dialer.Dialer()
|
||||
network := "udp"
|
||||
if strings.HasPrefix(c.Client.Net, "tcp") {
|
||||
network = "tcp"
|
||||
}
|
||||
|
||||
ip, err := c.r.ResolveIPv4(c.host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d := dialer.Dialer()
|
||||
if dialer.DialHook != nil {
|
||||
dialer.DialHook(d, network, ip)
|
||||
}
|
||||
|
||||
c.Client.Dialer = d
|
||||
|
||||
// miekg/dns ExchangeContext doesn't respond to context cancel.
|
||||
// this is a workaround
|
||||
@ -28,7 +46,7 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err
|
||||
}
|
||||
ch := make(chan result, 1)
|
||||
go func() {
|
||||
msg, _, err := c.Client.ExchangeContext(ctx, m, c.Address)
|
||||
msg, _, err := c.Client.Exchange(m, c.addr)
|
||||
ch <- result{msg, err}
|
||||
}()
|
||||
|
||||
|
33
dns/doh.go
33
dns/doh.go
@ -5,6 +5,7 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/Dreamacro/clash/component/dialer"
|
||||
@ -17,13 +18,9 @@ const (
|
||||
dotMimeType = "application/dns-message"
|
||||
)
|
||||
|
||||
var dohTransport = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{ClientSessionCache: globalSessionCache},
|
||||
DialContext: dialer.DialContext,
|
||||
}
|
||||
|
||||
type dohClient struct {
|
||||
url string
|
||||
url string
|
||||
transport *http.Transport
|
||||
}
|
||||
|
||||
func (dc *dohClient) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
||||
@ -58,7 +55,7 @@ func (dc *dohClient) newRequest(m *D.Msg) (*http.Request, error) {
|
||||
}
|
||||
|
||||
func (dc *dohClient) doRequest(req *http.Request) (msg *D.Msg, err error) {
|
||||
client := &http.Client{Transport: dohTransport}
|
||||
client := &http.Client{Transport: dc.transport}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -73,3 +70,25 @@ func (dc *dohClient) doRequest(req *http.Request) (msg *D.Msg, err error) {
|
||||
err = msg.Unpack(buf)
|
||||
return msg, err
|
||||
}
|
||||
|
||||
func newDoHClient(url string, r *Resolver) *dohClient {
|
||||
return &dohClient{
|
||||
url: url,
|
||||
transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{ClientSessionCache: globalSessionCache},
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
host, port, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ip, err := r.ResolveIPv4(host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dialer.DialContext(ctx, "tcp4", net.JoinHostPort(ip.String(), port))
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
106
dns/iputil.go
106
dns/iputil.go
@ -1,106 +0,0 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
errIPNotFound = errors.New("couldn't find ip")
|
||||
errIPVersion = errors.New("ip version error")
|
||||
)
|
||||
|
||||
// ResolveIPv4 with a host, return ipv4
|
||||
func ResolveIPv4(host string) (net.IP, error) {
|
||||
if node := DefaultHosts.Search(host); node != nil {
|
||||
if ip := node.Data.(net.IP).To4(); ip != nil {
|
||||
return ip, nil
|
||||
}
|
||||
}
|
||||
|
||||
ip := net.ParseIP(host)
|
||||
if ip != nil {
|
||||
if !strings.Contains(host, ":") {
|
||||
return ip, nil
|
||||
}
|
||||
return nil, errIPVersion
|
||||
}
|
||||
|
||||
if DefaultResolver != nil {
|
||||
return DefaultResolver.ResolveIPv4(host)
|
||||
}
|
||||
|
||||
ipAddrs, err := net.LookupIP(host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, ip := range ipAddrs {
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
return ip4, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errIPNotFound
|
||||
}
|
||||
|
||||
// ResolveIPv6 with a host, return ipv6
|
||||
func ResolveIPv6(host string) (net.IP, error) {
|
||||
if node := DefaultHosts.Search(host); node != nil {
|
||||
if ip := node.Data.(net.IP).To16(); ip != nil {
|
||||
return ip, nil
|
||||
}
|
||||
}
|
||||
|
||||
ip := net.ParseIP(host)
|
||||
if ip != nil {
|
||||
if strings.Contains(host, ":") {
|
||||
return ip, nil
|
||||
}
|
||||
return nil, errIPVersion
|
||||
}
|
||||
|
||||
if DefaultResolver != nil {
|
||||
return DefaultResolver.ResolveIPv6(host)
|
||||
}
|
||||
|
||||
ipAddrs, err := net.LookupIP(host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, ip := range ipAddrs {
|
||||
if ip.To4() == nil {
|
||||
return ip, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errIPNotFound
|
||||
}
|
||||
|
||||
// ResolveIP with a host, return ip
|
||||
func ResolveIP(host string) (net.IP, error) {
|
||||
if node := DefaultHosts.Search(host); node != nil {
|
||||
return node.Data.(net.IP), nil
|
||||
}
|
||||
|
||||
if DefaultResolver != nil {
|
||||
if DefaultResolver.ipv6 {
|
||||
return DefaultResolver.ResolveIP(host)
|
||||
}
|
||||
return DefaultResolver.ResolveIPv4(host)
|
||||
}
|
||||
|
||||
ip := net.ParseIP(host)
|
||||
if ip != nil {
|
||||
return ip, nil
|
||||
}
|
||||
|
||||
ipAddr, err := net.ResolveIPAddr("ip", host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ipAddr.IP, nil
|
||||
}
|
@ -11,26 +11,18 @@ import (
|
||||
|
||||
"github.com/Dreamacro/clash/common/cache"
|
||||
"github.com/Dreamacro/clash/common/picker"
|
||||
trie "github.com/Dreamacro/clash/component/domain-trie"
|
||||
"github.com/Dreamacro/clash/component/fakeip"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
"golang.org/x/sync/singleflight"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultResolver aim to resolve ip
|
||||
DefaultResolver *Resolver
|
||||
|
||||
// DefaultHosts aim to resolve hosts
|
||||
DefaultHosts = trie.New()
|
||||
)
|
||||
|
||||
var (
|
||||
globalSessionCache = tls.NewLRUClientSessionCache(64)
|
||||
)
|
||||
|
||||
type resolver interface {
|
||||
type dnsClient interface {
|
||||
Exchange(m *D.Msg) (msg *D.Msg, err error)
|
||||
ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error)
|
||||
}
|
||||
@ -45,8 +37,8 @@ type Resolver struct {
|
||||
mapping bool
|
||||
fakeip bool
|
||||
pool *fakeip.Pool
|
||||
main []resolver
|
||||
fallback []resolver
|
||||
main []dnsClient
|
||||
fallback []dnsClient
|
||||
fallbackFilters []fallbackFilter
|
||||
group singleflight.Group
|
||||
cache *cache.Cache
|
||||
@ -74,7 +66,7 @@ func (r *Resolver) ResolveIP(host string) (ip net.IP, err error) {
|
||||
|
||||
ip, open := <-ch
|
||||
if !open {
|
||||
return nil, errIPNotFound
|
||||
return nil, resolver.ErrIPNotFound
|
||||
}
|
||||
|
||||
return ip, nil
|
||||
@ -174,7 +166,7 @@ func (r *Resolver) IsFakeIP(ip net.IP) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *Resolver) batchExchange(clients []resolver, m *D.Msg) (msg *D.Msg, err error) {
|
||||
func (r *Resolver) batchExchange(clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) {
|
||||
fast, ctx := picker.WithTimeout(context.Background(), time.Second*5)
|
||||
for _, client := range clients {
|
||||
r := client
|
||||
@ -238,7 +230,7 @@ func (r *Resolver) resolveIP(host string, dnsType uint16) (ip net.IP, err error)
|
||||
ips := r.msgToIP(msg)
|
||||
ipLength := len(ips)
|
||||
if ipLength == 0 {
|
||||
return nil, errIPNotFound
|
||||
return nil, resolver.ErrIPNotFound
|
||||
}
|
||||
|
||||
ip = ips[rand.Intn(ipLength)]
|
||||
@ -260,7 +252,7 @@ func (r *Resolver) msgToIP(msg *D.Msg) []net.IP {
|
||||
return ips
|
||||
}
|
||||
|
||||
func (r *Resolver) asyncExchange(client []resolver, msg *D.Msg) <-chan *result {
|
||||
func (r *Resolver) asyncExchange(client []dnsClient, msg *D.Msg) <-chan *result {
|
||||
ch := make(chan *result)
|
||||
go func() {
|
||||
res, err := r.batchExchange(client, msg)
|
||||
@ -272,6 +264,7 @@ func (r *Resolver) asyncExchange(client []resolver, msg *D.Msg) <-chan *result {
|
||||
type NameServer struct {
|
||||
Net string
|
||||
Addr string
|
||||
Host string
|
||||
}
|
||||
|
||||
type FallbackFilter struct {
|
||||
@ -281,6 +274,7 @@ type FallbackFilter struct {
|
||||
|
||||
type Config struct {
|
||||
Main, Fallback []NameServer
|
||||
Default []NameServer
|
||||
IPv6 bool
|
||||
EnhancedMode EnhancedMode
|
||||
FallbackFilter FallbackFilter
|
||||
@ -288,9 +282,14 @@ type Config struct {
|
||||
}
|
||||
|
||||
func New(config Config) *Resolver {
|
||||
defaultResolver := &Resolver{
|
||||
main: transform(config.Default, nil),
|
||||
cache: cache.New(time.Second * 60),
|
||||
}
|
||||
|
||||
r := &Resolver{
|
||||
ipv6: config.IPv6,
|
||||
main: transform(config.Main),
|
||||
main: transform(config.Main, defaultResolver),
|
||||
cache: cache.New(time.Second * 60),
|
||||
mapping: config.EnhancedMode == MAPPING,
|
||||
fakeip: config.EnhancedMode == FAKEIP,
|
||||
@ -298,7 +297,7 @@ func New(config Config) *Resolver {
|
||||
}
|
||||
|
||||
if len(config.Fallback) != 0 {
|
||||
r.fallback = transform(config.Fallback)
|
||||
r.fallback = transform(config.Fallback, defaultResolver)
|
||||
}
|
||||
|
||||
fallbackFilters := []fallbackFilter{}
|
||||
|
11
dns/util.go
11
dns/util.go
@ -8,9 +8,9 @@ import (
|
||||
|
||||
"github.com/Dreamacro/clash/common/cache"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -117,11 +117,11 @@ func isIPRequest(q D.Question) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func transform(servers []NameServer) []resolver {
|
||||
ret := []resolver{}
|
||||
func transform(servers []NameServer, resolver *Resolver) []dnsClient {
|
||||
ret := []dnsClient{}
|
||||
for _, s := range servers {
|
||||
if s.Net == "https" {
|
||||
ret = append(ret, &dohClient{url: s.Addr})
|
||||
ret = append(ret, newDoHClient(s.Addr, resolver))
|
||||
continue
|
||||
}
|
||||
|
||||
@ -136,7 +136,8 @@ func transform(servers []NameServer) []resolver {
|
||||
UDPSize: 4096,
|
||||
Timeout: 5 * time.Second,
|
||||
},
|
||||
Address: s.Addr,
|
||||
addr: s.Addr,
|
||||
host: s.Host,
|
||||
})
|
||||
}
|
||||
return ret
|
||||
|
Reference in New Issue
Block a user