| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393 |
- package main
- import (
- "bytes"
- "crypto/rand"
- "encoding/base32"
- "encoding/binary"
- "fmt"
- "io"
- "log"
- "net"
- "time"
- "www.bamsoftware.com/git/dnstt.git/dns"
- "www.bamsoftware.com/git/dnstt.git/turbotunnel"
- )
- const (
- // How many bytes of random padding to insert into queries.
- numPadding = 3
- // In an otherwise empty polling query, insert even more random padding,
- // to reduce the chance of a cache hit. Cannot be greater than 31,
- // because the prefix codes indicating padding start at 224.
- numPaddingForPoll = 8
- // sendLoop has a poll timer that automatically sends an empty polling
- // query when a certain amount of time has elapsed without a send. The
- // poll timer is initially set to initPollDelay. It increases by a
- // factor of pollDelayMultiplier every time the poll timer expires, up
- // to a maximum of maxPollDelay. The poll timer is reset to
- // initPollDelay whenever an a send occurs that is not the result of the
- // poll timer expiring.
- initPollDelay = 500 * time.Millisecond
- maxPollDelay = 10 * time.Second
- pollDelayMultiplier = 2.0
- // A limit on the number of empty poll requests we may send in a burst
- // as a result of receiving data.
- pollLimit = 16
- )
- // base32Encoding is a base32 encoding without padding.
- var base32Encoding = base32.StdEncoding.WithPadding(base32.NoPadding)
- // DNSPacketConn provides a packet-sending and -receiving interface over various
- // forms of DNS. It handles the details of how packets and padding are encoded
- // as a DNS name in the Question section of an upstream query, and as a TXT RR
- // in downstream responses.
- //
- // DNSPacketConn does not handle the mechanics of actually sending and receiving
- // encoded DNS messages. That is rather the responsibility of some other
- // net.PacketConn such as net.UDPConn, HTTPPacketConn, or TLSPacketConn, one of
- // which must be provided to NewDNSPacketConn.
- //
- // We don't have a need to match up a query and a response by ID. Queries and
- // responses are vehicles for carrying data and for our purposes don't need to
- // be correlated. When sending a query, we generate a random ID, and when
- // receiving a response, we ignore the ID.
- type DNSPacketConn struct {
- clientID turbotunnel.ClientID
- domain dns.Name
- // Sending on pollChan permits sendLoop to send an empty polling query.
- // sendLoop also does its own polling according to a time schedule.
- pollChan chan struct{}
- // QueuePacketConn is the direct receiver of ReadFrom and WriteTo calls.
- // recvLoop and sendLoop take the messages out of the receive and send
- // queues and actually put them on the network.
- *turbotunnel.QueuePacketConn
- }
- // NewDNSPacketConn creates a new DNSPacketConn. transport, through its WriteTo
- // and ReadFrom methods, handles the actual sending and receiving the DNS
- // messages encoded by DNSPacketConn. addr is the address to be passed to
- // transport.WriteTo whenever a message needs to be sent.
- func NewDNSPacketConn(transport net.PacketConn, addr net.Addr, domain dns.Name) *DNSPacketConn {
- // Generate a new random ClientID.
- clientID := turbotunnel.NewClientID()
- c := &DNSPacketConn{
- clientID: clientID,
- domain: domain,
- pollChan: make(chan struct{}, pollLimit),
- QueuePacketConn: turbotunnel.NewQueuePacketConn(clientID, 0),
- }
- go func() {
- err := c.recvLoop(transport)
- if err != nil {
- log.Printf("recvLoop: %v", err)
- }
- }()
- go func() {
- err := c.sendLoop(transport, addr)
- if err != nil {
- log.Printf("sendLoop: %v", err)
- }
- }()
- return c
- }
- // dnsResponsePayload extracts the downstream payload of a DNS response, encoded
- // into the RDATA of a TXT RR. It returns nil if the message doesn't pass format
- // checks, or if the name in its Question entry is not a subdomain of domain.
- func dnsResponsePayload(resp *dns.Message, domain dns.Name) []byte {
- if resp.Flags&0x8000 != 0x8000 {
- // QR != 1, this is not a response.
- return nil
- }
- if resp.Flags&0x000f != dns.RcodeNoError {
- return nil
- }
- if len(resp.Answer) != 1 {
- return nil
- }
- answer := resp.Answer[0]
- _, ok := answer.Name.TrimSuffix(domain)
- if !ok {
- // Not the name we are expecting.
- return nil
- }
- if answer.Type != dns.RRTypeTXT {
- // We only support TYPE == TXT.
- return nil
- }
- payload, err := dns.DecodeRDataTXT(answer.Data)
- if err != nil {
- return nil
- }
- return payload
- }
- // nextPacket reads the next length-prefixed packet from r. It returns a nil
- // error only when a complete packet was read. It returns io.EOF only when there
- // were 0 bytes remaining to read from r. It returns io.ErrUnexpectedEOF when
- // EOF occurs in the middle of an encoded packet.
- func nextPacket(r *bytes.Reader) ([]byte, error) {
- for {
- var n uint16
- err := binary.Read(r, binary.BigEndian, &n)
- if err != nil {
- // We may return a real io.EOF only here.
- return nil, err
- }
- p := make([]byte, n)
- _, err = io.ReadFull(r, p)
- // Here we must change io.EOF to io.ErrUnexpectedEOF.
- if err == io.EOF {
- err = io.ErrUnexpectedEOF
- }
- return p, err
- }
- }
- // recvLoop repeatedly calls transport.ReadFrom to receive a DNS message,
- // extracts its payload and breaks it into packets, and stores the packets in a
- // queue to be returned from a future call to c.ReadFrom.
- //
- // Whenever we receive a DNS response containing at least one data packet, we
- // send on c.pollChan to permit sendLoop to send an immediate polling queries.
- // KCP itself will also send an ACK packet for incoming data, which is
- // effectively a second poll. Therefore, each time we receive data, we send up
- // to 2 polling queries (or 1 + f polling queries, if KCP only ACKs an f
- // fraction of incoming data). We say "up to" because sendLoop will discard an
- // empty polling query if it has an organic non-empty packet to send (this goes
- // also for KCP's organic ACK packets).
- //
- // The intuition behind polling immediately after receiving is that if server
- // has just had something to send, it may have more to send, and in order for
- // the server to send anything, we must give it a query to respond to. The
- // intuition behind polling *2 times* (or 1 + f times) is similar to TCP slow
- // start: we want to maintain some number of queries "in flight", and the faster
- // the server is sending, the higher that number should be. If we polled only
- // once for each received packet, we would tend to have only one query in flight
- // at a time, ping-pong style. The first polling query replaces the in-flight
- // query that has just finished its duty in returning data to us; the second
- // grows the effective in-flight window proportional to the rate at which
- // data-carrying responses are being received. Compare to Eq. (2) of
- // https://tools.ietf.org/html/rfc5681#section-3.1. The differences are that we
- // count messages, not bytes, and we don't maintain an explicit window. If a
- // response comes back without data, or if a query or response is dropped by the
- // network, then we don't poll again, which decreases the effective in-flight
- // window.
- func (c *DNSPacketConn) recvLoop(transport net.PacketConn) error {
- for {
- var buf [4096]byte
- n, addr, err := transport.ReadFrom(buf[:])
- if err != nil {
- if err, ok := err.(net.Error); ok && err.Temporary() {
- log.Printf("ReadFrom temporary error: %v", err)
- continue
- }
- return err
- }
- // Got a response. Try to parse it as a DNS message.
- resp, err := dns.MessageFromWireFormat(buf[:n])
- if err != nil {
- log.Printf("MessageFromWireFormat: %v", err)
- continue
- }
- payload := dnsResponsePayload(&resp, c.domain)
- // Pull out the packets contained in the payload.
- r := bytes.NewReader(payload)
- any := false
- for {
- p, err := nextPacket(r)
- if err != nil {
- break
- }
- any = true
- c.QueuePacketConn.QueueIncoming(p, addr)
- }
- // If the payload contained one or more packets, permit sendLoop
- // to poll immediately. ACKs on received data will effectively
- // serve as another stream of polls whose rate is proportional
- // to the rate of incoming packets.
- if any {
- select {
- case c.pollChan <- struct{}{}:
- default:
- }
- }
- }
- }
- // chunks breaks p into non-empty subslices of at most n bytes, greedily so that
- // only final subslice has length < n.
- func chunks(p []byte, n int) [][]byte {
- var result [][]byte
- for len(p) > 0 {
- sz := len(p)
- if sz > n {
- sz = n
- }
- result = append(result, p[:sz])
- p = p[sz:]
- }
- return result
- }
- // send sends p as a single packet encoded into a DNS query, using
- // transport.WriteTo(query, addr). The length of p must be less than 224 bytes.
- //
- // Here is an example of how a packet is encoded into a DNS name, using
- // p = "supercalifragilisticexpialidocious"
- // c.clientID = "CLIENTID"
- // domain = "t.example.com"
- //
- // 0. Start with the raw packet contents.
- // supercalifragilisticexpialidocious
- // 1. Length-prefix the packet and add random padding. A length prefix L < 0xe0
- // means a data packet of L bytes. A length prefix L >= 0xe0 means padding of L -
- // 0xe0 bytes (not counting the length of the length prefix itself).
- // \xe3\xd9\xa3\x15\x22supercalifragilisticexpialidocious
- // 2. Prefix the ClientID.
- // CLIENTID\xe3\xd9\xa3\x15\x22supercalifragilisticexpialidocious
- // 3. Base32-encode, without padding and in lower case.
- // ingesrkokreujy6zumkse43vobsxey3bnruwm4tbm5uwy2ltoruwgzlyobuwc3djmrxwg2lpovzq
- // 4. Break into labels of at most 63 octets.
- // ingesrkokreujy6zumkse43vobsxey3bnruwm4tbm5uwy2ltoruwgzlyobuwc3d.jmrxwg2lpovzq
- // 5. Append the domain.
- // ingesrkokreujy6zumkse43vobsxey3bnruwm4tbm5uwy2ltoruwgzlyobuwc3d.jmrxwg2lpovzq.t.example.com
- func (c *DNSPacketConn) send(transport net.PacketConn, p []byte, addr net.Addr) error {
- var decoded []byte
- {
- if len(p) >= 224 {
- return fmt.Errorf("too long")
- }
- var buf bytes.Buffer
- // ClientID
- buf.Write(c.clientID[:])
- n := numPadding
- if len(p) == 0 {
- n = numPaddingForPoll
- }
- // Padding / cache inhibition
- buf.WriteByte(byte(224 + n))
- io.CopyN(&buf, rand.Reader, int64(n))
- // Packet contents
- if len(p) > 0 {
- buf.WriteByte(byte(len(p)))
- buf.Write(p)
- }
- decoded = buf.Bytes()
- }
- encoded := make([]byte, base32Encoding.EncodedLen(len(decoded)))
- base32Encoding.Encode(encoded, decoded)
- encoded = bytes.ToLower(encoded)
- labels := chunks(encoded, 63)
- labels = append(labels, c.domain...)
- name, err := dns.NewName(labels)
- if err != nil {
- return err
- }
- var id uint16
- binary.Read(rand.Reader, binary.BigEndian, &id)
- query := &dns.Message{
- ID: id,
- Flags: 0x0100, // QR = 0, RD = 1
- Question: []dns.Question{
- {
- Name: name,
- Type: dns.RRTypeTXT,
- Class: dns.ClassIN,
- },
- },
- // EDNS(0)
- Additional: []dns.RR{
- {
- Name: dns.Name{},
- Type: dns.RRTypeOPT,
- Class: 4096, // requester's UDP payload size
- TTL: 0, // extended RCODE and flags
- Data: []byte{},
- },
- },
- }
- buf, err := query.WireFormat()
- if err != nil {
- return err
- }
- _, err = transport.WriteTo(buf, addr)
- return err
- }
- // sendLoop takes packets that have been written using c.WriteTo, and sends them
- // on the network using send. It also does polling with empty packets when
- // requested by pollChan or after a timeout.
- func (c *DNSPacketConn) sendLoop(transport net.PacketConn, addr net.Addr) error {
- pollDelay := initPollDelay
- pollTimer := time.NewTimer(pollDelay)
- for {
- var p []byte
- outgoing := c.QueuePacketConn.OutgoingQueue(addr)
- pollTimerExpired := false
- // Prioritize sending an actual data packet from outgoing. Only
- // consider a poll when outgoing is empty.
- select {
- case p = <-outgoing:
- default:
- select {
- case p = <-outgoing:
- case <-c.pollChan:
- case <-pollTimer.C:
- pollTimerExpired = true
- }
- }
- if len(p) > 0 {
- // A data-carrying packet displaces one pending poll
- // opportunity, if any.
- select {
- case <-c.pollChan:
- default:
- }
- }
- if pollTimerExpired {
- // We're polling because it's been a while since we last
- // polled. Increase the poll delay.
- pollDelay = time.Duration(float64(pollDelay) * pollDelayMultiplier)
- if pollDelay > maxPollDelay {
- pollDelay = maxPollDelay
- }
- } else {
- // We're sending an actual data packet, or we're polling
- // in response to a received packet. Reset the poll
- // delay to initial.
- if !pollTimer.Stop() {
- <-pollTimer.C
- }
- pollDelay = initPollDelay
- }
- pollTimer.Reset(pollDelay)
- // Unlike in the server, in the client we assume that because
- // the data capacity of queries is so limited, it's not worth
- // trying to send more than one packet per query.
- err := c.send(transport, p, addr)
- if err != nil {
- log.Printf("send: %v", err)
- continue
- }
- }
- }
|