How MasterDnsVPN Handles Packet Loss in Its Custom DNS Tunnel Protocol
MasterDnsVPN implements a full-featured ARQ (Automatic Repeat reQuest) layer that combines per-packet state tracking, adaptive RTO timers, periodic retransmit checks, and explicit NACK handling to guarantee reliable delivery over unreliable DNS/UDP transports.
MasterDnsVPN is an open-source VPN solution that tunnels traffic through DNS queries to bypass network restrictions and censorship. Because DNS uses UDP—a connectionless, best-effort transport—the project must handle packet loss internally. The solution is a sophisticated reliability layer implemented in Go that mimics TCP-like guarantees while operating within DNS payload constraints.
Per-Packet State Tracking in the Send Buffer
The ARQ engine maintains granular state for every transmitted packet until acknowledgment. These records live in the sndBuf map for data packets and controlSndBuf for control packets, both defined in internal/arq/arq.go.
The sndBuf and controlSndBuf Maps
According to the source code at lines 1340-1380, each entry stores:
- The packet sequence number
- CreatedAt: timestamp of first transmission
- LastSentAt: timestamp of most recent transmission
- CurrentRTO: the retransmission timeout value currently applied
- Retries: counter for retransmission attempts
- SampleEligible: boolean flag indicating whether this transmission may be used for RTT sampling
This state persistence allows the system to detect missing acknowledgments and determine precisely when a retransmission is required.
Retry Counters and RTO Tracking
Every packet carries a Retries counter that increments with each retransmission attempt. When this counter exceeds maxDataRetries (default approximately 60), the ARQ layer terminates the stream with an RST packet. This prevents infinite retransmission loops on permanently failed connections.
The Retransmit Loop and Timeout Detection
A dedicated background goroutine runs retransmitLoop, which periodically invokes checkRetransmits() to scan for overdue packets.
Periodic Checks with checkRetransmits()
As implemented in internal/arq/arq.go at lines 1464-1490, this function executes approximately every RTO/3 seconds. It compares the current time against each packet's LastSentAt timestamp. When now - LastSentAt >= CurrentRTO, the packet is immediately re-queued for transmission with an updated timeout.
Exponential Backoff with Growth Factors
The ARQ layer applies adaptive backoff to prevent network congestion. Data packets use a growth factor of 1.35 (dataRetransmitRTOGrowthFactor), while control packets use 1.25 (controlRetransmitRTOGrowthFactor). These constants, defined in the same file, multiply the CurrentRTO value with each retransmission attempt, spacing subsequent retries further apart while maintaining responsiveness.
Explicit Loss Recovery via Negative Acknowledgments
Beyond timer-based retransmission, MasterDnsVPN supports proactive loss signaling through HandleDataNack. When a receiver detects a missing sequence number, it transmits a NACK packet. Upon reception, the sender immediately re-queues the specific missing fragment using PACKET_STREAM_RESEND, bypassing the normal timeout wait. This mechanism, found at lines 1660-1670 of internal/arq/arq.go, enables rapid recovery from burst loss scenarios without waiting for the retransmission timer to expire.
Adaptive RTT Estimation for Dynamic Networks
The protocol maintains two independent estimators—dataAdaptiveRTO and controlAdaptiveRTO—that dynamically adjust to network conditions. The updateAdaptiveRTO function processes RTT samples from acknowledged packets (marked with SampleEligible), updating the smoothed RTT (srtt) and variance (rttvar) using standard TCP-style algorithms. This adaptation ensures that retransmission timeouts remain tightly coupled to actual latency, reducing unnecessary retransmits on high-delay links while maintaining responsiveness on fast networks.
When ReceiveAck processes an acknowledgment, it removes the entry from the send buffer, records the RTT sample if eligible, and calls signalWindowNotFull to unblock the flow-control window.
Implementation Example
The following Go code demonstrates configuring the ARQ layer for reliable DNS tunneling:
// Create an ARQ instance (client side)
cfg := arq.Config{
WindowSize: 512,
RTO: 0.2, // 200 ms base RTO
MaxRTO: 5.0, // seconds
MaxDataRetries: 10,
EnableControlReliability: true,
}
a := arq.NewARQ(1, 0, enqueuer, conn, 1400, logger, cfg)
// Start background workers (IO, write, retransmit)
a.Start()
// Send a payload – the ARQ layer stores it in sndBuf
payload := []byte("hello over DNS tunnel")
a.SendData(payload)
// Simulate loss by dropping the first transmission in the network
// (the test harness can configure the UDP socket to drop packets).
// After RTO expires, the retransmitLoop will call checkRetransmits(),
// see that the packet's CurrentRTO has elapsed, and push a resend:
// enqueuer.PushTXPacket(... PACKET_STREAM_RESEND ...)
// The receiver will ACK the retransmitted copy, causing the entry to be removed.
Summary
- MasterDnsVPN uses a complete ARQ implementation in
internal/arq/arq.goto provide TCP-like reliability over DNS/UDP - Per-packet state in
sndBufandcontrolSndBufenables precise timeout detection and retransmission tracking - The
retransmitLoopandcheckRetransmits()functions provide periodic scans every RTO/3 seconds - Adaptive RTO calculation with growth factors of 1.35 (data) and 1.25 (control) prevents congestion collapse while ensuring timely recovery
- Explicit NACK handling via
HandleDataNackallows immediate recovery without waiting for timeouts - Retry limits (default ~60) with RST fallback prevent resource exhaustion on unrecoverable connections
Frequently Asked Questions
What is the default retransmission timeout in MasterDnsVPN?
The base RTO is configurable but defaults to 200 milliseconds in the standard configuration. The actual timeout adapts dynamically based on network latency measurements, clamped to a maximum of 5 seconds (MaxRTO) to prevent excessive delays.
How does MasterDnsVPN distinguish between original transmissions and retransmissions?
The protocol uses distinct packet type identifiers defined in internal/enums/packet_identity.go. Retransmitted packets are marked as PACKET_STREAM_RESEND rather than PACKET_STREAM_DATA, allowing the receiver to handle them appropriately for RTT sampling and duplicate detection.
What happens when the retry limit is reached?
When a packet's retry counter exceeds maxDataRetries (approximately 60 by default), the ARQ layer terminates the connection by sending an RST packet and closing the stream. This prevents infinite retransmission loops when the network path is completely broken.
Why doesn't MasterDnsVPN use TCP instead of implementing custom ARQ?
MasterDnsVPN tunnels through DNS queries, which typically use UDP transport. Since many networks block or restrict non-DNS traffic, the application must encapsulate VPN data within standard DNS packets. TCP cannot be used for this encapsulation because DNS responses must fit within UDP datagram constraints, requiring a custom reliability layer optimized for DNS tunneling.
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 →