Launch
This commit is contained in:
407
internal/dnsttclient/dns.go
Normal file
407
internal/dnsttclient/dns.go
Normal file
@@ -0,0 +1,407 @@
|
||||
package dnsttclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/base32"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"socksrevivepc/internal/dnsttcore/dns"
|
||||
"socksrevivepc/internal/dnsttcore/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"
|
||||
//
|
||||
// as the input.
|
||||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user