Skip to content

Commit b46ea89

Browse files
committed
Add hostaddr support
1 parent 5433118 commit b46ea89

File tree

2 files changed

+91
-2
lines changed

2 files changed

+91
-2
lines changed

tokio-postgres/src/config.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use crate::{Client, Connection, Error};
1212
use std::borrow::Cow;
1313
#[cfg(unix)]
1414
use std::ffi::OsStr;
15+
use std::ops::Deref;
1516
#[cfg(unix)]
1617
use std::os::unix::ffi::OsStrExt;
1718
#[cfg(unix)]
@@ -90,6 +91,17 @@ pub enum Host {
9091
/// path to the directory containing Unix domain sockets. Otherwise, it is treated as a hostname. Multiple hosts
9192
/// can be specified, separated by commas. Each host will be tried in turn when connecting. Required if connecting
9293
/// with the `connect` method.
94+
/// * `hostaddr` - Numeric IP address of host to connect to. This should be in the standard IPv4 address format,
95+
/// e.g., 172.28.40.9. If your machine supports IPv6, you can also use those addresses.
96+
/// If this parameter is not specified, the value of `host` will be looked up to find the corresponding IP address,
97+
/// - or if host specifies an IP address, that value will be used directly.
98+
/// Using `hostaddr` allows the application to avoid a host name look-up, which might be important in applications
99+
/// with time constraints. However, a host name is required for verify-full SSL certificate verification.
100+
/// Note that `host` is always required regardless of whether `hostaddr` is present.
101+
/// * If `host` is specified without `hostaddr`, a host name lookup occurs;
102+
/// * If both `host` and `hostaddr` are specified, the value for `hostaddr` gives the server network address.
103+
/// The value for `host` is ignored unless the authentication method requires it,
104+
/// in which case it will be used as the host name.
93105
/// * `port` - The port to connect to. Multiple ports can be specified, separated by commas. The number of ports must be
94106
/// either 1, in which case it will be used for all hosts, or the same as the number of hosts. Defaults to 5432 if
95107
/// omitted or the empty string.
@@ -117,6 +129,10 @@ pub enum Host {
117129
/// ```
118130
///
119131
/// ```not_rust
132+
/// host=host1,host2,host3 port=1234,,5678 hostaddr=127.0.0.1,127.0.0.2,127.0.0.3 user=postgres target_session_attrs=read-write
133+
/// ```
134+
///
135+
/// ```not_rust
120136
/// host=host1,host2,host3 port=1234,,5678 user=postgres target_session_attrs=read-write
121137
/// ```
122138
///
@@ -153,6 +169,7 @@ pub struct Config {
153169
pub(crate) application_name: Option<String>,
154170
pub(crate) ssl_mode: SslMode,
155171
pub(crate) host: Vec<Host>,
172+
pub(crate) hostaddr: Vec<String>,
156173
pub(crate) port: Vec<u16>,
157174
pub(crate) connect_timeout: Option<Duration>,
158175
pub(crate) keepalives: bool,
@@ -178,6 +195,7 @@ impl Config {
178195
application_name: None,
179196
ssl_mode: SslMode::Prefer,
180197
host: vec![],
198+
hostaddr: vec![],
181199
port: vec![],
182200
connect_timeout: None,
183201
keepalives: true,
@@ -288,6 +306,11 @@ impl Config {
288306
&self.host
289307
}
290308

309+
/// Gets the hostaddrs that have been added to the configuration with `hostaddr`.
310+
pub fn get_hostaddrs(&self) -> &[String] {
311+
self.hostaddr.deref()
312+
}
313+
291314
/// Adds a Unix socket host to the configuration.
292315
///
293316
/// Unlike `host`, this method allows non-UTF8 paths.
@@ -300,6 +323,15 @@ impl Config {
300323
self
301324
}
302325

326+
/// Adds a hostaddr to the configuration.
327+
///
328+
/// Multiple hostaddrs can be specified by calling this method multiple times, and each will be tried in order.
329+
/// There must be either no hostaddrs, or the same number of hostaddrs as hosts.
330+
pub fn hostaddr(&mut self, hostaddr: &str) -> &mut Config {
331+
self.hostaddr.push(hostaddr.to_string());
332+
self
333+
}
334+
303335
/// Adds a port to the configuration.
304336
///
305337
/// Multiple ports can be specified by calling this method multiple times. There must either be no ports, in which
@@ -418,6 +450,11 @@ impl Config {
418450
self.host(host);
419451
}
420452
}
453+
"hostaddr" => {
454+
for hostaddr in value.split(',') {
455+
self.hostaddr(hostaddr);
456+
}
457+
}
421458
"port" => {
422459
for port in value.split(',') {
423460
let port = if port.is_empty() {
@@ -542,6 +579,7 @@ impl fmt::Debug for Config {
542579
.field("application_name", &self.application_name)
543580
.field("ssl_mode", &self.ssl_mode)
544581
.field("host", &self.host)
582+
.field("hostaddr", &self.hostaddr)
545583
.field("port", &self.port)
546584
.field("connect_timeout", &self.connect_timeout)
547585
.field("keepalives", &self.keepalives)
@@ -922,3 +960,35 @@ impl<'a> UrlParser<'a> {
922960
.map_err(|e| Error::config_parse(e.into()))
923961
}
924962
}
963+
964+
#[cfg(test)]
965+
mod tests {
966+
use crate::{config::Host, Config};
967+
968+
#[test]
969+
fn test_simple_parsing() {
970+
let s = "user=pass_user dbname=postgres host=host1,host2 hostaddr=127.0.0.1,127.0.0.2 port=26257";
971+
let config = s.parse::<Config>().unwrap();
972+
assert_eq!(Some("pass_user"), config.get_user());
973+
assert_eq!(Some("postgres"), config.get_dbname());
974+
assert_eq!(
975+
[
976+
Host::Tcp("host1".to_string()),
977+
Host::Tcp("host2".to_string())
978+
],
979+
config.get_hosts(),
980+
);
981+
982+
assert_eq!(["127.0.0.1", "127.0.0.2"], config.get_hostaddrs(),);
983+
984+
assert_eq!(1, 1);
985+
}
986+
987+
#[test]
988+
fn test_empty_hostaddrs() {
989+
let s =
990+
"user=pass_user dbname=postgres host=host1,host2,host3 hostaddr=127.0.0.1,,127.0.0.2";
991+
let config = s.parse::<Config>().unwrap();
992+
assert_eq!(["127.0.0.1", "", "127.0.0.2"], config.get_hostaddrs(),);
993+
}
994+
}

tokio-postgres/src/connect.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,15 @@ where
2323
return Err(Error::config("invalid number of ports".into()));
2424
}
2525

26+
if !config.hostaddr.is_empty() && config.hostaddr.len() != config.host.len() {
27+
let msg = format!(
28+
"invalid number of hostaddrs ({}). Possible values: 0 or number of hosts ({})",
29+
config.hostaddr.len(),
30+
config.host.len(),
31+
);
32+
return Err(Error::config(msg.into()));
33+
}
34+
2635
let mut error = None;
2736
for (i, host) in config.host.iter().enumerate() {
2837
let port = config
@@ -32,18 +41,28 @@ where
3241
.copied()
3342
.unwrap_or(5432);
3443

44+
// The value of host is always used as the hostname for TLS validation.
3545
let hostname = match host {
3646
Host::Tcp(host) => host.as_str(),
3747
// postgres doesn't support TLS over unix sockets, so the choice here doesn't matter
3848
#[cfg(unix)]
3949
Host::Unix(_) => "",
4050
};
41-
4251
let tls = tls
4352
.make_tls_connect(hostname)
4453
.map_err(|e| Error::tls(e.into()))?;
4554

46-
match connect_once(host, port, tls, config).await {
55+
// If both host and hostaddr are specified, the value of hostaddr is used to to establish the TCP connection.
56+
let hostaddr = match host {
57+
Host::Tcp(_hostname) => match config.hostaddr.get(i) {
58+
Some(hostaddr) if hostaddr.is_empty() => Host::Tcp(hostaddr.clone()),
59+
_ => host.clone(),
60+
},
61+
#[cfg(unix)]
62+
Host::Unix(_v) => host.clone(),
63+
};
64+
65+
match connect_once(&hostaddr, port, tls, config).await {
4766
Ok((client, connection)) => return Ok((client, connection)),
4867
Err(e) => error = Some(e),
4968
}

0 commit comments

Comments
 (0)