Skip to content

Commit 3697f6b

Browse files
committed
Add hostaddr support
1 parent 97db777 commit 3697f6b

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
@@ -13,6 +13,7 @@ use crate::{Client, Connection, Error};
1313
use std::borrow::Cow;
1414
#[cfg(unix)]
1515
use std::ffi::OsStr;
16+
use std::ops::Deref;
1617
#[cfg(unix)]
1718
use std::os::unix::ffi::OsStrExt;
1819
#[cfg(unix)]
@@ -91,6 +92,17 @@ pub enum Host {
9192
/// path to the directory containing Unix domain sockets. Otherwise, it is treated as a hostname. Multiple hosts
9293
/// can be specified, separated by commas. Each host will be tried in turn when connecting. Required if connecting
9394
/// with the `connect` method.
95+
/// * `hostaddr` - Numeric IP address of host to connect to. This should be in the standard IPv4 address format,
96+
/// e.g., 172.28.40.9. If your machine supports IPv6, you can also use those addresses.
97+
/// If this parameter is not specified, the value of `host` will be looked up to find the corresponding IP address,
98+
/// - or if host specifies an IP address, that value will be used directly.
99+
/// Using `hostaddr` allows the application to avoid a host name look-up, which might be important in applications
100+
/// with time constraints. However, a host name is required for verify-full SSL certificate verification.
101+
/// Note that `host` is always required regardless of whether `hostaddr` is present.
102+
/// * If `host` is specified without `hostaddr`, a host name lookup occurs;
103+
/// * If both `host` and `hostaddr` are specified, the value for `hostaddr` gives the server network address.
104+
/// The value for `host` is ignored unless the authentication method requires it,
105+
/// in which case it will be used as the host name.
94106
/// * `port` - The port to connect to. Multiple ports can be specified, separated by commas. The number of ports must be
95107
/// either 1, in which case it will be used for all hosts, or the same as the number of hosts. Defaults to 5432 if
96108
/// omitted or the empty string.
@@ -122,6 +134,10 @@ pub enum Host {
122134
/// ```
123135
///
124136
/// ```not_rust
137+
/// 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
138+
/// ```
139+
///
140+
/// ```not_rust
125141
/// host=host1,host2,host3 port=1234,,5678 user=postgres target_session_attrs=read-write
126142
/// ```
127143
///
@@ -158,6 +174,7 @@ pub struct Config {
158174
pub(crate) application_name: Option<String>,
159175
pub(crate) ssl_mode: SslMode,
160176
pub(crate) host: Vec<Host>,
177+
pub(crate) hostaddr: Vec<String>,
161178
pub(crate) port: Vec<u16>,
162179
pub(crate) connect_timeout: Option<Duration>,
163180
pub(crate) keepalives: bool,
@@ -188,6 +205,7 @@ impl Config {
188205
application_name: None,
189206
ssl_mode: SslMode::Prefer,
190207
host: vec![],
208+
hostaddr: vec![],
191209
port: vec![],
192210
connect_timeout: None,
193211
keepalives: true,
@@ -298,6 +316,11 @@ impl Config {
298316
&self.host
299317
}
300318

319+
/// Gets the hostaddrs that have been added to the configuration with `hostaddr`.
320+
pub fn get_hostaddrs(&self) -> &[String] {
321+
self.hostaddr.deref()
322+
}
323+
301324
/// Adds a Unix socket host to the configuration.
302325
///
303326
/// Unlike `host`, this method allows non-UTF8 paths.
@@ -310,6 +333,15 @@ impl Config {
310333
self
311334
}
312335

336+
/// Adds a hostaddr to the configuration.
337+
///
338+
/// Multiple hostaddrs can be specified by calling this method multiple times, and each will be tried in order.
339+
/// There must be either no hostaddrs, or the same number of hostaddrs as hosts.
340+
pub fn hostaddr(&mut self, hostaddr: &str) -> &mut Config {
341+
self.hostaddr.push(hostaddr.to_string());
342+
self
343+
}
344+
313345
/// Adds a port to the configuration.
314346
///
315347
/// Multiple ports can be specified by calling this method multiple times. There must either be no ports, in which
@@ -455,6 +487,11 @@ impl Config {
455487
self.host(host);
456488
}
457489
}
490+
"hostaddr" => {
491+
for hostaddr in value.split(',') {
492+
self.hostaddr(hostaddr);
493+
}
494+
}
458495
"port" => {
459496
for port in value.split(',') {
460497
let port = if port.is_empty() {
@@ -593,6 +630,7 @@ impl fmt::Debug for Config {
593630
.field("application_name", &self.application_name)
594631
.field("ssl_mode", &self.ssl_mode)
595632
.field("host", &self.host)
633+
.field("hostaddr", &self.hostaddr)
596634
.field("port", &self.port)
597635
.field("connect_timeout", &self.connect_timeout)
598636
.field("keepalives", &self.keepalives)
@@ -975,3 +1013,35 @@ impl<'a> UrlParser<'a> {
9751013
.map_err(|e| Error::config_parse(e.into()))
9761014
}
9771015
}
1016+
1017+
#[cfg(test)]
1018+
mod tests {
1019+
use crate::{config::Host, Config};
1020+
1021+
#[test]
1022+
fn test_simple_parsing() {
1023+
let s = "user=pass_user dbname=postgres host=host1,host2 hostaddr=127.0.0.1,127.0.0.2 port=26257";
1024+
let config = s.parse::<Config>().unwrap();
1025+
assert_eq!(Some("pass_user"), config.get_user());
1026+
assert_eq!(Some("postgres"), config.get_dbname());
1027+
assert_eq!(
1028+
[
1029+
Host::Tcp("host1".to_string()),
1030+
Host::Tcp("host2".to_string())
1031+
],
1032+
config.get_hosts(),
1033+
);
1034+
1035+
assert_eq!(["127.0.0.1", "127.0.0.2"], config.get_hostaddrs(),);
1036+
1037+
assert_eq!(1, 1);
1038+
}
1039+
1040+
#[test]
1041+
fn test_empty_hostaddrs() {
1042+
let s =
1043+
"user=pass_user dbname=postgres host=host1,host2,host3 hostaddr=127.0.0.1,,127.0.0.2";
1044+
let config = s.parse::<Config>().unwrap();
1045+
assert_eq!(["127.0.0.1", "", "127.0.0.2"], config.get_hostaddrs(),);
1046+
}
1047+
}

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)