mirror of
https://github.com/tladesignz/dnstt.git
synced 2026-10-01 19:38:02 +03:00
Documentation and light refactoring.
This commit is contained in:
@@ -66,7 +66,7 @@ $ go build
|
||||
First you need to generate the server keypair that will be used to
|
||||
authenticate the server and encrypt the tunnel.
|
||||
```
|
||||
$ ./dnstt-server -gen-key -privkey-file server.key
|
||||
$ ./dnstt-server -gen-key -privkey-file server.key -pubkey-file server.pub
|
||||
privkey written to server.key
|
||||
pubkey written to server.pub
|
||||
```
|
||||
|
||||
+58
-28
@@ -52,11 +52,11 @@ const (
|
||||
ClassIN = 1
|
||||
|
||||
// https://tools.ietf.org/html/rfc1035#section-4.1.1
|
||||
RcodeNoError = 0
|
||||
RcodeFormatError = 1
|
||||
RcodeNameError = 3 // a.k.a. NXDOMAIN
|
||||
RcodeNotImplemented = 4
|
||||
ExtendedRcodeBadVers = 16
|
||||
RcodeNoError = 0 // a.k.a. NOERROR
|
||||
RcodeFormatError = 1 // a.k.a. FORMERR
|
||||
RcodeNameError = 3 // a.k.a. NXDOMAIN
|
||||
RcodeNotImplemented = 4 // a.k.a. NOTIMPL
|
||||
ExtendedRcodeBadVers = 16 // a.k.a. BADVERS
|
||||
)
|
||||
|
||||
// Name represents a domain name, a sequence of labels each of which is 63
|
||||
@@ -83,7 +83,7 @@ func NewName(labels [][]byte) (Name, error) {
|
||||
}
|
||||
// Check the total length.
|
||||
builder := newMessageBuilder()
|
||||
builder.writeName(name)
|
||||
builder.WriteName(name)
|
||||
if len(builder.Bytes()) > 255 {
|
||||
return nil, ErrNameTooLong
|
||||
}
|
||||
@@ -145,11 +145,15 @@ type Message struct {
|
||||
}
|
||||
|
||||
// Opcode extracts the OPCODE part of the Flags field.
|
||||
//
|
||||
// https://tools.ietf.org/html/rfc1035#section-4.1.1
|
||||
func (msg *Message) Opcode() uint16 {
|
||||
return (msg.Flags >> 11) & 0xf
|
||||
}
|
||||
|
||||
// Rcode extracts the RCODE part of the Flags field.
|
||||
//
|
||||
// https://tools.ietf.org/html/rfc1035#section-4.1.1
|
||||
func (msg *Message) Rcode() uint16 {
|
||||
return msg.Flags & 0x000f
|
||||
}
|
||||
@@ -174,6 +178,8 @@ type RR struct {
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// readName parses a DNS name from r. It leaves r positioned just after the
|
||||
// parsed named.
|
||||
func readName(r io.ReadSeeker) (Name, error) {
|
||||
var labels [][]byte
|
||||
// We limit the number of compression pointers we are willing to follow.
|
||||
@@ -250,10 +256,12 @@ loop:
|
||||
return NewName(labels)
|
||||
}
|
||||
|
||||
// readQuestion parses one entry from the Question section. It leaves r
|
||||
// positioned just after the parsed entry.
|
||||
//
|
||||
// https://tools.ietf.org/html/rfc1035#section-4.1.2
|
||||
func readQuestion(r io.ReadSeeker) (Question, error) {
|
||||
var question Question
|
||||
|
||||
// https://tools.ietf.org/html/rfc1035#section-4.1.2
|
||||
var err error
|
||||
question.Name, err = readName(r)
|
||||
if err != nil {
|
||||
@@ -269,10 +277,12 @@ func readQuestion(r io.ReadSeeker) (Question, error) {
|
||||
return question, nil
|
||||
}
|
||||
|
||||
// readRR parses one resource record. It leaves r positioned just after the
|
||||
// parsed resource record.
|
||||
//
|
||||
// https://tools.ietf.org/html/rfc1035#section-4.1.3
|
||||
func readRR(r io.ReadSeeker) (RR, error) {
|
||||
var rr RR
|
||||
|
||||
// https://tools.ietf.org/html/rfc1035#section-4.1.3
|
||||
var err error
|
||||
rr.Name, err = readName(r)
|
||||
if err != nil {
|
||||
@@ -302,6 +312,8 @@ func readRR(r io.ReadSeeker) (RR, error) {
|
||||
return rr, nil
|
||||
}
|
||||
|
||||
// readMessage parses a complete DNS message. It leaves r positioned just after
|
||||
// the parsed message.
|
||||
func readMessage(r io.ReadSeeker) (Message, error) {
|
||||
var message Message
|
||||
|
||||
@@ -350,8 +362,9 @@ func readMessage(r io.ReadSeeker) (Message, error) {
|
||||
return message, nil
|
||||
}
|
||||
|
||||
// MessageFromWireFormat parses a message from a buffer of bytes and returns a
|
||||
// Message object.
|
||||
// MessageFromWireFormat parses a message from buf and returns a Message object.
|
||||
// It returns ErrTrailingBytes if there are bytes remaining in buf after parsing
|
||||
// is done.
|
||||
func MessageFromWireFormat(buf []byte) (Message, error) {
|
||||
r := bytes.NewReader(buf)
|
||||
message, err := readMessage(r)
|
||||
@@ -369,22 +382,29 @@ func MessageFromWireFormat(buf []byte) (Message, error) {
|
||||
return message, err
|
||||
}
|
||||
|
||||
// messageBuilder manages the state of serializing a DNS message. Its main
|
||||
// function is to keep track of names already written for the purpose of name
|
||||
// compression.
|
||||
type messageBuilder struct {
|
||||
w bytes.Buffer
|
||||
nameCache map[string]int
|
||||
}
|
||||
|
||||
// newMessageBuilder creates a new messageBuilder with an empty name cache.
|
||||
func newMessageBuilder() *messageBuilder {
|
||||
return &messageBuilder{
|
||||
nameCache: make(map[string]int),
|
||||
}
|
||||
}
|
||||
|
||||
// Bytes returns the serialized DNS message as a slice of bytes.
|
||||
func (builder *messageBuilder) Bytes() []byte {
|
||||
return builder.w.Bytes()
|
||||
}
|
||||
|
||||
func (builder *messageBuilder) writeName(name Name) {
|
||||
// WriteName appends name to the in-progress messageBuilder, employing
|
||||
// compression pointers to previously written names if possible.
|
||||
func (builder *messageBuilder) WriteName(name Name) {
|
||||
// https://tools.ietf.org/html/rfc1035#section-3.1
|
||||
for i := range name {
|
||||
// Has this suffix already been encoded in the message?
|
||||
@@ -406,17 +426,20 @@ func (builder *messageBuilder) writeName(name Name) {
|
||||
builder.w.WriteByte(0)
|
||||
}
|
||||
|
||||
func (builder *messageBuilder) writeQuestion(question *Question) error {
|
||||
// WriteQuestion appends a Question section entry to the in-progress
|
||||
// messageBuilder.
|
||||
func (builder *messageBuilder) WriteQuestion(question *Question) {
|
||||
// https://tools.ietf.org/html/rfc1035#section-4.1.2
|
||||
builder.writeName(question.Name)
|
||||
builder.WriteName(question.Name)
|
||||
binary.Write(&builder.w, binary.BigEndian, question.Type)
|
||||
binary.Write(&builder.w, binary.BigEndian, question.Class)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (builder *messageBuilder) writeRR(rr *RR) error {
|
||||
// WriteRR appends a resource record to the in-progress messageBuilder. It
|
||||
// returns ErrIntegerOverflow if the length of rr.Data does not fit in 16 bits.
|
||||
func (builder *messageBuilder) WriteRR(rr *RR) error {
|
||||
// https://tools.ietf.org/html/rfc1035#section-4.1.3
|
||||
builder.writeName(rr.Name)
|
||||
builder.WriteName(rr.Name)
|
||||
binary.Write(&builder.w, binary.BigEndian, rr.Type)
|
||||
binary.Write(&builder.w, binary.BigEndian, rr.Class)
|
||||
binary.Write(&builder.w, binary.BigEndian, rr.TTL)
|
||||
@@ -429,7 +452,11 @@ func (builder *messageBuilder) writeRR(rr *RR) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (builder *messageBuilder) writeMessage(message *Message) error {
|
||||
// WriteMessage appends a complete DNS message to the in-progress
|
||||
// messageBuilder. It returns ErrIntegerOverflow if the number of entries in any
|
||||
// section, or the length of the data in any resource record, does not fit in 16
|
||||
// bits.
|
||||
func (builder *messageBuilder) WriteMessage(message *Message) error {
|
||||
// Header section
|
||||
// https://tools.ietf.org/html/rfc1035#section-4.1.1
|
||||
binary.Write(&builder.w, binary.BigEndian, message.ID)
|
||||
@@ -450,17 +477,14 @@ func (builder *messageBuilder) writeMessage(message *Message) error {
|
||||
// Question section
|
||||
// https://tools.ietf.org/html/rfc1035#section-4.1.2
|
||||
for _, question := range message.Question {
|
||||
err := builder.writeQuestion(&question)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
builder.WriteQuestion(&question)
|
||||
}
|
||||
|
||||
// Answer, Authority, and Additional sections
|
||||
// https://tools.ietf.org/html/rfc1035#section-4.1.3
|
||||
for _, rrs := range [][]RR{message.Answer, message.Authority, message.Additional} {
|
||||
for _, rr := range rrs {
|
||||
err := builder.writeRR(&rr)
|
||||
err := builder.WriteRR(&rr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -470,10 +494,12 @@ func (builder *messageBuilder) writeMessage(message *Message) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// WireFormat encodes a Message as a slice of bytes in wire format.
|
||||
// WireFormat encodes a Message as a slice of bytes in DNS wire format. It
|
||||
// returns ErrIntegerOverflow if the number of entries in any section, or the
|
||||
// length of the data in any resource record, does not fit in 16 bits.
|
||||
func (message *Message) WireFormat() ([]byte, error) {
|
||||
builder := newMessageBuilder()
|
||||
err := builder.writeMessage(message)
|
||||
err := builder.WriteMessage(message)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -483,6 +509,8 @@ func (message *Message) WireFormat() ([]byte, error) {
|
||||
// DecodeRDataTXT decodes TXT-DATA (as found in the RDATA for a resource record
|
||||
// with TYPE=TXT) as a raw byte slice, by concatenating all the
|
||||
// <character-string>s it contains.
|
||||
//
|
||||
// https://tools.ietf.org/html/rfc1035#section-3.3.14
|
||||
func DecodeRDataTXT(p []byte) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
for {
|
||||
@@ -504,8 +532,10 @@ func DecodeRDataTXT(p []byte) ([]byte, error) {
|
||||
}
|
||||
|
||||
// EncodeRDataTXT encodes a slice of bytes as TXT-DATA, as appropriate for the
|
||||
// RDATA of a resource record with TYPE=TXT. There is no length restriction;
|
||||
// that must be checked at a higher level.
|
||||
// RDATA of a resource record with TYPE=TXT. No length restriction is enforced
|
||||
// here; that must be checked at a higher level.
|
||||
//
|
||||
// https://tools.ietf.org/html/rfc1035#section-3.3.14
|
||||
func EncodeRDataTXT(p []byte) []byte {
|
||||
// https://tools.ietf.org/html/rfc1035#section-3.3
|
||||
// https://tools.ietf.org/html/rfc1035#section-3.3.14
|
||||
|
||||
+105
-16
@@ -22,22 +22,55 @@ const (
|
||||
// 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 base32 encoding without padding.
|
||||
// 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.
|
||||
var clientID turbotunnel.ClientID
|
||||
rand.Read(clientID[:])
|
||||
clientID := turbotunnel.NewClientID()
|
||||
c := &DNSPacketConn{
|
||||
clientID: clientID,
|
||||
domain: domain,
|
||||
@@ -59,6 +92,9 @@ func NewDNSPacketConn(transport net.PacketConn, addr net.Addr, domain dns.Name)
|
||||
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.
|
||||
@@ -91,26 +127,50 @@ func dnsResponsePayload(resp *dns.Message, domain dns.Name) []byte {
|
||||
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) {
|
||||
eof := func(err error) error {
|
||||
if err == io.EOF {
|
||||
err = io.ErrUnexpectedEOF
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
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)
|
||||
return p, eof(err)
|
||||
// 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 response with a non-empty payload, we send twice on
|
||||
// c.pollChan to permit sendLoop to send two immediate polling queries. The
|
||||
// intuition behind polling immediately after receiving is that we know the
|
||||
// server has just had something to send, it may need to send more, and the only
|
||||
// way it can send is if we give it a query to respond to. The intuition behind
|
||||
// doing *two* polls when we receive 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 in response
|
||||
// to received data, we would tend to have only one query in flight at a time,
|
||||
// ping-pong style. The first polling request replaces the in-flight request
|
||||
// that has just finished in our receiving data; the second grows the effective
|
||||
// in-flight window proportionally 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
|
||||
@@ -155,6 +215,8 @@ func (c *DNSPacketConn) recvLoop(transport net.PacketConn) error {
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
@@ -168,7 +230,28 @@ func chunks(p []byte, n int) [][]byte {
|
||||
return result
|
||||
}
|
||||
|
||||
// send sends a single packet in a DNS query.
|
||||
// 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
|
||||
{
|
||||
@@ -235,6 +318,9 @@ func (c *DNSPacketConn) send(transport net.PacketConn, p []byte, addr net.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)
|
||||
@@ -242,9 +328,9 @@ func (c *DNSPacketConn) sendLoop(transport net.PacketConn, addr net.Addr) error
|
||||
var p []byte
|
||||
outgoingQueue := c.QueuePacketConn.OutgoingQueue(addr)
|
||||
pollTimerExpired := false
|
||||
// Prioritize sending an actual data packet from OutgoingQueue.
|
||||
// Only consider a poll when OutgoingQueue is empty.
|
||||
select {
|
||||
// Give priority to sending an actual data packet from
|
||||
// OutgoingQueue. Only when that is empty, consider a poll.
|
||||
case p = <-outgoingQueue:
|
||||
default:
|
||||
select {
|
||||
@@ -258,8 +344,8 @@ func (c *DNSPacketConn) sendLoop(transport net.PacketConn, addr net.Addr) error
|
||||
}
|
||||
|
||||
if len(p) > 0 {
|
||||
// We have an actual data-carrying packet, so discard a
|
||||
// pending poll opportunity, if any.
|
||||
// A data-carrying packet displaces one pending poll
|
||||
// opportunity, if any.
|
||||
select {
|
||||
case <-c.pollChan:
|
||||
default:
|
||||
@@ -284,6 +370,9 @@ func (c *DNSPacketConn) sendLoop(transport net.PacketConn, addr net.Addr) error
|
||||
}
|
||||
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)
|
||||
|
||||
+39
-14
@@ -2,26 +2,51 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"www.bamsoftware.com/git/dnstt.git/dns"
|
||||
)
|
||||
|
||||
func TestDNSNameCapacity(t *testing.T) {
|
||||
for domainLen := 0; domainLen < 255; domainLen++ {
|
||||
domain, err := dns.NewName(chunks(bytes.Repeat([]byte{'x'}, domainLen), 63))
|
||||
func allPackets(buf []byte) ([][]byte, error) {
|
||||
var packets [][]byte
|
||||
r := bytes.NewReader(buf)
|
||||
for {
|
||||
p, err := nextPacket(r)
|
||||
if err != nil {
|
||||
continue
|
||||
return packets, err
|
||||
}
|
||||
capacity := dnsNameCapacity(domain)
|
||||
if capacity <= 0 {
|
||||
continue
|
||||
packets = append(packets, p)
|
||||
}
|
||||
}
|
||||
|
||||
func packetsEqual(a, b [][]byte) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range a {
|
||||
if !bytes.Equal(a[i], b[i]) {
|
||||
return false
|
||||
}
|
||||
prefix := []byte(base32Encoding.EncodeToString(bytes.Repeat([]byte{'y'}, capacity)))
|
||||
labels := append(chunks(prefix, 63), domain...)
|
||||
_, err = dns.NewName(labels)
|
||||
if err != nil {
|
||||
t.Errorf("length %v capacity %v %v", domainLen, capacity, err)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func TestNextPacket(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
input string
|
||||
packets [][]byte
|
||||
err error
|
||||
}{
|
||||
{"", [][]byte{}, io.EOF},
|
||||
{"\x00", [][]byte{}, io.ErrUnexpectedEOF},
|
||||
{"\x00\x00", [][]byte{{}}, io.EOF},
|
||||
{"\x00\x00\x00", [][]byte{{}}, io.ErrUnexpectedEOF},
|
||||
{"\x00\x01", [][]byte{}, io.ErrUnexpectedEOF},
|
||||
{"\x00\x05hello\x00\x05world", [][]byte{[]byte("hello"), []byte("world")}, io.EOF},
|
||||
} {
|
||||
packets, err := allPackets([]byte(test.input))
|
||||
if !packetsEqual(packets, test.packets) || err != test.err {
|
||||
t.Errorf("%x\nreturned %x %v\nexpected %x %v",
|
||||
test.input, packets, err, test.packets, test.err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+64
-32
@@ -6,7 +6,6 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
@@ -15,50 +14,62 @@ import (
|
||||
"www.bamsoftware.com/git/dnstt.git/turbotunnel"
|
||||
)
|
||||
|
||||
// A default Retry-After delay to use when there is no explicit Retry-After
|
||||
// header in an HTTP response.
|
||||
const defaultRetryAfter = 10 * time.Second
|
||||
|
||||
// The *http.Client shared by instances of HTTPPacketConn. We use this instead
|
||||
// of http.DefaultClient in order to set a timeout.
|
||||
var httpClient = &http.Client{Timeout: 1 * time.Minute}
|
||||
|
||||
// HTTPPacketConn is an HTTP-based transport for DNS messages, used for DNS over
|
||||
// HTTPS (DoH). Its WriteTo and ReadFrom methods exchange DNS messages over HTTP
|
||||
// requests and responses.
|
||||
//
|
||||
// HTTPPacketConn deals only with alreaday formatted DNS messages. It does not
|
||||
// handle encoding information into the messages. That is rather the
|
||||
// responsibility of DNSPacketConn.
|
||||
//
|
||||
// https://tools.ietf.org/html/rfc8484
|
||||
type HTTPPacketConn struct {
|
||||
urlString string
|
||||
client *http.Client
|
||||
// urlString is the URL to which HTTP requests will be sent, for example
|
||||
// "https://doh.example/dns-query".
|
||||
urlString string
|
||||
|
||||
// notBefore, if not zero, is a time before which we may not send any
|
||||
// queries; queries are buffered or dropped until that time. notBefore
|
||||
// is set when we get a 429 Too Many Requests HTTP response or other
|
||||
// unexpected status code that causes us to need to slow down. It is set
|
||||
// according to the Retry-After header if available, otherwise it is set
|
||||
// to defaultRetryAfter in the future. notBeforeLock controls access to
|
||||
// notBefore.
|
||||
notBefore time.Time
|
||||
notBeforeLock sync.RWMutex
|
||||
|
||||
// QueuePacketConn is the direct receiver of ReadFrom and WriteTo calls.
|
||||
// sendLoop, via send, removes messages from the outgoing queue that
|
||||
// were placed there by WriteTo, and inserts messages into the incoming
|
||||
// queue to be returned from ReadFrom.
|
||||
*turbotunnel.QueuePacketConn
|
||||
}
|
||||
|
||||
// NewHTTPPacketConn creates a new HTTPPacketConn configured to use the HTTP
|
||||
// server at urlString as a DNS over HTTP resolver. urlString should include any
|
||||
// necessary path components; e.g., "/dns-query". numSenders is the number of
|
||||
// concurrent sender-receiver goroutines to run.
|
||||
func NewHTTPPacketConn(urlString string, numSenders int) (*HTTPPacketConn, error) {
|
||||
c := &HTTPPacketConn{
|
||||
urlString: urlString,
|
||||
client: &http.Client{
|
||||
Timeout: 1 * time.Minute,
|
||||
},
|
||||
urlString: urlString,
|
||||
QueuePacketConn: turbotunnel.NewQueuePacketConn(turbotunnel.DummyAddr{}, 0),
|
||||
}
|
||||
for i := 0; i < numSenders; i++ {
|
||||
go func() {
|
||||
for p := range c.QueuePacketConn.OutgoingQueue(turbotunnel.DummyAddr{}) {
|
||||
err := c.send(p)
|
||||
if err != nil {
|
||||
log.Printf("sender thread: %v", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
go c.sendLoop()
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *HTTPPacketConn) WriteTo(p []byte, addr net.Addr) (int, error) {
|
||||
// Drop packets while we are rate-limiting ourselves (as a result of a
|
||||
// Retry-After response header, for example).
|
||||
c.notBeforeLock.RLock()
|
||||
notBefore := c.notBefore
|
||||
c.notBeforeLock.RUnlock()
|
||||
if time.Now().Before(notBefore) {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// Ignore addr.
|
||||
return c.QueuePacketConn.WriteTo(p, turbotunnel.DummyAddr{})
|
||||
}
|
||||
|
||||
// send sends a single packet in an HTTP request.
|
||||
// send sends a message in an HTTP request, and queues the body HTTP response to
|
||||
// be returned from a future call to ReadFrom.
|
||||
func (c *HTTPPacketConn) send(p []byte) error {
|
||||
req, err := http.NewRequest("POST", c.urlString, bytes.NewReader(p))
|
||||
if err != nil {
|
||||
@@ -67,7 +78,7 @@ func (c *HTTPPacketConn) send(p []byte) error {
|
||||
req.Header.Set("Accept", "application/dns-message")
|
||||
req.Header.Set("Content-Type", "application/dns-message")
|
||||
req.Header.Set("User-Agent", "") // Disable default "Go-http-client/1.1".
|
||||
resp, err := c.client.Do(req)
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -100,7 +111,7 @@ func (c *HTTPPacketConn) send(p []byte) error {
|
||||
}
|
||||
if retryAfter.IsZero() {
|
||||
// Supply a default.
|
||||
retryAfter = now.Add(10 * time.Second)
|
||||
retryAfter = now.Add(defaultRetryAfter)
|
||||
}
|
||||
if retryAfter.Before(now) {
|
||||
log.Printf("got %+q, but Retry-After is %v in the past",
|
||||
@@ -122,6 +133,27 @@ func (c *HTTPPacketConn) send(p []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// sendLoop loops over the contents of the outgoing queue and passes them to
|
||||
// send. It drops packets while c.notBefore is in the future.
|
||||
func (c *HTTPPacketConn) sendLoop() {
|
||||
for p := range c.QueuePacketConn.OutgoingQueue(turbotunnel.DummyAddr{}) {
|
||||
// Stop sending while we are rate-limiting ourselves (as a
|
||||
// result of a Retry-After response header, for example).
|
||||
c.notBeforeLock.RLock()
|
||||
notBefore := c.notBefore
|
||||
c.notBeforeLock.RUnlock()
|
||||
if wait := notBefore.Sub(time.Now()); wait > 0 {
|
||||
// Drop it.
|
||||
continue
|
||||
}
|
||||
|
||||
err := c.send(p)
|
||||
if err != nil {
|
||||
log.Printf("sendLoop: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parseRetryAfter parses the value of a Retry-After header as an absolute
|
||||
// time.Time.
|
||||
func parseRetryAfter(value string, now time.Time) (time.Time, error) {
|
||||
|
||||
+57
-34
@@ -1,3 +1,28 @@
|
||||
// dnstt-client is the client end of a DNS tunnel.
|
||||
//
|
||||
// Usage:
|
||||
// dnstt-client [-doh URL|-dot ADDR|-udp ADDR] -pubkey-file PUBKEYFILE DOMAIN LOCALADDR
|
||||
//
|
||||
// Examples:
|
||||
// dnstt-client -doh https://resolver.example/dns-query -pubkey-file server.pub t.example.com 127.0.0.1:7000
|
||||
// dnstt-client -dot resolver.example:853 -pubkey-file server.pub t.example.com 127.0.0.1:7000
|
||||
//
|
||||
// The program supports DNS over HTTPS (DoH), DNS over TLS (DoT), and UDP DNS.
|
||||
// Use one of these options:
|
||||
// -doh https://resolver.example/dns-query
|
||||
// -dot resolver.example:853
|
||||
// -udp resolver.example:53
|
||||
//
|
||||
// You can give the server's public key as a file or as a hex string. Use
|
||||
// "dnstt-server -gen-key" to get the public key.
|
||||
// -pubkey-file server.pub
|
||||
// -pubkey 0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff
|
||||
//
|
||||
// DOMAIN is the root of the DNS zone reserved for the tunnel. See README for
|
||||
// instructions on setting it up.
|
||||
//
|
||||
// LOCALADDR is the TCP address that will listen for connections and forward
|
||||
// them over the tunnel.
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -17,12 +42,38 @@ import (
|
||||
"www.bamsoftware.com/git/dnstt.git/turbotunnel"
|
||||
)
|
||||
|
||||
const (
|
||||
idleTimeout = 10 * time.Minute
|
||||
initPollDelay = 500 * time.Millisecond
|
||||
maxPollDelay = 10 * time.Second
|
||||
pollDelayMultiplier = 2.0
|
||||
)
|
||||
// smux streams will be closed after this much time without receiving data.
|
||||
const idleTimeout = 10 * time.Minute
|
||||
|
||||
// dnsNameCapacity returns the number of bytes remaining for encoded data after
|
||||
// including domain in a DNS name.
|
||||
func dnsNameCapacity(domain dns.Name) int {
|
||||
// Names must be 255 octets or shorter in total length.
|
||||
// https://tools.ietf.org/html/rfc1035#section-2.3.4
|
||||
capacity := 255
|
||||
// Subtract the length of the null terminator.
|
||||
capacity -= 1
|
||||
for _, label := range domain {
|
||||
// Subtract the length of the label and the length octet.
|
||||
capacity -= len(label) + 1
|
||||
}
|
||||
// Each label may be up to 63 bytes long and requires 64 bytes to
|
||||
// encode.
|
||||
capacity = capacity * 63 / 64
|
||||
// Base32 expands every 5 bytes to 8.
|
||||
capacity = capacity * 5 / 8
|
||||
return capacity
|
||||
}
|
||||
|
||||
// readKeyFromFile reads a key from a named file.
|
||||
func readKeyFromFile(filename string) ([]byte, error) {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
return noise.ReadKey(f)
|
||||
}
|
||||
|
||||
func handle(local *net.TCPConn, sess *smux.Session, conv uint32) error {
|
||||
stream, err := sess.OpenStream()
|
||||
@@ -64,34 +115,6 @@ func handle(local *net.TCPConn, sess *smux.Session, conv uint32) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// dnsNameCapacity returns the number of bytes remaining for encoded data after
|
||||
// including domain in a DNS name.
|
||||
func dnsNameCapacity(domain dns.Name) int {
|
||||
// https://tools.ietf.org/html/rfc1035#section-2.3.4
|
||||
// Names must be 255 octets or shorter in total length.
|
||||
capacity := 255
|
||||
// Subtract the length of the null terminator.
|
||||
capacity -= 1
|
||||
for _, label := range domain {
|
||||
// Subtract the length of the label and the length octet.
|
||||
capacity -= len(label) + 1
|
||||
}
|
||||
// Each label may be up to 63 bytes long and requires 64
|
||||
capacity = capacity * 63 / 64
|
||||
// Base32 expands every 5 bytes to 8.
|
||||
capacity = capacity * 5 / 8
|
||||
return capacity
|
||||
}
|
||||
|
||||
func readKeyFromFile(filename string) ([]byte, error) {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
return noise.ReadKey(f)
|
||||
}
|
||||
|
||||
func run(pubkey []byte, domain dns.Name, localAddr *net.TCPAddr, remoteAddr net.Addr, pconn net.PacketConn) error {
|
||||
defer pconn.Close()
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"www.bamsoftware.com/git/dnstt.git/dns"
|
||||
)
|
||||
|
||||
func TestDNSNameCapacity(t *testing.T) {
|
||||
for domainLen := 0; domainLen < 255; domainLen++ {
|
||||
domain, err := dns.NewName(chunks(bytes.Repeat([]byte{'x'}, domainLen), 63))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
capacity := dnsNameCapacity(domain)
|
||||
if capacity <= 0 {
|
||||
continue
|
||||
}
|
||||
prefix := []byte(base32Encoding.EncodeToString(bytes.Repeat([]byte{'y'}, capacity)))
|
||||
labels := append(chunks(prefix, 63), domain...)
|
||||
_, err = dns.NewName(labels)
|
||||
if err != nil {
|
||||
t.Errorf("length %v capacity %v %v", domainLen, capacity, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
+26
-5
@@ -11,14 +11,35 @@ import (
|
||||
"www.bamsoftware.com/git/dnstt.git/turbotunnel"
|
||||
)
|
||||
|
||||
// TLSPacketConn is a TLS- and TCP-based transport for DNS messages, used for
|
||||
// DNS over TLS (DoT). Its WriteTo and ReadFrom methods exchange DNS messages
|
||||
// over a TLS channel, prefixing each message with a two-octet length field as
|
||||
// in DNS over TCP.
|
||||
//
|
||||
// TLSPacketConn deals only with alreaday formatted DNS messages. It does not
|
||||
// handle encoding information into the messages. That is rather the
|
||||
// responsibility of DNSPacketConn.
|
||||
//
|
||||
// https://tools.ietf.org/html/rfc7858
|
||||
type TLSPacketConn 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
|
||||
}
|
||||
|
||||
// NewTLSPacketConn creates a new TLSPacketConn configured to use the TLS
|
||||
// server at addr as a DNS over TLS resolver. It maintains a TLS connection to
|
||||
// the resolver, reconnecting as necessary. It closes the connection if any
|
||||
// reconnection attempt fails.
|
||||
func NewTLSPacketConn(addr string) (*TLSPacketConn, error) {
|
||||
c := &TLSPacketConn{
|
||||
QueuePacketConn: turbotunnel.NewQueuePacketConn(turbotunnel.DummyAddr{}, 0),
|
||||
}
|
||||
// We maintain one TLS connection at a time, redialing it whenever it
|
||||
// becomes disconnected. We do the first dial here, outside the
|
||||
// goroutine, so that any immediate and permanent connection errors are
|
||||
// reported directly to the caller of NewTLSPacketConn.
|
||||
tlsConfig := &tls.Config{}
|
||||
conn, err := tls.Dial("tcp", addr, tlsConfig)
|
||||
if err != nil {
|
||||
@@ -46,6 +67,7 @@ func NewTLSPacketConn(addr string) (*TLSPacketConn, error) {
|
||||
wg.Wait()
|
||||
conn.Close()
|
||||
|
||||
// Whenever the TLS connection dies, redial a new one.
|
||||
conn, err = tls.Dial("tcp", addr, tlsConfig)
|
||||
if err != nil {
|
||||
log.Printf("tls.Dial: %v", err)
|
||||
@@ -56,6 +78,8 @@ func NewTLSPacketConn(addr string) (*TLSPacketConn, error) {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// recvLoop reads length-prefixed messages from conn and passes them to the
|
||||
// incoming queue.
|
||||
func (c *TLSPacketConn) recvLoop(conn net.Conn) error {
|
||||
for {
|
||||
var length uint16
|
||||
@@ -75,6 +99,8 @@ func (c *TLSPacketConn) recvLoop(conn net.Conn) error {
|
||||
}
|
||||
}
|
||||
|
||||
// sendLoop reads messages from the outgoing queue and writes them,
|
||||
// length-prefixed, to conn.
|
||||
func (c *TLSPacketConn) sendLoop(conn net.Conn) error {
|
||||
for p := range c.QueuePacketConn.OutgoingQueue(turbotunnel.DummyAddr{}) {
|
||||
length := uint16(len(p))
|
||||
@@ -92,8 +118,3 @@ func (c *TLSPacketConn) sendLoop(conn net.Conn) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *TLSPacketConn) WriteTo(p []byte, addr net.Addr) (int, error) {
|
||||
// Ignore addr.
|
||||
return c.QueuePacketConn.WriteTo(p, turbotunnel.DummyAddr{})
|
||||
}
|
||||
|
||||
+173
-106
@@ -1,3 +1,32 @@
|
||||
// dnstt-server is the server end of a DNS tunnel.
|
||||
//
|
||||
// Usage:
|
||||
// dnstt-server -gen-key [-privkey-file PRIVKEYFILE] [-pubkey-file PUBKEYFILE]
|
||||
// dnstt-server -udp ADDR [-privkey PRIVKEY|-privkey-file PRIVKEYFILE] DOMAIN UPSTREAMADDR
|
||||
//
|
||||
// Example:
|
||||
// dnstt-server -gen-key -privkey-file server.key -pubkey-file server.pub
|
||||
// dnstt-server -udp 127.0.0.1:5300 -privkey-file server.key t.example.com 127.0.0.1:8000
|
||||
//
|
||||
// To generate a persistent server private key, first run with the -gen-key
|
||||
// option. By default the generated private and public keys are printed to
|
||||
// standard output. To save them to files instead, use the -privkey-file and
|
||||
// -pubkey-file options.
|
||||
// dnstt-server -gen-key
|
||||
// dnstt-server -gen-key -privkey-file server.key -pubkey-file server.pub
|
||||
//
|
||||
// You can give the server's private key as a file or as a hex string.
|
||||
// -privkey-file server.key
|
||||
// -privkey 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
|
||||
//
|
||||
// The -udp option controls the address that will listen for incoming DNS
|
||||
// queries.
|
||||
//
|
||||
// DOMAIN is the root of the DNS zone reserved for the tunnel. See README for
|
||||
// instructions on setting it up.
|
||||
//
|
||||
// UPSTREAMADDR is the TCP address to which incoming tunnelled streams will be
|
||||
// forwarded.
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -22,7 +51,10 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// smux streams will be closed after this much time without receiving data.
|
||||
idleTimeout = 10 * time.Minute
|
||||
|
||||
// How to set the TTL field in Answer resource records.
|
||||
responseTTL = 60
|
||||
|
||||
// We don't send UDP payloads larger than this, in an attempt to avoid
|
||||
@@ -64,10 +96,96 @@ const (
|
||||
maxResponseDelay = 1 * time.Second
|
||||
)
|
||||
|
||||
// A base32 encoding without padding.
|
||||
// base32Encoding is a base32 encoding without padding.
|
||||
var base32Encoding = base32.StdEncoding.WithPadding(base32.NoPadding)
|
||||
|
||||
// handleStream bidirectionally connects a client stream with the ORPort.
|
||||
// generateKeypair generates a private key and the corresponding public key. If
|
||||
// privkeyFilename and pubkeyFilename are respectively empty, it prints the
|
||||
// corresponding key to standard output; otherwise it saves the key to the given
|
||||
// file name. In case of any error, it attempts to delete any files it has
|
||||
// created before returning.
|
||||
func generateKeypair(privkeyFilename, pubkeyFilename string) (err error) {
|
||||
// Filenames to delete in case of error (avoid leaving partially written
|
||||
// files).
|
||||
var toDelete []string
|
||||
defer func() {
|
||||
for _, filename := range toDelete {
|
||||
fmt.Fprintf(os.Stderr, "deleting partially written file %s\n", filename)
|
||||
if closeErr := os.Remove(filename); closeErr != nil {
|
||||
fmt.Fprintf(os.Stderr, "cannot remove %s: %v\n", filename, closeErr)
|
||||
if err == nil {
|
||||
err = closeErr
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
privkey, pubkey, err := noise.GenerateKeypair()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if privkeyFilename != "" {
|
||||
// Save the privkey to a file.
|
||||
f, err := os.Create(privkeyFilename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
toDelete = append(toDelete, privkeyFilename)
|
||||
err = noise.WriteKey(f, privkey)
|
||||
if err2 := f.Close(); err == nil {
|
||||
err = err2
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if pubkeyFilename != "" {
|
||||
// Save the pubkey to a file.
|
||||
f, err := os.Create(pubkeyFilename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
toDelete = append(toDelete, pubkeyFilename)
|
||||
err = noise.WriteKey(f, pubkey)
|
||||
if err2 := f.Close(); err == nil {
|
||||
err = err2
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// All good, allow the written files to remain.
|
||||
toDelete = nil
|
||||
|
||||
if privkeyFilename != "" {
|
||||
fmt.Printf("privkey written to %s\n", privkeyFilename)
|
||||
} else {
|
||||
fmt.Printf("privkey %x\n", privkey)
|
||||
}
|
||||
if pubkeyFilename != "" {
|
||||
fmt.Printf("pubkey written to %s\n", pubkeyFilename)
|
||||
} else {
|
||||
fmt.Printf("pubkey %x\n", pubkey)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// readKeyFromFile reads a key from a named file.
|
||||
func readKeyFromFile(filename string) ([]byte, error) {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
return noise.ReadKey(f)
|
||||
}
|
||||
|
||||
// handleStream bidirectionally connects a client stream with a TCP socket
|
||||
// addressed by upstream.
|
||||
func handleStream(stream *smux.Stream, upstream *net.TCPAddr, conv uint32) error {
|
||||
conn, err := net.DialTCP("tcp", nil, upstream)
|
||||
if err != nil {
|
||||
@@ -104,8 +222,8 @@ func handleStream(stream *smux.Stream, upstream *net.TCPAddr, conv uint32) error
|
||||
return nil
|
||||
}
|
||||
|
||||
// acceptStreams layers an smux.Session on a KCP connection and awaits streams
|
||||
// on it. It passes each stream to handleStream.
|
||||
// acceptStreams wraps a KCP session in a Noise channel and an smux.Session,
|
||||
// then awaits smux streams. It passes each stream to handleStream.
|
||||
func acceptStreams(conn *kcp.UDPSession, privkey, pubkey []byte, upstream *net.TCPAddr) error {
|
||||
// Put a Noise channel on top of the KCP conn.
|
||||
rw, err := noise.NewServer(conn, privkey, pubkey)
|
||||
@@ -113,6 +231,7 @@ func acceptStreams(conn *kcp.UDPSession, privkey, pubkey []byte, upstream *net.T
|
||||
return err
|
||||
}
|
||||
|
||||
// Put an smux session on top of the encrypted Noise channel.
|
||||
smuxConfig := smux.DefaultConfig()
|
||||
smuxConfig.Version = 2
|
||||
smuxConfig.KeepAliveTimeout = idleTimeout
|
||||
@@ -183,7 +302,16 @@ func acceptSessions(ln *kcp.Listener, privkey, pubkey []byte, upstream *net.TCPA
|
||||
}
|
||||
}
|
||||
|
||||
// nextPacket reads the next length-prefixed packet from r, ignoring padding. It
|
||||
// returns a nil error only when a packet was read successfully. 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.
|
||||
//
|
||||
// The prefixing scheme is as follows. 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).
|
||||
func nextPacket(r *bytes.Reader) ([]byte, error) {
|
||||
// Convert io.EOF to io.ErrUnexpectedEOF.
|
||||
eof := func(err error) error {
|
||||
if err == io.EOF {
|
||||
err = io.ErrUnexpectedEOF
|
||||
@@ -194,6 +322,7 @@ func nextPacket(r *bytes.Reader) ([]byte, error) {
|
||||
for {
|
||||
prefix, err := r.ReadByte()
|
||||
if err != nil {
|
||||
// We may return a real io.EOF only here.
|
||||
return nil, err
|
||||
}
|
||||
if prefix >= 224 {
|
||||
@@ -202,14 +331,20 @@ func nextPacket(r *bytes.Reader) ([]byte, error) {
|
||||
if err != nil {
|
||||
return nil, eof(err)
|
||||
}
|
||||
continue
|
||||
} else {
|
||||
p := make([]byte, int(prefix))
|
||||
_, err = io.ReadFull(r, p)
|
||||
return p, eof(err)
|
||||
}
|
||||
p := make([]byte, int(prefix))
|
||||
_, err = io.ReadFull(r, p)
|
||||
return p, eof(err)
|
||||
}
|
||||
}
|
||||
|
||||
// responseFor constructs a response dns.Message that is appropriate for query.
|
||||
// Along with the dns.Message, it returns the ClientID extracted from the query
|
||||
// and its decoded data payload. If the returned dns.Message is nil, it means
|
||||
// that there should be no response to this query. If the returned dns.Message
|
||||
// has an Rcode() of dns.RcodeNoError, the message is a candidate for for
|
||||
// carrying downstream data in a TXT record.
|
||||
func responseFor(query *dns.Message, domain dns.Name) (*dns.Message, turbotunnel.ClientID, []byte) {
|
||||
var clientID turbotunnel.ClientID
|
||||
|
||||
@@ -344,30 +479,21 @@ func responseFor(query *dns.Message, domain dns.Name) (*dns.Message, turbotunnel
|
||||
return resp, clientID, payload[len(clientID):]
|
||||
}
|
||||
|
||||
// record represents a response set up with metadata appropriate for a response
|
||||
// to a previously received query. recvLoop sends instances of this type to
|
||||
// sendLoop via a channel. sendLoop may optionally fill in the response's Answer
|
||||
// section before sending it.
|
||||
// record represents a DNS message appropriate for a response to a previously
|
||||
// received query, along with metadata necessary for sending the response.
|
||||
// recvLoop sends instances of record to sendLoop via a channel. sendLoop
|
||||
// receives instances of record and may fill in the message's Answer section
|
||||
// before sending it.
|
||||
type record struct {
|
||||
Resp *dns.Message
|
||||
Addr net.Addr
|
||||
ClientID turbotunnel.ClientID
|
||||
}
|
||||
|
||||
func loop(dnsConn net.PacketConn, domain dns.Name, ttConn *turbotunnel.QueuePacketConn) error {
|
||||
ch := make(chan *record, 100)
|
||||
defer close(ch)
|
||||
|
||||
go func() {
|
||||
err := sendLoop(dnsConn, ttConn, ch)
|
||||
if err != nil {
|
||||
log.Printf("sendLoop: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
return recvLoop(domain, dnsConn, ttConn, ch)
|
||||
}
|
||||
|
||||
// recvLoop repeatedly calls dnsConn.ReadFrom, extracts the packets contained in
|
||||
// the incoming DNS queries, and puts them on ttConn's incoming queue. Whenever
|
||||
// a query calls for a response, constructs a partial response and passes it to
|
||||
// sendLoop over ch.
|
||||
func recvLoop(domain dns.Name, dnsConn net.PacketConn, ttConn *turbotunnel.QueuePacketConn, ch chan<- *record) error {
|
||||
for {
|
||||
var buf [4096]byte
|
||||
@@ -408,6 +534,10 @@ func recvLoop(domain dns.Name, dnsConn net.PacketConn, ttConn *turbotunnel.Queue
|
||||
}
|
||||
}
|
||||
|
||||
// sendLoop repeatedly receives records from ch. Those that represent an error
|
||||
// response, it sends on the network immediately. Those that represent a
|
||||
// response capable of carrying data, it packs full of as many packets as will
|
||||
// fit, then sends it.
|
||||
func sendLoop(dnsConn net.PacketConn, ttConn *turbotunnel.QueuePacketConn, ch <-chan *record) error {
|
||||
var nextRec *record
|
||||
var nextP []byte
|
||||
@@ -450,6 +580,10 @@ func sendLoop(dnsConn net.PacketConn, ttConn *turbotunnel.QueuePacketConn, ch <-
|
||||
}
|
||||
nextP = nil
|
||||
|
||||
// We loop and write as many packets from OutgoingQueue
|
||||
// into the response as will fit. Any packet that would
|
||||
// overflow the capacity of the DNS response, we save in
|
||||
// nextP to be included in a future response.
|
||||
timer := time.NewTimer(maxResponseDelay)
|
||||
loop:
|
||||
for {
|
||||
@@ -503,6 +637,8 @@ func sendLoop(dnsConn net.PacketConn, ttConn *turbotunnel.QueuePacketConn, ch <-
|
||||
buf = buf[:maxUDPPayload]
|
||||
buf[2] |= 0x02 // TC = 1
|
||||
}
|
||||
|
||||
// Now we actually send the message as a UDP packet.
|
||||
_, err = dnsConn.WriteTo(buf, rec.Addr)
|
||||
if err != nil {
|
||||
if err, ok := err.(net.Error); ok && err.Temporary() {
|
||||
@@ -515,85 +651,6 @@ func sendLoop(dnsConn net.PacketConn, ttConn *turbotunnel.QueuePacketConn, ch <-
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateKeypair(privkeyFilename, pubkeyFilename string) (err error) {
|
||||
// Filenames to delete in case of error (avoid leaving partially written
|
||||
// files).
|
||||
var toDelete []string
|
||||
defer func() {
|
||||
for _, filename := range toDelete {
|
||||
fmt.Fprintf(os.Stderr, "deleting partially written file %s\n", filename)
|
||||
if closeErr := os.Remove(filename); closeErr != nil {
|
||||
fmt.Fprintf(os.Stderr, "cannot remove %s: %v\n", filename, closeErr)
|
||||
if err == nil {
|
||||
err = closeErr
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
privkey, pubkey, err := noise.GenerateKeypair()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if privkeyFilename != "" {
|
||||
// Save the privkey to a file.
|
||||
f, err := os.Create(privkeyFilename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
toDelete = append(toDelete, privkeyFilename)
|
||||
err = noise.WriteKey(f, privkey)
|
||||
if err2 := f.Close(); err == nil {
|
||||
err = err2
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if pubkeyFilename != "" {
|
||||
// Save the pubkey to a file.
|
||||
f, err := os.Create(pubkeyFilename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
toDelete = append(toDelete, pubkeyFilename)
|
||||
err = noise.WriteKey(f, pubkey)
|
||||
if err2 := f.Close(); err == nil {
|
||||
err = err2
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// All good, allow the written files to remain.
|
||||
toDelete = nil
|
||||
|
||||
if privkeyFilename != "" {
|
||||
fmt.Printf("privkey written to %s\n", privkeyFilename)
|
||||
} else {
|
||||
fmt.Printf("privkey %x\n", privkey)
|
||||
}
|
||||
if pubkeyFilename != "" {
|
||||
fmt.Printf("pubkey written to %s\n", pubkeyFilename)
|
||||
} else {
|
||||
fmt.Printf("pubkey %x\n", pubkey)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readKeyFromFile(filename string) ([]byte, error) {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
return noise.ReadKey(f)
|
||||
}
|
||||
|
||||
func run(privkey, pubkey []byte, domain dns.Name, upstream net.Addr, dnsConn net.PacketConn) error {
|
||||
defer dnsConn.Close()
|
||||
|
||||
@@ -613,7 +670,17 @@ func run(privkey, pubkey []byte, domain dns.Name, upstream net.Addr, dnsConn net
|
||||
|
||||
log.Printf("pubkey %x", pubkey)
|
||||
|
||||
return loop(dnsConn, domain, ttConn)
|
||||
ch := make(chan *record, 100)
|
||||
defer close(ch)
|
||||
|
||||
go func() {
|
||||
err := sendLoop(dnsConn, ttConn, ch)
|
||||
if err != nil {
|
||||
log.Printf("sendLoop: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
return recvLoop(domain, dnsConn, ttConn, ch)
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
+52
-26
@@ -1,3 +1,8 @@
|
||||
// Package noise provides a net.Conn-like interface for a
|
||||
// Noise_NK_25519_ChaChaPoly_BLAKE2s. It encodes Noise messages onto a reliable
|
||||
// stream using 16-bit length prefixes.
|
||||
//
|
||||
// https://noiseprotocol.org/noise.html
|
||||
package noise
|
||||
|
||||
import (
|
||||
@@ -17,6 +22,13 @@ import (
|
||||
// The length of public and private keys as returned by GenerateKeypair.
|
||||
const KeyLen = 32
|
||||
|
||||
// cipherSuite represents 25519_ChaChaPoly_BLAKE2s.
|
||||
var cipherSuite = noise.NewCipherSuite(noise.DH25519, noise.CipherChaChaPoly, noise.HashBLAKE2s)
|
||||
|
||||
// readMessage reads a length-prefixed message from r. It returns a nil error
|
||||
// only when a complete message 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 message.
|
||||
func readMessage(r io.Reader) ([]byte, error) {
|
||||
var length uint16
|
||||
err := binary.Read(r, binary.BigEndian, &length)
|
||||
@@ -33,6 +45,8 @@ func readMessage(r io.Reader) ([]byte, error) {
|
||||
return msg, err
|
||||
}
|
||||
|
||||
// writeMessage writes msg as a length-prefixed message to w. It panics if the
|
||||
// length of msg cannot be represented in 16 bits.
|
||||
func writeMessage(w io.Writer, msg []byte) error {
|
||||
length := uint16(len(msg))
|
||||
if int(length) != len(msg) {
|
||||
@@ -46,20 +60,24 @@ func writeMessage(w io.Writer, msg []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
type ReadWriter struct {
|
||||
rw io.ReadWriteCloser
|
||||
// socket is the internal type that represents a Noise-wrapped
|
||||
// io.ReadWriteCloser.
|
||||
type socket struct {
|
||||
recvPipe *io.PipeReader
|
||||
sendCipher *noise.CipherState
|
||||
io.ReadWriteCloser
|
||||
}
|
||||
|
||||
func newReadWriter(rw io.ReadWriteCloser, recvCipher, sendCipher *noise.CipherState) *ReadWriter {
|
||||
func newSocket(rwc io.ReadWriteCloser, recvCipher, sendCipher *noise.CipherState) *socket {
|
||||
pr, pw := io.Pipe()
|
||||
// This loop calls readMessage, decrypts the messages, and feeds them
|
||||
// into recvPipe where they will be returned from Read.
|
||||
go func() (err error) {
|
||||
defer func() {
|
||||
pw.CloseWithError(err)
|
||||
}()
|
||||
for {
|
||||
msg, err := readMessage(rw)
|
||||
msg, err := readMessage(rwc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -73,25 +91,27 @@ func newReadWriter(rw io.ReadWriteCloser, recvCipher, sendCipher *noise.CipherSt
|
||||
}
|
||||
}
|
||||
}()
|
||||
return &ReadWriter{
|
||||
rw: rw,
|
||||
sendCipher: sendCipher,
|
||||
recvPipe: pr,
|
||||
return &socket{
|
||||
sendCipher: sendCipher,
|
||||
recvPipe: pr,
|
||||
ReadWriteCloser: rwc,
|
||||
}
|
||||
}
|
||||
|
||||
func (rw *ReadWriter) Read(p []byte) (int, error) {
|
||||
return rw.recvPipe.Read(p)
|
||||
// Read reads decrypted data from the wrapped io.Reader.
|
||||
func (s *socket) Read(p []byte) (int, error) {
|
||||
return s.recvPipe.Read(p)
|
||||
}
|
||||
|
||||
func (rw *ReadWriter) Write(p []byte) (int, error) {
|
||||
// Write writes encrypted data from the wrapped io.Writer.
|
||||
func (s *socket) Write(p []byte) (int, error) {
|
||||
total := 0
|
||||
for len(p) > 0 {
|
||||
n := len(p)
|
||||
if n > 4096 {
|
||||
n = 4096
|
||||
}
|
||||
err := writeMessage(rw.rw, rw.sendCipher.Encrypt(nil, nil, p[:n]))
|
||||
err := writeMessage(s.ReadWriteCloser, s.sendCipher.Encrypt(nil, nil, p[:n]))
|
||||
if err != nil {
|
||||
return total, err
|
||||
}
|
||||
@@ -101,12 +121,8 @@ func (rw *ReadWriter) Write(p []byte) (int, error) {
|
||||
return total, nil
|
||||
}
|
||||
|
||||
func (rw *ReadWriter) Close() error {
|
||||
return rw.rw.Close()
|
||||
}
|
||||
|
||||
var cipherSuite = noise.NewCipherSuite(noise.DH25519, noise.CipherChaChaPoly, noise.HashBLAKE2s)
|
||||
|
||||
// newConfig instantiates configuration settings that are common to clients and
|
||||
// servers.
|
||||
func newConfig(initiator bool) noise.Config {
|
||||
return noise.Config{
|
||||
CipherSuite: cipherSuite,
|
||||
@@ -116,7 +132,10 @@ func newConfig(initiator bool) noise.Config {
|
||||
}
|
||||
}
|
||||
|
||||
func NewClient(rw io.ReadWriteCloser, serverPubkey []byte) (*ReadWriter, error) {
|
||||
// NewClient wraps an io.ReadWriteCloser in a Noise protocol as a client, and
|
||||
// returns after completing the handshake. It returns a non-nil error if there
|
||||
// is an error during the handshake.
|
||||
func NewClient(rwc io.ReadWriteCloser, serverPubkey []byte) (io.ReadWriteCloser, error) {
|
||||
config := newConfig(true)
|
||||
config.PeerStatic = serverPubkey
|
||||
handshakeState, err := noise.NewHandshakeState(config)
|
||||
@@ -129,13 +148,13 @@ func NewClient(rw io.ReadWriteCloser, serverPubkey []byte) (*ReadWriter, error)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = writeMessage(rw, msg)
|
||||
err = writeMessage(rwc, msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// <- e, es
|
||||
msg, err = readMessage(rw)
|
||||
msg, err = readMessage(rwc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -147,10 +166,13 @@ func NewClient(rw io.ReadWriteCloser, serverPubkey []byte) (*ReadWriter, error)
|
||||
return nil, errors.New("unexpected server payload")
|
||||
}
|
||||
|
||||
return newReadWriter(rw, recvCipher, sendCipher), nil
|
||||
return newSocket(rwc, recvCipher, sendCipher), nil
|
||||
}
|
||||
|
||||
func NewServer(rw io.ReadWriteCloser, serverPrivkey, serverPubkey []byte) (*ReadWriter, error) {
|
||||
// NewClient wraps an io.ReadWriteCloser in a Noise protocol as a server, and
|
||||
// returns after completing the handshake. It returns a non-nil error if there
|
||||
// is an error during the handshake.
|
||||
func NewServer(rwc io.ReadWriteCloser, serverPrivkey, serverPubkey []byte) (io.ReadWriteCloser, error) {
|
||||
config := newConfig(false)
|
||||
config.StaticKeypair = noise.DHKey{Private: serverPrivkey, Public: serverPubkey}
|
||||
handshakeState, err := noise.NewHandshakeState(config)
|
||||
@@ -159,7 +181,7 @@ func NewServer(rw io.ReadWriteCloser, serverPrivkey, serverPubkey []byte) (*Read
|
||||
}
|
||||
|
||||
// -> e, es
|
||||
msg, err := readMessage(rw)
|
||||
msg, err := readMessage(rwc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -176,14 +198,17 @@ func NewServer(rw io.ReadWriteCloser, serverPrivkey, serverPubkey []byte) (*Read
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = writeMessage(rw, msg)
|
||||
err = writeMessage(rwc, msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newReadWriter(rw, recvCipher, sendCipher), nil
|
||||
return newSocket(rwc, recvCipher, sendCipher), nil
|
||||
}
|
||||
|
||||
// GenerateKeypair generates a private key and the corresponding public key.
|
||||
//
|
||||
// https://noiseprotocol.org/noise.html#dh-functions
|
||||
func GenerateKeypair() (privkey, pubkey []byte, err error) {
|
||||
pair, err := noise.DH25519.GenerateKeypair(rand.Reader)
|
||||
if err != nil {
|
||||
@@ -200,6 +225,7 @@ func GenerateKeypair() (privkey, pubkey []byte, err error) {
|
||||
return pair.Private, pair.Public, nil
|
||||
}
|
||||
|
||||
// PubkeyFromPrivkey returns the public key that corresponds to privkey.
|
||||
func PubkeyFromPrivkey(privkey []byte) []byte {
|
||||
pair, err := noise.DH25519.GenerateKeypair(bytes.NewReader(privkey))
|
||||
if err != nil {
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
// Package turbotunnel is facilities for embedding packet-based reliability
|
||||
// protocols inside other protocols.
|
||||
//
|
||||
// https://github.com/net4people/bbs/issues/9
|
||||
package turbotunnel
|
||||
|
||||
import "errors"
|
||||
|
||||
Reference in New Issue
Block a user