From c78841fc7399d133c33f125ea68c788db14020ff Mon Sep 17 00:00:00 2001 From: Amin Saedi Date: Mon, 23 Mar 2026 15:11:24 -0400 Subject: [PATCH] Auto-escalate max-qname-len when MTU is too small for the domain When the default max-qname-len (101) produces an MTU below the minimum threshold of 50 bytes, the client now automatically tries larger values (150, 200, 253) until a viable MTU is found. This avoids a confusing fatal error for users with longer tunnel domains (e.g. t.example.co.uk) who may not know about the -max-qname-len flag. Also improves the error message when auto-escalation fails, suggesting the user try -max-qname-len 253 or a shorter domain. Co-Authored-By: Claude Opus 4.6 (1M context) --- client/client.go | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/client/client.go b/client/client.go index c9955fa..e79c286 100644 --- a/client/client.go +++ b/client/client.go @@ -172,6 +172,27 @@ func (ts *TunnelServer) effectiveMaxQnameLen() int { return 101 } +// autoEscalateMaxQnameLen attempts to find a working max QNAME length when +// the current setting produces an MTU that is too small. It tries progressively +// larger values up to the RFC 1035 maximum of 253 bytes. If a working value is +// found, it updates MaxQnameLen and returns the new MTU. Otherwise it returns 0. +func (t *Tunnel) autoEscalateMaxQnameLen() int { + original := t.TunnelServer.effectiveMaxQnameLen() + for _, candidate := range []int{150, 200, 253} { + if candidate <= original { + continue + } + mtu := DNSNameCapacity(t.TunnelServer.Addr, candidate, t.TunnelServer.MaxNumLabels) - t.wireConfig.DataOverhead() + if mtu >= 50 { + log.Warnf("max-qname-len %d is too small for domain %s (MTU < 50); auto-raised to %d (MTU %d)", + original, t.TunnelServer.Addr, candidate, mtu) + t.TunnelServer.MaxQnameLen = candidate + return mtu + } + } + return 0 +} + // Tunnel represents a DNS tunnel connection. Create with NewTunnel, then // either call the step-by-step Initiate* methods (for embedding in frameworks // like xray-core) or call ListenAndServe for a fully managed session. @@ -337,9 +358,13 @@ func (t *Tunnel) InitiateKCPConn(mtu int) error { if mtu <= 0 { maxQnameLen := t.TunnelServer.effectiveMaxQnameLen() mtu = DNSNameCapacity(t.TunnelServer.Addr, maxQnameLen, t.TunnelServer.MaxNumLabels) - t.wireConfig.DataOverhead() + if mtu < 50 { + mtu = t.autoEscalateMaxQnameLen() + } } if mtu < 50 { - return fmt.Errorf("MTU %d is too small (minimum 50)", mtu) + return fmt.Errorf("MTU %d is too small (minimum 50); try increasing -max-qname-len (current: %d, max: 253) or use a shorter -domain", + mtu, t.TunnelServer.effectiveMaxQnameLen()) } t.TunnelServer.MTU = mtu log.Infof("effective MTU %d", mtu) @@ -496,7 +521,11 @@ func (t *Tunnel) ListenAndServe(listenAddr string) error { maxQnameLen := t.TunnelServer.effectiveMaxQnameLen() mtu := DNSNameCapacity(t.TunnelServer.Addr, maxQnameLen, t.TunnelServer.MaxNumLabels) - t.wireConfig.DataOverhead() if mtu < 50 { - return fmt.Errorf("MTU %d is too small (minimum 50)", mtu) + mtu = t.autoEscalateMaxQnameLen() + } + if mtu < 50 { + return fmt.Errorf("MTU %d is too small (minimum 50); try increasing -max-qname-len (current: %d, max: 253) or use a shorter -domain", + mtu, t.TunnelServer.effectiveMaxQnameLen()) } log.Infof("effective MTU %d", mtu)