-
-
Notifications
You must be signed in to change notification settings - Fork 14
/
dnsclient.go
129 lines (110 loc) · 3.01 KB
/
dnsclient.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package ipisp
import (
"context"
"errors"
"fmt"
"net"
"strings"
"time"
)
const hexDigit = "0123456789abcdef"
// LookupIP looks up a single IP with the API.
// The service recommends that bulk lookups use the BulkClient out of respect
// for their server load.
func LookupIP(ctx context.Context, ip net.IP) (*Response, error) {
var r net.Resolver
lookupName, err := formatDNSLookupName(ip)
if err != nil {
return nil, err
}
txts, err := r.LookupTXT(ctx, lookupName)
if err != nil {
return nil, err
}
for _, txt := range txts {
values := strings.Split(txt, "|")
if len(values) != 5 {
return nil, fmt.Errorf("unrecognized response: %s", txt)
}
for k := range values {
values[k] = strings.TrimSpace(values[k])
}
ret := &Response{
IP: ip,
Registry: strings.ToUpper(values[3]),
}
var err error
asn, err := ParseASN(values[0])
if err != nil {
return nil, fmt.Errorf("parse ASN %q: %w", values[0], err)
}
ret.ASN = asn
ret.Country = strings.TrimSpace(values[2])
_, ret.Range, err = net.ParseCIDR(values[1])
if err != nil {
return nil, fmt.Errorf("parse range (%s): %s", values[1], err)
}
if values[4] != "" { // There's not always an allocation date available :(
ret.AllocatedAt, err = time.Parse("2006-01-02", values[4])
if err != nil {
return nil, fmt.Errorf("parse date (%s): %s", values[4], err)
}
}
asnResponse, err := LookupASN(ctx, ret.ASN)
if err != nil {
return nil, fmt.Errorf("retrieve ASN (%s): %s", ret.ASN.String(), err.Error())
}
ret.ISPName = asnResponse.ISPName
return ret, nil
}
return nil, fmt.Errorf("no records found")
}
func LookupASN(ctx context.Context, asn ASN) (*Response, error) {
var r net.Resolver
txts, err := r.LookupTXT(ctx, asn.String()+".asn.cymru.com")
if err != nil {
return nil, err
}
for _, txt := range txts {
values := strings.Split(txt, "|")
if len(values) != 5 {
return nil, fmt.Errorf("Received unrecognized response in AS lookup: %s", txt)
}
for k := range values {
values[k] = strings.TrimSpace(values[k])
}
resp := &Response{
ASN: asn,
Registry: strings.ToUpper(values[2]),
ISPName: values[4],
}
resp.Country = values[1]
if values[3] != "" {
resp.AllocatedAt, err = time.Parse("2006-01-02", values[3])
if err != nil {
return nil, fmt.Errorf("Could not parse date (%s): %s", values[3], err)
}
}
return resp, nil
}
return nil, fmt.Errorf("no records found")
}
func formatDNSLookupName(ip net.IP) (string, error) {
switch {
case len(ip) == net.IPv4len || ip.To4() != nil:
ip = ip.To4()
return fmt.Sprintf("%d.%d.%d.%d.origin.asn.cymru.com", ip[3], ip[2], ip[1], ip[0]), nil
case len(ip) == net.IPv6len:
sep := []byte(`.`)[0]
b := make([]byte, 0, 64)
for i := 16; i >= 2; i -= 2 {
for j := 0; j <= 3; j++ {
v := ((uint32(ip[i-2]) << 8) | uint32(ip[i-1])) >> uint(j*4)
b = append(b, hexDigit[v&0xf], sep)
}
}
return fmt.Sprintf("%s.origin6.asn.cymru.com", b[:63]), nil
default:
return "", errors.New("invalid IP length")
}
}