From f2abcbf4de5bd93cdc01e87ac2943ed06c439cf2 Mon Sep 17 00:00:00 2001 From: David Fifield Date: Sat, 28 Mar 2020 20:21:11 -0600 Subject: [PATCH] Larger downstream capacity. --- dnstt-server/main.go | 71 ++++++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 33 deletions(-) diff --git a/dnstt-server/main.go b/dnstt-server/main.go index e4e18be..3c45622 100644 --- a/dnstt-server/main.go +++ b/dnstt-server/main.go @@ -23,6 +23,28 @@ import ( const ( idleTimeout = 10 * time.Minute responseTTL = 60 + + // We don't send UDP payloads larger than this, in an attempt to avoid + // network-layer fragmentation. 40 bytes is the size of an IPv6 header + // (though without any extension headers). 8 bytes is the size of a UDP + // header. + maxUDPPayload = 1500 - 40 - 8 + + // We may have a variable amount of room in which to encode downstream + // packets in each response, because we must echo the query's Question + // section, which is of variable length. But we cannot give dynamic + // packet size limits to KCP; the best we can do is set a global maximum + // which no packet will exceed. We choose that maximum to keep the UDP + // payload size under maxUDPPayload, even in the worst case of a + // maximum-length name in the Question section. The precise limit is + // 1153 = (maxUDPPayload - 294) * 255/256, where 294 is the size of a + // DNS message containing a Question section with a name that is 255 + // bytes long, an Answer section with a single TXT RR whose name is a + // compressed pointer to the name in the Question section and no data, + // and an Additional section with an OPT RR for EDNS(0); and 255/256 + // reflects the overhead of encoding data into a TXT RR. We leave some + // slack in case of IPv6 extension headers or non-Ethernet links. + maxEncodedPayload = 1100 ) // A base32 encoding without padding. @@ -110,23 +132,11 @@ func acceptSessions(ln *kcp.Listener, upstream *net.TCPAddr) error { 0, // default resend 1, // nc=1 => congestion window off ) - // Set the maximum transmission unit. - longName, err := dns.NewName([][]byte{ - bytes.Repeat([]byte{'a'}, 63), - bytes.Repeat([]byte{'b'}, 63), - bytes.Repeat([]byte{'c'}, 63), - bytes.Repeat([]byte{'d'}, 61), - }) - if err != nil { - panic(err) + // Set the maximum transmission unit. 2 bytes accounts for a + // packet length prefix. + if rc := conn.SetMtu(maxEncodedPayload - 2); !rc { + panic(rc) } - mtu := dnsMessageCapacity(longName) - if mtu < 80 { - // This value doesn't depend on any configuration values, so it - // should never be too small. - panic("too little space for downstream payload") - } - conn.SetMtu(mtu) go func() { defer conn.Close() err := acceptStreams(conn, upstream) @@ -137,22 +147,6 @@ func acceptSessions(ln *kcp.Listener, upstream *net.TCPAddr) error { } } -func dnsMessageCapacity(name dns.Name) int { - message := dns.Message{ - Question: []dns.Question{ - dns.Question{Name: name}, - }, - Answer: []dns.RR{ - dns.RR{Name: name}, - }, - } - builder, err := message.WireFormat() - if err != nil { - panic(err) - } - return (512 - len(builder)) * 255 / 256 -} - func nextPacket(r *bytes.Reader) ([]byte, error) { eof := func(err error) error { if err == io.EOF { @@ -240,6 +234,17 @@ func responseFor(query *dns.Message, domain dns.Name) (*dns.Message, turbotunnel payloadSize = 512 } } + if payloadSize < maxUDPPayload { + // We require clients to support EDNS(0) with a minimum payload + // size; otherwise we would have to set a small KCP MTU (only + // around 200 bytes). + // https://tools.ietf.org/html/rfc6891#section-7 + // "If there is a problem with processing the OPT record itself, + // such as an option value that is badly formatted or that + // includes out-of-range values, a FORMERR MUST be returned." + resp.Flags |= dns.RcodeFormatError + return resp, clientID, nil + } if query.Flags&0x7800 != 0 { // We don't support OPCODE != QUERY. @@ -381,7 +386,7 @@ func sendLoop(dnsConn net.PacketConn, ttConn *turbotunnel.QueuePacketConn, ch <- var payload bytes.Buffer - limit := dnsMessageCapacity(rec.Resp.Question[0].Name) + limit := maxEncodedPayload if len(nextP) > 0 { // No length check on any packet left over from // the previous bundle -- if it's too large, we