CHANGELOG for v1.20240513.0.
[dnstt.git] / dnstt-client / dns.go
blob67115c18bd6021dcc99b85fe85a26a2c863d91ee
1 package main
3 import (
4 "bytes"
5 "crypto/rand"
6 "encoding/base32"
7 "encoding/binary"
8 "fmt"
9 "io"
10 "log"
11 "net"
12 "time"
14 "www.bamsoftware.com/git/dnstt.git/dns"
15 "www.bamsoftware.com/git/dnstt.git/turbotunnel"
18 const (
19 // How many bytes of random padding to insert into queries.
20 numPadding = 3
21 // In an otherwise empty polling query, insert even more random padding,
22 // to reduce the chance of a cache hit. Cannot be greater than 31,
23 // because the prefix codes indicating padding start at 224.
24 numPaddingForPoll = 8
26 // sendLoop has a poll timer that automatically sends an empty polling
27 // query when a certain amount of time has elapsed without a send. The
28 // poll timer is initially set to initPollDelay. It increases by a
29 // factor of pollDelayMultiplier every time the poll timer expires, up
30 // to a maximum of maxPollDelay. The poll timer is reset to
31 // initPollDelay whenever an a send occurs that is not the result of the
32 // poll timer expiring.
33 initPollDelay = 500 * time.Millisecond
34 maxPollDelay = 10 * time.Second
35 pollDelayMultiplier = 2.0
37 // A limit on the number of empty poll requests we may send in a burst
38 // as a result of receiving data.
39 pollLimit = 16
42 // base32Encoding is a base32 encoding without padding.
43 var base32Encoding = base32.StdEncoding.WithPadding(base32.NoPadding)
45 // DNSPacketConn provides a packet-sending and -receiving interface over various
46 // forms of DNS. It handles the details of how packets and padding are encoded
47 // as a DNS name in the Question section of an upstream query, and as a TXT RR
48 // in downstream responses.
50 // DNSPacketConn does not handle the mechanics of actually sending and receiving
51 // encoded DNS messages. That is rather the responsibility of some other
52 // net.PacketConn such as net.UDPConn, HTTPPacketConn, or TLSPacketConn, one of
53 // which must be provided to NewDNSPacketConn.
55 // We don't have a need to match up a query and a response by ID. Queries and
56 // responses are vehicles for carrying data and for our purposes don't need to
57 // be correlated. When sending a query, we generate a random ID, and when
58 // receiving a response, we ignore the ID.
59 type DNSPacketConn struct {
60 clientID turbotunnel.ClientID
61 domain dns.Name
62 // Sending on pollChan permits sendLoop to send an empty polling query.
63 // sendLoop also does its own polling according to a time schedule.
64 pollChan chan struct{}
65 // QueuePacketConn is the direct receiver of ReadFrom and WriteTo calls.
66 // recvLoop and sendLoop take the messages out of the receive and send
67 // queues and actually put them on the network.
68 *turbotunnel.QueuePacketConn
71 // NewDNSPacketConn creates a new DNSPacketConn. transport, through its WriteTo
72 // and ReadFrom methods, handles the actual sending and receiving the DNS
73 // messages encoded by DNSPacketConn. addr is the address to be passed to
74 // transport.WriteTo whenever a message needs to be sent.
75 func NewDNSPacketConn(transport net.PacketConn, addr net.Addr, domain dns.Name) *DNSPacketConn {
76 // Generate a new random ClientID.
77 clientID := turbotunnel.NewClientID()
78 c := &DNSPacketConn{
79 clientID: clientID,
80 domain: domain,
81 pollChan: make(chan struct{}, pollLimit),
82 QueuePacketConn: turbotunnel.NewQueuePacketConn(clientID, 0),
84 go func() {
85 err := c.recvLoop(transport)
86 if err != nil {
87 log.Printf("recvLoop: %v", err)
89 }()
90 go func() {
91 err := c.sendLoop(transport, addr)
92 if err != nil {
93 log.Printf("sendLoop: %v", err)
95 }()
96 return c
99 // dnsResponsePayload extracts the downstream payload of a DNS response, encoded
100 // into the RDATA of a TXT RR. It returns nil if the message doesn't pass format
101 // checks, or if the name in its Question entry is not a subdomain of domain.
102 func dnsResponsePayload(resp *dns.Message, domain dns.Name) []byte {
103 if resp.Flags&0x8000 != 0x8000 {
104 // QR != 1, this is not a response.
105 return nil
107 if resp.Flags&0x000f != dns.RcodeNoError {
108 return nil
111 if len(resp.Answer) != 1 {
112 return nil
114 answer := resp.Answer[0]
116 _, ok := answer.Name.TrimSuffix(domain)
117 if !ok {
118 // Not the name we are expecting.
119 return nil
122 if answer.Type != dns.RRTypeTXT {
123 // We only support TYPE == TXT.
124 return nil
126 payload, err := dns.DecodeRDataTXT(answer.Data)
127 if err != nil {
128 return nil
131 return payload
134 // nextPacket reads the next length-prefixed packet from r. It returns a nil
135 // error only when a complete packet was read. It returns io.EOF only when there
136 // were 0 bytes remaining to read from r. It returns io.ErrUnexpectedEOF when
137 // EOF occurs in the middle of an encoded packet.
138 func nextPacket(r *bytes.Reader) ([]byte, error) {
139 for {
140 var n uint16
141 err := binary.Read(r, binary.BigEndian, &n)
142 if err != nil {
143 // We may return a real io.EOF only here.
144 return nil, err
146 p := make([]byte, n)
147 _, err = io.ReadFull(r, p)
148 // Here we must change io.EOF to io.ErrUnexpectedEOF.
149 if err == io.EOF {
150 err = io.ErrUnexpectedEOF
152 return p, err
156 // recvLoop repeatedly calls transport.ReadFrom to receive a DNS message,
157 // extracts its payload and breaks it into packets, and stores the packets in a
158 // queue to be returned from a future call to c.ReadFrom.
160 // Whenever we receive a DNS response containing at least one data packet, we
161 // send on c.pollChan to permit sendLoop to send an immediate polling queries.
162 // KCP itself will also send an ACK packet for incoming data, which is
163 // effectively a second poll. Therefore, each time we receive data, we send up
164 // to 2 polling queries (or 1 + f polling queries, if KCP only ACKs an f
165 // fraction of incoming data). We say "up to" because sendLoop will discard an
166 // empty polling query if it has an organic non-empty packet to send (this goes
167 // also for KCP's organic ACK packets).
169 // The intuition behind polling immediately after receiving is that if server
170 // has just had something to send, it may have more to send, and in order for
171 // the server to send anything, we must give it a query to respond to. The
172 // intuition behind polling *2 times* (or 1 + f times) is similar to TCP slow
173 // start: we want to maintain some number of queries "in flight", and the faster
174 // the server is sending, the higher that number should be. If we polled only
175 // once for each received packet, we would tend to have only one query in flight
176 // at a time, ping-pong style. The first polling query replaces the in-flight
177 // query that has just finished its duty in returning data to us; the second
178 // grows the effective in-flight window proportional to the rate at which
179 // data-carrying responses are being received. Compare to Eq. (2) of
180 // https://tools.ietf.org/html/rfc5681#section-3.1. The differences are that we
181 // count messages, not bytes, and we don't maintain an explicit window. If a
182 // response comes back without data, or if a query or response is dropped by the
183 // network, then we don't poll again, which decreases the effective in-flight
184 // window.
185 func (c *DNSPacketConn) recvLoop(transport net.PacketConn) error {
186 for {
187 var buf [4096]byte
188 n, addr, err := transport.ReadFrom(buf[:])
189 if err != nil {
190 if err, ok := err.(net.Error); ok && err.Temporary() {
191 log.Printf("ReadFrom temporary error: %v", err)
192 continue
194 return err
197 // Got a response. Try to parse it as a DNS message.
198 resp, err := dns.MessageFromWireFormat(buf[:n])
199 if err != nil {
200 log.Printf("MessageFromWireFormat: %v", err)
201 continue
204 payload := dnsResponsePayload(&resp, c.domain)
206 // Pull out the packets contained in the payload.
207 r := bytes.NewReader(payload)
208 any := false
209 for {
210 p, err := nextPacket(r)
211 if err != nil {
212 break
214 any = true
215 c.QueuePacketConn.QueueIncoming(p, addr)
218 // If the payload contained one or more packets, permit sendLoop
219 // to poll immediately. ACKs on received data will effectively
220 // serve as another stream of polls whose rate is proportional
221 // to the rate of incoming packets.
222 if any {
223 select {
224 case c.pollChan <- struct{}{}:
225 default:
231 // chunks breaks p into non-empty subslices of at most n bytes, greedily so that
232 // only final subslice has length < n.
233 func chunks(p []byte, n int) [][]byte {
234 var result [][]byte
235 for len(p) > 0 {
236 sz := len(p)
237 if sz > n {
238 sz = n
240 result = append(result, p[:sz])
241 p = p[sz:]
243 return result
246 // send sends p as a single packet encoded into a DNS query, using
247 // transport.WriteTo(query, addr). The length of p must be less than 224 bytes.
249 // Here is an example of how a packet is encoded into a DNS name, using
251 // p = "supercalifragilisticexpialidocious"
252 // c.clientID = "CLIENTID"
253 // domain = "t.example.com"
255 // as the input.
257 // 0. Start with the raw packet contents.
259 // supercalifragilisticexpialidocious
261 // 1. Length-prefix the packet and add random padding. A length prefix L < 0xe0
262 // means a data packet of L bytes. A length prefix L ≥ 0xe0 means padding
263 // of L − 0xe0 bytes (not counting the length of the length prefix itself).
265 // \xe3\xd9\xa3\x15\x22supercalifragilisticexpialidocious
267 // 2. Prefix the ClientID.
269 // CLIENTID\xe3\xd9\xa3\x15\x22supercalifragilisticexpialidocious
271 // 3. Base32-encode, without padding and in lower case.
273 // ingesrkokreujy6zumkse43vobsxey3bnruwm4tbm5uwy2ltoruwgzlyobuwc3djmrxwg2lpovzq
275 // 4. Break into labels of at most 63 octets.
277 // ingesrkokreujy6zumkse43vobsxey3bnruwm4tbm5uwy2ltoruwgzlyobuwc3d.jmrxwg2lpovzq
279 // 5. Append the domain.
281 // ingesrkokreujy6zumkse43vobsxey3bnruwm4tbm5uwy2ltoruwgzlyobuwc3d.jmrxwg2lpovzq.t.example.com
282 func (c *DNSPacketConn) send(transport net.PacketConn, p []byte, addr net.Addr) error {
283 var decoded []byte
285 if len(p) >= 224 {
286 return fmt.Errorf("too long")
288 var buf bytes.Buffer
289 // ClientID
290 buf.Write(c.clientID[:])
291 n := numPadding
292 if len(p) == 0 {
293 n = numPaddingForPoll
295 // Padding / cache inhibition
296 buf.WriteByte(byte(224 + n))
297 io.CopyN(&buf, rand.Reader, int64(n))
298 // Packet contents
299 if len(p) > 0 {
300 buf.WriteByte(byte(len(p)))
301 buf.Write(p)
303 decoded = buf.Bytes()
306 encoded := make([]byte, base32Encoding.EncodedLen(len(decoded)))
307 base32Encoding.Encode(encoded, decoded)
308 encoded = bytes.ToLower(encoded)
309 labels := chunks(encoded, 63)
310 labels = append(labels, c.domain...)
311 name, err := dns.NewName(labels)
312 if err != nil {
313 return err
316 var id uint16
317 binary.Read(rand.Reader, binary.BigEndian, &id)
318 query := &dns.Message{
319 ID: id,
320 Flags: 0x0100, // QR = 0, RD = 1
321 Question: []dns.Question{
323 Name: name,
324 Type: dns.RRTypeTXT,
325 Class: dns.ClassIN,
328 // EDNS(0)
329 Additional: []dns.RR{
331 Name: dns.Name{},
332 Type: dns.RRTypeOPT,
333 Class: 4096, // requester's UDP payload size
334 TTL: 0, // extended RCODE and flags
335 Data: []byte{},
339 buf, err := query.WireFormat()
340 if err != nil {
341 return err
344 _, err = transport.WriteTo(buf, addr)
345 return err
348 // sendLoop takes packets that have been written using c.WriteTo, and sends them
349 // on the network using send. It also does polling with empty packets when
350 // requested by pollChan or after a timeout.
351 func (c *DNSPacketConn) sendLoop(transport net.PacketConn, addr net.Addr) error {
352 pollDelay := initPollDelay
353 pollTimer := time.NewTimer(pollDelay)
354 for {
355 var p []byte
356 outgoing := c.QueuePacketConn.OutgoingQueue(addr)
357 pollTimerExpired := false
358 // Prioritize sending an actual data packet from outgoing. Only
359 // consider a poll when outgoing is empty.
360 select {
361 case p = <-outgoing:
362 default:
363 select {
364 case p = <-outgoing:
365 case <-c.pollChan:
366 case <-pollTimer.C:
367 pollTimerExpired = true
371 if len(p) > 0 {
372 // A data-carrying packet displaces one pending poll
373 // opportunity, if any.
374 select {
375 case <-c.pollChan:
376 default:
380 if pollTimerExpired {
381 // We're polling because it's been a while since we last
382 // polled. Increase the poll delay.
383 pollDelay = time.Duration(float64(pollDelay) * pollDelayMultiplier)
384 if pollDelay > maxPollDelay {
385 pollDelay = maxPollDelay
387 } else {
388 // We're sending an actual data packet, or we're polling
389 // in response to a received packet. Reset the poll
390 // delay to initial.
391 if !pollTimer.Stop() {
392 <-pollTimer.C
394 pollDelay = initPollDelay
396 pollTimer.Reset(pollDelay)
398 // Unlike in the server, in the client we assume that because
399 // the data capacity of queries is so limited, it's not worth
400 // trying to send more than one packet per query.
401 err := c.send(transport, p, addr)
402 if err != nil {
403 log.Printf("send: %v", err)
404 continue