Files
Archive/geoip/plugin/maxmind/common_out.go
2025-09-06 20:35:55 +02:00

187 lines
5.1 KiB
Go

package maxmind
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/Loyalsoldier/geoip/lib"
"github.com/oschwald/geoip2-golang"
"github.com/oschwald/maxminddb-golang"
)
var (
defaultGeoLite2CountryMMDBOutputName = "Country.mmdb"
defaultMaxmindOutputDir = filepath.Join("./", "output", "maxmind")
defaultDBIPOutputDir = filepath.Join("./", "output", "db-ip")
defaultIPInfoOutputDir = filepath.Join("./", "output", "ipinfo")
)
// Reference: https://ipinfo.io/lite
type ipInfoLite struct {
ASN string `maxminddb:"asn"`
ASName string `maxminddb:"as_name"`
ASDomain string `maxminddb:"as_domain"`
Continent string `maxminddb:"continent"`
ContinentCode string `maxminddb:"continent_code"`
Country string `maxminddb:"country"`
CountryCode string `maxminddb:"country_code"`
}
func newGeoLite2CountryMMDBOut(iType string, iDesc string, action lib.Action, data json.RawMessage) (lib.OutputConverter, error) {
var tmp struct {
OutputName string `json:"outputName"`
OutputDir string `json:"outputDir"`
Want []string `json:"wantedList"`
Overwrite []string `json:"overwriteList"`
Exclude []string `json:"excludedList"`
OnlyIPType lib.IPType `json:"onlyIPType"`
SourceMMDBURI string `json:"sourceMMDBURI"`
}
if len(data) > 0 {
if err := json.Unmarshal(data, &tmp); err != nil {
return nil, err
}
}
if tmp.OutputName == "" {
tmp.OutputName = defaultGeoLite2CountryMMDBOutputName
}
if tmp.OutputDir == "" {
switch iType {
case TypeGeoLite2CountryMMDBOut:
tmp.OutputDir = defaultMaxmindOutputDir
case TypeDBIPCountryMMDBOut:
tmp.OutputDir = defaultDBIPOutputDir
case TypeIPInfoCountryMMDBOut:
tmp.OutputDir = defaultIPInfoOutputDir
}
}
return &GeoLite2CountryMMDBOut{
Type: iType,
Action: action,
Description: iDesc,
OutputName: tmp.OutputName,
OutputDir: tmp.OutputDir,
Want: tmp.Want,
Overwrite: tmp.Overwrite,
Exclude: tmp.Exclude,
OnlyIPType: tmp.OnlyIPType,
SourceMMDBURI: tmp.SourceMMDBURI,
}, nil
}
func (g *GeoLite2CountryMMDBOut) GetExtraInfo() (map[string]any, error) {
if strings.TrimSpace(g.SourceMMDBURI) == "" {
return nil, nil
}
var content []byte
var err error
switch {
case strings.HasPrefix(strings.ToLower(g.SourceMMDBURI), "http://"), strings.HasPrefix(strings.ToLower(g.SourceMMDBURI), "https://"):
content, err = lib.GetRemoteURLContent(g.SourceMMDBURI)
default:
content, err = os.ReadFile(g.SourceMMDBURI)
}
if err != nil {
return nil, err
}
db, err := maxminddb.FromBytes(content)
if err != nil {
return nil, err
}
defer db.Close()
infoList := make(map[string]any)
networks := db.Networks(maxminddb.SkipAliasedNetworks)
for networks.Next() {
switch g.Type {
case TypeGeoLite2CountryMMDBOut, TypeDBIPCountryMMDBOut:
var record geoip2.Country
_, err := networks.Network(&record)
if err != nil {
return nil, err
}
switch {
case strings.TrimSpace(record.Country.IsoCode) != "":
countryCode := strings.ToUpper(strings.TrimSpace(record.Country.IsoCode))
if _, found := infoList[countryCode]; !found {
infoList[countryCode] = geoip2.Country{
Continent: record.Continent,
Country: record.Country,
}
}
case strings.TrimSpace(record.RegisteredCountry.IsoCode) != "":
countryCode := strings.ToUpper(strings.TrimSpace(record.RegisteredCountry.IsoCode))
if _, found := infoList[countryCode]; !found {
infoList[countryCode] = geoip2.Country{
Continent: record.Continent,
Country: record.RegisteredCountry,
}
}
case strings.TrimSpace(record.RepresentedCountry.IsoCode) != "":
countryCode := strings.ToUpper(strings.TrimSpace(record.RepresentedCountry.IsoCode))
if _, found := infoList[countryCode]; !found {
infoList[countryCode] = geoip2.Country{
Continent: record.Continent,
Country: struct {
Names map[string]string `maxminddb:"names"`
IsoCode string `maxminddb:"iso_code"`
GeoNameID uint `maxminddb:"geoname_id"`
IsInEuropeanUnion bool `maxminddb:"is_in_european_union"`
}{
Names: record.RepresentedCountry.Names,
IsoCode: record.RepresentedCountry.IsoCode,
GeoNameID: record.RepresentedCountry.GeoNameID,
IsInEuropeanUnion: record.RepresentedCountry.IsInEuropeanUnion,
},
}
}
}
case TypeIPInfoCountryMMDBOut:
var record ipInfoLite
_, err := networks.Network(&record)
if err != nil {
return nil, err
}
countryCode := strings.ToUpper(strings.TrimSpace(record.CountryCode))
if _, found := infoList[countryCode]; !found {
record.ASN = ""
record.ASName = ""
record.ASDomain = ""
infoList[countryCode] = record
}
default:
return nil, lib.ErrNotSupportedFormat
}
}
if networks.Err() != nil {
return nil, networks.Err()
}
if len(infoList) == 0 {
return nil, fmt.Errorf("❌ [type %s | action %s] no extra info found in the source MMDB file: %s", g.Type, g.Action, g.SourceMMDBURI)
}
return infoList, nil
}