Compare commits

...
Author SHA1 Message Date
renovate Bot 2a82951572 chore(deps): maintain cargo lockfile
Rust CI / cargo fmt (pull_request) Successful in 29s
Rust CI / cargo clippy (pull_request) Failing after 45s
Rust CI / cargo check (x86_64-unknown-linux-gnu) (pull_request) Successful in 49s
Rust CI / cargo check (x86_64-unknown-linux-musl) (pull_request) Failing after 10s
Rust CI / cargo test (x86_64-unknown-linux-musl) (pull_request) Failing after 9s
Rust CI / cargo doc (pull_request) Successful in 26s
Fuzz Smoke / cargo fuzz smoke (pull_request) Failing after 33s
Supply chain / cargo-deny advisories (pull_request) Failing after 3s
Netem Interop / netem interop (pull_request) Failing after 14s
Supply chain / cargo-deny bans licenses sources (pull_request) Failing after 3s
Supply chain / actionlint (pull_request) Successful in 3s
Rust CI / cargo test (x86_64-unknown-linux-gnu) (pull_request) Successful in 1m16s
2026-10-20 19:08:32 +00:00
Nils Andreas Svee 7da8d2141c chore(release): irtt-rs 0.9.0
Rust CI / cargo fmt (push) Successful in 18s
Rust CI / cargo check (x86_64-unknown-linux-musl) (push) Failing after 12s
Rust CI / cargo clippy (push) Failing after 35s
Rust CI / cargo check (x86_64-unknown-linux-gnu) (push) Successful in 38s
Rust CI / cargo test (x86_64-unknown-linux-musl) (push) Failing after 7s
Netem Interop / netem interop (push) Failing after 14s
Supply chain / cargo-deny advisories (push) Failing after 4s
Rust CI / cargo doc (push) Successful in 23s
Supply chain / cargo-deny bans licenses sources (push) Failing after 5s
Supply chain / actionlint (push) Successful in 5s
Fuzz Smoke / cargo fuzz smoke (push) Failing after 29s
Rust CI / cargo test (x86_64-unknown-linux-gnu) (push) Successful in 1m22s
Semver / cargo-semver-checks (push) Successful in 55s
Publish irtt-server image / publish (push) Successful in 1m3s
2026-10-21 01:31:32 +07:00
renovate Bot bdb8c13d7d chore(deps): update github actions 2026-10-21 01:24:56 +07:00
renovate Bot a04ea5727b chore(deps): maintain cargo lockfile 2026-10-21 01:24:56 +07:00
renovate Bot ab5f1ac807 fix(deps): update rust crate arbitrary to 1.5.0 2026-10-21 01:24:01 +07:00
renovate Bot 45b0c8549d chore(deps): update rust crate proptest to 1.12.0 2026-10-21 01:24:00 +07:00
Nils Andreas Svee 18a8e307fb feat(app): allow hexadecimal HMAC key specification and redact keys from debug output 2026-10-21 01:19:59 +07:00
Nils Andreas Svee ab241c4a2b feat(app/client): expose socket bind options 2026-10-21 00:33:46 +07:00
22 changed files with 415 additions and 204 deletions

No files matched your search

+1 -1
View File
@@ -36,7 +36,7 @@ jobs:
# It remains here while portable actionlint runs with Gitea CI, but scans # It remains here while portable actionlint runs with Gitea CI, but scans
# both workflow directories through its GitHub Actions rule set. # both workflow directories through its GitHub Actions rule set.
- name: Run zizmor - name: Run zizmor
uses: zizmorcore/zizmor-action@cc914d7f3750a2d13d75c7f184a1060aa0e9d482 # v0.6.4 uses: zizmorcore/zizmor-action@85b2b9fb8e3325e7a23d7d0a9d7ba5ae0f999e3e # v0.7.1
with: with:
persona: regular persona: regular
inputs: | inputs: |
+2 -2
View File
@@ -34,7 +34,7 @@ jobs:
# the old --skip-git flag is no longer supported. # the old --skip-git flag is no longer supported.
scan-scheduled: scan-scheduled:
if: ${{ github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }} if: ${{ github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }}
uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable.yml@a345acffa64b0eaede81a3d9aae6141214d9c8fc" # v2.6.0 uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable.yml@b1d267aea5211fa648d9d442fa64b90dbdcbdd36" # v2.7.0
scan-pr: scan-pr:
if: ${{ github.event_name == 'pull_request' || github.event_name == 'merge_group' }} if: ${{ github.event_name == 'pull_request' || github.event_name == 'merge_group' }}
uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable-pr.yml@a345acffa64b0eaede81a3d9aae6141214d9c8fc" # v2.6.0 uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable-pr.yml@b1d267aea5211fa648d9d442fa64b90dbdcbdd36" # v2.7.0
+1 -1
View File
@@ -60,7 +60,7 @@ jobs:
exit 0 exit 0
- name: Upload Clippy SARIF - name: Upload Clippy SARIF
uses: github/codeql-action/upload-sarif@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4 uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4
if: always() if: always()
with: with:
sarif_file: rust-clippy-results.sarif sarif_file: rust-clippy-results.sarif
+18
View File
@@ -192,6 +192,24 @@ release. The format is loosely based on
## irtt-rs ## irtt-rs
### 0.9.0
#### Added
- Client and TUI `--bind-addr IP[:PORT]` selects a local source address and optional UDP port. Omitted ports and port `0` use an ephemeral port; IPv6 addresses with an explicit port use `[ADDR]:PORT`. The bind address must match the target's address family, and fixed ports can conflict across multiple target sockets.
- Client and TUI `--bind-device DEVICE` binds sockets to a network interface on Linux, Android, and Fuchsia.
- Client and TUI `--mark MARK` sets a socket firewall mark for policy routing or firewall rules on Linux, Android, and Fuchsia. It accepts decimal and `0x`-prefixed hexadecimal unsigned 32-bit values.
- Client and TUI `--fib FIB` selects a routing table on FreeBSD, accepting a decimal unsigned 32-bit value.
#### Fixed
- Restored CLI compatibility with upstream IRTT for hexadecimal HMAC keys: client, TUI, and server `--hmac`, server `IRTT_SERVER_HMAC`, and client/TUI per-target `@hmac=KEY` accept a lowercase `0x` prefix followed by hexadecimal bytes, with either case allowed for digits. Literal UTF-8 keys remain supported; the HMAC-MD5 algorithm is unchanged.
- Server argument debug output now redacts HMAC keys, matching the client and TUI. Server help hides the `IRTT_SERVER_HMAC` value, and invalid global HMAC keys are rejected during configuration preparation without echoing key material.
#### Compatibility
- Keys beginning with literal `0x` are now interpreted as hexadecimal. Empty keys, empty hexadecimal keys, odd-length hexadecimal strings, and invalid hexadecimal digits are rejected. Per-target `@hmac=` continues to disable authentication explicitly.
### 0.8.0 ### 0.8.0
#### Added #### Added
Generated
+225 -136
View File
@@ -123,11 +123,11 @@ dependencies = [
[[package]] [[package]]
name = "bit-set" name = "bit-set"
version = "0.8.0" version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" checksum = "56d87354e4229f54a44f7bf2435906a4656dba36026ab6eaca629a2c436a691c"
dependencies = [ dependencies = [
"bit-vec 0.8.0", "bit-vec 0.10.1",
] ]
[[package]] [[package]]
@@ -138,9 +138,13 @@ checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
[[package]] [[package]]
name = "bit-vec" name = "bit-vec"
version = "0.8.0" version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" checksum = "5727b15fa97d4f4fee0a3b7c3d550ed0269f54329207b86388de918604e31269"
dependencies = [
"borsh",
"serde",
]
[[package]] [[package]]
name = "bitflags" name = "bitflags"
@@ -181,6 +185,30 @@ dependencies = [
"objc2", "objc2",
] ]
[[package]]
name = "borsh"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "553c5d846a6ba5150c65e3b1b8ec073bcf1abc20f9b7220de384a4443ea4e20a"
dependencies = [
"borsh-derive",
"bytes",
"cfg_aliases",
]
[[package]]
name = "borsh-derive"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12cdfe656708a01f89b451a7d36466e6fe6c414de0aa18fc54f864f6f9ca9f56"
dependencies = [
"once_cell",
"proc-macro-crate",
"proc-macro2",
"quote",
"syn 3.0.9",
]
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.20.3" version = "3.20.3"
@@ -199,6 +227,12 @@ version = "1.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797"
[[package]]
name = "bytes"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
[[package]] [[package]]
name = "castaway" name = "castaway"
version = "0.2.4" version = "0.2.4"
@@ -210,9 +244,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.6.0" version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f74872d07caf508b30a21f6836e7d7016a2eaf7d9ff4f48deaa58cd8a0407630" checksum = "877bf67ac8e52449eb2f9e98a344ea163beb70dd1f9eebe3cc5357dcfeaee4df"
dependencies = [ dependencies = [
"find-msvc-tools", "find-msvc-tools",
"shlex", "shlex",
@@ -230,6 +264,17 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
[[package]]
name = "chacha20"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06"
dependencies = [
"cfg-if",
"cpufeatures 0.3.1",
"rand_core 0.10.1",
]
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.45" version = "0.4.45"
@@ -251,6 +296,15 @@ dependencies = [
"clap_derive", "clap_derive",
] ]
[[package]]
name = "clap-num"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "822c4000301ac390e65995c62207501e3ef800a1fc441df913a5e8e4dc374816"
dependencies = [
"num-traits",
]
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.6.7" version = "4.6.7"
@@ -272,7 +326,7 @@ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.6", "syn 3.0.9",
] ]
[[package]] [[package]]
@@ -328,6 +382,12 @@ version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "core_detect"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48"
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.17" version = "0.2.17"
@@ -337,6 +397,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "cpufeatures"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "critical-section" name = "critical-section"
version = "1.2.0" version = "1.2.0"
@@ -440,7 +509,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"strsim", "strsim",
"syn 3.0.6", "syn 3.0.9",
] ]
[[package]] [[package]]
@@ -451,7 +520,7 @@ checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785"
dependencies = [ dependencies = [
"darling_core", "darling_core",
"quote", "quote",
"syn 3.0.6", "syn 3.0.9",
] ]
[[package]] [[package]]
@@ -462,9 +531,9 @@ checksum = "5729f5117e208430e437df2f4843f5e5952997175992d1414f94c57d61e270b4"
[[package]] [[package]]
name = "deranged" name = "deranged"
version = "0.5.8" version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" checksum = "9e9de72ce2ad1f90dc62fa25f0f430ef85eb4b0d8fa0be4f30373bc40a21d28e"
[[package]] [[package]]
name = "derive_more" name = "derive_more"
@@ -539,9 +608,9 @@ checksum = "0e9c71c2167ca323c882b99918929403426e2373ea17242ff5653e0d5e1058be"
[[package]] [[package]]
name = "equivalent" name = "equivalent"
version = "1.0.2" version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826"
[[package]] [[package]]
name = "errno" name = "errno"
@@ -591,9 +660,9 @@ dependencies = [
[[package]] [[package]]
name = "find-msvc-tools" name = "find-msvc-tools"
version = "0.1.14" version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" checksum = "1c66915cc5391097de7a2b0b972e8505aa33c0df401fd3a6811dbdb32881ba48"
[[package]] [[package]]
name = "finl_unicode" name = "finl_unicode"
@@ -674,17 +743,7 @@ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"r-efi 6.0.0", "r-efi 6.0.0",
] "rand_core 0.10.1",
[[package]]
name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash",
] ]
[[package]] [[package]]
@@ -759,10 +818,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]] [[package]]
name = "indoc" name = "indexmap"
version = "2.0.7" version = "2.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "indoc"
version = "2.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a37b2691796cffeb8a8cd305ac66e65841559f147f4e63231d0eafa4db5384d1"
dependencies = [ dependencies = [
"rustversion", "rustversion",
] ]
@@ -777,7 +846,7 @@ dependencies = [
"indoc", "indoc",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.6", "syn 3.0.9",
] ]
[[package]] [[package]]
@@ -815,13 +884,15 @@ dependencies = [
[[package]] [[package]]
name = "irtt-rs" name = "irtt-rs"
version = "0.8.0" version = "0.9.0"
dependencies = [ dependencies = [
"chrono", "chrono",
"clap", "clap",
"clap-num",
"crossterm", "crossterm",
"ctrlc", "ctrlc",
"futures-core", "futures-core",
"hex",
"irtt-client", "irtt-client",
"irtt-proto", "irtt-proto",
"irtt-server", "irtt-server",
@@ -886,11 +957,11 @@ dependencies = [
[[package]] [[package]]
name = "kasuari" name = "kasuari"
version = "0.4.12" version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bde5057d6143cc94e861d90f591b9303d6716c6b9602309150bd068853c10899" checksum = "72f14c90e40a6133f4805b83542a7ed7364f56070b447f56e7f4e90297122209"
dependencies = [ dependencies = [
"hashbrown 0.16.1", "hashbrown",
"portable-atomic", "portable-atomic",
"thiserror 2.0.21", "thiserror 2.0.21",
] ]
@@ -909,9 +980,9 @@ checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.190" version = "0.2.191"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78" checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a"
[[package]] [[package]]
name = "libm" name = "libm"
@@ -936,9 +1007,9 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
[[package]] [[package]]
name = "litrs" name = "litrs"
version = "1.0.0" version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" checksum = "4744e383959f0db86ede514b809b1c53251889093803c05267acc7d4e7030d70"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
@@ -961,16 +1032,16 @@ version = "0.18.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef9ac18847474e638e3702b76c65d4eb93428471a74778ef0f1be711717f89b5" checksum = "ef9ac18847474e638e3702b76c65d4eb93428471a74778ef0f1be711717f89b5"
dependencies = [ dependencies = [
"hashbrown 0.17.1", "hashbrown",
] ]
[[package]] [[package]]
name = "mac_address" name = "mac_address"
version = "1.2.0" version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b367b50a9be9a5d3b5718c1da74e5d6b9c17647f89752ee2562a470cc3c4eb1a" checksum = "f3d0581a75c45969c63afd2bb87b1d8b25cfcc556c7a918cf9bb13b0fe8d2381"
dependencies = [ dependencies = [
"nix 0.30.1", "nix 0.31.3",
"windows-sys", "windows-sys",
] ]
@@ -1041,19 +1112,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "nix"
version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
dependencies = [
"bitflags 2.13.2",
"cfg-if",
"cfg_aliases",
"libc",
"memoffset",
]
[[package]] [[package]]
name = "nix" name = "nix"
version = "0.31.3" version = "0.31.3"
@@ -1105,9 +1163,9 @@ dependencies = [
[[package]] [[package]]
name = "num_threads" name = "num_threads"
version = "0.1.7" version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" checksum = "cabcc654086861f9e1bc5115258de62ef92c79b20f4615a1b0cc57c4e82027d4"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@@ -1206,19 +1264,21 @@ dependencies = [
[[package]] [[package]]
name = "pest" name = "pest"
version = "2.9.2" version = "2.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45d3aca230fad2e6f6317ca0a72724338c4960cb97168a85cdee66df4a9a21a8" checksum = "fd47a460a9cbc3b3429c7e22bf0d76653e779319bae45a499ca010c9a990bb23"
dependencies = [ dependencies = [
"memchr", "memchr",
"psm",
"stacker",
"ucd-trie", "ucd-trie",
] ]
[[package]] [[package]]
name = "pest_derive" name = "pest_derive"
version = "2.9.2" version = "2.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "284b60557f2c4a2e72ad3f2d34d42685a2fa4a6a61d0d2a10c0ae2a5e916c2cf" checksum = "203e46697708f7519ab1608393cf89239cafb46b8e6d08a8ffe359359b11ef44"
dependencies = [ dependencies = [
"pest", "pest",
"pest_generator", "pest_generator",
@@ -1226,22 +1286,22 @@ dependencies = [
[[package]] [[package]]
name = "pest_generator" name = "pest_generator"
version = "2.9.2" version = "2.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d9d1f08a115309ee99268cf85e5228e0e56aa9caf8841ec12866b6be07c3109" checksum = "e84ae6340a458d3d6cfc73cf328cf3cfe90b2484cd0de25eb66aa954615ed4d5"
dependencies = [ dependencies = [
"pest", "pest",
"pest_meta", "pest_meta",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.119", "syn 3.0.9",
] ]
[[package]] [[package]]
name = "pest_meta" name = "pest_meta"
version = "2.9.2" version = "2.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed93ba1a9ffcca32130a5188701c81c0c49cf00d4b7c5007d5148951d743adcb" checksum = "b57f92f32b3dbce6598fd8f5cdf1f06a6072570b8103fa21728fa9036923e710"
dependencies = [ dependencies = [
"pest", "pest",
] ]
@@ -1317,12 +1377,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a6394b9e965e73d0a289ee54f589087e2c676aedf60885baf52c76b771e4958" checksum = "4a6394b9e965e73d0a289ee54f589087e2c676aedf60885baf52c76b771e4958"
[[package]] [[package]]
name = "ppv-lite86" name = "proc-macro-crate"
version = "0.2.21" version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
dependencies = [ dependencies = [
"zerocopy", "toml_edit",
] ]
[[package]] [[package]]
@@ -1336,16 +1396,17 @@ dependencies = [
[[package]] [[package]]
name = "proptest" name = "proptest"
version = "1.11.0" version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" checksum = "b8530004ccb15eae51c7e40009fbe317f341f804db54dc033eec1c50be28cfa0"
dependencies = [ dependencies = [
"bit-set 0.8.0", "bit-set 0.11.1",
"bit-vec 0.8.0", "bit-vec 0.10.1",
"bitflags 2.13.2", "bitflags 2.13.2",
"chacha20",
"core_detect",
"num-traits", "num-traits",
"rand 0.9.5", "rand 0.10.3",
"rand_chacha",
"rand_xorshift", "rand_xorshift",
"regex-syntax", "regex-syntax",
"rusty-fork", "rusty-fork",
@@ -1353,6 +1414,15 @@ dependencies = [
"unarray", "unarray",
] ]
[[package]]
name = "psm"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "200b9ff220857e53e184257720a14553b2f4aa02577d2ed9842d45d4b9654810"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "quick-error" name = "quick-error"
version = "1.2.3" version = "1.2.3"
@@ -1391,22 +1461,12 @@ dependencies = [
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.9.5" version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af"
dependencies = [ dependencies = [
"rand_chacha", "getrandom 0.4.3",
"rand_core 0.9.5", "rand_core 0.10.1",
]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core 0.9.5",
] ]
[[package]] [[package]]
@@ -1417,20 +1477,17 @@ checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
[[package]] [[package]]
name = "rand_core" name = "rand_core"
version = "0.9.5" version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
dependencies = [
"getrandom 0.3.4",
]
[[package]] [[package]]
name = "rand_xorshift" name = "rand_xorshift"
version = "0.4.0" version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" checksum = "60aa6af80be32871323012e02e6e65f8a7cc7890931ae421d217ad8fe0df2ccf"
dependencies = [ dependencies = [
"rand_core 0.9.5", "rand_core 0.10.1",
] ]
[[package]] [[package]]
@@ -1458,7 +1515,7 @@ dependencies = [
"bitflags 2.13.2", "bitflags 2.13.2",
"compact_str", "compact_str",
"critical-section", "critical-section",
"hashbrown 0.17.1", "hashbrown",
"itertools", "itertools",
"kasuari", "kasuari",
"lru", "lru",
@@ -1521,7 +1578,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66e3d19bcc9130ca376277d93b60767ff121ace3be06f5f95f81dd68956407d1" checksum = "66e3d19bcc9130ca376277d93b60767ff121ace3be06f5f95f81dd68956407d1"
dependencies = [ dependencies = [
"bitflags 2.13.2", "bitflags 2.13.2",
"hashbrown 0.17.1", "hashbrown",
"indoc", "indoc",
"instability", "instability",
"itertools", "itertools",
@@ -1657,7 +1714,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.6", "syn 3.0.9",
] ]
[[package]] [[package]]
@@ -1667,7 +1724,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"cpufeatures", "cpufeatures 0.2.17",
"digest 0.10.7", "digest 0.10.7",
] ]
@@ -1722,9 +1779,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.16.2" version = "1.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e" checksum = "5b3dc8af474f516a851ff4bd12db780f948b9250ad37211e4eec0bccea54e01b"
[[package]] [[package]]
name = "socket2" name = "socket2"
@@ -1736,6 +1793,19 @@ dependencies = [
"windows-sys", "windows-sys",
] ]
[[package]]
name = "stacker"
version = "0.1.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "707f49d46706bacf8a2b00d51dace3f9de527c13eec3778f570c411f89e69967"
dependencies = [
"cc",
"cfg-if",
"libc",
"psm",
"windows-sys",
]
[[package]] [[package]]
name = "static_assertions" name = "static_assertions"
version = "1.1.0" version = "1.1.0"
@@ -1799,9 +1869,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "3.0.6" version = "3.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -1934,7 +2004,7 @@ checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.6", "syn 3.0.9",
] ]
[[package]] [[package]]
@@ -1960,9 +2030,9 @@ checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.53.2" version = "1.54.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e95f91fcc7a621e8b030f6aa23c71fe9838ae2fb4d8118b75602a328f5144044" checksum = "a0c168a474b95bfecfe908a65d4bdffc523144b649a74c09c286afb35d53e0bc"
dependencies = [ dependencies = [
"libc", "libc",
"mio", "mio",
@@ -1980,7 +2050,37 @@ checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.6", "syn 3.0.9",
]
[[package]]
name = "toml_datetime"
version = "1.1.2+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b86d767906c6c42421dcba507eb9d203e779497710a47782a224bb871653053"
dependencies = [
"serde_core",
]
[[package]]
name = "toml_edit"
version = "0.25.17+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3641d5bbb5349a79e1020a242d251efbc546ad8048d133958323ce9c40a9c9c"
dependencies = [
"indexmap",
"toml_datetime",
"toml_parser",
"winnow",
]
[[package]]
name = "toml_parser"
version = "1.1.5+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baa693a8032d7e1cada7d0041e96126df243179ff061456783ac7f12bda4744c"
dependencies = [
"winnow",
] ]
[[package]] [[package]]
@@ -2003,9 +2103,9 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.26" version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
[[package]] [[package]]
name = "unicode-segmentation" name = "unicode-segmentation"
@@ -2038,9 +2138,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.27.0" version = "1.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97277d36b9c3ace13e58fa6e753f8b0bbbf302a18dd193240d70f9e29681059a" checksum = "7cc1186384beb7dd8eedea376413fd654937285ea6c9cfbb928dc3043ea4b606"
dependencies = [ dependencies = [
"atomic", "atomic",
"getrandom 0.4.3", "getrandom 0.4.3",
@@ -2119,7 +2219,7 @@ dependencies = [
"bumpalo", "bumpalo",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.6", "syn 3.0.9",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@@ -2294,28 +2394,17 @@ dependencies = [
"windows-link", "windows-link",
] ]
[[package]]
name = "winnow"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "wit-bindgen" name = "wit-bindgen"
version = "0.57.1" version = "0.57.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
[[package]]
name = "zerocopy"
version = "0.8.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879fb705ce98c32e41ebdb970fbe1204f8492423b314c6ab0354c3e7b5542866"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "708882a28301d604fa039cc7727607a98d04c4b86dc76ec9cc683f805d709759"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
+3 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "irtt-rs" name = "irtt-rs"
version = "0.8.0" version = "0.9.0"
description = "IRTT-compatible client, TUI, and server applications" description = "IRTT-compatible client, TUI, and server applications"
edition.workspace = true edition.workspace = true
license.workspace = true license.workspace = true
@@ -49,8 +49,10 @@ tui =["dep:tokio", "dep:futures-core", "dep:irtt-client", "irtt-client/ancillary
[dependencies] [dependencies]
chrono = { version = "0.4.45", default-features = false, features = ["clock"], optional = true } chrono = { version = "0.4.45", default-features = false, features = ["clock"], optional = true }
clap = { version = "4.6.7", features = ["derive", "env"] } clap = { version = "4.6.7", features = ["derive", "env"] }
clap-num = "=1.2.0"
crossterm = { version = "0.29.0", features = ["event-stream"], optional = true } crossterm = { version = "0.29.0", features = ["event-stream"], optional = true }
futures-core = { version = "0.3.34", default-features = false, optional = true } futures-core = { version = "0.3.34", default-features = false, optional = true }
hex = "0.4.3"
ctrlc = { version = "3.5.2", features = ["termination"] } ctrlc = { version = "3.5.2", features = ["termination"] }
irtt-client = { workspace = true, optional = true } irtt-client = { workspace = true, optional = true }
irtt-proto = { workspace = true, optional = true } irtt-proto = { workspace = true, optional = true }
+1 -1
View File
@@ -16,7 +16,7 @@ pub struct ClientArgs {
value_name = "TARGET", value_name = "TARGET",
num_args = 0.., num_args = 0..,
value_parser = parse_target, value_parser = parse_target,
long_help = "Server address/host, optionally prefixed with LABEL= and suffixed with @hmac=KEY. Repeat for multi-target mode.\n\nExamples:\n irtt-client host-a:2112 host-b:2112\n irtt-client eu=host-a:2112 us=host-b:2112\n irtt-client ams=host-a:2112@hmac=secret public=host-b:2112@hmac=\n\nA target without @hmac= inherits --hmac. @hmac= explicitly disables it for that target." long_help = "Server address/host, optionally prefixed with LABEL= and suffixed with @hmac=KEY. Repeat for multi-target mode. Keys use literal UTF-8 or lowercase 0x followed by hexadecimal bytes.\n\nExamples:\n irtt-client host-a:2112 host-b:2112\n irtt-client eu=host-a:2112 us=host-b:2112\n irtt-client ams=host-a:2112@hmac=secret public=host-b:2112@hmac=\n\nA target without @hmac= inherits --hmac. @hmac= explicitly disables it for that target."
)] )]
pub targets: Vec<TargetArg>, pub targets: Vec<TargetArg>,
+24 -16
View File
@@ -12,7 +12,7 @@ const DEFAULT_PORT: u16 = 2112;
// Every policy option is optional. An omitted option leaves the corresponding // Every policy option is optional. An omitted option leaves the corresponding
// `ServerConfig` default in place rather than restating it here, so library and // `ServerConfig` default in place rather than restating it here, so library and
// CLI defaults cannot drift apart. // CLI defaults cannot drift apart.
#[derive(Debug, Clone, Parser)] #[derive(Clone, Parser)]
#[command( #[command(
name = "irtt-server", name = "irtt-server",
about = "Minimal IRTT-compatible UDP server", about = "Minimal IRTT-compatible UDP server",
@@ -28,13 +28,14 @@ pub struct ServerArgs {
)] )]
pub bind: Vec<SocketAddr>, pub bind: Vec<SocketAddr>,
// Decode during configuration preparation so Clap cannot echo rejected keys.
/// HMAC key; every request must then carry a valid MAC. /// HMAC key; every request must then carry a valid MAC.
#[arg( #[arg(
long, long,
value_name = "KEY", value_name = "KEY",
value_parser = parse_hmac_key,
env = "IRTT_SERVER_HMAC", env = "IRTT_SERVER_HMAC",
long_help = "HMAC key, taken as the UTF-8 bytes of this argument.\n\nAuthentication is global: with a key configured, every request must carry a valid MAC and every reply is authenticated. Without one, authenticated requests are dropped. The key is visible in the process arguments, or in the environment as IRTT_SERVER_HMAC." hide_env_values = true,
long_help = "HMAC key: literal UTF-8 or lowercase 0x followed by hexadecimal bytes (digits may use either case). Empty keys and invalid hex are rejected.\n\nAuthentication is global: with a key configured, every request must carry a valid MAC and every reply is authenticated. Without one, authenticated requests are dropped. The key is visible in the process arguments, or in the environment as IRTT_SERVER_HMAC."
)] )]
pub hmac: Option<String>, pub hmac: Option<String>,
@@ -126,6 +127,23 @@ pub struct ServerArgs {
pub no_dscp: bool, pub no_dscp: bool,
} }
impl std::fmt::Debug for ServerArgs {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ServerArgs")
.field("hmac", &self.hmac.as_ref().map(|_| "[REDACTED]"))
.field("bind", &self.bind)
.field("max_sessions", &self.max_sessions)
.field("max_packet_length", &self.max_packet_length)
.field("min_interval", &self.min_interval)
.field("burst", &self.burst)
.field("idle_timeout", &self.idle_timeout)
.field("max_duration", &self.max_duration)
.field("timestamp_allowance", &self.timestamp_allowance)
.field("no_dscp", &self.no_dscp)
.finish()
}
}
/// The command-line spelling of [`TimestampAllowance`]. /// The command-line spelling of [`TimestampAllowance`].
/// ///
/// A CLI-local enum rather than a `clap` derive on the library type: `irtt-server` /// A CLI-local enum rather than a `clap` derive on the library type: `irtt-server`
@@ -166,11 +184,10 @@ impl ServerArgs {
} }
/// Builds the server configuration this invocation asks for. /// Builds the server configuration this invocation asks for.
#[must_use] pub fn server_config(&self) -> Result<ServerConfig, String> {
pub fn server_config(&self) -> ServerConfig {
let mut config = ServerConfig::default(); let mut config = ServerConfig::default();
if let Some(key) = self.hmac.as_ref() { if let Some(key) = self.hmac.as_ref() {
config = config.with_hmac_key(key.as_bytes()); config = config.with_hmac_key(crate::shared::decode_hmac_key(key)?);
} }
if let Some(value) = self.max_sessions { if let Some(value) = self.max_sessions {
config = config.with_max_sessions(value); config = config.with_max_sessions(value);
@@ -198,19 +215,10 @@ impl ServerArgs {
if self.no_dscp { if self.no_dscp {
config = config.with_dscp_allowed(false); config = config.with_dscp_allowed(false);
} }
config Ok(config)
} }
} }
/// Rejects an empty HMAC key so a blank shell expansion cannot silently
/// enable authentication with predictable, empty key material.
fn parse_hmac_key(input: &str) -> Result<String, String> {
if input.is_empty() {
return Err("HMAC key must not be empty".to_owned());
}
Ok(input.to_owned())
}
/// Parses a duration whose zero is meaningful server policy. /// Parses a duration whose zero is meaningful server policy.
fn parse_duration_allow_zero(input: &str) -> Result<Duration, String> { fn parse_duration_allow_zero(input: &str) -> Result<Duration, String> {
if input == "0" { if input == "0" {
+1 -1
View File
@@ -36,7 +36,7 @@ async fn serve(
args: ServerArgs, args: ServerArgs,
shutdown_requested: &AtomicBool, shutdown_requested: &AtomicBool,
) -> Result<(), Box<dyn std::error::Error>> { ) -> Result<(), Box<dyn std::error::Error>> {
let config = args.server_config(); let config = args.server_config()?;
let binds = args.resolve_binds(); let binds = args.resolve_binds();
let is_implicit_default = args.bind.is_empty(); let is_implicit_default = args.bind.is_empty();
let set = match ServerSet::bind(binds.clone(), config.clone()).await { let set = match ServerSet::bind(binds.clone(), config.clone()).await {
+1 -1
View File
@@ -18,7 +18,7 @@ pub struct TuiArgs {
num_args = 1.., num_args = 1..,
required = true, required = true,
value_parser = parse_target, value_parser = parse_target,
long_help = "Server address/host, optionally prefixed with LABEL= and suffixed with @hmac=KEY. Repeat for multi-target mode.\n\nExplicit labels are used in the legend and status table. A target without @hmac= inherits --hmac; @hmac= disables it for that target.\n\nExamples:\n irtt-tui host.example\n irtt-tui eu=host.example\n irtt-tui eu=host-a.example@hmac=secret us=host-b.example" long_help = "Server address/host, optionally prefixed with LABEL= and suffixed with @hmac=KEY. Repeat for multi-target mode. Keys use literal UTF-8 or lowercase 0x followed by hexadecimal bytes.\n\nExplicit labels are used in the legend and status table. A target without @hmac= inherits --hmac; @hmac= disables it for that target.\n\nExamples:\n irtt-tui host.example\n irtt-tui eu=host.example\n irtt-tui eu=host-a.example@hmac=secret us=host-b.example"
)] )]
pub targets: Vec<TargetArg>, pub targets: Vec<TargetArg>,
+53 -13
View File
@@ -1,4 +1,4 @@
use std::{fmt, time::Duration}; use std::{fmt, net::IpAddr, net::SocketAddr, time::Duration};
use clap::ValueEnum; use clap::ValueEnum;
use irtt_client::{MAX_DSCP_CODEPOINT, MAX_SERVER_FILL_BYTES, MAX_TTL, MAX_UDP_PAYLOAD_LENGTH}; use irtt_client::{MAX_DSCP_CODEPOINT, MAX_SERVER_FILL_BYTES, MAX_TTL, MAX_UDP_PAYLOAD_LENGTH};
@@ -26,8 +26,9 @@ pub struct CommonClientArgs {
#[arg(long, default_value_t = 0, value_parser = parse_length)] #[arg(long, default_value_t = 0, value_parser = parse_length)]
pub length: u32, pub length: u32,
/// HMAC key. // Decode during configuration preparation so Clap cannot echo rejected keys.
#[arg(long, value_parser = parse_hmac_key)] /// HMAC key: literal UTF-8 or lowercase 0x followed by hexadecimal bytes.
#[arg(long)]
pub hmac: Option<String>, pub hmac: Option<String>,
/// Clock mode to request. /// Clock mode to request.
@@ -70,6 +71,38 @@ pub struct CommonClientArgs {
#[arg(long, value_name = "1..=255", value_parser = parse_ttl)] #[arg(long, value_name = "1..=255", value_parser = parse_ttl)]
pub ttl: Option<u32>, pub ttl: Option<u32>,
#[arg(
long = "bind-addr",
value_name = "IP[:PORT]",
value_parser = parse_bind_addr,
long_help = "Local IP address and optional UDP port to bind. \
If no port is specified, the operating system selects \
an ephemeral port. IPv6 addresses with an explicit \
port must use [ADDR]:PORT syntax."
)]
pub bind_addr: Option<SocketAddr>,
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
#[arg(
long = "bind-device",
value_name = "DEVICE",
help = "Bind the socket to a network device"
)]
pub bind_to_device: Option<String>,
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
#[arg(
long,
value_name = "MARK",
value_parser = clap_num::maybe_hex::<u32>,
help = "Set the outgoing socket firewall mark"
)]
pub mark: Option<u32>,
#[cfg(target_os = "freebsd")]
#[arg(long, value_name = "FIB", help = "Set the socket routing table (FIB)")]
pub fib: Option<u32>,
/// Accept safe server restrictions during negotiation. /// Accept safe server restrictions during negotiation.
#[arg(long)] #[arg(long)]
pub loose: bool, pub loose: bool,
@@ -77,7 +110,8 @@ pub struct CommonClientArgs {
impl fmt::Debug for CommonClientArgs { impl fmt::Debug for CommonClientArgs {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CommonClientArgs") let mut debug = f.debug_struct("CommonClientArgs");
debug
.field("ipv4", &self.ipv4) .field("ipv4", &self.ipv4)
.field("ipv6", &self.ipv6) .field("ipv6", &self.ipv6)
.field("dual_stack", &self.dual_stack) .field("dual_stack", &self.dual_stack)
@@ -90,8 +124,14 @@ impl fmt::Debug for CommonClientArgs {
.field("server_fill", &self.server_fill) .field("server_fill", &self.server_fill)
.field("dscp", &self.dscp) .field("dscp", &self.dscp)
.field("ttl", &self.ttl) .field("ttl", &self.ttl)
.field("loose", &self.loose) .field("bind_addr", &self.bind_addr);
.finish() #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
debug
.field("bind_to_device", &self.bind_to_device)
.field("mark", &self.mark);
#[cfg(target_os = "freebsd")]
debug.field("fib", &self.fib);
debug.field("loose", &self.loose).finish()
} }
} }
@@ -152,6 +192,13 @@ impl From<ReceivedStatsArg> for ReceivedStats {
} }
} }
fn parse_bind_addr(input: &str) -> Result<SocketAddr, String> {
input
.parse::<SocketAddr>()
.or_else(|_| input.parse::<IpAddr>().map(|ip| SocketAddr::new(ip, 0)))
.map_err(|_| "expected an IP address, optionally with a port".to_owned())
}
pub fn parse_duration(input: &str) -> Result<Duration, String> { pub fn parse_duration(input: &str) -> Result<Duration, String> {
let (number, unit) = split_duration(input)?; let (number, unit) = split_duration(input)?;
let value: u64 = number let value: u64 = number
@@ -221,13 +268,6 @@ pub fn parse_length(input: &str) -> Result<u32, String> {
Ok(length) Ok(length)
} }
pub fn parse_hmac_key(input: &str) -> Result<String, String> {
if input.is_empty() {
return Err("HMAC key must not be empty".to_owned());
}
Ok(input.to_owned())
}
pub fn parse_server_fill(input: &str) -> Result<String, String> { pub fn parse_server_fill(input: &str) -> Result<String, String> {
if input.is_empty() { if input.is_empty() {
return Err("server fill must not be empty".to_owned()); return Err("server fill must not be empty".to_owned());
+14 -5
View File
@@ -21,8 +21,8 @@ impl CommonClientArgs {
/// Build the shared client configuration template for a managed run. /// Build the shared client configuration template for a managed run.
/// ///
/// The config is endpoint-independent; managed targets own their addresses. /// The config is endpoint-independent; managed targets own their addresses.
pub fn to_client_config(&self, duration: Duration) -> ClientConfig { pub fn to_client_config(&self, duration: Duration) -> Result<ClientConfig, String> {
ClientConfig { Ok(ClientConfig {
address_family: self.address_family(), address_family: self.address_family(),
open: OpenPolicy { open: OpenPolicy {
negotiation: if self.loose { negotiation: if self.loose {
@@ -45,16 +45,25 @@ impl CommonClientArgs {
}, },
auth: self auth: self
.hmac .hmac
.as_ref() .as_deref()
.map(crate::shared::decode_hmac_key)
.transpose()?
.map_or(Authentication::Unauthenticated, |key| { .map_or(Authentication::Unauthenticated, |key| {
Authentication::Hmac(HmacKey::new(key.as_bytes())) Authentication::Hmac(HmacKey::new(key))
}), }),
socket: SocketConfig { socket: SocketConfig {
bind_addr: self.bind_addr,
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
bind_to_device: self.bind_to_device.clone(),
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
mark: self.mark,
#[cfg(target_os = "freebsd")]
fib: self.fib,
ttl: self.ttl, ttl: self.ttl,
..SocketConfig::default() ..SocketConfig::default()
}, },
..ClientConfig::default() ..ClientConfig::default()
} })
} }
} }
+1 -1
View File
@@ -125,7 +125,7 @@ pub async fn prepare_managed_run(
.await?; .await?;
Ok(ManagedRunSetup { Ok(ManagedRunSetup {
targets, targets,
client: common.to_client_config(duration), client: common.to_client_config(duration)?,
pacing: selection.pacing.into(), pacing: selection.pacing.into(),
stdin_controlled: selection.stdin_controlled, stdin_controlled: selection.stdin_controlled,
preparation, preparation,
+7 -2
View File
@@ -88,12 +88,13 @@ impl From<GroupPacingArg> for ManagedPacing {
} }
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
enum TargetParseError { enum TargetParseError {
EmptyLabel, EmptyLabel,
EmptyAddress, EmptyAddress,
InvalidEscape, InvalidEscape,
EmptyTarget, EmptyTarget,
InvalidHmac(String),
} }
impl fmt::Display for TargetParseError { impl fmt::Display for TargetParseError {
@@ -103,6 +104,7 @@ impl fmt::Display for TargetParseError {
Self::EmptyAddress => "target address must not be empty", Self::EmptyAddress => "target address must not be empty",
Self::InvalidEscape => "invalid target escape sequence", Self::InvalidEscape => "invalid target escape sequence",
Self::EmptyTarget => "target must not be empty", Self::EmptyTarget => "target must not be empty",
Self::InvalidHmac(message) => message,
}; };
f.write_str(message) f.write_str(message)
} }
@@ -197,7 +199,10 @@ fn parse_target_syntax(
let auth = if value.is_empty() { let auth = if value.is_empty() {
TargetAuth::Override(Authentication::Unauthenticated) TargetAuth::Override(Authentication::Unauthenticated)
} else { } else {
TargetAuth::Override(Authentication::Hmac(HmacKey::new(value.into_bytes()))) TargetAuth::Override(Authentication::Hmac(HmacKey::new(
crate::shared::decode_hmac_key(&value)
.map_err(TargetParseError::InvalidHmac)?,
)))
}; };
(&input[..index], auth) (&input[..index], auth)
} }
+14
View File
@@ -0,0 +1,14 @@
/// Decode CLI key syntax without ever including key material in diagnostics.
pub(crate) fn decode_hmac_key(input: &str) -> Result<Vec<u8>, String> {
let key = match input.strip_prefix("0x") {
Some(hex) => hex::decode(hex).map_err(|_| {
"HMAC hexadecimal key must contain an even number of valid hexadecimal digits"
.to_owned()
})?,
None => input.as_bytes().to_vec(),
};
if key.is_empty() {
return Err("HMAC key must not be empty".to_owned());
}
Ok(key)
}
+5
View File
@@ -1,2 +1,7 @@
#[cfg(any(feature = "client", feature = "tui"))] #[cfg(any(feature = "client", feature = "tui"))]
pub mod client; pub mod client;
#[cfg(any(feature = "client", feature = "tui", feature = "server"))]
mod hmac;
#[cfg(any(feature = "client", feature = "tui", feature = "server"))]
pub(crate) use hmac::decode_hmac_key;
+1 -1
View File
@@ -18,4 +18,4 @@ subtle = "2.6.1"
thiserror = "2.0.21" thiserror = "2.0.21"
[dev-dependencies] [dev-dependencies]
proptest = "1.11.0" proptest = "1.12.0"
+1 -1
View File
@@ -19,4 +19,4 @@ irtt-client.workspace = true
name = "finite_vs_continuous" name = "finite_vs_continuous"
[dev-dependencies] [dev-dependencies]
proptest = "1.11.0" proptest = "1.12.0"
+22 -2
View File
@@ -37,12 +37,13 @@ overrides it for that target, and `@hmac=` explicitly disables HMAC for that
target. The modifier uses `@`, not a shell command separator; quote an argument target. The modifier uses `@`, not a shell command separator; quote an argument
if its key needs shell quoting. if its key needs shell quoting.
Global `--hmac KEY` and per-target `@hmac=KEY` accept plain-text keys or hexadecimal keys prefixed with `0x`. For example, `secret` and `0x736563726574` are equivalent.
Target syntax supports these backslash escapes in labels, addresses, and HMAC Target syntax supports these backslash escapes in labels, addresses, and HMAC
values: `\\` for a literal backslash, `\@` for a literal `@`, `\=` for a values: `\\` for a literal backslash, `\@` for a literal `@`, `\=` for a
literal `=`, and `\;` for a literal semicolon. Escaping `@` is necessary in a literal `=`, and `\;` for a literal semicolon. Escaping `@` is necessary in a
label or address when it would otherwise begin `@hmac=`; escaping the first label or address when it would otherwise begin `@hmac=`; escaping the first
`=` is how an unlabeled target or a label can contain that character. An `=` in `=` is how an unlabeled target or a label can contain that character. An `=` in an HMAC value does not need escaping. Positional
an HMAC value, including Base64 padding, does not need escaping. Positional
target arguments may contain a raw comma; stdin target sets have the additional target arguments may contain a raw comma; stdin target sets have the additional
`\,` escape described below. `\,` escape described below.
@@ -93,6 +94,25 @@ labels; choose another alias if one collides with an explicit label.
ordinary multi-target pacing and each sends its own probes, so dual-stack can ordinary multi-target pacing and each sends its own probes, so dual-stack can
double the number of probes per interval compared with one target per hostname. double the number of probes per interval compared with one target per hostname.
## LOCAL SOCKET CONFIGURATION
The client supports configuring its local UDP sockets using the following options:
- `--bind-addr IP[:PORT]` binds sockets to the specified local IP address and optional port. If no port is specified, or the port is `0`, the operating system assigns an ephemeral port. IPv6 addresses with an explicit port must use `[ADDR]:PORT` syntax. The address family must match the target's resolved address family.
- `--bind-device DEVICE` (Linux, Android, Fuchsia) binds sockets to a network interface using `SO_BINDTODEVICE`.
- `--mark MARK` (Linux, Android, Fuchsia) sets the socket's firewall mark using `SO_MARK`, allowing traffic to be selected by policy routing or firewall rules. Accepts a decimal or 0x-prefixed hexadecimal unsigned 32-bit integer.
- `--fib FIB` (FreeBSD) selects a routing table using `SO_SETFIB`. The FIB is specified as a decimal unsigned 32-bit integer.
For example, bind IPv4 probes to a specific source address and interface, with a firewall mark:
```sh
irtt-client -4 --bind-addr 192.0.2.10 --bind-device wan0 --mark 1234 host.example
```
Setting a firewall mark or binding to a network device may require elevated privileges or appropriate capabilities. If the operating system rejects a socket option, socket setup fails.
A fixed local port may cause binding conflicts when multiple target sockets are created. Use an ephemeral port unless a specific source port is required. A family-specific bind address is also incompatible with targets using the other address family.
## DYNAMIC TARGET SETS FROM STANDARD INPUT ## DYNAMIC TARGET SETS FROM STANDARD INPUT
`--targets-stdin` is available only with continuous mode (`--duration 0`). `--targets-stdin` is available only with continuous mode (`--duration 0`).
+3 -2
View File
@@ -74,8 +74,9 @@ IPv6 listener.
### `--hmac KEY` ### `--hmac KEY`
HMAC key, taken as the UTF-8 bytes of this argument. With a key configured, Accepts a plain-text key or a hexadecimal key prefixed with `0x`. For example, `secret` and `0x736563726574` are equivalent. The `IRTT_SERVER_HMAC` environment variable uses the same syntax.
every request must carry a valid MAC and every reply is authenticated;
With a key configured, every request must carry a valid MAC and every reply is authenticated;
without one, authenticated requests are dropped. The key is visible in the without one, authenticated requests are dropped. The key is visible in the
process arguments. process arguments.
+16 -16
View File
@@ -4,9 +4,9 @@ version = 4
[[package]] [[package]]
name = "arbitrary" name = "arbitrary"
version = "1.4.2" version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" checksum = "3bc62ac97cc33321f50863d514c3bc38a453947a8f9e781137e47c7401020aed"
[[package]] [[package]]
name = "autocfg" name = "autocfg"
@@ -31,9 +31,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.6.0" version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f74872d07caf508b30a21f6836e7d7016a2eaf7d9ff4f48deaa58cd8a0407630" checksum = "877bf67ac8e52449eb2f9e98a344ea163beb70dd1f9eebe3cc5357dcfeaee4df"
dependencies = [ dependencies = [
"find-msvc-tools", "find-msvc-tools",
"jobserver", "jobserver",
@@ -76,9 +76,9 @@ dependencies = [
[[package]] [[package]]
name = "ctutils" name = "ctutils"
version = "0.4.2" version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" checksum = "03bb0e1cc970d482d121d9a1744999169b69a07470b3d644a7894e53fcaf4574"
dependencies = [ dependencies = [
"cmov", "cmov",
] ]
@@ -97,9 +97,9 @@ dependencies = [
[[package]] [[package]]
name = "find-msvc-tools" name = "find-msvc-tools"
version = "0.1.14" version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" checksum = "1c66915cc5391097de7a2b0b972e8505aa33c0df401fd3a6811dbdb32881ba48"
[[package]] [[package]]
name = "getrandom" name = "getrandom"
@@ -174,9 +174,9 @@ dependencies = [
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.190" version = "0.2.191"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78" checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a"
[[package]] [[package]]
name = "libfuzzer-sys" name = "libfuzzer-sys"
@@ -285,9 +285,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]] [[package]]
name = "syn" name = "syn"
version = "3.0.6" version = "3.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -316,9 +316,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.53.2" version = "1.54.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e95f91fcc7a621e8b030f6aa23c71fe9838ae2fb4d8118b75602a328f5144044" checksum = "a0c168a474b95bfecfe908a65d4bdffc523144b649a74c09c286afb35d53e0bc"
dependencies = [ dependencies = [
"libc", "libc",
"mio", "mio",
@@ -347,9 +347,9 @@ checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.26" version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
[[package]] [[package]]
name = "wasi" name = "wasi"
+1 -1
View File
@@ -8,7 +8,7 @@ edition = "2021"
cargo-fuzz = true cargo-fuzz = true
[dependencies] [dependencies]
arbitrary = "1.4.2" arbitrary = "1.5.0"
libfuzzer-sys = "0.4.13" libfuzzer-sys = "0.4.13"
[dependencies.irtt-proto] [dependencies.irtt-proto]