Files
2026-05-16 00:18:06 -03:00

408 lines
12 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}
}
}