v0.25.6
This commit is contained in:
1 parent
c819f0a335
commit
e1a59f6e56
53 files changed
+817
-380
No files matched your search
+9
-126
@@ -1,23 +1,16 @@
|
||||
//! Per-backend connection pools and connect-time DNS validation.
|
||||
//! No HTTP caller can supply a destination. DNS answers are validated once and
|
||||
//! passed directly to HttpConnector; there is no second unvalidated lookup.
|
||||
use crate::config::{ip_policy, Backend, UpstreamProtocol};
|
||||
use crate::config::{ip_policy, Backend};
|
||||
use anyhow::{ensure, Context, Result};
|
||||
use axum::body::Body;
|
||||
use http::Uri;
|
||||
use hyper_rustls::{HttpsConnector, HttpsConnectorBuilder};
|
||||
use hyper_util::{
|
||||
client::legacy::{
|
||||
connect::{dns::Name, HttpConnector},
|
||||
Client,
|
||||
},
|
||||
rt::{TokioExecutor, TokioTimer},
|
||||
};
|
||||
use hyper_rustls::HttpsConnector;
|
||||
use hyper_util::client::legacy::connect::{dns::Name, HttpConnector};
|
||||
use ipnet::IpNet;
|
||||
use rustls::{
|
||||
client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
|
||||
pki_types::{CertificateDer, ServerName, UnixTime},
|
||||
ClientConfig, DigitallySignedStruct, RootCertStore, SignatureScheme,
|
||||
DigitallySignedStruct, RootCertStore, SignatureScheme,
|
||||
};
|
||||
use std::{
|
||||
future::Future,
|
||||
@@ -36,7 +29,11 @@ const CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
const MAX_DNS_ANSWERS: usize = 32;
|
||||
static DNS_SLOTS: OnceLock<Arc<Semaphore>> = OnceLock::new();
|
||||
pub type BoxError = Box<dyn std::error::Error + Send + Sync>;
|
||||
pub type ProxyClient = Client<TimedConnector, Body>;
|
||||
|
||||
mod client;
|
||||
pub use client::{
|
||||
client, client_with_keepalive, client_with_timeout, ClientRotationStats, ProxyClient,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct AddressPolicy {
|
||||
@@ -297,120 +294,6 @@ impl ServerCertVerifier for BackendCertificateVerificationDisabled {
|
||||
self.supported_schemes.clone()
|
||||
}
|
||||
}
|
||||
pub fn client(backend: &Backend, policy: AddressPolicy, websocket: bool) -> Result<ProxyClient> {
|
||||
client_with_keepalive(
|
||||
backend,
|
||||
policy,
|
||||
websocket,
|
||||
&crate::balance::Keepalive::default(),
|
||||
)
|
||||
}
|
||||
pub fn client_with_keepalive(
|
||||
backend: &Backend,
|
||||
policy: AddressPolicy,
|
||||
websocket: bool,
|
||||
keepalive: &crate::balance::Keepalive,
|
||||
) -> Result<ProxyClient> {
|
||||
client_with_timeout(backend, policy, websocket, keepalive, CONNECT_TIMEOUT)
|
||||
}
|
||||
pub fn client_with_timeout(
|
||||
backend: &Backend,
|
||||
policy: AddressPolicy,
|
||||
websocket: bool,
|
||||
keepalive: &crate::balance::Keepalive,
|
||||
timeout: Duration,
|
||||
) -> Result<ProxyClient> {
|
||||
// HTTPS "http2" prefers h2 but always offers HTTP/1.1. Cleartext http2 remains
|
||||
// explicit h2c prior knowledge; never replay a failed TLS request to downgrade.
|
||||
let is_https = url::Url::parse(&backend.url)?.scheme() == "https";
|
||||
let protocol = if websocket {
|
||||
UpstreamProtocol::Http1
|
||||
} else if backend.protocol == UpstreamProtocol::Http2 && is_https {
|
||||
UpstreamProtocol::Auto
|
||||
} else {
|
||||
backend.protocol
|
||||
};
|
||||
let provider = Arc::new(rustls::crypto::ring::default_provider());
|
||||
let trust_roots = if backend.tls_verify {
|
||||
roots(backend)?
|
||||
} else {
|
||||
// tls_ca_pem is intentionally ignored when verification is disabled.
|
||||
public_roots()
|
||||
};
|
||||
let mut config = ClientConfig::builder_with_provider(provider.clone())
|
||||
.with_safe_default_protocol_versions()?
|
||||
.with_root_certificates(trust_roots)
|
||||
.with_no_client_auth();
|
||||
if !backend.tls_verify {
|
||||
let supported_schemes = provider
|
||||
.signature_verification_algorithms
|
||||
.supported_schemes();
|
||||
config.dangerous().set_certificate_verifier(Arc::new(
|
||||
BackendCertificateVerificationDisabled { supported_schemes },
|
||||
));
|
||||
}
|
||||
let connect_host = if backend.tls_server_name.is_some() {
|
||||
Some(
|
||||
url::Url::parse(&backend.url)?
|
||||
.host_str()
|
||||
.context("backend host missing")?
|
||||
.trim_start_matches('[')
|
||||
.trim_end_matches(']')
|
||||
.to_owned(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut http = HttpConnector::new_with_resolver(GuardedResolver {
|
||||
policy: policy.clone(),
|
||||
connect_host,
|
||||
});
|
||||
http.enforce_http(false);
|
||||
http.set_connect_timeout(Some(timeout));
|
||||
http.set_nodelay(true);
|
||||
http.set_keepalive(
|
||||
keepalive
|
||||
.enabled
|
||||
.then_some(Duration::from_secs(keepalive.tcp_keepalive_seconds)),
|
||||
);
|
||||
let builder = HttpsConnectorBuilder::new()
|
||||
.with_tls_config(config)
|
||||
.https_or_http();
|
||||
let inner = match protocol {
|
||||
UpstreamProtocol::Http1 => builder.enable_http1().wrap_connector(http),
|
||||
UpstreamProtocol::Http2 => builder.enable_http2().wrap_connector(http),
|
||||
UpstreamProtocol::Auto => builder.enable_http1().enable_http2().wrap_connector(http),
|
||||
};
|
||||
let mut builder = Client::builder(TokioExecutor::new());
|
||||
builder
|
||||
// Header field names are case-insensitive by HTTP specification, but some
|
||||
// legacy/custom origins parse HTTP/1 request headers case-sensitively.
|
||||
// Hyper writes lowercase names by default; title-case them on the wire for
|
||||
// broad origin compatibility. This setting does not affect HTTP/2.
|
||||
.http1_title_case_headers(true)
|
||||
.pool_timer(TokioTimer::new())
|
||||
.pool_idle_timeout(Duration::from_secs(keepalive.idle_timeout_seconds))
|
||||
.pool_max_idle_per_host(if keepalive.enabled {
|
||||
keepalive.max_idle_per_host
|
||||
} else {
|
||||
0
|
||||
})
|
||||
.retry_canceled_requests(false)
|
||||
.http2_only(protocol == UpstreamProtocol::Http2)
|
||||
.timer(TokioTimer::new())
|
||||
.http2_keep_alive_interval(
|
||||
keepalive
|
||||
.enabled
|
||||
.then_some(Duration::from_secs(keepalive.tcp_keepalive_seconds)),
|
||||
)
|
||||
.http2_keep_alive_timeout(Duration::from_secs(10))
|
||||
.http2_keep_alive_while_idle(false);
|
||||
Ok(builder.build(TimedConnector {
|
||||
inner,
|
||||
policy,
|
||||
timeout,
|
||||
}))
|
||||
}
|
||||
/// A bounded raw TCP liveness check, using the same configured-origin address
|
||||
/// policy as normal requests. Every DNS answer is checked before any connection.
|
||||
pub async fn tcp_probe(origin: &str, policy: &AddressPolicy) -> Result<()> {
|
||||
|
||||
Reference in new issue
Block a user