136 lines
2.7 KiB
Go
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
|
|
}
|