Feature: resolve ip with a proxy adapter

This commit is contained in:
yaling888 2022-02-23 02:38:50 +08:00
parent b192238699
commit d876d6e74c
14 changed files with 357 additions and 101 deletions

View file

@ -36,6 +36,37 @@
Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash/wiki). Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash/wiki).
## Advanced usage for this branch ## Advanced usage for this branch
### DNS configuration
Support resolve ip with a proxy tunnel.
Support `geosite` with `fallback-filter`.
```yaml
dns:
enable: true
use-hosts: true
ipv6: false
enhanced-mode: fake-ip
fake-ip-range: 198.18.0.1/16
listen: 127.0.0.1:6868
default-nameserver:
- 119.29.29.29
- 114.114.114.114
nameserver:
- https://doh.pub/dns-query
- tls://223.5.5.5:853
fallback:
- 'https://1.0.0.1/dns-query#Proxy' # append the proxy adapter name to the end of DNS URL with '#' prefix.
- 'tls://8.8.4.4:853#Proxy'
fallback-filter:
geoip: false
geosite:
- gfw # `geosite` filter only use fallback server to resolve ip, prevent DNS leaks to unsafe DNS providers.
domain:
- +.example.com
ipcidr:
- 0.0.0.0/32
```
### TUN configuration ### TUN configuration
Supports macOS, Linux and Windows. Supports macOS, Linux and Windows.
@ -86,7 +117,7 @@ rules:
#- GEOIP,!cn,PROXY #- GEOIP,!cn,PROXY
# source IPCIDR condition for all rules in gateway proxy # source IPCIDR condition for all rules in gateway proxy
#- GEOIP,!cn,PROXY,192.168.1.88/32,192.168.1.99/32 #- GEOSITE,geolocation-!cn,REJECT,192.168.1.88/32,192.168.1.99/32
- MATCH,PROXY - MATCH,PROXY
``` ```
@ -103,22 +134,9 @@ proxies:
port: 443 port: 443
uuid: uuid uuid: uuid
network: tcp network: tcp
servername: example.com # AKA SNI servername: example.com
# flow: xtls-rprx-direct # xtls-rprx-origin # enable XTLS # flow: xtls-rprx-direct # xtls-rprx-origin # enable XTLS
# skip-cert-verify: true # skip-cert-verify: true
- name: "vless-ws"
type: vless
server: server
port: 443
uuid: uuid
udp: true
network: ws
servername: example.com # priority over wss host
# skip-cert-verify: true
ws-path: /path
ws-headers:
Host: example.com
``` ```
### IPTABLES auto-configuration ### IPTABLES auto-configuration

View file

@ -9,6 +9,19 @@ import (
) )
func DialContext(ctx context.Context, network, address string, options ...Option) (net.Conn, error) { func DialContext(ctx context.Context, network, address string, options ...Option) (net.Conn, error) {
opt := &option{
interfaceName: DefaultInterface.Load(),
routingMark: int(DefaultRoutingMark.Load()),
}
for _, o := range DefaultOptions {
o(opt)
}
for _, o := range options {
o(opt)
}
switch network { switch network {
case "tcp4", "tcp6", "udp4", "udp6": case "tcp4", "tcp6", "udp4", "udp6":
host, port, err := net.SplitHostPort(address) host, port, err := net.SplitHostPort(address)
@ -19,17 +32,25 @@ func DialContext(ctx context.Context, network, address string, options ...Option
var ip net.IP var ip net.IP
switch network { switch network {
case "tcp4", "udp4": case "tcp4", "udp4":
ip, err = resolver.ResolveIPv4(host) if opt.interfaceName != "" {
ip, err = resolver.ResolveIPv4WithMain(host)
} else {
ip, err = resolver.ResolveIPv4(host)
}
default: default:
ip, err = resolver.ResolveIPv6(host) if opt.interfaceName != "" {
ip, err = resolver.ResolveIPv6WithMain(host)
} else {
ip, err = resolver.ResolveIPv6(host)
}
} }
if err != nil { if err != nil {
return nil, err return nil, err
} }
return dialContext(ctx, network, ip, port, options) return dialContext(ctx, network, ip, port, opt)
case "tcp", "udp": case "tcp", "udp":
return dualStackDialContext(ctx, network, address, options) return dualStackDialContext(ctx, network, address, opt)
default: default:
return nil, errors.New("network invalid") return nil, errors.New("network invalid")
} }
@ -67,20 +88,7 @@ func ListenPacket(ctx context.Context, network, address string, options ...Optio
return lc.ListenPacket(ctx, network, address) return lc.ListenPacket(ctx, network, address)
} }
func dialContext(ctx context.Context, network string, destination net.IP, port string, options []Option) (net.Conn, error) { func dialContext(ctx context.Context, network string, destination net.IP, port string, opt *option) (net.Conn, error) {
opt := &option{
interfaceName: DefaultInterface.Load(),
routingMark: int(DefaultRoutingMark.Load()),
}
for _, o := range DefaultOptions {
o(opt)
}
for _, o := range options {
o(opt)
}
dialer := &net.Dialer{} dialer := &net.Dialer{}
if opt.interfaceName != "" { if opt.interfaceName != "" {
if err := bindIfaceToDialer(opt.interfaceName, dialer, network, destination); err != nil { if err := bindIfaceToDialer(opt.interfaceName, dialer, network, destination); err != nil {
@ -94,7 +102,7 @@ func dialContext(ctx context.Context, network string, destination net.IP, port s
return dialer.DialContext(ctx, network, net.JoinHostPort(destination.String(), port)) return dialer.DialContext(ctx, network, net.JoinHostPort(destination.String(), port))
} }
func dualStackDialContext(ctx context.Context, network, address string, options []Option) (net.Conn, error) { func dualStackDialContext(ctx context.Context, network, address string, opt *option) (net.Conn, error) {
host, port, err := net.SplitHostPort(address) host, port, err := net.SplitHostPort(address)
if err != nil { if err != nil {
return nil, err return nil, err
@ -127,16 +135,24 @@ func dualStackDialContext(ctx context.Context, network, address string, options
var ip net.IP var ip net.IP
if ipv6 { if ipv6 {
ip, result.error = resolver.ResolveIPv6(host) if opt.interfaceName != "" {
ip, result.error = resolver.ResolveIPv6WithMain(host)
} else {
ip, result.error = resolver.ResolveIPv6(host)
}
} else { } else {
ip, result.error = resolver.ResolveIPv4(host) if opt.interfaceName != "" {
ip, result.error = resolver.ResolveIPv4WithMain(host)
} else {
ip, result.error = resolver.ResolveIPv4(host)
}
} }
if result.error != nil { if result.error != nil {
return return
} }
result.resolved = true result.resolved = true
result.Conn, result.error = dialContext(ctx, network, ip, port, options) result.Conn, result.error = dialContext(ctx, network, ip, port, opt)
} }
go startRacer(ctx, network+"4", host, false) go startRacer(ctx, network+"4", host, false)

View file

@ -0,0 +1,30 @@
package geodata
import (
"github.com/Dreamacro/clash/component/geodata/router"
)
func LoadGeoSiteMatcher(countryCode string) (*router.DomainMatcher, int, error) {
geoLoaderName := "standard"
geoLoader, err := GetGeoDataLoader(geoLoaderName)
if err != nil {
return nil, 0, err
}
domains, err := geoLoader.LoadGeoSite(countryCode)
if err != nil {
return nil, 0, err
}
/**
linear: linear algorithm
matcher, err := router.NewDomainMatcher(domains)
mphminimal perfect hash algorithm
*/
matcher, err := router.NewMphMatcherGroup(domains)
if err != nil {
return nil, 0, err
}
return matcher, len(domains), nil
}

View file

@ -15,6 +15,9 @@ var (
// DefaultResolver aim to resolve ip // DefaultResolver aim to resolve ip
DefaultResolver Resolver DefaultResolver Resolver
// MainResolver resolve ip with main domain server
MainResolver Resolver
// DisableIPv6 means don't resolve ipv6 host // DisableIPv6 means don't resolve ipv6 host
// default value is true // default value is true
DisableIPv6 = true DisableIPv6 = true
@ -40,6 +43,14 @@ type Resolver interface {
// ResolveIPv4 with a host, return ipv4 // ResolveIPv4 with a host, return ipv4
func ResolveIPv4(host string) (net.IP, error) { func ResolveIPv4(host string) (net.IP, error) {
return ResolveIPv4WithResolver(host, DefaultResolver)
}
func ResolveIPv4WithMain(host string) (net.IP, error) {
return ResolveIPv4WithResolver(host, MainResolver)
}
func ResolveIPv4WithResolver(host string, r Resolver) (net.IP, error) {
if node := DefaultHosts.Search(host); node != nil { if node := DefaultHosts.Search(host); node != nil {
if ip := node.Data.(net.IP).To4(); ip != nil { if ip := node.Data.(net.IP).To4(); ip != nil {
return ip, nil return ip, nil
@ -54,8 +65,8 @@ func ResolveIPv4(host string) (net.IP, error) {
return nil, ErrIPVersion return nil, ErrIPVersion
} }
if DefaultResolver != nil { if r != nil {
return DefaultResolver.ResolveIPv4(host) return r.ResolveIPv4(host)
} }
ctx, cancel := context.WithTimeout(context.Background(), DefaultDNSTimeout) ctx, cancel := context.WithTimeout(context.Background(), DefaultDNSTimeout)
@ -72,6 +83,14 @@ func ResolveIPv4(host string) (net.IP, error) {
// ResolveIPv6 with a host, return ipv6 // ResolveIPv6 with a host, return ipv6
func ResolveIPv6(host string) (net.IP, error) { func ResolveIPv6(host string) (net.IP, error) {
return ResolveIPv6WithResolver(host, DefaultResolver)
}
func ResolveIPv6WithMain(host string) (net.IP, error) {
return ResolveIPv6WithResolver(host, MainResolver)
}
func ResolveIPv6WithResolver(host string, r Resolver) (net.IP, error) {
if DisableIPv6 { if DisableIPv6 {
return nil, ErrIPv6Disabled return nil, ErrIPv6Disabled
} }
@ -90,8 +109,8 @@ func ResolveIPv6(host string) (net.IP, error) {
return nil, ErrIPVersion return nil, ErrIPVersion
} }
if DefaultResolver != nil { if r != nil {
return DefaultResolver.ResolveIPv6(host) return r.ResolveIPv6(host)
} }
ctx, cancel := context.WithTimeout(context.Background(), DefaultDNSTimeout) ctx, cancel := context.WithTimeout(context.Background(), DefaultDNSTimeout)
@ -138,3 +157,8 @@ func ResolveIPWithResolver(host string, r Resolver) (net.IP, error) {
func ResolveIP(host string) (net.IP, error) { func ResolveIP(host string) (net.IP, error) {
return ResolveIPWithResolver(host, DefaultResolver) return ResolveIPWithResolver(host, DefaultResolver)
} }
// ResolveIPWithMainResolver with a host, use main resolver, return ip
func ResolveIPWithMainResolver(host string) (net.IP, error) {
return ResolveIPWithResolver(host, MainResolver)
}

View file

@ -15,6 +15,8 @@ import (
"github.com/Dreamacro/clash/adapter/provider" "github.com/Dreamacro/clash/adapter/provider"
"github.com/Dreamacro/clash/component/auth" "github.com/Dreamacro/clash/component/auth"
"github.com/Dreamacro/clash/component/fakeip" "github.com/Dreamacro/clash/component/fakeip"
"github.com/Dreamacro/clash/component/geodata"
"github.com/Dreamacro/clash/component/geodata/router"
"github.com/Dreamacro/clash/component/trie" "github.com/Dreamacro/clash/component/trie"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
providerTypes "github.com/Dreamacro/clash/constant/provider" providerTypes "github.com/Dreamacro/clash/constant/provider"
@ -74,10 +76,11 @@ type DNS struct {
// FallbackFilter config // FallbackFilter config
type FallbackFilter struct { type FallbackFilter struct {
GeoIP bool `yaml:"geoip"` GeoIP bool `yaml:"geoip"`
GeoIPCode string `yaml:"geoip-code"` GeoIPCode string `yaml:"geoip-code"`
IPCIDR []*net.IPNet `yaml:"ipcidr"` IPCIDR []*net.IPNet `yaml:"ipcidr"`
Domain []string `yaml:"domain"` Domain []string `yaml:"domain"`
GeoSite []*router.DomainMatcher `yaml:"geosite"`
} }
// Profile config // Profile config
@ -131,6 +134,7 @@ type RawFallbackFilter struct {
GeoIPCode string `yaml:"geoip-code"` GeoIPCode string `yaml:"geoip-code"`
IPCIDR []string `yaml:"ipcidr"` IPCIDR []string `yaml:"ipcidr"`
Domain []string `yaml:"domain"` Domain []string `yaml:"domain"`
GeoSite []string `yaml:"geosite"`
} }
type RawConfig struct { type RawConfig struct {
@ -198,6 +202,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
GeoIP: true, GeoIP: true,
GeoIPCode: "CN", GeoIPCode: "CN",
IPCIDR: []string{}, IPCIDR: []string{},
GeoSite: []string{},
}, },
DefaultNameserver: []string{ DefaultNameserver: []string{
"114.114.114.114", "114.114.114.114",
@ -247,7 +252,7 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
} }
config.Hosts = hosts config.Hosts = hosts
dnsCfg, err := parseDNS(rawCfg, hosts) dnsCfg, err := parseDNS(rawCfg, hosts, rules)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -533,8 +538,9 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
nameservers = append( nameservers = append(
nameservers, nameservers,
dns.NameServer{ dns.NameServer{
Net: dnsNetType, Net: dnsNetType,
Addr: addr, Addr: addr,
ProxyAdapter: u.Fragment,
}, },
) )
} }
@ -572,7 +578,37 @@ func parseFallbackIPCIDR(ips []string) ([]*net.IPNet, error) {
return ipNets, nil return ipNets, nil
} }
func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie) (*DNS, error) { func parseFallbackGeoSite(countries []string, rules []C.Rule) ([]*router.DomainMatcher, error) {
sites := []*router.DomainMatcher{}
for _, country := range countries {
found := false
for _, rule := range rules {
if rule.RuleType() == C.GEOSITE {
if strings.EqualFold(country, rule.Payload()) {
found = true
sites = append(sites, rule.(C.RuleGeoSite).GetDomainMatcher())
log.Infoln("Start initial GeoSite dns fallback filter from rule `%s`", country)
}
}
}
if !found {
matcher, recordsCount, err := geodata.LoadGeoSiteMatcher(country)
if err != nil {
return nil, err
}
sites = append(sites, matcher)
log.Infoln("Start initial GeoSite dns fallback filter `%s`, records: %d", country, recordsCount)
}
}
runtime.GC()
return sites, nil
}
func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie, rules []C.Rule) (*DNS, error) {
cfg := rawCfg.DNS cfg := rawCfg.DNS
if cfg.Enable && len(cfg.NameServer) == 0 { if cfg.Enable && len(cfg.NameServer) == 0 {
return nil, fmt.Errorf("if DNS configuration is turned on, NameServer cannot be empty") return nil, fmt.Errorf("if DNS configuration is turned on, NameServer cannot be empty")
@ -584,7 +620,8 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie) (*DNS, error) {
IPv6: cfg.IPv6, IPv6: cfg.IPv6,
EnhancedMode: cfg.EnhancedMode, EnhancedMode: cfg.EnhancedMode,
FallbackFilter: FallbackFilter{ FallbackFilter: FallbackFilter{
IPCIDR: []*net.IPNet{}, IPCIDR: []*net.IPNet{},
GeoSite: []*router.DomainMatcher{},
}, },
} }
var err error var err error
@ -629,6 +666,18 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie) (*DNS, error) {
} }
} }
if len(dnsCfg.Fallback) != 0 {
if host == nil {
host = trie.New()
}
for _, fb := range dnsCfg.Fallback {
if net.ParseIP(fb.Addr) != nil {
continue
}
host.Insert(fb.Addr, true)
}
}
pool, err := fakeip.New(fakeip.Options{ pool, err := fakeip.New(fakeip.Options{
IPNet: ipnet, IPNet: ipnet,
Size: 1000, Size: 1000,
@ -642,12 +691,19 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie) (*DNS, error) {
dnsCfg.FakeIPRange = pool dnsCfg.FakeIPRange = pool
} }
dnsCfg.FallbackFilter.GeoIP = cfg.FallbackFilter.GeoIP if len(cfg.Fallback) != 0 {
dnsCfg.FallbackFilter.GeoIPCode = cfg.FallbackFilter.GeoIPCode dnsCfg.FallbackFilter.GeoIP = cfg.FallbackFilter.GeoIP
if fallbackip, err := parseFallbackIPCIDR(cfg.FallbackFilter.IPCIDR); err == nil { dnsCfg.FallbackFilter.GeoIPCode = cfg.FallbackFilter.GeoIPCode
dnsCfg.FallbackFilter.IPCIDR = fallbackip if fallbackip, err := parseFallbackIPCIDR(cfg.FallbackFilter.IPCIDR); err == nil {
dnsCfg.FallbackFilter.IPCIDR = fallbackip
}
dnsCfg.FallbackFilter.Domain = cfg.FallbackFilter.Domain
fallbackGeoSite, err := parseFallbackGeoSite(cfg.FallbackFilter.GeoSite, rules)
if err != nil {
return nil, fmt.Errorf("load GeoSite dns fallback filter error, %w", err)
}
dnsCfg.FallbackFilter.GeoSite = fallbackGeoSite
} }
dnsCfg.FallbackFilter.Domain = cfg.FallbackFilter.Domain
if cfg.UseHosts { if cfg.UseHosts {
dnsCfg.Hosts = hosts dnsCfg.Hosts = hosts

View file

@ -1,7 +1,5 @@
package constant package constant
import "net"
// Rule Type // Rule Type
const ( const (
Domain RuleType = iota Domain RuleType = iota
@ -56,5 +54,3 @@ type Rule interface {
ShouldResolveIP() bool ShouldResolveIP() bool
RuleExtra() *RuleExtra RuleExtra() *RuleExtra
} }
var TunBroadcastAddr = net.IPv4(198, 18, 255, 255)

View file

@ -1,6 +1,12 @@
package constant package constant
import "net" import (
"net"
"github.com/Dreamacro/clash/component/geodata/router"
)
var TunBroadcastAddr = net.IPv4(198, 18, 255, 255)
type RuleExtra struct { type RuleExtra struct {
Network NetWork Network NetWork
@ -23,3 +29,7 @@ func (re *RuleExtra) NotMatchSourceIP(srcIP net.IP) bool {
} }
return true return true
} }
type RuleGeoSite interface {
GetDomainMatcher() *router.DomainMatcher
}

View file

@ -15,10 +15,11 @@ import (
type client struct { type client struct {
*D.Client *D.Client
r *Resolver r *Resolver
port string port string
host string host string
iface string iface string
proxyAdapter string
} }
func (c *client) Exchange(m *D.Msg) (*D.Msg, error) { func (c *client) Exchange(m *D.Msg) (*D.Msg, error) {
@ -50,7 +51,14 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
if c.iface != "" { if c.iface != "" {
options = append(options, dialer.WithInterface(c.iface)) options = append(options, dialer.WithInterface(c.iface))
} }
conn, err := dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), c.port), options...)
var conn net.Conn
if c.proxyAdapter == "" {
conn, err = dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), c.port), options...)
} else {
conn, err = dialContextWithProxyAdapter(ctx, c.proxyAdapter, network, ip, c.port, options...)
}
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -19,8 +19,9 @@ const (
) )
type dohClient struct { type dohClient struct {
url string url string
transport *http.Transport proxyAdapter string
transport *http.Transport
} }
func (dc *dohClient) Exchange(m *D.Msg) (msg *D.Msg, err error) { func (dc *dohClient) Exchange(m *D.Msg) (msg *D.Msg, err error) {
@ -79,9 +80,10 @@ func (dc *dohClient) doRequest(req *http.Request) (msg *D.Msg, err error) {
return msg, err return msg, err
} }
func newDoHClient(url string, r *Resolver) *dohClient { func newDoHClient(url string, r *Resolver, proxyAdapter string) *dohClient {
return &dohClient{ return &dohClient{
url: url, url: url,
proxyAdapter: proxyAdapter,
transport: &http.Transport{ transport: &http.Transport{
ForceAttemptHTTP2: true, ForceAttemptHTTP2: true,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { 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 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, "tcp", ip, port)
}
}, },
}, },
} }

View file

@ -4,6 +4,7 @@ import (
"net" "net"
"strings" "strings"
"github.com/Dreamacro/clash/component/geodata/router"
"github.com/Dreamacro/clash/component/mmdb" "github.com/Dreamacro/clash/component/mmdb"
"github.com/Dreamacro/clash/component/trie" "github.com/Dreamacro/clash/component/trie"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
@ -49,3 +50,16 @@ func NewDomainFilter(domains []string) *domainFilter {
func (df *domainFilter) Match(domain string) bool { func (df *domainFilter) Match(domain string) bool {
return df.tree.Search(domain) != nil 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
}

View file

@ -12,6 +12,7 @@ import (
"github.com/Dreamacro/clash/common/cache" "github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/common/picker" "github.com/Dreamacro/clash/common/picker"
"github.com/Dreamacro/clash/component/fakeip" "github.com/Dreamacro/clash/component/fakeip"
"github.com/Dreamacro/clash/component/geodata/router"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
"github.com/Dreamacro/clash/component/trie" "github.com/Dreamacro/clash/component/trie"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
@ -230,13 +231,12 @@ func (r *Resolver) ipExchange(ctx context.Context, m *D.Msg) (msg *D.Msg, err er
msgCh := r.asyncExchange(ctx, r.main, m) 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 res := <-msgCh
msg, err = res.Msg, res.Error msg, err = res.Msg, res.Error
return return
} }
fallbackMsg := r.asyncExchange(ctx, r.fallback, m)
res := <-msgCh res := <-msgCh
if res.Error == nil { if res.Error == nil {
if ips := msgToIP(res.Msg); len(ips) != 0 { if ips := msgToIP(res.Msg); len(ips) != 0 {
@ -248,7 +248,7 @@ func (r *Resolver) ipExchange(ctx context.Context, m *D.Msg) (msg *D.Msg, err er
} }
} }
res = <-fallbackMsg res = <-r.asyncExchange(ctx, r.fallback, m)
msg, err = res.Msg, res.Error msg, err = res.Msg, res.Error
return return
} }
@ -302,9 +302,10 @@ func (r *Resolver) asyncExchange(ctx context.Context, client []dnsClient, msg *D
} }
type NameServer struct { type NameServer struct {
Net string Net string
Addr string Addr string
Interface string Interface string
ProxyAdapter string
} }
type FallbackFilter struct { type FallbackFilter struct {
@ -312,6 +313,7 @@ type FallbackFilter struct {
GeoIPCode string GeoIPCode string
IPCIDR []*net.IPNet IPCIDR []*net.IPNet
Domain []string Domain []string
GeoSite []*router.DomainMatcher
} }
type Config struct { type Config struct {
@ -360,10 +362,28 @@ func NewResolver(config Config) *Resolver {
} }
r.fallbackIPFilters = fallbackIPFilters r.fallbackIPFilters = fallbackIPFilters
fallbackDomainFilters := []fallbackDomainFilter{}
if len(config.FallbackFilter.Domain) != 0 { if len(config.FallbackFilter.Domain) != 0 {
fallbackDomainFilters := []fallbackDomainFilter{NewDomainFilter(config.FallbackFilter.Domain)} fallbackDomainFilters = append(fallbackDomainFilters, NewDomainFilter(config.FallbackFilter.Domain))
r.fallbackDomainFilters = fallbackDomainFilters
} }
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,
policy: old.policy,
}
return r return r
} }

View file

@ -1,12 +1,17 @@
package dns package dns
import ( import (
"context"
"crypto/tls" "crypto/tls"
"fmt"
"net" "net"
"time" "time"
"github.com/Dreamacro/clash/common/cache" "github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/tunnel"
D "github.com/miekg/dns" D "github.com/miekg/dns"
) )
@ -51,7 +56,7 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient {
for _, s := range servers { for _, s := range servers {
switch s.Net { switch s.Net {
case "https": case "https":
ret = append(ret, newDoHClient(s.Addr, resolver)) ret = append(ret, newDoHClient(s.Addr, resolver, s.ProxyAdapter))
continue continue
case "dhcp": case "dhcp":
ret = append(ret, newDHCPClient(s.Addr)) ret = append(ret, newDHCPClient(s.Addr))
@ -70,10 +75,11 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient {
UDPSize: 4096, UDPSize: 4096,
Timeout: 5 * time.Second, Timeout: 5 * time.Second,
}, },
port: port, port: port,
host: host, host: host,
iface: s.Interface, iface: s.Interface,
r: resolver, r: resolver,
proxyAdapter: s.ProxyAdapter,
}) })
} }
return ret return ret
@ -104,3 +110,63 @@ func msgToIP(msg *D.Msg) []net.IP {
return ips return ips
} }
type wrapPacketConn struct {
net.PacketConn
rAddr net.Addr
}
func (wpc *wrapPacketConn) Read(b []byte) (n int, err error) {
n, _, err = wpc.PacketConn.ReadFrom(b)
return n, err
}
func (wpc *wrapPacketConn) Write(b []byte) (n int, err error) {
return wpc.PacketConn.WriteTo(b, wpc.rAddr)
}
func (wpc *wrapPacketConn) RemoteAddr() net.Addr {
return wpc.rAddr
}
func dialContextWithProxyAdapter(ctx context.Context, adapterName string, network string, dstIP net.IP, port string, opts ...dialer.Option) (net.Conn, error) {
adapter, ok := tunnel.Proxies()[adapterName]
if !ok {
return nil, fmt.Errorf("proxy adapter [%s] not found", adapterName)
}
networkType := C.TCP
if network == "udp" {
if !adapter.SupportUDP() {
return nil, fmt.Errorf("proxy adapter [%s] UDP is not supported", adapterName)
}
networkType = C.UDP
}
addrType := C.AtypIPv4
if dstIP.To4() == nil {
addrType = C.AtypIPv6
}
metadata := &C.Metadata{
NetWork: networkType,
AddrType: addrType,
Host: "",
DstIP: dstIP,
DstPort: port,
}
if networkType == C.UDP {
packetConn, err := adapter.ListenPacketContext(ctx, metadata, opts...)
if err != nil {
return nil, err
}
return &wrapPacketConn{
PacketConn: packetConn,
rAddr: metadata.UDPAddr(),
}, nil
}
return adapter.DialContext(ctx, metadata, opts...)
}

View file

@ -116,6 +116,7 @@ func updateExperimental(c *config.Config) {}
func updateDNS(c *config.DNS, general *config.General) { func updateDNS(c *config.DNS, general *config.General) {
if !c.Enable { if !c.Enable {
resolver.DefaultResolver = nil resolver.DefaultResolver = nil
resolver.MainResolver = nil
resolver.DefaultHostMapper = nil resolver.DefaultHostMapper = nil
dns.ReCreateServer("", nil, nil) dns.ReCreateServer("", nil, nil)
return return
@ -133,12 +134,14 @@ func updateDNS(c *config.DNS, general *config.General) {
GeoIPCode: c.FallbackFilter.GeoIPCode, GeoIPCode: c.FallbackFilter.GeoIPCode,
IPCIDR: c.FallbackFilter.IPCIDR, IPCIDR: c.FallbackFilter.IPCIDR,
Domain: c.FallbackFilter.Domain, Domain: c.FallbackFilter.Domain,
GeoSite: c.FallbackFilter.GeoSite,
}, },
Default: c.DefaultNameserver, Default: c.DefaultNameserver,
Policy: c.NameServerPolicy, Policy: c.NameServerPolicy,
} }
r := dns.NewResolver(cfg) r := dns.NewResolver(cfg)
mr := dns.NewMainResolver(r)
m := dns.NewEnhancer(cfg) m := dns.NewEnhancer(cfg)
// reuse cache of old host mapper // reuse cache of old host mapper
@ -147,6 +150,7 @@ func updateDNS(c *config.DNS, general *config.General) {
} }
resolver.DefaultResolver = r resolver.DefaultResolver = r
resolver.MainResolver = mr
resolver.DefaultHostMapper = m resolver.DefaultHostMapper = m
if general.Tun.Enable && strings.EqualFold(general.Tun.Stack, "system") { if general.Tun.Enable && strings.EqualFold(general.Tun.Stack, "system") {
resolver.DefaultLocalServer = dns.NewLocalServer(r, m) resolver.DefaultLocalServer = dns.NewLocalServer(r, m)

View file

@ -46,29 +46,17 @@ func (gs *GEOSITE) RuleExtra() *C.RuleExtra {
return gs.ruleExtra return gs.ruleExtra
} }
func (gs *GEOSITE) GetDomainMatcher() *router.DomainMatcher {
return gs.matcher
}
func NewGEOSITE(country string, adapter string, ruleExtra *C.RuleExtra) (*GEOSITE, error) { func NewGEOSITE(country string, adapter string, ruleExtra *C.RuleExtra) (*GEOSITE, error) {
geoLoaderName := "standard" matcher, recordsCount, err := geodata.LoadGeoSiteMatcher(country)
geoLoader, err := geodata.GetGeoDataLoader(geoLoaderName)
if err != nil { if err != nil {
return nil, fmt.Errorf("load GeoSite data error, %s", err.Error()) return nil, fmt.Errorf("load GeoSite data error, %s", err.Error())
} }
domains, err := geoLoader.LoadGeoSite(country) log.Infoln("Start initial GeoSite rule %s => %s, records: %d", country, adapter, recordsCount)
if err != nil {
return nil, fmt.Errorf("load GeoSite data error, %s", err.Error())
}
/**
linear: linear algorithm
matcher, err := router.NewDomainMatcher(domains)
mphminimal perfect hash algorithm
*/
matcher, err := router.NewMphMatcherGroup(domains)
if err != nil {
return nil, fmt.Errorf("load GeoSite data error, %s", err.Error())
}
log.Infoln("Start initial GeoSite rule %s => %s, records: %d", country, adapter, len(domains))
geoSite := &GEOSITE{ geoSite := &GEOSITE{
country: country, country: country,