Files
Mellaris/ruleset/builtins/geo/geo_matcher.go
T
hayzam 7a3f6e945d Improves flow handling and adds runtime stats APIs
Refactors TCP and UDP flow managers to enhance analyzer selection and flow binding accuracy, including O(1) UDP stream rebinding by 5-tuple.
Introduces runtime stats tracking for engine and ruleset operations, exposing new APIs for granular performance and error metrics.
Optimizes GeoMatcher with result caching and supports efficient geosite set matching, reducing redundant computation in ruleset expressions.
2026-05-13 06:10:38 +05:30

293 lines
6.3 KiB
Go

package geo
import (
"container/list"
"net"
"sort"
"strings"
"sync"
)
const (
geoSiteResultCacheSize = 1 << 16
geoSiteSetResultCacheSize = 1 << 16
)
type GeoMatcher struct {
geoLoader GeoLoader
geoSiteMatcher map[string]hostMatcher
siteMatcherLock sync.RWMutex
geoSiteSets map[string][]hostMatcher
siteSetLock sync.RWMutex
geoIpMatcher map[string]hostMatcher
ipMatcherLock sync.RWMutex
geoSiteResult *boolLRUCache
geoSiteSetCache *boolLRUCache
}
func NewGeoMatcher(geoSiteFilename, geoIpFilename string) *GeoMatcher {
return &GeoMatcher{
geoLoader: NewDefaultGeoLoader(geoSiteFilename, geoIpFilename),
geoSiteMatcher: make(map[string]hostMatcher),
geoSiteSets: make(map[string][]hostMatcher),
geoIpMatcher: make(map[string]hostMatcher),
geoSiteResult: newBoolLRUCache(geoSiteResultCacheSize),
geoSiteSetCache: newBoolLRUCache(geoSiteSetResultCacheSize),
}
}
func (g *GeoMatcher) MatchGeoIp(ip, condition string) bool {
matcher, ok := g.getOrCreateGeoIPMatcher(condition)
if !ok || matcher == nil {
return false
}
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 {
conditionKey := strings.TrimSpace(strings.ToLower(condition))
if conditionKey == "" {
return false
}
cacheKey := site + "\x1f" + conditionKey
if v, ok := g.geoSiteResult.Get(cacheKey); ok {
return v
}
matcher, ok := g.getOrCreateGeoSiteMatcher(condition)
if !ok || matcher == nil {
return false
}
result := matcher.Match(HostInfo{Name: site})
g.geoSiteResult.Set(cacheKey, result)
return result
}
func (g *GeoMatcher) MatchGeoSiteSet(site string, set *SiteConditionSet) bool {
if set == nil {
return false
}
conditions := normalizeGeoSiteSetConditions(set.Conditions)
if len(conditions) == 0 {
return false
}
key := strings.Join(conditions, "\x1f")
cacheKey := site + "\x1e" + key
if v, ok := g.geoSiteSetCache.Get(cacheKey); ok {
return v
}
g.siteSetLock.RLock()
matchers, ok := g.geoSiteSets[key]
g.siteSetLock.RUnlock()
if !ok {
compiled := make([]hostMatcher, 0, len(conditions))
for _, condition := range conditions {
m, ok := g.getOrCreateGeoSiteMatcher(condition)
if ok && m != nil {
compiled = append(compiled, m)
}
}
g.siteSetLock.Lock()
if existing, exists := g.geoSiteSets[key]; exists {
matchers = existing
} else {
g.geoSiteSets[key] = compiled
matchers = compiled
}
g.siteSetLock.Unlock()
}
if len(matchers) == 0 {
return false
}
host := HostInfo{Name: site}
for _, matcher := range matchers {
if matcher.Match(host) {
g.geoSiteSetCache.Set(cacheKey, true)
return true
}
}
g.geoSiteSetCache.Set(cacheKey, false)
return false
}
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
}
func (g *GeoMatcher) getOrCreateGeoSiteMatcher(condition string) (hostMatcher, bool) {
condition = strings.TrimSpace(strings.ToLower(condition))
if condition == "" {
return nil, false
}
g.siteMatcherLock.RLock()
matcher, ok := g.geoSiteMatcher[condition]
g.siteMatcherLock.RUnlock()
if ok {
return matcher, true
}
name, attrs := parseGeoSiteName(condition)
if len(name) == 0 {
return nil, false
}
gMap, err := g.geoLoader.LoadGeoSite()
if err != nil {
return nil, false
}
list, ok := gMap[name]
if !ok || list == nil {
return nil, false
}
matcher, err = newGeositeMatcher(list, attrs)
if err != nil {
return nil, false
}
g.siteMatcherLock.Lock()
if existing, exists := g.geoSiteMatcher[condition]; exists {
matcher = existing
} else {
g.geoSiteMatcher[condition] = matcher
}
g.siteMatcherLock.Unlock()
return matcher, true
}
func (g *GeoMatcher) getOrCreateGeoIPMatcher(condition string) (hostMatcher, bool) {
condition = strings.TrimSpace(strings.ToLower(condition))
if condition == "" {
return nil, false
}
g.ipMatcherLock.RLock()
matcher, ok := g.geoIpMatcher[condition]
g.ipMatcherLock.RUnlock()
if ok {
return matcher, true
}
gMap, err := g.geoLoader.LoadGeoIP()
if err != nil {
return nil, false
}
list, ok := gMap[condition]
if !ok || list == nil {
return nil, false
}
matcher, err = newGeoIPMatcher(list)
if err != nil {
return nil, false
}
g.ipMatcherLock.Lock()
if existing, exists := g.geoIpMatcher[condition]; exists {
matcher = existing
} else {
g.geoIpMatcher[condition] = matcher
}
g.ipMatcherLock.Unlock()
return matcher, true
}
func normalizeGeoSiteSetConditions(in []string) []string {
if len(in) == 0 {
return nil
}
out := make([]string, 0, len(in))
seen := make(map[string]struct{}, len(in))
for _, v := range in {
s := strings.TrimSpace(strings.ToLower(v))
if s == "" {
continue
}
if _, ok := seen[s]; ok {
continue
}
seen[s] = struct{}{}
out = append(out, s)
}
sort.Strings(out)
return out
}
type boolLRUCache struct {
mu sync.Mutex
cap int
ll *list.List
items map[string]*list.Element
}
type boolCacheEntry struct {
key string
value bool
}
func newBoolLRUCache(capacity int) *boolLRUCache {
if capacity <= 0 {
capacity = 1
}
return &boolLRUCache{
cap: capacity,
ll: list.New(),
items: make(map[string]*list.Element, capacity),
}
}
func (c *boolLRUCache) Get(key string) (bool, bool) {
c.mu.Lock()
defer c.mu.Unlock()
if ele, ok := c.items[key]; ok {
c.ll.MoveToFront(ele)
entry := ele.Value.(boolCacheEntry)
return entry.value, true
}
return false, false
}
func (c *boolLRUCache) Set(key string, value bool) {
c.mu.Lock()
defer c.mu.Unlock()
if ele, ok := c.items[key]; ok {
ele.Value = boolCacheEntry{key: key, value: value}
c.ll.MoveToFront(ele)
return
}
ele := c.ll.PushFront(boolCacheEntry{key: key, value: value})
c.items[key] = ele
if c.ll.Len() <= c.cap {
return
}
back := c.ll.Back()
if back == nil {
return
}
entry := back.Value.(boolCacheEntry)
delete(c.items, entry.key)
c.ll.Remove(back)
}