llro 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
llro-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Stefan Meinecke
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
llro-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,262 @@
1
+ Metadata-Version: 2.4
2
+ Name: llro
3
+ Version: 1.0.0
4
+ Summary: Service to get lowest latency route to targets and set static routes.
5
+ Author: LLRO contributors
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/greensec/LowestLatencyRoutesOptimizer
8
+ Project-URL: Repository, https://github.com/greensec/LowestLatencyRoutesOptimizer
9
+ Project-URL: Issues, https://github.com/greensec/LowestLatencyRoutesOptimizer/issues
10
+ Keywords: network,routing,latency,icmp
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3 :: Only
13
+ Classifier: Operating System :: POSIX :: Linux
14
+ Classifier: Topic :: System :: Networking
15
+ Requires-Python: >=3.7
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: icmplib==3.0.4
19
+ Requires-Dist: PyYAML==6.0.1; python_version < "3.8"
20
+ Requires-Dist: PyYAML==6.0.3; python_version >= "3.8"
21
+ Dynamic: license-file
22
+
23
+ # LLRO (Lowest Latency Routes Optimizer)
24
+
25
+ Service to measure ICMP latency from multiple uplinks and keep per-host `/32` routes pinned to the best path.
26
+
27
+ 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.
28
+
29
+ For a deeper technical walkthrough, see [HOW_IT_WORKS.md](/home/stefan/github/LowestLatencyRoutesOptimizer/HOW_IT_WORKS.md).
30
+
31
+ ## Runtime requirements
32
+
33
+ - Linux with `iproute2` (`ip` command available, default path `/usr/sbin/ip`)
34
+ - Root privileges or equivalent capabilities (`CAP_NET_ADMIN` and raw ICMP capability)
35
+ - Python `>=3.7`
36
+
37
+ ## Development setup
38
+
39
+ ```bash
40
+ uv sync
41
+ ```
42
+
43
+ ## Run locally
44
+
45
+ ```bash
46
+ uv run llro --config ./config.yml
47
+ ```
48
+
49
+ ## Configuration (recommended model)
50
+
51
+ Start from the example:
52
+
53
+ ```bash
54
+ cp config.example.yml config.yml
55
+ ```
56
+
57
+ Example:
58
+
59
+ ```yaml
60
+ monitor:
61
+ - 1.1.1.1
62
+ - 8.8.8.8
63
+
64
+ routes:
65
+ - name: wan_fiber
66
+ device: eth0
67
+ probe_source: 192.168.0.8
68
+ gateway: 192.168.0.1
69
+ - name: wan_lte
70
+ device: wwan0
71
+ probe_source: 10.0.0.2
72
+ gateway: 10.0.0.1
73
+
74
+ also_route:
75
+ 1.1.1.1:
76
+ - 1.0.0.1
77
+ 8.8.8.8:
78
+ - 8.8.4.4
79
+
80
+ fallback_routes:
81
+ 1.1.1.1: wan_fiber
82
+ 8.8.8.8: wan_lte
83
+
84
+ rtt_threshold: 20
85
+ packet_loss_threshold: 2
86
+ test_count: 5
87
+ test_interval: 1
88
+ scan_interval: 30
89
+ delete_preadded_routes: true
90
+ # ip_bin: /usr/sbin/ip
91
+ # admin_socket_path: /run/llro/admin.sock
92
+ ```
93
+
94
+ ### Key fields
95
+
96
+ - `monitor`: host IPs to probe and route.
97
+ - `routes`: route candidates.
98
+ - `routes[].name`: unique route identifier.
99
+ - `routes[].device`: network device used for route installation.
100
+ - `routes[].probe_source`: source IP used for probing and route `src`.
101
+ - `routes[].gateway`: next-hop gateway for the host route.
102
+ - `also_route`: optional extra IPs that should follow a monitored host route.
103
+ - `fallback_routes`: optional fallback route name per monitored host.
104
+ - `rtt_threshold`: minimum RTT improvement (ms) required before switching.
105
+ - `packet_loss_threshold`: packet-loss threshold (%) that can force switching.
106
+ - `test_count`: number of probe rounds aggregated before routing decisions.
107
+ - `test_interval`: interval between ping packets in a probe run.
108
+ - `scan_interval`: delay between scan cycles.
109
+ - `delete_preadded_routes`: remove existing static `/32` routes for monitored hosts on startup.
110
+ - `ip_bin`: optional `ip` binary path override.
111
+ - `admin_socket_path`: Unix socket path used by `llro-cli` for admin/monitoring.
112
+
113
+ ## Legacy config compatibility
114
+
115
+ The old `interfaces` model is still accepted for now:
116
+
117
+ ```yaml
118
+ interfaces:
119
+ eth0:
120
+ - 192.168.0.8
121
+ ```
122
+
123
+ Compatibility mode maps each `interfaces.<device>.<source>` entry to a generated route candidate:
124
+
125
+ - `name: "<device>:<source>"`
126
+ - `probe_source: <source>`
127
+ - `gateway: <source>` (legacy behavior)
128
+
129
+ `fallback_routes` may reference either route names (new) or legacy source IPs (old).
130
+
131
+ ## Tooling (Make + uv)
132
+
133
+ ```bash
134
+ make validate # format check + lint + typecheck + dead-code scan
135
+ make test # pytest
136
+ make integration-test # dockerized route mutation integration test
137
+ make build # build sdist/wheel + twine metadata check
138
+ ```
139
+
140
+ Auto-fix formatting/lint issues:
141
+
142
+ ```bash
143
+ make fix
144
+ ```
145
+
146
+ Run integration tests directly:
147
+
148
+ ```bash
149
+ RUN_DOCKER_INTEGRATION=1 uv run pytest -m integration
150
+ ```
151
+
152
+ 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.
153
+
154
+ ## Install as CLI
155
+
156
+ From local checkout:
157
+
158
+ ```bash
159
+ uv pip install .
160
+ ```
161
+
162
+ From wheel:
163
+
164
+ ```bash
165
+ uv pip install dist/*.whl
166
+ ```
167
+
168
+ Then run:
169
+
170
+ ```bash
171
+ llro --config /etc/llro.yml
172
+ ```
173
+
174
+ Admin commands (against running daemon):
175
+
176
+ ```bash
177
+ llro-cli status
178
+ llro-cli override --host 1.1.1.1 --route wan_fiber
179
+ llro-cli disable-switching --all
180
+ llro-cli reset-auto --host 1.1.1.1
181
+ ```
182
+
183
+ Example output:
184
+
185
+ ```text
186
+ $ llro-cli status
187
+ Host 1.1.1.1 | mode=auto | switching=yes | current=wan_fiber | override=-
188
+ wan_fiber: rtt=14.2 ms, loss=0%, alive=yes
189
+ wan_lte: rtt=35.8 ms, loss=0%, alive=yes
190
+ Host 8.8.8.8 | mode=frozen | switching=no | current=wan_lte | override=-
191
+ wan_fiber: rtt=48.1 ms, loss=0%, alive=yes
192
+ wan_lte: rtt=31.6 ms, loss=0%, alive=yes
193
+ ```
194
+
195
+ ```text
196
+ $ llro-cli status --json
197
+ {
198
+ "hosts": [
199
+ {
200
+ "current_route": "wan_fiber",
201
+ "host": "1.1.1.1",
202
+ "mode": "auto",
203
+ "override_route": null,
204
+ "routes": {
205
+ "wan_fiber": {
206
+ "avg_loss": 0,
207
+ "avg_rtt": 14.2,
208
+ "is_alive": true
209
+ },
210
+ "wan_lte": {
211
+ "avg_loss": 0,
212
+ "avg_rtt": 35.8,
213
+ "is_alive": true
214
+ }
215
+ },
216
+ "switching_enabled": true
217
+ }
218
+ ]
219
+ }
220
+ ```
221
+
222
+ ```text
223
+ $ llro-cli override --host 1.1.1.1 --route wan_lte
224
+ {"host": "1.1.1.1", "mode": "override", "route": "wan_lte"}
225
+
226
+ $ llro-cli disable-switching --all
227
+ {"hosts": ["1.1.1.1", "8.8.8.8"], "mode": "frozen"}
228
+
229
+ $ llro-cli reset-auto --host 1.1.1.1
230
+ {"hosts": ["1.1.1.1"], "mode": "auto"}
231
+ ```
232
+
233
+ ## systemd service
234
+
235
+ Use the provided unit as a base and verify the executable path in your environment:
236
+
237
+ ```bash
238
+ which llro
239
+ ```
240
+
241
+ Install:
242
+
243
+ ```bash
244
+ sudo cp llro.service /etc/systemd/system/llro.service
245
+ sudo systemctl daemon-reload
246
+ sudo systemctl enable --now llro
247
+ sudo systemctl status llro
248
+ ```
249
+
250
+ ## PyPI release flow
251
+
252
+ - Local dry-run build: `make build`
253
+ - Publish manually with Twine:
254
+ - `make publish-testpypi`
255
+ - `make publish-pypi`
256
+ - GitHub Actions publish:
257
+ - Push tag `v*` to trigger `.github/workflows/publish-to-pypi.yml`
258
+ - Workflow uses trusted publishing (`id-token`) for PyPI
259
+
260
+ ## Contributing
261
+
262
+ Inspired by <https://malaty.net/linux-lowest-latency-routes-optimizer/>
llro-1.0.0/README.md ADDED
@@ -0,0 +1,240 @@
1
+ # LLRO (Lowest Latency Routes Optimizer)
2
+
3
+ Service to measure ICMP latency from multiple uplinks and keep per-host `/32` routes pinned to the best path.
4
+
5
+ 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.
6
+
7
+ For a deeper technical walkthrough, see [HOW_IT_WORKS.md](/home/stefan/github/LowestLatencyRoutesOptimizer/HOW_IT_WORKS.md).
8
+
9
+ ## Runtime requirements
10
+
11
+ - Linux with `iproute2` (`ip` command available, default path `/usr/sbin/ip`)
12
+ - Root privileges or equivalent capabilities (`CAP_NET_ADMIN` and raw ICMP capability)
13
+ - Python `>=3.7`
14
+
15
+ ## Development setup
16
+
17
+ ```bash
18
+ uv sync
19
+ ```
20
+
21
+ ## Run locally
22
+
23
+ ```bash
24
+ uv run llro --config ./config.yml
25
+ ```
26
+
27
+ ## Configuration (recommended model)
28
+
29
+ Start from the example:
30
+
31
+ ```bash
32
+ cp config.example.yml config.yml
33
+ ```
34
+
35
+ Example:
36
+
37
+ ```yaml
38
+ monitor:
39
+ - 1.1.1.1
40
+ - 8.8.8.8
41
+
42
+ routes:
43
+ - name: wan_fiber
44
+ device: eth0
45
+ probe_source: 192.168.0.8
46
+ gateway: 192.168.0.1
47
+ - name: wan_lte
48
+ device: wwan0
49
+ probe_source: 10.0.0.2
50
+ gateway: 10.0.0.1
51
+
52
+ also_route:
53
+ 1.1.1.1:
54
+ - 1.0.0.1
55
+ 8.8.8.8:
56
+ - 8.8.4.4
57
+
58
+ fallback_routes:
59
+ 1.1.1.1: wan_fiber
60
+ 8.8.8.8: wan_lte
61
+
62
+ rtt_threshold: 20
63
+ packet_loss_threshold: 2
64
+ test_count: 5
65
+ test_interval: 1
66
+ scan_interval: 30
67
+ delete_preadded_routes: true
68
+ # ip_bin: /usr/sbin/ip
69
+ # admin_socket_path: /run/llro/admin.sock
70
+ ```
71
+
72
+ ### Key fields
73
+
74
+ - `monitor`: host IPs to probe and route.
75
+ - `routes`: route candidates.
76
+ - `routes[].name`: unique route identifier.
77
+ - `routes[].device`: network device used for route installation.
78
+ - `routes[].probe_source`: source IP used for probing and route `src`.
79
+ - `routes[].gateway`: next-hop gateway for the host route.
80
+ - `also_route`: optional extra IPs that should follow a monitored host route.
81
+ - `fallback_routes`: optional fallback route name per monitored host.
82
+ - `rtt_threshold`: minimum RTT improvement (ms) required before switching.
83
+ - `packet_loss_threshold`: packet-loss threshold (%) that can force switching.
84
+ - `test_count`: number of probe rounds aggregated before routing decisions.
85
+ - `test_interval`: interval between ping packets in a probe run.
86
+ - `scan_interval`: delay between scan cycles.
87
+ - `delete_preadded_routes`: remove existing static `/32` routes for monitored hosts on startup.
88
+ - `ip_bin`: optional `ip` binary path override.
89
+ - `admin_socket_path`: Unix socket path used by `llro-cli` for admin/monitoring.
90
+
91
+ ## Legacy config compatibility
92
+
93
+ The old `interfaces` model is still accepted for now:
94
+
95
+ ```yaml
96
+ interfaces:
97
+ eth0:
98
+ - 192.168.0.8
99
+ ```
100
+
101
+ Compatibility mode maps each `interfaces.<device>.<source>` entry to a generated route candidate:
102
+
103
+ - `name: "<device>:<source>"`
104
+ - `probe_source: <source>`
105
+ - `gateway: <source>` (legacy behavior)
106
+
107
+ `fallback_routes` may reference either route names (new) or legacy source IPs (old).
108
+
109
+ ## Tooling (Make + uv)
110
+
111
+ ```bash
112
+ make validate # format check + lint + typecheck + dead-code scan
113
+ make test # pytest
114
+ make integration-test # dockerized route mutation integration test
115
+ make build # build sdist/wheel + twine metadata check
116
+ ```
117
+
118
+ Auto-fix formatting/lint issues:
119
+
120
+ ```bash
121
+ make fix
122
+ ```
123
+
124
+ Run integration tests directly:
125
+
126
+ ```bash
127
+ RUN_DOCKER_INTEGRATION=1 uv run pytest -m integration
128
+ ```
129
+
130
+ 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.
131
+
132
+ ## Install as CLI
133
+
134
+ From local checkout:
135
+
136
+ ```bash
137
+ uv pip install .
138
+ ```
139
+
140
+ From wheel:
141
+
142
+ ```bash
143
+ uv pip install dist/*.whl
144
+ ```
145
+
146
+ Then run:
147
+
148
+ ```bash
149
+ llro --config /etc/llro.yml
150
+ ```
151
+
152
+ Admin commands (against running daemon):
153
+
154
+ ```bash
155
+ llro-cli status
156
+ llro-cli override --host 1.1.1.1 --route wan_fiber
157
+ llro-cli disable-switching --all
158
+ llro-cli reset-auto --host 1.1.1.1
159
+ ```
160
+
161
+ Example output:
162
+
163
+ ```text
164
+ $ llro-cli status
165
+ Host 1.1.1.1 | mode=auto | switching=yes | current=wan_fiber | override=-
166
+ wan_fiber: rtt=14.2 ms, loss=0%, alive=yes
167
+ wan_lte: rtt=35.8 ms, loss=0%, alive=yes
168
+ Host 8.8.8.8 | mode=frozen | switching=no | current=wan_lte | override=-
169
+ wan_fiber: rtt=48.1 ms, loss=0%, alive=yes
170
+ wan_lte: rtt=31.6 ms, loss=0%, alive=yes
171
+ ```
172
+
173
+ ```text
174
+ $ llro-cli status --json
175
+ {
176
+ "hosts": [
177
+ {
178
+ "current_route": "wan_fiber",
179
+ "host": "1.1.1.1",
180
+ "mode": "auto",
181
+ "override_route": null,
182
+ "routes": {
183
+ "wan_fiber": {
184
+ "avg_loss": 0,
185
+ "avg_rtt": 14.2,
186
+ "is_alive": true
187
+ },
188
+ "wan_lte": {
189
+ "avg_loss": 0,
190
+ "avg_rtt": 35.8,
191
+ "is_alive": true
192
+ }
193
+ },
194
+ "switching_enabled": true
195
+ }
196
+ ]
197
+ }
198
+ ```
199
+
200
+ ```text
201
+ $ llro-cli override --host 1.1.1.1 --route wan_lte
202
+ {"host": "1.1.1.1", "mode": "override", "route": "wan_lte"}
203
+
204
+ $ llro-cli disable-switching --all
205
+ {"hosts": ["1.1.1.1", "8.8.8.8"], "mode": "frozen"}
206
+
207
+ $ llro-cli reset-auto --host 1.1.1.1
208
+ {"hosts": ["1.1.1.1"], "mode": "auto"}
209
+ ```
210
+
211
+ ## systemd service
212
+
213
+ Use the provided unit as a base and verify the executable path in your environment:
214
+
215
+ ```bash
216
+ which llro
217
+ ```
218
+
219
+ Install:
220
+
221
+ ```bash
222
+ sudo cp llro.service /etc/systemd/system/llro.service
223
+ sudo systemctl daemon-reload
224
+ sudo systemctl enable --now llro
225
+ sudo systemctl status llro
226
+ ```
227
+
228
+ ## PyPI release flow
229
+
230
+ - Local dry-run build: `make build`
231
+ - Publish manually with Twine:
232
+ - `make publish-testpypi`
233
+ - `make publish-pypi`
234
+ - GitHub Actions publish:
235
+ - Push tag `v*` to trigger `.github/workflows/publish-to-pypi.yml`
236
+ - Workflow uses trusted publishing (`id-token`) for PyPI
237
+
238
+ ## Contributing
239
+
240
+ Inspired by <https://malaty.net/linux-lowest-latency-routes-optimizer/>
@@ -0,0 +1,92 @@
1
+ [build-system]
2
+ requires = ["setuptools>=65,<83", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "llro"
7
+ version = "1.0.0"
8
+ description = "Service to get lowest latency route to targets and set static routes."
9
+ readme = "README.md"
10
+ requires-python = ">=3.7"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "LLRO contributors" }]
13
+ keywords = ["network", "routing", "latency", "icmp"]
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "Programming Language :: Python :: 3 :: Only",
17
+ "Operating System :: POSIX :: Linux",
18
+ "Topic :: System :: Networking",
19
+ ]
20
+ dependencies = [
21
+ "icmplib==3.0.4",
22
+ "PyYAML==6.0.1; python_version < '3.8'",
23
+ "PyYAML==6.0.3; python_version >= '3.8'",
24
+ ]
25
+
26
+ [project.urls]
27
+ Homepage = "https://github.com/greensec/LowestLatencyRoutesOptimizer"
28
+ Repository = "https://github.com/greensec/LowestLatencyRoutesOptimizer"
29
+ Issues = "https://github.com/greensec/LowestLatencyRoutesOptimizer/issues"
30
+
31
+ [project.scripts]
32
+ llro = "llro:main"
33
+ llro-cli = "llro_cli:main"
34
+
35
+ [tool.setuptools]
36
+ py-modules = ["llro", "llro_cli"]
37
+
38
+ [tool.setuptools.package-dir]
39
+ "" = "src"
40
+
41
+ [dependency-groups]
42
+ dev = [
43
+ "build>=1.2.2; python_version >= '3.8'",
44
+ "pyright>=1.1.408; python_version >= '3.8'",
45
+ "pytest>=7.4,<8; python_version < '3.8'",
46
+ "pytest>=8.3; python_version >= '3.8'",
47
+ "pytest-cov>=4.1,<5; python_version < '3.8'",
48
+ "pytest-cov>=5; python_version >= '3.8'",
49
+ "ruff>=0.15.8",
50
+ "ruff>=0.12.0; python_version >= '3.8'",
51
+ "twine>=6.2.0; python_version >= '3.10'",
52
+ "vulture>=2.14; python_version >= '3.8'",
53
+ "xenon>=0.9.3; python_version >= '3.10'",
54
+ "vulture>=2.9.1",
55
+ "pyright>=1.1.408",
56
+ ]
57
+
58
+ [tool.ruff]
59
+ exclude = ["dist", "build", ".venv"]
60
+ line-length = 120
61
+ indent-width = 4
62
+
63
+ [tool.ruff.format]
64
+ quote-style = "double"
65
+ indent-style = "space"
66
+ line-ending = "lf"
67
+
68
+ [tool.ruff.lint]
69
+ select = ["E", "F", "I"]
70
+ ignore = ["E501"]
71
+
72
+ [tool.pytest.ini_options]
73
+ addopts = [
74
+ "-q",
75
+ "--cov=llro",
76
+ "--cov-report=term-missing",
77
+ ]
78
+ pythonpath = ["src"]
79
+ markers = [
80
+ "integration: real integration tests that require external services/tools",
81
+ ]
82
+
83
+ [tool.pyright]
84
+ pythonVersion = "3.7"
85
+ include = ["src", "tests"]
86
+ exclude = ["**/__pycache__", "**/node_modules", "**/dist", "**/build", "**/.venv"]
87
+
88
+ [tool.vulture]
89
+ exclude = ["dist", "build", "venv", ".venv", "tests"]
90
+ min_confidence = 80
91
+ paths = ["src"]
92
+ sort_by_size = true
llro-1.0.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+