Feature: resolve ip with proxy adapter
This commit is contained in:
@@ -15,10 +15,11 @@ import (
|
||||
|
||||
type client struct {
|
||||
*D.Client
|
||||
r *Resolver
|
||||
port string
|
||||
host string
|
||||
iface string
|
||||
r *Resolver
|
||||
port string
|
||||
host string
|
||||
iface string
|
||||
proxyAdapter string
|
||||
}
|
||||
|
||||
func (c *client) Exchange(m *D.Msg) (*D.Msg, error) {
|
||||
@@ -30,14 +31,15 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
|
||||
ip net.IP
|
||||
err error
|
||||
)
|
||||
if c.r == nil {
|
||||
// a default ip dns
|
||||
if ip = net.ParseIP(c.host); ip == nil {
|
||||
|
||||
if ip = net.ParseIP(c.host); ip == nil {
|
||||
if c.r == nil {
|
||||
return nil, fmt.Errorf("dns %s not a valid ip", c.host)
|
||||
}
|
||||
} else {
|
||||
if ip, err = resolver.ResolveIPWithResolver(c.host, c.r); err != nil {
|
||||
return nil, fmt.Errorf("use default dns resolve failed: %w", err)
|
||||
} else {
|
||||
if ip, err = resolver.ResolveIPWithResolver(c.host, c.r); err != nil {
|
||||
return nil, fmt.Errorf("use default dns resolve failed: %w", err)
|
||||
}
|
||||
c.host = ip.String()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,11 +48,17 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
|
||||
network = "tcp"
|
||||
}
|
||||
|
||||
options := []dialer.Option{}
|
||||
if c.iface != "" {
|
||||
options = append(options, dialer.WithInterface(c.iface))
|
||||
var conn net.Conn
|
||||
if c.proxyAdapter != "" && network == "tcp" {
|
||||
conn, err = dialContextWithProxyAdapter(ctx, c.proxyAdapter, ip, c.port)
|
||||
} else {
|
||||
options := []dialer.Option{}
|
||||
if c.iface != "" {
|
||||
options = append(options, dialer.WithInterface(c.iface))
|
||||
}
|
||||
conn, err = dialer.DialContext(ctx, network, net.JoinHostPort(c.host, c.port), options...)
|
||||
}
|
||||
conn, err := dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), c.port), options...)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
20
dns/doh.go
20
dns/doh.go
@@ -19,8 +19,9 @@ const (
|
||||
)
|
||||
|
||||
type dohClient struct {
|
||||
url string
|
||||
transport *http.Transport
|
||||
url string
|
||||
proxyAdapter string
|
||||
transport *http.Transport
|
||||
}
|
||||
|
||||
func (dc *dohClient) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
||||
@@ -62,7 +63,7 @@ func (dc *dohClient) newRequest(m *D.Msg) (*http.Request, error) {
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (dc *dohClient) doRequest(req *http.Request) (msg *D.Msg, err error) {
|
||||
func (dc *dohClient) doRequest(req *http.Request) (*D.Msg, error) {
|
||||
client := &http.Client{Transport: dc.transport}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
@@ -74,14 +75,15 @@ func (dc *dohClient) doRequest(req *http.Request) (msg *D.Msg, err error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msg = &D.Msg{}
|
||||
msg := &D.Msg{}
|
||||
err = msg.Unpack(buf)
|
||||
return msg, err
|
||||
}
|
||||
|
||||
func newDoHClient(url string, r *Resolver) *dohClient {
|
||||
func newDoHClient(url string, r *Resolver, proxyAdapter string) *dohClient {
|
||||
return &dohClient{
|
||||
url: url,
|
||||
url: url,
|
||||
proxyAdapter: proxyAdapter,
|
||||
transport: &http.Transport{
|
||||
ForceAttemptHTTP2: true,
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
@@ -95,7 +97,11 @@ func newDoHClient(url string, r *Resolver) *dohClient {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dialer.DialContext(ctx, "tcp", net.JoinHostPort(ip.String(), port))
|
||||
if proxyAdapter == "" {
|
||||
return dialer.DialContext(ctx, "tcp", net.JoinHostPort(ip.String(), port))
|
||||
} else {
|
||||
return dialContextWithProxyAdapter(ctx, proxyAdapter, ip, port)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/Dreamacro/clash/component/geodata/router"
|
||||
"github.com/Dreamacro/clash/component/mmdb"
|
||||
"github.com/Dreamacro/clash/component/trie"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
@@ -49,3 +50,16 @@ func NewDomainFilter(domains []string) *domainFilter {
|
||||
func (df *domainFilter) Match(domain string) bool {
|
||||
return df.tree.Search(domain) != nil
|
||||
}
|
||||
|
||||
type geoSiteFilter struct {
|
||||
matchers []*router.DomainMatcher
|
||||
}
|
||||
|
||||
func (gsf *geoSiteFilter) Match(domain string) bool {
|
||||
for _, matcher := range gsf.matchers {
|
||||
if matcher.ApplyDomain(domain) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/Dreamacro/clash/common/cache"
|
||||
"github.com/Dreamacro/clash/common/picker"
|
||||
"github.com/Dreamacro/clash/component/fakeip"
|
||||
"github.com/Dreamacro/clash/component/geodata/router"
|
||||
"github.com/Dreamacro/clash/component/resolver"
|
||||
"github.com/Dreamacro/clash/component/trie"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
@@ -149,7 +150,7 @@ func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.M
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Resolver) batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) {
|
||||
func (r *Resolver) batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (*D.Msg, error) {
|
||||
fast, ctx := picker.WithTimeout(ctx, resolver.DefaultDNSTimeout)
|
||||
for _, client := range clients {
|
||||
r := client
|
||||
@@ -173,8 +174,8 @@ func (r *Resolver) batchExchange(ctx context.Context, clients []dnsClient, m *D.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msg = elm.(*D.Msg)
|
||||
return
|
||||
msg := elm.(*D.Msg)
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
func (r *Resolver) matchPolicy(m *D.Msg) []dnsClient {
|
||||
@@ -215,7 +216,7 @@ func (r *Resolver) shouldOnlyQueryFallback(m *D.Msg) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *Resolver) ipExchange(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
||||
func (r *Resolver) ipExchange(ctx context.Context, m *D.Msg) (*D.Msg, error) {
|
||||
if matched := r.matchPolicy(m); len(matched) != 0 {
|
||||
res := <-r.asyncExchange(ctx, matched, m)
|
||||
return res.Msg, res.Error
|
||||
@@ -230,27 +231,22 @@ func (r *Resolver) ipExchange(ctx context.Context, m *D.Msg) (msg *D.Msg, err er
|
||||
|
||||
msgCh := r.asyncExchange(ctx, r.main, m)
|
||||
|
||||
if r.fallback == nil { // directly return if no fallback servers are available
|
||||
if r.fallback == nil || len(r.fallback) == 0 { // directly return if no fallback servers are available
|
||||
res := <-msgCh
|
||||
msg, err = res.Msg, res.Error
|
||||
return
|
||||
return res.Msg, res.Error
|
||||
}
|
||||
|
||||
fallbackMsg := r.asyncExchange(ctx, r.fallback, m)
|
||||
res := <-msgCh
|
||||
if res.Error == nil {
|
||||
if ips := msgToIP(res.Msg); len(ips) != 0 {
|
||||
if !r.shouldIPFallback(ips[0]) {
|
||||
msg = res.Msg // no need to wait for fallback result
|
||||
err = res.Error
|
||||
return msg, err
|
||||
return res.Msg, res.Error // no need to wait for fallback result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res = <-fallbackMsg
|
||||
msg, err = res.Msg, res.Error
|
||||
return
|
||||
res = <-r.asyncExchange(ctx, r.fallback, m)
|
||||
return res.Msg, res.Error
|
||||
}
|
||||
|
||||
func (r *Resolver) resolveIP(host string, dnsType uint16) (ip net.IP, err error) {
|
||||
@@ -302,9 +298,10 @@ func (r *Resolver) asyncExchange(ctx context.Context, client []dnsClient, msg *D
|
||||
}
|
||||
|
||||
type NameServer struct {
|
||||
Net string
|
||||
Addr string
|
||||
Interface string
|
||||
Net string
|
||||
Addr string
|
||||
Interface string
|
||||
ProxyAdapter string
|
||||
}
|
||||
|
||||
type FallbackFilter struct {
|
||||
@@ -312,6 +309,7 @@ type FallbackFilter struct {
|
||||
GeoIPCode string
|
||||
IPCIDR []*net.IPNet
|
||||
Domain []string
|
||||
GeoSite []*router.DomainMatcher
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
@@ -360,10 +358,27 @@ func NewResolver(config Config) *Resolver {
|
||||
}
|
||||
r.fallbackIPFilters = fallbackIPFilters
|
||||
|
||||
fallbackDomainFilters := []fallbackDomainFilter{}
|
||||
if len(config.FallbackFilter.Domain) != 0 {
|
||||
fallbackDomainFilters := []fallbackDomainFilter{NewDomainFilter(config.FallbackFilter.Domain)}
|
||||
r.fallbackDomainFilters = fallbackDomainFilters
|
||||
fallbackDomainFilters = append(fallbackDomainFilters, NewDomainFilter(config.FallbackFilter.Domain))
|
||||
}
|
||||
|
||||
if len(config.FallbackFilter.GeoSite) != 0 {
|
||||
fallbackDomainFilters = append(fallbackDomainFilters, &geoSiteFilter{
|
||||
matchers: config.FallbackFilter.GeoSite,
|
||||
})
|
||||
}
|
||||
r.fallbackDomainFilters = fallbackDomainFilters
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func NewMainResolver(old *Resolver) *Resolver {
|
||||
r := &Resolver{
|
||||
ipv6: old.ipv6,
|
||||
main: old.main,
|
||||
lruCache: old.lruCache,
|
||||
hosts: old.hosts,
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
38
dns/util.go
38
dns/util.go
@@ -1,12 +1,16 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/common/cache"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
)
|
||||
@@ -51,7 +55,7 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient {
|
||||
for _, s := range servers {
|
||||
switch s.Net {
|
||||
case "https":
|
||||
ret = append(ret, newDoHClient(s.Addr, resolver))
|
||||
ret = append(ret, newDoHClient(s.Addr, resolver, s.ProxyAdapter))
|
||||
continue
|
||||
case "dhcp":
|
||||
ret = append(ret, newDHCPClient(s.Addr))
|
||||
@@ -70,10 +74,11 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient {
|
||||
UDPSize: 4096,
|
||||
Timeout: 5 * time.Second,
|
||||
},
|
||||
port: port,
|
||||
host: host,
|
||||
iface: s.Interface,
|
||||
r: resolver,
|
||||
port: port,
|
||||
host: host,
|
||||
iface: s.Interface,
|
||||
r: resolver,
|
||||
proxyAdapter: s.ProxyAdapter,
|
||||
})
|
||||
}
|
||||
return ret
|
||||
@@ -104,3 +109,26 @@ func msgToIP(msg *D.Msg) []net.IP {
|
||||
|
||||
return ips
|
||||
}
|
||||
|
||||
func dialContextWithProxyAdapter(ctx context.Context, adapterName string, dstIP net.IP, port string) (net.Conn, error) {
|
||||
adapter, ok := tunnel.Proxies()[adapterName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("proxy dapter [%s] not found", adapterName)
|
||||
}
|
||||
|
||||
addrType := C.AtypIPv4
|
||||
|
||||
if dstIP.To4() == nil {
|
||||
addrType = C.AtypIPv6
|
||||
}
|
||||
|
||||
metadata := &C.Metadata{
|
||||
NetWork: C.TCP,
|
||||
AddrType: addrType,
|
||||
Host: "",
|
||||
DstIP: dstIP,
|
||||
DstPort: port,
|
||||
}
|
||||
|
||||
return adapter.DialContext(ctx, metadata)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user