mirror of
https://github.com/skidoodle/ipinfo.git
synced 2026-04-28 17:37:37 +02:00
add domain whois/dns support, refactor codebase
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"ipinfo/internal/db"
|
||||
|
||||
"github.com/likexian/whois-parser"
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
// LookupIPData looks up IP data in the databases with caching.
|
||||
func LookupIPData(geoIP *db.GeoIPManager, ip net.IP) *DataStruct {
|
||||
ipStr := ip.String()
|
||||
if data, found := cache.Get(ipStr); found {
|
||||
return data.(*DataStruct)
|
||||
}
|
||||
|
||||
var cityRecord struct {
|
||||
City struct {
|
||||
Names map[string]string `maxminddb:"names"`
|
||||
} `maxminddb:"city"`
|
||||
Subdivisions []struct {
|
||||
Names map[string]string `maxminddb:"names"`
|
||||
} `maxminddb:"subdivisions"`
|
||||
Country struct {
|
||||
IsoCode string `maxminddb:"iso_code"`
|
||||
} `maxminddb:"country"`
|
||||
Location struct {
|
||||
Latitude float64 `maxminddb:"latitude"`
|
||||
Longitude float64 `maxminddb:"longitude"`
|
||||
Timezone string `maxminddb:"time_zone"`
|
||||
} `maxminddb:"location"`
|
||||
}
|
||||
|
||||
if err := geoIP.GetCityDB().Lookup(ip, &cityRecord); err != nil {
|
||||
slog.Error("failed to look up city data", "err", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
var asnRecord db.ASNRecord
|
||||
if err := geoIP.GetASNDB().Lookup(ip, &asnRecord); err != nil {
|
||||
slog.Error("failed to look up asn data", "err", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
hostname, _ := net.LookupAddr(ipStr)
|
||||
hostnameStr := ""
|
||||
if len(hostname) > 0 {
|
||||
hostnameStr = strings.TrimSuffix(hostname[0], ".")
|
||||
}
|
||||
|
||||
var region *string
|
||||
if len(cityRecord.Subdivisions) > 0 {
|
||||
region = ToPtr(cityRecord.Subdivisions[0].Names["en"])
|
||||
}
|
||||
|
||||
data := &DataStruct{
|
||||
IP: ToPtr(ipStr),
|
||||
Hostname: ToPtr(hostnameStr),
|
||||
Org: ToPtr(fmt.Sprintf("AS%d %s", asnRecord.AutonomousSystemNumber, asnRecord.AutonomousSystemOrganization)),
|
||||
City: ToPtr(cityRecord.City.Names["en"]),
|
||||
Region: region,
|
||||
Country: ToPtr(cityRecord.Country.IsoCode),
|
||||
Timezone: ToPtr(cityRecord.Location.Timezone),
|
||||
Loc: ToPtr(fmt.Sprintf("%.4f,%.4f", cityRecord.Location.Latitude, cityRecord.Location.Longitude)),
|
||||
}
|
||||
|
||||
cache.Set(ipStr, data)
|
||||
return data
|
||||
}
|
||||
|
||||
// LookupASNData looks up ASN data in the databases with caching.
|
||||
func LookupASNData(geoIP *db.GeoIPManager, targetASN uint) (*ASNDataResponse, error) {
|
||||
if data, found := cache.Get(targetASN); found {
|
||||
return data.(*ASNDataResponse), nil
|
||||
}
|
||||
|
||||
prefixes := geoIP.GetASNPrefixes(targetASN)
|
||||
if len(prefixes) == 0 {
|
||||
return nil, fmt.Errorf("no prefixes found for as%d in the database", targetASN)
|
||||
}
|
||||
|
||||
var orgName string
|
||||
var record db.ASNRecord
|
||||
if err := geoIP.GetASNDB().Lookup(prefixes[0].IP, &record); err == nil {
|
||||
orgName = record.AutonomousSystemOrganization
|
||||
}
|
||||
|
||||
var ipv4Prefixes, ipv6Prefixes []string
|
||||
for _, prefix := range prefixes {
|
||||
prefixStr := prefix.String()
|
||||
if strings.Contains(prefixStr, ":") {
|
||||
ipv6Prefixes = append(ipv6Prefixes, prefixStr)
|
||||
} else {
|
||||
ipv4Prefixes = append(ipv4Prefixes, prefixStr)
|
||||
}
|
||||
}
|
||||
sort.Strings(ipv4Prefixes)
|
||||
sort.Strings(ipv6Prefixes)
|
||||
|
||||
response := &ASNDataResponse{
|
||||
Details: ASNDetails{
|
||||
ASN: targetASN,
|
||||
Name: orgName,
|
||||
},
|
||||
Prefixes: ASNPrefixInfo{
|
||||
IPv4: ipv4Prefixes,
|
||||
IPv6: ipv6Prefixes,
|
||||
},
|
||||
}
|
||||
|
||||
cache.Set(targetASN, response)
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// LookupDomainData looks up domain data with caching.
|
||||
func LookupDomainData(domain string) (*DomainDataResponse, error) {
|
||||
if data, found := cache.Get(domain); found {
|
||||
return data.(*DomainDataResponse), nil
|
||||
}
|
||||
|
||||
eTLD, err := publicsuffix.EffectiveTLDPlusOne(domain)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid domain: %w", err)
|
||||
}
|
||||
|
||||
whoisRaw, err := performWhoisWithFallback(eTLD)
|
||||
var whoisResult interface{}
|
||||
if err != nil {
|
||||
slog.Error("whois lookup failed after fallback", "domain", eTLD, "err", err)
|
||||
whoisResult = fmt.Sprintf("whois lookup failed: %v", err)
|
||||
} else {
|
||||
parsed, parseErr := whoisparser.Parse(whoisRaw)
|
||||
if parseErr != nil {
|
||||
slog.Warn("failed to parse whois data, returning raw text", "domain", eTLD, "err", parseErr)
|
||||
whoisResult = whoisRaw
|
||||
} else {
|
||||
whoisResult = formatWhois(parsed)
|
||||
}
|
||||
}
|
||||
|
||||
dnsData := DNSData{}
|
||||
var wg sync.WaitGroup
|
||||
var mu sync.Mutex
|
||||
|
||||
lookupTasks := []func(){
|
||||
func() { // A and AAAA records
|
||||
ips, err := net.LookupIP(domain)
|
||||
if err == nil {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
for _, ip := range ips {
|
||||
if ip.To4() != nil {
|
||||
dnsData.A = append(dnsData.A, ip.String())
|
||||
} else {
|
||||
dnsData.AAAA = append(dnsData.AAAA, ip.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
func() { // CNAME record
|
||||
cname, err := net.LookupCNAME(domain)
|
||||
if err == nil && cname != domain+"." && cname != "" {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
dnsData.CNAME = strings.TrimSuffix(cname, ".")
|
||||
}
|
||||
},
|
||||
func() { // MX records
|
||||
mxs, err := net.LookupMX(domain)
|
||||
if err == nil {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
for _, mx := range mxs {
|
||||
dnsData.MX = append(dnsData.MX, fmt.Sprintf("%d %s", mx.Pref, strings.TrimSuffix(mx.Host, ".")))
|
||||
}
|
||||
}
|
||||
},
|
||||
func() { // TXT records
|
||||
txts, err := net.LookupTXT(domain)
|
||||
if err == nil {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
dnsData.TXT = append(dnsData.TXT, txts...)
|
||||
}
|
||||
},
|
||||
func() { // NS records
|
||||
nss, err := net.LookupNS(eTLD)
|
||||
if err == nil {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
for _, ns := range nss {
|
||||
dnsData.NS = append(dnsData.NS, strings.TrimSuffix(ns.Host, "."))
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
wg.Add(len(lookupTasks))
|
||||
for _, task := range lookupTasks {
|
||||
go func(t func()) {
|
||||
defer wg.Done()
|
||||
t()
|
||||
}(task)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
response := &DomainDataResponse{
|
||||
Whois: whoisResult,
|
||||
DNS: dnsData,
|
||||
}
|
||||
|
||||
cache.Set(domain, response)
|
||||
return response, nil
|
||||
}
|
||||
Reference in New Issue
Block a user