mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2025-09-19 03:56:01 +08:00
chore: update geo unneeded reload whole config
This commit is contained in:
224
common/singleflight/singleflight.go
Normal file
224
common/singleflight/singleflight.go
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
// copy and modify from "golang.org/x/sync/singleflight"
|
||||||
|
|
||||||
|
// Copyright 2013 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package singleflight provides a duplicate function call suppression
|
||||||
|
// mechanism.
|
||||||
|
package singleflight
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"runtime/debug"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// errGoexit indicates the runtime.Goexit was called in
|
||||||
|
// the user given function.
|
||||||
|
var errGoexit = errors.New("runtime.Goexit was called")
|
||||||
|
|
||||||
|
// A panicError is an arbitrary value recovered from a panic
|
||||||
|
// with the stack trace during the execution of given function.
|
||||||
|
type panicError struct {
|
||||||
|
value interface{}
|
||||||
|
stack []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error implements error interface.
|
||||||
|
func (p *panicError) Error() string {
|
||||||
|
return fmt.Sprintf("%v\n\n%s", p.value, p.stack)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *panicError) Unwrap() error {
|
||||||
|
err, ok := p.value.(error)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPanicError(v interface{}) error {
|
||||||
|
stack := debug.Stack()
|
||||||
|
|
||||||
|
// The first line of the stack trace is of the form "goroutine N [status]:"
|
||||||
|
// but by the time the panic reaches Do the goroutine may no longer exist
|
||||||
|
// and its status will have changed. Trim out the misleading line.
|
||||||
|
if line := bytes.IndexByte(stack[:], '\n'); line >= 0 {
|
||||||
|
stack = stack[line+1:]
|
||||||
|
}
|
||||||
|
return &panicError{value: v, stack: stack}
|
||||||
|
}
|
||||||
|
|
||||||
|
// call is an in-flight or completed singleflight.Do call
|
||||||
|
type call[T any] struct {
|
||||||
|
wg sync.WaitGroup
|
||||||
|
|
||||||
|
// These fields are written once before the WaitGroup is done
|
||||||
|
// and are only read after the WaitGroup is done.
|
||||||
|
val T
|
||||||
|
err error
|
||||||
|
|
||||||
|
// These fields are read and written with the singleflight
|
||||||
|
// mutex held before the WaitGroup is done, and are read but
|
||||||
|
// not written after the WaitGroup is done.
|
||||||
|
dups int
|
||||||
|
chans []chan<- Result[T]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group represents a class of work and forms a namespace in
|
||||||
|
// which units of work can be executed with duplicate suppression.
|
||||||
|
type Group[T any] struct {
|
||||||
|
mu sync.Mutex // protects m
|
||||||
|
m map[string]*call[T] // lazily initialized
|
||||||
|
|
||||||
|
StoreResult bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Result holds the results of Do, so they can be passed
|
||||||
|
// on a channel.
|
||||||
|
type Result[T any] struct {
|
||||||
|
Val T
|
||||||
|
Err error
|
||||||
|
Shared bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do executes and returns the results of the given function, making
|
||||||
|
// sure that only one execution is in-flight for a given key at a
|
||||||
|
// time. If a duplicate comes in, the duplicate caller waits for the
|
||||||
|
// original to complete and receives the same results.
|
||||||
|
// The return value shared indicates whether v was given to multiple callers.
|
||||||
|
func (g *Group[T]) Do(key string, fn func() (T, error)) (v T, err error, shared bool) {
|
||||||
|
g.mu.Lock()
|
||||||
|
if g.m == nil {
|
||||||
|
g.m = make(map[string]*call[T])
|
||||||
|
}
|
||||||
|
if c, ok := g.m[key]; ok {
|
||||||
|
c.dups++
|
||||||
|
g.mu.Unlock()
|
||||||
|
c.wg.Wait()
|
||||||
|
|
||||||
|
if e, ok := c.err.(*panicError); ok {
|
||||||
|
panic(e)
|
||||||
|
} else if c.err == errGoexit {
|
||||||
|
runtime.Goexit()
|
||||||
|
}
|
||||||
|
return c.val, c.err, true
|
||||||
|
}
|
||||||
|
c := new(call[T])
|
||||||
|
c.wg.Add(1)
|
||||||
|
g.m[key] = c
|
||||||
|
g.mu.Unlock()
|
||||||
|
|
||||||
|
g.doCall(c, key, fn)
|
||||||
|
return c.val, c.err, c.dups > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// DoChan is like Do but returns a channel that will receive the
|
||||||
|
// results when they are ready.
|
||||||
|
//
|
||||||
|
// The returned channel will not be closed.
|
||||||
|
func (g *Group[T]) DoChan(key string, fn func() (T, error)) <-chan Result[T] {
|
||||||
|
ch := make(chan Result[T], 1)
|
||||||
|
g.mu.Lock()
|
||||||
|
if g.m == nil {
|
||||||
|
g.m = make(map[string]*call[T])
|
||||||
|
}
|
||||||
|
if c, ok := g.m[key]; ok {
|
||||||
|
c.dups++
|
||||||
|
c.chans = append(c.chans, ch)
|
||||||
|
g.mu.Unlock()
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
c := &call[T]{chans: []chan<- Result[T]{ch}}
|
||||||
|
c.wg.Add(1)
|
||||||
|
g.m[key] = c
|
||||||
|
g.mu.Unlock()
|
||||||
|
|
||||||
|
go g.doCall(c, key, fn)
|
||||||
|
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
// doCall handles the single call for a key.
|
||||||
|
func (g *Group[T]) doCall(c *call[T], key string, fn func() (T, error)) {
|
||||||
|
normalReturn := false
|
||||||
|
recovered := false
|
||||||
|
|
||||||
|
// use double-defer to distinguish panic from runtime.Goexit,
|
||||||
|
// more details see https://golang.org/cl/134395
|
||||||
|
defer func() {
|
||||||
|
// the given function invoked runtime.Goexit
|
||||||
|
if !normalReturn && !recovered {
|
||||||
|
c.err = errGoexit
|
||||||
|
}
|
||||||
|
|
||||||
|
g.mu.Lock()
|
||||||
|
defer g.mu.Unlock()
|
||||||
|
c.wg.Done()
|
||||||
|
if g.m[key] == c && !g.StoreResult {
|
||||||
|
delete(g.m, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
if e, ok := c.err.(*panicError); ok {
|
||||||
|
// In order to prevent the waiting channels from being blocked forever,
|
||||||
|
// needs to ensure that this panic cannot be recovered.
|
||||||
|
if len(c.chans) > 0 {
|
||||||
|
go panic(e)
|
||||||
|
select {} // Keep this goroutine around so that it will appear in the crash dump.
|
||||||
|
} else {
|
||||||
|
panic(e)
|
||||||
|
}
|
||||||
|
} else if c.err == errGoexit {
|
||||||
|
// Already in the process of goexit, no need to call again
|
||||||
|
} else {
|
||||||
|
// Normal return
|
||||||
|
for _, ch := range c.chans {
|
||||||
|
ch <- Result[T]{c.val, c.err, c.dups > 0}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
func() {
|
||||||
|
defer func() {
|
||||||
|
if !normalReturn {
|
||||||
|
// Ideally, we would wait to take a stack trace until we've determined
|
||||||
|
// whether this is a panic or a runtime.Goexit.
|
||||||
|
//
|
||||||
|
// Unfortunately, the only way we can distinguish the two is to see
|
||||||
|
// whether the recover stopped the goroutine from terminating, and by
|
||||||
|
// the time we know that, the part of the stack trace relevant to the
|
||||||
|
// panic has been discarded.
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
c.err = newPanicError(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
c.val, c.err = fn()
|
||||||
|
normalReturn = true
|
||||||
|
}()
|
||||||
|
|
||||||
|
if !normalReturn {
|
||||||
|
recovered = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forget tells the singleflight to forget about a key. Future calls
|
||||||
|
// to Do for this key will call the function rather than waiting for
|
||||||
|
// an earlier call to complete.
|
||||||
|
func (g *Group[T]) Forget(key string) {
|
||||||
|
g.mu.Lock()
|
||||||
|
delete(g.m, key)
|
||||||
|
g.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group[T]) Reset() {
|
||||||
|
g.mu.Lock()
|
||||||
|
g.m = nil
|
||||||
|
g.mu.Unlock()
|
||||||
|
}
|
@ -7,7 +7,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type AttributeList struct {
|
type AttributeList struct {
|
||||||
matcher []AttributeMatcher
|
matcher []BooleanMatcher
|
||||||
}
|
}
|
||||||
|
|
||||||
func (al *AttributeList) Match(domain *router.Domain) bool {
|
func (al *AttributeList) Match(domain *router.Domain) bool {
|
||||||
@ -23,6 +23,14 @@ func (al *AttributeList) IsEmpty() bool {
|
|||||||
return len(al.matcher) == 0
|
return len(al.matcher) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (al *AttributeList) String() string {
|
||||||
|
matcher := make([]string, len(al.matcher))
|
||||||
|
for i, match := range al.matcher {
|
||||||
|
matcher[i] = string(match)
|
||||||
|
}
|
||||||
|
return strings.Join(matcher, ",")
|
||||||
|
}
|
||||||
|
|
||||||
func parseAttrs(attrs []string) *AttributeList {
|
func parseAttrs(attrs []string) *AttributeList {
|
||||||
al := new(AttributeList)
|
al := new(AttributeList)
|
||||||
for _, attr := range attrs {
|
for _, attr := range attrs {
|
||||||
|
@ -33,12 +33,13 @@ func domainToMatcher(domain *Domain) (strmatcher.Matcher, error) {
|
|||||||
|
|
||||||
type DomainMatcher interface {
|
type DomainMatcher interface {
|
||||||
ApplyDomain(string) bool
|
ApplyDomain(string) bool
|
||||||
|
Count() int
|
||||||
}
|
}
|
||||||
|
|
||||||
type succinctDomainMatcher struct {
|
type succinctDomainMatcher struct {
|
||||||
set *trie.DomainSet
|
set *trie.DomainSet
|
||||||
otherMatchers []strmatcher.Matcher
|
otherMatchers []strmatcher.Matcher
|
||||||
not bool
|
count int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *succinctDomainMatcher) ApplyDomain(domain string) bool {
|
func (m *succinctDomainMatcher) ApplyDomain(domain string) bool {
|
||||||
@ -51,16 +52,17 @@ func (m *succinctDomainMatcher) ApplyDomain(domain string) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if m.not {
|
|
||||||
isMatched = !isMatched
|
|
||||||
}
|
|
||||||
return isMatched
|
return isMatched
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSuccinctMatcherGroup(domains []*Domain, not bool) (DomainMatcher, error) {
|
func (m *succinctDomainMatcher) Count() int {
|
||||||
|
return m.count
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSuccinctMatcherGroup(domains []*Domain) (DomainMatcher, error) {
|
||||||
t := trie.New[struct{}]()
|
t := trie.New[struct{}]()
|
||||||
m := &succinctDomainMatcher{
|
m := &succinctDomainMatcher{
|
||||||
not: not,
|
count: len(domains),
|
||||||
}
|
}
|
||||||
for _, d := range domains {
|
for _, d := range domains {
|
||||||
switch d.Type {
|
switch d.Type {
|
||||||
@ -90,10 +92,10 @@ func NewSuccinctMatcherGroup(domains []*Domain, not bool) (DomainMatcher, error)
|
|||||||
|
|
||||||
type v2rayDomainMatcher struct {
|
type v2rayDomainMatcher struct {
|
||||||
matchers strmatcher.IndexMatcher
|
matchers strmatcher.IndexMatcher
|
||||||
not bool
|
count int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMphMatcherGroup(domains []*Domain, not bool) (DomainMatcher, error) {
|
func NewMphMatcherGroup(domains []*Domain) (DomainMatcher, error) {
|
||||||
g := strmatcher.NewMphMatcherGroup()
|
g := strmatcher.NewMphMatcherGroup()
|
||||||
for _, d := range domains {
|
for _, d := range domains {
|
||||||
matcherType, f := matcherTypeMap[d.Type]
|
matcherType, f := matcherTypeMap[d.Type]
|
||||||
@ -108,119 +110,80 @@ func NewMphMatcherGroup(domains []*Domain, not bool) (DomainMatcher, error) {
|
|||||||
g.Build()
|
g.Build()
|
||||||
return &v2rayDomainMatcher{
|
return &v2rayDomainMatcher{
|
||||||
matchers: g,
|
matchers: g,
|
||||||
not: not,
|
count: len(domains),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *v2rayDomainMatcher) ApplyDomain(domain string) bool {
|
func (m *v2rayDomainMatcher) ApplyDomain(domain string) bool {
|
||||||
isMatched := len(m.matchers.Match(strings.ToLower(domain))) > 0
|
return len(m.matchers.Match(strings.ToLower(domain))) > 0
|
||||||
if m.not {
|
|
||||||
isMatched = !isMatched
|
|
||||||
}
|
|
||||||
return isMatched
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type GeoIPMatcher struct {
|
func (m *v2rayDomainMatcher) Count() int {
|
||||||
countryCode string
|
return m.count
|
||||||
reverseMatch bool
|
|
||||||
cidrSet *cidr.IpCidrSet
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *GeoIPMatcher) Init(cidrs []*CIDR) error {
|
type notDomainMatcher struct {
|
||||||
for _, cidr := range cidrs {
|
DomainMatcher
|
||||||
addr, ok := netip.AddrFromSlice(cidr.Ip)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("error when loading GeoIP: invalid IP: %s", cidr.Ip)
|
|
||||||
}
|
|
||||||
err := m.cidrSet.AddIpCidr(netip.PrefixFrom(addr, int(cidr.Prefix)))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error when loading GeoIP: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return m.cidrSet.Merge()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *GeoIPMatcher) SetReverseMatch(isReverseMatch bool) {
|
func (m notDomainMatcher) ApplyDomain(domain string) bool {
|
||||||
m.reverseMatch = isReverseMatch
|
return !m.DomainMatcher.ApplyDomain(domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNotDomainMatcherGroup(matcher DomainMatcher) DomainMatcher {
|
||||||
|
return notDomainMatcher{matcher}
|
||||||
|
}
|
||||||
|
|
||||||
|
type IPMatcher interface {
|
||||||
|
Match(ip netip.Addr) bool
|
||||||
|
Count() int
|
||||||
|
}
|
||||||
|
|
||||||
|
type geoIPMatcher struct {
|
||||||
|
cidrSet *cidr.IpCidrSet
|
||||||
|
count int
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match returns true if the given ip is included by the GeoIP.
|
// Match returns true if the given ip is included by the GeoIP.
|
||||||
func (m *GeoIPMatcher) Match(ip netip.Addr) bool {
|
func (m *geoIPMatcher) Match(ip netip.Addr) bool {
|
||||||
match := m.cidrSet.IsContain(ip)
|
return m.cidrSet.IsContain(ip)
|
||||||
if m.reverseMatch {
|
}
|
||||||
return !match
|
|
||||||
|
func (m *geoIPMatcher) Count() int {
|
||||||
|
return m.count
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGeoIPMatcher(cidrList []*CIDR) (IPMatcher, error) {
|
||||||
|
m := &geoIPMatcher{
|
||||||
|
cidrSet: cidr.NewIpCidrSet(),
|
||||||
|
count: len(cidrList),
|
||||||
}
|
}
|
||||||
return match
|
for _, cidr := range cidrList {
|
||||||
}
|
addr, ok := netip.AddrFromSlice(cidr.Ip)
|
||||||
|
if !ok {
|
||||||
// GeoIPMatcherContainer is a container for GeoIPMatchers. It keeps unique copies of GeoIPMatcher by country code.
|
return nil, fmt.Errorf("error when loading GeoIP: invalid IP: %s", cidr.Ip)
|
||||||
type GeoIPMatcherContainer struct {
|
}
|
||||||
matchers []*GeoIPMatcher
|
err := m.cidrSet.AddIpCidr(netip.PrefixFrom(addr, int(cidr.Prefix)))
|
||||||
}
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error when loading GeoIP: %w", err)
|
||||||
// Add adds a new GeoIP set into the container.
|
|
||||||
// If the country code of GeoIP is not empty, GeoIPMatcherContainer will try to find an existing one, instead of adding a new one.
|
|
||||||
func (c *GeoIPMatcherContainer) Add(geoip *GeoIP) (*GeoIPMatcher, error) {
|
|
||||||
if len(geoip.CountryCode) > 0 {
|
|
||||||
for _, m := range c.matchers {
|
|
||||||
if m.countryCode == geoip.CountryCode && m.reverseMatch == geoip.ReverseMatch {
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
err := m.cidrSet.Merge()
|
||||||
m := &GeoIPMatcher{
|
|
||||||
countryCode: geoip.CountryCode,
|
|
||||||
reverseMatch: geoip.ReverseMatch,
|
|
||||||
cidrSet: cidr.NewIpCidrSet(),
|
|
||||||
}
|
|
||||||
if err := m.Init(geoip.Cidr); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(geoip.CountryCode) > 0 {
|
|
||||||
c.matchers = append(c.matchers, m)
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var globalGeoIPContainer GeoIPMatcherContainer
|
|
||||||
|
|
||||||
type MultiGeoIPMatcher struct {
|
|
||||||
matchers []*GeoIPMatcher
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewGeoIPMatcher(geoip *GeoIP) (*GeoIPMatcher, error) {
|
|
||||||
matcher, err := globalGeoIPContainer.Add(geoip)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return matcher, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MultiGeoIPMatcher) ApplyIp(ip netip.Addr) bool {
|
type notIPMatcher struct {
|
||||||
for _, matcher := range m.matchers {
|
IPMatcher
|
||||||
if matcher.Match(ip) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMultiGeoIPMatcher(geoips []*GeoIP) (*MultiGeoIPMatcher, error) {
|
func (m notIPMatcher) Match(ip netip.Addr) bool {
|
||||||
var matchers []*GeoIPMatcher
|
return !m.IPMatcher.Match(ip)
|
||||||
for _, geoip := range geoips {
|
}
|
||||||
matcher, err := globalGeoIPContainer.Add(geoip)
|
|
||||||
if err != nil {
|
func NewNotIpMatcherGroup(matcher IPMatcher) IPMatcher {
|
||||||
return nil, err
|
return notIPMatcher{matcher}
|
||||||
}
|
|
||||||
matchers = append(matchers, matcher)
|
|
||||||
}
|
|
||||||
|
|
||||||
matcher := &MultiGeoIPMatcher{
|
|
||||||
matchers: matchers,
|
|
||||||
}
|
|
||||||
|
|
||||||
return matcher, nil
|
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/sync/singleflight"
|
"github.com/metacubex/mihomo/common/singleflight"
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/component/geodata/router"
|
"github.com/metacubex/mihomo/component/geodata/router"
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
"github.com/metacubex/mihomo/log"
|
"github.com/metacubex/mihomo/log"
|
||||||
@ -71,21 +70,22 @@ func SetSiteMatcher(newMatcher string) {
|
|||||||
func Verify(name string) error {
|
func Verify(name string) error {
|
||||||
switch name {
|
switch name {
|
||||||
case C.GeositeName:
|
case C.GeositeName:
|
||||||
_, _, err := LoadGeoSiteMatcher("CN")
|
_, err := LoadGeoSiteMatcher("CN")
|
||||||
return err
|
return err
|
||||||
case C.GeoipName:
|
case C.GeoipName:
|
||||||
_, _, err := LoadGeoIPMatcher("CN")
|
_, err := LoadGeoIPMatcher("CN")
|
||||||
return err
|
return err
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("not support name")
|
return fmt.Errorf("not support name")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var loadGeoSiteMatcherSF = singleflight.Group{}
|
var loadGeoSiteMatcherListSF = singleflight.Group[[]*router.Domain]{StoreResult: true}
|
||||||
|
var loadGeoSiteMatcherSF = singleflight.Group[router.DomainMatcher]{StoreResult: true}
|
||||||
|
|
||||||
func LoadGeoSiteMatcher(countryCode string) (router.DomainMatcher, int, error) {
|
func LoadGeoSiteMatcher(countryCode string) (router.DomainMatcher, error) {
|
||||||
if countryCode == "" {
|
if countryCode == "" {
|
||||||
return nil, 0, fmt.Errorf("country code could not be empty")
|
return nil, fmt.Errorf("country code could not be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
not := false
|
not := false
|
||||||
@ -97,73 +97,84 @@ func LoadGeoSiteMatcher(countryCode string) (router.DomainMatcher, int, error) {
|
|||||||
|
|
||||||
parts := strings.Split(countryCode, "@")
|
parts := strings.Split(countryCode, "@")
|
||||||
if len(parts) == 0 {
|
if len(parts) == 0 {
|
||||||
return nil, 0, errors.New("empty rule")
|
return nil, errors.New("empty rule")
|
||||||
}
|
}
|
||||||
listName := strings.TrimSpace(parts[0])
|
listName := strings.TrimSpace(parts[0])
|
||||||
attrVal := parts[1:]
|
attrVal := parts[1:]
|
||||||
|
attrs := parseAttrs(attrVal)
|
||||||
|
|
||||||
if listName == "" {
|
if listName == "" {
|
||||||
return nil, 0, fmt.Errorf("empty listname in rule: %s", countryCode)
|
return nil, fmt.Errorf("empty listname in rule: %s", countryCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
v, err, shared := loadGeoSiteMatcherSF.Do(listName, func() (interface{}, error) {
|
matcherName := listName
|
||||||
geoLoader, err := GetGeoDataLoader(geoLoaderName)
|
if !attrs.IsEmpty() {
|
||||||
|
matcherName += "@" + attrs.String()
|
||||||
|
}
|
||||||
|
matcher, err, shared := loadGeoSiteMatcherSF.Do(matcherName, func() (router.DomainMatcher, error) {
|
||||||
|
log.Infoln("Load GeoSite rule: %s", matcherName)
|
||||||
|
domains, err, shared := loadGeoSiteMatcherListSF.Do(listName, func() ([]*router.Domain, error) {
|
||||||
|
geoLoader, err := GetGeoDataLoader(geoLoaderName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return geoLoader.LoadGeoSite(listName)
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if !shared {
|
||||||
|
loadGeoSiteMatcherListSF.Forget(listName) // don't store the error result
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return geoLoader.LoadGeoSite(listName)
|
|
||||||
|
if attrs.IsEmpty() {
|
||||||
|
if strings.Contains(countryCode, "@") {
|
||||||
|
log.Warnln("empty attribute list: %s", countryCode)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
filteredDomains := make([]*router.Domain, 0, len(domains))
|
||||||
|
hasAttrMatched := false
|
||||||
|
for _, domain := range domains {
|
||||||
|
if attrs.Match(domain) {
|
||||||
|
hasAttrMatched = true
|
||||||
|
filteredDomains = append(filteredDomains, domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !hasAttrMatched {
|
||||||
|
log.Warnln("attribute match no rule: geosite: %s", countryCode)
|
||||||
|
}
|
||||||
|
domains = filteredDomains
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
linear: linear algorithm
|
||||||
|
matcher, err := router.NewDomainMatcher(domains)
|
||||||
|
mph:minimal perfect hash algorithm
|
||||||
|
*/
|
||||||
|
if geoSiteMatcher == "mph" {
|
||||||
|
return router.NewMphMatcherGroup(domains)
|
||||||
|
} else {
|
||||||
|
return router.NewSuccinctMatcherGroup(domains)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !shared {
|
if !shared {
|
||||||
loadGeoSiteMatcherSF.Forget(listName) // don't store the error result
|
loadGeoSiteMatcherSF.Forget(matcherName) // don't store the error result
|
||||||
}
|
}
|
||||||
return nil, 0, err
|
return nil, err
|
||||||
}
|
}
|
||||||
domains := v.([]*router.Domain)
|
if not {
|
||||||
|
matcher = router.NewNotDomainMatcherGroup(matcher)
|
||||||
attrs := parseAttrs(attrVal)
|
|
||||||
if attrs.IsEmpty() {
|
|
||||||
if strings.Contains(countryCode, "@") {
|
|
||||||
log.Warnln("empty attribute list: %s", countryCode)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
filteredDomains := make([]*router.Domain, 0, len(domains))
|
|
||||||
hasAttrMatched := false
|
|
||||||
for _, domain := range domains {
|
|
||||||
if attrs.Match(domain) {
|
|
||||||
hasAttrMatched = true
|
|
||||||
filteredDomains = append(filteredDomains, domain)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !hasAttrMatched {
|
|
||||||
log.Warnln("attribute match no rule: geosite: %s", countryCode)
|
|
||||||
}
|
|
||||||
domains = filteredDomains
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
return matcher, nil
|
||||||
linear: linear algorithm
|
|
||||||
matcher, err := router.NewDomainMatcher(domains)
|
|
||||||
mph:minimal perfect hash algorithm
|
|
||||||
*/
|
|
||||||
var matcher router.DomainMatcher
|
|
||||||
if geoSiteMatcher == "mph" {
|
|
||||||
matcher, err = router.NewMphMatcherGroup(domains, not)
|
|
||||||
} else {
|
|
||||||
matcher, err = router.NewSuccinctMatcherGroup(domains, not)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return matcher, len(domains), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var loadGeoIPMatcherSF = singleflight.Group{}
|
var loadGeoIPMatcherSF = singleflight.Group[router.IPMatcher]{StoreResult: true}
|
||||||
|
|
||||||
func LoadGeoIPMatcher(country string) (*router.GeoIPMatcher, int, error) {
|
func LoadGeoIPMatcher(country string) (router.IPMatcher, error) {
|
||||||
if len(country) == 0 {
|
if len(country) == 0 {
|
||||||
return nil, 0, fmt.Errorf("country code could not be empty")
|
return nil, fmt.Errorf("country code could not be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
not := false
|
not := false
|
||||||
@ -173,35 +184,33 @@ func LoadGeoIPMatcher(country string) (*router.GeoIPMatcher, int, error) {
|
|||||||
}
|
}
|
||||||
country = strings.ToLower(country)
|
country = strings.ToLower(country)
|
||||||
|
|
||||||
v, err, shared := loadGeoIPMatcherSF.Do(country, func() (interface{}, error) {
|
matcher, err, shared := loadGeoIPMatcherSF.Do(country, func() (router.IPMatcher, error) {
|
||||||
|
log.Infoln("Load GeoIP rule: %s", country)
|
||||||
geoLoader, err := GetGeoDataLoader(geoLoaderName)
|
geoLoader, err := GetGeoDataLoader(geoLoaderName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return geoLoader.LoadGeoIP(country)
|
cidrList, err := geoLoader.LoadGeoIP(country)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return router.NewGeoIPMatcher(cidrList)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !shared {
|
if !shared {
|
||||||
loadGeoIPMatcherSF.Forget(country) // don't store the error result
|
loadGeoIPMatcherSF.Forget(country) // don't store the error result
|
||||||
|
log.Warnln("Load GeoIP rule: %s", country)
|
||||||
}
|
}
|
||||||
return nil, 0, err
|
return nil, err
|
||||||
}
|
}
|
||||||
records := v.([]*router.CIDR)
|
if not {
|
||||||
|
matcher = router.NewNotIpMatcherGroup(matcher)
|
||||||
geoIP := &router.GeoIP{
|
|
||||||
CountryCode: country,
|
|
||||||
Cidr: records,
|
|
||||||
ReverseMatch: not,
|
|
||||||
}
|
}
|
||||||
|
return matcher, nil
|
||||||
matcher, err := router.NewGeoIPMatcher(geoIP)
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
return matcher, len(records), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ClearCache() {
|
func ClearCache() {
|
||||||
loadGeoSiteMatcherSF = singleflight.Group{}
|
loadGeoSiteMatcherListSF.Reset()
|
||||||
loadGeoIPMatcherSF = singleflight.Group{}
|
loadGeoSiteMatcherSF.Reset()
|
||||||
|
loadGeoIPMatcherSF.Reset()
|
||||||
}
|
}
|
||||||
|
@ -137,7 +137,7 @@ func getUpdateTime() (err error, time time.Time) {
|
|||||||
return nil, fileInfo.ModTime()
|
return nil, fileInfo.ModTime()
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterGeoUpdater(onSuccess func()) {
|
func RegisterGeoUpdater() {
|
||||||
if C.GeoUpdateInterval <= 0 {
|
if C.GeoUpdateInterval <= 0 {
|
||||||
log.Errorln("[GEO] Invalid update interval: %d", C.GeoUpdateInterval)
|
log.Errorln("[GEO] Invalid update interval: %d", C.GeoUpdateInterval)
|
||||||
return
|
return
|
||||||
@ -159,8 +159,6 @@ func RegisterGeoUpdater(onSuccess func()) {
|
|||||||
if err := UpdateGeoDatabases(); err != nil {
|
if err := UpdateGeoDatabases(); err != nil {
|
||||||
log.Errorln("[GEO] Failed to update GEO database: %s", err.Error())
|
log.Errorln("[GEO] Failed to update GEO database: %s", err.Error())
|
||||||
return
|
return
|
||||||
} else {
|
|
||||||
onSuccess()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,8 +166,6 @@ func RegisterGeoUpdater(onSuccess func()) {
|
|||||||
log.Infoln("[GEO] updating database every %d hours", C.GeoUpdateInterval)
|
log.Infoln("[GEO] updating database every %d hours", C.GeoUpdateInterval)
|
||||||
if err := UpdateGeoDatabases(); err != nil {
|
if err := UpdateGeoDatabases(); err != nil {
|
||||||
log.Errorln("[GEO] Failed to update GEO database: %s", err.Error())
|
log.Errorln("[GEO] Failed to update GEO database: %s", err.Error())
|
||||||
} else {
|
|
||||||
onSuccess()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/metacubex/mihomo/common/arc"
|
"github.com/metacubex/mihomo/common/arc"
|
||||||
"github.com/metacubex/mihomo/common/lru"
|
"github.com/metacubex/mihomo/common/lru"
|
||||||
|
"github.com/metacubex/mihomo/common/singleflight"
|
||||||
"github.com/metacubex/mihomo/component/fakeip"
|
"github.com/metacubex/mihomo/component/fakeip"
|
||||||
"github.com/metacubex/mihomo/component/resolver"
|
"github.com/metacubex/mihomo/component/resolver"
|
||||||
"github.com/metacubex/mihomo/component/trie"
|
"github.com/metacubex/mihomo/component/trie"
|
||||||
@ -18,7 +19,6 @@ import (
|
|||||||
D "github.com/miekg/dns"
|
D "github.com/miekg/dns"
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
"golang.org/x/exp/maps"
|
"golang.org/x/exp/maps"
|
||||||
"golang.org/x/sync/singleflight"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type dnsClient interface {
|
type dnsClient interface {
|
||||||
@ -44,7 +44,7 @@ type Resolver struct {
|
|||||||
fallback []dnsClient
|
fallback []dnsClient
|
||||||
fallbackDomainFilters []C.Rule
|
fallbackDomainFilters []C.Rule
|
||||||
fallbackIPFilters []C.Rule
|
fallbackIPFilters []C.Rule
|
||||||
group singleflight.Group
|
group singleflight.Group[*D.Msg]
|
||||||
cache dnsCache
|
cache dnsCache
|
||||||
policy []dnsPolicy
|
policy []dnsPolicy
|
||||||
proxyServer []dnsClient
|
proxyServer []dnsClient
|
||||||
@ -169,19 +169,20 @@ func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.M
|
|||||||
|
|
||||||
retryNum := 0
|
retryNum := 0
|
||||||
retryMax := 3
|
retryMax := 3
|
||||||
fn := func() (result any, err error) {
|
fn := func() (result *D.Msg, err error) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), resolver.DefaultDNSTimeout) // reset timeout in singleflight
|
ctx, cancel := context.WithTimeout(context.Background(), resolver.DefaultDNSTimeout) // reset timeout in singleflight
|
||||||
defer cancel()
|
defer cancel()
|
||||||
cache := false
|
cache := false
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
result = retryNum
|
result = &D.Msg{}
|
||||||
|
result.Opcode = retryNum
|
||||||
retryNum++
|
retryNum++
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := result.(*D.Msg)
|
msg := result
|
||||||
|
|
||||||
if cache {
|
if cache {
|
||||||
// OPT RRs MUST NOT be cached, forwarded, or stored in or loaded from master files.
|
// OPT RRs MUST NOT be cached, forwarded, or stored in or loaded from master files.
|
||||||
@ -208,7 +209,7 @@ func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.M
|
|||||||
|
|
||||||
ch := r.group.DoChan(q.String(), fn)
|
ch := r.group.DoChan(q.String(), fn)
|
||||||
|
|
||||||
var result singleflight.Result
|
var result singleflight.Result[*D.Msg]
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case result = <-ch:
|
case result = <-ch:
|
||||||
@ -221,7 +222,7 @@ func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.M
|
|||||||
go func() { // start a retrying monitor in background
|
go func() { // start a retrying monitor in background
|
||||||
result := <-ch
|
result := <-ch
|
||||||
ret, err, shared := result.Val, result.Err, result.Shared
|
ret, err, shared := result.Val, result.Err, result.Shared
|
||||||
if err != nil && !shared && ret.(int) < retryMax { // retry
|
if err != nil && !shared && ret.Opcode < retryMax { // retry
|
||||||
r.group.DoChan(q.String(), fn)
|
r.group.DoChan(q.String(), fn)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@ -230,12 +231,12 @@ func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.M
|
|||||||
}
|
}
|
||||||
|
|
||||||
ret, err, shared := result.Val, result.Err, result.Shared
|
ret, err, shared := result.Val, result.Err, result.Shared
|
||||||
if err != nil && !shared && ret.(int) < retryMax { // retry
|
if err != nil && !shared && ret.Opcode < retryMax { // retry
|
||||||
r.group.DoChan(q.String(), fn)
|
r.group.DoChan(q.String(), fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
msg = ret.(*D.Msg)
|
msg = ret
|
||||||
if shared {
|
if shared {
|
||||||
msg = msg.Copy()
|
msg = msg.Copy()
|
||||||
}
|
}
|
||||||
|
2
go.mod
2
go.mod
@ -53,7 +53,6 @@ require (
|
|||||||
golang.org/x/crypto v0.26.0
|
golang.org/x/crypto v0.26.0
|
||||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
|
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
|
||||||
golang.org/x/net v0.28.0
|
golang.org/x/net v0.28.0
|
||||||
golang.org/x/sync v0.8.0
|
|
||||||
golang.org/x/sys v0.23.0
|
golang.org/x/sys v0.23.0
|
||||||
google.golang.org/protobuf v1.34.2
|
google.golang.org/protobuf v1.34.2
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
@ -108,6 +107,7 @@ require (
|
|||||||
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
|
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
|
||||||
go.uber.org/mock v0.4.0 // indirect
|
go.uber.org/mock v0.4.0 // indirect
|
||||||
golang.org/x/mod v0.19.0 // indirect
|
golang.org/x/mod v0.19.0 // indirect
|
||||||
|
golang.org/x/sync v0.8.0 // indirect
|
||||||
golang.org/x/text v0.17.0 // indirect
|
golang.org/x/text v0.17.0 // indirect
|
||||||
golang.org/x/time v0.5.0 // indirect
|
golang.org/x/time v0.5.0 // indirect
|
||||||
golang.org/x/tools v0.23.0 // indirect
|
golang.org/x/tools v0.23.0 // indirect
|
||||||
|
@ -408,17 +408,5 @@ func updateGeoDatabases(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err := executor.ParseWithPath(C.Path.Config())
|
|
||||||
if err != nil {
|
|
||||||
log.Errorln("[GEO] update GEO databases failed: %v", err)
|
|
||||||
render.Status(r, http.StatusInternalServerError)
|
|
||||||
render.JSON(w, r, newError("Error parsing configuration"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Warnln("[GEO] update GEO databases success, applying config")
|
|
||||||
|
|
||||||
executor.ApplyConfig(cfg, false)
|
|
||||||
|
|
||||||
render.NoContent(w, r)
|
render.NoContent(w, r)
|
||||||
}
|
}
|
||||||
|
12
main.go
12
main.go
@ -120,17 +120,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if C.GeoAutoUpdate {
|
if C.GeoAutoUpdate {
|
||||||
updater.RegisterGeoUpdater(func() {
|
updater.RegisterGeoUpdater()
|
||||||
cfg, err := executor.ParseWithPath(C.Path.Config())
|
|
||||||
if err != nil {
|
|
||||||
log.Errorln("[GEO] update GEO databases failed: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Warnln("[GEO] update GEO databases success, applying config")
|
|
||||||
|
|
||||||
executor.ApplyConfig(cfg, false)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defer executor.Shutdown()
|
defer executor.Shutdown()
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -14,12 +15,11 @@ import (
|
|||||||
|
|
||||||
type GEOIP struct {
|
type GEOIP struct {
|
||||||
*Base
|
*Base
|
||||||
country string
|
country string
|
||||||
adapter string
|
adapter string
|
||||||
noResolveIP bool
|
noResolveIP bool
|
||||||
isSourceIP bool
|
isSourceIP bool
|
||||||
geoIPMatcher *router.GeoIPMatcher
|
geodata bool
|
||||||
recodeSize int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ C.Rule = (*GEOIP)(nil)
|
var _ C.Rule = (*GEOIP)(nil)
|
||||||
@ -78,7 +78,11 @@ func (g *GEOIP) Match(metadata *C.Metadata) (bool, string) {
|
|||||||
return false, g.adapter
|
return false, g.adapter
|
||||||
}
|
}
|
||||||
|
|
||||||
match := g.geoIPMatcher.Match(ip)
|
matcher, err := g.GetIPMatcher()
|
||||||
|
if err != nil {
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
match := matcher.Match(ip)
|
||||||
if match && !g.isSourceIP {
|
if match && !g.isSourceIP {
|
||||||
metadata.DstGeoIP = append(metadata.DstGeoIP, g.country)
|
metadata.DstGeoIP = append(metadata.DstGeoIP, g.country)
|
||||||
}
|
}
|
||||||
@ -101,12 +105,22 @@ func (g *GEOIP) GetCountry() string {
|
|||||||
return g.country
|
return g.country
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GEOIP) GetIPMatcher() *router.GeoIPMatcher {
|
func (g *GEOIP) GetIPMatcher() (router.IPMatcher, error) {
|
||||||
return g.geoIPMatcher
|
if g.geodata {
|
||||||
|
geoIPMatcher, err := geodata.LoadGeoIPMatcher(g.country)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("[GeoIP] %w", err)
|
||||||
|
}
|
||||||
|
return geoIPMatcher, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("geoip country not set")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GEOIP) GetRecodeSize() int {
|
func (g *GEOIP) GetRecodeSize() int {
|
||||||
return g.recodeSize
|
if matcher, err := g.GetIPMatcher(); err == nil {
|
||||||
|
return matcher.Count()
|
||||||
|
}
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGEOIP(country string, adapter string, isSrc, noResolveIP bool) (*GEOIP, error) {
|
func NewGEOIP(country string, adapter string, isSrc, noResolveIP bool) (*GEOIP, error) {
|
||||||
@ -116,31 +130,23 @@ func NewGEOIP(country string, adapter string, isSrc, noResolveIP bool) (*GEOIP,
|
|||||||
}
|
}
|
||||||
country = strings.ToLower(country)
|
country = strings.ToLower(country)
|
||||||
|
|
||||||
|
geoip := &GEOIP{
|
||||||
|
Base: &Base{},
|
||||||
|
country: country,
|
||||||
|
adapter: adapter,
|
||||||
|
noResolveIP: noResolveIP,
|
||||||
|
isSourceIP: isSrc,
|
||||||
|
}
|
||||||
if !C.GeodataMode || country == "lan" {
|
if !C.GeodataMode || country == "lan" {
|
||||||
geoip := &GEOIP{
|
|
||||||
Base: &Base{},
|
|
||||||
country: country,
|
|
||||||
adapter: adapter,
|
|
||||||
noResolveIP: noResolveIP,
|
|
||||||
isSourceIP: isSrc,
|
|
||||||
}
|
|
||||||
return geoip, nil
|
return geoip, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
geoIPMatcher, size, err := geodata.LoadGeoIPMatcher(country)
|
geoip.geodata = true
|
||||||
|
geoIPMatcher, err := geoip.GetIPMatcher() // test load
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("[GeoIP] %w", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infoln("Start initial GeoIP rule %s => %s, records: %d", country, adapter, size)
|
log.Infoln("Finished initial GeoIP rule %s => %s, records: %d", country, adapter, geoIPMatcher.Count())
|
||||||
geoip := &GEOIP{
|
|
||||||
Base: &Base{},
|
|
||||||
country: country,
|
|
||||||
adapter: adapter,
|
|
||||||
noResolveIP: noResolveIP,
|
|
||||||
isSourceIP: isSrc,
|
|
||||||
geoIPMatcher: geoIPMatcher,
|
|
||||||
recodeSize: size,
|
|
||||||
}
|
|
||||||
return geoip, nil
|
return geoip, nil
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,6 @@ type GEOSITE struct {
|
|||||||
*Base
|
*Base
|
||||||
country string
|
country string
|
||||||
adapter string
|
adapter string
|
||||||
matcher router.DomainMatcher
|
|
||||||
recodeSize int
|
recodeSize int
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,7 +27,11 @@ func (gs *GEOSITE) Match(metadata *C.Metadata) (bool, string) {
|
|||||||
if len(domain) == 0 {
|
if len(domain) == 0 {
|
||||||
return false, ""
|
return false, ""
|
||||||
}
|
}
|
||||||
return gs.matcher.ApplyDomain(domain), gs.adapter
|
matcher, err := gs.GetDomainMatcher()
|
||||||
|
if err != nil {
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
return matcher.ApplyDomain(domain), gs.adapter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gs *GEOSITE) Adapter() string {
|
func (gs *GEOSITE) Adapter() string {
|
||||||
@ -39,12 +42,19 @@ func (gs *GEOSITE) Payload() string {
|
|||||||
return gs.country
|
return gs.country
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gs *GEOSITE) GetDomainMatcher() router.DomainMatcher {
|
func (gs *GEOSITE) GetDomainMatcher() (router.DomainMatcher, error) {
|
||||||
return gs.matcher
|
matcher, err := geodata.LoadGeoSiteMatcher(gs.country)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("load GeoSite data error, %w", err)
|
||||||
|
}
|
||||||
|
return matcher, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gs *GEOSITE) GetRecodeSize() int {
|
func (gs *GEOSITE) GetRecodeSize() int {
|
||||||
return gs.recodeSize
|
if matcher, err := gs.GetDomainMatcher(); err == nil {
|
||||||
|
return matcher.Count()
|
||||||
|
}
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGEOSITE(country string, adapter string) (*GEOSITE, error) {
|
func NewGEOSITE(country string, adapter string) (*GEOSITE, error) {
|
||||||
@ -53,21 +63,19 @@ func NewGEOSITE(country string, adapter string) (*GEOSITE, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
matcher, size, err := geodata.LoadGeoSiteMatcher(country)
|
|
||||||
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, size)
|
|
||||||
|
|
||||||
geoSite := &GEOSITE{
|
geoSite := &GEOSITE{
|
||||||
Base: &Base{},
|
Base: &Base{},
|
||||||
country: country,
|
country: country,
|
||||||
adapter: adapter,
|
adapter: adapter,
|
||||||
matcher: matcher,
|
|
||||||
recodeSize: size,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
matcher, err := geoSite.GetDomainMatcher() // test load
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infoln("Finished initial GeoSite rule %s => %s, records: %d", country, adapter, matcher.Count())
|
||||||
|
|
||||||
return geoSite, nil
|
return geoSite, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user