/
request.go
99 lines (86 loc) · 3.18 KB
/
request.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
package nameresolver
import (
"github.com/miekg/dns"
"github.com/ANSSI-FR/transdep/tools"
"time"
"strings"
"github.com/ANSSI-FR/transdep/errors"
)
type RequestTopic struct {
// name is the request topic.
Name string
// except is the list of exceptions/violations of the DNS protocol that we are willing to accept for this query
Exceptions tools.Exceptions
}
// Request represents a request to the name resolution "finder".
type Request struct {
topic RequestTopic
// resultChan is used internally by the request methods to pass around the result of the request between the worker
// goroutine doing the resolution and the calling goroutines that initiated the resolution.
resultChan chan *result
// context is used for cycle detection to prevent cyclic name resolution, for instance if a domain name owns a
// CNAME to itself or if a CNAME chain is circular.
context map[RequestTopic]bool
}
// NewRequest builds a new Request instance.
// This is the standard way of building new requests from a third-party module.
func NewRequest(name string, except tools.Exceptions) *Request {
nr := new(Request)
nr.topic.Name = strings.ToLower(dns.Fqdn(name))
nr.topic.Exceptions = except
nr.resultChan = make(chan *result, 1)
nr.context = make(map[RequestTopic]bool)
return nr
}
// NewRequestWithContext builds a new Request instance, adding some context information to it to prevent resolution loops.
// This is mainly used by github.com/ANSSI-FR/transdep/nameresolver
func NewRequestWithContext(name string, except tools.Exceptions, ctx *Request) *Request {
nr := new(Request)
nr.topic.Name = strings.ToLower(dns.Fqdn(name))
nr.topic.Exceptions = except
nr.resultChan = make(chan *result, 1)
nr.context = make(map[RequestTopic]bool)
for k, v := range ctx.context {
nr.context[k] = v
}
nr.context[ctx.topic] = true
return nr
}
func (nr *Request) Name() string {
return nr.topic.Name
}
func (nr *Request) Exceptions() tools.Exceptions {
return nr.topic.Exceptions
}
func (nr *Request) RequestTopic() RequestTopic {
return nr.topic
}
// DetectLoop returns true if this request is part of a resolution loop
func (nr *Request) DetectLoop() bool {
_, ok := nr.context[nr.topic]
return ok
}
func (nr *Request) Equal(other *Request) bool {
return nr.topic == other.topic
}
// Result returns the result of a name resolution or an error.
// The error may be caused by a timeout after the default timeout duration, or an error during the resolution process.
func (nr *Request) Result() (*Entry, *errors.ErrorStack) {
return nr.ResultWithSpecificTimeout(tools.DEFAULT_TIMEOUT_DURATION)
}
// ResultWithSpecificTimeout is similar to Result except that a timeout duration may be specified.
func (nr *Request) ResultWithSpecificTimeout(dur time.Duration) (*Entry, *errors.ErrorStack) {
select {
case res := <-nr.resultChan:
return res.Result, res.Err
case _ = <-tools.StartTimeout(dur):
return nil, errors.NewErrorStack(errors.NewTimeoutError("name resolution", nr.topic.Name))
}
}
// SetResult allows the definition of the result value associated with this request.
func (nr *Request) SetResult(resEntry *Entry, err *errors.ErrorStack) {
if err != nil {
err = err.Copy()
}
nr.resultChan <- &result{resEntry, err}
}