-
-
Notifications
You must be signed in to change notification settings - Fork 65
/
Copy pathquic.go
154 lines (135 loc) · 4.67 KB
/
quic.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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package transport
import (
"context"
"crypto/tls"
"encoding/binary"
"fmt"
"io"
"net"
"github.com/miekg/dns"
"github.com/quic-go/quic-go"
log "github.com/sirupsen/logrus"
)
// DoQ Error Codes
// https://datatracker.ietf.org/doc/html/rfc9250#section-8.4
const (
DoQNoError = 0x0 // No error. This is used when the connection or stream needs to be closed, but there is no error to signal.
DoQInternalError = 0x1 // The DoQ implementation encountered an internal error and is incapable of pursuing the transaction or the connection.
DoQProtocolError = 0x2 // The DoQ implementation encountered a protocol error and is forcibly aborting the connection.
DoQRequestCancelled = 0x3 // A DoQ client uses this to signal that it wants to cancel an outstanding transaction.
DoQExcessiveLoad = 0x4 // A DoQ implementation uses this to signal when closing a connection due to excessive load.
DoQUnspecifiedError = 0x5 // A DoQ implementation uses this in the absence of a more specific error code.
DoQErrorReserved = 0xd098ea5e // Alternative error code used for tests.
)
// QUIC makes a DNS query over QUIC
type QUIC struct {
Common
TLSConfig *tls.Config
PMTUD bool
AddLengthPrefix bool
conn *quic.Connection
}
func (q *QUIC) connection() quic.Connection {
return *q.conn
}
// setServerName sets the TLS config server name to the QUIC server
func (q *QUIC) setServerName() {
host, _, err := net.SplitHostPort(q.Server)
if err != nil {
log.Fatalf("invalid QUIC server address: %s", err)
}
q.TLSConfig.ServerName = host
}
func (q *QUIC) Exchange(msg *dns.Msg) (*dns.Msg, error) {
if q.conn == nil || !q.ReuseConn {
log.Debugf("Connecting to %s", q.Server)
q.setServerName()
if len(q.TLSConfig.NextProtos) == 0 {
log.Debug("No ALPN tokens specified, using default: \"doq\"")
q.TLSConfig.NextProtos = []string{"doq"}
}
log.Debugf("Dialing with QUIC ALPN tokens: %v", q.TLSConfig.NextProtos)
conn, err := quic.DialAddr(
context.Background(),
q.Server,
q.TLSConfig,
&quic.Config{
DisablePathMTUDiscovery: !q.PMTUD,
},
)
if err != nil {
return nil, fmt.Errorf("opening quic session to %s: %v", q.Server, err)
}
q.conn = &conn
}
// Clients and servers MUST NOT send the edns-tcp-keepalive EDNS(0) Option [RFC7828] in any messages sent
// on a DoQ connection (because it is specific to the use of TCP/TLS as a transport).
// https://datatracker.ietf.org/doc/html/rfc9250#section-5.5.2
if opt := msg.IsEdns0(); opt != nil {
for _, option := range opt.Option {
if option.Option() == dns.EDNS0TCPKEEPALIVE {
_ = q.connection().CloseWithError(DoQProtocolError, "") // Already closing the connection, so we don't care about the error
q.conn = nil
return nil, fmt.Errorf("EDNS0 TCP keepalive option is set")
}
}
}
stream, err := q.connection().OpenStream()
if err != nil {
return nil, fmt.Errorf("open new stream to %s: %v", q.Server, err)
}
// When sending queries over a QUIC connection, the DNS Message ID MUST
// be set to zero. The stream mapping for DoQ allows for unambiguous
// correlation of queries and responses and so the Message ID field is
// not required.
// https://datatracker.ietf.org/doc/html/rfc9250#section-4.2.1
msg.Id = 0
buf, err := msg.Pack()
if err != nil {
return nil, err
}
if q.AddLengthPrefix {
// All DNS messages (queries and responses) sent over DoQ connections
// MUST be encoded as a 2-octet length field followed by the message
// content as specified in [RFC1035].
// https://datatracker.ietf.org/doc/html/rfc9250#section-4.2-4
_, err = stream.Write(addPrefix(buf))
} else {
_, err = stream.Write(buf)
}
if err != nil {
return nil, err
}
// The client MUST send the DNS query over the selected stream, and MUST
// indicate through the STREAM FIN mechanism that no further data will
// be sent on that stream.
// https://datatracker.ietf.org/doc/html/rfc9250#section-4.2
_ = stream.Close()
respBuf, err := io.ReadAll(stream)
if err != nil {
return nil, fmt.Errorf("reading response from %s: %s", q.Server, err)
}
if len(respBuf) == 0 {
return nil, fmt.Errorf("empty response from %s", q.Server)
}
reply := dns.Msg{}
if q.AddLengthPrefix {
err = reply.Unpack(respBuf[2:])
} else {
err = reply.Unpack(respBuf)
}
if err != nil {
return nil, fmt.Errorf("unpacking response from %s: %s", q.Server, err)
}
return &reply, nil
}
// addPrefix adds a 2-byte prefix with the DNS message length.
func addPrefix(b []byte) (m []byte) {
m = make([]byte, 2+len(b))
binary.BigEndian.PutUint16(m, uint16(len(b)))
copy(m[2:], b)
return m
}
func (q *QUIC) Close() error {
return q.connection().CloseWithError(DoQNoError, "")
}