Skip to content

ts_noise: factor out Noise implementation#186

Merged
danderson merged 3 commits into
mainfrom
push-tqznrnywrrvw
Jun 4, 2026
Merged

ts_noise: factor out Noise implementation#186
danderson merged 3 commits into
mainfrom
push-tqznrnywrrvw

Conversation

@danderson

@danderson danderson commented May 19, 2026

Copy link
Copy Markdown
Member

To enable both ts_tunnel and ts_control_noise to use the same underlying crypto primitives when possible.

Fixes #137

@danderson danderson force-pushed the push-tqznrnywrrvw branch 2 times, most recently from 300531e to 2ffc9ba Compare May 26, 2026 23:07
@danderson danderson force-pushed the push-tqznrnywrrvw branch 2 times, most recently from b62ef30 to c056f38 Compare June 3, 2026 02:18
@danderson

Copy link
Copy Markdown
Member Author

I think this is ready. There's probably some cleanups possible in how ts_tunnel and ts_control_noise use the common noise impls, but this was the minimally-ish invasive change needed to make the refactor happen. All tests pass of course, and also verified that examples/tcp_echo still works from a tailscale-go peer, which gives a proof that both the control protocol and wireguard work sufficiently to enable communication.

@npry npry left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good assuming the copy-paste typo on codec send/receive is resolved, everything else is non-blocking

Comment thread ts_noise/src/ik.rs
Comment on lines +54 to +63
/// Finalize the handshake and generate a response.
///
/// The response is written to `packet`, which must be exactly
/// [`ReceivedHandshake::RESP_SIZE`] bytes.
///
/// # Panics
///
/// If `packet` is the wrong size.
#[inline]
pub fn finish(self, packet: &mut [u8]) -> Session {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nonblocking/discussion: you could eliminate the internal length checking and panicking by explicitly making the length assertion the caller's responsibility (here and in the other handshake types):

Suggested change
/// Finalize the handshake and generate a response.
///
/// The response is written to `packet`, which must be exactly
/// [`ReceivedHandshake::RESP_SIZE`] bytes.
///
/// # Panics
///
/// If `packet` is the wrong size.
#[inline]
pub fn finish(self, packet: &mut [u8]) -> Session {
/// Finalize the handshake and generate a response
#[inline]
pub fn finish(self, packet: &mut [u8; Self::RESP_SIZE]) -> Session {

It makes it more annoying to call, but also makes the panic condition more visible to the caller (which notionally it has to check anyway), so it feels arguably worthwhile here

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. So the issue I have with this is that if the caller has a &[u8], converting that to a fixed size array is (afaik) a headache unless you make a copy, or use something like zerocopy to safely transmute the slice into something more structured.

In ts_tunnel we use zerocopy extensively, so there it would probably be fine, but I'm not sure how that fits with ts_control_noise.

In principle, I'd like to do this, but I'm going to defer to a followup PR just because it's one of those things where plumbing the change through the code may spiral out of control and unravel, so I want the damage contained 😂

Comment thread ts_noise/src/core.rs Outdated
Comment thread ts_control_noise/src/codec.rs Outdated
Comment thread ts_control_noise/src/codec.rs Outdated
Comment thread ts_control_noise/Cargo.toml Outdated
Comment thread ts_noise/src/ik.rs
Comment on lines +196 to +197
// These values were verified by hand to be identical to the packets produced by the
// third-party noise-protocol crate.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nonblocking on this PR, but would be nice in the future to have proptests comparing against a third-party implementation

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I considered doing this, but the third-party implementations have awkward APIs (due to trying to handle all handshake patterns in a single API), which made this quite painful. So I settled on using a 3p library to generate test vectors and burned them in here.

I'll have another go at an interop test in a followup.

@nrc nrc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One nit, one question (non-blocking) inline. Also there's quite a few [u8; 32]s in various places, I wonder if it is worth wrapping these in a struct to distinguish between keys and hashes, and maybe different kinds of keys?

Comment thread ts_tunnel/src/messages.rs
}
}

pub enum MessageMut<'packet> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this split be handled by having Message have it's payloads by value rather than reference and then using &Message or &mut Message rather than needing two enums?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought this too, but I tried it and it's a layout issue — the enum discriminator needs to physically live somewhere, so you can't cast ref-to-variant into enum or vice versa

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The payloads are zerocopy transmutations of a byte slice, so they can't own the values without copying, and that's what I'm trying to avoid. It's possible Message could take ownership of the underlying bytes and then have accessors for the transmuted view, but that's a larger refactor than I want to take on in this change.

I do want to try and get rid of this Message/MessageMut split though, it's unpleasant and strongly suggests the code structure isn't quite right.

Comment thread ts_control_noise/src/codec.rs Outdated

@dylan-tailscale dylan-tailscale left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just did a skim, but lgtm

Comment thread ts_noise/src/core.rs Outdated
Comment thread ts_noise/src/core.rs
@danderson danderson force-pushed the push-tqznrnywrrvw branch from c056f38 to 875d4ce Compare June 4, 2026 19:22
danderson added 3 commits June 4, 2026 12:24
Also switch to use aws-lc-rs. We already have to pull in aws-lc-rs
for rustls, so avoid using rustcrypto as well for binary size.

Signed-off-by: David Anderson <danderson@tailscale.com>
Change-Id: I690c8c138843750e0486ff9b1cf36df26a6a6964
Signed-off-by: David Anderson <danderson@tailscale.com>
Change-Id: Id81c21d18d19205b249d89d477a3d9a96a6a6964
Signed-off-by: David Anderson <danderson@tailscale.com>
Change-Id: Iffa6cb966097979b738ae22a946ac2aa6a6a6964
@danderson danderson force-pushed the push-tqznrnywrrvw branch from 875d4ce to 1ea6755 Compare June 4, 2026 19:24
@danderson danderson enabled auto-merge (rebase) June 4, 2026 19:26
@danderson danderson merged commit c06d303 into main Jun 4, 2026
27 checks passed
@danderson danderson deleted the push-tqznrnywrrvw branch June 4, 2026 19:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ts_control_noise, ts_tunnel: consolidate noise crypto

4 participants