From 8392567962ac759b5415e688afd8a1a2c3e59d48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Tue, 5 Jul 2022 09:05:35 +0800 Subject: [PATCH] Add geosite --- .gitignore | 3 +- adapter/router.go | 4 + common/domain/matcher.go | 23 ++- common/domain/set.go | 1 - common/geosite/reader.go | 29 ++-- common/geosite/rule.go | 41 +++++ go.mod | 2 +- go.sum | 4 +- option/route.go | 16 +- route/router.go | 353 +++++++++++++++++++++++++-------------- route/rule.go | 47 ++++++ route/rule_geosite.go | 67 ++++++++ route/rule_logical.go | 20 +++ 13 files changed, 464 insertions(+), 146 deletions(-) create mode 100644 route/rule_geosite.go diff --git a/.gitignore b/.gitignore index c4a979b2..c66de3ed 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /.idea/ /vendor/ /*.json -/Country.mmdb \ No newline at end of file +/Country.mmdb +/geosite.db \ No newline at end of file diff --git a/adapter/router.go b/adapter/router.go index 49019656..209e4123 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -5,6 +5,7 @@ import ( "net" "github.com/oschwald/geoip2-golang" + "github.com/sagernet/sing-box/common/geosite" N "github.com/sagernet/sing/common/network" ) @@ -16,9 +17,12 @@ type Router interface { RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error GeoIPReader() *geoip2.Reader + GeositeReader() *geosite.Reader } type Rule interface { + Start() error + Close() error Match(metadata *InboundContext) bool Outbound() string String() string diff --git a/common/domain/matcher.go b/common/domain/matcher.go index 05ff66e8..95dc0dc8 100644 --- a/common/domain/matcher.go +++ b/common/domain/matcher.go @@ -1,19 +1,32 @@ package domain -import "unicode/utf8" +import ( + "sort" + "unicode/utf8" +) type Matcher struct { set *succinctSet } func NewMatcher(domains []string, domainSuffix []string) *Matcher { - var domainList []string - for _, domain := range domains { - domainList = append(domainList, reverseDomain(domain)) - } + domainList := make([]string, 0, len(domains)+len(domainSuffix)) + seen := make(map[string]bool, len(domainList)) for _, domain := range domainSuffix { + if seen[domain] { + continue + } + seen[domain] = true domainList = append(domainList, reverseDomainSuffix(domain)) } + for _, domain := range domains { + if seen[domain] { + continue + } + seen[domain] = true + domainList = append(domainList, reverseDomain(domain)) + } + sort.Strings(domainList) return &Matcher{ newSuccinctSet(domainList), } diff --git a/common/domain/set.go b/common/domain/set.go index e513577a..adf661ac 100644 --- a/common/domain/set.go +++ b/common/domain/set.go @@ -35,7 +35,6 @@ func newSuccinctSet(keys []string) *succinctSet { setBit(&ss.labelBitmap, lIdx, 0) lIdx++ } - setBit(&ss.labelBitmap, lIdx, 1) lIdx++ } diff --git a/common/geosite/reader.go b/common/geosite/reader.go index 1a958c78..4e548323 100644 --- a/common/geosite/reader.go +++ b/common/geosite/reader.go @@ -2,7 +2,7 @@ package geosite import ( "io" - "sync" + "os" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/rw" @@ -10,12 +10,26 @@ import ( type Reader struct { reader io.ReadSeeker - access sync.Mutex - metadataRead bool domainIndex map[string]int domainLength map[string]int } +func Open(path string) (*Reader, error) { + content, err := os.Open(path) + if err != nil { + return nil, err + } + reader := &Reader{ + reader: content, + } + err = reader.readMetadata() + if err != nil { + content.Close() + return nil, err + } + return reader, nil +} + func (r *Reader) readMetadata() error { version, err := rw.ReadByte(r.reader) if err != nil { @@ -55,19 +69,10 @@ func (r *Reader) readMetadata() error { } r.domainIndex = domainIndex r.domainLength = domainLength - r.metadataRead = true return nil } func (r *Reader) Read(code string) ([]Item, error) { - r.access.Lock() - defer r.access.Unlock() - if !r.metadataRead { - err := r.readMetadata() - if err != nil { - return nil, err - } - } if _, exists := r.domainIndex[code]; !exists { return nil, E.New("code ", code, " not exists!") } diff --git a/common/geosite/rule.go b/common/geosite/rule.go index 83ad8d5f..55287156 100644 --- a/common/geosite/rule.go +++ b/common/geosite/rule.go @@ -60,3 +60,44 @@ func Compile(code []Item) option.DefaultRule { } return codeRule } + +func Merge(rules []option.DefaultRule) option.DefaultRule { + var domainLength int + var domainSuffixLength int + var domainKeywordLength int + var domainRegexLength int + for _, subRule := range rules { + domainLength += len(subRule.Domain) + domainSuffixLength += len(subRule.DomainSuffix) + domainKeywordLength += len(subRule.DomainKeyword) + domainRegexLength += len(subRule.DomainRegex) + } + var rule option.DefaultRule + if domainLength > 0 { + rule.Domain = make([]string, 0, domainLength) + } + if domainSuffixLength > 0 { + rule.DomainSuffix = make([]string, 0, domainSuffixLength) + } + if domainKeywordLength > 0 { + rule.DomainKeyword = make([]string, 0, domainKeywordLength) + } + if domainRegexLength > 0 { + rule.DomainRegex = make([]string, 0, domainRegexLength) + } + for _, subRule := range rules { + if len(subRule.Domain) > 0 { + rule.Domain = append(rule.Domain, subRule.Domain...) + } + if len(subRule.DomainSuffix) > 0 { + rule.DomainSuffix = append(rule.DomainSuffix, subRule.DomainSuffix...) + } + if len(subRule.DomainKeyword) > 0 { + rule.DomainKeyword = append(rule.DomainKeyword, subRule.DomainKeyword...) + } + if len(subRule.DomainRegex) > 0 { + rule.DomainRegex = append(rule.DomainRegex, subRule.DomainRegex...) + } + } + return rule +} diff --git a/go.mod b/go.mod index 19daca12..3db06801 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/goccy/go-json v0.9.8 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/oschwald/geoip2-golang v1.7.0 - github.com/sagernet/sing v0.0.0-20220704113227-8b990551511a + github.com/sagernet/sing v0.0.0-20220705005401-57d12d875b7a github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649 github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.5.0 diff --git a/go.sum b/go.sum index 79f316de..e997a183 100644 --- a/go.sum +++ b/go.sum @@ -20,8 +20,8 @@ github.com/oschwald/maxminddb-golang v1.9.0/go.mod h1:TK+s/Z2oZq0rSl4PSeAEoP0bgm github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagernet/sing v0.0.0-20220704113227-8b990551511a h1:IvYjuvuPNmZzQfBbCxE/uQqGkNWUa5/KrEMIecRMjZk= -github.com/sagernet/sing v0.0.0-20220704113227-8b990551511a/go.mod h1:3ZmoGNg/nNJTyHAZFNRSPaXpNIwpDvyIiAUd0KIWV5c= +github.com/sagernet/sing v0.0.0-20220705005401-57d12d875b7a h1:FhrHCkox9scuTzcT5DDh6flVLFuqU+QSk3VONd41I+o= +github.com/sagernet/sing v0.0.0-20220705005401-57d12d875b7a/go.mod h1:3ZmoGNg/nNJTyHAZFNRSPaXpNIwpDvyIiAUd0KIWV5c= github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649 h1:whNDUGOAX5GPZkSy4G3Gv9QyIgk5SXRyjkRuP7ohF8k= github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649/go.mod h1:MuyT+9fEPjvauAv0fSE0a6Q+l0Tv2ZrAafTkYfnxBFw= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= diff --git a/option/route.go b/option/route.go index 7c07bf88..de53e934 100644 --- a/option/route.go +++ b/option/route.go @@ -8,13 +8,15 @@ import ( ) type RouteOptions struct { - GeoIP *GeoIPOptions `json:"geoip,omitempty"` - Rules []Rule `json:"rules,omitempty"` - DefaultDetour string `json:"default_detour,omitempty"` + GeoIP *GeoIPOptions `json:"geoip,omitempty"` + Geosite *GeositeOptions `json:"geosite,omitempty"` + Rules []Rule `json:"rules,omitempty"` + DefaultDetour string `json:"default_detour,omitempty"` } func (o RouteOptions) Equals(other RouteOptions) bool { return common.ComparablePtrEquals(o.GeoIP, other.GeoIP) && + common.ComparablePtrEquals(o.Geosite, other.Geosite) && common.SliceEquals(o.Rules, other.Rules) } @@ -24,6 +26,12 @@ type GeoIPOptions struct { DownloadDetour string `json:"download_detour,omitempty"` } +type GeositeOptions struct { + Path string `json:"path,omitempty"` + DownloadURL string `json:"download_url,omitempty"` + DownloadDetour string `json:"download_detour,omitempty"` +} + type _Rule struct { Type string `json:"type,omitempty"` DefaultOptions DefaultRule `json:"-"` @@ -84,6 +92,7 @@ type DefaultRule struct { DomainSuffix Listable[string] `json:"domain_suffix,omitempty"` DomainKeyword Listable[string] `json:"domain_keyword,omitempty"` DomainRegex Listable[string] `json:"domain_regex,omitempty"` + Geosite Listable[string] `json:"geosite,omitempty"` SourceGeoIP Listable[string] `json:"source_geoip,omitempty"` GeoIP Listable[string] `json:"geoip,omitempty"` SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"` @@ -110,6 +119,7 @@ func (r DefaultRule) Equals(other DefaultRule) bool { common.ComparableSliceEquals(r.DomainSuffix, other.DomainSuffix) && common.ComparableSliceEquals(r.DomainKeyword, other.DomainKeyword) && common.ComparableSliceEquals(r.DomainRegex, other.DomainRegex) && + common.ComparableSliceEquals(r.Geosite, other.Geosite) && common.ComparableSliceEquals(r.SourceGeoIP, other.SourceGeoIP) && common.ComparableSliceEquals(r.GeoIP, other.GeoIP) && common.ComparableSliceEquals(r.SourceIPCIDR, other.SourceIPCIDR) && diff --git a/route/router.go b/route/router.go index 097fcc3d..ea76d14e 100644 --- a/route/router.go +++ b/route/router.go @@ -11,6 +11,7 @@ import ( "github.com/oschwald/geoip2-golang" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/geosite" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" @@ -35,20 +36,25 @@ type Router struct { defaultOutboundForConnection adapter.Outbound defaultOutboundForPacketConnection adapter.Outbound - needGeoDatabase bool - geoOptions option.GeoIPOptions - geoReader *geoip2.Reader + needGeoIPDatabase bool + geoIPOptions option.GeoIPOptions + geoIPReader *geoip2.Reader + + needGeositeDatabase bool + geositeOptions option.GeositeOptions + geositeReader *geosite.Reader } func NewRouter(ctx context.Context, logger log.Logger, options option.RouteOptions) (*Router, error) { router := &Router{ - ctx: ctx, - logger: logger.WithPrefix("router: "), - outboundByTag: make(map[string]adapter.Outbound), - rules: make([]adapter.Rule, 0, len(options.Rules)), - needGeoDatabase: hasGeoRule(options.Rules), - geoOptions: common.PtrValueOrDefault(options.GeoIP), - defaultDetour: options.DefaultDetour, + ctx: ctx, + logger: logger.WithPrefix("router: "), + outboundByTag: make(map[string]adapter.Outbound), + rules: make([]adapter.Rule, 0, len(options.Rules)), + needGeoIPDatabase: hasGeoRule(options.Rules, isGeoIPRule), + needGeositeDatabase: hasGeoRule(options.Rules, isGeositeRule), + geoIPOptions: common.PtrValueOrDefault(options.GeoIP), + defaultDetour: options.DefaultDetour, } for i, ruleOptions := range options.Rules { rule, err := NewRule(router, logger, ruleOptions) @@ -60,32 +66,6 @@ func NewRouter(ctx context.Context, logger log.Logger, options option.RouteOptio return router, nil } -func hasGeoRule(rules []option.Rule) bool { - for _, rule := range rules { - switch rule.Type { - case C.RuleTypeDefault: - if isGeoRule(rule.DefaultOptions) { - return true - } - case C.RuleTypeLogical: - for _, subRule := range rule.LogicalOptions.Rules { - if isGeoRule(subRule) { - return true - } - } - } - } - return false -} - -func isGeoRule(rule option.DefaultRule) bool { - return len(rule.SourceGeoIP) > 0 && common.Any(rule.SourceGeoIP, notPrivateNode) || len(rule.GeoIP) > 0 && common.Any(rule.GeoIP, notPrivateNode) -} - -func notPrivateNode(code string) bool { - return code != "private" -} - func (r *Router) Initialize(outbounds []adapter.Outbound, defaultOutbound func() adapter.Outbound) error { outboundByTag := make(map[string]adapter.Outbound) for _, detour := range outbounds { @@ -156,107 +136,40 @@ func (r *Router) Initialize(outbounds []adapter.Outbound, defaultOutbound func() } func (r *Router) Start() error { - if r.needGeoDatabase { + if r.needGeoIPDatabase { err := r.prepareGeoIPDatabase() if err != nil { return err } } + if r.needGeositeDatabase { + err := r.prepareGeositeDatabase() + if err != nil { + return err + } + } + for _, rule := range r.rules { + err := rule.Start() + if err != nil { + return err + } + } return nil } func (r *Router) Close() error { return common.Close( - common.PtrOrNil(r.geoReader), + common.PtrOrNil(r.geoIPReader), + common.PtrOrNil(r.geositeReader), ) } func (r *Router) GeoIPReader() *geoip2.Reader { - return r.geoReader + return r.geoIPReader } -func (r *Router) prepareGeoIPDatabase() error { - var geoPath string - if r.geoOptions.Path != "" { - geoPath = r.geoOptions.Path - } else { - geoPath = "Country.mmdb" - if foundPath, loaded := C.Find(geoPath); loaded { - geoPath = foundPath - } - } - if !rw.FileExists(geoPath) { - r.logger.Warn("geoip database not exists: ", geoPath) - var err error - for attempts := 0; attempts < 3; attempts++ { - err = r.downloadGeoIPDatabase(geoPath) - if err == nil { - break - } - r.logger.Error("download geoip database: ", err) - os.Remove(geoPath) - time.Sleep(10 * time.Second) - } - if err != nil { - return err - } - } - geoReader, err := geoip2.Open(geoPath) - if err == nil { - r.logger.Info("loaded geoip database") - r.geoReader = geoReader - } else { - return E.Cause(err, "open geoip database") - } - return nil -} - -func (r *Router) downloadGeoIPDatabase(savePath string) error { - var downloadURL string - if r.geoOptions.DownloadURL != "" { - downloadURL = r.geoOptions.DownloadURL - } else { - downloadURL = "https://cdn.jsdelivr.net/gh/Dreamacro/maxmind-geoip@release/Country.mmdb" - } - r.logger.Info("downloading geoip database") - var detour adapter.Outbound - if r.geoOptions.DownloadDetour != "" { - outbound, loaded := r.Outbound(r.geoOptions.DownloadDetour) - if !loaded { - return E.New("detour outbound not found: ", r.geoOptions.DownloadDetour) - } - detour = outbound - } else { - detour = r.defaultOutboundForConnection - } - - if parentDir := filepath.Dir(savePath); parentDir != "" { - os.MkdirAll(parentDir, 0o755) - } - - saveFile, err := os.OpenFile(savePath, os.O_CREATE|os.O_WRONLY, 0o644) - if err != nil { - return E.Cause(err, "open output file: ", downloadURL) - } - defer saveFile.Close() - - httpClient := &http.Client{ - Timeout: 5 * time.Second, - Transport: &http.Transport{ - ForceAttemptHTTP2: true, - TLSHandshakeTimeout: 5 * time.Second, - DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { - return detour.DialContext(ctx, network, M.ParseSocksaddr(addr)) - }, - }, - } - response, err := httpClient.Get(downloadURL) - if err != nil { - return err - } - defer response.Body.Close() - _, err = io.Copy(saveFile, response.Body) - return err +func (r *Router) GeositeReader() *geosite.Reader { + return r.geositeReader } func (r *Router) Outbound(tag string) (adapter.Outbound, bool) { @@ -296,3 +209,201 @@ func (r *Router) match(ctx context.Context, metadata adapter.InboundContext, def r.logger.WithContext(ctx).Info("no match") return defaultOutbound } + +func hasGeoRule(rules []option.Rule, cond func(rule option.DefaultRule) bool) bool { + for _, rule := range rules { + switch rule.Type { + case C.RuleTypeDefault: + if cond(rule.DefaultOptions) { + return true + } + case C.RuleTypeLogical: + for _, subRule := range rule.LogicalOptions.Rules { + if cond(subRule) { + return true + } + } + } + } + return false +} + +func isGeoIPRule(rule option.DefaultRule) bool { + return len(rule.SourceGeoIP) > 0 && common.Any(rule.SourceGeoIP, notPrivateNode) || len(rule.GeoIP) > 0 && common.Any(rule.GeoIP, notPrivateNode) +} + +func isGeositeRule(rule option.DefaultRule) bool { + return len(rule.Geosite) > 0 +} + +func notPrivateNode(code string) bool { + return code != "private" +} + +func (r *Router) prepareGeoIPDatabase() error { + var geoPath string + if r.geoIPOptions.Path != "" { + geoPath = r.geoIPOptions.Path + } else { + geoPath = "Country.mmdb" + if foundPath, loaded := C.Find(geoPath); loaded { + geoPath = foundPath + } + } + if !rw.FileExists(geoPath) { + r.logger.Warn("geoip database not exists: ", geoPath) + var err error + for attempts := 0; attempts < 3; attempts++ { + err = r.downloadGeoIPDatabase(geoPath) + if err == nil { + break + } + r.logger.Error("download geoip database: ", err) + os.Remove(geoPath) + time.Sleep(10 * time.Second) + } + if err != nil { + return err + } + } + geoReader, err := geoip2.Open(geoPath) + if err == nil { + r.logger.Info("loaded geoip database") + r.geoIPReader = geoReader + } else { + return E.Cause(err, "open geoip database") + } + return nil +} + +func (r *Router) prepareGeositeDatabase() error { + var geoPath string + if r.geositeOptions.Path != "" { + geoPath = r.geoIPOptions.Path + } else { + geoPath = "geosite.db" + if foundPath, loaded := C.Find(geoPath); loaded { + geoPath = foundPath + } + } + if !rw.FileExists(geoPath) { + r.logger.Warn("geosite database not exists: ", geoPath) + var err error + for attempts := 0; attempts < 3; attempts++ { + err = r.downloadGeositeDatabase(geoPath) + if err == nil { + break + } + r.logger.Error("download geosite database: ", err) + os.Remove(geoPath) + time.Sleep(10 * time.Second) + } + if err != nil { + return err + } + } + geoReader, err := geosite.Open(geoPath) + if err == nil { + r.logger.Info("loaded geosite database") + r.geositeReader = geoReader + } else { + return E.Cause(err, "open geosite database") + } + return nil +} + +func (r *Router) downloadGeoIPDatabase(savePath string) error { + var downloadURL string + if r.geoIPOptions.DownloadURL != "" { + downloadURL = r.geoIPOptions.DownloadURL + } else { + downloadURL = "https://cdn.jsdelivr.net/gh/Dreamacro/maxmind-geoip@release/Country.mmdb" + } + r.logger.Info("downloading geoip database") + var detour adapter.Outbound + if r.geoIPOptions.DownloadDetour != "" { + outbound, loaded := r.Outbound(r.geoIPOptions.DownloadDetour) + if !loaded { + return E.New("detour outbound not found: ", r.geoIPOptions.DownloadDetour) + } + detour = outbound + } else { + detour = r.defaultOutboundForConnection + } + + if parentDir := filepath.Dir(savePath); parentDir != "" { + os.MkdirAll(parentDir, 0o755) + } + + saveFile, err := os.OpenFile(savePath, os.O_CREATE|os.O_WRONLY, 0o644) + if err != nil { + return E.Cause(err, "open output file: ", downloadURL) + } + defer saveFile.Close() + + httpClient := &http.Client{ + Timeout: 5 * time.Second, + Transport: &http.Transport{ + ForceAttemptHTTP2: true, + TLSHandshakeTimeout: 5 * time.Second, + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return detour.DialContext(ctx, network, M.ParseSocksaddr(addr)) + }, + }, + } + response, err := httpClient.Get(downloadURL) + if err != nil { + return err + } + defer response.Body.Close() + _, err = io.Copy(saveFile, response.Body) + return err +} + +func (r *Router) downloadGeositeDatabase(savePath string) error { + var downloadURL string + if r.geositeOptions.DownloadURL != "" { + downloadURL = r.geositeOptions.DownloadURL + } else { + downloadURL = "https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db" + } + r.logger.Info("downloading geoip database") + var detour adapter.Outbound + if r.geositeOptions.DownloadDetour != "" { + outbound, loaded := r.Outbound(r.geositeOptions.DownloadDetour) + if !loaded { + return E.New("detour outbound not found: ", r.geoIPOptions.DownloadDetour) + } + detour = outbound + } else { + detour = r.defaultOutboundForConnection + } + + if parentDir := filepath.Dir(savePath); parentDir != "" { + os.MkdirAll(parentDir, 0o755) + } + + saveFile, err := os.OpenFile(savePath, os.O_CREATE|os.O_WRONLY, 0o644) + if err != nil { + return E.Cause(err, "open output file: ", downloadURL) + } + defer saveFile.Close() + + httpClient := &http.Client{ + Timeout: 5 * time.Second, + Transport: &http.Transport{ + ForceAttemptHTTP2: true, + TLSHandshakeTimeout: 5 * time.Second, + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return detour.DialContext(ctx, network, M.ParseSocksaddr(addr)) + }, + }, + } + response, err := httpClient.Get(downloadURL) + if err != nil { + return err + } + defer response.Body.Close() + _, err = io.Copy(saveFile, response.Body) + return err +} diff --git a/route/rule.go b/route/rule.go index 0adb2fbe..24eb31d7 100644 --- a/route/rule.go +++ b/route/rule.go @@ -91,6 +91,9 @@ func NewDefaultRule(router adapter.Router, logger log.Logger, options option.Def } rule.destinationAddressItems = append(rule.destinationAddressItems, item) } + if len(options.Geosite) > 0 { + rule.destinationAddressItems = append(rule.destinationAddressItems, NewGeositeItem(router, logger, options.Geosite)) + } if len(options.SourceGeoIP) > 0 { rule.sourceAddressItems = append(rule.sourceAddressItems, NewGeoIPItem(router, logger, true, options.SourceGeoIP)) } @@ -120,6 +123,50 @@ func NewDefaultRule(router adapter.Router, logger log.Logger, options option.Def return rule, nil } +func (r *DefaultRule) Start() error { + for _, item := range r.items { + err := common.Start(item) + if err != nil { + return err + } + } + for _, item := range r.sourceAddressItems { + err := common.Start(item) + if err != nil { + return err + } + } + for _, item := range r.destinationAddressItems { + err := common.Start(item) + if err != nil { + return err + } + } + return nil +} + +func (r *DefaultRule) Close() error { + for _, item := range r.items { + err := common.Close(item) + if err != nil { + return err + } + } + for _, item := range r.sourceAddressItems { + err := common.Close(item) + if err != nil { + return err + } + } + for _, item := range r.destinationAddressItems { + err := common.Close(item) + if err != nil { + return err + } + } + return nil +} + func (r *DefaultRule) Match(metadata *adapter.InboundContext) bool { for _, item := range r.items { if !item.Match(metadata) { diff --git a/route/rule_geosite.go b/route/rule_geosite.go new file mode 100644 index 00000000..dcbc060d --- /dev/null +++ b/route/rule_geosite.go @@ -0,0 +1,67 @@ +package route + +import ( + "strings" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/geosite" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" +) + +var _ RuleItem = (*GeositeItem)(nil) + +type GeositeItem struct { + router adapter.Router + logger log.Logger + codes []string + matcher *DefaultRule +} + +func NewGeositeItem(router adapter.Router, logger log.Logger, codes []string) *GeositeItem { + return &GeositeItem{ + router: router, + logger: logger, + codes: codes, + } +} + +func (r *GeositeItem) Start() error { + geositeReader := r.router.GeositeReader() + if geositeReader == nil { + return E.New("geosite reader is not initialized") + } + var subRules []option.DefaultRule + for _, code := range r.codes { + items, err := geositeReader.Read(code) + if err != nil { + return E.Cause(err, "read geosite") + } + subRules = append(subRules, geosite.Compile(items)) + } + matcherRule := geosite.Merge(subRules) + matcher, err := NewDefaultRule(r.router, r.logger, matcherRule) + if err != nil { + return E.Cause(err, "compile geosite") + } + r.matcher = matcher + return nil +} + +func (r *GeositeItem) Match(metadata *adapter.InboundContext) bool { + return r.matcher.Match(metadata) +} + +func (r *GeositeItem) String() string { + description := "geosite=" + cLen := len(r.codes) + if cLen == 1 { + description = r.codes[0] + } else if cLen > 3 { + description = "[" + strings.Join(r.codes[:3], " ") + "...]" + } else { + description = "[" + strings.Join(r.codes, " ") + "]" + } + return description +} diff --git a/route/rule_logical.go b/route/rule_logical.go index 0e3c840f..c9d89ae9 100644 --- a/route/rule_logical.go +++ b/route/rule_logical.go @@ -20,6 +20,26 @@ type LogicalRule struct { outbound string } +func (r *LogicalRule) Start() error { + for _, rule := range r.rules { + err := rule.Start() + if err != nil { + return err + } + } + return nil +} + +func (r *LogicalRule) Close() error { + for _, rule := range r.rules { + err := rule.Close() + if err != nil { + return err + } + } + return nil +} + func NewLogicalRule(router adapter.Router, logger log.Logger, options option.LogicalRule) (*LogicalRule, error) { r := &LogicalRule{ rules: make([]*DefaultRule, len(options.Rules)),