package geodb import ( _ "embed" "net/http" "os" "sync" "time" "imuslab.com/zoraxy/mod/database" "imuslab.com/zoraxy/mod/info/logger" "imuslab.com/zoraxy/mod/netutils" "imuslab.com/zoraxy/mod/utils" ) //go:embed geoipv4.csv var geoipv4 []byte //Geodb dataset for ipv4 //go:embed geoipv6.csv var geoipv6 []byte //Geodb dataset for ipv6 type Store struct { geodb [][]string //Parsed geodb list geodbIpv6 [][]string //Parsed geodb list for ipv6 geotrie *trie geotrieIpv6 *trie sysdb *database.Database slowLookupCacheIpv4 sync.Map //Cache for slow lookup, ip -> cc slowLookupCacheIpv6 sync.Map //Cache for slow lookup ipv6, ip -> cc cacheClearTicker *time.Ticker //Ticker for clearing cache cacheClearTickerStopChan chan bool //Stop channel for cache clear ticker option *StoreOptions } type StoreOptions struct { AllowSlowIpv4LookUp bool AllowSlowIpv6Lookup bool Logger *logger.Logger SlowLookupCacheClearInterval time.Duration //Clear slow lookup cache interval } type CountryInfo struct { CountryIsoCode string ContinetCode string } func NewGeoDb(sysdb *database.Database, option *StoreOptions) (*Store, error) { //Check if external geoDB data is available if utils.FileExists("./conf/geodb/geoipv4.csv") { externalV4Db, err := os.ReadFile("./conf/geodb/geoipv4.csv") if err == nil { option.Logger.PrintAndLog("GeoDB", "External GeoDB data found, using external IPv4 GeoIP data", nil) geoipv4 = externalV4Db } } if utils.FileExists("./conf/geodb/geoipv6.csv") { externalV6Db, err := os.ReadFile("./conf/geodb/geoipv6.csv") if err == nil { option.Logger.PrintAndLog("GeoDB", "External GeoDB data found, using external IPv6 GeoIP data", nil) geoipv6 = externalV6Db } } parsedGeoData, err := parseCSV(geoipv4) if err != nil { return nil, err } parsedGeoDataIpv6, err := parseCSV(geoipv6) if err != nil { return nil, err } var ipv4Trie *trie if !option.AllowSlowIpv4LookUp { ipv4Trie = constrctTrieTree(parsedGeoData) } var ipv6Trie *trie if !option.AllowSlowIpv6Lookup { ipv6Trie = constrctTrieTree(parsedGeoDataIpv6) } if option.SlowLookupCacheClearInterval == 0 { option.SlowLookupCacheClearInterval = 30 * time.Minute } //Create a new store thisGeoDBStore := &Store{ geodb: parsedGeoData, geotrie: ipv4Trie, geodbIpv6: parsedGeoDataIpv6, geotrieIpv6: ipv6Trie, sysdb: sysdb, slowLookupCacheIpv4: sync.Map{}, slowLookupCacheIpv6: sync.Map{}, cacheClearTicker: time.NewTicker(option.SlowLookupCacheClearInterval), cacheClearTickerStopChan: make(chan bool), option: option, } //Start cache clear ticker if option.AllowSlowIpv4LookUp || option.AllowSlowIpv6Lookup { go func(store *Store) { for { select { case <-store.cacheClearTickerStopChan: return case <-thisGeoDBStore.cacheClearTicker.C: thisGeoDBStore.slowLookupCacheIpv4 = sync.Map{} thisGeoDBStore.slowLookupCacheIpv6 = sync.Map{} } } }(thisGeoDBStore) } return thisGeoDBStore, nil } func (s *Store) ResolveCountryCodeFromIP(ipstring string) (*CountryInfo, error) { cc := s.search(ipstring) return &CountryInfo{ CountryIsoCode: cc, ContinetCode: "", }, nil } // Close the store func (s *Store) Close() { if s.option.AllowSlowIpv4LookUp || s.option.AllowSlowIpv6Lookup { //Stop cache clear ticker s.cacheClearTickerStopChan <- true } } func (s *Store) GetRequesterCountryISOCode(r *http.Request) string { ipAddr := netutils.GetRequesterIP(r) if ipAddr == "" { return "" } if netutils.IsPrivateIP(ipAddr) { return "LAN" } countryCode, err := s.ResolveCountryCodeFromIP(ipAddr) if err != nil { return "" } return countryCode.CountryIsoCode }