Skip to main content

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 (ip command available, default path /usr/sbin/ip)
  • Root privileges or equivalent capabilities (CAP_NET_ADMIN and raw ICMP capability)
  • Python >=3.11

Development setup

uv sync

Run locally

uv run llro --config ./config.yml

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
pings_per_probe: 5
decision_cycles: 5
test_interval: 1
scan_interval: 30
delete_preadded_routes: true
# probe_timeout: 2
# bind_to_device: false
# ewma_alpha: 0.4
# switch_cooldown: 60
# 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 route src.
  • 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.
  • pings_per_probe: ICMP echo requests sent per host per route in each probe cycle.
  • decision_cycles: number of probe cycles aggregated before a routing decision.
  • test_interval: interval between ping packets in a probe run.
  • probe_timeout: seconds to wait for each ICMP reply (default 2).
  • payload_size: ICMP Echo Request payload size in bytes.
  • scan_interval: delay between scan cycles.
  • bind_to_device: bind probe sockets to each route's device (SO_BINDTODEVICE) so probes egress that interface (default false; see "Probe path pinning").
  • ewma_alpha: smoothing factor (0, 1] applied to per-route RTT/loss before decisions (default 0.4; use 1.0 to disable smoothing).
  • switch_cooldown: minimum seconds between RTT-improvement switches per host (default 60; 0 disables; loss/dead failover is never delayed).
  • test_count: deprecated; when set without the new keys it maps to both pings_per_probe and decision_cycles.
  • delete_preadded_routes: remove existing static /32 routes for monitored hosts on startup.
  • ip_bin: optional ip binary path override.
  • admin_socket_path: Unix socket path used by llro-cli for admin/monitoring.

Probe path pinning

Probes are sent with each route's probe_source as their source address. Source binding alone does not fix the egress interface: once a <host>/32 route is installed, all probes for that host follow it in the forward direction, so per-uplink metrics reflect hybrid paths (forward via the current route, return via the probe's uplink) — and packets leaving one uplink with another uplink's source IP may be dropped by upstream anti-spoofing (uRPF), showing up as phantom loss.

Two ways to make probes actually traverse the intended uplink:

  • bind_to_device: true — LLRO sets SO_BINDTODEVICE on each probe socket, pinning egress to the route's device. The bound interface still needs a usable route to the destination (e.g. its own default route in the main table); otherwise probes fail with ENETUNREACH. Requires CAP_NET_RAW (already required by LLRO).
  • Alternatively, keep bind_to_device off and configure per-source policy routing yourself, e.g. ip rule from <probe_source> table <uplink-table> plus a default route in that table. The probe's source binding then selects the right table.

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/complexity/security scans
make test              # unit tests (pytest -m "not integration")
make test-integration  # dockerized integration tests (needs a docker daemon)
make build             # build sdist/wheel + twine metadata check

Auto-fix formatting/lint issues:

make fix

Run integration tests directly:

uv run pytest tests/test_integration_compose.py -v -m integration

The compose integration scenario spins up multiple containers and verifies the daemon end-to-end: admin socket controls (status/override/disable-switching/reset-auto via llro-cli), route switchover when ICMP is blocked on the active path, and fallback_routes when all paths fail.

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", "route_applied": true}

$ 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

The provided unit runs LLRO under a dedicated unprivileged user with only the network capabilities it needs (CAP_NET_ADMIN and CAP_NET_RAW).

Prerequisites

Create the service user and verify the executable path:

sudo useradd --system --no-create-home --shell /usr/sbin/nologin llro
which llro
# Update ExecStart in llro.service if the path differs from /usr/local/bin/llro

The unit uses RuntimeDirectory=llro so /run/llro is created automatically with correct ownership for the admin socket.

Install

sudo cp llro.service /etc/systemd/system/llro.service
sudo systemctl daemon-reload
sudo systemctl enable --now llro
sudo systemctl status llro

Verifying capabilities

On a hardened unit, LLRO must still be able to create raw ICMP sockets and modify routes. If the service fails to probe or install routes, check the journal for capability denials:

sudo journalctl -u llro -n 50

PyPI release flow

  • Local dry-run build: make build
  • Publish manually with Twine:
  • make publish-testpypi
  • make publish-pypi
  • GitHub Actions publish:
  • Push tag v*.*.* to trigger .github/workflows/release.yml
  • Workflow verifies the tag matches the package version, attests build provenance, creates a GitHub release, and publishes to PyPI via trusted publishing (id-token)

Contributing

Inspired by https://malaty.net/linux-lowest-latency-routes-optimizer/

Release files for llro 1.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for llro 1.1.0
File Size Uploaded
llro-1.1.0.tar.gz 29.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for llro 1.1.0
File Interpreter ABI Platform
llro-1.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 47.9 kB

Release files / llro-1.1.0.tar.gz

Download URL llro-1.1.0.tar.gz
Size 29.8 kB
Tags Source
SHA-256 checksum
How to use checksums
bd8a94f9378209804e75e1ecc2dbec28e0c537e03fdcd8a21c0295938e5b8715
BLAKE2b-256 checksum
How to use checksums
dd4ac1f2d30aedc17a257bdfb1c5dec6f80761e53e0104503badc522a7c0de75
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.

Transparency log

Release files / llro-1.1.0-py3-none-any.whl

Download URL llro-1.1.0-py3-none-any.whl
Size 18.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
28963471ceada0fddc775366c2b66a08aa1720dde24ed3956309fab65bb0d424
BLAKE2b-256 checksum
How to use checksums
b823f93e1169e4bf835a2728b6676853bbbc4d4d92478b9bc2bfa8fd9de26c8b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.1.0 This release

2 release files

1.0.2

2 release files

1.0.1

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page