GitHub Repository: https://github.com/hejhdiss/lkm-ndm-tcp
The Pure Delay Problem: v1's Only Weakness (Based on Testing Until Now)
NDM-TCP v1 failed catastrophically in one specific test scenario: pure delay-only with extreme variation (100ms ±200ms). It achieved only 6.82 Mbps with 50% zero-throughput intervals.
This is the only weakness found based on all localhost tests conducted until now.
Extreme Delay Test Results (100ms ±200ms)
| Algorithm | Throughput | Retransmissions |
|---|---|---|
| NDM-TCP v1 | 6.82 Mbps | 34 ✅ (lowest) |
| Cubic | 6.87 Mbps | 54 |
| Reno | 7.94 Mbps | 36 |
| BBR | 48.1 Mbps | 427 ❌ |
Key Observation: Even in catastrophic failure, v1 had the lowest retransmissions (34) - better than Cubic (54), Reno (36), and far better than BBR (427). The problem was very low throughput and stalls, not retransmissions.
Moderate Delay Test Results (50ms ±100ms)
| Algorithm | Throughput | Retransmissions |
|---|---|---|
| Cubic | 23.5 Mbps | 74 |
| Reno | 27.3 Mbps | 76 |
| v1 | 21.8 Mbps | 84 |
| BBR | 57.6 Mbps | 1,227 |
Key Observation: In moderate delay, v1 performs reasonably (21.8 Mbps with 84 retrans), just not as high throughput as Reno/Cubic. v1 doesn't shine or excel in pure delay cases like 50ms ±100ms or similar, but it's not catastrophic either.
Summary of v1's Pure Delay Performance:
Extreme delay (100ms ±200ms): Catastrophic throughput, but lowest retransmissions
Moderate delay (50ms ±100ms): Reasonable performance, just not excellent- Pure delay is where v1 can't shine or excel
However, this failure only occurs in very specific edge cases that have zero chance in normal network conditions:
High-Frequency Trading (HFT) networks - ultra-low latency links with minimal buffering, delay variation from route changes
Dedicated fiber connections - point-to-point links with minimal packet loss but variable delay from temperature/physical changes
Satellite communication during clear weather - atmospheric delay variation without signal loss
Quality wireless links - good signal strength but variable delay from interference patterns
Data center cross-connects - well-maintained links where delay varies but packet loss is extremely rare
These scenarios require: only delay variation, NO loss, NO queueing, NO bandwidth constraints, and NO other typical network issues. This combination almost never happens in normal network cases.
Why Not BBR-Inspired? High Retransmissions.
I considered BBR-inspired implementation since BBR excels in pure delay scenarios (81.6 Mbps vs everyone else).
But BBR is highly unstable with massive retransmissions:
- Pure delay test: 885 retransmissions
- Extreme delay test: 1,326 retransmissions
- High throughput but sacrifices stability completely
NDM-TCP's core principle is stability - this conflicts fundamentally with BBR's approach.
v4 Delay Enhancement Attempts
I tried creating v4 with delay awareness to fix the pure delay problem. Here's what I experimented with:
What Changed in v4
Added Two New Inputs (replacing dummy inputs 6 & 7):
Input 6 - Queuing Delay:
/* Calculate absolute difference between current RTT and minimum RTT */
u32 q_delay = (rtt_us > ca->min_rtt_us) ? (rtt_us - ca->min_rtt_us) : 0;
inputs[6] = (s32)min_t(u64, (q_delay * 1000ULL) / max(ca->min_rtt_us, 1U), 1000);
Input 7 - RTT Gradient:
/* Calculate difference between current RTT and previous RTT */
u16 last_rtt_ms = ca->rtt_history[(ca->history_index + ENTROPY_WINDOW_SIZE - 1) % ENTROPY_WINDOW_SIZE];
s32 rtt_diff = (s32)(rtt_us / 1000) - (s32)last_rtt_ms;
inputs[7] = clamp(rtt_diff * 100, -1000, 1000);
Modified Congestion Avoidance Logic
Added three new response modes in ndm_tcp_cong_avoid():
/* 1. DELAY-ONLY DETECTION: High queuing delay but no loss/entropy signals */
if (inputs[6] > 800 && !ca->congestion_detected) {
/* Pure delay scenario: cautious growth */
u32 delta = max(1U, acked * cwnd_delta / 3000);
tcp_cong_avoid_ai(tp, tp->snd_cwnd, delta);
}
/* 2. REAL CONGESTION: Low entropy + High RTT Gradient */
else if (ca->has_data && ca->congestion_detected) {
/* Conservative growth, extra conservative if gradient is high */
u32 divisor = (inputs[7] > 500) ? 4000 : 2000;
u32 delta = max(1U, acked * cwnd_delta / divisor);
tcp_cong_avoid_ai(tp, tp->snd_cwnd, delta);
}
/* 3. NOISE/CLEAR PATH: High entropy or low delay */
else if (ca->has_data && !ca->congestion_detected) {
/* High entropy = noise: be aggressive */
u32 delta = max(1U, acked * cwnd_delta / 1000);
tcp_cong_avoid_ai(tp, tp->snd_cwnd, delta);
}
The Problems with v4
I tried multiple variations:
BBR-like delay additions → Increased RTT, high retransmissions
Delay queue + RTT variance → Lower retransmissions but caused other issues
RTT jitter + variance (current v4) → Low retransmissions but significantly slower
v4 Test Results (50ms ±100ms delay, 40 seconds):
- Throughput: 11.6 Mbps
- Retransmissions: 70
Comparison to v1 (same delay conditions, 60 seconds):
- v1: 21.8 Mbps with 84 retransmissions
- v4: 11.6 Mbps with 70 retransmissions (scaled to 60s: ~105 retrans)
The Philosophical Problem:
Even though v4's result is not an utter failure, it creates a fundamental issue with NDM-TCP's design philosophy.
NDM-TCP prioritizes stability over throughput. This is why we can't simply use Cubic or Reno algorithm styles here - they have their own drawbacks in many other scenarios (shown in previous articles' test results where v1 excelled).
The conflict: v4 achieves the lowest retransmissions (70), which aligns with stability goals. But its throughput (11.6 Mbps) falls into a problematic range - it's in the territory of advanced/higher-level optimization cases where expectations are higher.
Since NDM-TCP has stability-focused design and delivered good results in all other tests, I expected it to perform well in this delay case too. But that expectation was faulty.
The delay-based mathematics additions reduce throughput significantly. I can't clearly say if this is acceptable because:
- Stability means balancing retransmissions AND throughput
- v4 has excellent stability (70 retrans) but poor throughput (11.6 Mbps)
- v1 has good stability (84 retrans) with better throughput (21.8 Mbps)
I prefer v1 over v4 because v1 provides better overall balance based on all localhost tests conducted so far.
Unknown: Does v4 cause problems in the other scenarios where v1 excels? We haven't tested this yet.
Why I'm Sticking with v1
We cannot specialize for specific edge cases when it hurts general performance.
Based on all localhost tests conducted so far, NDM-TCP v1 works well in general network scenarios and has better outputs than alternatives:
v1's Performance Across Tests:
- Constrained networks (loss + delay + bandwidth limits): ✅ Excellent
- Loss-heavy environments: ✅ Excellent (26-63 retransmissions)
- Mixed conditions: ✅ Good stability
- Pure delay ONLY: ❌ Failed (6.82 Mbps) - but this is extremely rare
For the specific edge cases where v1 fails (pure delay), users should:
Use Reno:
- Moderate delay test: 27.3 Mbps
- Retransmissions: 76
- Balanced approach for pure delay scenarios
Use Cubic:
- Moderate delay test: 23.5 Mbps
- Retransmissions: 74 (lowest traditional algorithm)
- Good compromise
Use BBR (if you need raw throughput):
- Moderate delay test: 57.6 Mbps
- Retransmissions: 1,227 (extremely high)
- Maximum throughput but sacrifices stability
v1 Comparison (moderate delay):
- 21.8 Mbps
- 84 retransmissions (comparable to Reno/Cubic)
- Actually performs well, just not as high throughput
v4 Comparison (moderate delay):
- 11.6 Mbps (47% slower than v1)
- 70 retransmissions (lowest of all, but at what cost?)
- Sacrificed too much throughput for minimal retransmission improvement
I am not touching v1 anymore. It works well for general cases based on all localhost testing done so far.
Community Invitation: Implement Your Own Version
The code for v4 is available in the GitHub repository. If you want to experiment with delay enhancements, here is the complete v4 congestion avoidance logic that was modified:
Complete v4 Congestion Avoidance Code
/* Apply congestion control decision */
if (ca->in_slow_start) {
/* Slow start: exponential growth */
/* ADAPTIVE DELAY RESPONSE: If RTT gradient is high (Input 7),
slow down even if no loss is detected yet. */
if (ca->congestion_detected || inputs[7] > 400) {
/* Detected congestion or rising delay, grow slower */
tcp_slow_start(tp, acked / 2);
} else {
/* Normal slow start */
tcp_slow_start(tp, acked);
}
} else {
/* Congestion avoidance */
/* 1. DELAY-ONLY DETECTION: High queuing delay but no loss/entropy signals */
if (inputs[6] > 800 && !ca->congestion_detected) {
/* Pure delay scenario: use a cautious delta to avoid bufferbloat */
u32 delta = max(1U, acked * cwnd_delta / 3000);
tcp_cong_avoid_ai(tp, tp->snd_cwnd, delta);
}
/* 2. REAL CONGESTION: Low entropy + High RTT Gradient */
else if (ca->has_data && ca->congestion_detected) {
/* Real congestion: be conservative */
/* If gradient (Input 7) is also high, be extra conservative */
u32 divisor = (inputs[7] > 500) ? 4000 : 2000;
u32 delta = max(1U, acked * cwnd_delta / divisor);
tcp_cong_avoid_ai(tp, tp->snd_cwnd, delta);
}
/* 3. NOISE/CLEAR PATH: High entropy or low delay */
else if (ca->has_data && !ca->congestion_detected) {
/* High entropy = noise: be aggressive */
u32 delta = max(1U, acked * cwnd_delta / 1000);
tcp_cong_avoid_ai(tp, tp->snd_cwnd, delta);
}
/* 4. FALLBACK */
else {
/* Not enough data: use standard Reno */
tcp_reno_cong_avoid(sk, ack, acked);
}
}
This is the real update - what I called "catastrophic" is actually just a case where minor updates were needed, but the results show the issue clearly.
My Recommendation: Use v1 for Overall Cases
Based on all localhost tests conducted so far, v1 has the upper hand in general cases.
The pure delay scenario is problematic, but it's not like Reno, Cubic, or BBR where we can just borrow their approach. NDM-TCP has a different philosophy: stability first.
If you want the lowest retransmissions: v4 achieves this (70 retrans), but at the cost of poor throughput.
If you want balanced stability: v1 is better (84 retrans with 21.8 Mbps throughput).
My preference: v1 over v4 because stability means balancing BOTH retransmissions AND throughput, not just minimizing retransmissions.
Alternative for Pure Delay Cases
When NOT using v4 for pure delay scenarios, use Cubic:
- 23.5 Mbps throughput
- 74 retransmissions (excellent stability)
- Proven traditional algorithm
- Better balance than v4 for these specific cases
You can modify v1 with this code if you want to experiment, or create your own version with different thresholds and approaches.
But be aware: The delay-based additions reduce throughput. I can't touch v1 anymore because the trade-offs are unclear, and v1 works well for general cases.
Final Recommendation
For general network use: Use NDM-TCP v1
- Proven in multiple localhost test scenarios
- Excellent stability in loss-heavy conditions
- Good performance in constrained networks
- Balanced retransmissions (84) and throughput (21.8 Mbps) even in moderate delay
- Only fails catastrophically in extremely rare pure delay-only edge cases
For pure delay-only scenarios (HFT, dedicated fiber, etc.):
- Use Cubic for best balance (23.5 Mbps, 74 retrans)
- Use Reno for slightly higher throughput (27.3 Mbps, 76 retrans)
- Use BBR if you need maximum throughput and can tolerate massive retransmissions (57.6 Mbps, 1,227 retrans)
- Use v4 only if you want absolute lowest retransmissions (70) and can accept very low throughput (11.6 Mbps)
I prefer v1 over v4 because stability means balancing retransmissions AND throughput together, not just optimizing one metric.
Community help desperately needed for real-world tests. All these results are from localhost artificial testing. Real hardware validation is critical to understand:
- How these algorithms actually perform on real networks
- Whether v1's or v4's approach works better in production
- What the actual trade-offs are outside of localhost simulation
- Which version should be recommended for real deployment
Community contributions welcome if you can solve the pure delay problem without sacrificing v1's general-case performance.
Disclaimer: All results from localhost artificial testing. None of any version has been tested on real hardware. Real hardware validation critically needed. v4 is experimental and untested beyond one scenario. v1 remains the main version for general use based on localhost testing results across multiple scenarios. Community testing on real hardware is essential.
SOCIAL SHARE CARD GENERATOR