MasterDnsVPN Performance Tuning: Optimizing ARQ Window Size and RTO Configuration
MasterDnsVPN performance tuning centers on adjusting the ARQ WindowSize to manage in-flight packets and configuring RTO/MaxRTO to control retransmission timeouts, enabling optimal throughput across varying network latencies.
The MasterDnsVPN project implements a QUIC-style sliding-window protocol over UDP for DNS tunneling, encapsulated in the ARQ (Automatic Repeat-reQuest) layer. By modifying the WindowSize and retransmission timeout (RTO) parameters defined in internal/arq/arq.go, you can fine-tune the balance between network throughput and recovery latency. This guide examines the specific configuration fields, their minimum bounds, and practical tuning strategies based on the actual source code implementation.
Core ARQ Parameters in MasterDnsVPN
The ARQ configuration is defined in the Config struct within internal/arq/arq.go (lines 279–301). Two fields dominate performance characteristics: WindowSize, which governs send-side capacity, and the RTO/MaxRTO pair, which bounds retransmission timing.
WindowSize Configuration
The WindowSize parameter specifies the maximum number of unacknowledged data packets the sender can have in flight. According to the source code in internal/arq/arq.go, the NewARQ function enforces a hard minimum of 300 packets during initialization (lines 337–340):
a := &ARQ{
windowSize: max(cfg.WindowSize, 300),
receiveWindowSize: max(windowSize, windowSize*2),
limit: max(int(float64(windowSize)*0.8), 50),
// ...
}
The limit field represents the effective back-pressure threshold, set to 80% of the window size (minimum 50). When len(a.sndBuf) >= a.limit, the waitWindowNotFull mechanism blocks further transmission until acknowledgments arrive.
RTO and MaxRTO Bounds
The RTO (Retransmission TimeOut) and MaxRTO fields define the base timeout and ceiling for packet retransmission. These values are processed during ARQ construction (lines 96–100) with a minimum bound of 50 milliseconds (0.05 seconds):
userMaxRto := maxF(0.05, cfg.MaxRTO)
a.maxRTO = time.Duration(userMaxRto * float64(time.Second))
a.rto = time.Duration(minF(maxF(0.05, cfg.RTO), userMaxRto) * float64(time.Second))
The adaptive algorithm refines the actual timeout based on RTT samples via noteSuccessfulDataSample, but the operational value never falls below a.rto or exceeds a.maxRTO.
The Interaction Between Window Size and Retransmission Timing
These parameters operate interdependently. The window size determines pipe capacity—how many packets can saturate the network before back-pressure engages. The RTO drives the retransmitLoop, which uses the greater of a.rto and a.controlRto as the base interval (rtoFactor).
If you configure a large window with an aggressive (low) RTO, the sender may inject retransmits before the receiver can acknowledge original transmissions, causing unnecessary congestion. Conversely, a small window paired with a conservative (high) RTO leaves bandwidth underutilized, as the sender stalls waiting for timeouts despite available capacity.
Network-Specific Tuning Guidelines
Adjust these parameters based on measured network path characteristics:
-
Low-latency LAN (RTT ≈ 1 ms): Set
WindowSizebetween 1,000–2,000 (the default 300 often suffices). ConfigureRTOto 0.03s (30 ms) andMaxRTOto 0.2s (200 ms). -
High-latency WAN (RTT ≈ 100 ms): Increase
WindowSizeto 2,000–4,000 to maintain pipe fullness. SetRTOto 0.15s (150 ms) andMaxRTOto 0.5s (500 ms). -
Lossy mobile links (>10% packet loss): Constrain
WindowSizeto approximately 800 to prevent buffer bloat. RaiseMaxRTOto 1.0s to avoid premature retransmission; consider settingRTOto 0.2s (200 ms).
Implementation Examples
Initializing ARQ with Custom Configuration
Create an ARQ instance with tuned parameters by populating the Config struct before passing it to NewARQ in internal/arq/arq.go:
import (
"github.com/masterking32/MasterDnsVPN/internal/arq"
"time"
)
cfg := arq.Config{
WindowSize: 4000, // Allow 4k in-flight packets
RTO: 0.12, // 120 ms base timeout
MaxRTO: 0.6, // 600 ms ceiling
EnableControlReliability: true,
ControlRTO: 0.08,
ControlMaxRTO: 0.4,
}
// Obtained from your UDP server implementation
var enq packetEnqueuer
localConn, _ := net.Dial("tcp", "127.0.0.1:1080")
a := arq.NewARQ(
streamID, // uint16
sessionID, // uint8
enq,
localConn,
1472, // MTU
nil, // Logger
cfg,
)
a.Start()
Runtime Window Adjustment
While the ARQ layer does not expose a dedicated resize API, you can dynamically adjust the window by modifying the protected fields and signaling the condition variable. This example calculates a new window based on measured bandwidth:
func adaptWindow(arq *arq.ARQ, measuredMbps float64) {
// Heuristic: 1 Mbps ≈ 55 packets/sec (assuming ~1500 byte packets)
// Maintain 10 seconds of burst capacity
newWin := int(measuredMbps * 55 * 10)
if newWin < 300 {
newWin = 300 // Enforce ARQ minimum
}
arq.mu.Lock()
arq.windowSize = newWin
arq.receiveWindowSize = max(newWin, newWin*2)
arq.limit = max(int(float64(newWin)*0.8), 50)
arq.mu.Unlock()
arq.signalWindowNotFull() // Unblock waiting writers
}
Configuring RTO Bounds at Startup
For unreliable networks, set conservative timeout bounds during construction:
cfg := arq.Config{
RTO: 0.2, // 200 ms initial
MaxRTO: 1.5, // 1.5 s maximum
}
a := arq.NewARQ(streamID, sessionID, enq, localConn, mtu, nil, cfg)
Summary
- The
WindowSizeparameter ininternal/arq/arq.gocontrols in-flight packet capacity, with a hard minimum of 300 enforced at initialization. RTOandMaxRTOset the adaptive timeout bounds, guaranteeing a minimum of 50 ms and capping the retransmission delay.- The
limitfield (80% of window size) triggers back-pressure viawaitWindowNotFullwhen the send buffer fills. - Tuning requires balancing pipe capacity (window) against recovery latency (RTO); high-RTT paths need larger windows, while lossy paths need higher RTO ceilings.
- Configuration originates in
internal/config/client.go, flows through thearq.Configstruct, and is utilized byinternal/udpserver/server.gowhich implements thePacketEnqueuerinterface.
Frequently Asked Questions
What is the minimum WindowSize allowed in MasterDnsVPN?
The ARQ implementation enforces a minimum WindowSize of 300 packets. In internal/arq/arq.go (lines 337–340), the NewARQ function applies max(cfg.WindowSize, 300) to ensure sufficient buffering for the sliding-window protocol. Values below 300 are silently clamped to this threshold.
How does the ARQ layer calculate the actual retransmission timeout?
The actual RTO is computed adaptively using RTT samples collected via noteSuccessfulDataSample, but it is strictly bounded by the configured RTO (minimum) and MaxRTO (maximum) values. The retransmitLoop uses the greater of the base a.rto or a.controlRto as the rtoFactor for scheduling retransmissions.
Can I modify ARQ settings dynamically while the tunnel is active?
While WindowSize, RTO, and MaxRTO are typically set at construction time via arq.Config, you can modify the window size at runtime by acquiring the ARQ mutex, updating windowSize, receiveWindowSize, and limit, then calling signalWindowNotFull(). However, RTO bounds can only be changed during initial NewARQ invocation.
Where does the ARQ configuration originate in the client architecture?
The configuration flows from internal/config/client.go, where user settings are parsed and populated into an arq.Config struct. This struct is passed to NewARQ in internal/arq/arq.go. The resulting ARQ instance is driven by internal/udpserver/server.go, which implements the PacketEnqueuer interface responsible for UDP packet transmission.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →