Skip to content

Commit 5d5bf35

Browse files
authored
[HTTP/3] Optimize IPv6 fallback and enforce HTTPS scheme #2911 (#3006)
* optimize connection fallback and validate scheme * reformat * reformat * local_addr fix * loacl_add fixed
1 parent 93dc1b2 commit 5d5bf35

File tree

2 files changed

+93
-17
lines changed

2 files changed

+93
-17
lines changed

src/async_impl/h3_client/connect.rs

Lines changed: 77 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ pub(crate) struct H3Connector {
5959
resolver: DynResolver,
6060
endpoint: Endpoint,
6161
client_config: H3ClientConfig,
62+
bound_addr: IpAddr,
6263
}
6364

6465
impl H3Connector {
@@ -74,6 +75,7 @@ impl H3Connector {
7475
// FIXME: Replace this when there is a setter.
7576
config.transport_config(Arc::new(transport_config));
7677

78+
// Pipe the local address through to the endpoint creation
7779
let socket_addr = match local_addr {
7880
Some(ip) => SocketAddr::new(ip, 0),
7981
None => "[::]:0".parse::<SocketAddr>().unwrap(),
@@ -82,10 +84,14 @@ impl H3Connector {
8284
let mut endpoint = Endpoint::client(socket_addr)?;
8385
endpoint.set_default_client_config(config);
8486

87+
// Get the actual bound address from the endpoint
88+
let bound_addr = endpoint.local_addr()?.ip();
89+
8590
Ok(Self {
8691
resolver,
8792
endpoint,
8893
client_config,
94+
bound_addr,
8995
})
9096
}
9197

@@ -117,28 +123,83 @@ impl H3Connector {
117123
addrs: Vec<SocketAddr>,
118124
server_name: &str,
119125
) -> Result<H3Connection, BoxError> {
120-
let mut err = None;
126+
if addrs.is_empty() {
127+
return Err("no addresses to connect to".into());
128+
}
129+
130+
let (mut ipv6_addrs, mut ipv4_addrs): (Vec<SocketAddr>, Vec<SocketAddr>) =
131+
addrs.into_iter().partition(|addr| addr.is_ipv6());
132+
133+
if self.bound_addr.is_ipv6() {
134+
ipv4_addrs.clear();
135+
} else {
136+
ipv6_addrs.clear();
137+
}
138+
139+
if ipv6_addrs.is_empty() {
140+
return Self::try_addresses_static(
141+
&self.endpoint,
142+
&ipv4_addrs,
143+
server_name,
144+
&self.client_config,
145+
)
146+
.await;
147+
}
148+
if ipv4_addrs.is_empty() {
149+
return Self::try_addresses_static(
150+
&self.endpoint,
151+
&ipv6_addrs,
152+
server_name,
153+
&self.client_config,
154+
)
155+
.await;
156+
}
157+
158+
let endpoint = self.endpoint.clone();
159+
let client_config = self.client_config.clone();
160+
161+
match Self::try_addresses_static(&endpoint, &ipv6_addrs, server_name, &client_config).await
162+
{
163+
Ok(conn) => Ok(conn),
164+
Err(_) => {
165+
Self::try_addresses_static(&endpoint, &ipv4_addrs, server_name, &client_config)
166+
.await
167+
}
168+
}
169+
}
170+
171+
async fn try_addresses_static(
172+
endpoint: &Endpoint,
173+
addrs: &[SocketAddr],
174+
server_name: &str,
175+
client_config: &H3ClientConfig,
176+
) -> Result<H3Connection, BoxError> {
177+
let mut last_err: Option<BoxError> = None;
178+
121179
for addr in addrs {
122-
match self.endpoint.connect(addr, server_name)?.await {
123-
Ok(new_conn) => {
124-
let quinn_conn = Connection::new(new_conn);
125-
let mut h3_client_builder = h3::client::builder();
126-
if let Some(max_field_section_size) = self.client_config.max_field_section_size
127-
{
128-
h3_client_builder.max_field_section_size(max_field_section_size);
180+
match endpoint.connect(*addr, server_name) {
181+
Ok(connecting) => match connecting.await {
182+
Ok(new_conn) => {
183+
let quinn_conn = Connection::new(new_conn);
184+
let mut h3_client_builder = h3::client::builder();
185+
if let Some(max_field_section_size) = client_config.max_field_section_size {
186+
h3_client_builder.max_field_section_size(max_field_section_size);
187+
}
188+
if let Some(send_grease) = client_config.send_grease {
189+
h3_client_builder.send_grease(send_grease);
190+
}
191+
return Ok(h3_client_builder.build(quinn_conn).await?);
129192
}
130-
if let Some(send_grease) = self.client_config.send_grease {
131-
h3_client_builder.send_grease(send_grease);
193+
Err(e) => {
194+
last_err = Some(Box::new(e) as BoxError);
132195
}
133-
return Ok(h3_client_builder.build(quinn_conn).await?);
196+
},
197+
Err(e) => {
198+
last_err = Some(Box::new(e) as BoxError);
134199
}
135-
Err(e) => err = Some(e),
136200
}
137201
}
138202

139-
match err {
140-
Some(e) => Err(Box::new(e) as BoxError),
141-
None => Err("failed to establish connection for HTTP/3 request".into()),
142-
}
203+
Err(last_err.unwrap_or_else(|| "no addresses available".into()))
143204
}
144205
}

src/async_impl/h3_client/pool.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,22 @@ where
364364
pub(crate) fn extract_domain(uri: &mut Uri) -> Result<Key, Error> {
365365
let uri_clone = uri.clone();
366366
match (uri_clone.scheme(), uri_clone.authority()) {
367-
(Some(scheme), Some(auth)) => Ok((scheme.clone(), auth.clone())),
367+
(Some(scheme), Some(auth)) => {
368+
let scheme_str = scheme.as_str();
369+
if scheme_str != "https" && scheme_str != "h3" {
370+
return Err(Error::new(
371+
Kind::Request,
372+
Some(Box::new(std::io::Error::new(
373+
std::io::ErrorKind::InvalidInput,
374+
format!(
375+
"HTTP/3 only supports 'https' or 'h3' schemes, got: {}",
376+
scheme_str
377+
),
378+
))),
379+
));
380+
}
381+
Ok((scheme.clone(), auth.clone()))
382+
}
368383
_ => Err(Error::new(Kind::Request, None::<Error>)),
369384
}
370385
}

0 commit comments

Comments
 (0)