client: http: Use custom resolver for statically linked binary

The dependency on the `getaddrinfo` based `GaiResolver` used by
default for the `HttpClient` is not suitable for the statically
linked binary of the `proxmox-backup-client`, because of the
dependency on glibc NSS libraries, as described in glibc's FAQs [0].

As a workaround, conditionally compile the binary using the `hickory-dns`
resolver.

[0] https://sourceware.org/glibc/wiki/FAQ#Even_statically_linked_programs_need_some_shared_libraries_which_is_not_acceptable_for_me.__What_can_I_do.3F

Suggested-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
Tested-by: Lukas Wagner <l.wagner@proxmox.com>
FG: bump proxmox-http dependency
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
This commit is contained in:
Christian Ebner 2025-04-09 13:05:19 +02:00 committed by Fabian Grünbichler
parent 601a84ae74
commit 83e7b9de88
4 changed files with 88 additions and 13 deletions

View File

@ -62,7 +62,7 @@ proxmox-compression = "0.2"
proxmox-config-digest = "0.1.0"
proxmox-daemon = "0.1.0"
proxmox-fuse = "0.1.3"
proxmox-http = { version = "0.9.0", features = [ "client", "http-helpers", "websocket" ] } # see below
proxmox-http = { version = "0.9.5", features = [ "client", "http-helpers", "websocket" ] } # see below
proxmox-human-byte = "0.1"
proxmox-io = "1.0.1" # tools and client use "tokio" feature
proxmox-lang = "1.1"
@ -127,6 +127,7 @@ futures = "0.3"
h2 = { version = "0.4", features = [ "legacy", "stream" ] }
handlebars = "3.0"
hex = "0.4.3"
hickory-resolver = { version = "0.24.1", default-features = false, features = [ "system-config", "tokio-runtime" ] }
hyper = { version = "0.14", features = [ "backports", "deprecated", "full" ] }
libc = "0.2"
log = "0.4.17"

16
debian/control vendored
View File

@ -61,14 +61,14 @@ Build-Depends: bash-completion,
librust-proxmox-config-digest-0.1+default-dev,
librust-proxmox-daemon-0.1+default-dev,
librust-proxmox-fuse-0.1+default-dev (>= 0.1.3-~~),
librust-proxmox-http-0.9+client-dev,
librust-proxmox-http-0.9+client-trait-dev,
librust-proxmox-http-0.9+default-dev,
librust-proxmox-http-0.9+http-helpers-dev,
librust-proxmox-http-0.9+proxmox-async-dev,
librust-proxmox-http-0.9+rate-limited-stream-dev,
librust-proxmox-http-0.9+rate-limiter-dev,
librust-proxmox-http-0.9+websocket-dev,
librust-proxmox-http-0.9+client-dev (>= 0.9.5-~~),
librust-proxmox-http-0.9+client-trait-dev (>= 0.9.5-~~),
librust-proxmox-http-0.9+default-dev (>= 0.9.5-~~),
librust-proxmox-http-0.9+http-helpers-dev (>= 0.9.5-~~),
librust-proxmox-http-0.9+proxmox-async-dev (>= 0.9.5-~~),
librust-proxmox-http-0.9+rate-limited-stream-dev (>= 0.9.5-~~),
librust-proxmox-http-0.9+rate-limiter-dev (>= 0.9.5-~~),
librust-proxmox-http-0.9+websocket-dev (>= 0.9.5-~~),
librust-proxmox-human-byte-0.1+default-dev,
librust-proxmox-io-1+default-dev (>= 1.0.1-~~),
librust-proxmox-io-1+tokio-dev (>= 1.0.1-~~),

View File

@ -27,6 +27,7 @@ tokio = { workspace = true, features = [ "fs", "signal" ] }
tokio-stream.workspace = true
tower-service.workspace = true
xdg.workspace = true
hickory-resolver.workspace = true
pathpatterns.workspace = true

View File

@ -4,6 +4,8 @@ use std::time::Duration;
use anyhow::{bail, format_err, Error};
use futures::*;
#[cfg(not(target_feature = "crt-static"))]
use hyper::client::connect::dns::GaiResolver;
use hyper::client::{Client, HttpConnector};
use hyper::http::header::HeaderValue;
use hyper::http::Uri;
@ -33,6 +35,74 @@ use pbs_api_types::{Authid, RateLimitConfig, Userid};
use super::pipe_to_stream::PipeToSendStream;
use super::PROXMOX_BACKUP_TCP_KEEPALIVE_TIME;
#[cfg(not(target_feature = "crt-static"))]
type DnsResolver = GaiResolver;
#[cfg(target_feature = "crt-static")]
type DnsResolver = resolver::HickoryDnsResolver;
#[cfg(target_feature = "crt-static")]
mod resolver {
use std::net::SocketAddr;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use futures::Future;
use hickory_resolver::error::ResolveError;
use hickory_resolver::lookup_ip::LookupIpIntoIter;
use hickory_resolver::TokioAsyncResolver;
use hyper::client::connect::dns::Name;
use tower_service::Service;
pub(crate) struct SocketAddrIter {
inner: LookupIpIntoIter,
}
impl Iterator for SocketAddrIter {
type Item = SocketAddr;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|ip_addr| SocketAddr::new(ip_addr, 0))
}
}
#[derive(Clone)]
pub(crate) struct HickoryDnsResolver {
inner: Arc<TokioAsyncResolver>,
}
impl HickoryDnsResolver {
pub(crate) fn new() -> Self {
Self {
inner: Arc::new(TokioAsyncResolver::tokio_from_system_conf().unwrap()),
}
}
}
impl Service<Name> for HickoryDnsResolver {
type Response = SocketAddrIter;
type Error = ResolveError;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
fn poll_ready(&mut self, _ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, name: Name) -> Self::Future {
let inner = self.inner.clone();
Box::pin(async move {
inner
.lookup_ip(name.as_str())
.await
.map(|r| SocketAddrIter {
inner: r.into_iter(),
})
})
}
}
}
/// Timeout used for several HTTP operations that are expected to finish quickly but may block in
/// certain error conditions. Keep it generous, to avoid false-positive under high load.
const HTTP_TIMEOUT: Duration = Duration::from_secs(2 * 60);
@ -134,7 +204,7 @@ impl Default for HttpClientOptions {
/// HTTP(S) API client
pub struct HttpClient {
client: Client<HttpsConnector>,
client: Client<HttpsConnector<DnsResolver>>,
server: String,
port: u16,
fingerprint: Arc<Mutex<Option<String>>>,
@ -365,7 +435,8 @@ impl HttpClient {
ssl_connector_builder.set_verify(openssl::ssl::SslVerifyMode::NONE);
}
let mut httpc = HttpConnector::new();
let resolver = DnsResolver::new();
let mut httpc = HttpConnector::new_with_resolver(resolver);
httpc.set_nodelay(true); // important for h2 download performance!
httpc.enforce_http(false); // we want https...
@ -526,7 +597,9 @@ impl HttpClient {
_options: options,
})
}
}
impl HttpClient {
/// Login
///
/// Login is done on demand, so this is only required if you need
@ -814,7 +887,7 @@ impl HttpClient {
}
async fn credentials(
client: Client<HttpsConnector>,
client: Client<HttpsConnector<DnsResolver>>,
server: String,
port: u16,
username: Userid,
@ -859,7 +932,7 @@ impl HttpClient {
}
async fn api_request(
client: Client<HttpsConnector>,
client: Client<HttpsConnector<DnsResolver>>,
req: Request<Body>,
) -> Result<Value, Error> {
Self::api_response(