/
dnsencoder.go
126 lines (105 loc) · 3.09 KB
/
dnsencoder.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
package netxlite
//
// Encode DNS queries to byte arrays
//
import (
"sync"
"sync/atomic"
"github.com/miekg/dns"
"github.com/ooni/probe-engine/pkg/model"
)
// DNSEncoderMiekg uses github.com/miekg/dns to implement the Encoder.
type DNSEncoderMiekg struct{}
const (
// dnsPaddingDesiredBlockSize is the size that the padded query should be multiple of
dnsPaddingDesiredBlockSize = 128
// dnsEDNS0MaxResponseSize is the maximum response size for EDNS0
dnsEDNS0MaxResponseSize = 4096
// dnsDNSSECEnabled turns on support for DNSSEC when using EDNS0
dnsDNSSECEnabled = true
)
// Encoder implements model.DNSEncoder.Encode.
func (e *DNSEncoderMiekg) Encode(domain string, qtype uint16, padding bool) model.DNSQuery {
return &dnsQuery{
bytesCalls: &atomic.Int64{},
domain: domain,
kind: qtype,
id: dns.Id(),
memoizedBytes: []byte{},
mu: sync.Mutex{},
padding: padding,
}
}
// dnsQuery implements model.DNSQuery.
type dnsQuery struct {
// bytesCalls counts the calls to the bytes() method
bytesCalls *atomic.Int64
// domain is the domain.
domain string
// kind is the query type.
kind uint16
// id is the query ID.
id uint16
// memoizedBytes contains the query encoded as bytes. We only fill
// this field the first time the Bytes method is called.
memoizedBytes []byte
// mu provides mutual exclusion.
mu sync.Mutex
// padding indicates whether we need padding.
padding bool
}
// Domain implements model.DNSQuery.Domain.
func (q *dnsQuery) Domain() string {
return q.domain
}
// Type implements model.DNSQuery.Type.
func (q *dnsQuery) Type() uint16 {
return q.kind
}
// Bytes implements model.DNSQuery.Bytes.
func (q *dnsQuery) Bytes() ([]byte, error) {
defer q.mu.Unlock()
q.mu.Lock()
if len(q.memoizedBytes) <= 0 {
q.bytesCalls.Add(1) // for testing
data, err := q.bytes()
if err != nil {
return nil, err
}
q.memoizedBytes = data
}
return q.memoizedBytes, nil
}
// bytes is the unmemoized implementation of Bytes
func (q *dnsQuery) bytes() ([]byte, error) {
question := dns.Question{
Name: dns.Fqdn(q.domain),
Qtype: q.kind,
Qclass: dns.ClassINET,
}
query := new(dns.Msg)
query.Id = q.id
query.RecursionDesired = true
query.Question = make([]dns.Question, 1)
query.Question[0] = question
if q.padding {
query.SetEdns0(dnsEDNS0MaxResponseSize, dnsDNSSECEnabled)
// Clients SHOULD pad queries to the closest multiple of
// 128 octets RFC8467#section-4.1. We inflate the query
// length by the size of the option (i.e. 4 octets). The
// cast to uint is necessary to make the modulus operation
// work as intended when the desiredBlockSize is smaller
// than (query.Len()+4) ¯\_(ツ)_/¯.
remainder := (dnsPaddingDesiredBlockSize - uint(query.Len()+4)) % dnsPaddingDesiredBlockSize
opt := new(dns.EDNS0_PADDING)
opt.Padding = make([]byte, remainder)
query.IsEdns0().Option = append(query.IsEdns0().Option, opt)
}
return query.Pack()
}
// ID implements model.DNSQuery.ID
func (q *dnsQuery) ID() uint16 {
return q.id
}
var _ model.DNSEncoder = &DNSEncoderMiekg{}
var _ model.DNSQuery = &dnsQuery{}