From 60403a85d55fd3ce13022918a441c742600f84a0 Mon Sep 17 00:00:00 2001 From: Nathan Perry Date: Tue, 28 Apr 2026 11:22:45 -0400 Subject: [PATCH 1/8] time: fix underflow panic on range construction Since `Instant` is a monotonic counter, it's not generally safe to add and subtract `Duration`s from it without controlling for under/overflow. While overflow may be relatively safely assumed not to occur, that's not the case for underflow: in the `ts_tunnel` tests, a `TimeRange` was constructed with an 1000s window around `Instant::now()`, but the testing VMs are ephemeral and so hadn't been alive for 1k seconds, which induced a panic. This clamps the range to the provided `Instant` on overflow in `ts_time::TimeRange::new_around`. Unfortunately there's no `saturating_{sub,add}` for `std::time::Instant`, which I would've preferred here. Signed-off-by: Nathan Perry Change-Id: I614ccd1ec53f91f731f34d63d242b3906a6a6964 --- ts_time/src/lib.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ts_time/src/lib.rs b/ts_time/src/lib.rs index 82978702..c3faabd6 100644 --- a/ts_time/src/lib.rs +++ b/ts_time/src/lib.rs @@ -26,8 +26,14 @@ impl TimeRange { } /// Return a time range centered on `t`, with `plus_minus` time on either side. + /// + /// If `t` can't add or subtract `plus_minus`, the respective end of the range is + /// clamped to `t` instead. pub fn new_around(t: Instant, plus_minus: Duration) -> Self { - Self::new(t - plus_minus, t + plus_minus) + Self::new( + t.checked_sub(plus_minus).unwrap_or(t), + t.checked_add(plus_minus).unwrap_or(t), + ) } /// The first [`Instant`] that the interval covers. From 26cfad6d34e37def11a2b44ba0677673ca29d0c7 Mon Sep 17 00:00:00 2001 From: Nathan Perry Date: Tue, 28 Apr 2026 10:43:06 -0400 Subject: [PATCH 2/8] rust-toolchain: remove armv7 target Doesn't make sense to install this by default anymore. Signed-off-by: Nathan Perry Change-Id: Ie552891d4598b0a0e110443e6ed939926a6a6964 --- rust-toolchain.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index cf06b692..0d8ed42d 100755 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,3 @@ [toolchain] channel = "1.94.0" -targets = ["armv7-unknown-linux-gnueabihf"] components = ["rustfmt", "clippy"] From c6f98fe3f5d9e8fa8ac09f55dc8fc34783baa0a9 Mon Sep 17 00:00:00 2001 From: Nathan Perry Date: Tue, 28 Apr 2026 10:43:06 -0400 Subject: [PATCH 3/8] .github/setup-rust: include build-triple as target Signed-off-by: Nathan Perry Change-Id: I6cb998776295caa85f3e80fc7ce687846a6a6964 --- .github/actions/setup-rust/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup-rust/action.yml b/.github/actions/setup-rust/action.yml index ed23936d..dda7783e 100644 --- a/.github/actions/setup-rust/action.yml +++ b/.github/actions/setup-rust/action.yml @@ -59,7 +59,7 @@ runs: with: toolchain: ${{ inputs.toolchain-version }} components: ${{ inputs.components }} - targets: ${{ env.targets }} + targets: ${{ inputs.builder-triple }},${{ inputs.targets }} - name: Cache target/ id: cache-rust-target From d53d23f3111f5191ebf3708179cee5cca700ac54 Mon Sep 17 00:00:00 2001 From: Nathan Perry Date: Tue, 28 Apr 2026 10:43:06 -0400 Subject: [PATCH 4/8] .github/elixir: fix build-triple name Signed-off-by: Nathan Perry Change-Id: Icde2069d2d79f712581889e70078f0676a6a6964 --- .github/workflows/elixir.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index 9fb65e80..42f386d1 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -82,7 +82,7 @@ jobs: uses: ./.github/actions/setup-rust with: toolchain-version: ${{ matrix.rust_toolchain.version }} - builder-triple: x86_64-linux-unknown-gnu + builder-triple: x86_64-unknown-linux-gnu - name: Install dependencies run: mix deps.get From 85fbd185e55860d628ea9f6602dea3a906c53419 Mon Sep 17 00:00:00 2001 From: Nathan Perry Date: Tue, 28 Apr 2026 10:43:06 -0400 Subject: [PATCH 5/8] .github/rust: avoid repeating toolchain details Rather than specifying the toolchain everywhere via `+$TOOLCHAIN`, use `rustup override set` in the composite action. Configure common items in the rust ci action via env vars. Signed-off-by: Nathan Perry Change-Id: I98fcdb1e9f3b358c958d0a5f23af91a76a6a6964 --- .github/actions/setup-rust/action.yml | 4 ++++ .github/workflows/ci.yml | 34 +++++++++++++++++---------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/.github/actions/setup-rust/action.yml b/.github/actions/setup-rust/action.yml index dda7783e..c87da649 100644 --- a/.github/actions/setup-rust/action.yml +++ b/.github/actions/setup-rust/action.yml @@ -70,3 +70,7 @@ runs: key: rust-target-${{ inputs.cache-key }}-${{ inputs.builder-triple }}-${{ inputs.toolchain-version }}-${{ hashFiles('**/Cargo.lock') }} restore-keys: rust-target-${{ inputs.cache-key }}-${{ inputs.builder-triple }}-${{ inputs.toolchain-version }}- + + - name: Set toolchain override + shell: bash + run: rustup override set ${{ inputs.toolchain-version }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c449a185..d75f6e84 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,6 +26,9 @@ on: env: CARGO_TERM_COLOR: always + CARGO_TERM_VERBOSE: true + RUST_BACKTRACE: full + crates_environment: &crates_environment ${{ case(inputs.crates_repo == 'prod', 'crates.io', startsWith(github.ref, 'refs/tags/'), 'crates.io', 'staging.crates.io') }} is_tag_push: ${{ startsWith(github.ref, 'refs/tags/') }} @@ -63,6 +66,11 @@ jobs: # status check matching on job name doesn't support regexes, just an exact job name match. name: test (rust ${{ matrix.toolchain.label }}, ${{ matrix.target.triple }}) runs-on: ${{ matrix.target.runner }} + + env: + COMMON_FLAGS: --all-features --workspace + CARGO_BUILD_TARGET: ${{ matrix.target.triple }} + steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -75,28 +83,28 @@ jobs: components: clippy - name: Lint lib targets (cargo clippy) run: |- - cargo +${{ matrix.toolchain.version }} clippy \ - --all-features --workspace --no-deps \ + cargo clippy \ + $COMMON_FLAGS \ + --no-deps \ --lib \ - --target ${{ matrix.target.triple }} \ -- -D warnings - # These are separated from `--lib` to avoid enforcing missing docs in targets that - # can't become part of public API + # These are separated from `--lib` to avoid enforcing missing docs in targets that + # can't become part of public API - name: Lint other targets (cargo clippy) run: |- - cargo +${{ matrix.toolchain.version }} clippy \ - --all-features --workspace --no-deps \ + cargo clippy \ + $COMMON_FLAGS \ + --no-deps \ --bins --tests --benches --examples \ - --target ${{ matrix.target.triple }} \ -- -D warnings -A missing_docs - name: Build (cargo build) - run: cargo +${{ matrix.toolchain.version }} build --all-features --workspace --verbose --all-targets --target ${{ matrix.target.triple }} + run: cargo build $COMMON_FLAGS --all-targets - name: Test (cargo test) - run: cargo +${{ matrix.toolchain.version }} test --all-features --workspace --verbose --target ${{ matrix.target.triple }} + run: cargo test $COMMON_FLAGS - name: Release build (cargo build --release) - run: cargo +${{ matrix.toolchain.version }} build --all-features --workspace --release --verbose --all-targets --target ${{ matrix.target.triple }} + run: cargo build $COMMON_FLAGS --release --all-targets - name: Docs (cargo doc) - run: cargo +${{ matrix.toolchain.version }} doc --workspace --no-deps --all-features --target ${{ matrix.target.triple }} + run: cargo doc $COMMON_FLAGS --no-deps arch_independent: name: arch-independent checks @@ -126,7 +134,7 @@ jobs: - name: Dependency audit (cargo deny) run: cargo deny --workspace --all-features check all - name: Check formatting (cargo fmt) - run: cargo +nightly fmt --check + run: cargo fmt --check - name: Custom workspace checks run: cargo run -p checks - &dry_publish From ac71ebde0fbaf3b83834fd0c1d6d23d15af4c287 Mon Sep 17 00:00:00 2001 From: Nathan Perry Date: Tue, 28 Apr 2026 10:43:06 -0400 Subject: [PATCH 6/8] .github/rust: pin binstall by sha Signed-off-by: Nathan Perry Change-Id: I4b10b5e80f8562ead1bf039fd5c192be6a6a6964 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d75f6e84..952818d7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -121,7 +121,7 @@ jobs: components: rustfmt - &binstall name: binstall - uses: cargo-bins/cargo-binstall@main + uses: cargo-bins/cargo-binstall@dc19f1e48450eefe5a29b8da6c6b00a87d730b37 #v1.18.1 - name: Install cargo-deny run: command -v cargo-deny || cargo binstall --no-confirm cargo-deny - &cargo_ws From 1fb419fae598ed64f1ea76e1e8b7c5c11c5de090 Mon Sep 17 00:00:00 2001 From: Nathan Perry Date: Wed, 29 Apr 2026 15:54:44 -0400 Subject: [PATCH 7/8] cli_util: restrict tracy to non *-windows-gnu It doesn't seem to work here, just don't depend on it or use it on this platform. Signed-off-by: Nathan Perry Change-Id: Ia64e4899b2373672e3c2768742453e836a6a6964 --- Cargo.lock | 1 + ts_cli_util/Cargo.toml | 3 +++ ts_cli_util/src/lib.rs | 9 ++++++++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 087427d4..25e2ef7d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4142,6 +4142,7 @@ dependencies = [ name = "ts_cli_util" version = "0.2.0" dependencies = [ + "cfg-if", "clap", "futures-util", "tailscale", diff --git a/ts_cli_util/Cargo.toml b/ts_cli_util/Cargo.toml index 7596c3cb..46a30d74 100644 --- a/ts_cli_util/Cargo.toml +++ b/ts_cli_util/Cargo.toml @@ -19,11 +19,14 @@ ts_netcheck.workspace = true ts_transport_derp.workspace = true # Unconditionally required dependencies. +cfg-if.workspace = true clap = { workspace = true, features = ["derive", "env"] } futures-util.workspace = true tracing.workspace = true tracing-subscriber = { version = "0.3", features = ["env-filter", "tracing-log"] } + +[target.'cfg(not(all(target_os = "windows", target_env = "gnu")))'.dependencies] # Required dependencies for the "tracy" feature. tracy-client = { version = "0.18", optional = true } tracing-tracy = { version = "0.11", optional = true } diff --git a/ts_cli_util/src/lib.rs b/ts_cli_util/src/lib.rs index e439c9a7..f126306f 100644 --- a/ts_cli_util/src/lib.rs +++ b/ts_cli_util/src/lib.rs @@ -11,6 +11,7 @@ use ts_transport_derp::{RegionId, ServerConnInfo}; /// Result with a boxed [`core::error::Error`] trait object. pub type Result = core::result::Result>; +#[cfg(not(all(target_os = "windows", target_env = "gnu")))] #[cfg(feature = "tracy")] #[global_allocator] static GLOBAL_ALLOC: tracy_client::ProfiledAllocator = @@ -93,7 +94,13 @@ pub fn init_tracing() { #[cfg(feature = "tracy")] { - layers.push(tracing_tracy::TracyLayer::default().boxed()); + cfg_if::cfg_if! { + if #[cfg(not(all(target_os = "windows", target_env = "gnu")))] { + layers.push(tracing_tracy::TracyLayer::default().boxed()); + } else { + eprintln!("warning: ts_cli_util/tracy feature enabled but this is a noop on *-windows-gnu"); + } + } } tracing_subscriber::registry().with(layers).init(); From 4169695909699b0ee9aef2458b7ac67580818dba Mon Sep 17 00:00:00 2001 From: Nathan Perry Date: Tue, 28 Apr 2026 10:43:06 -0400 Subject: [PATCH 8/8] .github/rust: build and test on windows Updates #65 Signed-off-by: Nathan Perry Change-Id: I5381bc94a5817a69cd848df929b5860c6a6a6964 --- .github/workflows/ci.yml | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 952818d7..576852e7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,6 +37,11 @@ env: prev_rust: &prev_rust 1.93.1 latest_rust: &latest_rust 1.94.0 + +defaults: + run: + shell: bash + jobs: build_test: strategy: @@ -51,6 +56,10 @@ jobs: runner: linux-arm64-16cpu - triple: aarch64-apple-darwin runner: macos-26 + - triple: x86_64-pc-windows-gnu + runner: windows-8vcpu + - triple: x86_64-pc-windows-msvc + runner: windows-8vcpu toolchain: - version: *prev_rust label: prev @@ -70,6 +79,7 @@ jobs: env: COMMON_FLAGS: --all-features --workspace CARGO_BUILD_TARGET: ${{ matrix.target.triple }} + is_windows_gnu: ${{ endsWith(matrix.target.triple, '-windows-gnu') }} steps: - name: Checkout @@ -99,8 +109,16 @@ jobs: -- -D warnings -A missing_docs - name: Build (cargo build) run: cargo build $COMMON_FLAGS --all-targets - - name: Test (cargo test) - run: cargo test $COMMON_FLAGS + + # `ts_python` has the same lib name as the `tailscale` root crate, which again appears + # to just be an issue on `*-windows-gnu` targets, so don't test it here (depend on + # python tests instead). + - name: Test (cargo test), --all-features + run: |- + cargo test \ + $COMMON_FLAGS \ + ${{ case(env.is_windows_gnu == 'true', '--exclude ts_python', '') }} + - name: Release build (cargo build --release) run: cargo build $COMMON_FLAGS --release --all-targets - name: Docs (cargo doc)