Skip to content

Commit 7f17d98

Browse files
feat: relay only configuration
This is a next step into the world of configurable transports. We now allow disabling the IP based transports entirely. Internally this starts to prepare for a world where the user can configure multiple different transports, IP, relay and others in the future. Closes #2957
1 parent 6ef582d commit 7f17d98

File tree

7 files changed

+413
-178
lines changed

7 files changed

+413
-178
lines changed

iroh/bench/src/iroh.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::{
66
use bytes::Bytes;
77
use iroh::{
88
Endpoint, EndpointAddr, RelayMode, RelayUrl,
9-
endpoint::{Connection, ConnectionError, RecvStream, SendStream, TransportConfig},
9+
endpoint::{Connection, ConnectionError, QuinnTransportConfig, RecvStream, SendStream},
1010
};
1111
use n0_error::{Result, StackResultExt, StdResultExt};
1212
use tracing::{trace, warn};
@@ -126,10 +126,10 @@ pub async fn connect_client(
126126
Ok((endpoint, connection))
127127
}
128128

129-
pub fn transport_config(max_streams: usize, initial_mtu: u16) -> TransportConfig {
129+
pub fn transport_config(max_streams: usize, initial_mtu: u16) -> QuinnTransportConfig {
130130
// High stream windows are chosen because the amount of concurrent streams
131131
// is configurable as a parameter.
132-
let mut config = TransportConfig::default();
132+
let mut config = QuinnTransportConfig::default();
133133
config.max_concurrent_uni_streams(max_streams.try_into().unwrap());
134134
config.initial_mtu(initial_mtu);
135135

iroh/src/endpoint.rs

Lines changed: 179 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ pub use quinn::{
5656
ConnectionClose, ConnectionError, ConnectionStats, MtuDiscoveryConfig, OpenBi, OpenUni,
5757
PathStats, ReadDatagram, ReadError, ReadExactError, ReadToEndError, RecvStream, ResetError,
5858
RetryError, SendDatagramError, SendStream, ServerConfig, StoppedError, StreamId,
59-
TransportConfig, VarInt, WeakConnectionHandle, WriteError,
59+
TransportConfig as QuinnTransportConfig, VarInt, WeakConnectionHandle, WriteError,
6060
};
6161
pub use quinn_proto::{
6262
FrameStats, TransportError, TransportErrorCode, UdpStats, Written,
@@ -72,6 +72,7 @@ pub use self::connection::{
7272
Incoming, IncomingZeroRttConnection, OutgoingZeroRttConnection, RemoteEndpointIdError,
7373
ZeroRttStatus,
7474
};
75+
pub use crate::magicsock::transports::TransportConfig;
7576

7677
/// The delay to fall back to discovery when direct addresses fail.
7778
///
@@ -101,7 +102,6 @@ pub enum PathSelection {
101102
#[derive(Debug)]
102103
pub struct Builder {
103104
secret_key: Option<SecretKey>,
104-
relay_mode: RelayMode,
105105
alpn_protocols: Vec<Vec<u8>>,
106106
transport_config: quinn::TransportConfig,
107107
keylog: bool,
@@ -112,13 +112,27 @@ pub struct Builder {
112112
dns_resolver: Option<DnsResolver>,
113113
#[cfg(any(test, feature = "test-utils"))]
114114
insecure_skip_relay_cert_verify: bool,
115-
addr_v4: Option<SocketAddrV4>,
116-
addr_v6: Option<SocketAddrV6>,
115+
transports: Vec<TransportConfig>,
117116
#[cfg(any(test, feature = "test-utils"))]
118117
path_selection: PathSelection,
119118
max_tls_tickets: usize,
120119
}
121120

121+
impl From<RelayMode> for Option<TransportConfig> {
122+
fn from(mode: RelayMode) -> Self {
123+
match mode {
124+
RelayMode::Disabled => None,
125+
RelayMode::Default => Some(TransportConfig::Relay {
126+
relay_map: mode.relay_map(),
127+
}),
128+
RelayMode::Staging => Some(TransportConfig::Relay {
129+
relay_map: mode.relay_map(),
130+
}),
131+
RelayMode::Custom(relay_map) => Some(TransportConfig::Relay { relay_map }),
132+
}
133+
}
134+
}
135+
122136
impl Builder {
123137
// The ordering of public methods is reflected directly in the documentation. This is
124138
// roughly ordered by what is most commonly needed by users.
@@ -140,9 +154,18 @@ impl Builder {
140154
pub fn empty(relay_mode: RelayMode) -> Self {
141155
let mut transport_config = quinn::TransportConfig::default();
142156
transport_config.keep_alive_interval(Some(Duration::from_secs(1)));
157+
158+
let mut transports = vec![
159+
#[cfg(not(wasm_browser))]
160+
TransportConfig::default_ipv4(),
161+
#[cfg(not(wasm_browser))]
162+
TransportConfig::default_ipv6(),
163+
];
164+
if let Some(relay) = relay_mode.into() {
165+
transports.push(relay);
166+
}
143167
Self {
144168
secret_key: Default::default(),
145-
relay_mode,
146169
alpn_protocols: Default::default(),
147170
transport_config: quinn::TransportConfig::default(),
148171
keylog: Default::default(),
@@ -153,11 +176,10 @@ impl Builder {
153176
dns_resolver: None,
154177
#[cfg(any(test, feature = "test-utils"))]
155178
insecure_skip_relay_cert_verify: false,
156-
addr_v4: None,
157-
addr_v6: None,
158179
#[cfg(any(test, feature = "test-utils"))]
159180
path_selection: PathSelection::default(),
160181
max_tls_tickets: DEFAULT_MAX_TLS_TICKETS,
182+
transports,
161183
}
162184
}
163185

@@ -166,7 +188,6 @@ impl Builder {
166188
/// Binds the magic endpoint.
167189
pub async fn bind(mut self) -> Result<Endpoint, BindError> {
168190
let mut rng = rand::rng();
169-
let relay_map = self.relay_mode.relay_map();
170191
let secret_key = self
171192
.secret_key
172193
.unwrap_or_else(move || SecretKey::generate(&mut rng));
@@ -194,10 +215,8 @@ impl Builder {
194215
let metrics = EndpointMetrics::default();
195216

196217
let msock_opts = magicsock::Options {
197-
addr_v4: self.addr_v4,
198-
addr_v6: self.addr_v6,
218+
transports: self.transports,
199219
secret_key,
200-
relay_map,
201220
discovery_user_data: self.discovery_user_data,
202221
proxy_url: self.proxy_url,
203222
#[cfg(not(wasm_browser))]
@@ -230,25 +249,46 @@ impl Builder {
230249

231250
// # The very common methods everyone basically needs.
232251

233-
/// Sets the IPv4 bind address.
252+
/// Adds an IP transport, binding to the provided IPv4 address.
253+
///
254+
/// If you want to remove the default transports, make sure to call `clear_ip` first.
234255
///
235256
/// Setting the port to `0` will use a random port.
236257
/// If the port specified is already in use, it will fallback to choosing a random port.
237-
///
238-
/// By default will use `0.0.0.0:0` to bind to.
239-
pub fn bind_addr_v4(mut self, addr: SocketAddrV4) -> Self {
240-
self.addr_v4.replace(addr);
258+
#[cfg(not(wasm_browser))]
259+
pub fn bind_addr_v4(mut self, bind_addr: SocketAddrV4) -> Self {
260+
self.transports.push(TransportConfig::Ip {
261+
bind_addr: bind_addr.into(),
262+
});
241263
self
242264
}
243265

244-
/// Sets the IPv6 bind address.
266+
/// Adds an IP transport, binding to the provided IPv6 address.
267+
///
268+
/// If you want to remove the default transports, make sure to call `clear_ip` first.
245269
///
246270
/// Setting the port to `0` will use a random port.
247271
/// If the port specified is already in use, it will fallback to choosing a random port.
248-
///
249-
/// By default will use `[::]:0` to bind to.
250-
pub fn bind_addr_v6(mut self, addr: SocketAddrV6) -> Self {
251-
self.addr_v6.replace(addr);
272+
#[cfg(not(wasm_browser))]
273+
pub fn bind_addr_v6(mut self, bind_addr: SocketAddrV6) -> Self {
274+
self.transports.push(TransportConfig::Ip {
275+
bind_addr: bind_addr.into(),
276+
});
277+
self
278+
}
279+
280+
/// Removes all IP based transports
281+
#[cfg(not(wasm_browser))]
282+
pub fn clear_ip_transports(mut self) -> Self {
283+
self.transports
284+
.retain(|t| !matches!(t, TransportConfig::Ip { .. }));
285+
self
286+
}
287+
288+
/// Removes all relay based transports
289+
pub fn clear_relay_transports(mut self) -> Self {
290+
self.transports
291+
.retain(|t| !matches!(t, TransportConfig::Relay { .. }));
252292
self
253293
}
254294

@@ -294,7 +334,24 @@ impl Builder {
294334
/// [crate docs]: crate
295335
/// [number 0]: https://n0.computer
296336
pub fn relay_mode(mut self, relay_mode: RelayMode) -> Self {
297-
self.relay_mode = relay_mode;
337+
let transport: Option<_> = relay_mode.into();
338+
match transport {
339+
Some(transport) => {
340+
if let Some(og) = self
341+
.transports
342+
.iter_mut()
343+
.find(|t| matches!(t, TransportConfig::Relay { .. }))
344+
{
345+
*og = transport;
346+
} else {
347+
self.transports.push(transport);
348+
}
349+
}
350+
None => {
351+
self.transports
352+
.retain(|t| !matches!(t, TransportConfig::Relay { .. }));
353+
}
354+
}
298355
self
299356
}
300357

@@ -865,8 +922,6 @@ impl Endpoint {
865922
let endpoint_id = self.id();
866923

867924
watch_addrs.or(watch_relay).map(move |(addrs, relays)| {
868-
debug_assert!(!addrs.is_empty(), "direct addresses must never be empty");
869-
870925
EndpointAddr::from_parts(
871926
endpoint_id,
872927
relays
@@ -1252,7 +1307,7 @@ impl Endpoint {
12521307
/// Options for the [`Endpoint::connect_with_opts`] function.
12531308
#[derive(Default, Debug, Clone)]
12541309
pub struct ConnectOptions {
1255-
transport_config: Option<Arc<TransportConfig>>,
1310+
transport_config: Option<Arc<quinn::TransportConfig>>,
12561311
additional_alpns: Vec<Vec<u8>>,
12571312
}
12581313

@@ -1266,7 +1321,7 @@ impl ConnectOptions {
12661321
}
12671322

12681323
/// Sets the QUIC transport config options for this connection.
1269-
pub fn with_transport_config(mut self, transport_config: Arc<TransportConfig>) -> Self {
1324+
pub fn with_transport_config(mut self, transport_config: Arc<quinn::TransportConfig>) -> Self {
12701325
self.transport_config = Some(transport_config);
12711326
self
12721327
}
@@ -1340,6 +1395,7 @@ fn proxy_url_from_env() -> Option<Url> {
13401395
#[derive(Debug, Clone, PartialEq, Eq)]
13411396
pub enum RelayMode {
13421397
/// Disable relay servers completely.
1398+
/// This means that neither listening nor dialing relays will be available.
13431399
Disabled,
13441400
/// Use the default relay map, with production relay servers from n0.
13451401
///
@@ -1837,6 +1893,103 @@ mod tests {
18371893
Ok(())
18381894
}
18391895

1896+
#[tokio::test]
1897+
#[traced_test]
1898+
async fn endpoint_two_relay_only_no_ip() -> Result {
1899+
// Connect two endpoints on the same network, via a relay server, without
1900+
// discovery.
1901+
let (relay_map, _relay_url, _relay_server_guard) = run_relay_server().await?;
1902+
let (node_addr_tx, node_addr_rx) = oneshot::channel();
1903+
1904+
#[instrument(name = "client", skip_all)]
1905+
async fn connect(
1906+
relay_map: RelayMap,
1907+
node_addr_rx: oneshot::Receiver<EndpointAddr>,
1908+
) -> Result<quinn::ConnectionError> {
1909+
let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0u64);
1910+
let secret = SecretKey::generate(&mut rng);
1911+
let ep = Endpoint::builder()
1912+
.secret_key(secret)
1913+
.alpns(vec![TEST_ALPN.to_vec()])
1914+
.insecure_skip_relay_cert_verify(true)
1915+
.relay_mode(RelayMode::Custom(relay_map))
1916+
.clear_ip_transports() // disable direct
1917+
.bind()
1918+
.await?;
1919+
info!(me = %ep.id().fmt_short(), "client starting");
1920+
let dst = node_addr_rx.await.anyerr()?;
1921+
1922+
info!(me = %ep.id().fmt_short(), "client connecting");
1923+
let conn = ep.connect(dst, TEST_ALPN).await?;
1924+
let mut send = conn.open_uni().await.anyerr()?;
1925+
send.write_all(b"hello").await.anyerr()?;
1926+
let mut paths = conn.paths().stream();
1927+
info!("Waiting for connection");
1928+
'outer: while let Some(infos) = paths.next().await {
1929+
info!(?infos, "new PathInfos");
1930+
for info in infos {
1931+
if info.is_ip() {
1932+
panic!("should not happen: {:?}", info);
1933+
}
1934+
if info.is_relay() {
1935+
break 'outer;
1936+
}
1937+
}
1938+
}
1939+
info!("Have relay connection");
1940+
send.write_all(b"close please").await.anyerr()?;
1941+
send.finish().anyerr()?;
1942+
Ok(conn.closed().await)
1943+
}
1944+
1945+
#[instrument(name = "server", skip_all)]
1946+
async fn accept(
1947+
relay_map: RelayMap,
1948+
node_addr_tx: oneshot::Sender<EndpointAddr>,
1949+
) -> Result {
1950+
let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(1u64);
1951+
let secret = SecretKey::generate(&mut rng);
1952+
let ep = Endpoint::builder()
1953+
.secret_key(secret)
1954+
.alpns(vec![TEST_ALPN.to_vec()])
1955+
.insecure_skip_relay_cert_verify(true)
1956+
.relay_mode(RelayMode::Custom(relay_map))
1957+
.clear_ip_transports()
1958+
.bind()
1959+
.await?;
1960+
ep.online().await;
1961+
let node_addr = ep.addr();
1962+
node_addr_tx.send(node_addr).unwrap();
1963+
1964+
info!(me = %ep.id().fmt_short(), "server starting");
1965+
let conn = ep.accept().await.anyerr()?.await.anyerr()?;
1966+
// let node_id = conn.remote_node_id()?;
1967+
// assert_eq!(node_id, src);
1968+
let mut recv = conn.accept_uni().await.anyerr()?;
1969+
let mut msg = [0u8; 5];
1970+
recv.read_exact(&mut msg).await.anyerr()?;
1971+
assert_eq!(&msg, b"hello");
1972+
info!("received hello");
1973+
let msg = recv.read_to_end(100).await.anyerr()?;
1974+
assert_eq!(msg, b"close please");
1975+
info!("received 'close please'");
1976+
// Dropping the connection closes it just fine.
1977+
Ok(())
1978+
}
1979+
1980+
let server_task = tokio::spawn(accept(relay_map.clone(), node_addr_tx));
1981+
let client_task = tokio::spawn(connect(relay_map, node_addr_rx));
1982+
1983+
server_task.await.anyerr()??;
1984+
let conn_closed = dbg!(client_task.await.anyerr()??);
1985+
assert!(matches!(
1986+
conn_closed,
1987+
ConnectionError::ApplicationClosed(quinn::ApplicationClose { .. })
1988+
));
1989+
1990+
Ok(())
1991+
}
1992+
18401993
#[tokio::test]
18411994
#[traced_test]
18421995
async fn endpoint_two_direct_add_relay() -> Result {

0 commit comments

Comments
 (0)