1mod codec;
20mod dyncfgs;
21
22use std::collections::BTreeMap;
23use std::net::{IpAddr, SocketAddr};
24use std::path::PathBuf;
25use std::pin::Pin;
26use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
27use std::sync::{Arc, Mutex};
28use std::time::{Duration, Instant};
29
30use anyhow::Context;
31use axum::{Router, routing};
32use bytes::BytesMut;
33use futures::TryFutureExt;
34use futures::stream::BoxStream;
35use hickory_resolver::config::LookupIpStrategy;
36use hickory_resolver::lookup_ip::LookupIp;
37use hickory_resolver::net::runtime::TokioRuntimeProvider;
38use hickory_resolver::proto::rr::{RData, RecordType};
39use hickory_resolver::system_conf::read_system_conf;
40use hickory_resolver::{Resolver, TokioResolver};
41use hyper::StatusCode;
42use hyper_util::rt::TokioIo;
43use launchdarkly_server_sdk as ld;
44use mz_build_info::{BuildInfo, build_info};
45use mz_dyncfg::ConfigSet;
46use mz_frontegg_auth::Authenticator as FronteggAuthentication;
47use mz_ore::cast::CastFrom;
48use mz_ore::id_gen::conn_id_org_uuid;
49use mz_ore::metrics::{
50 ComputedGauge, ComputedIntGauge, ComputedUIntGauge, IntCounter, IntGauge, MetricsRegistry,
51};
52use mz_ore::netio::AsyncReady;
53use mz_ore::now::{NowFn, SYSTEM_TIME, epoch_to_uuid_v7};
54use mz_ore::task::{JoinSetExt, spawn};
55use mz_ore::tracing::TracingHandle;
56use mz_ore::{metric, netio};
57use mz_pgwire_common::{
58 ACCEPT_SSL_ENCRYPTION, CONN_UUID_KEY, Conn, ErrorResponse, FrontendMessage,
59 FrontendStartupMessage, MAX_STARTUP_FRAME_SIZE, MZ_FORWARDED_FOR_KEY, REJECT_ENCRYPTION,
60 VERSION_3, decode_startup,
61};
62use mz_server_core::{
63 Connection, ConnectionStream, ListenerHandle, ReloadTrigger, ReloadingSslContext,
64 ReloadingTlsConfig, ServeConfig, ServeDyncfg, TlsCertConfig, TlsMode, listen,
65};
66use openssl::error::ErrorStack;
67use openssl::ssl::{NameType, Ssl, SslConnector, SslMethod, SslVerifyMode};
68use prometheus::{IntCounterVec, IntGaugeVec};
69use proxy_header::{ProxiedAddress, ProxyHeader};
70use semver::Version;
71use tokio::io::{self, AsyncRead, AsyncWrite, AsyncWriteExt};
72use tokio::net::TcpStream;
73use tokio::sync::oneshot;
74use tokio::task::JoinSet;
75use tokio_metrics::TaskMetrics;
76use tokio_openssl::SslStream;
77use tokio_postgres::error::SqlState;
78use tower::Service;
79use tracing::{debug, error, info, warn};
80use uuid::Uuid;
81
82use crate::codec::{BackendMessage, FramedConn};
83use crate::dyncfgs::{
84 CONNECTION_HIGH_WATERMARK, CONNECTION_LOW_WATERMARK, INJECT_PROXY_PROTOCOL_HEADER_HTTP,
85 MAX_CONNECTIONS, PRE_RESOLVED_TIMEOUT, SIGTERM_CONNECTION_WAIT, SIGTERM_LISTEN_WAIT,
86 has_tracing_config_update, tracing_config,
87};
88
89pub const BUILD_INFO: BuildInfo = build_info!();
91
92pub struct BalancerConfig {
93 build_version: Version,
95 internal_http_listen_addr: SocketAddr,
97 pgwire_listen_addr: SocketAddr,
99 https_listen_addr: SocketAddr,
101 cancellation_resolver: CancellationResolver,
103 resolver: BalancerResolver,
105 https_sni_addr_template: String,
106 tls: Option<TlsCertConfig>,
107 internal_tls: bool,
108 metrics_registry: MetricsRegistry,
109 reload_certs: BoxStream<'static, Option<oneshot::Sender<Result<(), anyhow::Error>>>>,
110 launchdarkly_sdk_key: Option<String>,
111 config_sync_file_path: Option<PathBuf>,
112 config_sync_timeout: Duration,
113 config_sync_loop_interval: Option<Duration>,
114 cloud_provider: Option<String>,
115 cloud_provider_region: Option<String>,
116 tracing_handle: TracingHandle,
117 default_configs: Vec<(String, String)>,
118}
119
120impl BalancerConfig {
121 pub fn new(
122 build_info: &BuildInfo,
123 internal_http_listen_addr: SocketAddr,
124 pgwire_listen_addr: SocketAddr,
125 https_listen_addr: SocketAddr,
126 cancellation_resolver: CancellationResolver,
127 resolver: BalancerResolver,
128 https_sni_addr_template: String,
129 tls: Option<TlsCertConfig>,
130 internal_tls: bool,
131 metrics_registry: MetricsRegistry,
132 reload_certs: ReloadTrigger,
133 launchdarkly_sdk_key: Option<String>,
134 config_sync_file: Option<PathBuf>,
135 config_sync_timeout: Duration,
136 config_sync_loop_interval: Option<Duration>,
137 cloud_provider: Option<String>,
138 cloud_provider_region: Option<String>,
139 tracing_handle: TracingHandle,
140 default_configs: Vec<(String, String)>,
141 ) -> Self {
142 Self {
143 build_version: build_info.semver_version(),
144 internal_http_listen_addr,
145 pgwire_listen_addr,
146 https_listen_addr,
147 cancellation_resolver,
148 resolver,
149 https_sni_addr_template,
150 tls,
151 internal_tls,
152 metrics_registry,
153 reload_certs,
154 launchdarkly_sdk_key,
155 config_sync_file_path: config_sync_file,
156 config_sync_timeout,
157 config_sync_loop_interval,
158 cloud_provider,
159 cloud_provider_region,
160 tracing_handle,
161 default_configs,
162 }
163 }
164}
165
166#[derive(Debug)]
168pub struct BalancerMetrics {
169 _uptime: ComputedGauge,
170}
171
172impl BalancerMetrics {
173 pub fn new(cfg: &BalancerConfig) -> Self {
175 let start = Instant::now();
176 let uptime = cfg.metrics_registry.register_computed_gauge(
177 metric!(
178 name: "mz_balancer_metadata_seconds",
179 help: "server uptime, labels are build metadata",
180 const_labels: {
181 "version" => cfg.build_version,
182 "build_type" => if cfg!(release) { "release" } else { "debug" }
183 },
184 ),
185 move || start.elapsed().as_secs_f64(),
186 );
187 BalancerMetrics { _uptime: uptime }
188 }
189}
190
191pub struct BalancerService {
192 cfg: BalancerConfig,
193 pub pgwire: (ListenerHandle, Pin<Box<dyn ConnectionStream>>),
194 pub https: (ListenerHandle, Pin<Box<dyn ConnectionStream>>),
195 pub internal_http: (ListenerHandle, Pin<Box<dyn ConnectionStream>>),
196 _metrics: BalancerMetrics,
197 configs: ConfigSet,
198}
199
200impl BalancerService {
201 pub async fn new(cfg: BalancerConfig) -> Result<Self, anyhow::Error> {
202 let pgwire = listen(&cfg.pgwire_listen_addr).await?;
203 let https = listen(&cfg.https_listen_addr).await?;
204 let internal_http = listen(&cfg.internal_http_listen_addr).await?;
205 let metrics = BalancerMetrics::new(&cfg);
206 let mut configs = ConfigSet::default();
207 configs = dyncfgs::all_dyncfgs(configs);
208 dyncfgs::set_defaults(&configs, cfg.default_configs.clone())?;
209 let tracing_handle = cfg.tracing_handle.clone();
210 match (
212 cfg.launchdarkly_sdk_key.as_deref(),
213 cfg.config_sync_file_path.as_deref(),
214 ) {
215 (Some(key), None) => {
216 let _ = mz_dyncfg_launchdarkly::sync_launchdarkly_to_configset(
217 configs.clone(),
218 &BUILD_INFO,
219 |builder| {
220 let region = cfg
221 .cloud_provider_region
222 .clone()
223 .unwrap_or_else(|| String::from("unknown"));
224 if let Some(provider) = cfg.cloud_provider.clone() {
225 builder.add_context(
226 ld::ContextBuilder::new(format!(
227 "{}/{}/{}",
228 provider, region, cfg.build_version
229 ))
230 .kind("balancer")
231 .set_string("provider", provider)
232 .set_string("region", region)
233 .set_string("version", cfg.build_version.to_string())
234 .build()
235 .map_err(|e| anyhow::anyhow!(e))?,
236 );
237 } else {
238 builder.add_context(
239 ld::ContextBuilder::new(format!(
240 "{}/{}/{}",
241 "unknown", region, cfg.build_version
242 ))
243 .anonymous(true) .kind("balancer")
245 .set_string("provider", "unknown")
246 .set_string("region", region)
247 .set_string("version", cfg.build_version.to_string())
248 .build()
249 .map_err(|e| anyhow::anyhow!(e))?,
250 );
251 }
252 Ok(())
253 },
254 Some(key),
255 cfg.config_sync_timeout,
256 cfg.config_sync_loop_interval,
257 move |updates, configs| {
258 if has_tracing_config_update(updates) {
259 match tracing_config(configs) {
260 Ok(parameters) => parameters.apply(&tracing_handle),
261 Err(err) => warn!("unable to update tracing: {err}"),
262 }
263 }
264 },
265 )
266 .await
267 .inspect_err(|e| warn!("LaunchDarkly sync error: {e}"));
268 }
269 (None, Some(path)) => {
270 let _ = mz_dyncfg_file::sync_file_to_configset(
271 configs.clone(),
272 path,
273 cfg.config_sync_timeout,
274 cfg.config_sync_loop_interval,
275 move |updates, configs| {
276 if has_tracing_config_update(updates) {
277 match tracing_config(configs) {
278 Ok(parameters) => parameters.apply(&tracing_handle),
279 Err(err) => warn!("unable to update tracing: {err}"),
280 }
281 }
282 },
283 )
284 .await
285 .inspect_err(|e| warn!("File config sync error: {e}"));
291 }
292 (Some(_), Some(_)) => panic!(
293 "must provide either config_sync_file_path or launchdarkly_sdk_key for config syncing",
294 ),
295 (None, None) => {}
296 };
297 Ok(Self {
298 cfg,
299 pgwire,
300 https,
301 internal_http,
302 _metrics: metrics,
303 configs,
304 })
305 }
306
307 pub async fn serve(self) -> Result<(), anyhow::Error> {
308 let (pgwire_tls, https_tls) = match &self.cfg.tls {
309 Some(tls) => {
310 let context = tls.reloading_context(self.cfg.reload_certs)?;
311 (
312 Some(ReloadingTlsConfig {
313 context: context.clone(),
314 mode: TlsMode::Require,
315 }),
316 Some(context),
317 )
318 }
319 None => (None, None),
320 };
321
322 let internal_tls = self
325 .cfg
326 .internal_tls
327 .then(internal_tls_connector)
328 .transpose()
329 .context("building internal TLS connector")?;
330
331 let metrics = ServerMetricsConfig::register_into(&self.cfg.metrics_registry);
332 let limiter = ConnectionLimiter::new(&self.cfg.metrics_registry, self.configs.clone());
333
334 let mut set = JoinSet::new();
335 let mut server_handles = Vec::new();
336 let pgwire_addr = self.pgwire.0.local_addr();
337 let https_addr = self.https.0.local_addr();
338 let internal_http_addr = self.internal_http.0.local_addr();
339
340 let shared_dns = match self.cfg.resolver.shared_dns() {
345 Some(dns) => dns,
346 None => Arc::new(TenantDnsResolver::new()?),
347 };
348
349 {
350 let pgwire = PgwireBalancer {
351 resolver: Arc::new(self.cfg.resolver),
352 cancellation_resolver: Arc::new(self.cfg.cancellation_resolver),
353 tls: pgwire_tls,
354 internal_tls: internal_tls.clone(),
355 metrics: ServerMetrics::new(metrics.clone(), "pgwire"),
356 limiter: Arc::clone(&limiter),
357 configs: self.configs.clone(),
358 now: SYSTEM_TIME.clone(),
359 };
360 let (handle, stream) = self.pgwire;
361 server_handles.push(handle);
362 set.spawn_named(|| "pgwire_stream", {
363 let config_set = self.configs.clone();
364 async move {
365 mz_server_core::serve(ServeConfig {
366 server: pgwire,
367 conns: stream,
368 dyncfg: Some(ServeDyncfg {
369 config_set,
370 sigterm_wait_config: &SIGTERM_CONNECTION_WAIT,
371 }),
372 })
373 .await;
374 warn!("pgwire server exited");
375 }
376 });
377 }
378 {
379 let Some((addr, port)) = self.cfg.https_sni_addr_template.split_once(':') else {
380 panic!("expected port in https_addr_template");
381 };
382 let port: u16 = port.parse().expect("unexpected port");
383
384 let https = HttpsBalancer {
385 resolver: shared_dns,
386 tls: https_tls,
387 resolve_template: Arc::from(addr),
388 port,
389 metrics: Arc::from(ServerMetrics::new(metrics, "https")),
390 limiter: Arc::clone(&limiter),
391 configs: self.configs.clone(),
392 internal_tls,
393 };
394 let (handle, stream) = self.https;
395 server_handles.push(handle);
396 set.spawn_named(|| "https_stream", {
397 let config_set = self.configs.clone();
398 async move {
399 mz_server_core::serve(ServeConfig {
400 server: https,
401 conns: stream,
402 dyncfg: Some(ServeDyncfg {
403 config_set,
404 sigterm_wait_config: &SIGTERM_CONNECTION_WAIT,
405 }),
406 })
407 .await;
408 warn!("https server exited");
409 }
410 });
411 }
412 {
413 let router = Router::new()
414 .route(
415 "/metrics",
416 routing::get(move |headers: axum::http::HeaderMap| async move {
417 mz_http_util::handle_prometheus(&self.cfg.metrics_registry, headers).await
418 }),
419 )
420 .route(
421 "/api/livez",
422 routing::get(mz_http_util::handle_liveness_check),
423 )
424 .route("/api/readyz", {
425 let limiter = Arc::clone(&limiter);
426 routing::get(move || async move {
427 if limiter.evaluate_watermarks() {
428 (StatusCode::OK, "ready")
429 } else {
430 (
431 StatusCode::SERVICE_UNAVAILABLE,
432 "at the connection high watermark",
433 )
434 }
435 })
436 });
437 let internal_http = InternalHttpServer { router };
438 let (handle, stream) = self.internal_http;
439 server_handles.push(handle);
440 set.spawn_named(|| "internal_http_stream", async move {
441 mz_server_core::serve(ServeConfig {
442 server: internal_http,
443 conns: stream,
444 dyncfg: None,
447 })
448 .await;
449 warn!("internal_http server exited");
450 });
451 }
452 spawn(|| "connection_watermark_tick", async move {
455 let mut tick = tokio::time::interval(Duration::from_secs(1));
456 loop {
457 tick.tick().await;
458 limiter.evaluate_watermarks();
459 }
460 });
461 #[cfg(unix)]
462 {
463 let mut sigterm =
464 tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())?;
465 set.spawn_named(|| "sigterm_handler", async move {
466 sigterm.recv().await;
467 let wait = SIGTERM_LISTEN_WAIT.get(&self.configs);
468 warn!("received signal TERM - delaying for {:?}!", wait);
469 tokio::time::sleep(wait).await;
470 warn!("sigterm delay complete, dropping server handles");
471 drop(server_handles);
472 });
473 }
474
475 println!("balancerd {} listening...", BUILD_INFO.human_version(None));
476 println!(" TLS enabled: {}", self.cfg.tls.is_some());
477 println!(" pgwire address: {}", pgwire_addr);
478 println!(" HTTPS address: {}", https_addr);
479 println!(" internal HTTP address: {}", internal_http_addr);
480
481 while let Some(res) = set.join_next().await {
483 if let Err(err) = res {
484 error!("serving task failed: {err}")
485 }
486 }
487 Ok(())
488 }
489}
490
491struct InternalHttpServer {
492 router: Router,
493}
494
495impl mz_server_core::Server for InternalHttpServer {
496 const NAME: &'static str = "internal_http";
497
498 fn handle_connection(
500 &self,
501 conn: Connection,
502 _tokio_metrics_intervals: impl Iterator<Item = TaskMetrics> + Send + 'static,
503 ) -> mz_server_core::ConnectionHandler {
504 let router = self.router.clone();
505 let service = hyper::service::service_fn(move |req| router.clone().call(req));
506 let conn = TokioIo::new(conn);
507
508 Box::pin(async {
509 let http = hyper::server::conn::http1::Builder::new();
510 http.serve_connection(conn, service).err_into().await
511 })
512 }
513}
514
515fn internal_tls_connector() -> Result<SslConnector, ErrorStack> {
517 let mut builder = SslConnector::builder(SslMethod::tls())?;
518 builder.set_verify(SslVerifyMode::NONE);
520 Ok(builder.build())
521}
522
523struct GaugeGuard {
526 gauge: IntGauge,
527}
528
529impl From<IntGauge> for GaugeGuard {
530 fn from(gauge: IntGauge) -> Self {
531 let _self = Self { gauge };
532 _self.gauge.inc();
533 _self
534 }
535}
536
537impl Drop for GaugeGuard {
538 fn drop(&mut self) {
539 self.gauge.dec();
540 }
541}
542
543#[derive(Clone, Debug)]
544struct ServerMetricsConfig {
545 connection_status: IntCounterVec,
546 active_connections: IntGaugeVec,
547 pre_resolved_connections: IntGaugeVec,
548 pre_resolved_timeouts: IntCounterVec,
549 tenant_connections: IntGaugeVec,
550 tenant_connection_rx: IntCounterVec,
551 tenant_connection_tx: IntCounterVec,
552 tenant_pgwire_sni_count: IntCounterVec,
553}
554
555impl ServerMetricsConfig {
556 fn register_into(registry: &MetricsRegistry) -> Self {
557 let connection_status = registry.register(metric!(
558 name: "mz_balancer_connection_status",
559 help: "Count of completed network connections, by status",
560 var_labels: ["source", "status"],
561 ));
562 let active_connections = registry.register(metric!(
563 name: "mz_balancer_connection_active",
564 help: "Count of currently open network connections.",
565 var_labels: ["source"],
566 ));
567 let pre_resolved_connections = registry.register(metric!(
568 name: "mz_balancer_pre_resolved_connection_active",
569 help: "Count of open network connections that have not yet resolved a backend.",
570 var_labels: ["source"],
571 ));
572 let pre_resolved_timeouts = registry.register(metric!(
573 name: "mz_balancer_pre_resolved_timeout_total",
574 help: "Count of connections closed for not resolving a backend within the pre-resolved timeout.",
575 var_labels: ["source"],
576 ));
577 let tenant_connections = registry.register(metric!(
578 name: "mz_balancer_tenant_connection_active",
579 help: "Count of opened network connections by tenant.",
580 var_labels: ["source", "tenant"]
581 ));
582 let tenant_connection_rx = registry.register(metric!(
583 name: "mz_balancer_tenant_connection_rx",
584 help: "Number of bytes received from a client for a tenant.",
585 var_labels: ["source", "tenant"],
586 ));
587 let tenant_connection_tx = registry.register(metric!(
588 name: "mz_balancer_tenant_connection_tx",
589 help: "Number of bytes sent to a client for a tenant.",
590 var_labels: ["source", "tenant"],
591 ));
592 let tenant_pgwire_sni_count = registry.register(metric!(
593 name: "mz_balancer_tenant_pgwire_sni_count",
594 help: "Count of pgwire connections that have and do not have SNI available per tenant.",
595 var_labels: ["tenant", "has_sni"],
596 ));
597 Self {
598 connection_status,
599 active_connections,
600 pre_resolved_connections,
601 pre_resolved_timeouts,
602 tenant_connections,
603 tenant_connection_rx,
604 tenant_connection_tx,
605 tenant_pgwire_sni_count,
606 }
607 }
608}
609
610#[derive(Clone, Debug)]
611struct ServerMetrics {
612 inner: ServerMetricsConfig,
613 source: &'static str,
614}
615
616impl ServerMetrics {
617 fn new(inner: ServerMetricsConfig, source: &'static str) -> Self {
618 let self_ = Self { inner, source };
619
620 self_.connection_status(false);
623 self_.connection_status(true);
624 drop(self_.active_connections());
625
626 self_
627 }
628
629 fn connection_status(&self, is_ok: bool) -> IntCounter {
630 self.inner
631 .connection_status
632 .with_label_values(&[self.source, Self::status_label(is_ok)])
633 }
634
635 fn active_connections(&self) -> GaugeGuard {
636 self.inner
637 .active_connections
638 .with_label_values(&[self.source])
639 .into()
640 }
641
642 fn pre_resolved_connections(&self) -> GaugeGuard {
643 self.inner
644 .pre_resolved_connections
645 .with_label_values(&[self.source])
646 .into()
647 }
648
649 fn pre_resolved_timeout(&self) -> IntCounter {
650 self.inner
651 .pre_resolved_timeouts
652 .with_label_values(&[self.source])
653 }
654
655 fn tenant_connections(&self, tenant: &str) -> GaugeGuard {
656 self.inner
657 .tenant_connections
658 .with_label_values(&[self.source, tenant])
659 .into()
660 }
661
662 fn tenant_connections_rx(&self, tenant: &str) -> IntCounter {
663 self.inner
664 .tenant_connection_rx
665 .with_label_values(&[self.source, tenant])
666 }
667
668 fn tenant_connections_tx(&self, tenant: &str) -> IntCounter {
669 self.inner
670 .tenant_connection_tx
671 .with_label_values(&[self.source, tenant])
672 }
673
674 fn tenant_pgwire_sni_count(&self, tenant: &str, has_sni: bool) -> IntCounter {
675 self.inner
676 .tenant_pgwire_sni_count
677 .with_label_values(&[tenant, &has_sni.to_string()])
678 }
679
680 fn status_label(is_ok: bool) -> &'static str {
681 if is_ok { "success" } else { "error" }
682 }
683}
684
685#[derive(Debug)]
692struct ConnectionLimiter {
693 configs: ConfigSet,
694 active: AtomicU32,
695 limited: AtomicBool,
698 accepting: Arc<AtomicBool>,
700 watermark_state: Mutex<WatermarkState>,
703 rejected: IntCounter,
704 watermark_transitions: IntCounter,
705 _limit: ComputedUIntGauge,
706 _accepting: ComputedIntGauge,
707 _high_watermark: ComputedUIntGauge,
708 _low_watermark: ComputedUIntGauge,
709}
710
711#[derive(Debug, Default)]
712struct WatermarkState {
713 out_of_rotation: bool,
714 last_warning: Option<String>,
716}
717
718#[derive(Debug, Clone, Copy)]
720struct Watermarks {
721 low: usize,
722 high: usize,
723}
724
725fn configured_watermarks(configs: &ConfigSet) -> Result<Option<Watermarks>, String> {
732 let low = CONNECTION_LOW_WATERMARK.get(configs);
733 let high = CONNECTION_HIGH_WATERMARK.get(configs);
734 let max = MAX_CONNECTIONS.get(configs);
735 let reason = match (low, high) {
736 (0, 0) => return Ok(None),
737 (0, _) | (_, 0) => "both watermarks must be set",
738 (low, high) if low >= high => "the low watermark must be below the high watermark",
739 _ if max == 0 => "balancerd_max_connections must be enabled",
740 (_, high) if high >= usize::cast_from(max) => {
741 "the high watermark must be below balancerd_max_connections"
742 }
743 (low, high) => return Ok(Some(Watermarks { low, high })),
744 };
745 Err(format!(
746 "{reason} (low={low}, high={high}, max_connections={max})"
747 ))
748}
749
750fn watermark_metric(configs: &ConfigSet, pick: fn(Watermarks) -> usize) -> u64 {
752 configured_watermarks(configs)
753 .ok()
754 .flatten()
755 .map_or(0, |w| u64::cast_from(pick(w)))
756}
757
758impl ConnectionLimiter {
759 fn new(registry: &MetricsRegistry, configs: ConfigSet) -> Arc<Self> {
760 let rejected = registry.register(metric!(
761 name: "mz_balancer_connection_rejected_total",
762 help: "Count of connections refused because the connection limit was reached.",
763 ));
764 let limit = registry.register_computed_gauge(
765 metric!(
766 name: "mz_balancer_connection_limit",
767 help: "Maximum number of connections proxied at once, 0 if unlimited.",
768 ),
769 {
770 let configs = configs.clone();
771 move || u64::from(MAX_CONNECTIONS.get(&configs))
772 },
773 );
774 let watermark_transitions = registry.register(metric!(
775 name: "mz_balancer_connection_watermark_transitions_total",
776 help: "Count of times balancerd left or returned to load balancer rotation because \
777 of the connection watermarks.",
778 ));
779 let accepting = Arc::new(AtomicBool::new(true));
780 let accepting_gauge: ComputedIntGauge = registry.register_computed_gauge(
781 metric!(
782 name: "mz_balancer_connection_accepting",
783 help: "Whether /api/readyz reports ready: 1 in load balancer rotation, 0 out.",
784 ),
785 {
786 let accepting = Arc::clone(&accepting);
787 move || i64::from(accepting.load(Ordering::Relaxed))
788 },
789 );
790 let high_watermark = registry.register_computed_gauge(
792 metric!(
793 name: "mz_balancer_connection_high_watermark",
794 help: "Connections at which balancerd leaves load balancer rotation, 0 if disabled.",
795 ),
796 {
797 let configs = configs.clone();
798 move || watermark_metric(&configs, |w| w.high)
799 },
800 );
801 let low_watermark = registry.register_computed_gauge(
802 metric!(
803 name: "mz_balancer_connection_low_watermark",
804 help: "Connections below which balancerd returns to load balancer rotation, 0 if \
805 disabled.",
806 ),
807 {
808 let configs = configs.clone();
809 move || watermark_metric(&configs, |w| w.low)
810 },
811 );
812 Arc::new(ConnectionLimiter {
813 configs,
814 active: AtomicU32::new(0),
815 limited: AtomicBool::new(false),
816 accepting,
817 watermark_state: Mutex::new(WatermarkState::default()),
818 rejected,
819 watermark_transitions,
820 _limit: limit,
821 _accepting: accepting_gauge,
822 _high_watermark: high_watermark,
823 _low_watermark: low_watermark,
824 })
825 }
826
827 #[cfg(test)]
828 fn accepting(&self) -> bool {
829 self.accepting.load(Ordering::Relaxed)
830 }
831
832 fn evaluate_watermarks(&self) -> bool {
844 let mut state = self.watermark_state.lock().expect("lock poisoned");
845 let watermarks = self.watermarks(&mut state.last_warning);
846 let active = usize::cast_from(self.active.load(Ordering::SeqCst));
847 let accepting = match (watermarks, state.out_of_rotation) {
848 (Some(w), false) if active >= w.high => {
849 warn!(
850 "leaving load balancer rotation: {active} active connections reached the \
851 high watermark of {}",
852 w.high
853 );
854 false
855 }
856 (Some(w), true) if active < w.low => {
857 info!(
858 "returning to load balancer rotation: {active} active connections, below \
859 the low watermark of {}",
860 w.low
861 );
862 true
863 }
864 (None, true) => {
867 info!(
868 "returning to load balancer rotation: connection watermarks no longer in effect"
869 );
870 true
871 }
872 _ => return !state.out_of_rotation,
873 };
874 state.out_of_rotation = !accepting;
875 self.accepting.store(accepting, Ordering::Relaxed);
876 self.watermark_transitions.inc();
877 accepting
878 }
879
880 fn watermarks(&self, last: &mut Option<String>) -> Option<Watermarks> {
881 match configured_watermarks(&self.configs) {
882 Ok(watermarks) => {
883 *last = None;
884 watermarks
885 }
886 Err(reason) => {
887 if last.as_deref() != Some(&reason) {
888 warn!("ignoring connection watermarks: {reason}");
889 *last = Some(reason);
890 }
891 None
892 }
893 }
894 }
895
896 fn acquire(self: &Arc<Self>) -> Option<ConnectionGuard> {
899 let limit = MAX_CONNECTIONS.get(&self.configs);
900 let unlimited = limit == 0;
901 match self
902 .active
903 .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |active| {
904 (unlimited || active < limit).then(|| active + 1)
905 }) {
906 Ok(prev) => {
907 if unlimited || prev + 1 <= limit - limit / 10 {
910 if self.limited.swap(false, Ordering::Relaxed) {
911 info!("accepting new connections again, limit is {limit}");
912 }
913 }
914 Some(ConnectionGuard(Arc::clone(self)))
915 }
916 Err(active) => {
917 self.rejected.inc();
918 if !self.limited.swap(true, Ordering::Relaxed) {
919 warn!(
920 "refusing new connections: at the limit of {limit} connections \
921 ({active} active)"
922 );
923 }
924 None
925 }
926 }
927 }
928}
929
930struct ConnectionGuard(Arc<ConnectionLimiter>);
932
933impl Drop for ConnectionGuard {
934 fn drop(&mut self) {
935 self.0.active.fetch_sub(1, Ordering::SeqCst);
936 }
937}
938
939async fn reject<A>(conn: &mut FramedConn<A>, err: ErrorResponse) -> Result<(), io::Error>
946where
947 A: AsyncRead + AsyncWrite + Unpin,
948{
949 conn.send(err).await?;
950 conn.flush().await
951}
952
953async fn under_pre_resolved_timeout<F, T, E>(
959 timeout: Duration,
960 metrics: &ServerMetrics,
961 fut: F,
962) -> Result<T, E>
963where
964 F: Future<Output = Result<T, E>>,
965 E: From<io::Error>,
966{
967 if timeout.is_zero() {
968 return fut.await;
969 }
970 match tokio::time::timeout(timeout, fut).await {
971 Ok(result) => result,
972 Err(_) => {
973 metrics.pre_resolved_timeout().inc();
974 Err(io::Error::new(
975 io::ErrorKind::TimedOut,
976 "timed out before resolving a backend",
977 )
978 .into())
979 }
980 }
981}
982
983pub enum CancellationResolver {
984 Directory(PathBuf),
985 Static(String),
986}
987
988struct PgwireBalancer {
989 configs: ConfigSet,
991 tls: Option<ReloadingTlsConfig>,
992 internal_tls: Option<SslConnector>,
993 cancellation_resolver: Arc<CancellationResolver>,
994 resolver: Arc<BalancerResolver>,
995 metrics: ServerMetrics,
996 limiter: Arc<ConnectionLimiter>,
997 now: NowFn,
998}
999
1000impl PgwireBalancer {
1001 async fn pre_resolve(
1006 conn: Connection,
1007 tls: Option<ReloadingTlsConfig>,
1008 resolver: &BalancerResolver,
1009 cancellation_resolver: Arc<CancellationResolver>,
1010 conn_uuid: Uuid,
1011 metrics: &ServerMetrics,
1012 ) -> Result<
1013 Option<(
1014 FramedConn<Connection>,
1015 ResolvedAddr,
1016 BTreeMap<String, String>,
1017 )>,
1018 anyhow::Error,
1019 > {
1020 let peer_addr = conn.peer_addr();
1021 let mut conn = Conn::Unencrypted(conn);
1022 loop {
1023 let message = decode_startup(&mut conn, MAX_STARTUP_FRAME_SIZE).await?;
1024 conn = match message {
1025 None => return Ok(None),
1029
1030 Some(FrontendStartupMessage::Startup {
1031 version,
1032 mut params,
1033 }) => {
1034 let mut conn = FramedConn::new(conn);
1035 let rejected = SqlState::SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION;
1036 let peer_addr = match peer_addr {
1037 Ok(addr) => addr.ip(),
1038 Err(e) => {
1039 error!("Invalid peer_addr {:?}", e);
1040 reject(
1041 &mut conn,
1042 ErrorResponse::fatal(rejected, "invalid peer address"),
1043 )
1044 .await?;
1045 return Ok(None);
1046 }
1047 };
1048 debug!(
1049 %conn_uuid, %peer_addr,
1050 "starting new pgwire connection in balancer",
1051 );
1052 let prev = params.insert(CONN_UUID_KEY.to_string(), conn_uuid.to_string());
1053 if prev.is_some() {
1054 reject(
1055 &mut conn,
1056 ErrorResponse::fatal(
1057 rejected,
1058 format!("invalid parameter '{CONN_UUID_KEY}'"),
1059 ),
1060 )
1061 .await?;
1062 return Ok(None);
1063 }
1064
1065 let forwarded_for = params.insert(
1066 MZ_FORWARDED_FOR_KEY.to_string(),
1067 peer_addr.to_string().clone(),
1068 );
1069 if let Some(_) = forwarded_for {
1070 reject(
1071 &mut conn,
1072 ErrorResponse::fatal(
1073 rejected,
1074 format!("invalid parameter '{MZ_FORWARDED_FOR_KEY}'"),
1075 ),
1076 )
1077 .await?;
1078 return Ok(None);
1079 };
1080
1081 let Some(resolved) = Self::resolve_destination(
1082 &mut conn,
1083 version,
1084 ¶ms,
1085 resolver,
1086 tls.map(|tls| tls.mode),
1087 metrics,
1088 )
1089 .await?
1090 else {
1091 return Ok(None);
1092 };
1093 return Ok(Some((conn, resolved, params)));
1094 }
1095
1096 Some(FrontendStartupMessage::CancelRequest {
1097 conn_id,
1098 secret_key,
1099 }) => {
1100 spawn(|| "cancel request", async move {
1101 cancel_request(conn_id, secret_key, &cancellation_resolver).await;
1102 });
1103 return Ok(None);
1106 }
1107
1108 Some(FrontendStartupMessage::SslRequest) => match (conn, &tls) {
1109 (Conn::Unencrypted(mut conn), Some(tls)) => {
1110 conn.write_all(&[ACCEPT_SSL_ENCRYPTION]).await?;
1111 let mut ssl_stream = SslStream::new(Ssl::new(&tls.context.get())?, conn)?;
1112 if let Err(e) = Pin::new(&mut ssl_stream).accept().await {
1113 let _ = ssl_stream.get_mut().shutdown().await;
1114 return Err(e.into());
1115 }
1116 Conn::Ssl(ssl_stream)
1117 }
1118 (mut conn, _) => {
1119 conn.write_all(&[REJECT_ENCRYPTION]).await?;
1120 conn
1121 }
1122 },
1123
1124 Some(FrontendStartupMessage::GssEncRequest) => {
1125 conn.write_all(&[REJECT_ENCRYPTION]).await?;
1126 conn
1127 }
1128 }
1129 }
1130 }
1131
1132 #[mz_ore::instrument(level = "debug")]
1133 async fn resolve_destination<'a, A>(
1137 conn: &'a mut FramedConn<A>,
1138 version: i32,
1139 params: &BTreeMap<String, String>,
1140 resolver: &BalancerResolver,
1141 tls_mode: Option<TlsMode>,
1142 metrics: &ServerMetrics,
1143 ) -> Result<Option<ResolvedAddr>, io::Error>
1144 where
1145 A: AsyncRead + AsyncWrite + AsyncReady + Send + Sync + Unpin,
1146 {
1147 if version != VERSION_3 {
1148 reject(
1149 conn,
1150 ErrorResponse::fatal(
1151 SqlState::SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION,
1152 "server does not support the client's requested protocol version",
1153 ),
1154 )
1155 .await?;
1156 return Ok(None);
1157 }
1158
1159 let Some(user) = params.get("user") else {
1160 reject(
1161 conn,
1162 ErrorResponse::fatal(
1163 SqlState::SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION,
1164 "user parameter required",
1165 ),
1166 )
1167 .await?;
1168 return Ok(None);
1169 };
1170
1171 if let Err(err) = conn.inner().ensure_tls_compatibility(&tls_mode) {
1172 reject(conn, err).await?;
1173 return Ok(None);
1174 }
1175
1176 let resolved = match resolver.resolve(conn, user, metrics).await {
1177 Ok(v) => v,
1178 Err(err) => {
1179 let sql_state = match &err {
1180 ResolveError::InvalidPassword => SqlState::INVALID_PASSWORD,
1181 ResolveError::Client(details) => {
1182 warn!("client-caused connection failure: {details:#}");
1183 SqlState::SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION
1184 }
1185 ResolveError::Upstream(details) => {
1186 warn!("upstream not available: {details:#}");
1187 SqlState::SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION
1188 }
1189 ResolveError::Internal(details) => {
1190 error!("resolving connection destination: {details:#}");
1191 SqlState::SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION
1192 }
1193 };
1194 reject(conn, ErrorResponse::fatal(sql_state, err.to_string())).await?;
1195 return Ok(None);
1196 }
1197 };
1198
1199 Ok(Some(resolved))
1200 }
1201
1202 async fn proxy<A>(
1204 mut conn: FramedConn<A>,
1205 resolved: ResolvedAddr,
1206 params: BTreeMap<String, String>,
1207 internal_tls: Option<&SslConnector>,
1208 metrics: &ServerMetrics,
1209 ) -> Result<(), io::Error>
1210 where
1211 A: AsyncRead + AsyncWrite + AsyncReady + Send + Sync + Unpin,
1212 {
1213 let _active_guard = resolved
1214 .tenant
1215 .as_ref()
1216 .map(|tenant| metrics.tenant_connections(tenant));
1217 let mut mz_stream = match Self::init_stream(
1218 &mut conn,
1219 resolved.addr,
1220 resolved.password,
1221 params,
1222 internal_tls,
1223 )
1224 .await
1225 {
1226 Ok(stream) => stream,
1227 Err(e) => {
1228 error!("failed to connect to upstream server: {e}");
1229 return reject(
1230 &mut conn,
1231 ErrorResponse::fatal(
1232 SqlState::SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION,
1233 "upstream server not available",
1234 ),
1235 )
1236 .await;
1237 }
1238 };
1239
1240 let (mut client, unread) = conn.into_parts();
1244 if !unread.is_empty() {
1245 mz_stream.write_all(&unread).await?;
1246 }
1247 drop(unread);
1248
1249 let mut client_counter = CountingConn::new(&mut client);
1250
1251 let res = tokio::io::copy_bidirectional(&mut client_counter, &mut mz_stream).await;
1254 if let Some(tenant) = &resolved.tenant {
1255 metrics
1256 .tenant_connections_tx(tenant)
1257 .inc_by(u64::cast_from(client_counter.written));
1258 metrics
1259 .tenant_connections_rx(tenant)
1260 .inc_by(u64::cast_from(client_counter.read));
1261 }
1262 res?;
1263
1264 Ok(())
1265 }
1266
1267 #[mz_ore::instrument(level = "debug")]
1268 async fn init_stream<'a, A>(
1269 conn: &'a mut FramedConn<A>,
1270 envd_addr: SocketAddr,
1271 password: Option<String>,
1272 params: BTreeMap<String, String>,
1273 internal_tls: Option<&SslConnector>,
1274 ) -> Result<Conn<netio::Stream>, anyhow::Error>
1275 where
1276 A: AsyncRead + AsyncWrite + AsyncReady + Send + Sync + Unpin,
1277 {
1278 let mut mz_stream = netio::Stream::connect(envd_addr).await?;
1283 let mut buf = BytesMut::new();
1284
1285 let mut mz_stream = if let Some(internal_tls) = internal_tls {
1286 FrontendStartupMessage::SslRequest.encode(&mut buf)?;
1287 mz_stream.write_all(&buf).await?;
1288 buf.clear();
1289 let mut maybe_ssl_request_response = [0u8; 1];
1290 let nread =
1291 netio::read_exact_or_eof(&mut mz_stream, &mut maybe_ssl_request_response).await?;
1292 if nread == 1 && maybe_ssl_request_response == [ACCEPT_SSL_ENCRYPTION] {
1293 let mut ssl = internal_tls.configure()?.into_ssl(&envd_addr.to_string())?;
1295 ssl.set_connect_state();
1296 Conn::Ssl(SslStream::new(ssl, mz_stream)?)
1297 } else {
1298 Conn::Unencrypted(mz_stream)
1299 }
1300 } else {
1301 Conn::Unencrypted(mz_stream)
1302 };
1303
1304 let startup = FrontendStartupMessage::Startup {
1306 version: VERSION_3,
1307 params,
1308 };
1309 startup.encode(&mut buf)?;
1310 mz_stream.write_all(&buf).await?;
1311 let client_stream = conn.inner_mut();
1312
1313 if password.is_none() {
1329 return Ok(mz_stream);
1330 }
1331
1332 let mut maybe_auth_frame = [0; 1 + 4 + 4];
1336 let nread = netio::read_exact_or_eof(&mut mz_stream, &mut maybe_auth_frame).await?;
1337 const AUTH_PASSWORD_CLEARTEXT: [u8; 9] = [b'R', 0, 0, 0, 8, 0, 0, 0, 3];
1340 if nread == AUTH_PASSWORD_CLEARTEXT.len()
1341 && maybe_auth_frame == AUTH_PASSWORD_CLEARTEXT
1342 && password.is_some()
1343 {
1344 let Some(password) = password else {
1346 unreachable!("verified some above");
1347 };
1348 let password = FrontendMessage::Password { password };
1349 buf.clear();
1350 password.encode(&mut buf)?;
1351 mz_stream.write_all(&buf).await?;
1352 mz_stream.flush().await?;
1353 } else {
1354 client_stream.write_all(&maybe_auth_frame[0..nread]).await?;
1357 }
1358
1359 Ok(mz_stream)
1360 }
1361}
1362
1363impl mz_server_core::Server for PgwireBalancer {
1364 const NAME: &'static str = "pgwire_balancer";
1365
1366 fn handle_connection(
1367 &self,
1368 conn: Connection,
1369 _tokio_metrics_intervals: impl Iterator<Item = TaskMetrics> + Send + 'static,
1370 ) -> mz_server_core::ConnectionHandler {
1371 let tls = self.tls.clone();
1372 let internal_tls = self.internal_tls.clone();
1373 let resolver = Arc::clone(&self.resolver);
1374 let inner_metrics = self.metrics.clone();
1375 let outer_metrics = self.metrics.clone();
1376 let limiter = Arc::clone(&self.limiter);
1377 let pre_resolved_timeout = PRE_RESOLVED_TIMEOUT.get(&self.configs);
1378 let cancellation_resolver = Arc::clone(&self.cancellation_resolver);
1379 let conn_uuid = epoch_to_uuid_v7(&(self.now)());
1380 conn.uuid_handle().set(conn_uuid);
1381 Box::pin(async move {
1382 let active_guard = outer_metrics.active_connections();
1385 let result: Result<(), anyhow::Error> = async move {
1386 let Some(_conn_guard) = limiter.acquire() else {
1387 return Ok(());
1388 };
1389 let pre_resolved = inner_metrics.pre_resolved_connections();
1390
1391 let destination = under_pre_resolved_timeout(
1395 pre_resolved_timeout,
1396 &inner_metrics,
1397 Box::pin(Self::pre_resolve(
1398 conn,
1399 tls,
1400 &resolver,
1401 cancellation_resolver,
1402 conn_uuid,
1403 &inner_metrics,
1404 )),
1405 )
1406 .await?;
1407
1408 let Some((conn, resolved, params)) = destination else {
1409 return Ok(());
1410 };
1411 drop(pre_resolved);
1412
1413 PgwireBalancer::proxy(
1414 conn,
1415 resolved,
1416 params,
1417 internal_tls.as_ref(),
1418 &inner_metrics,
1419 )
1420 .await?;
1421 Ok(())
1422 }
1423 .await;
1424 drop(active_guard);
1425 outer_metrics.connection_status(result.is_ok()).inc();
1426 Ok(())
1427 })
1428 }
1429}
1430
1431struct CountingConn<C> {
1433 inner: C,
1434 read: usize,
1435 written: usize,
1436}
1437
1438impl<C> CountingConn<C> {
1439 fn new(inner: C) -> Self {
1440 CountingConn {
1441 inner,
1442 read: 0,
1443 written: 0,
1444 }
1445 }
1446}
1447
1448impl<C> AsyncRead for CountingConn<C>
1449where
1450 C: AsyncRead + Unpin,
1451{
1452 fn poll_read(
1453 self: Pin<&mut Self>,
1454 cx: &mut std::task::Context<'_>,
1455 buf: &mut io::ReadBuf<'_>,
1456 ) -> std::task::Poll<std::io::Result<()>> {
1457 let counter = self.get_mut();
1458 let pin = Pin::new(&mut counter.inner);
1459 let bytes = buf.filled().len();
1460 let poll = pin.poll_read(cx, buf);
1461 let bytes = buf.filled().len() - bytes;
1462 if let std::task::Poll::Ready(Ok(())) = poll {
1463 counter.read += bytes
1464 }
1465 poll
1466 }
1467}
1468
1469impl<C> AsyncWrite for CountingConn<C>
1470where
1471 C: AsyncWrite + Unpin,
1472{
1473 fn poll_write(
1474 self: Pin<&mut Self>,
1475 cx: &mut std::task::Context<'_>,
1476 buf: &[u8],
1477 ) -> std::task::Poll<Result<usize, std::io::Error>> {
1478 let counter = self.get_mut();
1479 let pin = Pin::new(&mut counter.inner);
1480 let poll = pin.poll_write(cx, buf);
1481 if let std::task::Poll::Ready(Ok(bytes)) = poll {
1482 counter.written += bytes
1483 }
1484 poll
1485 }
1486
1487 fn poll_flush(
1488 self: Pin<&mut Self>,
1489 cx: &mut std::task::Context<'_>,
1490 ) -> std::task::Poll<Result<(), std::io::Error>> {
1491 let counter = self.get_mut();
1492 let pin = Pin::new(&mut counter.inner);
1493 pin.poll_flush(cx)
1494 }
1495
1496 fn poll_shutdown(
1497 self: Pin<&mut Self>,
1498 cx: &mut std::task::Context<'_>,
1499 ) -> std::task::Poll<Result<(), std::io::Error>> {
1500 let counter = self.get_mut();
1501 let pin = Pin::new(&mut counter.inner);
1502 pin.poll_shutdown(cx)
1503 }
1504}
1505
1506async fn cancel_request(
1523 conn_id: u32,
1524 secret_key: u32,
1525 cancellation_resolver: &CancellationResolver,
1526) {
1527 let suffix = conn_id_org_uuid(conn_id);
1528 let contents = match cancellation_resolver {
1529 CancellationResolver::Directory(dir) => {
1530 let path = dir.join(&suffix);
1531 match std::fs::read_to_string(&path) {
1532 Ok(contents) => contents,
1533 Err(err) => {
1534 error!("could not read cancel file {path:?}: {err}");
1535 return;
1536 }
1537 }
1538 }
1539 CancellationResolver::Static(addr) => addr.to_owned(),
1540 };
1541 let mut all_ips = Vec::new();
1542 for addr in contents.lines() {
1543 let addr = addr.trim();
1544 if addr.is_empty() {
1545 continue;
1546 }
1547 match tokio::net::lookup_host(addr).await {
1548 Ok(ips) => all_ips.extend(ips),
1549 Err(err) => {
1550 error!("{addr} failed resolution: {err}");
1551 }
1552 }
1553 }
1554 let mut buf = BytesMut::with_capacity(16);
1555 let msg = FrontendStartupMessage::CancelRequest {
1556 conn_id,
1557 secret_key,
1558 };
1559 msg.encode(&mut buf).expect("must encode");
1560 let buf = buf.freeze();
1561 for ip in all_ips {
1562 debug!("cancelling {suffix} to {ip}");
1563 let buf = buf.clone();
1564 spawn(|| "cancel request for ip", async move {
1565 let send = async {
1566 let mut stream = TcpStream::connect(&ip).await?;
1567 stream.write_all(&buf).await?;
1568 stream.shutdown().await?;
1569 Ok::<_, io::Error>(())
1570 };
1571 if let Err(err) = send.await {
1572 error!("error mirroring cancel to {ip}: {err}");
1573 }
1574 });
1575 }
1576}
1577
1578async fn send_http_error(client_stream: &mut Box<dyn ClientStream>, status: &str, body: &str) {
1583 let response = format!(
1584 "HTTP/1.1 {status}\r\n\
1585 Content-Type: text/plain\r\n\
1586 Content-Length: {}\r\n\
1587 Connection: close\r\n\
1588 \r\n\
1589 {body}",
1590 body.len(),
1591 );
1592 let _ = client_stream.write_all(response.as_bytes()).await;
1593 let _ = client_stream.shutdown().await;
1594}
1595
1596struct HttpsBalancer {
1597 resolver: Arc<TenantDnsResolver>,
1598 tls: Option<ReloadingSslContext>,
1599 resolve_template: Arc<str>,
1600 port: u16,
1601 metrics: Arc<ServerMetrics>,
1602 limiter: Arc<ConnectionLimiter>,
1603 configs: ConfigSet,
1604 internal_tls: Option<SslConnector>,
1605}
1606
1607impl HttpsBalancer {
1608 async fn pre_resolve(
1610 conn: Connection,
1611 tls_context: Option<ReloadingSslContext>,
1612 resolver: &TenantDnsResolver,
1613 resolve_template: &str,
1614 port: u16,
1615 ) -> Result<(Box<dyn ClientStream>, ResolvedAddr, SocketAddr), anyhow::Error> {
1616 let peer_addr = conn.peer_addr().context("fetching peer addr")?;
1617 let (client_stream, servername): (Box<dyn ClientStream>, Option<String>) = match tls_context
1618 {
1619 Some(tls_context) => {
1620 let mut ssl_stream = SslStream::new(Ssl::new(&tls_context.get())?, conn)?;
1621 if let Err(e) = Pin::new(&mut ssl_stream).accept().await {
1622 let _ = ssl_stream.get_mut().shutdown().await;
1623 return Err(e.into());
1624 }
1625 let servername: Option<String> =
1626 ssl_stream.ssl().servername(NameType::HOST_NAME).map(|sn| {
1627 match sn.split_once('.') {
1628 Some((left, _right)) => left,
1629 None => sn,
1630 }
1631 .into()
1632 });
1633 debug!("Found sni servername: {servername:?} (https)");
1634 (Box::new(ssl_stream), servername)
1635 }
1636 _ => (Box::new(conn), None),
1637 };
1638 let resolved =
1639 Self::resolve(resolver, resolve_template, port, servername.as_deref()).await?;
1640 Ok((client_stream, resolved, peer_addr))
1641 }
1642
1643 async fn resolve(
1644 resolver: &TenantDnsResolver,
1645 resolve_template: &str,
1646 port: u16,
1647 servername: Option<&str>,
1648 ) -> Result<ResolvedAddr, anyhow::Error> {
1649 let (addr, tenant) = match servername {
1650 Some(sni) => resolver.resolve_sni(resolve_template, port, sni).await?,
1651 None => {
1652 debug!("https hostname (no SNI): {}:{}", resolve_template, port);
1655 resolver.resolve(resolve_template, port).await?
1656 }
1657 };
1658
1659 Ok(ResolvedAddr {
1660 addr,
1661 password: None,
1662 tenant,
1663 })
1664 }
1665}
1666
1667fn extract_tenant_from_cname(cname: &str) -> Option<String> {
1680 let mut parts = cname.split('.');
1681 let _service = parts.next();
1682 let Some(namespace) = parts.next() else {
1683 return None;
1684 };
1685 let Some((_, namespace)) = namespace.split_once('-') else {
1687 return None;
1688 };
1689 let Some((tenant, _)) = namespace.rsplit_once('-') else {
1691 return None;
1692 };
1693 let Ok(tenant) = Uuid::parse_str(tenant) else {
1696 error!("cname tenant not a uuid: {tenant}");
1697 return None;
1698 };
1699 Some(tenant.to_string())
1700}
1701
1702fn strip_ipv6_brackets(host: &str) -> &str {
1707 host.strip_prefix('[')
1708 .and_then(|h| h.strip_suffix(']'))
1709 .unwrap_or(host)
1710}
1711
1712impl mz_server_core::Server for HttpsBalancer {
1713 const NAME: &'static str = "https_balancer";
1714
1715 fn handle_connection(
1717 &self,
1718 conn: Connection,
1719 _tokio_metrics_intervals: impl Iterator<Item = TaskMetrics> + Send + 'static,
1720 ) -> mz_server_core::ConnectionHandler {
1721 let tls_context = self.tls.clone();
1722 let internal_tls = self.internal_tls.clone();
1723 let resolver = Arc::clone(&self.resolver);
1724 let resolve_template = Arc::clone(&self.resolve_template);
1725 let port = self.port;
1726 let inner_metrics = Arc::clone(&self.metrics);
1727 let outer_metrics = Arc::clone(&self.metrics);
1728 let limiter = Arc::clone(&self.limiter);
1729 let pre_resolved_timeout = PRE_RESOLVED_TIMEOUT.get(&self.configs);
1730 let inject_proxy_headers = INJECT_PROXY_PROTOCOL_HEADER_HTTP.get(&self.configs);
1731 Box::pin(async move {
1732 let active_guard = inner_metrics.active_connections();
1733 let result: Result<_, anyhow::Error> = Box::pin(async move {
1734 let Some(_conn_guard) = limiter.acquire() else {
1735 return Ok(());
1736 };
1737 let pre_resolved = inner_metrics.pre_resolved_connections();
1738
1739 let (mut client_stream, resolved, peer_addr) = under_pre_resolved_timeout(
1740 pre_resolved_timeout,
1741 &inner_metrics,
1742 Self::pre_resolve(conn, tls_context, &resolver, &resolve_template, port),
1743 )
1744 .await?;
1745 drop(pre_resolved);
1746 let inner_active_guard = resolved
1747 .tenant
1748 .as_ref()
1749 .map(|tenant| inner_metrics.tenant_connections(tenant));
1750 let mut mz_stream = match netio::Stream::connect(resolved.addr).await {
1751 Ok(stream) => stream,
1752 Err(e) => {
1753 error!("failed to connect to upstream server: {e}");
1754 send_http_error(
1755 &mut client_stream,
1756 "502 Bad Gateway",
1757 "upstream server not available",
1758 )
1759 .await;
1760 return Ok(());
1761 }
1762 };
1763
1764 if inject_proxy_headers {
1765 let addrs = ProxiedAddress::stream(peer_addr, resolved.addr);
1767 let header = ProxyHeader::with_address(addrs);
1768 let mut buf = [0u8; 1024];
1769 let len = header.encode_to_slice_v2(&mut buf)?;
1770 mz_stream.write_all(&buf[..len]).await?;
1771 }
1772
1773 let mut mz_stream = if let Some(internal_tls) = &internal_tls {
1774 let mut ssl = internal_tls
1776 .configure()?
1777 .into_ssl(&resolved.addr.to_string())?;
1778 ssl.set_connect_state();
1779 Conn::Ssl(SslStream::new(ssl, mz_stream)?)
1780 } else {
1781 Conn::Unencrypted(mz_stream)
1782 };
1783
1784 let mut client_counter = CountingConn::new(client_stream);
1785
1786 let _ = tokio::io::copy_bidirectional(&mut client_counter, &mut mz_stream).await;
1790 if let Some(tenant) = &resolved.tenant {
1791 inner_metrics
1792 .tenant_connections_tx(tenant)
1793 .inc_by(u64::cast_from(client_counter.written));
1794 inner_metrics
1795 .tenant_connections_rx(tenant)
1796 .inc_by(u64::cast_from(client_counter.read));
1797 }
1798 drop(inner_active_guard);
1799 Ok(())
1800 })
1801 .await;
1802 drop(active_guard);
1803 outer_metrics.connection_status(result.is_ok()).inc();
1804 if let Err(e) = result {
1805 debug!("connection error: {e}");
1806 }
1807 Ok(())
1808 })
1809 }
1810}
1811
1812#[derive(Debug)]
1815pub struct SniTemplate {
1816 pub template: String,
1817 pub port: u16,
1818}
1819
1820trait ClientStream: AsyncRead + AsyncWrite + Unpin + Send {}
1821impl<T: AsyncRead + AsyncWrite + Unpin + Send> ClientStream for T {}
1822
1823#[derive(Debug)]
1824pub enum BalancerResolver {
1825 Static(String),
1826 MultiTenant {
1827 dns: Arc<TenantDnsResolver>,
1828 frontegg: FronteggResolver,
1829 sni: Option<SniTemplate>,
1830 },
1831}
1832
1833#[derive(Debug, thiserror::Error)]
1839enum ResolveError {
1840 #[error("invalid password")]
1841 InvalidPassword,
1842 #[error("internal error")]
1847 Client(#[source] anyhow::Error),
1848 #[error("upstream server not available")]
1855 Upstream(#[source] anyhow::Error),
1856 #[error("internal error")]
1858 Internal(#[from] anyhow::Error),
1859}
1860
1861impl From<io::Error> for ResolveError {
1862 fn from(e: io::Error) -> Self {
1863 ResolveError::Internal(e.into())
1864 }
1865}
1866
1867impl BalancerResolver {
1868 pub fn shared_dns(&self) -> Option<Arc<TenantDnsResolver>> {
1871 match self {
1872 BalancerResolver::Static(_) => None,
1873 BalancerResolver::MultiTenant { dns, .. } => Some(Arc::clone(dns)),
1874 }
1875 }
1876
1877 async fn resolve<A>(
1878 &self,
1879 conn: &mut FramedConn<A>,
1880 user: &str,
1881 metrics: &ServerMetrics,
1882 ) -> Result<ResolvedAddr, ResolveError>
1883 where
1884 A: AsyncRead + AsyncWrite + Unpin,
1885 {
1886 match self {
1887 BalancerResolver::MultiTenant {
1888 dns: dns_resolver,
1889 frontegg:
1890 FronteggResolver {
1891 auth,
1892 addr_template,
1893 },
1894 sni: sni_resolver,
1895 } => {
1896 let servername = match conn.inner() {
1897 Conn::Ssl(ssl_stream) => {
1898 ssl_stream.ssl().servername(NameType::HOST_NAME).map(|sn| {
1899 match sn.split_once('.') {
1900 Some((left, _right)) => left,
1901 None => sn,
1902 }
1903 })
1904 }
1905 Conn::Unencrypted(_) => None,
1906 };
1907 let has_sni = servername.is_some();
1908 let resolved_addr = match (servername, sni_resolver.as_ref()) {
1909 (Some(servername), Some(SniTemplate { template, port })) => {
1910 let (addr, tenant) = dns_resolver
1914 .resolve_sni(template, *port, servername)
1915 .await
1916 .map_err(ResolveError::Upstream)?;
1917 debug!("pgwire SNI resolved tenant: {:?}", tenant);
1918 ResolvedAddr {
1919 addr,
1920 password: None,
1921 tenant,
1922 }
1923 }
1924 _ => {
1925 conn.send(BackendMessage::AuthenticationCleartextPassword)
1926 .await?;
1927 conn.flush().await?;
1928 let password = match conn.recv().await? {
1929 Some(FrontendMessage::Password { password }) => password,
1930 _ => {
1931 return Err(ResolveError::Client(anyhow::anyhow!(
1932 "expected Password message"
1933 )));
1934 }
1935 };
1936
1937 let auth_response = auth.authenticate(user, &password, None).await;
1941 let auth_session = match auth_response {
1942 Ok((auth_session, _)) => auth_session,
1943 Err(e) => {
1944 warn!("pgwire connection failed authentication: {}", e);
1945 return Err(ResolveError::InvalidPassword);
1946 }
1947 };
1948
1949 let hostname_with_port =
1950 addr_template.replace("{}", &auth_session.tenant_id().to_string());
1951 let (hostname, port_str) = hostname_with_port
1952 .rsplit_once(':')
1953 .ok_or_else(|| anyhow::anyhow!("port required in addr_template"))?;
1954 let port: u16 = port_str.parse().with_context(|| {
1955 format!("invalid port in addr_template: {}", port_str)
1956 })?;
1957 let addr = dns_resolver
1961 .resolve_addr(hostname, port)
1962 .await
1963 .map_err(ResolveError::Upstream)?;
1964 let tenant = auth_session.tenant_id().to_string();
1965 debug!("Frontegg resolved tenant: {}", tenant);
1966 ResolvedAddr {
1967 addr,
1968 password: Some(password),
1969 tenant: Some(tenant),
1970 }
1971 }
1972 };
1973 metrics
1974 .tenant_pgwire_sni_count(
1975 resolved_addr.tenant.as_deref().unwrap_or("unknown"),
1976 has_sni,
1977 )
1978 .inc();
1979
1980 Ok(resolved_addr)
1981 }
1982 BalancerResolver::Static(addr) => {
1983 let Some(addr) = tokio::net::lookup_host(addr).await?.next() else {
1986 return Err(anyhow::anyhow!("{addr} did not resolve to any addresses").into());
1987 };
1988
1989 Ok(ResolvedAddr {
1990 addr,
1991 password: None,
1992 tenant: None,
1993 })
1994 }
1995 }
1996 }
1997}
1998
1999fn create_resolver() -> Result<TokioResolver, anyhow::Error> {
2007 let (config, mut opts) = read_system_conf().context("reading system DNS configuration")?;
2008 opts.cache_size = 0;
2009 opts.ip_strategy = LookupIpStrategy::Ipv4thenIpv6;
2011
2012 Ok(
2013 Resolver::builder_with_config(config, TokioRuntimeProvider::default())
2014 .with_options(opts)
2015 .build()?,
2016 )
2017}
2018
2019#[derive(Debug)]
2025pub struct TenantDnsResolver {
2026 resolver: TokioResolver,
2027}
2028
2029impl TenantDnsResolver {
2030 pub fn new() -> Result<Self, anyhow::Error> {
2033 Ok(Self {
2034 resolver: create_resolver()?,
2035 })
2036 }
2037
2038 async fn resolve_cname(&self, hostname: &str) -> Option<String> {
2040 match self.resolver.lookup(hostname, RecordType::CNAME).await {
2041 Ok(cname_response) => {
2042 if let Some(cname_record) = cname_response.answers().first() {
2043 if let RData::CNAME(cname_data) = &cname_record.data {
2044 let cname = cname_data.to_string();
2045 debug!("CNAME for {}: {}", hostname, cname);
2046 return Some(cname);
2047 }
2048 }
2049 None
2050 }
2051 Err(e) => {
2052 debug!("CNAME lookup failed for {}: {}", hostname, e);
2053 None
2054 }
2055 }
2056 }
2057
2058 async fn resolve_a(&self, hostname: &str) -> Result<LookupIp, anyhow::Error> {
2060 self.resolver
2061 .lookup_ip(hostname)
2062 .await
2063 .with_context(|| format!("resolving A records for {}", hostname))
2064 }
2065
2066 pub async fn resolve_sni(
2077 &self,
2078 template: &str,
2079 port: u16,
2080 servername: &str,
2081 ) -> Result<(SocketAddr, Option<String>), anyhow::Error> {
2082 let hostname = template.replace("{}", servername);
2083 debug!("SNI hostname: {}", hostname);
2084 self.resolve(&hostname, port).await
2085 }
2086
2087 async fn resolve_addr(&self, host: &str, port: u16) -> Result<SocketAddr, anyhow::Error> {
2090 let host = strip_ipv6_brackets(host);
2091 if let Ok(ip) = host.parse::<IpAddr>() {
2095 return Ok(SocketAddr::new(ip, port));
2096 }
2097 Self::first_addr(self.resolve_a(host).await?, port)
2098 }
2099
2100 async fn resolve(
2104 &self,
2105 host: &str,
2106 port: u16,
2107 ) -> Result<(SocketAddr, Option<String>), anyhow::Error> {
2108 let host = strip_ipv6_brackets(host);
2109 if let Ok(ip) = host.parse::<IpAddr>() {
2111 return Ok((SocketAddr::new(ip, port), None));
2112 }
2113
2114 let (ips, tenant) = if let Some(cname) = self.resolve_cname(host).await {
2116 let tenant = extract_tenant_from_cname(&cname);
2117 (self.resolve_a(&cname).await?, tenant)
2118 } else {
2119 (self.resolve_a(host).await?, None)
2120 };
2121
2122 Ok((Self::first_addr(ips, port)?, tenant))
2123 }
2124
2125 fn first_addr(ips: LookupIp, port: u16) -> Result<SocketAddr, anyhow::Error> {
2127 ips.iter()
2128 .next()
2129 .map(|ip| SocketAddr::new(ip, port))
2130 .ok_or_else(|| anyhow::anyhow!("no A records found in DNS response"))
2131 }
2132}
2133
2134#[derive(Debug)]
2135pub struct FronteggResolver {
2136 pub auth: FronteggAuthentication,
2137 pub addr_template: String,
2138}
2139
2140#[derive(Debug)]
2141struct ResolvedAddr {
2142 addr: SocketAddr,
2143 password: Option<String>,
2144 tenant: Option<String>,
2145}
2146
2147#[cfg(test)]
2148mod tests {
2149 use mz_dyncfg::ConfigUpdates;
2150
2151 use super::*;
2152
2153 #[mz_ore::test]
2154 fn test_tenant() {
2155 let tests = vec![
2156 ("", None),
2157 (
2158 "environmentd.environment-58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3-0.svc.cluster.local",
2159 Some("58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3"),
2160 ),
2161 (
2162 "environmentd.environment-58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3-0.svc.cluster.local.",
2165 Some("58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3"),
2166 ),
2167 (
2168 "service.something-58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3-0.ssvvcc.cloister.faraway",
2170 Some("58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3"),
2171 ),
2172 (
2173 "environmentd.environment-58cd23ffa4d74bd0ad85a6ff29cc86c3-0.svc.cluster.local",
2175 Some("58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3"),
2176 ),
2177 (
2178 "environmentd.environment-58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3-1234.svc.cluster.local",
2180 Some("58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3"),
2181 ),
2182 (
2183 "environmentd.environment-58CD23FF-A4D7-4BD0-AD85-A6FF29CC86C3-0.svc.cluster.local",
2185 Some("58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3"),
2186 ),
2187 (
2188 "environmentd.environment-58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3.svc.cluster.local",
2190 None,
2191 ),
2192 (
2193 "environment-58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3-0.svc.cluster.local",
2195 None,
2196 ),
2197 (
2198 "environmentd.environment-8cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3-0.svc.cluster.local",
2200 None,
2201 ),
2202 ];
2203 for (name, expect) in tests {
2204 let cname = extract_tenant_from_cname(name);
2205 assert_eq!(
2206 cname.as_deref(),
2207 expect,
2208 "{name} got {cname:?} expected {expect:?}"
2209 );
2210 }
2211 }
2212
2213 #[mz_ore::test]
2214 fn test_strip_ipv6_brackets() {
2215 assert_eq!(strip_ipv6_brackets("[::1]"), "::1");
2216 assert_eq!(strip_ipv6_brackets("[2001:db8::1]"), "2001:db8::1");
2217 assert_eq!(strip_ipv6_brackets("127.0.0.1"), "127.0.0.1");
2219 assert_eq!(strip_ipv6_brackets("host.example.com"), "host.example.com");
2220 assert_eq!(strip_ipv6_brackets("[unclosed"), "[unclosed");
2221 assert_eq!(strip_ipv6_brackets("unopened]"), "unopened]");
2222 }
2223
2224 #[mz_ore::test]
2225 fn test_connection_limiter() {
2226 let configs = dyncfgs::all_dyncfgs(ConfigSet::default());
2227 let set_max = |max: u32| {
2228 let mut updates = ConfigUpdates::default();
2229 updates.add(&MAX_CONNECTIONS, max);
2230 updates.apply(&configs);
2231 };
2232
2233 set_max(2);
2234 let limiter = ConnectionLimiter::new(&MetricsRegistry::new(), configs.clone());
2235 let first = limiter.acquire().expect("under the limit");
2236 let second = limiter.acquire().expect("at the limit");
2237 assert!(limiter.acquire().is_none());
2238 assert_eq!(limiter.rejected.get(), 1);
2239
2240 drop(second);
2242 let _third = limiter.acquire().expect("capacity freed");
2243 drop(first);
2244
2245 set_max(1);
2248 assert!(limiter.acquire().is_none());
2249
2250 set_max(0);
2252 assert!(limiter.acquire().is_some());
2253 }
2254
2255 #[mz_ore::test(tokio::test)]
2258 #[cfg_attr(miri, ignore)] async fn test_dyncfgs_sync_from_launchdarkly() {
2260 mz_dyncfg_launchdarkly::sync_launchdarkly_to_configset(
2261 dyncfgs::all_dyncfgs(ConfigSet::default()),
2262 &BUILD_INFO,
2263 |_| Ok(()),
2264 None,
2265 Duration::ZERO,
2266 None,
2267 |_, _| {},
2268 )
2269 .await
2270 .expect("every balancerd dyncfg converts to a LaunchDarkly flag");
2271 }
2272
2273 #[mz_ore::test]
2274 fn test_connection_watermarks() {
2275 let configs = dyncfgs::all_dyncfgs(ConfigSet::default());
2276 let set = |low: usize, high: usize, max: u32| {
2277 let mut updates = ConfigUpdates::default();
2278 updates.add(&CONNECTION_LOW_WATERMARK, low);
2279 updates.add(&CONNECTION_HIGH_WATERMARK, high);
2280 updates.add(&MAX_CONNECTIONS, max);
2281 updates.apply(&configs);
2282 };
2283 let limiter = ConnectionLimiter::new(&MetricsRegistry::new(), configs.clone());
2284 let _first = limiter.acquire().expect("under the limit");
2285 let second = limiter.acquire().expect("under the limit");
2286 let third = limiter.acquire().expect("under the limit");
2287
2288 for (low, high, max) in [
2290 (0, 0, 5),
2291 (1, 0, 5),
2292 (0, 3, 5),
2293 (3, 3, 5),
2294 (4, 3, 5),
2295 (1, 5, 5),
2296 (1, 3, 0),
2297 ] {
2298 set(low, high, max);
2299 limiter.evaluate_watermarks();
2300 assert!(
2301 limiter.accepting(),
2302 "watermarks low={low} high={high} max={max} must be ignored",
2303 );
2304 }
2305 assert_eq!(limiter.watermark_transitions.get(), 0);
2306
2307 set(2, 3, 5);
2308 limiter.evaluate_watermarks();
2309 assert!(!limiter.accepting(), "at the high watermark");
2310 assert_eq!(limiter.watermark_transitions.get(), 1);
2311
2312 drop(third);
2313 limiter.evaluate_watermarks();
2314 assert!(
2315 !limiter.accepting(),
2316 "between the watermarks the state is kept"
2317 );
2318 drop(second);
2319 limiter.evaluate_watermarks();
2320 assert!(
2321 limiter.accepting(),
2322 "below the low watermark readiness returns"
2323 );
2324 assert_eq!(limiter.watermark_transitions.get(), 2);
2325
2326 let _fourth = limiter.acquire().expect("under the limit");
2328 let _fifth = limiter.acquire().expect("under the limit");
2329 limiter.evaluate_watermarks();
2330 assert!(!limiter.accepting(), "at the high watermark again");
2331 set(0, 0, 5);
2332 limiter.evaluate_watermarks();
2333 assert!(
2334 limiter.accepting(),
2335 "disabling the watermarks restores readiness"
2336 );
2337 assert_eq!(limiter.watermark_transitions.get(), 4);
2338 }
2339
2340 #[mz_ore::test(tokio::test(start_paused = true))]
2342 async fn test_pre_resolved_timeout_zero_disables() {
2343 let metrics = ServerMetrics::new(
2344 ServerMetricsConfig::register_into(&MetricsRegistry::new()),
2345 "pgwire",
2346 );
2347
2348 let slow = async {
2350 tokio::time::sleep(Duration::from_secs(60 * 60)).await;
2351 Ok::<(), io::Error>(())
2352 };
2353 assert!(
2354 under_pre_resolved_timeout(Duration::ZERO, &metrics, slow)
2355 .await
2356 .is_ok()
2357 );
2358 assert_eq!(metrics.pre_resolved_timeout().get(), 0);
2359
2360 let stalled = std::future::pending::<Result<(), io::Error>>();
2362 let err = under_pre_resolved_timeout(Duration::from_secs(30), &metrics, stalled)
2363 .await
2364 .expect_err("a phase that never finishes must be cut short");
2365 assert_eq!(err.kind(), io::ErrorKind::TimedOut);
2366 assert_eq!(metrics.pre_resolved_timeout().get(), 1);
2367 }
2368}