forked from ooni/probe-cli
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdnsovertcp.go
133 lines (118 loc) · 3.83 KB
/
dnsovertcp.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
130
131
132
133
package netxlite
//
// DNS-over-{TCP,TLS} transport
//
import (
"context"
"errors"
"io"
"math"
"net"
"time"
"github.com/ooni/probe-cli/v3/internal/model"
)
// DialContextFunc is the type of net.Dialer.DialContext.
type DialContextFunc func(context.Context, string, string) (net.Conn, error)
// DNSOverTCPTransport is a DNS-over-{TCP,TLS} DNSTransport.
//
// Note: this implementation always creates a new connection for each query. This
// strategy is less efficient but MAY be more robust for cleartext TCP connections
// when querying for a blocked domain name causes endpoint blocking.
type DNSOverTCPTransport struct {
dial DialContextFunc
decoder model.DNSDecoder
address string
network string
requiresPadding bool
}
// NewUnwrappedDNSOverTCPTransport creates a new DNSOverTCPTransport
// that has not been wrapped yet.
//
// Arguments:
//
// - dial is a function with the net.Dialer.DialContext's signature;
//
// - address is the endpoint address (e.g., 8.8.8.8:53).
func NewUnwrappedDNSOverTCPTransport(dial DialContextFunc, address string) *DNSOverTCPTransport {
return newDNSOverTCPOrTLSTransport(dial, "tcp", address, false)
}
// NewUnwrappedDNSOverTLSTransport creates a new DNSOverTLS transport
// that has not been wrapped yet.
//
// Arguments:
//
// - dial is a function with the net.Dialer.DialContext's signature;
//
// - address is the endpoint address (e.g., 8.8.8.8:853).
func NewUnwrappedDNSOverTLSTransport(dial DialContextFunc, address string) *DNSOverTCPTransport {
return newDNSOverTCPOrTLSTransport(dial, "dot", address, true)
}
// newDNSOverTCPOrTLSTransport is the common factory for creating a transport
func newDNSOverTCPOrTLSTransport(
dial DialContextFunc, network, address string, padding bool) *DNSOverTCPTransport {
return &DNSOverTCPTransport{
dial: dial,
decoder: &DNSDecoderMiekg{},
address: address,
network: network,
requiresPadding: padding,
}
}
// errQueryTooLarge indicates the query is too large for the transport.
var errQueryTooLarge = errors.New("oodns: query too large for this transport")
// RoundTrip sends a query and receives a reply.
func (t *DNSOverTCPTransport) RoundTrip(
ctx context.Context, query model.DNSQuery) (model.DNSResponse, error) {
// TODO(bassosimone): this method should more strictly honour the context, which
// currently is only used to bound the dial operation
rawQuery, err := query.Bytes()
if err != nil {
return nil, err
}
if len(rawQuery) > math.MaxUint16 {
return nil, errQueryTooLarge
}
conn, err := t.dial(ctx, "tcp", t.address)
if err != nil {
return nil, err
}
defer conn.Close()
const iotimeout = 10 * time.Second
conn.SetDeadline(time.Now().Add(iotimeout))
// Write request
buf := []byte{byte(len(rawQuery) >> 8)}
buf = append(buf, byte(len(rawQuery)))
buf = append(buf, rawQuery...)
if _, err = conn.Write(buf); err != nil {
return nil, err
}
// Read response
header := make([]byte, 2)
if _, err = io.ReadFull(conn, header); err != nil {
return nil, err
}
length := int(header[0])<<8 | int(header[1])
rawResponse := make([]byte, length)
if _, err = io.ReadFull(conn, rawResponse); err != nil {
return nil, err
}
return t.decoder.DecodeResponse(rawResponse, query)
}
// RequiresPadding returns true for DoT and false for TCP
// according to RFC8467.
func (t *DNSOverTCPTransport) RequiresPadding() bool {
return t.requiresPadding
}
// Network returns the transport network, i.e., "dot" or "tcp".
func (t *DNSOverTCPTransport) Network() string {
return t.network
}
// Address returns the upstream server endpoint (e.g., "1.1.1.1:853").
func (t *DNSOverTCPTransport) Address() string {
return t.address
}
// CloseIdleConnections closes idle connections, if any.
func (t *DNSOverTCPTransport) CloseIdleConnections() {
// nothing to do
}
var _ model.DNSTransport = &DNSOverTCPTransport{}