Documentation and light refactoring.

This commit is contained in:
David Fifield
2020-04-19 17:16:27 -06:00
parent 83a67e3874
commit d14deab12b
11 changed files with 606 additions and 262 deletions
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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()
+27
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 {
+4
View File
@@ -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"