Skip to main content

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

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/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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

llro-1.0.1.tar.gz (21.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

llro-1.0.1-py3-none-any.whl (14.5 kB view details)

Uploaded Python 3

File details

Details for the file llro-1.0.1.tar.gz.

File metadata

  • Download URL: llro-1.0.1.tar.gz
  • Upload date:
  • Size: 21.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for llro-1.0.1.tar.gz
Algorithm Hash digest
SHA256 7d194979d9461e228446bde561cc9b1649797107f5efc9eec0856f74c22d3eae
MD5 12ab5c909c22929956eaed67ead7ece4
BLAKE2b-256 040d2b5c9660b2b65814bc45a96ab1b357ba284a9de510de6c5c96e40476b363

See more details on using hashes here.

Provenance

The following attestation bundles were made for llro-1.0.1.tar.gz:

Publisher: publish-to-pypi.yml on smeinecke/llro

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file llro-1.0.1-py3-none-any.whl.

File metadata

  • Download URL: llro-1.0.1-py3-none-any.whl
  • Upload date:
  • Size: 14.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for llro-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 569c37ad5014af10c593c89479bef1e29f1a72c595a0d5a6b261cb88318395c9
MD5 7d742e00925dad0c0619e3ee8c675ffb
BLAKE2b-256 823535e2c39cd1e267323adb38233051caecff21cb1e96929bfb9df196598419

See more details on using hashes here.

Provenance

The following attestation bundles were made for llro-1.0.1-py3-none-any.whl:

Publisher: publish-to-pypi.yml on smeinecke/llro

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page