# MasterDnsVPN Performance Tuning: Optimizing ARQ Window Size and RTO Configuration

> Optimize MasterDnsVPN performance by fine-tuning ARQ Window Size and RTO configuration. Achieve higher throughput and better latency management for your VPN.

- Repository: [Amin Mahmoudi/MasterDnsVPN](https://github.com/masterking32/MasterDnsVPN)
- Tags: performance
- Published: 2026-05-10

---

**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`](https://github.com/masterking32/MasterDnsVPN/blob/main/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`](https://github.com/masterking32/MasterDnsVPN/blob/main/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`](https://github.com/masterking32/MasterDnsVPN/blob/main/internal/arq/arq.go), the `NewARQ` function enforces a hard minimum of **300** packets during initialization (lines 337–340):

```go
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):

```go
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 `WindowSize` between 1,000–2,000 (the default 300 often suffices). Configure `RTO` to 0.03s (30 ms) and `MaxRTO` to 0.2s (200 ms).

- **High-latency WAN (RTT ≈ 100 ms):** Increase `WindowSize` to 2,000–4,000 to maintain pipe fullness. Set `RTO` to 0.15s (150 ms) and `MaxRTO` to 0.5s (500 ms).

- **Lossy mobile links (>10% packet loss):** Constrain `WindowSize` to approximately 800 to prevent buffer bloat. Raise `MaxRTO` to 1.0s to avoid premature retransmission; consider setting `RTO` to 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`](https://github.com/masterking32/MasterDnsVPN/blob/main/internal/arq/arq.go):

```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:

```go
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:

```go
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 `WindowSize` parameter in [`internal/arq/arq.go`](https://github.com/masterking32/MasterDnsVPN/blob/main/internal/arq/arq.go) controls in-flight packet capacity, with a hard minimum of **300** enforced at initialization.
- `RTO` and `MaxRTO` set the adaptive timeout bounds, guaranteeing a minimum of **50 ms** and capping the retransmission delay.
- The `limit` field (80% of window size) triggers back-pressure via `waitWindowNotFull` when 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`](https://github.com/masterking32/MasterDnsVPN/blob/main/internal/config/client.go), flows through the `arq.Config` struct, and is utilized by [`internal/udpserver/server.go`](https://github.com/masterking32/MasterDnsVPN/blob/main/internal/udpserver/server.go) which implements the `PacketEnqueuer` interface.

## 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`](https://github.com/masterking32/MasterDnsVPN/blob/main/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`](https://github.com/masterking32/MasterDnsVPN/blob/main/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`](https://github.com/masterking32/MasterDnsVPN/blob/main/internal/arq/arq.go). The resulting ARQ instance is driven by [`internal/udpserver/server.go`](https://github.com/masterking32/MasterDnsVPN/blob/main/internal/udpserver/server.go), which implements the `PacketEnqueuer` interface responsible for UDP packet transmission.