mirror of https://github.com/Dreamacro/clash.git
Feature: add fallback filters (#105)
This commit is contained in:
parent
09f435d928
commit
b76737bdbb
|
@ -138,6 +138,10 @@ experimental:
|
||||||
# - https://1.1.1.1/dns-query # dns over https
|
# - https://1.1.1.1/dns-query # dns over https
|
||||||
# fallback: # concurrent request with nameserver, fallback used when GEOIP country isn't CN
|
# fallback: # concurrent request with nameserver, fallback used when GEOIP country isn't CN
|
||||||
# - tcp://1.1.1.1
|
# - tcp://1.1.1.1
|
||||||
|
# fallback-filter:
|
||||||
|
# geoip: true # default
|
||||||
|
# ipcidr: # ips in these subnets will be considered polluted
|
||||||
|
# - 240.0.0.0/4
|
||||||
|
|
||||||
Proxy:
|
Proxy:
|
||||||
|
|
||||||
|
|
|
@ -44,11 +44,18 @@ type DNS struct {
|
||||||
IPv6 bool `yaml:"ipv6"`
|
IPv6 bool `yaml:"ipv6"`
|
||||||
NameServer []dns.NameServer `yaml:"nameserver"`
|
NameServer []dns.NameServer `yaml:"nameserver"`
|
||||||
Fallback []dns.NameServer `yaml:"fallback"`
|
Fallback []dns.NameServer `yaml:"fallback"`
|
||||||
|
FallbackFilter FallbackFilter `yaml:"fallback-filter"`
|
||||||
Listen string `yaml:"listen"`
|
Listen string `yaml:"listen"`
|
||||||
EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"`
|
EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"`
|
||||||
FakeIPRange *fakeip.Pool
|
FakeIPRange *fakeip.Pool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FallbackFilter config
|
||||||
|
type FallbackFilter struct {
|
||||||
|
GeoIP bool `yaml:"geoip"`
|
||||||
|
IPCIDR []*net.IPNet `yaml:"ipcidr"`
|
||||||
|
}
|
||||||
|
|
||||||
// Experimental config
|
// Experimental config
|
||||||
type Experimental struct {
|
type Experimental struct {
|
||||||
IgnoreResolveFail bool `yaml:"ignore-resolve-fail"`
|
IgnoreResolveFail bool `yaml:"ignore-resolve-fail"`
|
||||||
|
@ -70,11 +77,17 @@ type rawDNS struct {
|
||||||
IPv6 bool `yaml:"ipv6"`
|
IPv6 bool `yaml:"ipv6"`
|
||||||
NameServer []string `yaml:"nameserver"`
|
NameServer []string `yaml:"nameserver"`
|
||||||
Fallback []string `yaml:"fallback"`
|
Fallback []string `yaml:"fallback"`
|
||||||
|
FallbackFilter rawFallbackFilter `yaml:"fallback-filter"`
|
||||||
Listen string `yaml:"listen"`
|
Listen string `yaml:"listen"`
|
||||||
EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"`
|
EnhancedMode dns.EnhancedMode `yaml:"enhanced-mode"`
|
||||||
FakeIPRange string `yaml:"fake-ip-range"`
|
FakeIPRange string `yaml:"fake-ip-range"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type rawFallbackFilter struct {
|
||||||
|
GeoIP bool `yaml:"geoip"`
|
||||||
|
IPCIDR []string `yaml:"ipcidr"`
|
||||||
|
}
|
||||||
|
|
||||||
type rawConfig struct {
|
type rawConfig struct {
|
||||||
Port int `yaml:"port"`
|
Port int `yaml:"port"`
|
||||||
SocksPort int `yaml:"socks-port"`
|
SocksPort int `yaml:"socks-port"`
|
||||||
|
@ -145,6 +158,10 @@ func readConfig(path string) (*rawConfig, error) {
|
||||||
DNS: rawDNS{
|
DNS: rawDNS{
|
||||||
Enable: false,
|
Enable: false,
|
||||||
FakeIPRange: "198.18.0.1/16",
|
FakeIPRange: "198.18.0.1/16",
|
||||||
|
FallbackFilter: rawFallbackFilter{
|
||||||
|
GeoIP: true,
|
||||||
|
IPCIDR: []string{},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
err = yaml.Unmarshal([]byte(data), &rawConfig)
|
err = yaml.Unmarshal([]byte(data), &rawConfig)
|
||||||
|
@ -545,6 +562,20 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
|
||||||
return nameservers, nil
|
return nameservers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseFallbackIPCIDR(ips []string) ([]*net.IPNet, error) {
|
||||||
|
ipNets := []*net.IPNet{}
|
||||||
|
|
||||||
|
for idx, ip := range ips {
|
||||||
|
_, ipnet, err := net.ParseCIDR(ip)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("DNS FallbackIP[%d] format error: %s", idx, err.Error())
|
||||||
|
}
|
||||||
|
ipNets = append(ipNets, ipnet)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ipNets, nil
|
||||||
|
}
|
||||||
|
|
||||||
func parseDNS(cfg rawDNS) (*DNS, error) {
|
func parseDNS(cfg rawDNS) (*DNS, error) {
|
||||||
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")
|
||||||
|
@ -555,6 +586,9 @@ func parseDNS(cfg rawDNS) (*DNS, error) {
|
||||||
Listen: cfg.Listen,
|
Listen: cfg.Listen,
|
||||||
IPv6: cfg.IPv6,
|
IPv6: cfg.IPv6,
|
||||||
EnhancedMode: cfg.EnhancedMode,
|
EnhancedMode: cfg.EnhancedMode,
|
||||||
|
FallbackFilter: FallbackFilter{
|
||||||
|
IPCIDR: []*net.IPNet{},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
if dnsCfg.NameServer, err = parseNameServer(cfg.NameServer); err != nil {
|
if dnsCfg.NameServer, err = parseNameServer(cfg.NameServer); err != nil {
|
||||||
|
@ -578,6 +612,11 @@ func parseDNS(cfg rawDNS) (*DNS, error) {
|
||||||
dnsCfg.FakeIPRange = pool
|
dnsCfg.FakeIPRange = pool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dnsCfg.FallbackFilter.GeoIP = cfg.FallbackFilter.GeoIP
|
||||||
|
if fallbackip, err := parseFallbackIPCIDR(cfg.FallbackFilter.IPCIDR); err == nil {
|
||||||
|
dnsCfg.FallbackFilter.IPCIDR = fallbackip
|
||||||
|
}
|
||||||
|
|
||||||
return dnsCfg, nil
|
return dnsCfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import "net"
|
||||||
|
|
||||||
|
type fallbackFilter interface {
|
||||||
|
Match(net.IP) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type geoipFilter struct{}
|
||||||
|
|
||||||
|
func (gf *geoipFilter) Match(ip net.IP) bool {
|
||||||
|
if mmdb == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
record, _ := mmdb.Country(ip)
|
||||||
|
return record.Country.IsoCode == "CN" || record.Country.IsoCode == ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type ipnetFilter struct {
|
||||||
|
ipnet *net.IPNet
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inf *ipnetFilter) Match(ip net.IP) bool {
|
||||||
|
return inf.ipnet.Contains(ip)
|
||||||
|
}
|
|
@ -50,8 +50,9 @@ type Resolver struct {
|
||||||
mapping bool
|
mapping bool
|
||||||
fakeip bool
|
fakeip bool
|
||||||
pool *fakeip.Pool
|
pool *fakeip.Pool
|
||||||
fallback []resolver
|
|
||||||
main []resolver
|
main []resolver
|
||||||
|
fallback []resolver
|
||||||
|
fallbackFilters []fallbackFilter
|
||||||
group singleflight.Group
|
group singleflight.Group
|
||||||
cache *cache.Cache
|
cache *cache.Cache
|
||||||
}
|
}
|
||||||
|
@ -94,6 +95,15 @@ func (r *Resolver) ResolveIPv6(host string) (ip net.IP, err error) {
|
||||||
return r.resolveIP(host, D.TypeAAAA)
|
return r.resolveIP(host, D.TypeAAAA)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Resolver) shouldFallback(ip net.IP) bool {
|
||||||
|
for _, filter := range r.fallbackFilters {
|
||||||
|
if filter.Match(ip) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Exchange a batch of dns request, and it use cache
|
// Exchange a batch of dns request, and it use cache
|
||||||
func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
func (r *Resolver) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
||||||
if len(m.Question) == 0 {
|
if len(m.Question) == 0 {
|
||||||
|
@ -195,13 +205,8 @@ func (r *Resolver) fallbackExchange(m *D.Msg) (msg *D.Msg, err error) {
|
||||||
fallbackMsg := r.asyncExchange(r.fallback, m)
|
fallbackMsg := r.asyncExchange(r.fallback, m)
|
||||||
res := <-msgCh
|
res := <-msgCh
|
||||||
if res.Error == nil {
|
if res.Error == nil {
|
||||||
if mmdb == nil {
|
|
||||||
return nil, errors.New("GeoIP cannot use")
|
|
||||||
}
|
|
||||||
|
|
||||||
if ips := r.msgToIP(res.Msg); len(ips) != 0 {
|
if ips := r.msgToIP(res.Msg); len(ips) != 0 {
|
||||||
if record, _ := mmdb.Country(ips[0]); record.Country.IsoCode == "CN" || record.Country.IsoCode == "" {
|
if r.shouldFallback(ips[0]) {
|
||||||
// release channel
|
|
||||||
go func() { <-fallbackMsg }()
|
go func() { <-fallbackMsg }()
|
||||||
msg = res.Msg
|
msg = res.Msg
|
||||||
return msg, err
|
return msg, err
|
||||||
|
@ -272,18 +277,20 @@ type NameServer struct {
|
||||||
Addr string
|
Addr string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FallbackFilter struct {
|
||||||
|
GeoIP bool
|
||||||
|
IPCIDR []*net.IPNet
|
||||||
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Main, Fallback []NameServer
|
Main, Fallback []NameServer
|
||||||
IPv6 bool
|
IPv6 bool
|
||||||
EnhancedMode EnhancedMode
|
EnhancedMode EnhancedMode
|
||||||
|
FallbackFilter FallbackFilter
|
||||||
Pool *fakeip.Pool
|
Pool *fakeip.Pool
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(config Config) *Resolver {
|
func New(config Config) *Resolver {
|
||||||
once.Do(func() {
|
|
||||||
mmdb, _ = geoip2.Open(C.Path.MMDB())
|
|
||||||
})
|
|
||||||
|
|
||||||
r := &Resolver{
|
r := &Resolver{
|
||||||
ipv6: config.IPv6,
|
ipv6: config.IPv6,
|
||||||
main: transform(config.Main),
|
main: transform(config.Main),
|
||||||
|
@ -292,8 +299,23 @@ func New(config Config) *Resolver {
|
||||||
fakeip: config.EnhancedMode == FAKEIP,
|
fakeip: config.EnhancedMode == FAKEIP,
|
||||||
pool: config.Pool,
|
pool: config.Pool,
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(config.Fallback) != 0 {
|
if len(config.Fallback) != 0 {
|
||||||
r.fallback = transform(config.Fallback)
|
r.fallback = transform(config.Fallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fallbackFilters := []fallbackFilter{}
|
||||||
|
if config.FallbackFilter.GeoIP {
|
||||||
|
once.Do(func() {
|
||||||
|
mmdb, _ = geoip2.Open(C.Path.MMDB())
|
||||||
|
})
|
||||||
|
|
||||||
|
fallbackFilters = append(fallbackFilters, &geoipFilter{})
|
||||||
|
}
|
||||||
|
for _, ipnet := range config.FallbackFilter.IPCIDR {
|
||||||
|
fallbackFilters = append(fallbackFilters, &ipnetFilter{ipnet: ipnet})
|
||||||
|
}
|
||||||
|
r.fallbackFilters = fallbackFilters
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,6 +72,10 @@ func updateDNS(c *config.DNS) {
|
||||||
IPv6: c.IPv6,
|
IPv6: c.IPv6,
|
||||||
EnhancedMode: c.EnhancedMode,
|
EnhancedMode: c.EnhancedMode,
|
||||||
Pool: c.FakeIPRange,
|
Pool: c.FakeIPRange,
|
||||||
|
FallbackFilter: dns.FallbackFilter{
|
||||||
|
GeoIP: c.FallbackFilter.GeoIP,
|
||||||
|
IPCIDR: c.FallbackFilter.IPCIDR,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
dns.DefaultResolver = r
|
dns.DefaultResolver = r
|
||||||
if err := dns.ReCreateServer(c.Listen, r); err != nil {
|
if err := dns.ReCreateServer(c.Listen, r); err != nil {
|
||||||
|
|
Loading…
Reference in New Issue