Skip to content

Commit 1bf0bd9

Browse files
committed
feat(client): add RFC 8305 (Happy Eyeballs v2) support
Implement RFC 8305 as an alternative connection strategy alongside the existing RFC 6555 behavior. Default behavior remains RFC 6555 for backward compatibility. RFC 8305 improvements over RFC 6555: - Address interleaving: alternates between address families (v6, v4, v6, v4...) - Staggered attempts: starts new connections at regular intervals - Better parallelism: multiple connections can race simultaneously Closes hyperium/hyper#2450
1 parent 8c4f4a0 commit 1bf0bd9

File tree

3 files changed

+649
-42
lines changed

3 files changed

+649
-42
lines changed

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ base64 = { version = "0.22", optional = true }
2222
bytes = "1.7.1"
2323
futures-channel = { version = "0.3", optional = true }
2424
futures-core = { version = "0.3" }
25-
futures-util = { version = "0.3.16", default-features = false, optional = true }
25+
futures-util = { version = "0.3.16", default-features = false, features = ["alloc"], optional = true }
2626
http = "1.0"
2727
http-body = "1.0.0"
2828
hyper = "1.8.0"
@@ -43,6 +43,7 @@ futures-util = { version = "0.3.16", default-features = false, features = ["allo
4343
http-body-util = "0.1.0"
4444
tokio = { version = "1", features = ["macros", "test-util", "signal"] }
4545
tokio-test = "0.4"
46+
tokio-util = { version = "0.7", features = ["rt"] }
4647
tower-test = "0.4"
4748
pretty_env_logger = "0.5"
4849

src/client/legacy/connect/dns.rs

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,8 +240,96 @@ impl SocketAddrs {
240240
pub(super) fn len(&self) -> usize {
241241
self.iter.as_slice().len()
242242
}
243+
244+
/// Create an interleaved address iterator per RFC 8305 (Happy Eyeballs v2) Section 4.
245+
///
246+
/// Takes `first_family_count` addresses from the preferred family,
247+
/// then interleaves remaining addresses: one fallback, one preferred, repeat.
248+
///
249+
/// Input: `[v6_1, v6_2, v4_1, v4_2]` (IPv6 preferred)
250+
/// Output: `[v6_1, v4_1, v6_2, v4_2]` (with `first_family_count=1`)
251+
pub(super) fn interleave_by_family(self, first_family_count: usize) -> InterleavedAddrs {
252+
InterleavedAddrs::new(self, first_family_count)
253+
}
254+
}
255+
256+
/// Iterator over addresses interleaved by family per RFC 8305 (Happy Eyeballs v2).
257+
pub(super) struct InterleavedAddrs {
258+
inner: vec::IntoIter<SocketAddr>,
259+
total: usize,
260+
}
261+
262+
impl InterleavedAddrs {
263+
fn new(addrs: SocketAddrs, first_family_count: usize) -> Self {
264+
let addrs: Vec<_> = addrs.iter.collect();
265+
let total = addrs.len();
266+
267+
if addrs.is_empty() {
268+
return InterleavedAddrs {
269+
inner: Vec::new().into_iter(),
270+
total: 0,
271+
};
272+
}
273+
274+
// Determine preferred family from first address
275+
let prefer_ipv6 = addrs[0].is_ipv6();
276+
277+
let (mut preferred, fallback): (Vec<_>, Vec<_>) = if prefer_ipv6 {
278+
addrs.into_iter().partition(|a| a.is_ipv6())
279+
} else {
280+
addrs.into_iter().partition(|a| a.is_ipv4())
281+
};
282+
283+
let mut result = Vec::with_capacity(total);
284+
285+
// Take first_family_count from preferred
286+
let take_count = first_family_count.min(preferred.len());
287+
result.extend(preferred.drain(..take_count));
288+
289+
// Interleave remaining: fallback, preferred, fallback, preferred...
290+
let mut pref_iter = preferred.into_iter();
291+
let mut fall_iter = fallback.into_iter();
292+
293+
loop {
294+
match (fall_iter.next(), pref_iter.next()) {
295+
(Some(f), Some(p)) => {
296+
result.push(f);
297+
result.push(p);
298+
}
299+
(Some(f), None) => result.push(f),
300+
(None, Some(p)) => result.push(p),
301+
(None, None) => break,
302+
}
303+
}
304+
305+
InterleavedAddrs {
306+
inner: result.into_iter(),
307+
total,
308+
}
309+
}
310+
311+
/// Total number of addresses (original count before any iteration).
312+
pub(super) fn total(&self) -> usize {
313+
self.total
314+
}
315+
}
316+
317+
impl Iterator for InterleavedAddrs {
318+
type Item = SocketAddr;
319+
320+
#[inline]
321+
fn next(&mut self) -> Option<SocketAddr> {
322+
self.inner.next()
323+
}
324+
325+
#[inline]
326+
fn size_hint(&self) -> (usize, Option<usize>) {
327+
self.inner.size_hint()
328+
}
243329
}
244330

331+
impl ExactSizeIterator for InterleavedAddrs {}
332+
245333
impl Iterator for SocketAddrs {
246334
type Item = SocketAddr;
247335
#[inline]
@@ -357,4 +445,101 @@ mod tests {
357445
assert_eq!(name.as_str(), DOMAIN);
358446
assert_eq!(name.to_string(), DOMAIN);
359447
}
448+
449+
// === RFC 8305 Address Interleaving Tests ===
450+
451+
#[test]
452+
fn test_interleave_by_family_basic() {
453+
// IPv6 preferred (first in list), interleave with IPv4
454+
let v6_1: SocketAddr = "[2001:db8::1]:80".parse().unwrap();
455+
let v6_2: SocketAddr = "[2001:db8::2]:80".parse().unwrap();
456+
let v4_1: SocketAddr = "192.0.2.1:80".parse().unwrap();
457+
let v4_2: SocketAddr = "192.0.2.2:80".parse().unwrap();
458+
459+
let addrs = SocketAddrs::new(vec![v6_1, v6_2, v4_1, v4_2]);
460+
let result: Vec<_> = addrs.interleave_by_family(1).collect();
461+
462+
// RFC 8305: first_family_count=1 means v6, v4, v6, v4...
463+
assert_eq!(result, vec![v6_1, v4_1, v6_2, v4_2]);
464+
}
465+
466+
#[test]
467+
fn test_interleave_by_family_empty() {
468+
let addrs = SocketAddrs::new(vec![]);
469+
let result: Vec<_> = addrs.interleave_by_family(1).collect();
470+
assert!(result.is_empty());
471+
}
472+
473+
#[test]
474+
fn test_interleave_by_family_single_family() {
475+
// All IPv4 - no interleaving needed
476+
let v4_1: SocketAddr = "192.0.2.1:80".parse().unwrap();
477+
let v4_2: SocketAddr = "192.0.2.2:80".parse().unwrap();
478+
let v4_3: SocketAddr = "192.0.2.3:80".parse().unwrap();
479+
480+
let addrs = SocketAddrs::new(vec![v4_1, v4_2, v4_3]);
481+
let result: Vec<_> = addrs.interleave_by_family(1).collect();
482+
483+
assert_eq!(result, vec![v4_1, v4_2, v4_3]);
484+
}
485+
486+
#[test]
487+
fn test_interleave_by_family_count_2() {
488+
// first_family_count=2: take 2 from preferred, then interleave
489+
let v6_1: SocketAddr = "[2001:db8::1]:80".parse().unwrap();
490+
let v6_2: SocketAddr = "[2001:db8::2]:80".parse().unwrap();
491+
let v6_3: SocketAddr = "[2001:db8::3]:80".parse().unwrap();
492+
let v4_1: SocketAddr = "192.0.2.1:80".parse().unwrap();
493+
let v4_2: SocketAddr = "192.0.2.2:80".parse().unwrap();
494+
495+
let addrs = SocketAddrs::new(vec![v6_1, v6_2, v6_3, v4_1, v4_2]);
496+
let result: Vec<_> = addrs.interleave_by_family(2).collect();
497+
498+
// First 2 v6, then interleave: v4, v6, v4
499+
assert_eq!(result, vec![v6_1, v6_2, v4_1, v6_3, v4_2]);
500+
}
501+
502+
#[test]
503+
fn test_interleave_by_family_count_0() {
504+
// first_family_count=0: immediate interleave, fallback first
505+
let v6_1: SocketAddr = "[2001:db8::1]:80".parse().unwrap();
506+
let v6_2: SocketAddr = "[2001:db8::2]:80".parse().unwrap();
507+
let v4_1: SocketAddr = "192.0.2.1:80".parse().unwrap();
508+
let v4_2: SocketAddr = "192.0.2.2:80".parse().unwrap();
509+
510+
let addrs = SocketAddrs::new(vec![v6_1, v6_2, v4_1, v4_2]);
511+
let result: Vec<_> = addrs.interleave_by_family(0).collect();
512+
513+
// Fallback first: v4, v6, v4, v6
514+
assert_eq!(result, vec![v4_1, v6_1, v4_2, v6_2]);
515+
}
516+
517+
#[test]
518+
fn test_interleave_by_family_count_exceeds() {
519+
// first_family_count exceeds available preferred addresses
520+
let v6_1: SocketAddr = "[2001:db8::1]:80".parse().unwrap();
521+
let v4_1: SocketAddr = "192.0.2.1:80".parse().unwrap();
522+
let v4_2: SocketAddr = "192.0.2.2:80".parse().unwrap();
523+
524+
let addrs = SocketAddrs::new(vec![v6_1, v4_1, v4_2]);
525+
let result: Vec<_> = addrs.interleave_by_family(5).collect();
526+
527+
// Only 1 v6, take it, then all v4s
528+
assert_eq!(result, vec![v6_1, v4_1, v4_2]);
529+
}
530+
531+
#[test]
532+
fn test_interleave_by_family_ipv4_preferred() {
533+
// IPv4 first in list means IPv4 is preferred
534+
let v4_1: SocketAddr = "192.0.2.1:80".parse().unwrap();
535+
let v4_2: SocketAddr = "192.0.2.2:80".parse().unwrap();
536+
let v6_1: SocketAddr = "[2001:db8::1]:80".parse().unwrap();
537+
let v6_2: SocketAddr = "[2001:db8::2]:80".parse().unwrap();
538+
539+
let addrs = SocketAddrs::new(vec![v4_1, v4_2, v6_1, v6_2]);
540+
let result: Vec<_> = addrs.interleave_by_family(1).collect();
541+
542+
// v4 preferred: v4, v6, v4, v6
543+
assert_eq!(result, vec![v4_1, v6_1, v4_2, v6_2]);
544+
}
360545
}

0 commit comments

Comments
 (0)