diff --git a/go.mod b/go.mod index a89e235..21be2b2 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/joho/godotenv v1.5.1 github.com/likexian/whois v1.15.6 github.com/likexian/whois-parser v1.24.20 + github.com/miekg/dns v1.1.68 github.com/oschwald/maxminddb-golang v1.13.1 golang.org/x/net v0.44.0 ) @@ -15,6 +16,9 @@ require ( github.com/likexian/gokit v0.25.15 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/stretchr/testify v1.10.0 // indirect + golang.org/x/mod v0.27.0 // indirect + golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.36.0 // indirect golang.org/x/text v0.29.0 // indirect + golang.org/x/tools v0.36.0 // indirect ) diff --git a/go.sum b/go.sum index 54cfce4..027367d 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/likexian/gokit v0.25.15 h1:QjospM1eXhdMMHwZRpMKKAHY/Wig9wgcREmLtf9NslY= @@ -8,17 +10,25 @@ github.com/likexian/whois v1.15.6 h1:hizngFHJTNQDlhwhU+FEGyPGxy8bRnf25gHDNrSB4Ag github.com/likexian/whois v1.15.6/go.mod h1:vx3kt3sZ4mx4XFgpaNp3GXQCZQIzAoyrUAkRtJwoM2I= github.com/likexian/whois-parser v1.24.20 h1:oxEkRi0GxgqWQRLDMJpXU1EhgWmLmkqEFZ2ChXTeQLE= github.com/likexian/whois-parser v1.24.20/go.mod h1:rAtaofg2luol09H+ogDzGIfcG8ig1NtM5R16uQADDz4= +github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA= +github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps= github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE= github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/common/lookup.go b/internal/common/lookup.go index 69e2aa6..c99d99d 100644 --- a/internal/common/lookup.go +++ b/internal/common/lookup.go @@ -11,6 +11,7 @@ import ( "ipinfo/internal/db" "github.com/likexian/whois-parser" + "github.com/miekg/dns" "golang.org/x/net/publicsuffix" ) @@ -94,11 +95,14 @@ func LookupASNData(geoIP *db.GeoIPManager, targetASN uint) (*ASNDataResponse, er var ipv4Prefixes, ipv6Prefixes []string for _, prefix := range prefixes { - prefixStr := prefix.String() - if strings.Contains(prefixStr, ":") { - ipv6Prefixes = append(ipv6Prefixes, prefixStr) - } else { - ipv4Prefixes = append(ipv4Prefixes, prefixStr) + // Filter out bogon prefixes before adding them to the list. + if !IsBogon(prefix.IP) { + prefixStr := prefix.String() + if strings.Contains(prefixStr, ":") { + ipv6Prefixes = append(ipv6Prefixes, prefixStr) + } else { + ipv4Prefixes = append(ipv4Prefixes, prefixStr) + } } } sort.Strings(ipv4Prefixes) @@ -119,6 +123,25 @@ func LookupASNData(geoIP *db.GeoIPManager, targetASN uint) (*ASNDataResponse, er return response, nil } +// queryDns performs a DNS query for a specific type against a public resolver. +func queryDns(domain string, recordType uint16) ([]dns.RR, error) { + c := new(dns.Client) + m := new(dns.Msg) + m.SetQuestion(dns.Fqdn(domain), recordType) + m.RecursionDesired = true + + r, _, err := c.Exchange(m, "1.1.1.1:53") // Using Cloudflare's public resolver + if err != nil { + return nil, err + } + + if r.Rcode != dns.RcodeSuccess { + return nil, nil // No error, just no records found + } + + return r.Answer, nil +} + // LookupDomainData looks up domain data with caching. func LookupDomainData(domain string) (*DomainDataResponse, error) { if data, found := cache.Get(domain); found { @@ -149,68 +172,65 @@ func LookupDomainData(domain string) (*DomainDataResponse, error) { 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, ".")) - } - } - }, + recordTypes := map[string]uint16{ + "A": dns.TypeA, + "AAAA": dns.TypeAAAA, + "CNAME": dns.TypeCNAME, + "MX": dns.TypeMX, + "TXT": dns.TypeTXT, + "NS": dns.TypeNS, + "SOA": dns.TypeSOA, + "CAA": dns.TypeCAA, } - wg.Add(len(lookupTasks)) - for _, task := range lookupTasks { - go func(t func()) { + for key, rType := range recordTypes { + wg.Add(1) + go func(name string, recordType uint16) { defer wg.Done() - t() - }(task) + answers, err := queryDns(domain, recordType) + if err != nil { + slog.Debug("dns lookup failed for type", "type", name, "domain", domain, "err", err) + return + } + + mu.Lock() + defer mu.Unlock() + for _, ans := range answers { + switch rr := ans.(type) { + case *dns.A: + dnsData.A = append(dnsData.A, rr.A.String()) + case *dns.AAAA: + dnsData.AAAA = append(dnsData.AAAA, rr.AAAA.String()) + case *dns.CNAME: + dnsData.CNAME = strings.TrimSuffix(rr.Target, ".") + case *dns.MX: + dnsData.MX = append(dnsData.MX, fmt.Sprintf("%d %s", rr.Preference, strings.TrimSuffix(rr.Mx, "."))) + case *dns.TXT: + dnsData.TXT = append(dnsData.TXT, strings.Join(rr.Txt, " ")) + case *dns.NS: + dnsData.NS = append(dnsData.NS, strings.TrimSuffix(rr.Ns, ".")) + case *dns.SOA: + soaStr := fmt.Sprintf("%s %s %d %d %d %d %d", + strings.TrimSuffix(rr.Ns, "."), strings.TrimSuffix(rr.Mbox, "."), + rr.Serial, rr.Refresh, rr.Retry, rr.Expire, rr.Minttl) + dnsData.SOA = append(dnsData.SOA, soaStr) + case *dns.CAA: + dnsData.CAA = append(dnsData.CAA, fmt.Sprintf(`%d %s "%s"`, rr.Flag, rr.Tag, rr.Value)) + } + } + }(key, rType) } + wg.Wait() + // Sort MX records for consistent output + sort.Slice(dnsData.MX, func(i, j int) bool { + var prefI, prefJ int + _, _ = fmt.Sscanf(dnsData.MX[i], "%d", &prefI) + _, _ = fmt.Sscanf(dnsData.MX[j], "%d", &prefJ) + return prefI < prefJ + }) + response := &DomainDataResponse{ Whois: whoisResult, DNS: dnsData, diff --git a/internal/common/types.go b/internal/common/types.go index ec86e5b..0203417 100644 --- a/internal/common/types.go +++ b/internal/common/types.go @@ -44,6 +44,8 @@ type DNSData struct { MX []string `json:"MX,omitempty"` TXT []string `json:"TXT,omitempty"` NS []string `json:"NS,omitempty"` + SOA []string `json:"SOA,omitempty"` + CAA []string `json:"CAA,omitempty"` } // WhoisInfo is a sanitized version of the parsed whois data for the API response.