nutils/geo.go
2026-09-02 12:53:50 -03:00

136 lines
2.7 KiB
Go

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
}