Adaptive CAKE bandwidth control for MikroTik RouterOS and Linux CAKE backends.
Reduces bufferbloat by continuously monitoring RTT and adjusting CAKE bandwidth limits in real time. Supports multi-WAN RouterOS deployments with optional intelligent traffic steering.
- Continuous RTT monitoring - 50ms control loops (40x faster than original 2s baseline)
- Multi-state congestion control - GREEN/YELLOW/SOFT_RED/RED state machine
- Multi-signal detection - RTT + CAKE drops + queue depth for accuracy
- REST API transport - 2x faster than SSH (~50ms vs ~150ms latency)
- Optional WAN steering - Route latency-sensitive traffic during congestion
- Config-driven - Same code works for fiber, cable, DSL, or any connection
- FHS compliant - Proper Linux directory layout and service user
- Hardened security - Input validation, EWMA bounds checking, rate limiting, centralized validation
- Production reliability - Bounded memory, file locking, automatic state backup recovery
- Observability - Health check endpoint, Prometheus metrics, JSON structured logging
- Signal processing - Hampel outlier filter + EWMA smoothing for noise-resilient RTT measurement
- Dual-signal fusion - Weighted ICMP + IRTT UDP measurement blending (ships disabled, SIGUSR1 toggle)
- IRTT measurement - Isochronous UDP RTT with directional loss detection and OWD asymmetry analysis
- Reflector quality scoring - Rolling quality scores with automatic deprioritization and recovery
- Adaptive tuning - Self-optimizing controller learns optimal parameters from production metrics
- Alerting - Discord webhook notifications for congestion, hard-red, connectivity, IRTT loss, fusion healing, and cycle-budget events
- Operator inspection - Health endpoints, compact summaries, and metrics/alert/tuning history CLI
- CLI tools - Config validation, CAKE queue audit, RRUL benchmarking, metrics/alert/tuning history
- MikroTik router running RouterOS 7.x with CAKE queues configured, or Linux CAKE qdiscs for
linux-caketransports - Linux host (LXC container, VM, or bare metal) with Python 3.11+
- For RouterOS control: REST API enabled on the router (recommended) or SSH key authentication
# Clone the repository
git clone https://github.com/kevinb361/wanctl.git
cd wanctl
# Run installation with interactive setup wizard (recommended)
sudo ./scripts/install.shThe interactive setup wizard guides you through:
- Router connection setup (REST API or SSH)
- Automated connection testing and validation
- Queue discovery from your router
- Connection-type presets (cable/DSL/fiber) with optimized defaults
- Multi-WAN architecture guidance
- Optional traffic steering configuration
Alternative installation modes:
# Non-interactive install (for automation)
sudo ./scripts/install.sh --no-wizard
# Re-run wizard on existing installation
sudo ./scripts/install.sh --reconfigure
# Uninstall wanctl
sudo ./scripts/install.sh --uninstallSupported installs require a clean Git tree and embed its full commit revision. Verify the installed release and source identity with:
wanctl-version --jsonBuild local container images through the identity-preserving wrapper rather than invoking
docker build directly:
./scripts/build-image.sh
# Export the emitted version-revision tag before using docker/docker-compose.yml.
export WANCTL_IMAGE='wanctl:<version>-<revision-prefix>'The controller and steering health payloads expose the same identity as top-level version
and revision fields plus the nested build object.
After wizard completion, enable the native wanctl@ service:
sudo systemctl enable --now wanctl@wan1.serviceThis quickstart describes the portable native mode: wanctl@<wan>.service
owns rate control directly and publishes the state consumed by steering. wanctl
also supports an external cake-autorate deployment mode where
cake-autorate-<wan>.service owns the Linux CAKE rate decisions and a
wanctl-side cake-autorate-<wan>-state-bridge.service publishes the same state,
health, and metrics contract. See Deployment for choosing
between the two service models.
REST API (recommended):
# Add password to secrets file (loaded by systemd as environment variable)
sudo nano /etc/wanctl/secrets
# Add line: ROUTER_PASSWORD=your_router_passwordIn your config, reference the environment variable:
router:
transport: "rest"
host: "192.168.1.1"
user: "admin"
ssh_key: "/etc/wanctl/ssh/router.key" # Currently required by base validation
password: "${ROUTER_PASSWORD}" # Expanded from /etc/wanctl/secrets
port: 443
verify_ssl: falseThe plaintext password is not stored in the config file - systemd loads /etc/wanctl/secrets via EnvironmentFile and the ${VAR} syntax is expanded at runtime.
SSH (alternative):
# Copy your router SSH key
sudo cp ~/.ssh/router_key /etc/wanctl/ssh/router.key
sudo chown wanctl:wanctl /etc/wanctl/ssh/router.key
sudo chmod 600 /etc/wanctl/ssh/router.keyIn your config, set:
router:
transport: "ssh"
host: "192.168.1.1"
user: "admin"
ssh_key: "/etc/wanctl/ssh/router.key"Deploy from your development machine to a target host:
./scripts/deploy.sh wan1 target-hostname
./scripts/deploy.sh wan2 192.168.1.100 --with-steeringEvery 50ms by default:
- Measure RTT to reference hosts (1.1.1.1, 8.8.8.8, 9.9.9.9)
- Track baseline RTT via slow EWMA (only updates when idle)
- Calculate delta = loaded_rtt - baseline_rtt
- Determine state based on delta thresholds
- Adjust bandwidth limits on the configured CAKE backend
- Apply floors based on current state (policy enforcement)
delta <= 15ms
┌─────────────────────────┐
│ │
▼ 15-45ms │
GREEN ───────────────► YELLOW
▲ │
│ 45-80ms ▼
│ ┌───────────── SOFT_RED
│ │ │
│ │ >80ms ▼
└─────┴───────────────── RED
(recovery requires
sustained GREEN)
State-dependent floors prevent bandwidth collapse:
- GREEN: High floor (e.g., 550 Mbps) - normal operation
- YELLOW: Moderate floor (e.g., 350 Mbps) - early warning
- SOFT_RED: Aggressive floor (e.g., 275 Mbps) - RTT-only congestion
- RED: Emergency floor (e.g., 200 Mbps) - hard congestion
wanctl has been optimized for extremely fast congestion response while maintaining stability:
Cycle Interval: 50ms (20Hz polling)
- 40x faster than original 2-second baseline
- Sub-second congestion detection (50-100ms response time)
- Controlled by the
CYCLE_INTERVAL_SECONDSsource constant, not by YAML config
Router Efficiency:
- 0% CPU at idle - Zero measurable impact from 20Hz REST API polling
- ~45% peak under heavy load - Comfortable headroom during RRUL stress testing
- MikroTik RB5009 handles 50ms intervals effortlessly
Time-Constant Preservation:
- EWMA alpha values automatically scale with interval changes
- Steering hysteresis uses configured sample counts to maintain stable activation and recovery timing
- Same congestion response characteristics regardless of polling rate
Validation:
- Proven stable under 3-minute RRUL bidirectional stress testing
- Perfect baseline RTT stability (no drift under extreme alpha values)
- Zero errors or timing violations
- Tested on both cable (Spectrum) and DSL (AT&T) connections
Performance Boundary:
- 50ms represents practical limit (60-80% cycle utilization)
- Execution time: 30-40ms per cycle
- ATT (DSL): ±1ms timing consistency
- Spectrum (cable): ±10ms variance (acceptable for cable networks)
The 50ms interval provides maximum responsiveness without sacrificing stability. Conservative intervals such as 100ms or 250ms have historical validation context, but the active deployment standard is 50ms and the interval is not a YAML setting.
Example configs are provided for common connection types:
| Config | Use Case |
|---|---|
wan1.yaml.example |
Generic primary WAN |
wan2.yaml.example |
Generic secondary WAN |
fiber.yaml.example |
GPON/XGS-PON fiber (low latency) |
cable.yaml.example |
DOCSIS cable (variable latency) |
dsl.yaml.example |
DSL/VDSL (sensitive upload) |
steering.yaml.example |
Multi-WAN traffic steering |
Copy to /etc/wanctl/ and customize for your setup. See CONFIG_SCHEMA.md for the complete configuration reference including alerting, fusion, IRTT, and adaptive tuning options.
Additional references:
- Documentation Index - canonical map of current docs versus archived historical notes
- SUBSYSTEMS.md - storage, backend, health, alerting, and measurement-quality internals
- PERFORMANCE.md - production timing, cycle-budget, and profiling guidance
- TESTING.md - current test commands and integration-test invocation
- SILICOM-BYPASS.md - Silicom bypass NIC operations, powered fail-open watchdogs, and Spectrum migration validation notes
/opt/wanctl/ # Code
/etc/wanctl/ # Configuration
├── wan1.yaml # WAN config
├── secrets # Environment secrets (ROUTER_PASSWORD, DISCORD_WEBHOOK_URL)
└── ssh/router.key # Router SSH key (for SSH transport)
/var/lib/wanctl/ # State files (EWMA persistence, SQLite metrics database)
/var/log/wanctl/ # Logs
/run/wanctl/ # Lock files
For dual-WAN setups, the steering daemon routes latency-sensitive traffic to the healthier WAN during congestion:
# Enable steering
sudo systemctl enable --now steering.serviceWhat gets steered: VoIP, gaming, DNS, SSH, interactive web What stays: Bulk downloads, video streaming, background traffic
Steering uses multi-signal detection (RTT + CAKE drops + queue depth) with hysteresis to prevent flapping.
The examples below are for native wanctl@ mode. In external cake-autorate mode,
monitor cake-autorate-<wan>.service,
cake-autorate-<wan>-state-bridge.service, and steering.service; the state JSON
and health endpoint contract remain wanctl-compatible.
# Service status
systemctl status wanctl@wan1.service
# Live logs
journalctl -u wanctl@wan1 -f
tail -f /var/log/wanctl/wan1.log
# Current state
cat /var/lib/wanctl/wan1_state.jsonHealthy output:
[GREEN/GREEN] RTT=25.5ms, baseline=24.0ms, delta=1.5ms | DL=940M, UL=38M
HTTP endpoint for Kubernetes probes and monitoring systems (enabled by default):
curl http://127.0.0.1:9101/health{
"status": "healthy",
"uptime_seconds": 3600.5,
"version": "<installed wanctl version>",
"consecutive_failures": 0,
"wan_count": 1,
"wans": [
{
"name": "wan1",
"baseline_rtt_ms": 24.01,
"load_rtt_ms": 25.5,
"download": { "state": "GREEN", "current_rate_mbps": 940.0 },
"upload": { "state": "GREEN", "current_rate_mbps": 38.0 },
"signal_quality": {
"jitter_ms": 0.42,
"variance_ms2": 0.18,
"confidence": 0.95,
"outlier_rate": 0.03
},
"irtt": { "available": true, "rtt_mean_ms": 28.5, "ipdv_ms": 0.8 },
"reflector_quality": {
"available": true,
"hosts": { "1.1.1.1": { "score": 0.98, "status": "active" } }
},
"fusion": { "enabled": false, "reason": "disabled" },
"tuning": { "enabled": false, "reason": "disabled" }
}
],
"alerting": { "enabled": true, "fire_count": 3, "active_cooldowns": [] },
"router_reachable": true,
"disk_space": { "status": "ok" }
}Configure in your WAN config:
health_check:
enabled: true # default
port: 9101 # defaultPrometheus-compatible metrics endpoint (disabled by default):
curl http://127.0.0.1:9100/metricsCommon metrics include wanctl_bandwidth_mbps, wanctl_rtt_delta_ms, wanctl_state, wanctl_cycles_total, storage pressure gauges, runtime pressure gauges, checkpoint/WAL counters, router update counters, ping failure counters, and steering counters.
Native-controller SQLite history also retains wanctl_state_download and
wanctl_state_upload so adaptive response analysis can distinguish directions;
the legacy labeled wanctl_state series remains for compatibility. External
cake-autorate state bridges do not run native adaptive tuning.
Stored SQLite history is available through the autorate health server:
curl 'http://127.0.0.1:9101/metrics/history?range=1h&limit=20'The history response includes metadata.source so operators can tell whether the data came from the endpoint-local daemon DB or merged DB discovery fallback.
Enable in config:
metrics:
enabled: true
port: 9100For log aggregation tools (Loki, ELK):
export WANCTL_LOG_FORMAT=jsonValidate configuration without starting the daemon:
wanctl --config /etc/wanctl/wan1.yaml --validate-config
# Exit code: 0 = valid, 1 = invalidFor more thorough offline validation, use the dedicated CLI tool:
wanctl-check-config /etc/wanctl/wan1.yamlThis validates all sections including alerting, tuning, signal_processing, IRTT, and fusion config.
wanctl ships with several CLI utilities for diagnostics, operations, and validation:
| Tool | Purpose | Example |
|---|---|---|
wanctl |
Run the autorate daemon or one-shot validation modes | wanctl --config /etc/wanctl/wan1.yaml |
wanctl-calibrate |
Measure baseline/throughput and generate a starter WAN config | wanctl-calibrate --wan-name wan1 --router 192.168.1.1 |
wanctl-steering |
Run the optional multi-WAN steering daemon | wanctl-steering --config /etc/wanctl/steering.yaml |
wanctl-operator-summary |
Render compact health summaries from health JSON URLs/files | wanctl-operator-summary http://host:9101/health |
wanctl-history |
Query metrics, alerts, tuning, and per-tin history from SQLite | wanctl-history --last 1h --metrics wanctl_rtt_ms --json |
wanctl-check-config |
Validate config files offline before deploy | wanctl-check-config /etc/wanctl/wan1.yaml |
wanctl-check-cake |
Audit live CAKE queue config; --fix mutates router state |
wanctl-check-cake /etc/wanctl/wan1.yaml |
wanctl-benchmark |
Run RRUL benchmark, store results, compare/list past runs | wanctl-benchmark --quick --label before-change |
wanctl-analyze-baseline |
Summarize CAKE signal baselines and state transitions | wanctl-analyze-baseline --hours 24 --wan spectrum |
# Query recent metrics history
wanctl-history --last 1h --metrics wanctl_rtt_ms
# View alert history
wanctl-history --alerts --last 24h
# View tuning adjustment history
wanctl-history --tuning --last 24h
# View CAKE tin history as JSON
wanctl-history --tins --last 1h --json
# Validate config before deploying
wanctl-check-config /etc/wanctl/wan1.yaml
# Audit CAKE queues on router
wanctl-check-cake /etc/wanctl/wan1.yaml
# Run RRUL benchmark
wanctl-benchmark --server netperf-server --wan wan1 --label post-deploy
wanctl-benchmark history --last 24h --wan wan1By default, wanctl-history auto-discovers active per-WAN metrics DBs under /var/lib/wanctl. Use --db PATH only when inspecting a specific database file.
Here's actual output from a stress test on a 940/38 Mbps Spectrum cable connection. Eight parallel netperf streams were used to saturate the link.
Note: This test was conducted with the original 2-second interval. Modern deployments run at 50ms intervals for 40x faster response (see Performance section below).
Time State Delta Upload BW RTT Event
────────────────────────────────────────────────────────────────
00:00:37 GREEN/GREEN 2.2ms 38M 26ms Idle baseline
00:00:44 GREEN/GREEN 10.8ms 38M 70ms Load increasing
00:00:52 YELLOW/RED 62.6ms 34M 295ms Congestion detected!
00:00:59 YELLOW/RED 60.8ms 31M 79ms Backing off upload
00:01:06 SOFT_RED/RED 47.9ms 28M 21ms Continued reduction
00:01:18 YELLOW/YELLOW 29.3ms 28M 21ms Recovering
00:01:31 YELLOW/YELLOW 17.9ms 28M 22ms Almost there
00:01:56 GREEN/GREEN 7.1ms 28M 26ms Recovered
- Congestion spike - RTT jumped from 26ms to 295ms (bufferbloat)
- Automatic response - Upload reduced from 38M to 28M (26% reduction)
- Latency controlled - Delta dropped from 62ms back to 7ms
- Self-healing - System returned to GREEN, upload will gradually recover
The entire event was handled automatically in under 90 seconds with no user intervention. Upload bandwidth will slowly climb back to 38M while the system stays GREEN (1 Mbps per cycle).
wanctl is designed to support multiple shaping backends. Current backends include RouterOS REST/SSH and local Linux CAKE control via tc or optional pyroute2/netlink.
To add a new backend (e.g., OpenWrt, pfSense):
- Create
src/wanctl/backends/<platform>.py - Implement the
RouterBackendinterface - Add to factory in
__init__.py
See src/wanctl/backends/base.py for the interface definition.
This project stands on the shoulders of Dave Täht, pioneer of the bufferbloat movement and lead developer of CAKE. Dave personally helped configure CAKE on MikroTik in the early days:
- Forum thread: Some quick comments on configuring CAKE (October-November 2021)
His work on CAKE, fq_codel, and the bufferbloat project benefits millions of internet users. Rest in peace, Dave.
- CAKE team - Jonathan Morton, Toke Høiland-Jørgensen, and contributors
- LibreQoS - Robert McMahon and team for enterprise-grade CAKE orchestration
- sqm-autorate - Lynx and the OpenWrt community for automatic SQM tuning
- MikroTik - For implementing CAKE in RouterOS
This project was developed with assistance from Claude (Anthropic). The architecture, algorithms, and documentation were created collaboratively between a human sysadmin and AI.
This is a power-user tool, not enterprise software.
Built by a sysadmin for personal use, now shared with the community. Not competing with LibreQoS - just a well-engineered solution for MikroTik users who want adaptive CAKE tuning.
Target audience: Power users, sysadmins, and homelabbers who can read configs and adapt.
- Not a replacement for understanding CAKE - You should know how CAKE works before using this
- Not intended for automatic ISP tuning - Designed for user-managed networks
- Not enterprise orchestration software - See LibreQoS for that
See CONTRIBUTING.md for guidelines.
Issues and PRs are welcome, but this is maintained by a sysadmin in spare time. Please be patient and provide detailed information when reporting issues.
GPL-2.0 - See LICENSE
wanctl aims to be the reference implementation for adaptive CAKE bandwidth control on RouterOS.