Skip to content

Commit 8970847

Browse files
authored
RUST-257 Properly fallback when unable to connect to first addre… (#123)
1 parent 3a6cd7e commit 8970847

File tree

2 files changed

+43
-15
lines changed

2 files changed

+43
-15
lines changed

src/cmap/conn/stream.rs

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::{
22
io::{self, Read, Write},
3-
net::{TcpStream, ToSocketAddrs},
3+
net::{SocketAddr, TcpStream, ToSocketAddrs},
44
sync::Arc,
55
time::Duration,
66
};
@@ -10,7 +10,7 @@ use derivative::Derivative;
1010
use webpki::DNSNameRef;
1111

1212
use crate::{
13-
error::Result,
13+
error::{ErrorKind, Result},
1414
options::{StreamAddress, TlsOptions},
1515
};
1616

@@ -38,27 +38,52 @@ pub(super) enum Stream {
3838
),
3939
}
4040

41+
fn try_connect(address: &SocketAddr, timeout: Duration) -> Result<TcpStream> {
42+
// The URI options spec requires that the default connect timeout is 10 seconds, but that 0
43+
// should indicate no timeout.
44+
let stream = if timeout == Duration::from_secs(0) {
45+
TcpStream::connect(address)?
46+
} else {
47+
TcpStream::connect_timeout(address, timeout)?
48+
};
49+
50+
Ok(stream)
51+
}
52+
53+
fn connect_stream(address: &StreamAddress, connect_timeout: Option<Duration>) -> Result<TcpStream> {
54+
let timeout = connect_timeout.unwrap_or(DEFAULT_CONNECT_TIMEOUT);
55+
56+
let mut socket_addrs: Vec<_> = address.to_socket_addrs()?.collect();
57+
58+
if socket_addrs.is_empty() {
59+
return Err(ErrorKind::NoDnsResults(address.clone()).into());
60+
}
61+
62+
// After considering various approaches, we decided to do what other drivers do, namely try each
63+
// of the addresses in sequence with a preference for IPv4.
64+
socket_addrs.sort_by_key(|addr| if addr.is_ipv4() { 0 } else { 1 });
65+
66+
let mut connect_error = None;
67+
68+
for address in &socket_addrs {
69+
connect_error = match try_connect(address, timeout) {
70+
Ok(stream) => return Ok(stream),
71+
Err(err) => Some(err),
72+
};
73+
}
74+
75+
Err(connect_error.unwrap_or_else(|| ErrorKind::NoDnsResults(address.clone()).into()))
76+
}
77+
4178
impl Stream {
4279
/// Creates a new stream connected to `address`.
4380
pub(super) fn connect(
4481
host: StreamAddress,
4582
connect_timeout: Option<Duration>,
4683
tls_options: Option<TlsOptions>,
4784
) -> Result<Self> {
48-
let timeout = connect_timeout.unwrap_or(DEFAULT_CONNECT_TIMEOUT);
49-
50-
// The URI options spec requires that the default is 10 seconds, but that 0 should indicate
51-
// no timeout.
52-
let inner = if timeout == Duration::from_secs(0) {
53-
TcpStream::connect(&host)?
54-
} else {
55-
let mut socket_addrs: Vec<_> = host.to_socket_addrs()?.collect();
56-
socket_addrs.sort_by_key(|addr| if addr.is_ipv4() { 0 } else { 1 });
57-
58-
TcpStream::connect_timeout(&socket_addrs[0], timeout)?
59-
};
85+
let inner = connect_stream(&host, connect_timeout)?;
6086
inner.set_nodelay(true)?;
61-
let inner = inner;
6287

6388
match tls_options {
6489
Some(cfg) => {

src/error.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@ pub enum ErrorKind {
124124
#[error(display = "{}", _0)]
125125
Io(#[error(source)] std::io::Error),
126126

127+
#[error(display = "No DNS results for domain {}", _0)]
128+
NoDnsResults(StreamAddress),
129+
127130
/// A database operation failed to send or receive a reply.
128131
#[error(
129132
display = "A database operation failed to send or receive a reply: {}",

0 commit comments

Comments
 (0)