Document a simpler SSH SOCKS configuration.
[dnstt.git] / dnstt-client / dns.go
blob564d2cf831f1c5eae1fdbba86826c60dc69f0ca7
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
38 // base32Encoding is a base32 encoding without padding.
39 var base32Encoding = base32.StdEncoding.WithPadding(base32.NoPadding)
41 // DNSPacketConn provides a packet-sending and -receiving interface over various
42 // forms of DNS. It handles the details of how packets and padding are encoded
43 // as a DNS name in the Question section of an upstream query, and as a TXT RR
44 // in downstream responses.
46 // DNSPacketConn does not handle the mechanics of actually sending and receiving
47 // encoded DNS messages. That is rather the responsibility of some other
48 // net.PacketConn such as net.UDPConn, HTTPPacketConn, or TLSPacketConn, one of
49 // which must be provided to NewDNSPacketConn.
51 // We don't have a need to match up a query and a response by ID. Queries and
52 // responses are vehicles for carrying data and for our purposes don't need to
53 // be correlated. When sending a query, we generate a random ID, and when
54 // receiving a response, we ignore the ID.
55 type DNSPacketConn struct {
56 clientID turbotunnel.ClientID
57 domain dns.Name
58 // Sending on pollChan permits sendLoop to send an empty polling query.
59 // sendLoop also does its own polling according to a time schedule.
60 pollChan chan struct{}
61 // QueuePacketConn is the direct receiver of ReadFrom and WriteTo calls.
62 // recvLoop and sendLoop take the messages out of the receive and send
63 // queues and actually put them on the network.
64 *turbotunnel.QueuePacketConn
67 // NewDNSPacketConn creates a new DNSPacketConn. transport, through its WriteTo
68 // and ReadFrom methods, handles the actual sending and receiving the DNS
69 // messages encoded by DNSPacketConn. addr is the address to be passed to
70 // transport.WriteTo whenever a message needs to be sent.
71 func NewDNSPacketConn(transport net.PacketConn, addr net.Addr, domain dns.Name) *DNSPacketConn {
72 // Generate a new random ClientID.
73 clientID := turbotunnel.NewClientID()
74 c := &DNSPacketConn{
75 clientID: clientID,
76 domain: domain,
77 pollChan: make(chan struct{}),
78 QueuePacketConn: turbotunnel.NewQueuePacketConn(clientID, 0),
80 go func() {
81 err := c.recvLoop(transport)
82 if err != nil {
83 log.Printf("recvLoop: %v", err)
85 }()
86 go func() {
87 err := c.sendLoop(transport, addr)
88 if err != nil {
89 log.Printf("sendLoop: %v", err)
91 }()
92 return c
95 // dnsResponsePayload extracts the downstream payload of a DNS response, encoded
96 // into the RDATA of a TXT RR. It returns nil if the message doesn't pass format
97 // checks, or if the name in its Question entry is not a subdomain of domain.
98 func dnsResponsePayload(resp *dns.Message, domain dns.Name) []byte {
99 if resp.Flags&0x8000 != 0x8000 {
100 // QR != 1, this is not a response.
101 return nil
103 if resp.Flags&0x000f != dns.RcodeNoError {
104 return nil
107 if len(resp.Answer) != 1 {
108 return nil
110 answer := resp.Answer[0]
112 _, ok := answer.Name.TrimSuffix(domain)
113 if !ok {
114 // Not the name we are expecting.
115 return nil
118 if answer.Type != dns.RRTypeTXT {
119 // We only support TYPE == TXT.
120 return nil
122 payload, err := dns.DecodeRDataTXT(answer.Data)
123 if err != nil {
124 return nil
127 return payload
130 // nextPacket reads the next length-prefixed packet from r. It returns a nil
131 // error only when a complete packet was read. It returns io.EOF only when there
132 // were 0 bytes remaining to read from r. It returns io.ErrUnexpectedEOF when
133 // EOF occurs in the middle of an encoded packet.
134 func nextPacket(r *bytes.Reader) ([]byte, error) {
135 for {
136 var n uint16
137 err := binary.Read(r, binary.BigEndian, &n)
138 if err != nil {
139 // We may return a real io.EOF only here.
140 return nil, err
142 p := make([]byte, n)
143 _, err = io.ReadFull(r, p)
144 // Here we must change io.EOF to io.ErrUnexpectedEOF.
145 if err == io.EOF {
146 err = io.ErrUnexpectedEOF
148 return p, err
152 // recvLoop repeatedly calls transport.ReadFrom to receive a DNS message,
153 // extracts its payload and breaks it into packets, and stores the packets in a
154 // queue to be returned from a future call to c.ReadFrom.
156 // Whenever we receive a response with a non-empty payload, we send twice on
157 // c.pollChan to permit sendLoop to send two immediate polling queries. The
158 // intuition behind polling immediately after receiving is that we know the
159 // server has just had something to send, it may need to send more, and the only
160 // way it can send is if we give it a query to respond to. The intuition behind
161 // doing *two* polls when we receive is similar to TCP slow start: we want to
162 // maintain some number of queries "in flight", and the faster the server is
163 // sending, the higher that number should be. If we polled only once in response
164 // to received data, we would tend to have only one query in flight at a time,
165 // ping-pong style. The first polling request replaces the in-flight request
166 // that has just finished in our receiving data; the second grows the effective
167 // in-flight window proportionally to the rate at which data-carrying responses
168 // are being received. Compare to Eq. (2) of
169 // https://tools.ietf.org/html/rfc5681#section-3.1; the differences are that we
170 // count messages, not bytes, and we don't maintain an explicit window. If a
171 // response comes back without data, or if a query or response is dropped by the
172 // network, then we don't poll again, which decreases the effective in-flight
173 // window.
174 func (c *DNSPacketConn) recvLoop(transport net.PacketConn) error {
175 for {
176 var buf [4096]byte
177 n, addr, err := transport.ReadFrom(buf[:])
178 if err != nil {
179 if err, ok := err.(net.Error); ok && err.Temporary() {
180 log.Printf("ReadFrom temporary error: %v", err)
181 continue
183 return err
186 // Got a response. Try to parse it as a DNS message.
187 resp, err := dns.MessageFromWireFormat(buf[:n])
188 if err != nil {
189 log.Printf("MessageFromWireFormat: %v", err)
190 continue
193 payload := dnsResponsePayload(&resp, c.domain)
195 // Pull out the packets contained in the payload.
196 r := bytes.NewReader(payload)
197 any := false
198 for {
199 p, err := nextPacket(r)
200 if err != nil {
201 break
203 any = true
204 c.QueuePacketConn.QueueIncoming(p, addr)
207 // If the payload contained one or more packets, permit sendLoop
208 // to poll immediately.
209 if any {
210 select {
211 case c.pollChan <- struct{}{}:
212 default:
214 select {
215 case c.pollChan <- struct{}{}:
216 default:
222 // chunks breaks p into non-empty subslices of at most n bytes, greedily so that
223 // only final subslice has length < n.
224 func chunks(p []byte, n int) [][]byte {
225 var result [][]byte
226 for len(p) > 0 {
227 sz := len(p)
228 if sz > n {
229 sz = n
231 result = append(result, p[:sz])
232 p = p[sz:]
234 return result
237 // send sends p as a single packet encoded into a DNS query, using
238 // transport.WriteTo(query, addr). The length of p must be less than 224 bytes.
240 // Here is an example of how a packet is encoded into a DNS name, using
241 // p = "supercalifragilisticexpialidocious"
242 // c.clientID = "CLIENTID"
243 // domain = "t.example.com"
245 // 0. Start with the raw packet contents.
246 // supercalifragilisticexpialidocious
247 // 1. Length-prefix the packet and add random padding. A length prefix L < 0xe0
248 // means a data packet of L bytes. A length prefix L >= 0xe0 means padding of L -
249 // 0xe0 bytes (not counting the length of the length prefix itself).
250 // \xe3\xd9\xa3\x15\x22supercalifragilisticexpialidocious
251 // 2. Prefix the ClientID.
252 // CLIENTID\xe3\xd9\xa3\x15\x22supercalifragilisticexpialidocious
253 // 3. Base32-encode, without padding and in lower case.
254 // ingesrkokreujy6zumkse43vobsxey3bnruwm4tbm5uwy2ltoruwgzlyobuwc3djmrxwg2lpovzq
255 // 4. Break into labels of at most 63 octets.
256 // ingesrkokreujy6zumkse43vobsxey3bnruwm4tbm5uwy2ltoruwgzlyobuwc3d.jmrxwg2lpovzq
257 // 5. Append the domain.
258 // ingesrkokreujy6zumkse43vobsxey3bnruwm4tbm5uwy2ltoruwgzlyobuwc3d.jmrxwg2lpovzq.t.example.com
259 func (c *DNSPacketConn) send(transport net.PacketConn, p []byte, addr net.Addr) error {
260 var decoded []byte
262 if len(p) >= 224 {
263 return fmt.Errorf("too long")
265 var buf bytes.Buffer
266 // ClientID
267 buf.Write(c.clientID[:])
268 n := numPadding
269 if len(p) == 0 {
270 n = numPaddingForPoll
272 // Padding / cache inhibition
273 buf.WriteByte(byte(224 + n))
274 io.CopyN(&buf, rand.Reader, int64(n))
275 // Packet contents
276 if len(p) > 0 {
277 buf.WriteByte(byte(len(p)))
278 buf.Write(p)
280 decoded = buf.Bytes()
283 encoded := make([]byte, base32Encoding.EncodedLen(len(decoded)))
284 base32Encoding.Encode(encoded, decoded)
285 encoded = bytes.ToLower(encoded)
286 labels := chunks(encoded, 63)
287 labels = append(labels, c.domain...)
288 name, err := dns.NewName(labels)
289 if err != nil {
290 return err
293 var id uint16
294 binary.Read(rand.Reader, binary.BigEndian, &id)
295 query := &dns.Message{
296 ID: id,
297 Flags: 0x0100, // QR = 0, RD = 1
298 Question: []dns.Question{
300 Name: name,
301 Type: dns.RRTypeTXT,
302 Class: dns.ClassIN,
305 // EDNS(0)
306 Additional: []dns.RR{
308 Name: dns.Name{},
309 Type: dns.RRTypeOPT,
310 Class: 4096, // requester's UDP payload size
311 TTL: 0, // extended RCODE and flags
312 Data: []byte{},
316 buf, err := query.WireFormat()
317 if err != nil {
318 return err
321 _, err = transport.WriteTo(buf, addr)
322 return err
325 // sendLoop takes packets that have been written using c.WriteTo, and sends them
326 // on the network using send. It also does polling with empty packets when
327 // requested by pollChan or after a timeout.
328 func (c *DNSPacketConn) sendLoop(transport net.PacketConn, addr net.Addr) error {
329 pollDelay := initPollDelay
330 pollTimer := time.NewTimer(pollDelay)
331 for {
332 var p []byte
333 outgoingQueue := c.QueuePacketConn.OutgoingQueue(addr)
334 pollTimerExpired := false
335 // Prioritize sending an actual data packet from OutgoingQueue.
336 // Only consider a poll when OutgoingQueue is empty.
337 select {
338 case p = <-outgoingQueue:
339 default:
340 select {
341 case p = <-outgoingQueue:
342 case <-c.pollChan:
343 p = nil
344 case <-pollTimer.C:
345 p = nil
346 pollTimerExpired = true
350 if len(p) > 0 {
351 // A data-carrying packet displaces one pending poll
352 // opportunity, if any.
353 select {
354 case <-c.pollChan:
355 default:
359 if pollTimerExpired {
360 // We're polling because it's been a while since we last
361 // polled. Increase the poll delay.
362 pollDelay = time.Duration(float64(pollDelay) * pollDelayMultiplier)
363 if pollDelay > maxPollDelay {
364 pollDelay = maxPollDelay
366 } else {
367 // We're sending an actual data packet, or we're polling
368 // in response to a received packet. Reset the poll
369 // delay to initial.
370 if !pollTimer.Stop() {
371 <-pollTimer.C
373 pollDelay = initPollDelay
375 pollTimer.Reset(pollDelay)
377 // Unlike in the server, in the client we assume that because
378 // the data capacity of queries is so limited, it's not worth
379 // trying to send more than one packet per query.
380 err := c.send(transport, p, addr)
381 if err != nil {
382 log.Printf("send: %v", err)
383 continue