package main import ( "fmt" "io" "net" "net/http" "os" "path/filepath" "github.com/oschwald/maxminddb-golang" ) type Geo struct { asn *maxminddb.Reader city *maxminddb.Reader } type ASNResult struct { AutonomousSystemNumber uint `maxminddb:"autonomous_system_number"` AutonomousSystemOrganization string `maxminddb:"autonomous_system_organization"` } type CityResult struct { Continent struct { Names map[string]string `maxminddb:"names"` } `maxminddb:"continent"` Country struct { Names map[string]string `maxminddb:"names"` } `maxminddb:"country"` City struct { Names map[string]string `maxminddb:"names"` } `maxminddb:"city"` } func DownloadFile(url, dest string) error { if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil { return err } fmt.Printf("downloading %s -> %s\n", url, dest) resp, err := http.Get(url) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != 200 { return fmt.Errorf("HTTP %d for %s", resp.StatusCode, url) } out, err := os.Create(dest) if err != nil { return err } defer out.Close() _, err = io.Copy(out, resp.Body) return err } func ensureMMDB(url, path string) error { if _, err := os.Stat(path); err == nil { return nil } return DownloadFile(url, path) } func InitGeo(cfg *GeoConfig) (*Geo, error) { if err := ensureMMDB(cfg.AsnURL, cfg.AsnPath); err != nil { return nil, fmt.Errorf("ASN DB: %w", err) } if err := ensureMMDB(cfg.CityURL, cfg.CityPath); err != nil { return nil, fmt.Errorf("City DB: %w", err) } asn, err := maxminddb.Open(cfg.AsnPath) if err != nil { return nil, fmt.Errorf("open ASN DB: %w", err) } city, err := maxminddb.Open(cfg.CityPath) if err != nil { asn.Close() return nil, fmt.Errorf("open City DB: %w", err) } return &Geo{asn: asn, city: city}, nil } func (g *Geo) Close() { g.asn.Close() g.city.Close() } type GeoInfo struct { ASN string Org string Continent string Country string City string ReverseDNS string } func (g *Geo) Lookup(ip net.IP) GeoInfo { info := GeoInfo{ReverseDNS: "Unknown"} hosts, err := net.LookupAddr(ip.String()) if err == nil && len(hosts) > 0 { info.ReverseDNS = hosts[0] } var asn ASNResult if err := g.asn.Lookup(ip, &asn); err == nil { if asn.AutonomousSystemNumber > 0 { info.ASN = fmt.Sprintf("AS%d", asn.AutonomousSystemNumber) info.Org = asn.AutonomousSystemOrganization } } var cr CityResult if err := g.city.Lookup(ip, &cr); err == nil { info.Continent = cr.Continent.Names["en"] info.Country = cr.Country.Names["en"] info.City = cr.City.Names["en"] } return info } type GeoConfig struct { AsnURL string CityURL string AsnPath string CityPath string }