Skip to content

Commit 839bfaa

Browse files
authored
feat(iroh): Allow connecting with "fallback" ALPNs (#3282)
## Description Allows connecting with multiple ALPNs so you can actually take advantage of ALPN also on the connecting side to support connections with older accept sides. ## Notes & open questions This only implements this via the `ConnectOptions`, which I think is fair? I generally like the pattern of `connect` -> `connect_with_opts`, and I don't think this is worth changing `connect` for. Closes #2949 ## Change checklist - [x] Self-review. - [x] Documentation updates following the [style guide](https://rust-lang.github.io/rfcs/1574-more-api-documentation-conventions.html#appendix-a-full-conventions-text), if relevant. - [x] Tests if relevant.
1 parent 7b4bce8 commit 839bfaa

File tree

1 file changed

+127
-2
lines changed

1 file changed

+127
-2
lines changed

iroh/src/endpoint.rs

Lines changed: 127 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ impl Builder {
253253
/// Sets the [ALPN] protocols that this endpoint will accept on incoming connections.
254254
///
255255
/// Not setting this will still allow creating connections, but to accept incoming
256-
/// connections the [ALPN] must be set.
256+
/// connections at least one [ALPN] must be set.
257257
///
258258
/// [ALPN]: https://en.wikipedia.org/wiki/Application-Layer_Protocol_Negotiation
259259
pub fn alpns(mut self, alpn_protocols: Vec<Vec<u8>>) -> Self {
@@ -752,7 +752,8 @@ impl Endpoint {
752752
"Attempting connection..."
753753
);
754754
let client_config = {
755-
let alpn_protocols = vec![alpn.to_vec()];
755+
let mut alpn_protocols = vec![alpn.to_vec()];
756+
alpn_protocols.extend(options.additional_alpns);
756757
let quic_client_config = self.static_config.tls_auth.make_client_config(
757758
&self.static_config.secret_key,
758759
node_id,
@@ -1263,6 +1264,7 @@ impl Endpoint {
12631264
#[derive(Default, Debug, Clone)]
12641265
pub struct ConnectOptions {
12651266
transport_config: Option<Arc<TransportConfig>>,
1267+
additional_alpns: Vec<Vec<u8>>,
12661268
}
12671269

12681270
impl ConnectOptions {
@@ -1279,6 +1281,31 @@ impl ConnectOptions {
12791281
self.transport_config = Some(transport_config);
12801282
self
12811283
}
1284+
1285+
/// Sets [ALPN] identifiers that should be signaled as supported on connection, *in
1286+
/// addition* to the main [ALPN] identifier used in [`Endpoint::connect_with_opts`].
1287+
///
1288+
/// This allows connecting to servers that may only support older versions of your
1289+
/// protocol. In this case, you would add the older [ALPN] identifiers with this
1290+
/// function.
1291+
///
1292+
/// You'll know the final negotiated [ALPN] identifier once your connection was
1293+
/// established using [`Connection::alpn`], or even slightly earlier in the
1294+
/// handshake by using [`Connecting::alpn`].
1295+
/// The negotiated [ALPN] identifier may be any of the [ALPN] identifiers in this
1296+
/// list or the main [ALPN] used in [`Endpoint::connect_with_opts`].
1297+
///
1298+
/// The [ALPN] identifier order on the connect side doesn't matter, since it's the
1299+
/// accept side that determines the protocol.
1300+
///
1301+
/// For setting the supported [ALPN] identifiers on the accept side, see the endpoint
1302+
/// builder's [`Builder::alpns`] function.
1303+
///
1304+
/// [ALPN]: https://en.wikipedia.org/wiki/Application-Layer_Protocol_Negotiation
1305+
pub fn with_additional_alpns(mut self, alpns: Vec<Vec<u8>>) -> Self {
1306+
self.additional_alpns = alpns;
1307+
self
1308+
}
12821309
}
12831310

12841311
/// Future produced by [`Endpoint::accept`].
@@ -2779,4 +2806,102 @@ mod tests {
27792806

27802807
Ok(())
27812808
}
2809+
2810+
/// Configures the accept side to take `accept_alpns` ALPNs, then connects to it with `primary_connect_alpn`
2811+
/// with `secondary_connect_alpns` set, and finally returns the negotiated ALPN.
2812+
async fn alpn_connection_test(
2813+
accept_alpns: Vec<Vec<u8>>,
2814+
primary_connect_alpn: &[u8],
2815+
secondary_connect_alpns: Vec<Vec<u8>>,
2816+
) -> testresult::TestResult<Option<Vec<u8>>> {
2817+
let client = Endpoint::builder()
2818+
.relay_mode(RelayMode::Disabled)
2819+
.bind()
2820+
.await?;
2821+
let server = Endpoint::builder()
2822+
.relay_mode(RelayMode::Disabled)
2823+
.alpns(accept_alpns)
2824+
.bind()
2825+
.await?;
2826+
let server_addr = server.node_addr().await?;
2827+
let server_task = tokio::spawn({
2828+
let server = server.clone();
2829+
async move {
2830+
let incoming = server.accept().await.unwrap();
2831+
let conn = incoming.await?;
2832+
conn.close(0u32.into(), b"bye!");
2833+
testresult::TestResult::Ok(conn.alpn())
2834+
}
2835+
});
2836+
2837+
let conn = client
2838+
.connect_with_opts(
2839+
server_addr,
2840+
primary_connect_alpn,
2841+
ConnectOptions::new().with_additional_alpns(secondary_connect_alpns),
2842+
)
2843+
.await?;
2844+
let conn = conn.await?;
2845+
let client_alpn = conn.alpn();
2846+
conn.closed().await;
2847+
client.close().await;
2848+
server.close().await;
2849+
2850+
let server_alpn = server_task.await??;
2851+
2852+
assert_eq!(client_alpn, server_alpn);
2853+
2854+
Ok(server_alpn)
2855+
}
2856+
2857+
#[tokio::test]
2858+
#[traced_test]
2859+
async fn connect_multiple_alpn_negotiated() -> testresult::TestResult {
2860+
const ALPN_ONE: &[u8] = b"alpn/1";
2861+
const ALPN_TWO: &[u8] = b"alpn/2";
2862+
2863+
assert_eq!(
2864+
alpn_connection_test(
2865+
// Prefer version 2 over version 1 on the accept side
2866+
vec![ALPN_TWO.to_vec(), ALPN_ONE.to_vec()],
2867+
ALPN_TWO,
2868+
vec![ALPN_ONE.to_vec()],
2869+
)
2870+
.await?,
2871+
Some(ALPN_TWO.to_vec()),
2872+
"accept side prefers version 2 over 1"
2873+
);
2874+
2875+
assert_eq!(
2876+
alpn_connection_test(
2877+
// Only support the old version
2878+
vec![ALPN_ONE.to_vec()],
2879+
ALPN_TWO,
2880+
vec![ALPN_ONE.to_vec()],
2881+
)
2882+
.await?,
2883+
Some(ALPN_ONE.to_vec()),
2884+
"accept side only supports the old version"
2885+
);
2886+
2887+
assert_eq!(
2888+
alpn_connection_test(
2889+
vec![ALPN_TWO.to_vec(), ALPN_ONE.to_vec()],
2890+
ALPN_ONE,
2891+
vec![ALPN_TWO.to_vec()],
2892+
)
2893+
.await?,
2894+
Some(ALPN_TWO.to_vec()),
2895+
"connect side ALPN order doesn't matter"
2896+
);
2897+
2898+
assert_eq!(
2899+
alpn_connection_test(vec![ALPN_TWO.to_vec(), ALPN_ONE.to_vec()], ALPN_ONE, vec![],)
2900+
.await?,
2901+
Some(ALPN_ONE.to_vec()),
2902+
"connect side only supports the old version"
2903+
);
2904+
2905+
Ok(())
2906+
}
27822907
}

0 commit comments

Comments
 (0)