-
Notifications
You must be signed in to change notification settings - Fork 744
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add GeoIP and GeoSite support for expr (#38)
* feat: copy something from hysteria/extras/outbounds/acl * feat: add geoip and geosite support for expr * refactor: geo matcher * fix: typo * refactor: geo matcher * feat: expose config options to specify local geoip/geosite db files * refactor: engine.Config should not contains geo * feat: make geosite and geoip lazy downloaded * chore: minor code improvement * docs: add geoip/geosite usage --------- Co-authored-by: Toby <[email protected]>
- Loading branch information
1 parent
e23f8e0
commit f07a38b
Showing
16 changed files
with
1,502 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
package geo | ||
|
||
import ( | ||
"io" | ||
"net/http" | ||
"os" | ||
"time" | ||
|
||
"github.com/apernet/OpenGFW/ruleset/builtins/geo/v2geo" | ||
) | ||
|
||
const ( | ||
geoipFilename = "geoip.dat" | ||
geoipURL = "https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geoip.dat" | ||
geositeFilename = "geosite.dat" | ||
geositeURL = "https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat" | ||
|
||
geoDefaultUpdateInterval = 7 * 24 * time.Hour // 7 days | ||
) | ||
|
||
var _ GeoLoader = (*V2GeoLoader)(nil) | ||
|
||
// V2GeoLoader provides the on-demand GeoIP/MatchGeoSite database | ||
// loading functionality required by the ACL engine. | ||
// Empty filenames = automatic download from built-in URLs. | ||
type V2GeoLoader struct { | ||
GeoIPFilename string | ||
GeoSiteFilename string | ||
UpdateInterval time.Duration | ||
|
||
DownloadFunc func(filename, url string) | ||
DownloadErrFunc func(err error) | ||
|
||
geoipMap map[string]*v2geo.GeoIP | ||
geositeMap map[string]*v2geo.GeoSite | ||
} | ||
|
||
func NewDefaultGeoLoader(geoSiteFilename, geoIpFilename string) *V2GeoLoader { | ||
return &V2GeoLoader{ | ||
GeoIPFilename: geoIpFilename, | ||
GeoSiteFilename: geoSiteFilename, | ||
DownloadFunc: func(filename, url string) {}, | ||
DownloadErrFunc: func(err error) {}, | ||
} | ||
} | ||
|
||
func (l *V2GeoLoader) shouldDownload(filename string) bool { | ||
info, err := os.Stat(filename) | ||
if os.IsNotExist(err) { | ||
return true | ||
} | ||
dt := time.Now().Sub(info.ModTime()) | ||
if l.UpdateInterval == 0 { | ||
return dt > geoDefaultUpdateInterval | ||
} else { | ||
return dt > l.UpdateInterval | ||
} | ||
} | ||
|
||
func (l *V2GeoLoader) download(filename, url string) error { | ||
l.DownloadFunc(filename, url) | ||
|
||
resp, err := http.Get(url) | ||
if err != nil { | ||
l.DownloadErrFunc(err) | ||
return err | ||
} | ||
defer resp.Body.Close() | ||
|
||
f, err := os.Create(filename) | ||
if err != nil { | ||
l.DownloadErrFunc(err) | ||
return err | ||
} | ||
defer f.Close() | ||
|
||
_, err = io.Copy(f, resp.Body) | ||
l.DownloadErrFunc(err) | ||
return err | ||
} | ||
|
||
func (l *V2GeoLoader) LoadGeoIP() (map[string]*v2geo.GeoIP, error) { | ||
if l.geoipMap != nil { | ||
return l.geoipMap, nil | ||
} | ||
autoDL := false | ||
filename := l.GeoIPFilename | ||
if filename == "" { | ||
autoDL = true | ||
filename = geoipFilename | ||
} | ||
if autoDL && l.shouldDownload(filename) { | ||
err := l.download(filename, geoipURL) | ||
if err != nil { | ||
return nil, err | ||
} | ||
} | ||
m, err := v2geo.LoadGeoIP(filename) | ||
if err != nil { | ||
return nil, err | ||
} | ||
l.geoipMap = m | ||
return m, nil | ||
} | ||
|
||
func (l *V2GeoLoader) LoadGeoSite() (map[string]*v2geo.GeoSite, error) { | ||
if l.geositeMap != nil { | ||
return l.geositeMap, nil | ||
} | ||
autoDL := false | ||
filename := l.GeoSiteFilename | ||
if filename == "" { | ||
autoDL = true | ||
filename = geositeFilename | ||
} | ||
if autoDL && l.shouldDownload(filename) { | ||
err := l.download(filename, geositeURL) | ||
if err != nil { | ||
return nil, err | ||
} | ||
} | ||
m, err := v2geo.LoadGeoSite(filename) | ||
if err != nil { | ||
return nil, err | ||
} | ||
l.geositeMap = m | ||
return m, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
package geo | ||
|
||
import ( | ||
"net" | ||
"strings" | ||
"sync" | ||
) | ||
|
||
type GeoMatcher struct { | ||
geoLoader GeoLoader | ||
geoSiteMatcher map[string]hostMatcher | ||
siteMatcherLock sync.Mutex | ||
geoIpMatcher map[string]hostMatcher | ||
ipMatcherLock sync.Mutex | ||
} | ||
|
||
func NewGeoMatcher(geoSiteFilename, geoIpFilename string) (*GeoMatcher, error) { | ||
geoLoader := NewDefaultGeoLoader(geoSiteFilename, geoIpFilename) | ||
|
||
return &GeoMatcher{ | ||
geoLoader: geoLoader, | ||
geoSiteMatcher: make(map[string]hostMatcher), | ||
geoIpMatcher: make(map[string]hostMatcher), | ||
}, nil | ||
} | ||
|
||
func (g *GeoMatcher) MatchGeoIp(ip, condition string) bool { | ||
g.ipMatcherLock.Lock() | ||
defer g.ipMatcherLock.Unlock() | ||
|
||
matcher, ok := g.geoIpMatcher[condition] | ||
if !ok { | ||
// GeoIP matcher | ||
condition = strings.ToLower(condition) | ||
country := condition | ||
if len(country) == 0 { | ||
return false | ||
} | ||
gMap, err := g.geoLoader.LoadGeoIP() | ||
if err != nil { | ||
return false | ||
} | ||
list, ok := gMap[country] | ||
if !ok || list == nil { | ||
return false | ||
} | ||
matcher, err = newGeoIPMatcher(list) | ||
if err != nil { | ||
return false | ||
} | ||
g.geoIpMatcher[condition] = matcher | ||
} | ||
parseIp := net.ParseIP(ip) | ||
if parseIp == nil { | ||
return false | ||
} | ||
ipv4 := parseIp.To4() | ||
if ipv4 != nil { | ||
return matcher.Match(HostInfo{IPv4: ipv4}) | ||
} | ||
ipv6 := parseIp.To16() | ||
if ipv6 != nil { | ||
return matcher.Match(HostInfo{IPv6: ipv6}) | ||
} | ||
return false | ||
} | ||
|
||
func (g *GeoMatcher) MatchGeoSite(site, condition string) bool { | ||
g.siteMatcherLock.Lock() | ||
defer g.siteMatcherLock.Unlock() | ||
|
||
matcher, ok := g.geoSiteMatcher[condition] | ||
if !ok { | ||
// MatchGeoSite matcher | ||
condition = strings.ToLower(condition) | ||
name, attrs := parseGeoSiteName(condition) | ||
if len(name) == 0 { | ||
return false | ||
} | ||
gMap, err := g.geoLoader.LoadGeoSite() | ||
if err != nil { | ||
return false | ||
} | ||
list, ok := gMap[name] | ||
if !ok || list == nil { | ||
return false | ||
} | ||
matcher, err = newGeositeMatcher(list, attrs) | ||
if err != nil { | ||
return false | ||
} | ||
g.geoSiteMatcher[condition] = matcher | ||
} | ||
return matcher.Match(HostInfo{Name: site}) | ||
} | ||
|
||
func (g *GeoMatcher) LoadGeoSite() error { | ||
_, err := g.geoLoader.LoadGeoSite() | ||
return err | ||
} | ||
|
||
func (g *GeoMatcher) LoadGeoIP() error { | ||
_, err := g.geoLoader.LoadGeoIP() | ||
return err | ||
} | ||
|
||
func parseGeoSiteName(s string) (string, []string) { | ||
parts := strings.Split(s, "@") | ||
base := strings.TrimSpace(parts[0]) | ||
attrs := parts[1:] | ||
for i := range attrs { | ||
attrs[i] = strings.TrimSpace(attrs[i]) | ||
} | ||
return base, attrs | ||
} |
Oops, something went wrong.