Feature: add fallback filters (#105)

This commit is contained in:
宋辰文 2019-09-15 13:36:45 +08:00 committed by Dreamacro
parent 09f435d928
commit b76737bdbb
5 changed files with 127 additions and 32 deletions

View File

@ -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:

View File

@ -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
} }

26
dns/filters.go Normal file
View File

@ -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)
}

View File

@ -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
} }

View File

@ -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 {