11 Commits
Author SHA1 Message Date
renovate Bot f7dbec00d3 chore(deps): update zizmorcore/zizmor-action action to v0.7.1
Build binaries / Build (aarch64-musl) (pull_request) Waiting to run
Build binaries / Build (arm-musleabi) (pull_request) Waiting to run
Build binaries / Build (arm-musleabihf) (pull_request) Blocked by required conditions
Build binaries / Build (armv7-musleabi) (pull_request) Blocked by required conditions
Build binaries / Build (armv7-musleabihf) (pull_request) Blocked by required conditions
Build binaries / Build (i686-musl) (pull_request) Blocked by required conditions
Build binaries / Build (mips-musl) (pull_request) Blocked by required conditions
Build binaries / Build (mips64-muslabi64) (pull_request) Blocked by required conditions
Build binaries / Build (mips64el-muslabi64) (pull_request) Blocked by required conditions
Build binaries / Build (mipsel-musl) (pull_request) Blocked by required conditions
Build binaries / Build (riscv64gc-musl) (pull_request) Blocked by required conditions
Build binaries / Build (x86_64-musl) (pull_request) Blocked by required conditions
Rust CI / cargo fmt (pull_request) Waiting to run
Rust CI / cargo clippy (pull_request) Waiting to run
Rust CI / cargo check (x86_64-unknown-linux-gnu) (pull_request) Waiting to run
Rust CI / cargo test (x86_64-unknown-linux-gnu) (pull_request) Waiting to run
Rust CI / cargo check (x86_64-unknown-linux-musl) (pull_request) Waiting to run
Rust CI / cargo test (x86_64-unknown-linux-musl) (pull_request) Waiting to run
Rust CI / cargo doc (pull_request) Waiting to run
Supply chain / cargo-deny advisories (pull_request) Waiting to run
Supply chain / cargo-deny bans licenses sources (pull_request) Waiting to run
Supply chain / actionlint (pull_request) Waiting to run
Build binaries / Build (aarch64-musl) (push) Waiting to run
Build binaries / Build (arm-musleabi) (push) Waiting to run
Build binaries / Build (arm-musleabihf) (push) Blocked by required conditions
Build binaries / Build (armv7-musleabi) (push) Blocked by required conditions
Build binaries / Build (armv7-musleabihf) (push) Blocked by required conditions
Build binaries / Build (i686-musl) (push) Blocked by required conditions
Build binaries / Build (mips-musl) (push) Blocked by required conditions
Build binaries / Build (mips64-muslabi64) (push) Blocked by required conditions
Build binaries / Build (mips64el-muslabi64) (push) Blocked by required conditions
Build binaries / Build (mipsel-musl) (push) Blocked by required conditions
Build binaries / Build (riscv64gc-musl) (push) Blocked by required conditions
Build binaries / Build (x86_64-musl) (push) Blocked by required conditions
Rust CI / cargo fmt (push) Waiting to run
Rust CI / cargo clippy (push) Waiting to run
Rust CI / cargo check (x86_64-unknown-linux-gnu) (push) Waiting to run
Rust CI / cargo test (x86_64-unknown-linux-gnu) (push) Waiting to run
Rust CI / cargo check (x86_64-unknown-linux-musl) (push) Waiting to run
Rust CI / cargo test (x86_64-unknown-linux-musl) (push) Waiting to run
Rust CI / cargo doc (push) Waiting to run
Supply chain / cargo-deny advisories (push) Waiting to run
Supply chain / cargo-deny bans licenses sources (push) Waiting to run
Supply chain / actionlint (push) Waiting to run
2026-10-16 16:58:06 +00:00
renovate Bot 5757b45c5f chore(deps): update github/codeql-action digest to 6f2ac81
Build binaries / Build (aarch64-musl) (pull_request) Canceled after 0s
Build binaries / Build (arm-musleabi) (pull_request) Canceled after 0s
Build binaries / Build (arm-musleabihf) (pull_request) Canceled after 0s
Build binaries / Build (armv7-musleabi) (pull_request) Canceled after 0s
Build binaries / Build (armv7-musleabihf) (pull_request) Canceled after 0s
Build binaries / Build (i686-musl) (pull_request) Canceled after 0s
Build binaries / Build (mips-musl) (pull_request) Canceled after 0s
Build binaries / Build (mips64-muslabi64) (pull_request) Canceled after 0s
Build binaries / Build (mips64el-muslabi64) (pull_request) Canceled after 0s
Build binaries / Build (mipsel-musl) (pull_request) Canceled after 0s
Build binaries / Build (riscv64gc-musl) (pull_request) Canceled after 0s
Build binaries / Build (x86_64-musl) (pull_request) Canceled after 0s
Rust CI / cargo fmt (pull_request) Canceled after 0s
Rust CI / cargo clippy (pull_request) Canceled after 0s
Rust CI / cargo check (x86_64-unknown-linux-gnu) (pull_request) Canceled after 0s
Rust CI / cargo test (x86_64-unknown-linux-gnu) (pull_request) Canceled after 0s
Rust CI / cargo check (x86_64-unknown-linux-musl) (pull_request) Canceled after 0s
Rust CI / cargo test (x86_64-unknown-linux-musl) (pull_request) Canceled after 0s
Rust CI / cargo doc (pull_request) Canceled after 0s
Supply chain / cargo-deny advisories (pull_request) Canceled after 0s
Supply chain / cargo-deny bans licenses sources (pull_request) Canceled after 0s
Supply chain / actionlint (pull_request) Canceled after 0s
Build binaries / Build (aarch64-musl) (push) Waiting to run
Build binaries / Build (arm-musleabi) (push) Waiting to run
Build binaries / Build (arm-musleabihf) (push) Blocked by required conditions
Build binaries / Build (armv7-musleabi) (push) Blocked by required conditions
Build binaries / Build (armv7-musleabihf) (push) Blocked by required conditions
Build binaries / Build (i686-musl) (push) Blocked by required conditions
Build binaries / Build (mips-musl) (push) Blocked by required conditions
Build binaries / Build (mips64-muslabi64) (push) Blocked by required conditions
Build binaries / Build (mips64el-muslabi64) (push) Blocked by required conditions
Build binaries / Build (mipsel-musl) (push) Blocked by required conditions
Build binaries / Build (riscv64gc-musl) (push) Blocked by required conditions
Build binaries / Build (x86_64-musl) (push) Blocked by required conditions
Rust CI / cargo fmt (push) Waiting to run
Rust CI / cargo clippy (push) Waiting to run
Rust CI / cargo check (x86_64-unknown-linux-gnu) (push) Waiting to run
Rust CI / cargo test (x86_64-unknown-linux-gnu) (push) Waiting to run
Rust CI / cargo check (x86_64-unknown-linux-musl) (push) Waiting to run
Rust CI / cargo test (x86_64-unknown-linux-musl) (push) Waiting to run
Rust CI / cargo doc (push) Waiting to run
Supply chain / cargo-deny advisories (push) Waiting to run
Supply chain / cargo-deny bans licenses sources (push) Waiting to run
Supply chain / actionlint (push) Waiting to run
2026-10-15 11:07:55 +00:00
Nils Andreas Svee a58e6d1ce5 Merge branch 'feature/irtt'
Rust CI / cargo fmt (push) Successful in 13s
Rust CI / cargo clippy (push) Failing after 21s
Rust CI / cargo check (x86_64-unknown-linux-gnu) (push) Failing after 25s
Rust CI / cargo check (x86_64-unknown-linux-musl) (push) Failing after 8s
Rust CI / cargo test (x86_64-unknown-linux-musl) (push) Failing after 6s
Rust CI / cargo test (x86_64-unknown-linux-gnu) (push) Failing after 26s
Supply chain / cargo-deny advisories (push) Failing after 2s
Supply chain / cargo-deny bans licenses sources (push) Failing after 2s
Supply chain / actionlint (push) Failing after 2s
Rust CI / cargo doc (push) Failing after 51m16s
Build binaries / Build (arm-musleabi) (push) Failing after 52m19s
Build binaries / Build (aarch64-musl) (push) Failing after 52m19s
Build binaries / Build (arm-musleabihf) (push) Failing after 11m33s
Build binaries / Build (armv7-musleabi) (push) Successful in 2m37s
Build binaries / Build (armv7-musleabihf) (push) Successful in 1m51s
Build binaries / Build (i686-musl) (push) Successful in 1m38s
Build binaries / Build (mips-musl) (push) Successful in 2m2s
Build binaries / Build (mips64-muslabi64) (push) Successful in 2m15s
Build binaries / Build (mips64el-muslabi64) (push) Successful in 2m28s
Build binaries / Build (mipsel-musl) (push) Successful in 2m23s
Build binaries / Build (riscv64gc-musl) (push) Successful in 1m59s
Build binaries / Build (x86_64-musl) (push) Successful in 1m50s
2026-10-14 12:50:36 +07:00
Nils Andreas Svee 4770b4fb5d docs(reflectors): bundle protocol-aware inventory and configuration examples
Replace the ICMP-only inventory with protocol-aware records, remove Mullvad DNS entries and omit misleading anycast countries. Document IRTT authentication and explicit TOML/UCI reflector configuration, including disabling file loading.
2026-10-14 12:50:29 +07:00
Nils Andreas Svee eaa20e79d2 feat(measurement): integrate IRTT and configurable reflector selection
Add protocol-aware reflector definitions, TOML/UCI lists, validation and environment overrides. Resolve and select endpoint-aware measurement streams, honor configured ICMP timing and socket routing, and retain partial-set reselection behavior from the standalone fixes.

Drive the managed IRTT task throughout its lifecycle, retry terminal targets without restarting healthy sessions, normalize blank HMAC keys and use effective RTT for latency fallback. Fold the reviewed configuration, resolution and measurement corrections into their implementation.
2026-10-14 12:50:29 +07:00
Nils Andreas Svee 41929875f7 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-14 12:50:28 +07:00
renovate Bot 8aa971920c chore(deps): update rust crate tokio to v1.53.3
Rust CI / cargo fmt (push) Successful in 15s
Build binaries / Build (arm-musleabi) (push) Successful in 1m41s
Build binaries / Build (aarch64-musl) (push) Successful in 1m42s
Rust CI / cargo clippy (push) Failing after 21s
Rust CI / cargo check (x86_64-unknown-linux-musl) (push) Failing after 8s
Rust CI / cargo check (x86_64-unknown-linux-gnu) (push) Failing after 20s
Supply chain / cargo-deny advisories (push) Failing after 2s
Rust CI / cargo test (x86_64-unknown-linux-musl) (push) Failing after 5s
Supply chain / cargo-deny bans licenses sources (push) Failing after 3s
Supply chain / actionlint (push) Failing after 2s
Rust CI / cargo test (x86_64-unknown-linux-gnu) (push) Failing after 27s
Rust CI / cargo doc (push) Failing after 22s
Build binaries / Build (armv7-musleabi) (push) Failing after 30m43s
Build binaries / Build (arm-musleabihf) (push) Failing after 30m44s
Build binaries / Build (armv7-musleabihf) (push) Successful in 1m47s
Build binaries / Build (i686-musl) (push) Successful in 1m48s
Build binaries / Build (mips64-muslabi64) (push) Successful in 2m2s
Build binaries / Build (mips-musl) (push) Successful in 2m5s
Build binaries / Build (mips64el-muslabi64) (push) Successful in 2m13s
Build binaries / Build (mipsel-musl) (push) Successful in 2m13s
Build binaries / Build (riscv64gc-musl) (push) Successful in 1m48s
Build binaries / Build (x86_64-musl) (push) Successful in 1m44s
2026-10-14 12:49:23 +07:00
Nils Andreas Svee 2efa0d30c2 fix(uci): gate schema generation behind UCI feature
Rust CI / cargo fmt (push) Successful in 10s
Rust CI / cargo clippy (push) Failing after 28s
Rust CI / cargo check (x86_64-unknown-linux-gnu) (push) Failing after 26s
Rust CI / cargo check (x86_64-unknown-linux-musl) (push) Failing after 6s
Rust CI / cargo test (x86_64-unknown-linux-musl) (push) Failing after 6s
Rust CI / cargo test (x86_64-unknown-linux-gnu) (push) Failing after 22s
Supply chain / cargo-deny advisories (push) Failing after 2s
Supply chain / cargo-deny bans licenses sources (push) Failing after 3s
Supply chain / actionlint (push) Failing after 5s
Rust CI / cargo doc (push) Failing after 27s
Build binaries / Build (arm-musleabi) (push) Successful in 1m45s
Build binaries / Build (aarch64-musl) (push) Successful in 1m49s
Build binaries / Build (armv7-musleabi) (push) Successful in 1m54s
Build binaries / Build (arm-musleabihf) (push) Successful in 1m55s
Build binaries / Build (armv7-musleabihf) (push) Successful in 2m34s
Build binaries / Build (i686-musl) (push) Successful in 2m34s
Build binaries / Build (mips-musl) (push) Successful in 2m5s
Build binaries / Build (mips64-muslabi64) (push) Successful in 2m5s
Build binaries / Build (mipsel-musl) (push) Successful in 2m10s
Build binaries / Build (mips64el-muslabi64) (push) Successful in 2m12s
Build binaries / Build (riscv64gc-musl) (push) Successful in 2m8s
Build binaries / Build (x86_64-musl) (push) Successful in 2m1s
2026-10-14 12:13:13 +07:00
Nils Andreas Svee f1a50af7d3 fix(reflectors): retain current peers when no candidates respond
Rust CI / cargo fmt (push) Successful in 22s
Rust CI / cargo check (x86_64-unknown-linux-gnu) (push) Failing after 37s
Rust CI / cargo clippy (push) Failing after 37s
Rust CI / cargo test (x86_64-unknown-linux-musl) (push) Failing after 12s
Rust CI / cargo check (x86_64-unknown-linux-musl) (push) Failing after 13s
Rust CI / cargo test (x86_64-unknown-linux-gnu) (push) Failing after 31s
Supply chain / cargo-deny advisories (push) Failing after 3s
Supply chain / cargo-deny bans licenses sources (push) Failing after 2s
Supply chain / actionlint (push) Failing after 3s
Rust CI / cargo doc (push) Failing after 32s
Build binaries / Build (arm-musleabi) (push) Successful in 2m16s
Build binaries / Build (aarch64-musl) (push) Failing after 24m1s
Build binaries / Build (arm-musleabihf) (push) Failing after 51m7s
Build binaries / Build (armv7-musleabihf) (push) Failing after 11m33s
Build binaries / Build (armv7-musleabi) (push) Failing after 11m33s
Build binaries / Build (i686-musl) (push) Successful in 2m6s
Build binaries / Build (mips-musl) (push) Successful in 2m10s
Build binaries / Build (mips64-muslabi64) (push) Successful in 2m5s
Build binaries / Build (mips64el-muslabi64) (push) Successful in 2m12s
Build binaries / Build (mipsel-musl) (push) Successful in 2m10s
Build binaries / Build (riscv64gc-musl) (push) Successful in 2m9s
Build binaries / Build (x86_64-musl) (push) Successful in 1m42s
Restore the pre-probe selection when evaluation finds no usable candidates. Preserve master’s existing selection of smaller candidate sets.
2026-10-14 11:58:07 +07:00
Nils Andreas Svee 5624746914 fix(ratecontroller): account for configured and partial reflector sets
Cap the required delta count at the active peer count using advanced_settings.num_reflectors. Keep requesting reselection when the active set is empty so recovery remains possible.
2026-10-14 11:58:07 +07:00
Nils Andreas Svee 2e24da4bf8 chore: remove unused clock and mutex helpers 2026-10-14 11:58:07 +07:00
18 changed files with 197 additions and 116 deletions

No files matched your search

+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
with:
persist-credentials: false
- name: Run zizmor
uses: zizmorcore/zizmor-action@cc914d7f3750a2d13d75c7f184a1060aa0e9d482 # v0.6.4
uses: zizmorcore/zizmor-action@85b2b9fb8e3325e7a23d7d0a9d7ba5ae0f999e3e # v0.7.1
with:
persona: regular
inputs: |
+1 -1
View File
@@ -50,7 +50,7 @@ jobs:
echo "exit_code=${clippy_status}" >> "$GITHUB_OUTPUT"
exit 0
- name: Upload Clippy SARIF
uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4
uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4
if: always()
with:
sarif_file: rust-clippy-results.sarif
Generated
+2 -2
View File
@@ -867,9 +867,9 @@ dependencies = [
[[package]]
name = "tokio"
version = "1.53.2"
version = "1.53.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e95f91fcc7a621e8b030f6aa23c71fe9838ae2fb4d8118b75602a328f5144044"
checksum = "ce3335fa71841cda333a58d7615b03901380ecf09d59b3296d21f8bbac0dde4e"
dependencies = [
"bytes",
"libc",
+20
View File
@@ -57,3 +57,23 @@ 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
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,6 +82,7 @@ fn expand_uci_section(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStre
let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
Ok(quote! {
#[cfg(all(feature = "uci", unix))]
#[automatically_derived]
impl #impl_generics
crate::settings::uci_schema::UciSectionSchema
@@ -155,6 +156,7 @@ fn expand_uci_config(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStrea
let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
Ok(quote! {
#[cfg(all(feature = "uci", unix))]
#[automatically_derived]
impl #impl_generics
crate::settings::uci_schema::UciConfigSchema
+43 -47
View File
@@ -1,45 +1,41 @@
host,country,protocols,irtt_port,irtt_hmac
94.140.14.14,CY,icmp-echo|icmp-timestamps,,
94.140.15.15,CY,icmp-echo|icmp-timestamps,,
94.140.14.15,CY,icmp-echo|icmp-timestamps,,
94.140.15.16,CY,icmp-echo|icmp-timestamps,,
94.140.14.140,CY,icmp-echo|icmp-timestamps,,
94.140.14.141,CY,icmp-echo|icmp-timestamps,,
205.171.3.65,US,icmp-echo|icmp-timestamps,,
205.171.2.65,US,icmp-echo|icmp-timestamps,,
185.228.168.9,US,icmp-echo|icmp-timestamps,,
185.228.169.9,US,icmp-echo|icmp-timestamps,,
185.228.168.168,US,icmp-echo|icmp-timestamps,,
185.228.169.168,US,icmp-echo|icmp-timestamps,,
185.228.168.10,US,icmp-echo|icmp-timestamps,,
185.228.169.11,US,icmp-echo|icmp-timestamps,,
8.26.56.26,US,icmp-echo|icmp-timestamps,,
8.20.247.20,US,icmp-echo|icmp-timestamps,,
94.140.14.14,,icmp-echo|icmp-timestamps,,
94.140.15.15,,icmp-echo|icmp-timestamps,,
94.140.14.15,,icmp-echo|icmp-timestamps,,
94.140.15.16,,icmp-echo|icmp-timestamps,,
94.140.14.140,,icmp-echo|icmp-timestamps,,
94.140.14.141,,icmp-echo|icmp-timestamps,,
205.171.3.65,,icmp-echo|icmp-timestamps,,
205.171.2.65,,icmp-echo|icmp-timestamps,,
185.228.168.9,,icmp-echo|icmp-timestamps,,
185.228.169.9,,icmp-echo|icmp-timestamps,,
185.228.168.168,,icmp-echo|icmp-timestamps,,
185.228.169.168,,icmp-echo|icmp-timestamps,,
185.228.168.10,,icmp-echo|icmp-timestamps,,
185.228.169.11,,icmp-echo|icmp-timestamps,,
8.26.56.26,,icmp-echo|icmp-timestamps,,
8.20.247.20,,icmp-echo|icmp-timestamps,,
84.200.69.80,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.55,GB,icmp-echo|icmp-timestamps,,
46.227.200.55,,icmp-echo|icmp-timestamps,,
45.33.97.5,US,icmp-echo|icmp-timestamps,,
37.235.1.177,AT,icmp-echo|icmp-timestamps,,
74.82.42.42,US,icmp-echo|icmp-timestamps,,
194.242.2.2,US,icmp-echo|icmp-timestamps,,
194.242.2.3,US,icmp-echo|icmp-timestamps,,
193.19.108.2,US,icmp-echo|icmp-timestamps,,
193.19.108.3,US,icmp-echo|icmp-timestamps,,
208.67.222.222,US,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,,
74.82.42.42,,icmp-echo|icmp-timestamps,,
208.67.222.222,,icmp-echo|icmp-timestamps,,
208.67.220.220,,icmp-echo|icmp-timestamps,,
208.67.222.123,,icmp-echo|icmp-timestamps,,
208.67.220.123,,icmp-echo|icmp-timestamps,,
208.67.222.2,,icmp-echo|icmp-timestamps,,
208.67.220.2,,icmp-echo|icmp-timestamps,,
159.89.120.99,CA,icmp-echo|icmp-timestamps,,
134.195.4.2,US,icmp-echo|icmp-timestamps,,
9.9.9.9,US,icmp-echo|icmp-timestamps,,
9.9.9.10,US,icmp-echo|icmp-timestamps,,
9.9.9.11,US,icmp-echo|icmp-timestamps,,
149.112.112.10,US,icmp-echo|icmp-timestamps,,
149.112.112.11,US,icmp-echo|icmp-timestamps,,
149.112.112.112,US,icmp-echo|icmp-timestamps,,
134.195.4.2,,icmp-echo|icmp-timestamps,,
9.9.9.9,,icmp-echo|icmp-timestamps,,
9.9.9.10,,icmp-echo|icmp-timestamps,,
9.9.9.11,,icmp-echo|icmp-timestamps,,
149.112.112.10,,icmp-echo|icmp-timestamps,,
149.112.112.11,,icmp-echo|icmp-timestamps,,
149.112.112.112,,icmp-echo|icmp-timestamps,,
176.9.93.198,DE,icmp-echo|icmp-timestamps,,
176.9.1.117,DE,icmp-echo|icmp-timestamps,,
104.236.210.29,US,icmp-echo|icmp-timestamps,,
@@ -58,11 +54,11 @@ host,country,protocols,irtt_port,irtt_hmac
140.238.215.192,CH,icmp-echo|icmp-timestamps,,
199.58.81.218,CA,icmp-echo|icmp-timestamps,,
146.255.56.98,AT,icmp-echo|icmp-timestamps,,
185.222.222.222,DE,icmp-echo|icmp-timestamps,,
185.222.222.222,,icmp-echo|icmp-timestamps,,
185.184.222.222,NL,icmp-echo|icmp-timestamps,,
5.1.66.255,AT,icmp-echo|icmp-timestamps,,
80.80.80.80,SM,icmp-echo|icmp-timestamps,,
80.80.81.81,SM,icmp-echo|icmp-timestamps,,
80.80.80.80,,icmp-echo|icmp-timestamps,,
80.80.81.81,,icmp-echo|icmp-timestamps,,
80.67.169.12,FR,icmp-echo|icmp-timestamps,,
80.67.169.40,FR,icmp-echo|icmp-timestamps,,
217.24.255.134,AL,icmp-echo|icmp-timestamps,,
@@ -79,10 +75,10 @@ host,country,protocols,irtt_port,irtt_hmac
45.133.105.123,AM,icmp-echo|icmp-timestamps,,
61.8.0.113,AU,icmp-echo|icmp-timestamps,,
103.224.162.40,AU,icmp-echo|icmp-timestamps,,
139.130.4.4,AU,icmp-echo|icmp-timestamps,,
139.130.4.4,,icmp-echo|icmp-timestamps,,
139.134.5.51,AU,icmp-echo|icmp-timestamps,,
139.134.2.190,AU,icmp-echo|icmp-timestamps,,
203.50.2.71,AU,icmp-echo|icmp-timestamps,,
203.50.2.71,,icmp-echo|icmp-timestamps,,
83.137.41.8,AT,icmp-echo|icmp-timestamps,,
83.137.41.9,AT,icmp-echo|icmp-timestamps,,
194.36.144.87,DE,icmp-echo|icmp-timestamps,,
@@ -124,10 +120,10 @@ host,country,protocols,irtt_port,irtt_hmac
194.172.160.4,DE,icmp-echo|icmp-timestamps,,
185.233.106.232,DE,icmp-echo|icmp-timestamps,,
185.233.107.4,DE,icmp-echo|icmp-timestamps,,
141.1.1.1,GB,icmp-echo|icmp-timestamps,,
141.1.27.249,GB,icmp-echo|icmp-timestamps,,
176.103.130.130,CY,icmp-echo|icmp-timestamps,,
176.103.130.131,CY,icmp-echo|icmp-timestamps,,
141.1.1.1,,icmp-echo|icmp-timestamps,,
141.1.27.249,,icmp-echo|icmp-timestamps,,
176.103.130.130,,icmp-echo|icmp-timestamps,,
176.103.130.131,,icmp-echo|icmp-timestamps,,
80.243.64.67,RU,icmp-echo|icmp-timestamps,,
195.218.133.88,RU,icmp-echo|icmp-timestamps,,
83.69.120.1,RU,icmp-echo|icmp-timestamps,,
@@ -154,7 +150,7 @@ host,country,protocols,irtt_port,irtt_hmac
45.77.61.165,FR,icmp-echo|icmp-timestamps,,
178.62.57.141,GB,icmp-echo|icmp-timestamps,,
194.145.240.6,GB,icmp-echo|icmp-timestamps,,
195.27.1.1,GB,icmp-echo|icmp-timestamps,,
195.27.1.1,,icmp-echo|icmp-timestamps,,
81.17.66.13,GB,icmp-echo|icmp-timestamps,,
87.117.196.200,GB,icmp-echo|icmp-timestamps,,
213.123.251.87,GB,icmp-echo|icmp-timestamps,,
@@ -179,7 +175,7 @@ host,country,protocols,irtt_port,irtt_hmac
46.246.29.69,SE,icmp-echo|icmp-timestamps,,
203.210.237.165,VN,icmp-echo|icmp-timestamps,,
113.190.253.229,VN,icmp-echo|icmp-timestamps,,
119.29.29.29,CN,icmp-echo|icmp-timestamps,,
119.29.29.29,,icmp-echo|icmp-timestamps,,
209.193.4.7,US,icmp-echo|icmp-timestamps,,
209.112.160.2,US,icmp-echo|icmp-timestamps,,
216.68.4.10,US,icmp-echo|icmp-timestamps,,
@@ -303,7 +299,7 @@ host,country,protocols,irtt_port,irtt_hmac
116.193.190.164,ID,icmp-echo|icmp-timestamps,,
103.56.148.179,ID,icmp-echo|icmp-timestamps,,
8.215.26.147,ID,icmp-echo|icmp-timestamps,,
global.irtt.lochnair.net,SI,irtt,2112,
global.irtt.lochnair.net,,irtt,2112,
uk-cov1.irtt.lochnair.net,GB,irtt,2112,
uk-cov2.irtt.lochnair.net,GB,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 194.242.2.2 208.67.222.222 US icmp-echo|icmp-timestamps
26 194.242.2.3 208.67.220.220 US icmp-echo|icmp-timestamps
27 193.19.108.2 208.67.222.123 US icmp-echo|icmp-timestamps
28 193.19.108.3 208.67.220.123 US icmp-echo|icmp-timestamps
29 208.67.222.222 208.67.222.2 US icmp-echo|icmp-timestamps
30 208.67.220.220 208.67.220.2 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
31 159.89.120.99 CA icmp-echo|icmp-timestamps
32 134.195.4.2 US icmp-echo|icmp-timestamps
33 9.9.9.9 US icmp-echo|icmp-timestamps
34 9.9.9.10 US icmp-echo|icmp-timestamps
35 9.9.9.11 US icmp-echo|icmp-timestamps
36 149.112.112.10 US icmp-echo|icmp-timestamps
37 149.112.112.11 US icmp-echo|icmp-timestamps
38 149.112.112.112 US icmp-echo|icmp-timestamps
39 176.9.93.198 DE icmp-echo|icmp-timestamps
40 176.9.1.117 DE icmp-echo|icmp-timestamps
41 104.236.210.29 US icmp-echo|icmp-timestamps
54 140.238.215.192 CH icmp-echo|icmp-timestamps
55 199.58.81.218 CA icmp-echo|icmp-timestamps
56 146.255.56.98 AT icmp-echo|icmp-timestamps
57 185.222.222.222 DE icmp-echo|icmp-timestamps
58 185.184.222.222 NL icmp-echo|icmp-timestamps
59 5.1.66.255 AT icmp-echo|icmp-timestamps
60 80.80.80.80 SM icmp-echo|icmp-timestamps
61 80.80.81.81 SM icmp-echo|icmp-timestamps
62 80.67.169.12 FR icmp-echo|icmp-timestamps
63 80.67.169.40 FR icmp-echo|icmp-timestamps
64 217.24.255.134 AL icmp-echo|icmp-timestamps
75 45.133.105.123 AM icmp-echo|icmp-timestamps
76 61.8.0.113 AU icmp-echo|icmp-timestamps
77 103.224.162.40 AU icmp-echo|icmp-timestamps
78 139.130.4.4 AU icmp-echo|icmp-timestamps
79 139.134.5.51 AU icmp-echo|icmp-timestamps
80 139.134.2.190 AU icmp-echo|icmp-timestamps
81 203.50.2.71 AU icmp-echo|icmp-timestamps
82 83.137.41.8 AT icmp-echo|icmp-timestamps
83 83.137.41.9 AT icmp-echo|icmp-timestamps
84 194.36.144.87 DE icmp-echo|icmp-timestamps
120 194.172.160.4 DE icmp-echo|icmp-timestamps
121 185.233.106.232 DE icmp-echo|icmp-timestamps
122 185.233.107.4 DE icmp-echo|icmp-timestamps
123 141.1.1.1 GB icmp-echo|icmp-timestamps
124 141.1.27.249 GB icmp-echo|icmp-timestamps
125 176.103.130.130 CY icmp-echo|icmp-timestamps
126 176.103.130.131 CY icmp-echo|icmp-timestamps
127 80.243.64.67 RU icmp-echo|icmp-timestamps
128 195.218.133.88 RU icmp-echo|icmp-timestamps
129 83.69.120.1 RU icmp-echo|icmp-timestamps
150 45.77.61.165 FR icmp-echo|icmp-timestamps
151 178.62.57.141 GB icmp-echo|icmp-timestamps
152 194.145.240.6 GB icmp-echo|icmp-timestamps
153 195.27.1.1 GB icmp-echo|icmp-timestamps
154 81.17.66.13 GB icmp-echo|icmp-timestamps
155 87.117.196.200 GB icmp-echo|icmp-timestamps
156 213.123.251.87 GB icmp-echo|icmp-timestamps
175 46.246.29.69 SE icmp-echo|icmp-timestamps
176 203.210.237.165 VN icmp-echo|icmp-timestamps
177 113.190.253.229 VN icmp-echo|icmp-timestamps
178 119.29.29.29 CN icmp-echo|icmp-timestamps
179 209.193.4.7 US icmp-echo|icmp-timestamps
180 209.112.160.2 US icmp-echo|icmp-timestamps
181 216.68.4.10 US icmp-echo|icmp-timestamps
299 116.193.190.164 ID icmp-echo|icmp-timestamps
300 103.56.148.179 ID icmp-echo|icmp-timestamps
301 8.215.26.147 ID icmp-echo|icmp-timestamps
302 global.irtt.lochnair.net SI irtt 2112
303 uk-cov1.irtt.lochnair.net GB irtt 2112
304 uk-cov2.irtt.lochnair.net GB irtt 2112
305 de-fra.irtt.lochnair.net DE irtt 2112
+1
View File
@@ -289,6 +289,7 @@ mod tests {
},
started_at: received_at,
observed_at: received_at,
round_trip: None,
one_way: Some(OneWayLatency {
uplink: SignedDuration::from_millis(up_time as i64),
downlink: SignedDuration::from_millis(down_time as i64),
+1 -3
View File
@@ -120,9 +120,7 @@ impl Controller {
icmp.push(IcmpBinding::new(stream, schedule))
}
#[cfg(feature = "irtt")]
MeasurementSource::Irtt => {
irtt.push(IrttBinding::new(stream, schedule, port, hmac))
}
MeasurementSource::Irtt => irtt.push(IrttBinding::new(stream, port, hmac)),
#[cfg(not(feature = "irtt"))]
MeasurementSource::Irtt => unreachable!("IRTT feature is checked before startup"),
_ => unreachable!("unsupported reflector measurement source"),
+2 -3
View File
@@ -161,8 +161,8 @@ fn resolve_reflectors(
}
if filter_country
&& !countries.is_empty()
&& !definition.country.as_ref().is_some_and(|country| {
countries
&& definition.country.as_ref().is_some_and(|country| {
!countries
.iter()
.any(|allowed| allowed.eq_ignore_ascii_case(country))
})
@@ -389,7 +389,6 @@ fn run(settings: &Settings) -> anyhow::Result<()> {
let (irtt_task, irtt_engine) = {
let _guard = rt.enter();
IrttEngine::new(
start_time,
Duration::from_secs_f64(settings.advanced_settings.tick_interval),
&settings.network,
measurement_tx,
+2
View File
@@ -326,6 +326,7 @@ impl IcmpEngine {
stream: probe.stream,
started_at: probe.sent_at,
observed_at,
round_trip: None,
one_way: None,
})
}
@@ -385,6 +386,7 @@ impl IcmpEngine {
stream: probe.stream,
started_at: probe.sent_at,
observed_at,
round_trip: None,
one_way: Some(OneWayLatency {
uplink: SignedDuration::from_millis(uplink_ms),
downlink: SignedDuration::from_millis(downlink_ms),
+30 -26
View File
@@ -1,4 +1,4 @@
use std::{collections::HashMap, net::SocketAddr, time::Instant};
use std::{collections::HashMap, net::SocketAddr};
use flume::{Receiver, Sender};
use irtt_client::{
@@ -7,7 +7,7 @@ use irtt_client::{
ManagedClient, ManagedClientConfig, ManagedClientHandle, ManagedClientTask,
ManagedCommandApplyError, ManagedCommandError, ManagedCompletionPolicy, ManagedConfigError,
ManagedEndReason, ManagedEvent, ManagedLifecycle, ManagedSubscribeError,
ManagedTargetConfig, ManagedTargetEndReason, TargetAuth, TargetId,
ManagedTargetConfig, ManagedTargetEndReason, ManagedTargetLifecycle, TargetAuth, TargetId,
},
};
use log::{debug, trace, warn};
@@ -17,7 +17,7 @@ use tokio::sync::broadcast::error::RecvError;
use crate::SHUTDOWN;
use crate::measurement::model::{
MeasurementEvent, MeasurementLoss, MeasurementObservation, MeasurementStream,
MeasurementStreamId, OneWayClock, OneWayLatency, ProbeSchedule, SignedDuration,
MeasurementStreamId, OneWayClock, OneWayLatency, SignedDuration,
};
use crate::settings::NetworkSettings;
@@ -53,27 +53,13 @@ pub enum IrttError {
pub struct IrttBinding {
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,
hmac: Option<String>,
}
impl IrttBinding {
pub fn new(
stream: MeasurementStream,
schedule: ProbeSchedule,
port: u16,
hmac: Option<String>,
) -> Self {
Self {
stream,
schedule,
port,
hmac,
}
pub fn new(stream: MeasurementStream, port: u16, hmac: Option<String>) -> Self {
Self { stream, port, hmac }
}
}
@@ -81,8 +67,7 @@ pub struct IrttEngine {
client: ManagedClientHandle,
bindings: HashMap<MeasurementStreamId, IrttBinding>,
origin: Instant,
targets: Vec<ManagedTargetConfig>,
binding_rx: Receiver<Vec<IrttBinding>>,
observation_tx: Sender<MeasurementEvent>,
@@ -90,7 +75,6 @@ pub struct IrttEngine {
impl IrttEngine {
pub fn new(
origin: Instant,
interval: std::time::Duration,
settings: &NetworkSettings,
observation_tx: Sender<MeasurementEvent>,
@@ -104,7 +88,7 @@ impl IrttEngine {
Self {
client: handle,
bindings: HashMap::new(),
origin,
targets: Vec::new(),
binding_rx,
observation_tx,
},
@@ -119,16 +103,17 @@ impl IrttEngine {
TargetId::new(binding.stream.id.0.to_string()),
SocketAddr::new(binding.stream.peer, binding.port).to_string(),
);
if let Some(key) = &binding.hmac {
if let Some(key) = binding.hmac.as_ref().filter(|key| !key.trim().is_empty()) {
target.auth = TargetAuth::Override(irtt_client::Authentication::Hmac(
irtt_client::HmacKey::new(key.as_bytes().to_vec().into_boxed_slice()),
));
}
target
})
.collect();
.collect::<Vec<_>>();
self.client.update_targets(targets)?.await?;
self.client.update_targets(targets.clone())?.await?;
self.targets = targets;
self.bindings = bindings
.into_iter()
@@ -166,6 +151,11 @@ impl IrttEngine {
async fn run_events(&mut self) -> Result<(), IrttError> {
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 {
tokio::select! {
bindings = self.binding_rx.recv_async() => {
@@ -175,6 +165,17 @@ impl IrttEngine {
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() => {
let event = match event {
Ok(event) => event,
@@ -255,6 +256,7 @@ impl IrttEngine {
sent_at,
received_at,
one_way,
rtt,
..
} => {
let Some(stream) = stream else {
@@ -265,6 +267,8 @@ impl IrttEngine {
stream,
started_at: sent_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),
};
+7 -1
View File
@@ -121,6 +121,9 @@ pub struct MeasurementObservation {
/// Local monotonic time at which the result was observed.
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.
pub one_way: Option<OneWayLatency>,
}
@@ -131,7 +134,10 @@ impl MeasurementObservation {
}
pub fn rtt_ms(&self) -> f64 {
self.rtt().as_secs_f64() * 1000.0
self.round_trip
.map(SignedDuration::as_secs_f64)
.unwrap_or_else(|| self.rtt().as_secs_f64())
* 1000.0
}
}
+5 -2
View File
@@ -491,8 +491,11 @@ impl<S: InterfaceStatsProvider, T: TrafficControlBackend> Ratecontroller<S, T> {
self.state_dl.deltas = down_deltas;
self.state_ul.deltas = up_deltas;
let minimum = usize::from(self.settings.reflectors.num_reflectors);
if self.state_dl.deltas.len() < minimum || self.state_ul.deltas.len() < minimum {
let minimum = usize::from(self.settings.reflectors.num_reflectors).min(reflectors.len());
if reflectors.is_empty()
|| self.state_dl.deltas.len() < minimum
|| self.state_ul.deltas.len() < minimum
{
// trigger reselection
warn!(
"Not enough delta values (D: {}, U: {}, need {}), triggering reselection",
+10 -6
View File
@@ -252,17 +252,21 @@ impl ReflectorSelector {
candidates.swap(i, j);
}
if (candidates.len() as u8) < num_reflectors {
info!(
"Only {} usable reflectors, retaining current set of {}",
candidates.len(),
num_reflectors
);
if candidates.is_empty() {
info!("No usable reflectors, retaining current set");
self.reflector_tx.send(current_peers.clone())?;
*reflectors_peers = current_peers;
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();
for i in 0..num_reflectors {
let peer = candidates[i as usize].0;
+54 -4
View File
@@ -2,12 +2,10 @@
//
// SPDX-License-Identifier: MPL-2.0
use anyhow::{Context, bail};
use config::{Config, ConfigError, Environment};
use log::Level;
use serde::{Deserialize, Deserializer, de};
use std::fmt::{self, Display};
use std::net::IpAddr;
use std::str::FromStr;
use uci_derive::{UciConfig, UciSection};
@@ -17,6 +15,7 @@ use crate::settings::uci::UciSource;
#[cfg(all(feature = "uci", unix))]
pub mod uci;
#[cfg(all(feature = "uci", unix))]
pub(crate) mod uci_schema;
mod validation;
@@ -47,7 +46,7 @@ where
type Value = bool;
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("a boolean or a UCI-compatible boolean string")
formatter.write_str("a boolean, integer 0 or 1, or a UCI-compatible boolean string")
}
fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E>
@@ -57,6 +56,28 @@ where
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>
where
E: de::Error,
@@ -200,7 +221,14 @@ impl Settings {
config::File::with_name("/etc/sqm-autorate-rust/config.toml").required(false),
);
let builder = builder.add_source(Environment::with_prefix("SQMA").separator("__"));
let builder = builder.add_source(
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()?;
settings.reflectors.load_files()?;
@@ -241,6 +269,7 @@ impl NetworkSettings {
#[derive(Clone, Debug, Deserialize, UciSection)]
#[serde(default)]
pub(crate) struct ReflectorSettings {
#[serde(deserialize_with = "deserialize_reflector_files")]
pub(crate) files: Vec<String>,
pub(crate) countries: Vec<String>,
@@ -443,3 +472,24 @@ where
let value = String::deserialize(deserializer)?;
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,6 +212,21 @@ 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() {
if file.trim().is_empty() {
report.error(
-9
View File
@@ -14,16 +14,11 @@ pub trait Clock {
}
pub enum Realtime {}
pub enum Monotonic {}
impl Clock for Realtime {
const ID: ClockId = ClockId::Realtime;
}
impl Clock for Monotonic {
const ID: ClockId = ClockId::Monotonic;
}
pub struct ClockSample<C> {
time_s: u64,
time_ns: u64,
@@ -56,10 +51,6 @@ impl<C: Clock> ClockSample<C> {
pub fn as_secs_f64(&self) -> f64 {
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> {
+1 -11
View File
@@ -3,20 +3,10 @@
// SPDX-License-Identifier: MPL-2.0
use anyhow::anyhow;
use std::sync::{Arc, Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard};
use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
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> {
fn read_anyhow(&self) -> anyhow::Result<RwLockReadGuard<'_, T>>;
fn write_anyhow(&self) -> anyhow::Result<RwLockWriteGuard<'_, T>>;