Feature: resolve ip with a proxy adapter
This commit is contained in:
parent
b192238699
commit
d876d6e74c
14 changed files with 357 additions and 101 deletions
48
README.md
48
README.md
|
@ -36,6 +36,37 @@
|
|||
Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash/wiki).
|
||||
|
||||
## 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
|
||||
Supports macOS, Linux and Windows.
|
||||
|
||||
|
@ -86,7 +117,7 @@ rules:
|
|||
#- GEOIP,!cn,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
|
||||
```
|
||||
|
@ -103,22 +134,9 @@ proxies:
|
|||
port: 443
|
||||
uuid: uuid
|
||||
network: tcp
|
||||
servername: example.com # AKA SNI
|
||||
servername: example.com
|
||||
# flow: xtls-rprx-direct # xtls-rprx-origin # enable XTLS
|
||||
# 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
|
||||
|
|
|
@ -9,6 +9,19 @@ import (
|
|||
)
|
||||
|
||||
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 {
|
||||
case "tcp4", "tcp6", "udp4", "udp6":
|
||||
host, port, err := net.SplitHostPort(address)
|
||||
|
@ -19,17 +32,25 @@ func DialContext(ctx context.Context, network, address string, options ...Option
|
|||
var ip net.IP
|
||||
switch network {
|
||||
case "tcp4", "udp4":
|
||||
ip, err = resolver.ResolveIPv4(host)
|
||||
if opt.interfaceName != "" {
|
||||
ip, err = resolver.ResolveIPv4WithMain(host)
|
||||
} else {
|
||||
ip, err = resolver.ResolveIPv4(host)
|
||||
}
|
||||
default:
|
||||
ip, err = resolver.ResolveIPv6(host)
|
||||
if opt.interfaceName != "" {
|
||||
ip, err = resolver.ResolveIPv6WithMain(host)
|
||||
} else {
|
||||
ip, err = resolver.ResolveIPv6(host)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dialContext(ctx, network, ip, port, options)
|
||||
return dialContext(ctx, network, ip, port, opt)
|
||||
case "tcp", "udp":
|
||||
return dualStackDialContext(ctx, network, address, options)
|
||||
return dualStackDialContext(ctx, network, address, opt)
|
||||
default:
|
||||
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)
|
||||
}
|
||||
|
||||
func dialContext(ctx context.Context, network string, destination net.IP, port 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)
|
||||
}
|
||||
|
||||
func dialContext(ctx context.Context, network string, destination net.IP, port string, opt *option) (net.Conn, error) {
|
||||
dialer := &net.Dialer{}
|
||||
if opt.interfaceName != "" {
|
||||
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))
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -127,16 +135,24 @@ func dualStackDialContext(ctx context.Context, network, address string, options
|
|||
|
||||
var ip net.IP
|
||||
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 {
|
||||
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 {
|
||||
return
|
||||
}
|
||||
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)
|
||||
|
|
30
component/geodata/utils.go
Normal file
30
component/geodata/utils.go
Normal 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)
|
||||
mph:minimal perfect hash algorithm
|
||||
*/
|
||||
matcher, err := router.NewMphMatcherGroup(domains)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return matcher, len(domains), nil
|
||||
}
|
|
@ -15,6 +15,9 @@ var (
|
|||
// DefaultResolver aim to resolve ip
|
||||
DefaultResolver Resolver
|
||||
|
||||
// MainResolver resolve ip with main domain server
|
||||
MainResolver Resolver
|
||||
|
||||
// DisableIPv6 means don't resolve ipv6 host
|
||||
// default value is true
|
||||
DisableIPv6 = true
|
||||
|
@ -40,6 +43,14 @@ type Resolver interface {
|
|||
|
||||
// ResolveIPv4 with a host, return ipv4
|
||||
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 ip := node.Data.(net.IP).To4(); ip != nil {
|
||||
return ip, nil
|
||||
|
@ -54,8 +65,8 @@ func ResolveIPv4(host string) (net.IP, error) {
|
|||
return nil, ErrIPVersion
|
||||
}
|
||||
|
||||
if DefaultResolver != nil {
|
||||
return DefaultResolver.ResolveIPv4(host)
|
||||
if r != nil {
|
||||
return r.ResolveIPv4(host)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), DefaultDNSTimeout)
|
||||
|
@ -72,6 +83,14 @@ func ResolveIPv4(host string) (net.IP, error) {
|
|||
|
||||
// ResolveIPv6 with a host, return ipv6
|
||||
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 {
|
||||
return nil, ErrIPv6Disabled
|
||||
}
|
||||
|
@ -90,8 +109,8 @@ func ResolveIPv6(host string) (net.IP, error) {
|
|||
return nil, ErrIPVersion
|
||||
}
|
||||
|
||||
if DefaultResolver != nil {
|
||||
return DefaultResolver.ResolveIPv6(host)
|
||||
if r != nil {
|
||||
return r.ResolveIPv6(host)
|
||||
}
|
||||
|
||||
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) {
|
||||
return ResolveIPWithResolver(host, DefaultResolver)
|
||||
}
|
||||
|
||||
// ResolveIPWithMainResolver with a host, use main resolver, return ip
|
||||
func ResolveIPWithMainResolver(host string) (net.IP, error) {
|
||||
return ResolveIPWithResolver(host, MainResolver)
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@ import (
|
|||
"github.com/Dreamacro/clash/adapter/provider"
|
||||
"github.com/Dreamacro/clash/component/auth"
|
||||
"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"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
providerTypes "github.com/Dreamacro/clash/constant/provider"
|
||||
|
@ -74,10 +76,11 @@ type DNS struct {
|
|||
|
||||
// FallbackFilter config
|
||||
type FallbackFilter struct {
|
||||
GeoIP bool `yaml:"geoip"`
|
||||
GeoIPCode string `yaml:"geoip-code"`
|
||||
IPCIDR []*net.IPNet `yaml:"ipcidr"`
|
||||
Domain []string `yaml:"domain"`
|
||||
GeoIP bool `yaml:"geoip"`
|
||||
GeoIPCode string `yaml:"geoip-code"`
|
||||
IPCIDR []*net.IPNet `yaml:"ipcidr"`
|
||||
Domain []string `yaml:"domain"`
|
||||
GeoSite []*router.DomainMatcher `yaml:"geosite"`
|
||||
}
|
||||
|
||||
// Profile config
|
||||
|
@ -131,6 +134,7 @@ type RawFallbackFilter struct {
|
|||
GeoIPCode string `yaml:"geoip-code"`
|
||||
IPCIDR []string `yaml:"ipcidr"`
|
||||
Domain []string `yaml:"domain"`
|
||||
GeoSite []string `yaml:"geosite"`
|
||||
}
|
||||
|
||||
type RawConfig struct {
|
||||
|
@ -198,6 +202,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
|||
GeoIP: true,
|
||||
GeoIPCode: "CN",
|
||||
IPCIDR: []string{},
|
||||
GeoSite: []string{},
|
||||
},
|
||||
DefaultNameserver: []string{
|
||||
"114.114.114.114",
|
||||
|
@ -247,7 +252,7 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
|||
}
|
||||
config.Hosts = hosts
|
||||
|
||||
dnsCfg, err := parseDNS(rawCfg, hosts)
|
||||
dnsCfg, err := parseDNS(rawCfg, hosts, rules)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -533,8 +538,9 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
|
|||
nameservers = append(
|
||||
nameservers,
|
||||
dns.NameServer{
|
||||
Net: dnsNetType,
|
||||
Addr: addr,
|
||||
Net: dnsNetType,
|
||||
Addr: addr,
|
||||
ProxyAdapter: u.Fragment,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@ -572,7 +578,37 @@ func parseFallbackIPCIDR(ips []string) ([]*net.IPNet, error) {
|
|||
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
|
||||
if cfg.Enable && len(cfg.NameServer) == 0 {
|
||||
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,
|
||||
EnhancedMode: cfg.EnhancedMode,
|
||||
FallbackFilter: FallbackFilter{
|
||||
IPCIDR: []*net.IPNet{},
|
||||
IPCIDR: []*net.IPNet{},
|
||||
GeoSite: []*router.DomainMatcher{},
|
||||
},
|
||||
}
|
||||
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{
|
||||
IPNet: ipnet,
|
||||
Size: 1000,
|
||||
|
@ -642,12 +691,19 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie) (*DNS, error) {
|
|||
dnsCfg.FakeIPRange = pool
|
||||
}
|
||||
|
||||
dnsCfg.FallbackFilter.GeoIP = cfg.FallbackFilter.GeoIP
|
||||
dnsCfg.FallbackFilter.GeoIPCode = cfg.FallbackFilter.GeoIPCode
|
||||
if fallbackip, err := parseFallbackIPCIDR(cfg.FallbackFilter.IPCIDR); err == nil {
|
||||
dnsCfg.FallbackFilter.IPCIDR = fallbackip
|
||||
if len(cfg.Fallback) != 0 {
|
||||
dnsCfg.FallbackFilter.GeoIP = cfg.FallbackFilter.GeoIP
|
||||
dnsCfg.FallbackFilter.GeoIPCode = cfg.FallbackFilter.GeoIPCode
|
||||
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 {
|
||||
dnsCfg.Hosts = hosts
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
package constant
|
||||
|
||||
import "net"
|
||||
|
||||
// Rule Type
|
||||
const (
|
||||
Domain RuleType = iota
|
||||
|
@ -56,5 +54,3 @@ type Rule interface {
|
|||
ShouldResolveIP() bool
|
||||
RuleExtra() *RuleExtra
|
||||
}
|
||||
|
||||
var TunBroadcastAddr = net.IPv4(198, 18, 255, 255)
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
package constant
|
||||
|
||||
import "net"
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/Dreamacro/clash/component/geodata/router"
|
||||
)
|
||||
|
||||
var TunBroadcastAddr = net.IPv4(198, 18, 255, 255)
|
||||
|
||||
type RuleExtra struct {
|
||||
Network NetWork
|
||||
|
@ -23,3 +29,7 @@ func (re *RuleExtra) NotMatchSourceIP(srcIP net.IP) bool {
|
|||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type RuleGeoSite interface {
|
||||
GetDomainMatcher() *router.DomainMatcher
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
@ -50,7 +51,14 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
|
|||
if 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 {
|
||||
return nil, err
|
||||
}
|
||||
|
|
16
dns/doh.go
16
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) {
|
||||
|
@ -79,9 +80,10 @@ func (dc *dohClient) doRequest(req *http.Request) (msg *D.Msg, err error) {
|
|||
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, "tcp", 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"
|
||||
|
@ -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)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
fallbackMsg := r.asyncExchange(ctx, r.fallback, m)
|
||||
res := <-msgCh
|
||||
if res.Error == nil {
|
||||
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
|
||||
return
|
||||
}
|
||||
|
@ -302,9 +302,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 +313,7 @@ type FallbackFilter struct {
|
|||
GeoIPCode string
|
||||
IPCIDR []*net.IPNet
|
||||
Domain []string
|
||||
GeoSite []*router.DomainMatcher
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
|
@ -360,10 +362,28 @@ 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,
|
||||
policy: old.policy,
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
|
76
dns/util.go
76
dns/util.go
|
@ -1,12 +1,17 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"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/tunnel"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
)
|
||||
|
@ -51,7 +56,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 +75,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 +110,63 @@ func msgToIP(msg *D.Msg) []net.IP {
|
|||
|
||||
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...)
|
||||
}
|
||||
|
|
|
@ -116,6 +116,7 @@ func updateExperimental(c *config.Config) {}
|
|||
func updateDNS(c *config.DNS, general *config.General) {
|
||||
if !c.Enable {
|
||||
resolver.DefaultResolver = nil
|
||||
resolver.MainResolver = nil
|
||||
resolver.DefaultHostMapper = nil
|
||||
dns.ReCreateServer("", nil, nil)
|
||||
return
|
||||
|
@ -133,12 +134,14 @@ func updateDNS(c *config.DNS, general *config.General) {
|
|||
GeoIPCode: c.FallbackFilter.GeoIPCode,
|
||||
IPCIDR: c.FallbackFilter.IPCIDR,
|
||||
Domain: c.FallbackFilter.Domain,
|
||||
GeoSite: c.FallbackFilter.GeoSite,
|
||||
},
|
||||
Default: c.DefaultNameserver,
|
||||
Policy: c.NameServerPolicy,
|
||||
}
|
||||
|
||||
r := dns.NewResolver(cfg)
|
||||
mr := dns.NewMainResolver(r)
|
||||
m := dns.NewEnhancer(cfg)
|
||||
|
||||
// reuse cache of old host mapper
|
||||
|
@ -147,6 +150,7 @@ func updateDNS(c *config.DNS, general *config.General) {
|
|||
}
|
||||
|
||||
resolver.DefaultResolver = r
|
||||
resolver.MainResolver = mr
|
||||
resolver.DefaultHostMapper = m
|
||||
if general.Tun.Enable && strings.EqualFold(general.Tun.Stack, "system") {
|
||||
resolver.DefaultLocalServer = dns.NewLocalServer(r, m)
|
||||
|
|
|
@ -46,29 +46,17 @@ func (gs *GEOSITE) RuleExtra() *C.RuleExtra {
|
|||
return gs.ruleExtra
|
||||
}
|
||||
|
||||
func (gs *GEOSITE) GetDomainMatcher() *router.DomainMatcher {
|
||||
return gs.matcher
|
||||
}
|
||||
|
||||
func NewGEOSITE(country string, adapter string, ruleExtra *C.RuleExtra) (*GEOSITE, error) {
|
||||
geoLoaderName := "standard"
|
||||
geoLoader, err := geodata.GetGeoDataLoader(geoLoaderName)
|
||||
matcher, recordsCount, err := geodata.LoadGeoSiteMatcher(country)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load GeoSite data error, %s", err.Error())
|
||||
}
|
||||
|
||||
domains, err := geoLoader.LoadGeoSite(country)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load GeoSite data error, %s", err.Error())
|
||||
}
|
||||
|
||||
/**
|
||||
linear: linear algorithm
|
||||
matcher, err := router.NewDomainMatcher(domains)
|
||||
mph:minimal 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))
|
||||
log.Infoln("Start initial GeoSite rule %s => %s, records: %d", country, adapter, recordsCount)
|
||||
|
||||
geoSite := &GEOSITE{
|
||||
country: country,
|
||||
|
|
Loading…
Reference in a new issue