1
1
package dnslink
2
2
3
3
import (
4
+ "context"
4
5
"encoding/json"
6
+ "math/rand"
5
7
"net"
6
8
"net/url"
7
9
"sort"
8
10
"strings"
9
11
10
12
isd "github.com/jbenet/go-is-domain"
13
+ dns "github.com/miekg/dns"
11
14
)
12
15
13
16
type PathEntry struct {
@@ -77,9 +80,9 @@ func (url *URLParts) MarshalJSON() ([]byte, error) {
77
80
}
78
81
79
82
type Result struct {
80
- Links map [string ][]string `json:"links"`
81
- Path []PathEntry `json:"path"`
82
- Log []LogStatement `json:"log"`
83
+ Links map [string ][]LookupEntry `json:"links"`
84
+ Path []PathEntry `json:"path"`
85
+ Log []LogStatement `json:"log"`
83
86
}
84
87
85
88
type Resolver struct {
@@ -94,7 +97,54 @@ func (r *Resolver) ResolveN(domain string) (Result, error) {
94
97
return resolve (r , domain , true )
95
98
}
96
99
97
- type LookupTXTFunc func (name string ) (txt []string , err error )
100
+ type LookupEntry struct {
101
+ Value string `json:"value"`
102
+ Ttl uint32 `json:"ttl"`
103
+ }
104
+ type LookupEntries []LookupEntry
105
+
106
+ func (l LookupEntries ) Len () int { return len (l ) }
107
+ func (l LookupEntries ) Swap (i , j int ) { l [i ], l [j ] = l [j ], l [i ] }
108
+
109
+ type ByValue struct { LookupEntries }
110
+
111
+ func (s ByValue ) Less (i , j int ) bool { return s .LookupEntries [i ].Value < s .LookupEntries [j ].Value }
112
+
113
+ type LookupTXTFunc func (name string ) (txt []LookupEntry , err error )
114
+
115
+ func NewUDPLookup (servers []string ) LookupTXTFunc {
116
+ return func (domain string ) (entries []LookupEntry , err error ) {
117
+ if ! strings .HasSuffix (domain , "." ) {
118
+ domain += "."
119
+ }
120
+ req := new (dns.Msg )
121
+ req .Id = dns .Id ()
122
+ req .RecursionDesired = true
123
+ req .Question = make ([]dns.Question , 1 )
124
+ req .Question [0 ] = dns.Question {
125
+ Name : domain ,
126
+ Qtype : dns .TypeTXT ,
127
+ Qclass : dns .ClassINET ,
128
+ }
129
+ server := servers [rand .Intn (len (servers ))]
130
+ res , err := dns .Exchange (req , server )
131
+ if err != nil {
132
+ return nil , err
133
+ }
134
+ entries = make ([]LookupEntry , len (res .Answer ))
135
+ for index , answer := range res .Answer {
136
+ if answer .Header ().Rrtype == dns .TypeTXT {
137
+ txtAnswer := answer .(* dns.TXT )
138
+ entries [index ] = LookupEntry {
139
+ Value : strings .Join (txtAnswer .Txt , "" ),
140
+ Ttl : txtAnswer .Header ().Ttl ,
141
+ }
142
+ }
143
+ }
144
+ sort .Sort (ByValue {entries })
145
+ return entries , nil
146
+ }
147
+ }
98
148
99
149
const Version = "v0.0.1"
100
150
const dnsPrefix = "_dnslink."
@@ -110,13 +160,33 @@ func ResolveN(domain string) (Result, error) {
110
160
return defaultResolver .ResolveN (domain )
111
161
}
112
162
163
+ func wrapLookup (r * net.Resolver , ttl uint32 ) LookupTXTFunc {
164
+ return func (domain string ) (res []LookupEntry , err error ) {
165
+ txt , err := r .LookupTXT (context .Background (), domain )
166
+ if err != nil {
167
+ return nil , err
168
+ }
169
+ res = make ([]LookupEntry , len (txt ))
170
+ for index , txt := range txt {
171
+ res [index ] = LookupEntry {
172
+ Value : txt ,
173
+ // net.LookupTXT doesn't support ttl :-(
174
+ Ttl : ttl ,
175
+ }
176
+ }
177
+ return res , nil
178
+ }
179
+ }
180
+
181
+ var defaultLookupTXT = wrapLookup (net .DefaultResolver , 0 )
182
+
113
183
func resolve (r * Resolver , domain string , recursive bool ) (result Result , err error ) {
114
184
lookupTXT := r .LookupTXT
115
185
if lookupTXT == nil {
116
- lookupTXT = net . LookupTXT
186
+ lookupTXT = defaultLookupTXT
117
187
}
118
188
lookup , error := validateDomain (domain )
119
- result .Links = map [string ][]string {}
189
+ result .Links = map [string ][]LookupEntry {}
120
190
result .Path = []PathEntry {}
121
191
result .Log = []LogStatement {}[:]
122
192
if lookup == nil {
@@ -189,6 +259,7 @@ func validateDomain(input string) (*URLParts, *LogStatement) {
189
259
type processedEntry struct {
190
260
value string
191
261
entry string
262
+ ttl uint32
192
263
}
193
264
194
265
func relevantURLParts (input string ) URLParts {
@@ -248,8 +319,8 @@ func getPathFromLog(log []LogStatement) []PathEntry {
248
319
return path
249
320
}
250
321
251
- func resolveTxtEntries (domain string , recursive bool , txtEntries []string ) (links map [string ][]string , log []LogStatement , redirect * URLParts ) {
252
- links = make (map [string ][]string )
322
+ func resolveTxtEntries (domain string , recursive bool , txtEntries []LookupEntry ) (links map [string ][]LookupEntry , log []LogStatement , redirect * URLParts ) {
323
+ links = make (map [string ][]LookupEntry )
253
324
log = []LogStatement {}[:]
254
325
if ! hasDNSLinkEntry (txtEntries ) && strings .HasPrefix (domain , dnsPrefix ) {
255
326
return links , log , & URLParts {Domain : domain [len (dnsPrefix ):]}
@@ -290,40 +361,43 @@ func resolveTxtEntries(domain string, recursive bool, txtEntries []string) (link
290
361
}
291
362
}
292
363
for key , foundEntries := range found {
293
- list := []string {}[:]
364
+ list := []LookupEntry {}[:]
294
365
for _ , foundEntry := range foundEntries {
295
- list = append (list , foundEntry .value )
366
+ list = append (list , LookupEntry {
367
+ Value : foundEntry .value ,
368
+ Ttl : foundEntry .ttl ,
369
+ })
296
370
}
297
- sort .Strings ( list )
371
+ sort .Sort ( ByValue { list } )
298
372
links [key ] = list
299
373
}
300
374
return links , log , nil
301
375
}
302
376
303
- func hasDNSLinkEntry (txtEntries []string ) bool {
377
+ func hasDNSLinkEntry (txtEntries []LookupEntry ) bool {
304
378
for _ , txtEntry := range txtEntries {
305
- if strings .HasPrefix (txtEntry , txtPrefix ) {
379
+ if strings .HasPrefix (txtEntry . Value , txtPrefix ) {
306
380
return true
307
381
}
308
382
}
309
383
return false
310
384
}
311
385
312
- func processEntries (dnslinkEntries []string ) (map [string ][]processedEntry , []LogStatement ) {
386
+ func processEntries (dnslinkEntries []LookupEntry ) (map [string ][]processedEntry , []LogStatement ) {
313
387
log := []LogStatement {}[:]
314
388
found := make (map [string ][]processedEntry )
315
389
for _ , entry := range dnslinkEntries {
316
- if ! strings .HasPrefix (entry , txtPrefix ) {
390
+ if ! strings .HasPrefix (entry . Value , txtPrefix ) {
317
391
continue
318
392
}
319
- key , value , error := validateDNSLinkEntry (entry )
393
+ key , value , error := validateDNSLinkEntry (entry . Value )
320
394
321
395
if error != "" {
322
- log = append (log , LogStatement {Code : "INVALID_ENTRY" , Entry : entry , Reason : error })
396
+ log = append (log , LogStatement {Code : "INVALID_ENTRY" , Entry : entry . Value , Reason : error })
323
397
continue
324
398
}
325
399
list , hasList := found [key ]
326
- processed := processedEntry {value , entry }
400
+ processed := processedEntry {value , entry . Value , entry . Ttl }
327
401
if ! hasList {
328
402
found [key ] = []processedEntry {processed }
329
403
} else {
0 commit comments