Service to get lowest latency route to targets and set static routes.
Project description
LLRO (Lowest Latency Routes Optimizer)
Service to measure ICMP latency from multiple uplinks and keep per-host /32 routes pinned to the best path.
LLRO continuously probes each monitored destination through each configured uplink source, compares packet loss and latency, and installs host routes using Linux ip route so traffic for that destination follows the healthiest path. It can freeze or override routing decisions per host through a local admin socket, and it can fall back to predefined routes when probes fail. In short, it automates per-destination path selection based on live network conditions instead of static one-time routing choices.
For a deeper technical walkthrough, see HOW_IT_WORKS.md.
Runtime requirements
- Linux with
iproute2(ipcommand available, default path/usr/sbin/ip) - Root privileges or equivalent capabilities (
CAP_NET_ADMINand raw ICMP capability) - Python
>=3.7
Development setup
uv sync
Run locally
uv run llro --config ./config.yml
Configuration (recommended model)
Start from the example:
cp config.example.yml config.yml
Example:
monitor:
- 1.1.1.1
- 8.8.8.8
routes:
- name: wan_fiber
device: eth0
probe_source: 192.168.0.8
gateway: 192.168.0.1
- name: wan_lte
device: wwan0
probe_source: 10.0.0.2
gateway: 10.0.0.1
also_route:
1.1.1.1:
- 1.0.0.1
8.8.8.8:
- 8.8.4.4
fallback_routes:
1.1.1.1: wan_fiber
8.8.8.8: wan_lte
rtt_threshold: 20
packet_loss_threshold: 2
test_count: 5
test_interval: 1
scan_interval: 30
delete_preadded_routes: true
# ip_bin: /usr/sbin/ip
# admin_socket_path: /run/llro/admin.sock
Key fields
monitor: host IPs to probe and route.routes: route candidates.routes[].name: unique route identifier.routes[].device: network device used for route installation.routes[].probe_source: source IP used for probing and routesrc.routes[].gateway: next-hop gateway for the host route.also_route: optional extra IPs that should follow a monitored host route.fallback_routes: optional fallback route name per monitored host.rtt_threshold: minimum RTT improvement (ms) required before switching.packet_loss_threshold: packet-loss threshold (%) that can force switching.test_count: number of probe rounds aggregated before routing decisions.test_interval: interval between ping packets in a probe run.scan_interval: delay between scan cycles.delete_preadded_routes: remove existing static/32routes for monitored hosts on startup.ip_bin: optionalipbinary path override.admin_socket_path: Unix socket path used byllro-clifor admin/monitoring.
Legacy config compatibility
The old interfaces model is still accepted for now:
interfaces:
eth0:
- 192.168.0.8
Compatibility mode maps each interfaces.<device>.<source> entry to a generated route candidate:
name: "<device>:<source>"probe_source: <source>gateway: <source>(legacy behavior)
fallback_routes may reference either route names (new) or legacy source IPs (old).
Tooling (Make + uv)
make validate # format check + lint + typecheck + dead-code scan
make test # pytest
make integration-test # dockerized route mutation integration test
make build # build sdist/wheel + twine metadata check
Auto-fix formatting/lint issues:
make fix
Run integration tests directly:
RUN_DOCKER_INTEGRATION=1 uv run pytest -m integration
The compose integration scenario spins up multiple containers, blocks ICMP on one path, and verifies LLRO switches the monitored host route to the remaining healthy path.
Install as CLI
From local checkout:
uv pip install .
From wheel:
uv pip install dist/*.whl
Then run:
llro --config /etc/llro.yml
Admin commands (against running daemon):
llro-cli status
llro-cli override --host 1.1.1.1 --route wan_fiber
llro-cli disable-switching --all
llro-cli reset-auto --host 1.1.1.1
Example output:
$ llro-cli status
Host 1.1.1.1 | mode=auto | switching=yes | current=wan_fiber | override=-
wan_fiber: rtt=14.2 ms, loss=0%, alive=yes
wan_lte: rtt=35.8 ms, loss=0%, alive=yes
Host 8.8.8.8 | mode=frozen | switching=no | current=wan_lte | override=-
wan_fiber: rtt=48.1 ms, loss=0%, alive=yes
wan_lte: rtt=31.6 ms, loss=0%, alive=yes
$ llro-cli status --json
{
"hosts": [
{
"current_route": "wan_fiber",
"host": "1.1.1.1",
"mode": "auto",
"override_route": null,
"routes": {
"wan_fiber": {
"avg_loss": 0,
"avg_rtt": 14.2,
"is_alive": true
},
"wan_lte": {
"avg_loss": 0,
"avg_rtt": 35.8,
"is_alive": true
}
},
"switching_enabled": true
}
]
}
$ llro-cli override --host 1.1.1.1 --route wan_lte
{"host": "1.1.1.1", "mode": "override", "route": "wan_lte"}
$ llro-cli disable-switching --all
{"hosts": ["1.1.1.1", "8.8.8.8"], "mode": "frozen"}
$ llro-cli reset-auto --host 1.1.1.1
{"hosts": ["1.1.1.1"], "mode": "auto"}
systemd service
Use the provided unit as a base and verify the executable path in your environment:
which llro
Install:
sudo cp llro.service /etc/systemd/system/llro.service
sudo systemctl daemon-reload
sudo systemctl enable --now llro
sudo systemctl status llro
PyPI release flow
- Local dry-run build:
make build - Publish manually with Twine:
make publish-testpypimake publish-pypi- GitHub Actions publish:
- Push tag
v*to trigger.github/workflows/publish-to-pypi.yml - Workflow uses trusted publishing (
id-token) for PyPI
Contributing
Inspired by https://malaty.net/linux-lowest-latency-routes-optimizer/
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file llro-1.0.0.tar.gz.
File metadata
- Download URL: llro-1.0.0.tar.gz
- Upload date:
- Size: 19.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2814756ff18a790a588a6584d81fc708a63219d53cd48335994217350c124d45
|
|
| MD5 |
43771da0fa203f9d7d2dc08ab8175687
|
|
| BLAKE2b-256 |
a835636de700e70518a726740cc5fa2a216df773a00409279b14f2f316fcbf21
|
Provenance
The following attestation bundles were made for llro-1.0.0.tar.gz:
Publisher:
publish-to-pypi.yml on smeinecke/llro
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
llro-1.0.0.tar.gz -
Subject digest:
2814756ff18a790a588a6584d81fc708a63219d53cd48335994217350c124d45 - Sigstore transparency entry: 1203479637
- Sigstore integration time:
-
Permalink:
smeinecke/llro@e9aafe00a8e0f72dfe0f60fb847038d5a9056b79 -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/smeinecke
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-to-pypi.yml@e9aafe00a8e0f72dfe0f60fb847038d5a9056b79 -
Trigger Event:
push
-
Statement type:
File details
Details for the file llro-1.0.0-py3-none-any.whl.
File metadata
- Download URL: llro-1.0.0-py3-none-any.whl
- Upload date:
- Size: 13.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ab594a9dc657c6807ffd9e0a9735f3f17f7b9917eb989ce1e354851a18b2172d
|
|
| MD5 |
1f234d7d9685e33bbad56e7480104b81
|
|
| BLAKE2b-256 |
46009e627b977b8afa33258663ec3b188bc0f788b295214d2f9f023455582d62
|
Provenance
The following attestation bundles were made for llro-1.0.0-py3-none-any.whl:
Publisher:
publish-to-pypi.yml on smeinecke/llro
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
llro-1.0.0-py3-none-any.whl -
Subject digest:
ab594a9dc657c6807ffd9e0a9735f3f17f7b9917eb989ce1e354851a18b2172d - Sigstore transparency entry: 1203479642
- Sigstore integration time:
-
Permalink:
smeinecke/llro@e9aafe00a8e0f72dfe0f60fb847038d5a9056b79 -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/smeinecke
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-to-pypi.yml@e9aafe00a8e0f72dfe0f60fb847038d5a9056b79 -
Trigger Event:
push
-
Statement type: