9 Commits
Author SHA1 Message Date
Nils Andreas Svee 7fb744790d fix(reflectors): merge missing country metadata without startup conflicts
Build binaries / Build (aarch64-musl) (push) Successful in 1m54s
Build binaries / Build (arm-musleabi) (push) Successful in 1m54s
Build binaries / Build (arm-musleabihf) (push) Successful in 1m56s
Build binaries / Build (armv7-musleabi) (push) Successful in 1m56s
Build binaries / Build (armv7-musleabihf) (push) Successful in 1m56s
Build binaries / Build (i686-musl) (push) Successful in 1m55s
Build binaries / Build (mips64-muslabi64) (push) Successful in 2m0s
Build binaries / Build (mips-musl) (push) Successful in 2m3s
Build binaries / Build (mipsel-musl) (push) Successful in 2m9s
Build binaries / Build (mips64el-muslabi64) (push) Successful in 2m13s
Build binaries / Build (riscv64gc-musl) (push) Successful in 1m53s
Build binaries / Build (x86_64-musl) (push) Successful in 1m53s
2026-10-13 22:58:15 +07:00
Nils Andreas Svee cb4ae4d251 chore: update reflectors list to use new format 2026-10-13 22:56:25 +07:00
Nils Andreas Svee 93bfee0624 fix(irtt): honor measurement socket routing settings
Build binaries / Build (arm-musleabi) (push) Successful in 1m37s
Build binaries / Build (aarch64-musl) (push) Successful in 1m39s
Build binaries / Build (armv7-musleabi) (push) Successful in 1m44s
Build binaries / Build (arm-musleabihf) (push) Successful in 1m45s
Build binaries / Build (armv7-musleabihf) (push) Successful in 1m52s
Build binaries / Build (i686-musl) (push) Successful in 1m53s
Build binaries / Build (mips64-muslabi64) (push) Successful in 2m19s
Build binaries / Build (mips-musl) (push) Successful in 2m20s
Build binaries / Build (mips64el-muslabi64) (push) Successful in 2m12s
Build binaries / Build (mipsel-musl) (push) Successful in 2m13s
Build binaries / Build (x86_64-musl) (push) Successful in 1m48s
Build binaries / Build (riscv64gc-musl) (push) Successful in 1m50s
2026-10-13 22:03:09 +07:00
Nils Andreas Svee 80b39da9de fix(ratecontroller): use the configured reflector count for reselection 2026-10-13 22:02:58 +07:00
Nils Andreas Svee f8691c5189 fix(reflectors): reject conflicting resolved endpoints
Build binaries / Build (aarch64-musl) (push) Successful in 2m16s
Build binaries / Build (arm-musleabi) (push) Successful in 2m48s
Build binaries / Build (arm-musleabihf) (push) Successful in 2m6s
Build binaries / Build (armv7-musleabi) (push) Successful in 2m8s
Build binaries / Build (armv7-musleabihf) (push) Successful in 2m29s
Build binaries / Build (i686-musl) (push) Successful in 2m34s
Build binaries / Build (mips-musl) (push) Successful in 1m57s
Build binaries / Build (mips64-muslabi64) (push) Successful in 1m58s
Build binaries / Build (mips64el-muslabi64) (push) Successful in 2m0s
Build binaries / Build (mipsel-musl) (push) Successful in 1m53s
Build binaries / Build (riscv64gc-musl) (push) Successful in 2m10s
Build binaries / Build (x86_64-musl) (push) Successful in 2m41s
Deduplicate compatible definitions and reject conflicting metadata or authentication within each list and across bootstrap and pool entries. Treat hostname aliases, protocol ordering, country-code case, and omitted default ports consistently.
2026-10-13 21:24:05 +07:00
Nils Andreas Svee a073a9d049 fix(reflectors): tolerate individual DNS resolution failures
Log and skip unresolved reflectors so startup can proceed with usable endpoints. Report an error when none remain for the selected protocol.
2026-10-13 21:24:05 +07:00
Nils Andreas Svee 78bcb7886a fix(controller): honor the configured ICMP probe interval
Use advanced_settings.tick_interval for Controller schedules so ICMP probe pacing follows the configured measurement cadence.
2026-10-13 21:24:05 +07:00
Nils Andreas Svee de77e5a281 feat(measurement): add configurable IRTT reflector measurements
Support optional IRTT measurements alongside ICMP with CSV and inline reflector definitions, repeated UCI registration, authentication, and endpoint-specific stream tracking.

Keep the managed driver running during target acknowledgements and stop and join it on event-loop exits. Carry stream identity through baselines, selection, and rate control so endpoints sharing an IP remain distinct.
2026-10-13 21:24:05 +07:00
Nils Andreas Svee 76a7e14835 feat(uci): support repeated sections and skipped fields
Allow structured lists to share the existing UCI configuration adapter while keeping collection fields out of scalar section mappings.
2026-10-13 21:24:04 +07:00
18 changed files with 116 additions and 197 deletions

No files matched your search

+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
with: with:
persist-credentials: false persist-credentials: false
- name: Run zizmor - name: Run zizmor
uses: zizmorcore/zizmor-action@85b2b9fb8e3325e7a23d7d0a9d7ba5ae0f999e3e # v0.7.1 uses: zizmorcore/zizmor-action@cc914d7f3750a2d13d75c7f184a1060aa0e9d482 # v0.6.4
with: with:
persona: regular persona: regular
inputs: | inputs: |
+1 -1
View File
@@ -50,7 +50,7 @@ jobs:
echo "exit_code=${clippy_status}" >> "$GITHUB_OUTPUT" echo "exit_code=${clippy_status}" >> "$GITHUB_OUTPUT"
exit 0 exit 0
- name: Upload Clippy SARIF - name: Upload Clippy SARIF
uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4 uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4
if: always() if: always()
with: with:
sarif_file: rust-clippy-results.sarif sarif_file: rust-clippy-results.sarif
Generated
+2 -2
View File
@@ -867,9 +867,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.53.3" version = "1.53.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce3335fa71841cda333a58d7615b03901380ecf09d59b3296d21f8bbac0dde4e" checksum = "e95f91fcc7a621e8b030f6aa23c71fe9838ae2fb4d8118b75602a328f5144044"
dependencies = [ dependencies = [
"bytes", "bytes",
"libc", "libc",
-20
View File
@@ -57,23 +57,3 @@ You can also build an OpenWrt package to install in your images with the SDK:
# Copy the generated .ipk from bin/packages/... to your router # Copy the generated .ipk from bin/packages/... to your router
opkg install sqm-autorate-rust_*.ipk opkg install sqm-autorate-rust_*.ipk
``` ```
## Reflector files and inline UCI reflectors
Inline `config reflector` sections supplement the default
`/etc/sqm-autorate-rust/reflectors.csv`. To use only inline reflectors, explicitly
disable file loading:
```uci
config reflectors
option files 'none'
config reflector
option host 'global.irtt.lochnair.net'
list protocols 'irtt'
```
Use `list files '/path/to/reflectors.csv'` instead of `option files 'none'` to
combine CSV files with inline reflectors. Omitting `files` keeps the default CSV;
UCI discards empty string options, so `option files ''` does not disable it.
Absent or blank `irtt_hmac` values use unauthenticated IRTT sessions.
-2
View File
@@ -82,7 +82,6 @@ fn expand_uci_section(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStre
let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl(); let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
Ok(quote! { Ok(quote! {
#[cfg(all(feature = "uci", unix))]
#[automatically_derived] #[automatically_derived]
impl #impl_generics impl #impl_generics
crate::settings::uci_schema::UciSectionSchema crate::settings::uci_schema::UciSectionSchema
@@ -156,7 +155,6 @@ fn expand_uci_config(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStrea
let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl(); let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
Ok(quote! { Ok(quote! {
#[cfg(all(feature = "uci", unix))]
#[automatically_derived] #[automatically_derived]
impl #impl_generics impl #impl_generics
crate::settings::uci_schema::UciConfigSchema crate::settings::uci_schema::UciConfigSchema
+47 -43
View File
@@ -1,41 +1,45 @@
host,country,protocols,irtt_port,irtt_hmac host,country,protocols,irtt_port,irtt_hmac
94.140.14.14,,icmp-echo|icmp-timestamps,, 94.140.14.14,CY,icmp-echo|icmp-timestamps,,
94.140.15.15,,icmp-echo|icmp-timestamps,, 94.140.15.15,CY,icmp-echo|icmp-timestamps,,
94.140.14.15,,icmp-echo|icmp-timestamps,, 94.140.14.15,CY,icmp-echo|icmp-timestamps,,
94.140.15.16,,icmp-echo|icmp-timestamps,, 94.140.15.16,CY,icmp-echo|icmp-timestamps,,
94.140.14.140,,icmp-echo|icmp-timestamps,, 94.140.14.140,CY,icmp-echo|icmp-timestamps,,
94.140.14.141,,icmp-echo|icmp-timestamps,, 94.140.14.141,CY,icmp-echo|icmp-timestamps,,
205.171.3.65,,icmp-echo|icmp-timestamps,, 205.171.3.65,US,icmp-echo|icmp-timestamps,,
205.171.2.65,,icmp-echo|icmp-timestamps,, 205.171.2.65,US,icmp-echo|icmp-timestamps,,
185.228.168.9,,icmp-echo|icmp-timestamps,, 185.228.168.9,US,icmp-echo|icmp-timestamps,,
185.228.169.9,,icmp-echo|icmp-timestamps,, 185.228.169.9,US,icmp-echo|icmp-timestamps,,
185.228.168.168,,icmp-echo|icmp-timestamps,, 185.228.168.168,US,icmp-echo|icmp-timestamps,,
185.228.169.168,,icmp-echo|icmp-timestamps,, 185.228.169.168,US,icmp-echo|icmp-timestamps,,
185.228.168.10,,icmp-echo|icmp-timestamps,, 185.228.168.10,US,icmp-echo|icmp-timestamps,,
185.228.169.11,,icmp-echo|icmp-timestamps,, 185.228.169.11,US,icmp-echo|icmp-timestamps,,
8.26.56.26,,icmp-echo|icmp-timestamps,, 8.26.56.26,US,icmp-echo|icmp-timestamps,,
8.20.247.20,,icmp-echo|icmp-timestamps,, 8.20.247.20,US,icmp-echo|icmp-timestamps,,
84.200.69.80,DE,icmp-echo|icmp-timestamps,, 84.200.69.80,DE,icmp-echo|icmp-timestamps,,
84.200.70.40,DE,icmp-echo|icmp-timestamps,, 84.200.70.40,DE,icmp-echo|icmp-timestamps,,
46.227.200.54,GB,icmp-echo|icmp-timestamps,, 46.227.200.54,GB,icmp-echo|icmp-timestamps,,
46.227.200.55,,icmp-echo|icmp-timestamps,, 46.227.200.55,GB,icmp-echo|icmp-timestamps,,
45.33.97.5,US,icmp-echo|icmp-timestamps,, 45.33.97.5,US,icmp-echo|icmp-timestamps,,
37.235.1.177,AT,icmp-echo|icmp-timestamps,, 37.235.1.177,AT,icmp-echo|icmp-timestamps,,
74.82.42.42,,icmp-echo|icmp-timestamps,, 74.82.42.42,US,icmp-echo|icmp-timestamps,,
208.67.222.222,,icmp-echo|icmp-timestamps,, 194.242.2.2,US,icmp-echo|icmp-timestamps,,
208.67.220.220,,icmp-echo|icmp-timestamps,, 194.242.2.3,US,icmp-echo|icmp-timestamps,,
208.67.222.123,,icmp-echo|icmp-timestamps,, 193.19.108.2,US,icmp-echo|icmp-timestamps,,
208.67.220.123,,icmp-echo|icmp-timestamps,, 193.19.108.3,US,icmp-echo|icmp-timestamps,,
208.67.222.2,,icmp-echo|icmp-timestamps,, 208.67.222.222,US,icmp-echo|icmp-timestamps,,
208.67.220.2,,icmp-echo|icmp-timestamps,, 208.67.220.220,US,icmp-echo|icmp-timestamps,,
208.67.222.123,US,icmp-echo|icmp-timestamps,,
208.67.220.123,US,icmp-echo|icmp-timestamps,,
208.67.222.2,US,icmp-echo|icmp-timestamps,,
208.67.220.2,US,icmp-echo|icmp-timestamps,,
159.89.120.99,CA,icmp-echo|icmp-timestamps,, 159.89.120.99,CA,icmp-echo|icmp-timestamps,,
134.195.4.2,,icmp-echo|icmp-timestamps,, 134.195.4.2,US,icmp-echo|icmp-timestamps,,
9.9.9.9,,icmp-echo|icmp-timestamps,, 9.9.9.9,US,icmp-echo|icmp-timestamps,,
9.9.9.10,,icmp-echo|icmp-timestamps,, 9.9.9.10,US,icmp-echo|icmp-timestamps,,
9.9.9.11,,icmp-echo|icmp-timestamps,, 9.9.9.11,US,icmp-echo|icmp-timestamps,,
149.112.112.10,,icmp-echo|icmp-timestamps,, 149.112.112.10,US,icmp-echo|icmp-timestamps,,
149.112.112.11,,icmp-echo|icmp-timestamps,, 149.112.112.11,US,icmp-echo|icmp-timestamps,,
149.112.112.112,,icmp-echo|icmp-timestamps,, 149.112.112.112,US,icmp-echo|icmp-timestamps,,
176.9.93.198,DE,icmp-echo|icmp-timestamps,, 176.9.93.198,DE,icmp-echo|icmp-timestamps,,
176.9.1.117,DE,icmp-echo|icmp-timestamps,, 176.9.1.117,DE,icmp-echo|icmp-timestamps,,
104.236.210.29,US,icmp-echo|icmp-timestamps,, 104.236.210.29,US,icmp-echo|icmp-timestamps,,
@@ -54,11 +58,11 @@ host,country,protocols,irtt_port,irtt_hmac
140.238.215.192,CH,icmp-echo|icmp-timestamps,, 140.238.215.192,CH,icmp-echo|icmp-timestamps,,
199.58.81.218,CA,icmp-echo|icmp-timestamps,, 199.58.81.218,CA,icmp-echo|icmp-timestamps,,
146.255.56.98,AT,icmp-echo|icmp-timestamps,, 146.255.56.98,AT,icmp-echo|icmp-timestamps,,
185.222.222.222,,icmp-echo|icmp-timestamps,, 185.222.222.222,DE,icmp-echo|icmp-timestamps,,
185.184.222.222,NL,icmp-echo|icmp-timestamps,, 185.184.222.222,NL,icmp-echo|icmp-timestamps,,
5.1.66.255,AT,icmp-echo|icmp-timestamps,, 5.1.66.255,AT,icmp-echo|icmp-timestamps,,
80.80.80.80,,icmp-echo|icmp-timestamps,, 80.80.80.80,SM,icmp-echo|icmp-timestamps,,
80.80.81.81,,icmp-echo|icmp-timestamps,, 80.80.81.81,SM,icmp-echo|icmp-timestamps,,
80.67.169.12,FR,icmp-echo|icmp-timestamps,, 80.67.169.12,FR,icmp-echo|icmp-timestamps,,
80.67.169.40,FR,icmp-echo|icmp-timestamps,, 80.67.169.40,FR,icmp-echo|icmp-timestamps,,
217.24.255.134,AL,icmp-echo|icmp-timestamps,, 217.24.255.134,AL,icmp-echo|icmp-timestamps,,
@@ -75,10 +79,10 @@ host,country,protocols,irtt_port,irtt_hmac
45.133.105.123,AM,icmp-echo|icmp-timestamps,, 45.133.105.123,AM,icmp-echo|icmp-timestamps,,
61.8.0.113,AU,icmp-echo|icmp-timestamps,, 61.8.0.113,AU,icmp-echo|icmp-timestamps,,
103.224.162.40,AU,icmp-echo|icmp-timestamps,, 103.224.162.40,AU,icmp-echo|icmp-timestamps,,
139.130.4.4,,icmp-echo|icmp-timestamps,, 139.130.4.4,AU,icmp-echo|icmp-timestamps,,
139.134.5.51,AU,icmp-echo|icmp-timestamps,, 139.134.5.51,AU,icmp-echo|icmp-timestamps,,
139.134.2.190,AU,icmp-echo|icmp-timestamps,, 139.134.2.190,AU,icmp-echo|icmp-timestamps,,
203.50.2.71,,icmp-echo|icmp-timestamps,, 203.50.2.71,AU,icmp-echo|icmp-timestamps,,
83.137.41.8,AT,icmp-echo|icmp-timestamps,, 83.137.41.8,AT,icmp-echo|icmp-timestamps,,
83.137.41.9,AT,icmp-echo|icmp-timestamps,, 83.137.41.9,AT,icmp-echo|icmp-timestamps,,
194.36.144.87,DE,icmp-echo|icmp-timestamps,, 194.36.144.87,DE,icmp-echo|icmp-timestamps,,
@@ -120,10 +124,10 @@ host,country,protocols,irtt_port,irtt_hmac
194.172.160.4,DE,icmp-echo|icmp-timestamps,, 194.172.160.4,DE,icmp-echo|icmp-timestamps,,
185.233.106.232,DE,icmp-echo|icmp-timestamps,, 185.233.106.232,DE,icmp-echo|icmp-timestamps,,
185.233.107.4,DE,icmp-echo|icmp-timestamps,, 185.233.107.4,DE,icmp-echo|icmp-timestamps,,
141.1.1.1,,icmp-echo|icmp-timestamps,, 141.1.1.1,GB,icmp-echo|icmp-timestamps,,
141.1.27.249,,icmp-echo|icmp-timestamps,, 141.1.27.249,GB,icmp-echo|icmp-timestamps,,
176.103.130.130,,icmp-echo|icmp-timestamps,, 176.103.130.130,CY,icmp-echo|icmp-timestamps,,
176.103.130.131,,icmp-echo|icmp-timestamps,, 176.103.130.131,CY,icmp-echo|icmp-timestamps,,
80.243.64.67,RU,icmp-echo|icmp-timestamps,, 80.243.64.67,RU,icmp-echo|icmp-timestamps,,
195.218.133.88,RU,icmp-echo|icmp-timestamps,, 195.218.133.88,RU,icmp-echo|icmp-timestamps,,
83.69.120.1,RU,icmp-echo|icmp-timestamps,, 83.69.120.1,RU,icmp-echo|icmp-timestamps,,
@@ -150,7 +154,7 @@ host,country,protocols,irtt_port,irtt_hmac
45.77.61.165,FR,icmp-echo|icmp-timestamps,, 45.77.61.165,FR,icmp-echo|icmp-timestamps,,
178.62.57.141,GB,icmp-echo|icmp-timestamps,, 178.62.57.141,GB,icmp-echo|icmp-timestamps,,
194.145.240.6,GB,icmp-echo|icmp-timestamps,, 194.145.240.6,GB,icmp-echo|icmp-timestamps,,
195.27.1.1,,icmp-echo|icmp-timestamps,, 195.27.1.1,GB,icmp-echo|icmp-timestamps,,
81.17.66.13,GB,icmp-echo|icmp-timestamps,, 81.17.66.13,GB,icmp-echo|icmp-timestamps,,
87.117.196.200,GB,icmp-echo|icmp-timestamps,, 87.117.196.200,GB,icmp-echo|icmp-timestamps,,
213.123.251.87,GB,icmp-echo|icmp-timestamps,, 213.123.251.87,GB,icmp-echo|icmp-timestamps,,
@@ -175,7 +179,7 @@ host,country,protocols,irtt_port,irtt_hmac
46.246.29.69,SE,icmp-echo|icmp-timestamps,, 46.246.29.69,SE,icmp-echo|icmp-timestamps,,
203.210.237.165,VN,icmp-echo|icmp-timestamps,, 203.210.237.165,VN,icmp-echo|icmp-timestamps,,
113.190.253.229,VN,icmp-echo|icmp-timestamps,, 113.190.253.229,VN,icmp-echo|icmp-timestamps,,
119.29.29.29,,icmp-echo|icmp-timestamps,, 119.29.29.29,CN,icmp-echo|icmp-timestamps,,
209.193.4.7,US,icmp-echo|icmp-timestamps,, 209.193.4.7,US,icmp-echo|icmp-timestamps,,
209.112.160.2,US,icmp-echo|icmp-timestamps,, 209.112.160.2,US,icmp-echo|icmp-timestamps,,
216.68.4.10,US,icmp-echo|icmp-timestamps,, 216.68.4.10,US,icmp-echo|icmp-timestamps,,
@@ -299,7 +303,7 @@ host,country,protocols,irtt_port,irtt_hmac
116.193.190.164,ID,icmp-echo|icmp-timestamps,, 116.193.190.164,ID,icmp-echo|icmp-timestamps,,
103.56.148.179,ID,icmp-echo|icmp-timestamps,, 103.56.148.179,ID,icmp-echo|icmp-timestamps,,
8.215.26.147,ID,icmp-echo|icmp-timestamps,, 8.215.26.147,ID,icmp-echo|icmp-timestamps,,
global.irtt.lochnair.net,,irtt,2112, global.irtt.lochnair.net,SI,irtt,2112,
uk-cov1.irtt.lochnair.net,GB,irtt,2112, uk-cov1.irtt.lochnair.net,GB,irtt,2112,
uk-cov2.irtt.lochnair.net,GB,irtt,2112, uk-cov2.irtt.lochnair.net,GB,irtt,2112,
de-fra.irtt.lochnair.net,DE,irtt,2112, de-fra.irtt.lochnair.net,DE,irtt,2112,
1 host country protocols irtt_port irtt_hmac
2 94.140.14.14 CY icmp-echo|icmp-timestamps
3 94.140.15.15 CY icmp-echo|icmp-timestamps
4 94.140.14.15 CY icmp-echo|icmp-timestamps
5 94.140.15.16 CY icmp-echo|icmp-timestamps
6 94.140.14.140 CY icmp-echo|icmp-timestamps
7 94.140.14.141 CY icmp-echo|icmp-timestamps
8 205.171.3.65 US icmp-echo|icmp-timestamps
9 205.171.2.65 US icmp-echo|icmp-timestamps
10 185.228.168.9 US icmp-echo|icmp-timestamps
11 185.228.169.9 US icmp-echo|icmp-timestamps
12 185.228.168.168 US icmp-echo|icmp-timestamps
13 185.228.169.168 US icmp-echo|icmp-timestamps
14 185.228.168.10 US icmp-echo|icmp-timestamps
15 185.228.169.11 US icmp-echo|icmp-timestamps
16 8.26.56.26 US icmp-echo|icmp-timestamps
17 8.20.247.20 US icmp-echo|icmp-timestamps
18 84.200.69.80 DE icmp-echo|icmp-timestamps
19 84.200.70.40 DE icmp-echo|icmp-timestamps
20 46.227.200.54 GB icmp-echo|icmp-timestamps
21 46.227.200.55 GB icmp-echo|icmp-timestamps
22 45.33.97.5 US icmp-echo|icmp-timestamps
23 37.235.1.177 AT icmp-echo|icmp-timestamps
24 74.82.42.42 US icmp-echo|icmp-timestamps
25 208.67.222.222 194.242.2.2 US icmp-echo|icmp-timestamps
26 208.67.220.220 194.242.2.3 US icmp-echo|icmp-timestamps
27 208.67.222.123 193.19.108.2 US icmp-echo|icmp-timestamps
28 208.67.220.123 193.19.108.3 US icmp-echo|icmp-timestamps
29 208.67.222.2 208.67.222.222 US icmp-echo|icmp-timestamps
30 208.67.220.2 208.67.220.220 US icmp-echo|icmp-timestamps
31 208.67.222.123 US icmp-echo|icmp-timestamps
32 208.67.220.123 US icmp-echo|icmp-timestamps
33 208.67.222.2 US icmp-echo|icmp-timestamps
34 208.67.220.2 US icmp-echo|icmp-timestamps
35 159.89.120.99 CA icmp-echo|icmp-timestamps
36 134.195.4.2 US icmp-echo|icmp-timestamps
37 9.9.9.9 US icmp-echo|icmp-timestamps
38 9.9.9.10 US icmp-echo|icmp-timestamps
39 9.9.9.11 US icmp-echo|icmp-timestamps
40 149.112.112.10 US icmp-echo|icmp-timestamps
41 149.112.112.11 US icmp-echo|icmp-timestamps
42 149.112.112.112 US icmp-echo|icmp-timestamps
43 176.9.93.198 DE icmp-echo|icmp-timestamps
44 176.9.1.117 DE icmp-echo|icmp-timestamps
45 104.236.210.29 US icmp-echo|icmp-timestamps
58 140.238.215.192 CH icmp-echo|icmp-timestamps
59 199.58.81.218 CA icmp-echo|icmp-timestamps
60 146.255.56.98 AT icmp-echo|icmp-timestamps
61 185.222.222.222 DE icmp-echo|icmp-timestamps
62 185.184.222.222 NL icmp-echo|icmp-timestamps
63 5.1.66.255 AT icmp-echo|icmp-timestamps
64 80.80.80.80 SM icmp-echo|icmp-timestamps
65 80.80.81.81 SM icmp-echo|icmp-timestamps
66 80.67.169.12 FR icmp-echo|icmp-timestamps
67 80.67.169.40 FR icmp-echo|icmp-timestamps
68 217.24.255.134 AL icmp-echo|icmp-timestamps
79 45.133.105.123 AM icmp-echo|icmp-timestamps
80 61.8.0.113 AU icmp-echo|icmp-timestamps
81 103.224.162.40 AU icmp-echo|icmp-timestamps
82 139.130.4.4 AU icmp-echo|icmp-timestamps
83 139.134.5.51 AU icmp-echo|icmp-timestamps
84 139.134.2.190 AU icmp-echo|icmp-timestamps
85 203.50.2.71 AU icmp-echo|icmp-timestamps
86 83.137.41.8 AT icmp-echo|icmp-timestamps
87 83.137.41.9 AT icmp-echo|icmp-timestamps
88 194.36.144.87 DE icmp-echo|icmp-timestamps
124 194.172.160.4 DE icmp-echo|icmp-timestamps
125 185.233.106.232 DE icmp-echo|icmp-timestamps
126 185.233.107.4 DE icmp-echo|icmp-timestamps
127 141.1.1.1 GB icmp-echo|icmp-timestamps
128 141.1.27.249 GB icmp-echo|icmp-timestamps
129 176.103.130.130 CY icmp-echo|icmp-timestamps
130 176.103.130.131 CY icmp-echo|icmp-timestamps
131 80.243.64.67 RU icmp-echo|icmp-timestamps
132 195.218.133.88 RU icmp-echo|icmp-timestamps
133 83.69.120.1 RU icmp-echo|icmp-timestamps
154 45.77.61.165 FR icmp-echo|icmp-timestamps
155 178.62.57.141 GB icmp-echo|icmp-timestamps
156 194.145.240.6 GB icmp-echo|icmp-timestamps
157 195.27.1.1 GB icmp-echo|icmp-timestamps
158 81.17.66.13 GB icmp-echo|icmp-timestamps
159 87.117.196.200 GB icmp-echo|icmp-timestamps
160 213.123.251.87 GB icmp-echo|icmp-timestamps
179 46.246.29.69 SE icmp-echo|icmp-timestamps
180 203.210.237.165 VN icmp-echo|icmp-timestamps
181 113.190.253.229 VN icmp-echo|icmp-timestamps
182 119.29.29.29 CN icmp-echo|icmp-timestamps
183 209.193.4.7 US icmp-echo|icmp-timestamps
184 209.112.160.2 US icmp-echo|icmp-timestamps
185 216.68.4.10 US icmp-echo|icmp-timestamps
303 116.193.190.164 ID icmp-echo|icmp-timestamps
304 103.56.148.179 ID icmp-echo|icmp-timestamps
305 8.215.26.147 ID icmp-echo|icmp-timestamps
306 global.irtt.lochnair.net SI irtt 2112
307 uk-cov1.irtt.lochnair.net GB irtt 2112
308 uk-cov2.irtt.lochnair.net GB irtt 2112
309 de-fra.irtt.lochnair.net DE irtt 2112
-1
View File
@@ -289,7 +289,6 @@ mod tests {
}, },
started_at: received_at, started_at: received_at,
observed_at: received_at, observed_at: received_at,
round_trip: None,
one_way: Some(OneWayLatency { one_way: Some(OneWayLatency {
uplink: SignedDuration::from_millis(up_time as i64), uplink: SignedDuration::from_millis(up_time as i64),
downlink: SignedDuration::from_millis(down_time as i64), downlink: SignedDuration::from_millis(down_time as i64),
+3 -1
View File
@@ -120,7 +120,9 @@ impl Controller {
icmp.push(IcmpBinding::new(stream, schedule)) icmp.push(IcmpBinding::new(stream, schedule))
} }
#[cfg(feature = "irtt")] #[cfg(feature = "irtt")]
MeasurementSource::Irtt => irtt.push(IrttBinding::new(stream, port, hmac)), MeasurementSource::Irtt => {
irtt.push(IrttBinding::new(stream, schedule, port, hmac))
}
#[cfg(not(feature = "irtt"))] #[cfg(not(feature = "irtt"))]
MeasurementSource::Irtt => unreachable!("IRTT feature is checked before startup"), MeasurementSource::Irtt => unreachable!("IRTT feature is checked before startup"),
_ => unreachable!("unsupported reflector measurement source"), _ => unreachable!("unsupported reflector measurement source"),
+3 -2
View File
@@ -161,8 +161,8 @@ fn resolve_reflectors(
} }
if filter_country if filter_country
&& !countries.is_empty() && !countries.is_empty()
&& definition.country.as_ref().is_some_and(|country| { && !definition.country.as_ref().is_some_and(|country| {
!countries countries
.iter() .iter()
.any(|allowed| allowed.eq_ignore_ascii_case(country)) .any(|allowed| allowed.eq_ignore_ascii_case(country))
}) })
@@ -389,6 +389,7 @@ fn run(settings: &Settings) -> anyhow::Result<()> {
let (irtt_task, irtt_engine) = { let (irtt_task, irtt_engine) = {
let _guard = rt.enter(); let _guard = rt.enter();
IrttEngine::new( IrttEngine::new(
start_time,
Duration::from_secs_f64(settings.advanced_settings.tick_interval), Duration::from_secs_f64(settings.advanced_settings.tick_interval),
&settings.network, &settings.network,
measurement_tx, measurement_tx,
-2
View File
@@ -326,7 +326,6 @@ impl IcmpEngine {
stream: probe.stream, stream: probe.stream,
started_at: probe.sent_at, started_at: probe.sent_at,
observed_at, observed_at,
round_trip: None,
one_way: None, one_way: None,
}) })
} }
@@ -386,7 +385,6 @@ impl IcmpEngine {
stream: probe.stream, stream: probe.stream,
started_at: probe.sent_at, started_at: probe.sent_at,
observed_at, observed_at,
round_trip: None,
one_way: Some(OneWayLatency { one_way: Some(OneWayLatency {
uplink: SignedDuration::from_millis(uplink_ms), uplink: SignedDuration::from_millis(uplink_ms),
downlink: SignedDuration::from_millis(downlink_ms), downlink: SignedDuration::from_millis(downlink_ms),
+26 -30
View File
@@ -1,4 +1,4 @@
use std::{collections::HashMap, net::SocketAddr}; use std::{collections::HashMap, net::SocketAddr, time::Instant};
use flume::{Receiver, Sender}; use flume::{Receiver, Sender};
use irtt_client::{ use irtt_client::{
@@ -7,7 +7,7 @@ use irtt_client::{
ManagedClient, ManagedClientConfig, ManagedClientHandle, ManagedClientTask, ManagedClient, ManagedClientConfig, ManagedClientHandle, ManagedClientTask,
ManagedCommandApplyError, ManagedCommandError, ManagedCompletionPolicy, ManagedConfigError, ManagedCommandApplyError, ManagedCommandError, ManagedCompletionPolicy, ManagedConfigError,
ManagedEndReason, ManagedEvent, ManagedLifecycle, ManagedSubscribeError, ManagedEndReason, ManagedEvent, ManagedLifecycle, ManagedSubscribeError,
ManagedTargetConfig, ManagedTargetEndReason, ManagedTargetLifecycle, TargetAuth, TargetId, ManagedTargetConfig, ManagedTargetEndReason, TargetAuth, TargetId,
}, },
}; };
use log::{debug, trace, warn}; use log::{debug, trace, warn};
@@ -17,7 +17,7 @@ use tokio::sync::broadcast::error::RecvError;
use crate::SHUTDOWN; use crate::SHUTDOWN;
use crate::measurement::model::{ use crate::measurement::model::{
MeasurementEvent, MeasurementLoss, MeasurementObservation, MeasurementStream, MeasurementEvent, MeasurementLoss, MeasurementObservation, MeasurementStream,
MeasurementStreamId, OneWayClock, OneWayLatency, SignedDuration, MeasurementStreamId, OneWayClock, OneWayLatency, ProbeSchedule, SignedDuration,
}; };
use crate::settings::NetworkSettings; use crate::settings::NetworkSettings;
@@ -53,13 +53,27 @@ pub enum IrttError {
pub struct IrttBinding { pub struct IrttBinding {
stream: MeasurementStream, stream: MeasurementStream,
/// Retained for future hybrid-mode scheduling. IRTT currently uses the
/// global interval configured on its managed client.
#[allow(dead_code)]
schedule: ProbeSchedule,
port: u16, port: u16,
hmac: Option<String>, hmac: Option<String>,
} }
impl IrttBinding { impl IrttBinding {
pub fn new(stream: MeasurementStream, port: u16, hmac: Option<String>) -> Self { pub fn new(
Self { stream, port, hmac } stream: MeasurementStream,
schedule: ProbeSchedule,
port: u16,
hmac: Option<String>,
) -> Self {
Self {
stream,
schedule,
port,
hmac,
}
} }
} }
@@ -67,7 +81,8 @@ pub struct IrttEngine {
client: ManagedClientHandle, client: ManagedClientHandle,
bindings: HashMap<MeasurementStreamId, IrttBinding>, bindings: HashMap<MeasurementStreamId, IrttBinding>,
targets: Vec<ManagedTargetConfig>,
origin: Instant,
binding_rx: Receiver<Vec<IrttBinding>>, binding_rx: Receiver<Vec<IrttBinding>>,
observation_tx: Sender<MeasurementEvent>, observation_tx: Sender<MeasurementEvent>,
@@ -75,6 +90,7 @@ pub struct IrttEngine {
impl IrttEngine { impl IrttEngine {
pub fn new( pub fn new(
origin: Instant,
interval: std::time::Duration, interval: std::time::Duration,
settings: &NetworkSettings, settings: &NetworkSettings,
observation_tx: Sender<MeasurementEvent>, observation_tx: Sender<MeasurementEvent>,
@@ -88,7 +104,7 @@ impl IrttEngine {
Self { Self {
client: handle, client: handle,
bindings: HashMap::new(), bindings: HashMap::new(),
targets: Vec::new(), origin,
binding_rx, binding_rx,
observation_tx, observation_tx,
}, },
@@ -103,17 +119,16 @@ impl IrttEngine {
TargetId::new(binding.stream.id.0.to_string()), TargetId::new(binding.stream.id.0.to_string()),
SocketAddr::new(binding.stream.peer, binding.port).to_string(), SocketAddr::new(binding.stream.peer, binding.port).to_string(),
); );
if let Some(key) = binding.hmac.as_ref().filter(|key| !key.trim().is_empty()) { if let Some(key) = &binding.hmac {
target.auth = TargetAuth::Override(irtt_client::Authentication::Hmac( target.auth = TargetAuth::Override(irtt_client::Authentication::Hmac(
irtt_client::HmacKey::new(key.as_bytes().to_vec().into_boxed_slice()), irtt_client::HmacKey::new(key.as_bytes().to_vec().into_boxed_slice()),
)); ));
} }
target target
}) })
.collect::<Vec<_>>(); .collect();
self.client.update_targets(targets.clone())?.await?; self.client.update_targets(targets)?.await?;
self.targets = targets;
self.bindings = bindings self.bindings = bindings
.into_iter() .into_iter()
@@ -151,11 +166,6 @@ impl IrttEngine {
async fn run_events(&mut self) -> Result<(), IrttError> { async fn run_events(&mut self) -> Result<(), IrttError> {
let mut events = self.client.subscribe()?; let mut events = self.client.subscribe()?;
// Durable status catches failures even if their presentation events were lost.
// Resubmitting the full desired set restarts terminal targets only.
let mut retry = tokio::time::interval(std::time::Duration::from_secs(5));
retry.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
retry.tick().await;
loop { loop {
tokio::select! { tokio::select! {
bindings = self.binding_rx.recv_async() => { bindings = self.binding_rx.recv_async() => {
@@ -165,17 +175,6 @@ impl IrttEngine {
self.set_bindings(bindings).await?; self.set_bindings(bindings).await?;
} }
_ = retry.tick() => {
if SHUTDOWN.load(std::sync::atomic::Ordering::Relaxed) {
return Ok(());
}
if self.client.status().targets.iter().any(|target| {
target.desired && target.lifecycle == ManagedTargetLifecycle::Terminal
}) {
self.client.update_targets(self.targets.clone())?.await?;
}
}
event = events.recv() => { event = events.recv() => {
let event = match event { let event = match event {
Ok(event) => event, Ok(event) => event,
@@ -256,7 +255,6 @@ impl IrttEngine {
sent_at, sent_at,
received_at, received_at,
one_way, one_way,
rtt,
.. ..
} => { } => {
let Some(stream) = stream else { let Some(stream) = stream else {
@@ -267,8 +265,6 @@ impl IrttEngine {
stream, stream,
started_at: sent_at.mono, started_at: sent_at.mono,
observed_at: received_at.mono, observed_at: received_at.mono,
round_trip: i64::try_from(rtt.effective.as_nanos())
.ok().map(SignedDuration::from_nanos),
one_way: one_way.and_then(map_one_way), one_way: one_way.and_then(map_one_way),
}; };
+1 -7
View File
@@ -121,9 +121,6 @@ pub struct MeasurementObservation {
/// Local monotonic time at which the result was observed. /// Local monotonic time at which the result was observed.
pub observed_at: Instant, pub observed_at: Instant,
/// Measured round-trip latency when it differs from elapsed local time.
pub round_trip: Option<SignedDuration>,
/// Directional timing, when the measurement mechanism can provide it. /// Directional timing, when the measurement mechanism can provide it.
pub one_way: Option<OneWayLatency>, pub one_way: Option<OneWayLatency>,
} }
@@ -134,10 +131,7 @@ impl MeasurementObservation {
} }
pub fn rtt_ms(&self) -> f64 { pub fn rtt_ms(&self) -> f64 {
self.round_trip self.rtt().as_secs_f64() * 1000.0
.map(SignedDuration::as_secs_f64)
.unwrap_or_else(|| self.rtt().as_secs_f64())
* 1000.0
} }
} }
+2 -5
View File
@@ -491,11 +491,8 @@ impl<S: InterfaceStatsProvider, T: TrafficControlBackend> Ratecontroller<S, T> {
self.state_dl.deltas = down_deltas; self.state_dl.deltas = down_deltas;
self.state_ul.deltas = up_deltas; self.state_ul.deltas = up_deltas;
let minimum = usize::from(self.settings.reflectors.num_reflectors).min(reflectors.len()); let minimum = usize::from(self.settings.reflectors.num_reflectors);
if reflectors.is_empty() if self.state_dl.deltas.len() < minimum || self.state_ul.deltas.len() < minimum {
|| self.state_dl.deltas.len() < minimum
|| self.state_ul.deltas.len() < minimum
{
// trigger reselection // trigger reselection
warn!( warn!(
"Not enough delta values (D: {}, U: {}, need {}), triggering reselection", "Not enough delta values (D: {}, U: {}, need {}), triggering reselection",
+6 -10
View File
@@ -252,21 +252,17 @@ impl ReflectorSelector {
candidates.swap(i, j); candidates.swap(i, j);
} }
if candidates.is_empty() { if (candidates.len() as u8) < num_reflectors {
info!("No usable reflectors, retaining current set"); info!(
"Only {} usable reflectors, retaining current set of {}",
candidates.len(),
num_reflectors
);
self.reflector_tx.send(current_peers.clone())?; self.reflector_tx.send(current_peers.clone())?;
*reflectors_peers = current_peers; *reflectors_peers = current_peers;
continue; continue;
} }
if (candidates.len() as u8) < num_reflectors {
info!(
"Only {} usable reflectors, selecting available candidates",
candidates.len()
);
}
let num_reflectors = num_reflectors.min(candidates.len() as u8);
let mut new_peers = Vec::new(); let mut new_peers = Vec::new();
for i in 0..num_reflectors { for i in 0..num_reflectors {
let peer = candidates[i as usize].0; let peer = candidates[i as usize].0;
+4 -54
View File
@@ -2,10 +2,12 @@
// //
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
use anyhow::{Context, bail};
use config::{Config, ConfigError, Environment}; use config::{Config, ConfigError, Environment};
use log::Level; use log::Level;
use serde::{Deserialize, Deserializer, de}; use serde::{Deserialize, Deserializer, de};
use std::fmt::{self, Display}; use std::fmt::{self, Display};
use std::net::IpAddr;
use std::str::FromStr; use std::str::FromStr;
use uci_derive::{UciConfig, UciSection}; use uci_derive::{UciConfig, UciSection};
@@ -15,7 +17,6 @@ use crate::settings::uci::UciSource;
#[cfg(all(feature = "uci", unix))] #[cfg(all(feature = "uci", unix))]
pub mod uci; pub mod uci;
#[cfg(all(feature = "uci", unix))]
pub(crate) mod uci_schema; pub(crate) mod uci_schema;
mod validation; mod validation;
@@ -46,7 +47,7 @@ where
type Value = bool; type Value = bool;
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("a boolean, integer 0 or 1, or a UCI-compatible boolean string") formatter.write_str("a boolean or a UCI-compatible boolean string")
} }
fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E> fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E>
@@ -56,28 +57,6 @@ where
Ok(value) Ok(value)
} }
fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
where
E: de::Error,
{
match value {
0 => Ok(false),
1 => Ok(true),
_ => Err(E::custom("boolean integer must be 0 or 1")),
}
}
fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
where
E: de::Error,
{
match value {
0 => Ok(false),
1 => Ok(true),
_ => Err(E::custom("boolean integer must be 0 or 1")),
}
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where where
E: de::Error, E: de::Error,
@@ -221,14 +200,7 @@ impl Settings {
config::File::with_name("/etc/sqm-autorate-rust/config.toml").required(false), config::File::with_name("/etc/sqm-autorate-rust/config.toml").required(false),
); );
let builder = builder.add_source( let builder = builder.add_source(Environment::with_prefix("SQMA").separator("__"));
Environment::with_prefix("SQMA")
.separator("__")
.try_parsing(true)
.list_separator(",")
.with_list_parse_key("reflectors.files")
.with_list_parse_key("reflectors.countries"),
);
let mut settings: Self = builder.build()?.try_deserialize()?; let mut settings: Self = builder.build()?.try_deserialize()?;
settings.reflectors.load_files()?; settings.reflectors.load_files()?;
@@ -269,7 +241,6 @@ impl NetworkSettings {
#[derive(Clone, Debug, Deserialize, UciSection)] #[derive(Clone, Debug, Deserialize, UciSection)]
#[serde(default)] #[serde(default)]
pub(crate) struct ReflectorSettings { pub(crate) struct ReflectorSettings {
#[serde(deserialize_with = "deserialize_reflector_files")]
pub(crate) files: Vec<String>, pub(crate) files: Vec<String>,
pub(crate) countries: Vec<String>, pub(crate) countries: Vec<String>,
@@ -472,24 +443,3 @@ where
let value = String::deserialize(deserializer)?; let value = String::deserialize(deserializer)?;
value.parse().map_err(de::Error::custom) value.parse().map_err(de::Error::custom)
} }
// UCI drops empty string options; `option files 'none'` disables file loading.
fn deserialize_reflector_files<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum Files {
List(Vec<String>),
String(String),
}
match Files::deserialize(deserializer)? {
Files::List(files) => Ok(files),
Files::String(value) if value.trim().is_empty() || value.trim() == "none" => Ok(Vec::new()),
Files::String(_) => Err(de::Error::custom(
"reflector files must be a list, an empty string, or none",
)),
}
}
-15
View File
@@ -212,21 +212,6 @@ impl ReflectorSettings {
); );
} }
for (index, reflector) in self.entries.iter().enumerate() {
if reflector.host.trim().is_empty() {
report.error(
"reflectors.entries",
format!("entry {index} must have a nonempty host"),
);
}
if reflector.protocols.is_empty() {
report.error(
"reflectors.entries",
format!("entry {index} must have at least one measurement protocol"),
);
}
}
for (index, file) in self.files.iter().enumerate() { for (index, file) in self.files.iter().enumerate() {
if file.trim().is_empty() { if file.trim().is_empty() {
report.error( report.error(
+9
View File
@@ -14,11 +14,16 @@ pub trait Clock {
} }
pub enum Realtime {} pub enum Realtime {}
pub enum Monotonic {}
impl Clock for Realtime { impl Clock for Realtime {
const ID: ClockId = ClockId::Realtime; const ID: ClockId = ClockId::Realtime;
} }
impl Clock for Monotonic {
const ID: ClockId = ClockId::Monotonic;
}
pub struct ClockSample<C> { pub struct ClockSample<C> {
time_s: u64, time_s: u64,
time_ns: u64, time_ns: u64,
@@ -51,6 +56,10 @@ impl<C: Clock> ClockSample<C> {
pub fn as_secs_f64(&self) -> f64 { pub fn as_secs_f64(&self) -> f64 {
self.time_s as f64 + self.time_ns as f64 / 1_000_000_000.0 self.time_s as f64 + self.time_ns as f64 / 1_000_000_000.0
} }
pub fn to_milliseconds(&self) -> u64 {
(self.time_s * 1000) + (self.time_ns / 1000000)
}
} }
impl ClockSample<Realtime> { impl ClockSample<Realtime> {
+11 -1
View File
@@ -3,10 +3,20 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
use anyhow::anyhow; use anyhow::anyhow;
use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; use std::sync::{Arc, Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard};
pub type ArcRwLock<T> = Arc<RwLock<T>>; pub type ArcRwLock<T> = Arc<RwLock<T>>;
pub trait MutexExt<T> {
fn lock_anyhow(&self) -> anyhow::Result<MutexGuard<'_, T>>;
}
impl<T> MutexExt<T> for Mutex<T> {
fn lock_anyhow(&self) -> anyhow::Result<MutexGuard<'_, T>> {
self.lock().map_err(|e| anyhow!("mutex poisoned: {e}"))
}
}
pub trait RwLockExt<T> { pub trait RwLockExt<T> {
fn read_anyhow(&self) -> anyhow::Result<RwLockReadGuard<'_, T>>; fn read_anyhow(&self) -> anyhow::Result<RwLockReadGuard<'_, T>>;
fn write_anyhow(&self) -> anyhow::Result<RwLockWriteGuard<'_, T>>; fn write_anyhow(&self) -> anyhow::Result<RwLockWriteGuard<'_, T>>;