Skip to content

Commit 259d51d

Browse files
committed
ts_derp: factor out transport behavior
The baseline derp client doesn't need to know about translating NodeKeys to transport `PeerId`s, that's a higher level concern now provided by `ts_derp::Transport`, which wraps the underlying client (abstracted behind `NodekeyTransport`) with a `PeerLookup` in order to provide `UnderlayTransport`. This keeps the examples and other functionality dependent on bare derp working without needing to bring in a dummy peer id lookup layer. Signed-off-by: Nathan Perry <nathan@tailscale.com> Change-Id: Ib965f787e92880ac3d74c364760acc546a6a6964
1 parent 4e02574 commit 259d51d

10 files changed

Lines changed: 154 additions & 122 deletions

File tree

Cargo.lock

Lines changed: 0 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ts_derp/examples/listen.rs

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
//! Intended to test ping/pong/keepalive.
44
55
use ts_keys::NodeKeyPair;
6-
use ts_transport::UnderlayTransport;
76

87
mod common;
98

@@ -16,20 +15,16 @@ async fn main() -> ts_cli_util::Result<()> {
1615

1716
let keypair = NodeKeyPair::new();
1817

19-
let client =
20-
ts_derp::Client::connect(region, &keypair, ts_derp::DummyStaticLookup::default()).await?;
18+
let client = ts_derp::Client::connect(region, &keypair).await?;
2119
tracing::info!("derp handshake done");
2220

2321
loop {
24-
for result in client.recv().await {
25-
match result {
26-
Ok((peer, pkts)) => {
27-
let pkts = pkts.into_iter().collect::<Vec<_>>();
28-
tracing::info!(?peer, ?pkts);
29-
}
30-
Err(e) => {
31-
tracing::error!(err = %e, "recv");
32-
}
22+
match client.recv_one().await {
23+
Ok((peer, pkt)) => {
24+
tracing::info!(?peer, ?pkt);
25+
}
26+
Err(e) => {
27+
tracing::error!(err = %e, "recv");
3328
}
3429
}
3530
}

ts_derp/examples/ping.rs

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33
use std::{sync::Arc, time::Duration};
44

55
use tokio::task::JoinSet;
6-
use ts_derp::PeerLookup;
76
use ts_keys::NodeKeyPair;
8-
use ts_transport::UnderlayTransport;
97

108
mod common;
119

@@ -18,10 +16,7 @@ async fn main() -> ts_cli_util::Result<()> {
1816

1917
let keypair = NodeKeyPair::new();
2018

21-
let peer_map = &*Box::leak(Box::new(ts_derp::DummyStaticLookup::default()));
22-
let self_id = peer_map.key_to_id(&keypair.public).unwrap();
23-
24-
let client = ts_derp::Client::connect(region, &keypair, peer_map).await?;
19+
let client = ts_derp::Client::connect(region, &keypair).await?;
2520
tracing::info!("derp handshake done");
2621

2722
let client = Arc::new(client);
@@ -33,7 +28,7 @@ async fn main() -> ts_cli_util::Result<()> {
3328
let mut ticker = tokio::time::interval(Duration::from_secs(1));
3429

3530
loop {
36-
if let Err(e) = pinger.send([(self_id, vec![vec![1].into()])]).await {
31+
if let Err(e) = pinger.send_one(keypair.public, &[1]).await {
3732
tracing::error!(err = %e, "ping");
3833
} else {
3934
tracing::info!("ping");
@@ -47,10 +42,8 @@ async fn main() -> ts_cli_util::Result<()> {
4742
js.spawn(async move {
4843
loop {
4944
match recv.recv_one().await {
50-
Ok((peer_id, pkt)) => {
51-
let peer_key = peer_map.id_to_key(peer_id);
52-
53-
tracing::info!(?pkt, %peer_id, ?peer_key, "pong");
45+
Ok((peer_key, pkt)) => {
46+
tracing::info!(?pkt, %peer_key, "pong");
5447
}
5548
Err(e) => {
5649
tracing::error!(err = %e, "recv");
Lines changed: 31 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,25 @@ use tokio::{
88
};
99
use tokio_util::codec::{FramedRead, FramedWrite};
1010
use ts_http_util::Client as _;
11-
use ts_keys::NodeKeyPair;
11+
use ts_keys::{NodeKeyPair, NodePublicKey};
1212
use ts_packet::PacketMut;
13-
use ts_transport::{PeerId, UnderlayTransport};
1413
use url::Url;
1514

1615
use crate::{
17-
Error, ServerConnInfo, frame,
16+
Error, ServerConnInfo, Transport, frame,
1817
frame::{ClientInfo, FrameType, PeerGone, Ping, RawFrame, ServerInfo, ServerKey},
19-
peer_lookup::PeerLookup,
18+
transport::NodekeyTransport,
2019
};
2120

2221
type DefaultIo = ts_http_util::Upgraded;
2322

2423
/// Type alias for the default derp client over upgraded HTTP on a tokio executor.
25-
pub type DefaultClient<Lookup> = Client<DefaultIo, Lookup>;
24+
pub type DefaultClient = Client<DefaultIo>;
2625

27-
/// Asynchronous DERP transport for a single DERP region.
28-
pub struct Client<Io, Lookup> {
26+
/// Single-region DERP client.
27+
pub struct Client<Io> {
2928
read_conn: Mutex<FramedRead<ReadHalf<Io>, frame::Codec>>,
3029
write_conn: Mutex<FramedWrite<WriteHalf<Io>, frame::Codec>>,
31-
peer_lookup: Lookup,
3230
}
3331

3432
/// Establish and upgrade a http connection to the derp region.
@@ -56,18 +54,19 @@ pub async fn connect<'c>(
5654
Ok(Some(upgraded))
5755
}
5856

59-
impl<Io, Lookup> Client<Io, Lookup>
57+
impl<Io> Client<Io>
6058
where
6159
Io: AsyncRead + AsyncWrite,
62-
Lookup: PeerLookup,
6360
{
61+
/// Convert this derp client into a [`ts_transport::UnderlayTransport`] given the
62+
/// specified `Lookup`.
63+
pub fn into_transport<Lookup>(self, lookup: Lookup) -> Transport<Self, Lookup> {
64+
Transport::new(self, lookup)
65+
}
66+
6467
/// Perform a derp handshake over the given transport and return a [`Client`].
6568
#[tracing::instrument(skip_all)]
66-
pub async fn handshake(
67-
conn: Io,
68-
node_keypair: &NodeKeyPair,
69-
peer_lookup: Lookup,
70-
) -> Result<Self, Error> {
69+
pub async fn handshake(conn: Io, node_keypair: &NodeKeyPair) -> Result<Self, Error> {
7170
let (read_conn, write_conn) = tokio::io::split(conn);
7271

7372
let mut fw = FramedWrite::new(write_conn, frame::Codec);
@@ -121,10 +120,15 @@ where
121120
Ok(Self {
122121
read_conn: Mutex::new(fr),
123122
write_conn: Mutex::new(fw),
124-
peer_lookup,
125123
})
126124
}
127125

126+
/// Send a message to a nodekey on the derp server.
127+
pub async fn send_one(&self, node_key: NodePublicKey, msg: &[u8]) -> Result<(), Error> {
128+
self.send_frame_with_extra(&frame::SendPacket { dest: node_key }, msg)
129+
.await
130+
}
131+
128132
/// Send a frame to the derp server.
129133
pub async fn send_frame(
130134
&self,
@@ -151,7 +155,7 @@ where
151155

152156
/// Waits for a single data packet from a peer to arrive via this DERP server and returns it.
153157
/// DERP control messages (KeepAlive, Ping, etc) are handled inline and are not returned.
154-
pub async fn recv_one(&self) -> Result<(PeerId, PacketMut), Error> {
158+
pub async fn recv_one(&self) -> Result<(NodePublicKey, PacketMut), Error> {
155159
// DERP exchanges control messages (KeepAlives, Pings, etc) in-band with data messages
156160
// (SendPacket, RecvPacket, etc). The caller only cares about the payloads of data
157161
// messages, so we recv_one_raw() in a loop to handle any control messages while waiting
@@ -199,12 +203,8 @@ where
199203
}
200204
FrameType::RecvPacket => {
201205
let (recv, payload) = frame.as_type::<frame::RecvPacket>().unwrap();
202-
let Some(id) = self.peer_lookup.key_to_id(&recv.src) else {
203-
tracing::trace!(src = %recv.src, "no known peer for node key");
204-
continue;
205-
};
206206

207-
return Ok((id, payload.into()));
207+
return Ok((recv.src, payload.into()));
208208
}
209209
t => {
210210
return Err(Error::UnexpectedRecvFrameType(t));
@@ -214,29 +214,25 @@ where
214214
}
215215
}
216216

217-
impl<Lookup> Client<DefaultIo, Lookup>
218-
where
219-
Lookup: PeerLookup,
220-
{
217+
impl Client<DefaultIo> {
221218
/// Connect to and handshake with the derp server with the given URL over HTTP.
222219
pub async fn connect<'c>(
223220
region: impl IntoIterator<Item = &'c ServerConnInfo>,
224221
node_keypair: &NodeKeyPair,
225-
lookup: Lookup,
226222
) -> Result<Self, Error> {
227223
let conn = connect(region).await?.unwrap();
228224

229-
Client::handshake(conn, node_keypair, lookup).await
225+
Client::handshake(conn, node_keypair).await
230226
}
231227
}
232228

233-
impl<Io, Lookup> fmt::Debug for Client<Io, Lookup> {
229+
impl<Io> fmt::Debug for Client<Io> {
234230
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
235231
fmt::Display::fmt(self, f)
236232
}
237233
}
238234

239-
impl<Io, Lookup> fmt::Display for Client<Io, Lookup> {
235+
impl<Io> fmt::Display for Client<Io> {
240236
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
241237
f.debug_tuple("Client").finish()
242238
}
@@ -291,41 +287,15 @@ fn decrypt_server_info(
291287
Ok(sip)
292288
}
293289

294-
impl<Io, Lookup> UnderlayTransport for Client<Io, Lookup>
290+
impl<Io> NodekeyTransport for Client<Io>
295291
where
296292
Io: AsyncRead + AsyncWrite + Send,
297-
Lookup: PeerLookup,
298293
{
299-
type Error = Error;
300-
301-
#[tracing::instrument(fields(%self))]
302-
async fn recv(
303-
&self,
304-
) -> impl IntoIterator<Item = Result<(PeerId, impl IntoIterator<Item = PacketMut>), Self::Error>>
305-
{
306-
[self.recv_one().await.map(|(k, pkt)| (k, [pkt]))]
294+
async fn send_one(&self, node_key: NodePublicKey, body: &[u8]) -> Result<(), Error> {
295+
self.send_one(node_key, body).await
307296
}
308297

309-
/// Send a batch of packets to a peer via this DERP server.
310-
async fn send<BatchIter, PacketIter>(&self, peer_packets: BatchIter) -> Result<(), Self::Error>
311-
where
312-
BatchIter: IntoIterator<Item = (PeerId, PacketIter)> + Send,
313-
BatchIter::IntoIter: Send,
314-
PacketIter: IntoIterator<Item = PacketMut> + Send,
315-
PacketIter::IntoIter: Send,
316-
{
317-
for (peer, packets) in peer_packets {
318-
let Some(node_key) = self.peer_lookup.id_to_key(peer) else {
319-
tracing::warn!(peer_id = %peer, "no node key known for peer");
320-
continue;
321-
};
322-
323-
for packet in packets {
324-
self.send_frame_with_extra(&frame::SendPacket { dest: node_key }, packet.as_ref())
325-
.await?;
326-
}
327-
}
328-
329-
Ok(())
298+
async fn recv_one(&self) -> Result<(NodePublicKey, PacketMut), Error> {
299+
self.recv_one().await
330300
}
331301
}

ts_derp/src/lib.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,17 @@ use core::{
88

99
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
1010

11-
mod async_tokio;
11+
mod client;
1212
pub mod dial;
1313
mod error;
1414
pub mod frame;
1515
mod peer_lookup;
16+
mod transport;
1617

17-
pub use async_tokio::{Client, DefaultClient};
18+
pub use client::{Client, DefaultClient};
1819
pub use error::Error;
1920
pub use peer_lookup::{DummyStaticLookup, PeerLookup};
21+
pub use transport::Transport;
2022

2123
/// A 24-byte nonce for symmetric encryption with ChaCha20Poly1305.
2224
#[repr(C)]

ts_derp/src/peer_lookup.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ use std::sync::Mutex;
33
/// Trait providing conversion between [`ts_keys::NodePublicKey`] (required to send and
44
/// receive derp messages) and [`ts_transport::PeerId`] (tailscale-rs internal type).
55
pub trait PeerLookup: Send + Sync {
6-
/// Convert `key` to a [`ts_transport::PeerId`], allocating a new id if the peer doesn't
7-
/// exist yet.
6+
/// Convert `key` to a [`ts_transport::PeerId`].
87
fn key_to_id(&self, key: &ts_keys::NodePublicKey) -> Option<ts_transport::PeerId>;
98

109
/// Convert the `id` to a [`ts_keys::NodePublicKey`].

ts_derp/src/transport.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
use ts_keys::NodePublicKey;
2+
use ts_packet::PacketMut;
3+
use ts_transport::{PeerId, UnderlayTransport};
4+
5+
use crate::{Error, PeerLookup};
6+
7+
pub trait NodekeyTransport: Send + Sync {
8+
/// Send a message addressed to the given node key.
9+
fn send_one(
10+
&self,
11+
node_key: NodePublicKey,
12+
body: &[u8],
13+
) -> impl Future<Output = Result<(), Error>> + Send;
14+
15+
/// Receive a frame from a particular node key.
16+
fn recv_one(&self) -> impl Future<Output = Result<(NodePublicKey, PacketMut), Error>> + Send;
17+
}
18+
19+
/// An implementation of [`UnderlayTransport`] wrapping a `NodekeyTransport` with a
20+
/// [`PeerLookup`].
21+
pub struct Transport<NkT, Lookup> {
22+
inner: NkT,
23+
peer_lookup: Lookup,
24+
}
25+
26+
impl<NkT, Lookup> Transport<NkT, Lookup> {
27+
/// Construct a new [`Transport`] with the given `NodekeyTransport` and [`PeerLookup`].
28+
pub fn new(client: NkT, lookup: Lookup) -> Self {
29+
Self {
30+
inner: client,
31+
peer_lookup: lookup,
32+
}
33+
}
34+
35+
/// Destruct this [`Transport`] into its constituent parts.
36+
pub fn into_parts(self) -> (NkT, Lookup) {
37+
(self.inner, self.peer_lookup)
38+
}
39+
}
40+
41+
impl<NkT, Lookup> UnderlayTransport for Transport<NkT, Lookup>
42+
where
43+
NkT: NodekeyTransport,
44+
Lookup: PeerLookup,
45+
{
46+
type Error = Error;
47+
48+
async fn recv(
49+
&self,
50+
) -> impl IntoIterator<Item = Result<(PeerId, impl IntoIterator<Item = PacketMut>), Self::Error>>
51+
{
52+
match self.inner.recv_one().await {
53+
Ok((k, pkt)) => {
54+
let Some(id) = self.peer_lookup.key_to_id(&k) else {
55+
tracing::warn!(node_key = %k, lookup_ty = std::any::type_name::<Lookup>(), "no peer id known for node key");
56+
return None;
57+
};
58+
59+
Some(Ok((id, [pkt])))
60+
}
61+
Err(e) => Some(Err(e)),
62+
}
63+
}
64+
65+
/// Send a batch of packets to a peer via this DERP server.
66+
async fn send<BatchIter, PacketIter>(&self, peer_packets: BatchIter) -> Result<(), Self::Error>
67+
where
68+
BatchIter: IntoIterator<Item = (PeerId, PacketIter)> + Send,
69+
BatchIter::IntoIter: Send,
70+
PacketIter: IntoIterator<Item = PacketMut> + Send,
71+
PacketIter::IntoIter: Send,
72+
{
73+
for (peer, packets) in peer_packets {
74+
let Some(node_key) = self.peer_lookup.id_to_key(peer) else {
75+
tracing::warn!(peer_id = %peer, "no node key known for peer");
76+
continue;
77+
};
78+
79+
for packet in packets {
80+
self.inner.send_one(node_key, packet.as_ref()).await?;
81+
}
82+
}
83+
84+
Ok(())
85+
}
86+
}

0 commit comments

Comments
 (0)