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
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 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.
  • test_count: number of probe rounds aggregated before routing decisions.
  • test_interval: interval between ping packets in a probe run.
  • payload_size: ICMP Echo Request payload size in bytes.
  • scan_interval: delay between scan 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.

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.0.2

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.0.2
File Size Uploaded
llro-1.0.2.tar.gz 25.8 kB Details

Built distribution (wheel)

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

Total release size: 41.5 kB

Release files / llro-1.0.2.tar.gz

Download URL llro-1.0.2.tar.gz
Size 25.8 kB
Tags Source
SHA-256 checksum
How to use checksums
07b3b6ce2459d2fe9f58aad7dc0ed4808132e9e9401977909dcb623bfacff5f0
BLAKE2b-256 checksum
How to use checksums
cb9e00c070c87a03e12ed66a349113dfb6d93b7e6eaebc41309a287e7855ae80
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 23, 2026.

Transparency log

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

Download URL llro-1.0.2-py3-none-any.whl
Size 15.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
636eec765e56a1fb1d899090e41a7d7dd2b2654d134b872e606b0a8b0958cfa8
BLAKE2b-256 checksum
How to use checksums
4806076aa4c06162a7f7978de719b2020b975f699280b86ba622e2c776dfc5c5
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 23, 2026.

Transparency log

Release history Release notifications | RSS feed

1.1.0

2 release files

This release

1.0.2 This release

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