7a3f6e945d
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.
194 lines
4.6 KiB
Go
194 lines
4.6 KiB
Go
package geo
|
|
|
|
import (
|
|
"sync/atomic"
|
|
"testing"
|
|
|
|
"git.difuse.io/Difuse/Mellaris/ruleset/builtins/geo/v2geo"
|
|
)
|
|
|
|
type fakeGeoLoader struct {
|
|
geoip map[string]*v2geo.GeoIP
|
|
geosite map[string]*v2geo.GeoSite
|
|
}
|
|
|
|
func (l *fakeGeoLoader) LoadGeoIP() (map[string]*v2geo.GeoIP, error) {
|
|
return l.geoip, nil
|
|
}
|
|
|
|
func (l *fakeGeoLoader) LoadGeoSite() (map[string]*v2geo.GeoSite, error) {
|
|
return l.geosite, nil
|
|
}
|
|
|
|
func TestGeoMatcher_MatchGeoIp_Cached(t *testing.T) {
|
|
loader := &fakeGeoLoader{
|
|
geoip: map[string]*v2geo.GeoIP{
|
|
"us": {
|
|
Cidr: []*v2geo.CIDR{
|
|
{Ip: ipv4(8, 8, 8, 0), Prefix: 24},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
g := NewGeoMatcher("", "")
|
|
g.geoLoader = loader
|
|
|
|
if !g.MatchGeoIp("8.8.8.8", "US") {
|
|
t.Error("MatchGeoIp should match 8.8.8.8 in US range")
|
|
}
|
|
if g.MatchGeoIp("9.9.9.9", "US") {
|
|
t.Error("MatchGeoIp should not match 9.9.9.9 in US range")
|
|
}
|
|
}
|
|
|
|
func TestGeoMatcher_MatchGeoIp_EmptyCondition(t *testing.T) {
|
|
g := NewGeoMatcher("", "")
|
|
if g.MatchGeoIp("1.2.3.4", "") {
|
|
t.Error("MatchGeoIp with empty condition should return false")
|
|
}
|
|
}
|
|
|
|
func TestGeoMatcher_MatchGeoIp_InvalidIP(t *testing.T) {
|
|
g := NewGeoMatcher("", "")
|
|
if g.MatchGeoIp("not-an-ip", "us") {
|
|
t.Error("MatchGeoIp with invalid IP should return false")
|
|
}
|
|
}
|
|
|
|
func TestGeoMatcher_MatchGeoIp_MissingCountry(t *testing.T) {
|
|
loader := &fakeGeoLoader{
|
|
geoip: map[string]*v2geo.GeoIP{},
|
|
}
|
|
g := NewGeoMatcher("", "")
|
|
g.geoLoader = loader
|
|
|
|
if g.MatchGeoIp("8.8.8.8", "us") {
|
|
t.Error("MatchGeoIp for missing country should return false")
|
|
}
|
|
}
|
|
|
|
func TestGeoMatcher_MatchGeoSite(t *testing.T) {
|
|
loader := &fakeGeoLoader{
|
|
geosite: map[string]*v2geo.GeoSite{
|
|
"openai": {
|
|
Domain: []*v2geo.Domain{
|
|
{Type: v2geo.Domain_Plain, Value: "openai"},
|
|
{Type: v2geo.Domain_Full, Value: "chatgpt.com"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
g := NewGeoMatcher("", "")
|
|
g.geoLoader = loader
|
|
|
|
if !g.MatchGeoSite("api.openai.com", "openai") {
|
|
t.Error("MatchGeoSite should match via plain domain")
|
|
}
|
|
if !g.MatchGeoSite("chatgpt.com", "openai") {
|
|
t.Error("MatchGeoSite should match via full domain")
|
|
}
|
|
if g.MatchGeoSite("google.com", "openai") {
|
|
t.Error("MatchGeoSite should not match unrelated host")
|
|
}
|
|
}
|
|
|
|
func TestGeoMatcher_MatchGeoSite_EmptyCondition(t *testing.T) {
|
|
g := NewGeoMatcher("", "")
|
|
if g.MatchGeoSite("test.com", "") {
|
|
t.Error("MatchGeoSite with empty condition should return false")
|
|
}
|
|
}
|
|
|
|
func TestGeoMatcher_MatchGeoSite_MissingSite(t *testing.T) {
|
|
loader := &fakeGeoLoader{
|
|
geosite: map[string]*v2geo.GeoSite{},
|
|
}
|
|
g := NewGeoMatcher("", "")
|
|
g.geoLoader = loader
|
|
|
|
if g.MatchGeoSite("test.com", "nonexistent") {
|
|
t.Error("MatchGeoSite for missing site should return false")
|
|
}
|
|
}
|
|
|
|
func TestGeoMatcher_MatchGeoSiteSet(t *testing.T) {
|
|
loader := &fakeGeoLoader{
|
|
geosite: map[string]*v2geo.GeoSite{
|
|
"openai": {
|
|
Domain: []*v2geo.Domain{
|
|
{Type: v2geo.Domain_Plain, Value: "openai"},
|
|
},
|
|
},
|
|
"google": {
|
|
Domain: []*v2geo.Domain{
|
|
{Type: v2geo.Domain_RootDomain, Value: "google.com"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
g := NewGeoMatcher("", "")
|
|
g.geoLoader = loader
|
|
|
|
set := &SiteConditionSet{Conditions: []string{" google ", "openai", "OPENAI"}}
|
|
if !g.MatchGeoSiteSet("api.openai.com", set) {
|
|
t.Error("MatchGeoSiteSet should match openai")
|
|
}
|
|
if !g.MatchGeoSiteSet("mail.google.com", set) {
|
|
t.Error("MatchGeoSiteSet should match google")
|
|
}
|
|
if g.MatchGeoSiteSet("example.com", set) {
|
|
t.Error("MatchGeoSiteSet should not match unrelated host")
|
|
}
|
|
}
|
|
|
|
type countingMatcher struct {
|
|
calls *atomic.Uint64
|
|
match bool
|
|
}
|
|
|
|
func (m countingMatcher) Match(host HostInfo) bool {
|
|
_ = host
|
|
m.calls.Add(1)
|
|
return m.match
|
|
}
|
|
|
|
func TestGeoMatcher_MatchGeoSite_UsesResultCache(t *testing.T) {
|
|
g := NewGeoMatcher("", "")
|
|
var calls atomic.Uint64
|
|
g.geoSiteMatcher["openai"] = countingMatcher{calls: &calls, match: true}
|
|
|
|
if !g.MatchGeoSite("api.openai.com", "openai") {
|
|
t.Fatal("expected match")
|
|
}
|
|
if !g.MatchGeoSite("api.openai.com", "openai") {
|
|
t.Fatal("expected cached match")
|
|
}
|
|
if got := calls.Load(); got != 1 {
|
|
t.Fatalf("matcher calls=%d want=1", got)
|
|
}
|
|
}
|
|
|
|
func TestGeoMatcher_MatchGeoSiteSet_UsesResultCache(t *testing.T) {
|
|
g := NewGeoMatcher("", "")
|
|
var calls atomic.Uint64
|
|
g.geoSiteSets["openai\x1fyoutube"] = []hostMatcher{
|
|
countingMatcher{calls: &calls, match: false},
|
|
countingMatcher{calls: &calls, match: true},
|
|
}
|
|
|
|
set := &SiteConditionSet{Conditions: []string{"youtube", "openai"}}
|
|
if !g.MatchGeoSiteSet("www.youtube.com", set) {
|
|
t.Fatal("expected match")
|
|
}
|
|
if !g.MatchGeoSiteSet("www.youtube.com", set) {
|
|
t.Fatal("expected cached match")
|
|
}
|
|
if got := calls.Load(); got != 2 {
|
|
t.Fatalf("matcher calls=%d want=2", got)
|
|
}
|
|
}
|
|
|
|
func ipv4(a, b, c, d byte) []byte {
|
|
return []byte{a, b, c, d}
|
|
}
|