Skip to main content

mz_balancerd/
lib.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10//! The balancerd service is a horizontally scalable, stateless, multi-tenant ingress router for
11//! pgwire and HTTPS connections.
12//!
13//! It listens on pgwire and HTTPS ports. When a new pgwire connection starts, the requested user is
14//! authenticated with frontegg from which a tenant id is returned. From that a target internal
15//! hostname is resolved to an IP address, and the connection is proxied to that address which has a
16//! running environmentd's pgwire port. When a new HTTPS connection starts, its SNI hostname is used
17//! to generate an internal hostname that is resolved to an IP address, which is similarly proxied.
18
19mod 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::NetError;
38use hickory_resolver::net::runtime::TokioRuntimeProvider;
39use hickory_resolver::proto::rr::{Name, RData, RecordType};
40use hickory_resolver::system_conf::read_system_conf;
41use hickory_resolver::{Resolver, TokioResolver};
42use hyper::StatusCode;
43use hyper_util::rt::TokioIo;
44use launchdarkly_server_sdk as ld;
45use mz_build_info::{BuildInfo, build_info};
46use mz_dyncfg::ConfigSet;
47use mz_frontegg_auth::Authenticator as FronteggAuthentication;
48use mz_orchestrator_tracing::NO_SENTRY_TARGET;
49use mz_ore::cast::CastFrom;
50use mz_ore::id_gen::conn_id_org_uuid;
51use mz_ore::metrics::{
52    ComputedGauge, ComputedIntGauge, ComputedUIntGauge, IntCounter, IntGauge, MetricsRegistry,
53};
54use mz_ore::netio::AsyncReady;
55use mz_ore::now::{NowFn, SYSTEM_TIME, epoch_to_uuid_v7};
56use mz_ore::stats::histogram_seconds_buckets;
57use mz_ore::task::{JoinSetExt, spawn};
58use mz_ore::tracing::TracingHandle;
59use mz_ore::{metric, netio};
60use mz_pgwire_common::{
61    ACCEPT_SSL_ENCRYPTION, CONN_UUID_KEY, Conn, ErrorResponse, FrontendMessage,
62    FrontendStartupMessage, MAX_STARTUP_FRAME_SIZE, MZ_FORWARDED_FOR_KEY, REJECT_ENCRYPTION,
63    VERSION_3, decode_startup,
64};
65use mz_server_core::{
66    Connection, ConnectionStream, ListenerHandle, ReloadTrigger, ReloadingSslContext,
67    ReloadingTlsConfig, ServeConfig, ServeDyncfg, TlsCertConfig, TlsMode, listen,
68};
69use openssl::error::ErrorStack;
70use openssl::ssl::{NameType, Ssl, SslConnector, SslMethod, SslVerifyMode};
71use prometheus::{Histogram, HistogramVec, IntCounterVec, IntGaugeVec};
72use proxy_header::{ProxiedAddress, ProxyHeader};
73use semver::Version;
74use tokio::io::{self, AsyncRead, AsyncWrite, AsyncWriteExt};
75use tokio::net::TcpStream;
76use tokio::sync::oneshot;
77use tokio::task::JoinSet;
78use tokio_metrics::TaskMetrics;
79use tokio_openssl::SslStream;
80use tokio_postgres::error::SqlState;
81use tower::Service;
82use tracing::{debug, error, info, warn};
83use uuid::Uuid;
84
85use crate::codec::{BackendMessage, FramedConn};
86use crate::dyncfgs::{
87    CONNECTION_HIGH_WATERMARK, CONNECTION_LOW_WATERMARK, INJECT_PROXY_PROTOCOL_HEADER_HTTP,
88    MAX_CONNECTIONS, PRE_RESOLVED_TIMEOUT, SIGTERM_CONNECTION_WAIT, SIGTERM_LISTEN_WAIT,
89    has_tracing_config_update, tracing_config,
90};
91
92/// Balancer build information.
93pub const BUILD_INFO: BuildInfo = build_info!();
94
95pub struct BalancerConfig {
96    /// Info about which version of the code is running.
97    build_version: Version,
98    /// Listen address for internal HTTP health and metrics server.
99    internal_http_listen_addr: SocketAddr,
100    /// Listen address for pgwire connections.
101    pgwire_listen_addr: SocketAddr,
102    /// Listen address for HTTPS connections.
103    https_listen_addr: SocketAddr,
104    /// DNS resolver for pgwire cancellation requests
105    cancellation_resolver: CancellationResolver,
106    /// DNS resolver.
107    resolver: BalancerResolver,
108    https_sni_addr_template: String,
109    tls: Option<TlsCertConfig>,
110    internal_tls: bool,
111    metrics_registry: MetricsRegistry,
112    reload_certs: BoxStream<'static, Option<oneshot::Sender<Result<(), anyhow::Error>>>>,
113    launchdarkly_sdk_key: Option<String>,
114    config_sync_file_path: Option<PathBuf>,
115    config_sync_timeout: Duration,
116    config_sync_loop_interval: Option<Duration>,
117    cloud_provider: Option<String>,
118    cloud_provider_region: Option<String>,
119    tracing_handle: TracingHandle,
120    default_configs: Vec<(String, String)>,
121}
122
123impl BalancerConfig {
124    pub fn new(
125        build_info: &BuildInfo,
126        internal_http_listen_addr: SocketAddr,
127        pgwire_listen_addr: SocketAddr,
128        https_listen_addr: SocketAddr,
129        cancellation_resolver: CancellationResolver,
130        resolver: BalancerResolver,
131        https_sni_addr_template: String,
132        tls: Option<TlsCertConfig>,
133        internal_tls: bool,
134        metrics_registry: MetricsRegistry,
135        reload_certs: ReloadTrigger,
136        launchdarkly_sdk_key: Option<String>,
137        config_sync_file: Option<PathBuf>,
138        config_sync_timeout: Duration,
139        config_sync_loop_interval: Option<Duration>,
140        cloud_provider: Option<String>,
141        cloud_provider_region: Option<String>,
142        tracing_handle: TracingHandle,
143        default_configs: Vec<(String, String)>,
144    ) -> Self {
145        Self {
146            build_version: build_info.semver_version(),
147            internal_http_listen_addr,
148            pgwire_listen_addr,
149            https_listen_addr,
150            cancellation_resolver,
151            resolver,
152            https_sni_addr_template,
153            tls,
154            internal_tls,
155            metrics_registry,
156            reload_certs,
157            launchdarkly_sdk_key,
158            config_sync_file_path: config_sync_file,
159            config_sync_timeout,
160            config_sync_loop_interval,
161            cloud_provider,
162            cloud_provider_region,
163            tracing_handle,
164            default_configs,
165        }
166    }
167}
168
169/// Prometheus monitoring metrics.
170#[derive(Debug)]
171pub struct BalancerMetrics {
172    _uptime: ComputedGauge,
173}
174
175impl BalancerMetrics {
176    /// Returns a new [BalancerMetrics] instance connected to the registry in cfg.
177    pub fn new(cfg: &BalancerConfig) -> Self {
178        let start = Instant::now();
179        let uptime = cfg.metrics_registry.register_computed_gauge(
180            metric!(
181                name: "mz_balancer_metadata_seconds",
182                help: "server uptime, labels are build metadata",
183                const_labels: {
184                    "version" => cfg.build_version,
185                    "build_type" => if cfg!(release) { "release" } else { "debug" }
186                },
187            ),
188            move || start.elapsed().as_secs_f64(),
189        );
190        BalancerMetrics { _uptime: uptime }
191    }
192}
193
194pub struct BalancerService {
195    cfg: BalancerConfig,
196    pub pgwire: (ListenerHandle, Pin<Box<dyn ConnectionStream>>),
197    pub https: (ListenerHandle, Pin<Box<dyn ConnectionStream>>),
198    pub internal_http: (ListenerHandle, Pin<Box<dyn ConnectionStream>>),
199    _metrics: BalancerMetrics,
200    configs: ConfigSet,
201}
202
203impl BalancerService {
204    pub async fn new(cfg: BalancerConfig) -> Result<Self, anyhow::Error> {
205        let pgwire = listen(&cfg.pgwire_listen_addr)?;
206        let https = listen(&cfg.https_listen_addr)?;
207        let internal_http = listen(&cfg.internal_http_listen_addr)?;
208        let metrics = BalancerMetrics::new(&cfg);
209        let mut configs = ConfigSet::default();
210        configs = dyncfgs::all_dyncfgs(configs);
211        dyncfgs::set_defaults(&configs, cfg.default_configs.clone())?;
212        let tracing_handle = cfg.tracing_handle.clone();
213        // Configure dyncfg sync
214        match (
215            cfg.launchdarkly_sdk_key.as_deref(),
216            cfg.config_sync_file_path.as_deref(),
217        ) {
218            (Some(key), None) => {
219                let _ = mz_dyncfg_launchdarkly::sync_launchdarkly_to_configset(
220                    configs.clone(),
221                    &BUILD_INFO,
222                    |builder| {
223                        let region = cfg
224                            .cloud_provider_region
225                            .clone()
226                            .unwrap_or_else(|| String::from("unknown"));
227                        if let Some(provider) = cfg.cloud_provider.clone() {
228                            builder.add_context(
229                                ld::ContextBuilder::new(format!(
230                                    "{}/{}/{}",
231                                    provider, region, cfg.build_version
232                                ))
233                                .kind("balancer")
234                                .set_string("provider", provider)
235                                .set_string("region", region)
236                                .set_string("version", cfg.build_version.to_string())
237                                .build()
238                                .map_err(|e| anyhow::anyhow!(e))?,
239                            );
240                        } else {
241                            builder.add_context(
242                                ld::ContextBuilder::new(format!(
243                                    "{}/{}/{}",
244                                    "unknown", region, cfg.build_version
245                                ))
246                                .anonymous(true) // exclude this user from the dashboard
247                                .kind("balancer")
248                                .set_string("provider", "unknown")
249                                .set_string("region", region)
250                                .set_string("version", cfg.build_version.to_string())
251                                .build()
252                                .map_err(|e| anyhow::anyhow!(e))?,
253                            );
254                        }
255                        Ok(())
256                    },
257                    Some(key),
258                    cfg.config_sync_timeout,
259                    cfg.config_sync_loop_interval,
260                    move |updates, configs| {
261                        if has_tracing_config_update(updates) {
262                            match tracing_config(configs) {
263                                Ok(parameters) => parameters.apply(&tracing_handle),
264                                Err(err) => warn!("unable to update tracing: {err}"),
265                            }
266                        }
267                    },
268                )
269                .await
270                .inspect_err(|e| warn!("LaunchDarkly sync error: {e}"));
271            }
272            (None, Some(path)) => {
273                let _ = mz_dyncfg_file::sync_file_to_configset(
274                    configs.clone(),
275                    path,
276                    cfg.config_sync_timeout,
277                    cfg.config_sync_loop_interval,
278                    move |updates, configs| {
279                        if has_tracing_config_update(updates) {
280                            match tracing_config(configs) {
281                                Ok(parameters) => parameters.apply(&tracing_handle),
282                                Err(err) => warn!("unable to update tracing: {err}"),
283                            }
284                        }
285                    },
286                )
287                .await
288                // If there's an Error, log but continue anyway. If LD is down
289                // we have no way of fetching the previous value of the flag
290                // (unlike the adapter, but it has a durable catalog). The
291                // ConfigSet defaults have been chosen to be good enough if this
292                // is the case.
293                .inspect_err(|e| warn!("File config sync error: {e}"));
294            }
295            (Some(_), Some(_)) => panic!(
296                "must provide either config_sync_file_path or launchdarkly_sdk_key for config syncing",
297            ),
298            (None, None) => {}
299        };
300        Ok(Self {
301            cfg,
302            pgwire,
303            https,
304            internal_http,
305            _metrics: metrics,
306            configs,
307        })
308    }
309
310    pub async fn serve(self) -> Result<(), anyhow::Error> {
311        let (pgwire_tls, https_tls) = match &self.cfg.tls {
312            Some(tls) => {
313                let context = tls.reloading_context(self.cfg.reload_certs)?;
314                (
315                    Some(ReloadingTlsConfig {
316                        context: context.clone(),
317                        mode: TlsMode::Require,
318                    }),
319                    Some(context),
320                )
321            }
322            None => (None, None),
323        };
324
325        // Built once and shared by every upstream connection, since building a connector loads
326        // and parses the system CA bundle.
327        let internal_tls = self
328            .cfg
329            .internal_tls
330            .then(internal_tls_connector)
331            .transpose()
332            .context("building internal TLS connector")?;
333
334        let metrics = ServerMetricsConfig::register_into(&self.cfg.metrics_registry);
335        let limiter = ConnectionLimiter::new(&self.cfg.metrics_registry, self.configs.clone());
336
337        let mut set = JoinSet::new();
338        let mut server_handles = Vec::new();
339        let pgwire_addr = self.pgwire.0.local_addr();
340        let https_addr = self.https.0.local_addr();
341        let internal_http_addr = self.internal_http.0.local_addr();
342
343        // The HTTPS balancer always resolves through a TenantDnsResolver. In
344        // multi-tenant mode it shares the pgwire resolver so both listeners use
345        // one resolver. In static mode pgwire does not resolve through it, so
346        // HTTPS gets its own.
347        let shared_dns = match self.cfg.resolver.shared_dns() {
348            Some(dns) => dns,
349            None => Arc::new(TenantDnsResolver::new()?),
350        };
351
352        {
353            let pgwire = PgwireBalancer {
354                resolver: Arc::new(self.cfg.resolver),
355                cancellation_resolver: Arc::new(self.cfg.cancellation_resolver),
356                tls: pgwire_tls,
357                internal_tls: internal_tls.clone(),
358                metrics: ServerMetrics::new(metrics.clone(), "pgwire"),
359                limiter: Arc::clone(&limiter),
360                configs: self.configs.clone(),
361                now: SYSTEM_TIME.clone(),
362            };
363            let (handle, stream) = self.pgwire;
364            server_handles.push(handle);
365            set.spawn_named(|| "pgwire_stream", {
366                let config_set = self.configs.clone();
367                async move {
368                    mz_server_core::serve(ServeConfig {
369                        server: pgwire,
370                        conns: stream,
371                        dyncfg: Some(ServeDyncfg {
372                            config_set,
373                            sigterm_wait_config: &SIGTERM_CONNECTION_WAIT,
374                        }),
375                    })
376                    .await;
377                    warn!("pgwire server exited");
378                }
379            });
380        }
381        {
382            let Some((addr, port)) = self.cfg.https_sni_addr_template.split_once(':') else {
383                panic!("expected port in https_addr_template");
384            };
385            let port: u16 = port.parse().expect("unexpected port");
386
387            let https = HttpsBalancer {
388                resolver: shared_dns,
389                tls: https_tls,
390                resolve_template: Arc::from(addr),
391                port,
392                metrics: Arc::from(ServerMetrics::new(metrics, "https")),
393                limiter: Arc::clone(&limiter),
394                configs: self.configs.clone(),
395                internal_tls,
396            };
397            let (handle, stream) = self.https;
398            server_handles.push(handle);
399            set.spawn_named(|| "https_stream", {
400                let config_set = self.configs.clone();
401                async move {
402                    mz_server_core::serve(ServeConfig {
403                        server: https,
404                        conns: stream,
405                        dyncfg: Some(ServeDyncfg {
406                            config_set,
407                            sigterm_wait_config: &SIGTERM_CONNECTION_WAIT,
408                        }),
409                    })
410                    .await;
411                    warn!("https server exited");
412                }
413            });
414        }
415        {
416            let router = Router::new()
417                .route(
418                    "/metrics",
419                    routing::get(move |headers: axum::http::HeaderMap| async move {
420                        mz_http_util::handle_prometheus(&self.cfg.metrics_registry, headers).await
421                    }),
422                )
423                .route(
424                    "/api/livez",
425                    routing::get(mz_http_util::handle_liveness_check),
426                )
427                .route("/api/readyz", {
428                    let limiter = Arc::clone(&limiter);
429                    routing::get(move || async move {
430                        if limiter.evaluate_watermarks() {
431                            (StatusCode::OK, "ready")
432                        } else {
433                            (
434                                StatusCode::SERVICE_UNAVAILABLE,
435                                "at the connection high watermark",
436                            )
437                        }
438                    })
439                });
440            let internal_http = InternalHttpServer { router };
441            let (handle, stream) = self.internal_http;
442            server_handles.push(handle);
443            set.spawn_named(|| "internal_http_stream", async move {
444                mz_server_core::serve(ServeConfig {
445                    server: internal_http,
446                    conns: stream,
447                    // Disable graceful termination because our internal
448                    // monitoring keeps persistent HTTP connections open.
449                    dyncfg: None,
450                })
451                .await;
452                warn!("internal_http server exited");
453            });
454        }
455        // Keeps `mz_balancer_connection_accepting` and config changes current between probes.
456        // Detached so that it does not keep `serve` from returning once the listeners are gone.
457        spawn(|| "connection_watermark_tick", async move {
458            let mut tick = tokio::time::interval(Duration::from_secs(1));
459            loop {
460                tick.tick().await;
461                limiter.evaluate_watermarks();
462            }
463        });
464        #[cfg(unix)]
465        {
466            let mut sigterm =
467                tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())?;
468            set.spawn_named(|| "sigterm_handler", async move {
469                sigterm.recv().await;
470                let wait = SIGTERM_LISTEN_WAIT.get(&self.configs);
471                warn!("received signal TERM - delaying for {:?}!", wait);
472                tokio::time::sleep(wait).await;
473                warn!("sigterm delay complete, dropping server handles");
474                drop(server_handles);
475            });
476        }
477
478        println!("balancerd {} listening...", BUILD_INFO.human_version(None));
479        println!(" TLS enabled: {}", self.cfg.tls.is_some());
480        println!(" pgwire address: {}", pgwire_addr);
481        println!(" HTTPS address: {}", https_addr);
482        println!(" internal HTTP address: {}", internal_http_addr);
483
484        // Wait for all tasks to exit, which can happen on SIGTERM.
485        while let Some(res) = set.join_next().await {
486            if let Err(err) = res {
487                error!("serving task failed: {err}")
488            }
489        }
490        Ok(())
491    }
492}
493
494struct InternalHttpServer {
495    router: Router,
496}
497
498impl mz_server_core::Server for InternalHttpServer {
499    const NAME: &'static str = "internal_http";
500
501    // TODO(jkosh44) consider forwarding the connection UUID to the adapter.
502    fn handle_connection(
503        &self,
504        conn: Connection,
505        _tokio_metrics_intervals: impl Iterator<Item = TaskMetrics> + Send + 'static,
506    ) -> mz_server_core::ConnectionHandler {
507        let router = self.router.clone();
508        let service = hyper::service::service_fn(move |req| router.clone().call(req));
509        let conn = TokioIo::new(conn);
510
511        Box::pin(async {
512            let http = hyper::server::conn::http1::Builder::new();
513            http.serve_connection(conn, service).err_into().await
514        })
515    }
516}
517
518/// Builds the connector for TLS to environmentd.
519fn internal_tls_connector() -> Result<SslConnector, ErrorStack> {
520    let mut builder = SslConnector::builder(SslMethod::tls())?;
521    // environmentd doesn't yet have a cert we trust, so for now disable verification.
522    builder.set_verify(SslVerifyMode::NONE);
523    Ok(builder.build())
524}
525
526/// Wraps an IntGauge and automatically `inc`s on init and `drop`s on drop. Callers should not call
527/// `inc().`. Useful for handling multiple task exit points, for example in the case of a panic.
528struct GaugeGuard {
529    gauge: IntGauge,
530}
531
532impl From<IntGauge> for GaugeGuard {
533    fn from(gauge: IntGauge) -> Self {
534        let _self = Self { gauge };
535        _self.gauge.inc();
536        _self
537    }
538}
539
540impl Drop for GaugeGuard {
541    fn drop(&mut self) {
542        self.gauge.dec();
543    }
544}
545
546#[derive(Clone, Debug)]
547struct ServerMetricsConfig {
548    connection_status: IntCounterVec,
549    connection_setup_duration: HistogramVec,
550    upstream_connect_duration: HistogramVec,
551    connection_duration: HistogramVec,
552    active_connections: IntGaugeVec,
553    pre_resolved_connections: IntGaugeVec,
554    pre_resolved_timeouts: IntCounterVec,
555    tls_handshake_failures: IntCounterVec,
556    tenant_connections: IntGaugeVec,
557    tenant_connection_rx: IntCounterVec,
558    tenant_connection_tx: IntCounterVec,
559    tenant_pgwire_sni_count: IntCounterVec,
560}
561
562impl ServerMetricsConfig {
563    fn register_into(registry: &MetricsRegistry) -> Self {
564        let connection_status = registry.register(metric!(
565            name: "mz_balancer_connection_status",
566            help: "Count of completed network connections, by status. `error` means balancerd or \
567                its backend could not serve the connection. Connections the client ended or \
568                failed count as `success`.",
569            var_labels: ["source", "status"],
570        ));
571        let connection_setup_duration = registry.register(metric!(
572            name: "mz_balancer_connection_setup_duration_seconds",
573            help: "Time from accepting a connection until proxying it to environmentd begins, \
574                for connections that reach proxying.",
575            var_labels: ["source"],
576            buckets: histogram_seconds_buckets(0.001, 64.0),
577        ));
578        let upstream_connect_duration = registry.register(metric!(
579            name: "mz_balancer_upstream_connect_duration_seconds",
580            help: "Time to open a TCP connection to environmentd, for connections that succeed.",
581            var_labels: ["source"],
582            buckets: histogram_seconds_buckets(0.000_128, 16.0),
583        ));
584        let connection_duration = registry.register(metric!(
585            name: "mz_balancer_connection_duration_seconds",
586            help: "Time from accepting a connection until it closes, for connections that reach \
587                proxying.",
588            var_labels: ["source"],
589            buckets: histogram_seconds_buckets(1.0, 8192.0),
590        ));
591        let active_connections = registry.register(metric!(
592            name: "mz_balancer_connection_active",
593            help: "Count of currently open network connections.",
594            var_labels: ["source"],
595        ));
596        let pre_resolved_connections = registry.register(metric!(
597            name: "mz_balancer_pre_resolved_connection_active",
598            help: "Count of open network connections that have not yet resolved a backend.",
599            var_labels: ["source"],
600        ));
601        let pre_resolved_timeouts = registry.register(metric!(
602            name: "mz_balancer_pre_resolved_timeout_total",
603            help: "Count of connections closed for not resolving a backend within the pre-resolved timeout.",
604            var_labels: ["source"],
605        ));
606        let tls_handshake_failures = registry.register(metric!(
607            name: "mz_balancer_tls_handshake_failures_total",
608            help: "Count of client TLS handshakes that failed. Rises when clients reject the \
609                certificate balancerd presents, as well as with scanners and misconfigured \
610                clients, none of which count as an `error` connection status.",
611            var_labels: ["source"],
612        ));
613        let tenant_connections = registry.register(metric!(
614            name: "mz_balancer_tenant_connection_active",
615            help: "Count of opened network connections by tenant.",
616            var_labels: ["source",  "tenant"]
617        ));
618        let tenant_connection_rx = registry.register(metric!(
619            name: "mz_balancer_tenant_connection_rx",
620            help: "Number of bytes received from a client for a tenant.",
621            var_labels: ["source", "tenant"],
622        ));
623        let tenant_connection_tx = registry.register(metric!(
624            name: "mz_balancer_tenant_connection_tx",
625            help: "Number of bytes sent to a client for a tenant.",
626            var_labels: ["source", "tenant"],
627        ));
628        let tenant_pgwire_sni_count = registry.register(metric!(
629            name: "mz_balancer_tenant_pgwire_sni_count",
630            help: "Count of pgwire connections that have and do not have SNI available per tenant.",
631            var_labels: ["tenant", "has_sni"],
632        ));
633        Self {
634            connection_status,
635            connection_setup_duration,
636            upstream_connect_duration,
637            connection_duration,
638            active_connections,
639            pre_resolved_connections,
640            pre_resolved_timeouts,
641            tls_handshake_failures,
642            tenant_connections,
643            tenant_connection_rx,
644            tenant_connection_tx,
645            tenant_pgwire_sni_count,
646        }
647    }
648}
649
650#[derive(Clone, Debug)]
651struct ServerMetrics {
652    inner: ServerMetricsConfig,
653    source: &'static str,
654}
655
656impl ServerMetrics {
657    fn new(inner: ServerMetricsConfig, source: &'static str) -> Self {
658        let self_ = Self { inner, source };
659
660        // Pre-initialize labels we are planning to use to ensure they are all always emitted as
661        // time series.
662        self_.connection_status(false);
663        self_.connection_status(true);
664        self_.connection_setup_duration();
665        self_.upstream_connect_duration();
666        self_.connection_duration();
667        self_.tls_handshake_failures();
668        drop(self_.active_connections());
669
670        self_
671    }
672
673    fn connection_status(&self, is_ok: bool) -> IntCounter {
674        self.inner
675            .connection_status
676            .with_label_values(&[self.source, Self::status_label(is_ok)])
677    }
678
679    fn connection_setup_duration(&self) -> Histogram {
680        self.inner
681            .connection_setup_duration
682            .with_label_values(&[self.source])
683    }
684
685    fn upstream_connect_duration(&self) -> Histogram {
686        self.inner
687            .upstream_connect_duration
688            .with_label_values(&[self.source])
689    }
690
691    fn connection_duration(&self) -> Histogram {
692        self.inner
693            .connection_duration
694            .with_label_values(&[self.source])
695    }
696
697    fn active_connections(&self) -> GaugeGuard {
698        self.inner
699            .active_connections
700            .with_label_values(&[self.source])
701            .into()
702    }
703
704    fn pre_resolved_connections(&self) -> GaugeGuard {
705        self.inner
706            .pre_resolved_connections
707            .with_label_values(&[self.source])
708            .into()
709    }
710
711    fn pre_resolved_timeout(&self) -> IntCounter {
712        self.inner
713            .pre_resolved_timeouts
714            .with_label_values(&[self.source])
715    }
716
717    fn tls_handshake_failures(&self) -> IntCounter {
718        self.inner
719            .tls_handshake_failures
720            .with_label_values(&[self.source])
721    }
722
723    fn tenant_connections(&self, tenant: &str) -> GaugeGuard {
724        self.inner
725            .tenant_connections
726            .with_label_values(&[self.source, tenant])
727            .into()
728    }
729
730    fn tenant_connections_rx(&self, tenant: &str) -> IntCounter {
731        self.inner
732            .tenant_connection_rx
733            .with_label_values(&[self.source, tenant])
734    }
735
736    fn tenant_connections_tx(&self, tenant: &str) -> IntCounter {
737        self.inner
738            .tenant_connection_tx
739            .with_label_values(&[self.source, tenant])
740    }
741
742    fn tenant_pgwire_sni_count(&self, tenant: &str, has_sni: bool) -> IntCounter {
743        self.inner
744            .tenant_pgwire_sni_count
745            .with_label_values(&[tenant, &has_sni.to_string()])
746    }
747
748    fn status_label(is_ok: bool) -> &'static str {
749        if is_ok { "success" } else { "error" }
750    }
751}
752
753/// Ceiling on the number of client connections proxied at once, shared by the pgwire and HTTPS
754/// listeners.
755///
756/// balancerd's memory use scales with the number of connections it proxies. Without a ceiling it
757/// keeps accepting until the container is OOM killed, which drops every established connection.
758/// Refusing new connections instead sheds only the load we cannot serve.
759#[derive(Debug)]
760struct ConnectionLimiter {
761    configs: ConfigSet,
762    active: AtomicU32,
763    /// Whether connections are currently being refused, so that reaching and clearing the limit
764    /// are logged once each rather than once per connection.
765    limited: AtomicBool,
766    /// Whether `/api/readyz` last reported ready. Written only by `evaluate_watermarks`.
767    accepting: Arc<AtomicBool>,
768    /// Taken only by the watermark tick and `/api/readyz`, never on connection acquire or
769    /// release.
770    watermark_state: Mutex<WatermarkState>,
771    rejected: IntCounter,
772    watermark_transitions: IntCounter,
773    _limit: ComputedUIntGauge,
774    _accepting: ComputedIntGauge,
775    _high_watermark: ComputedUIntGauge,
776    _low_watermark: ComputedUIntGauge,
777}
778
779#[derive(Debug, Default)]
780struct WatermarkState {
781    out_of_rotation: bool,
782    /// The last watermark misconfiguration warned about, so it is logged once per change.
783    last_warning: Option<String>,
784}
785
786/// Validated connection watermarks, see [`configured_watermarks`].
787#[derive(Debug, Clone, Copy)]
788struct Watermarks {
789    low: usize,
790    high: usize,
791}
792
793/// The effective watermarks, `Ok(None)` when both are zero, or `Err` describing why the
794/// configured values are ignored.
795///
796/// The high watermark must be strictly below the hard limit. At the limit the pod would leave
797/// rotation exactly when it is full, which is when a load balancer that closes connections to
798/// unhealthy targets does the most damage.
799fn configured_watermarks(configs: &ConfigSet) -> Result<Option<Watermarks>, String> {
800    let low = CONNECTION_LOW_WATERMARK.get(configs);
801    let high = CONNECTION_HIGH_WATERMARK.get(configs);
802    let max = MAX_CONNECTIONS.get(configs);
803    let reason = match (low, high) {
804        (0, 0) => return Ok(None),
805        (0, _) | (_, 0) => "both watermarks must be set",
806        (low, high) if low >= high => "the low watermark must be below the high watermark",
807        _ if max == 0 => "balancerd_max_connections must be enabled",
808        (_, high) if high >= usize::cast_from(max) => {
809            "the high watermark must be below balancerd_max_connections"
810        }
811        (low, high) => return Ok(Some(Watermarks { low, high })),
812    };
813    Err(format!(
814        "{reason} (low={low}, high={high}, max_connections={max})"
815    ))
816}
817
818/// One effective watermark as a gauge value, 0 when the watermarks are off.
819fn watermark_metric(configs: &ConfigSet, pick: fn(Watermarks) -> usize) -> u64 {
820    configured_watermarks(configs)
821        .ok()
822        .flatten()
823        .map_or(0, |w| u64::cast_from(pick(w)))
824}
825
826impl ConnectionLimiter {
827    fn new(registry: &MetricsRegistry, configs: ConfigSet) -> Arc<Self> {
828        let rejected = registry.register(metric!(
829            name: "mz_balancer_connection_rejected_total",
830            help: "Count of connections refused because the connection limit was reached.",
831        ));
832        let limit = registry.register_computed_gauge(
833            metric!(
834                name: "mz_balancer_connection_limit",
835                help: "Maximum number of connections proxied at once, 0 if unlimited.",
836            ),
837            {
838                let configs = configs.clone();
839                move || u64::from(MAX_CONNECTIONS.get(&configs))
840            },
841        );
842        let watermark_transitions = registry.register(metric!(
843            name: "mz_balancer_connection_watermark_transitions_total",
844            help: "Count of times balancerd left or returned to load balancer rotation because \
845            of the connection watermarks.",
846        ));
847        let accepting = Arc::new(AtomicBool::new(true));
848        let accepting_gauge: ComputedIntGauge = registry.register_computed_gauge(
849            metric!(
850                name: "mz_balancer_connection_accepting",
851                help: "Whether /api/readyz reports ready: 1 in load balancer rotation, 0 out.",
852            ),
853            {
854                let accepting = Arc::clone(&accepting);
855                move || i64::from(accepting.load(Ordering::Relaxed))
856            },
857        );
858        // The metrics catalog is generated from `metric!` literals, so each gauge is spelled out.
859        let high_watermark = registry.register_computed_gauge(
860            metric!(
861                name: "mz_balancer_connection_high_watermark",
862                help: "Connections at which balancerd leaves load balancer rotation, 0 if disabled.",
863            ),
864            {
865                let configs = configs.clone();
866                move || watermark_metric(&configs, |w| w.high)
867            },
868        );
869        let low_watermark = registry.register_computed_gauge(
870            metric!(
871                name: "mz_balancer_connection_low_watermark",
872                help: "Connections below which balancerd returns to load balancer rotation, 0 if \
873                disabled.",
874            ),
875            {
876                let configs = configs.clone();
877                move || watermark_metric(&configs, |w| w.low)
878            },
879        );
880        Arc::new(ConnectionLimiter {
881            configs,
882            active: AtomicU32::new(0),
883            limited: AtomicBool::new(false),
884            accepting,
885            watermark_state: Mutex::new(WatermarkState::default()),
886            rejected,
887            watermark_transitions,
888            _limit: limit,
889            _accepting: accepting_gauge,
890            _high_watermark: high_watermark,
891            _low_watermark: low_watermark,
892        })
893    }
894
895    #[cfg(test)]
896    fn accepting(&self) -> bool {
897        self.accepting.load(Ordering::Relaxed)
898    }
899
900    /// Applies the watermark transitions for the current connection count and returns whether
901    /// balancerd is in load balancer rotation.
902    ///
903    /// Runs on every `/api/readyz` request and on a 1s tick, not on connection acquire or release,
904    /// so the connection path stays free of locks and dyncfg reads. Each call depends only on the
905    /// current count, configuration and state, so how often probes poll does not change the
906    /// outcome, only how soon a transition is seen.
907    ///
908    /// There is no minimum time out of rotation. How long `/api/readyz` must fail before a pod
909    /// actually leaves, and how long it must pass before it returns, is up to the health check
910    /// thresholds of whatever reads it.
911    fn evaluate_watermarks(&self) -> bool {
912        let mut state = self.watermark_state.lock().expect("lock poisoned");
913        let watermarks = self.watermarks(&mut state.last_warning);
914        let active = usize::cast_from(self.active.load(Ordering::SeqCst));
915        let accepting = match (watermarks, state.out_of_rotation) {
916            (Some(w), false) if active >= w.high => {
917                warn!(
918                    "leaving load balancer rotation: {active} active connections reached the \
919                    high watermark of {}",
920                    w.high
921                );
922                false
923            }
924            (Some(w), true) if active < w.low => {
925                info!(
926                    "returning to load balancer rotation: {active} active connections, below \
927                    the low watermark of {}",
928                    w.low
929                );
930                true
931            }
932            // Watermarks disabled or invalidated while out of rotation restore readiness at once,
933            // since off must behave as if they had never been set.
934            (None, true) => {
935                info!(
936                    "returning to load balancer rotation: connection watermarks no longer in effect"
937                );
938                true
939            }
940            _ => return !state.out_of_rotation,
941        };
942        state.out_of_rotation = !accepting;
943        self.accepting.store(accepting, Ordering::Relaxed);
944        self.watermark_transitions.inc();
945        accepting
946    }
947
948    fn watermarks(&self, last: &mut Option<String>) -> Option<Watermarks> {
949        match configured_watermarks(&self.configs) {
950            Ok(watermarks) => {
951                *last = None;
952                watermarks
953            }
954            Err(reason) => {
955                if last.as_deref() != Some(&reason) {
956                    warn!("ignoring connection watermarks: {reason}");
957                    *last = Some(reason);
958                }
959                None
960            }
961        }
962    }
963
964    /// Reserves capacity for one connection, or returns `None` if the limit has been reached, in
965    /// which case the caller must refuse the connection.
966    fn acquire(self: &Arc<Self>) -> Option<ConnectionGuard> {
967        let limit = MAX_CONNECTIONS.get(&self.configs);
968        let unlimited = limit == 0;
969        match self
970            .active
971            .try_update(Ordering::SeqCst, Ordering::SeqCst, |active| {
972                (unlimited || active < limit).then(|| active + 1)
973            }) {
974            Ok(prev) => {
975                // Only clear the limited state once comfortably below the limit, so that
976                // connections churning at the ceiling do not flap the log lines.
977                if unlimited || prev + 1 <= limit - limit / 10 {
978                    if self.limited.swap(false, Ordering::Relaxed) {
979                        info!("accepting new connections again, limit is {limit}");
980                    }
981                }
982                Some(ConnectionGuard(Arc::clone(self)))
983            }
984            Err(active) => {
985                self.rejected.inc();
986                if !self.limited.swap(true, Ordering::Relaxed) {
987                    warn!(
988                        "refusing new connections: at the limit of {limit} connections \
989                        ({active} active)"
990                    );
991                }
992                None
993            }
994        }
995    }
996}
997
998/// Releases the connection reserved by [`ConnectionLimiter::acquire`] when dropped.
999struct ConnectionGuard(Arc<ConnectionLimiter>);
1000
1001impl Drop for ConnectionGuard {
1002    fn drop(&mut self) {
1003        self.0.active.fetch_sub(1, Ordering::SeqCst);
1004    }
1005}
1006
1007/// Answers the client with a final message and flushes it.
1008///
1009/// [`FramedConn::send`] only enqueues into a buffer, so a rejection that is not flushed reaches
1010/// the client as a bare socket close rather than the error it was given. Every path that answers
1011/// the client and then stops has to flush, because the connection is dropped as soon as it
1012/// returns.
1013async fn reject<A>(conn: &mut FramedConn<A>, err: ErrorResponse) -> Result<(), io::Error>
1014where
1015    A: AsyncRead + AsyncWrite + Unpin,
1016{
1017    conn.send(err).await?;
1018    conn.flush().await
1019}
1020
1021/// Decides the `status` a connection reports to `mz_balancer_connection_status`.
1022///
1023/// A connection is an `error` when balancerd or its backend could not serve it: the connection
1024/// limit refused it, a server-side step failed (resolving the backend, authenticating against
1025/// Frontegg, connecting upstream), or the pre-resolved deadline expired during balancerd's own
1026/// work. Everything else is a `success`, including connections that reached proxying and
1027/// connections the client ended or failed: hangups, protocol violations, rejected credentials, an
1028/// SNI that is missing or names no tenant ([`UnknownHost`]), or stalling until the deadline.
1029///
1030/// A failed TLS handshake is a `success` too. balancerd cannot tell a client that rejects its
1031/// certificate from one that cannot speak TLS, so those are counted on their own, in
1032/// `mz_balancer_tls_handshake_failures_total`.
1033///
1034/// Every server-side failure has to be reported through [`ConnectionFault::server`]: a connection
1035/// that is never marked is a `success`, whatever its handler returned.
1036#[derive(Debug, Default)]
1037struct ConnectionFault {
1038    server: AtomicBool,
1039    in_server_work: AtomicBool,
1040}
1041
1042impl ConnectionFault {
1043    /// Records that balancerd or its backend failed the connection.
1044    fn server(&self) {
1045        self.server.store(true, Ordering::Relaxed);
1046    }
1047
1048    /// Marks `fut` as balancerd's own work, so a pre-resolved deadline that fires while it is
1049    /// pending counts as a server fault.
1050    ///
1051    /// NOTE: The flag is cleared only when `fut` completes. A deadline that drops this future
1052    /// leaves it set, which is what [`ConnectionFault::timed_out`] relies on.
1053    async fn server_work<F: Future>(&self, fut: F) -> F::Output {
1054        self.in_server_work.store(true, Ordering::Relaxed);
1055        let output = fut.await;
1056        self.in_server_work.store(false, Ordering::Relaxed);
1057        output
1058    }
1059
1060    /// Records that the pre-resolved deadline expired. Only a deadline that interrupted
1061    /// [`ConnectionFault::server_work`] is a server fault.
1062    fn timed_out(&self) {
1063        if self.in_server_work.load(Ordering::Relaxed) {
1064            self.server();
1065        }
1066    }
1067
1068    fn is_ok(&self) -> bool {
1069        !self.server.load(Ordering::Relaxed)
1070    }
1071}
1072
1073/// Runs the pre-resolved phase under [`PRE_RESOLVED_TIMEOUT`], counting an overrun.
1074///
1075/// `fut` must cover TLS negotiation, startup, authentication, and backend resolution.
1076/// Proxying must run after it completes, outside the deadline. A zero timeout leaves
1077/// `fut` unbounded.
1078async fn under_pre_resolved_timeout<F, T, E>(
1079    timeout: Duration,
1080    metrics: &ServerMetrics,
1081    fault: &ConnectionFault,
1082    fut: F,
1083) -> Result<T, E>
1084where
1085    F: Future<Output = Result<T, E>>,
1086    E: From<io::Error>,
1087{
1088    if timeout.is_zero() {
1089        return fut.await;
1090    }
1091    match tokio::time::timeout(timeout, fut).await {
1092        Ok(result) => result,
1093        Err(_) => {
1094            metrics.pre_resolved_timeout().inc();
1095            fault.timed_out();
1096            Err(io::Error::new(
1097                io::ErrorKind::TimedOut,
1098                "timed out before resolving a backend",
1099            )
1100            .into())
1101        }
1102    }
1103}
1104
1105pub enum CancellationResolver {
1106    Directory(PathBuf),
1107    Static(String),
1108}
1109
1110struct PgwireBalancer {
1111    /// Read for [`PRE_RESOLVED_TIMEOUT`] at connection time.
1112    configs: ConfigSet,
1113    tls: Option<ReloadingTlsConfig>,
1114    internal_tls: Option<SslConnector>,
1115    cancellation_resolver: Arc<CancellationResolver>,
1116    resolver: Arc<BalancerResolver>,
1117    metrics: ServerMetrics,
1118    limiter: Arc<ConnectionLimiter>,
1119    now: NowFn,
1120}
1121
1122impl PgwireBalancer {
1123    /// Negotiates pgwire startup and resolves a backend.
1124    ///
1125    /// Returns `None` after a client disconnect, a flushed rejection, or a dispatched
1126    /// cancellation request.
1127    async fn pre_resolve(
1128        conn: Connection,
1129        tls: Option<ReloadingTlsConfig>,
1130        resolver: &BalancerResolver,
1131        cancellation_resolver: Arc<CancellationResolver>,
1132        conn_uuid: Uuid,
1133        metrics: &ServerMetrics,
1134        fault: &ConnectionFault,
1135    ) -> Result<
1136        Option<(
1137            FramedConn<Connection>,
1138            ResolvedAddr,
1139            BTreeMap<String, String>,
1140        )>,
1141        anyhow::Error,
1142    > {
1143        let peer_addr = conn.peer_addr();
1144        let mut conn = Conn::Unencrypted(conn);
1145        loop {
1146            let message = decode_startup(&mut conn, MAX_STARTUP_FRAME_SIZE).await?;
1147            conn = match message {
1148                // Clients sometimes hang up during the startup sequence, e.g.
1149                // because they receive an unacceptable response to an
1150                // `SslRequest`. This is considered a graceful termination.
1151                None => return Ok(None),
1152
1153                Some(FrontendStartupMessage::Startup {
1154                    version,
1155                    mut params,
1156                }) => {
1157                    let mut conn = FramedConn::new(conn);
1158                    let rejected = SqlState::SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION;
1159                    let peer_addr = match peer_addr {
1160                        Ok(addr) => addr.ip(),
1161                        Err(e) => {
1162                            error!("Invalid peer_addr {:?}", e);
1163                            fault.server();
1164                            reject(
1165                                &mut conn,
1166                                ErrorResponse::fatal(rejected, "invalid peer address"),
1167                            )
1168                            .await?;
1169                            return Ok(None);
1170                        }
1171                    };
1172                    debug!(
1173                        %conn_uuid, %peer_addr,
1174                        "starting new pgwire connection in balancer",
1175                    );
1176                    let prev = params.insert(CONN_UUID_KEY.to_string(), conn_uuid.to_string());
1177                    if prev.is_some() {
1178                        reject(
1179                            &mut conn,
1180                            ErrorResponse::fatal(
1181                                rejected,
1182                                format!("invalid parameter '{CONN_UUID_KEY}'"),
1183                            ),
1184                        )
1185                        .await?;
1186                        return Ok(None);
1187                    }
1188
1189                    let forwarded_for = params.insert(
1190                        MZ_FORWARDED_FOR_KEY.to_string(),
1191                        peer_addr.to_string().clone(),
1192                    );
1193                    if let Some(_) = forwarded_for {
1194                        reject(
1195                            &mut conn,
1196                            ErrorResponse::fatal(
1197                                rejected,
1198                                format!("invalid parameter '{MZ_FORWARDED_FOR_KEY}'"),
1199                            ),
1200                        )
1201                        .await?;
1202                        return Ok(None);
1203                    };
1204
1205                    let Some(resolved) = Self::resolve_destination(
1206                        &mut conn,
1207                        version,
1208                        &params,
1209                        resolver,
1210                        tls.map(|tls| tls.mode),
1211                        metrics,
1212                        fault,
1213                    )
1214                    .await?
1215                    else {
1216                        return Ok(None);
1217                    };
1218                    return Ok(Some((conn, resolved, params)));
1219                }
1220
1221                Some(FrontendStartupMessage::CancelRequest {
1222                    conn_id,
1223                    secret_key,
1224                }) => {
1225                    spawn(|| "cancel request", async move {
1226                        cancel_request(conn_id, secret_key, &cancellation_resolver).await;
1227                    });
1228                    // Do not wait on cancel requests to return because cancellation is best
1229                    // effort.
1230                    return Ok(None);
1231                }
1232
1233                Some(FrontendStartupMessage::SslRequest) => match (conn, &tls) {
1234                    (Conn::Unencrypted(mut conn), Some(tls)) => {
1235                        conn.write_all(&[ACCEPT_SSL_ENCRYPTION]).await?;
1236                        let mut ssl_stream = Ssl::new(&tls.context.get())
1237                            .and_then(|ssl| SslStream::new(ssl, conn))
1238                            .inspect_err(|_| fault.server())?;
1239                        if let Err(e) = Pin::new(&mut ssl_stream).accept().await {
1240                            metrics.tls_handshake_failures().inc();
1241                            let _ = ssl_stream.get_mut().shutdown().await;
1242                            return Err(e.into());
1243                        }
1244                        Conn::Ssl(ssl_stream)
1245                    }
1246                    (mut conn, _) => {
1247                        conn.write_all(&[REJECT_ENCRYPTION]).await?;
1248                        conn
1249                    }
1250                },
1251
1252                Some(FrontendStartupMessage::GssEncRequest) => {
1253                    conn.write_all(&[REJECT_ENCRYPTION]).await?;
1254                    conn
1255                }
1256            }
1257        }
1258    }
1259
1260    #[mz_ore::instrument(level = "debug")]
1261    /// Resolves a destination for the connection, or answers the client saying why it cannot.
1262    ///
1263    /// `Ok(None)` means the rejection has been flushed and the connection is finished.
1264    async fn resolve_destination<'a, A>(
1265        conn: &'a mut FramedConn<A>,
1266        version: i32,
1267        params: &BTreeMap<String, String>,
1268        resolver: &BalancerResolver,
1269        tls_mode: Option<TlsMode>,
1270        metrics: &ServerMetrics,
1271        fault: &ConnectionFault,
1272    ) -> Result<Option<ResolvedAddr>, io::Error>
1273    where
1274        A: AsyncRead + AsyncWrite + AsyncReady + Send + Sync + Unpin,
1275    {
1276        if version != VERSION_3 {
1277            reject(
1278                conn,
1279                ErrorResponse::fatal(
1280                    SqlState::SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION,
1281                    "server does not support the client's requested protocol version",
1282                ),
1283            )
1284            .await?;
1285            return Ok(None);
1286        }
1287
1288        let Some(user) = params.get("user") else {
1289            reject(
1290                conn,
1291                ErrorResponse::fatal(
1292                    SqlState::SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION,
1293                    "user parameter required",
1294                ),
1295            )
1296            .await?;
1297            return Ok(None);
1298        };
1299
1300        if let Err(err) = conn.inner().ensure_tls_compatibility(&tls_mode) {
1301            reject(conn, err).await?;
1302            return Ok(None);
1303        }
1304
1305        let resolved = match resolver.resolve(conn, user, metrics, fault).await {
1306            Ok(v) => v,
1307            Err(err) => {
1308                let sql_state = match &err {
1309                    ResolveError::InvalidPassword => SqlState::INVALID_PASSWORD,
1310                    ResolveError::Client(details) => {
1311                        warn!("client-caused connection failure: {details:#}");
1312                        SqlState::SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION
1313                    }
1314                    ResolveError::Upstream(details) => {
1315                        warn!("upstream not available: {details:#}");
1316                        if !names_unknown_host(details) {
1317                            fault.server();
1318                        }
1319                        SqlState::SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION
1320                    }
1321                    ResolveError::Internal(details) => {
1322                        error!("resolving connection destination: {details:#}");
1323                        fault.server();
1324                        SqlState::SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION
1325                    }
1326                };
1327                reject(conn, ErrorResponse::fatal(sql_state, err.to_string())).await?;
1328                return Ok(None);
1329            }
1330        };
1331
1332        Ok(Some(resolved))
1333    }
1334
1335    /// Proxies a resolved connection until either side closes.
1336    ///
1337    /// `accepted` is when the connection was accepted, which the setup and lifetime durations
1338    /// are measured from.
1339    async fn proxy<A>(
1340        mut conn: FramedConn<A>,
1341        resolved: ResolvedAddr,
1342        params: BTreeMap<String, String>,
1343        internal_tls: Option<&SslConnector>,
1344        metrics: &ServerMetrics,
1345        fault: &ConnectionFault,
1346        accepted: Instant,
1347    ) -> Result<(), io::Error>
1348    where
1349        A: AsyncRead + AsyncWrite + AsyncReady + Send + Sync + Unpin,
1350    {
1351        let _active_guard = resolved
1352            .tenant
1353            .as_ref()
1354            .map(|tenant| metrics.tenant_connections(tenant));
1355        let mut mz_stream = match Self::init_stream(
1356            &mut conn,
1357            resolved.addr,
1358            resolved.password,
1359            params,
1360            internal_tls,
1361            metrics,
1362        )
1363        .await
1364        {
1365            Ok(stream) => stream,
1366            Err(e) => {
1367                error!("failed to connect to upstream server: {e}");
1368                fault.server();
1369                return reject(
1370                    &mut conn,
1371                    ErrorResponse::fatal(
1372                        SqlState::SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION,
1373                        "upstream server not available",
1374                    ),
1375                )
1376                .await;
1377            }
1378        };
1379
1380        // Proxying does not frame, so release the framing buffers rather than hold them for the
1381        // life of the connection. Bytes the client sent after its last decoded message, e.g. a
1382        // query pipelined behind its password, are already buffered and must go upstream first.
1383        let (mut client, unread) = conn.into_parts();
1384        if !unread.is_empty() {
1385            mz_stream
1386                .write_all(&unread)
1387                .await
1388                .inspect_err(|_| fault.server())?;
1389        }
1390        drop(unread);
1391        metrics
1392            .connection_setup_duration()
1393            .observe(accepted.elapsed().as_secs_f64());
1394
1395        let mut client_counter = CountingConn::new(&mut client);
1396
1397        // Now blindly shuffle bytes back and forth until closed.
1398        // TODO: Limit total memory use.
1399        let res = tokio::io::copy_bidirectional(&mut client_counter, &mut mz_stream).await;
1400        metrics
1401            .connection_duration()
1402            .observe(accepted.elapsed().as_secs_f64());
1403        if let Some(tenant) = &resolved.tenant {
1404            metrics
1405                .tenant_connections_tx(tenant)
1406                .inc_by(u64::cast_from(client_counter.written));
1407            metrics
1408                .tenant_connections_rx(tenant)
1409                .inc_by(u64::cast_from(client_counter.read));
1410        }
1411        res?;
1412
1413        Ok(())
1414    }
1415
1416    #[mz_ore::instrument(level = "debug")]
1417    async fn init_stream<'a, A>(
1418        conn: &'a mut FramedConn<A>,
1419        envd_addr: SocketAddr,
1420        password: Option<String>,
1421        params: BTreeMap<String, String>,
1422        internal_tls: Option<&SslConnector>,
1423        metrics: &ServerMetrics,
1424    ) -> Result<Conn<netio::Stream>, anyhow::Error>
1425    where
1426        A: AsyncRead + AsyncWrite + AsyncReady + Send + Sync + Unpin,
1427    {
1428        // NOTE: `netio::Stream::connect` sets `TCP_NODELAY`. Without it, Nagle's algorithm holds
1429        // the tail of an extended-protocol batch that arrives in several reads until
1430        // environmentd's delayed ACK (at least 40 ms on Linux), because environmentd responds
1431        // only at `Sync`.
1432        let mut mz_stream = connect_upstream(envd_addr, metrics).await?;
1433        let mut buf = BytesMut::new();
1434
1435        let mut mz_stream = if let Some(internal_tls) = internal_tls {
1436            FrontendStartupMessage::SslRequest.encode(&mut buf)?;
1437            mz_stream.write_all(&buf).await?;
1438            buf.clear();
1439            let mut maybe_ssl_request_response = [0u8; 1];
1440            let nread =
1441                netio::read_exact_or_eof(&mut mz_stream, &mut maybe_ssl_request_response).await?;
1442            if nread == 1 && maybe_ssl_request_response == [ACCEPT_SSL_ENCRYPTION] {
1443                // do a TLS handshake
1444                let mut ssl = internal_tls.configure()?.into_ssl(&envd_addr.to_string())?;
1445                ssl.set_connect_state();
1446                Conn::Ssl(SslStream::new(ssl, mz_stream)?)
1447            } else {
1448                Conn::Unencrypted(mz_stream)
1449            }
1450        } else {
1451            Conn::Unencrypted(mz_stream)
1452        };
1453
1454        // Send initial startup and password messages.
1455        let startup = FrontendStartupMessage::Startup {
1456            version: VERSION_3,
1457            params,
1458        };
1459        startup.encode(&mut buf)?;
1460        mz_stream.write_all(&buf).await?;
1461        let client_stream = conn.inner_mut();
1462
1463        // This early return is important in self managed with SASL mode.
1464        // The below code specifically looks for cleartext password requests, but in SASL mode
1465        // the server will send a different message type (SASLInitialResponse) that we should
1466        // not try to interpret or respond to.
1467        // "Why not? That code looks like it should fall back fine?" You may ask.
1468        // The below block unconditionally reads 9 bytes from the server. If we don't have
1469        // a password or the message isn't a cleartext password request, we forward those 9 bytes
1470        // to the client. Then we return the stream to the caller, who will continue shuffling bytes.
1471        // The problem is that with TLS enabled between balancerd <-> client, flushing the first 9 bytes
1472        // before copying bidirectionally will have the side effect of splitting the auth handshake into
1473        // two SSL records. Pgbouncer misbehaves in this scenario, and fails the connection.
1474        // PGbouncer shouldn't do this! It's a common footgun of protocols over TLS.
1475        // So common in fact that PGbouncer already hit and fixed this issue on the bouncer <-> client side:
1476        // once before: https://github.com/pgbouncer/pgbouncer/pull/1058.
1477        // We will work to upstream a fix, but in the meantime, this early return avoids the issue entirely.
1478        if password.is_none() {
1479            return Ok(mz_stream);
1480        }
1481
1482        // Read a single backend message, which may be a password request. Send ours if so.
1483        // Otherwise start shuffling bytes. message type (len 1, 'R') + message len (len 4, 8_i32) +
1484        // auth type (len 4, 3_i32).
1485        let mut maybe_auth_frame = [0; 1 + 4 + 4];
1486        let nread = netio::read_exact_or_eof(&mut mz_stream, &mut maybe_auth_frame).await?;
1487        // 'R' for auth message, 0008 for message length, 0003 for password cleartext variant.
1488        // See: https://www.postgresql.org/docs/current/protocol-message-formats.html#PROTOCOL-MESSAGE-FORMATS-AUTHENTICATIONCLEARTEXTPASSWORD
1489        const AUTH_PASSWORD_CLEARTEXT: [u8; 9] = [b'R', 0, 0, 0, 8, 0, 0, 0, 3];
1490        if nread == AUTH_PASSWORD_CLEARTEXT.len()
1491            && maybe_auth_frame == AUTH_PASSWORD_CLEARTEXT
1492            && password.is_some()
1493        {
1494            // If we got exactly a cleartext password request and have one, send it.
1495            let Some(password) = password else {
1496                unreachable!("verified some above");
1497            };
1498            let password = FrontendMessage::Password { password };
1499            buf.clear();
1500            password.encode(&mut buf)?;
1501            mz_stream.write_all(&buf).await?;
1502            mz_stream.flush().await?;
1503        } else {
1504            // Otherwise pass on the bytes we just got. This *might* even be a password request, but
1505            // we don't have a password. In which case it can be forwarded up to the client.
1506            client_stream.write_all(&maybe_auth_frame[0..nread]).await?;
1507        }
1508
1509        Ok(mz_stream)
1510    }
1511}
1512
1513impl mz_server_core::Server for PgwireBalancer {
1514    const NAME: &'static str = "pgwire_balancer";
1515
1516    fn handle_connection(
1517        &self,
1518        conn: Connection,
1519        _tokio_metrics_intervals: impl Iterator<Item = TaskMetrics> + Send + 'static,
1520    ) -> mz_server_core::ConnectionHandler {
1521        let tls = self.tls.clone();
1522        let internal_tls = self.internal_tls.clone();
1523        let resolver = Arc::clone(&self.resolver);
1524        let inner_metrics = self.metrics.clone();
1525        let outer_metrics = self.metrics.clone();
1526        let limiter = Arc::clone(&self.limiter);
1527        let pre_resolved_timeout = PRE_RESOLVED_TIMEOUT.get(&self.configs);
1528        let cancellation_resolver = Arc::clone(&self.cancellation_resolver);
1529        let conn_uuid = epoch_to_uuid_v7(&(self.now)());
1530        conn.uuid_handle().set(conn_uuid);
1531        Box::pin(async move {
1532            // TODO: Try to merge this with pgwire/server.rs to avoid the duplication. May not be
1533            // worth it.
1534            let accepted = Instant::now();
1535            let fault = ConnectionFault::default();
1536            let fault_ref = &fault;
1537            let active_guard = outer_metrics.active_connections();
1538            let _: Result<(), anyhow::Error> = async move {
1539                let Some(_conn_guard) = limiter.acquire() else {
1540                    fault_ref.server();
1541                    return Ok(());
1542                };
1543                let pre_resolved = inner_metrics.pre_resolved_connections();
1544
1545                // Boxed so that startup's state, much larger than the proxying state, is freed once
1546                // startup completes. Held inline, it would size the connection's task, which lives
1547                // as long as the connection.
1548                let destination = under_pre_resolved_timeout(
1549                    pre_resolved_timeout,
1550                    &inner_metrics,
1551                    fault_ref,
1552                    Box::pin(Self::pre_resolve(
1553                        conn,
1554                        tls,
1555                        &resolver,
1556                        cancellation_resolver,
1557                        conn_uuid,
1558                        &inner_metrics,
1559                        fault_ref,
1560                    )),
1561                )
1562                .await?;
1563
1564                let Some((conn, resolved, params)) = destination else {
1565                    return Ok(());
1566                };
1567                drop(pre_resolved);
1568                // One line per connection ties a client IP to a tenant; the metrics carry the
1569                // tenant but not the IP, and flow logs see the IP but not the tenant. Skipped
1570                // without a tenant (self-managed). `authenticated` is false when the tenant came
1571                // from the client's SNI, which environmentd authenticates only after this point.
1572                if let Some(tenant) = &resolved.tenant {
1573                    info!(
1574                        target: NO_SENTRY_TARGET,
1575                        %conn_uuid,
1576                        client_addr = params.get(MZ_FORWARDED_FOR_KEY).map_or("", String::as_str),
1577                        %tenant,
1578                        authenticated = resolved.password.is_some(),
1579                        "pgwire connection resolved",
1580                    );
1581                }
1582
1583                PgwireBalancer::proxy(
1584                    conn,
1585                    resolved,
1586                    params,
1587                    internal_tls.as_ref(),
1588                    &inner_metrics,
1589                    fault_ref,
1590                    accepted,
1591                )
1592                .await?;
1593                Ok(())
1594            }
1595            .await;
1596            drop(active_guard);
1597            outer_metrics.connection_status(fault.is_ok()).inc();
1598            Ok(())
1599        })
1600    }
1601}
1602
1603// A struct that counts bytes exchanged.
1604struct CountingConn<C> {
1605    inner: C,
1606    read: usize,
1607    written: usize,
1608}
1609
1610impl<C> CountingConn<C> {
1611    fn new(inner: C) -> Self {
1612        CountingConn {
1613            inner,
1614            read: 0,
1615            written: 0,
1616        }
1617    }
1618}
1619
1620impl<C> AsyncRead for CountingConn<C>
1621where
1622    C: AsyncRead + Unpin,
1623{
1624    fn poll_read(
1625        self: Pin<&mut Self>,
1626        cx: &mut std::task::Context<'_>,
1627        buf: &mut io::ReadBuf<'_>,
1628    ) -> std::task::Poll<std::io::Result<()>> {
1629        let counter = self.get_mut();
1630        let pin = Pin::new(&mut counter.inner);
1631        let bytes = buf.filled().len();
1632        let poll = pin.poll_read(cx, buf);
1633        let bytes = buf.filled().len() - bytes;
1634        if let std::task::Poll::Ready(Ok(())) = poll {
1635            counter.read += bytes
1636        }
1637        poll
1638    }
1639}
1640
1641impl<C> AsyncWrite for CountingConn<C>
1642where
1643    C: AsyncWrite + Unpin,
1644{
1645    fn poll_write(
1646        self: Pin<&mut Self>,
1647        cx: &mut std::task::Context<'_>,
1648        buf: &[u8],
1649    ) -> std::task::Poll<Result<usize, std::io::Error>> {
1650        let counter = self.get_mut();
1651        let pin = Pin::new(&mut counter.inner);
1652        let poll = pin.poll_write(cx, buf);
1653        if let std::task::Poll::Ready(Ok(bytes)) = poll {
1654            counter.written += bytes
1655        }
1656        poll
1657    }
1658
1659    fn poll_flush(
1660        self: Pin<&mut Self>,
1661        cx: &mut std::task::Context<'_>,
1662    ) -> std::task::Poll<Result<(), std::io::Error>> {
1663        let counter = self.get_mut();
1664        let pin = Pin::new(&mut counter.inner);
1665        pin.poll_flush(cx)
1666    }
1667
1668    fn poll_shutdown(
1669        self: Pin<&mut Self>,
1670        cx: &mut std::task::Context<'_>,
1671    ) -> std::task::Poll<Result<(), std::io::Error>> {
1672        let counter = self.get_mut();
1673        let pin = Pin::new(&mut counter.inner);
1674        pin.poll_shutdown(cx)
1675    }
1676}
1677
1678/// Broadcasts cancellation to all matching environmentds. `conn_id`'s bits [31..20] are the lower
1679/// 12 bits of a UUID for an environmentd/organization. Using that and the template in
1680/// `cancellation_resolver` we generate a hostname. That hostname resolves to all IPs of envds that
1681/// match the UUID (cloud k8s infrastructure maintains that mapping). This function creates a new
1682/// task for each envd and relays the cancellation message to it, broadcasting it to any envd that
1683/// might match the connection.
1684///
1685/// This function returns after it has spawned the tasks, and does not wait for them to complete.
1686/// This is acceptable because cancellation in the Postgres protocol is best effort and has no
1687/// guarantees.
1688///
1689/// The safety of broadcasting this is due to the various randomness in the connection id and secret
1690/// key, which must match exactly in order to execute a query cancellation. The connection id has 19
1691/// bits of randomness, and the secret key the full 32, for a total of 51 bits. That is more than
1692/// 2e15 combinations, enough to nearly certainly prevent two different envds generating identical
1693/// combinations.
1694async fn cancel_request(
1695    conn_id: u32,
1696    secret_key: u32,
1697    cancellation_resolver: &CancellationResolver,
1698) {
1699    let suffix = conn_id_org_uuid(conn_id);
1700    let contents = match cancellation_resolver {
1701        CancellationResolver::Directory(dir) => {
1702            let path = dir.join(&suffix);
1703            match std::fs::read_to_string(&path) {
1704                Ok(contents) => contents,
1705                Err(err) => {
1706                    error!("could not read cancel file {path:?}: {err}");
1707                    return;
1708                }
1709            }
1710        }
1711        CancellationResolver::Static(addr) => addr.to_owned(),
1712    };
1713    let mut all_ips = Vec::new();
1714    for addr in contents.lines() {
1715        let addr = addr.trim();
1716        if addr.is_empty() {
1717            continue;
1718        }
1719        match tokio::net::lookup_host(addr).await {
1720            Ok(ips) => all_ips.extend(ips),
1721            Err(err) => {
1722                error!("{addr} failed resolution: {err}");
1723            }
1724        }
1725    }
1726    let mut buf = BytesMut::with_capacity(16);
1727    let msg = FrontendStartupMessage::CancelRequest {
1728        conn_id,
1729        secret_key,
1730    };
1731    msg.encode(&mut buf).expect("must encode");
1732    let buf = buf.freeze();
1733    for ip in all_ips {
1734        debug!("cancelling {suffix} to {ip}");
1735        let buf = buf.clone();
1736        spawn(|| "cancel request for ip", async move {
1737            let send = async {
1738                let mut stream = TcpStream::connect(&ip).await?;
1739                stream.write_all(&buf).await?;
1740                stream.shutdown().await?;
1741                Ok::<_, io::Error>(())
1742            };
1743            if let Err(err) = send.await {
1744                error!("error mirroring cancel to {ip}: {err}");
1745            }
1746        });
1747    }
1748}
1749
1750/// Opens a TCP connection to environmentd, recording how long a successful connect took.
1751async fn connect_upstream(addr: SocketAddr, metrics: &ServerMetrics) -> io::Result<netio::Stream> {
1752    let start = Instant::now();
1753    let stream = netio::Stream::connect(addr).await?;
1754    metrics
1755        .upstream_connect_duration()
1756        .observe(start.elapsed().as_secs_f64());
1757    Ok(stream)
1758}
1759
1760/// Writes an HTTP error response to a client and closes the connection.
1761///
1762/// The proxied connections are raw TCP streams, so the HTTP framing has to be written by hand.
1763/// Errors are ignored: the connection is going away regardless.
1764async fn send_http_error(client_stream: &mut Box<dyn ClientStream>, status: &str, body: &str) {
1765    let response = format!(
1766        "HTTP/1.1 {status}\r\n\
1767         Content-Type: text/plain\r\n\
1768         Content-Length: {}\r\n\
1769         Connection: close\r\n\
1770         \r\n\
1771         {body}",
1772        body.len(),
1773    );
1774    let _ = client_stream.write_all(response.as_bytes()).await;
1775    let _ = client_stream.shutdown().await;
1776}
1777
1778struct HttpsBalancer {
1779    resolver: Arc<TenantDnsResolver>,
1780    tls: Option<ReloadingSslContext>,
1781    resolve_template: Arc<str>,
1782    port: u16,
1783    metrics: Arc<ServerMetrics>,
1784    limiter: Arc<ConnectionLimiter>,
1785    configs: ConfigSet,
1786    internal_tls: Option<SslConnector>,
1787}
1788
1789impl HttpsBalancer {
1790    /// Negotiates TLS and resolves a backend using the client's SNI hostname.
1791    async fn pre_resolve(
1792        conn: Connection,
1793        tls_context: Option<ReloadingSslContext>,
1794        resolver: &TenantDnsResolver,
1795        resolve_template: &str,
1796        port: u16,
1797        metrics: &ServerMetrics,
1798        fault: &ConnectionFault,
1799    ) -> Result<(Box<dyn ClientStream>, ResolvedAddr, SocketAddr), anyhow::Error> {
1800        let peer_addr = conn
1801            .peer_addr()
1802            .context("fetching peer addr")
1803            .inspect_err(|_| fault.server())?;
1804        let (client_stream, servername): (Box<dyn ClientStream>, Option<String>) = match tls_context
1805        {
1806            Some(tls_context) => {
1807                let mut ssl_stream = Ssl::new(&tls_context.get())
1808                    .and_then(|ssl| SslStream::new(ssl, conn))
1809                    .inspect_err(|_| fault.server())?;
1810                if let Err(e) = Pin::new(&mut ssl_stream).accept().await {
1811                    metrics.tls_handshake_failures().inc();
1812                    let _ = ssl_stream.get_mut().shutdown().await;
1813                    return Err(e.into());
1814                }
1815                let servername: Option<String> =
1816                    ssl_stream.ssl().servername(NameType::HOST_NAME).map(|sn| {
1817                        match sn.split_once('.') {
1818                            Some((left, _right)) => left,
1819                            None => sn,
1820                        }
1821                        .into()
1822                    });
1823                debug!("Found sni servername: {servername:?} (https)");
1824                (Box::new(ssl_stream), servername)
1825            }
1826            _ => (Box::new(conn), None),
1827        };
1828        let resolved = fault
1829            .server_work(Self::resolve(
1830                resolver,
1831                resolve_template,
1832                port,
1833                servername.as_deref(),
1834            ))
1835            .await
1836            .inspect_err(|e| {
1837                if !names_unknown_host(e) {
1838                    fault.server();
1839                }
1840            })?;
1841        Ok((client_stream, resolved, peer_addr))
1842    }
1843
1844    async fn resolve(
1845        resolver: &TenantDnsResolver,
1846        resolve_template: &str,
1847        port: u16,
1848        servername: Option<&str>,
1849    ) -> Result<ResolvedAddr, anyhow::Error> {
1850        let (addr, tenant) = match servername {
1851            Some(sni) => resolver.resolve_sni(resolve_template, port, sni).await?,
1852            // A template with a placeholder routes by SNI, so without one the client named no
1853            // tenant. Scanners connecting by IP land here.
1854            None if resolve_template.contains("{}") => return Err(UnknownHost::NoSni.into()),
1855            None => {
1856                debug!("https hostname (no SNI): {}:{}", resolve_template, port);
1857                resolver.resolve(resolve_template, port, false).await?
1858            }
1859        };
1860
1861        Ok(ResolvedAddr {
1862            addr,
1863            password: None,
1864            tenant,
1865        })
1866    }
1867}
1868
1869/// Extracts the tenant ID from an environmentd CNAME target.
1870///
1871/// The CNAME points at the environmentd service, of the form
1872/// `<service>.<namespace>.svc.cluster.local`, e.g.
1873/// `environmentd.environment-58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3-0.svc.cluster.local`.
1874/// The `<namespace>` is the environment name `environment-<tenant_id>-<index>`,
1875/// where `<tenant_id>` is the tenant's UUID and `<index>` is the environment
1876/// generation.
1877///
1878/// NOTE: `<index>` is currently always 0, since a tenant has one environment
1879/// per region, but this does not rely on that so that multiple environments
1880/// per tenant can be supported later.
1881fn extract_tenant_from_cname(cname: &str) -> Option<String> {
1882    let mut parts = cname.split('.');
1883    let _service = parts.next();
1884    let Some(namespace) = parts.next() else {
1885        return None;
1886    };
1887    // Trim off the starting `environment-`.
1888    let Some((_, namespace)) = namespace.split_once('-') else {
1889        return None;
1890    };
1891    // Trim off the ending `-<index>`.
1892    let Some((tenant, _)) = namespace.rsplit_once('-') else {
1893        return None;
1894    };
1895    // Convert to a Uuid so that this tenant matches the frontegg resolver exactly, because it
1896    // also uses Uuid::to_string.
1897    let Ok(tenant) = Uuid::parse_str(tenant) else {
1898        error!("cname tenant not a uuid: {tenant}");
1899        return None;
1900    };
1901    Some(tenant.to_string())
1902}
1903
1904/// Strips the surrounding brackets from an IPv6 host literal, e.g. `[::1]`
1905/// becomes `::1`. Leaves other hosts unchanged. This lets a bracketed IPv6
1906/// literal in an address template parse as an `IpAddr` and resolve, matching
1907/// what `tokio::net::lookup_host` accepts.
1908fn strip_ipv6_brackets(host: &str) -> &str {
1909    host.strip_prefix('[')
1910        .and_then(|h| h.strip_suffix(']'))
1911        .unwrap_or(host)
1912}
1913
1914impl mz_server_core::Server for HttpsBalancer {
1915    const NAME: &'static str = "https_balancer";
1916
1917    // TODO(jkosh44) consider forwarding the connection UUID to the adapter.
1918    fn handle_connection(
1919        &self,
1920        conn: Connection,
1921        _tokio_metrics_intervals: impl Iterator<Item = TaskMetrics> + Send + 'static,
1922    ) -> mz_server_core::ConnectionHandler {
1923        let tls_context = self.tls.clone();
1924        let internal_tls = self.internal_tls.clone();
1925        let resolver = Arc::clone(&self.resolver);
1926        let resolve_template = Arc::clone(&self.resolve_template);
1927        let port = self.port;
1928        let inner_metrics = Arc::clone(&self.metrics);
1929        let outer_metrics = Arc::clone(&self.metrics);
1930        let limiter = Arc::clone(&self.limiter);
1931        let pre_resolved_timeout = PRE_RESOLVED_TIMEOUT.get(&self.configs);
1932        let inject_proxy_headers = INJECT_PROXY_PROTOCOL_HEADER_HTTP.get(&self.configs);
1933        Box::pin(async move {
1934            let accepted = Instant::now();
1935            let fault = ConnectionFault::default();
1936            let fault_ref = &fault;
1937            let active_guard = inner_metrics.active_connections();
1938            let result: Result<_, anyhow::Error> = Box::pin(async move {
1939                let Some(_conn_guard) = limiter.acquire() else {
1940                    fault_ref.server();
1941                    return Ok(());
1942                };
1943                let pre_resolved = inner_metrics.pre_resolved_connections();
1944
1945                let (mut client_stream, resolved, peer_addr) = under_pre_resolved_timeout(
1946                    pre_resolved_timeout,
1947                    &inner_metrics,
1948                    fault_ref,
1949                    Self::pre_resolve(
1950                        conn,
1951                        tls_context,
1952                        &resolver,
1953                        &resolve_template,
1954                        port,
1955                        &inner_metrics,
1956                        fault_ref,
1957                    ),
1958                )
1959                .await?;
1960                drop(pre_resolved);
1961                // See the matching log line in the pgwire handler. The tenant always comes from
1962                // SNI here, before any authentication.
1963                if let Some(tenant) = &resolved.tenant {
1964                    info!(
1965                        target: NO_SENTRY_TARGET,
1966                        client_addr = %peer_addr.ip(),
1967                        %tenant,
1968                        authenticated = false,
1969                        "https connection resolved",
1970                    );
1971                }
1972                let inner_active_guard = resolved
1973                    .tenant
1974                    .as_ref()
1975                    .map(|tenant| inner_metrics.tenant_connections(tenant));
1976                let mut mz_stream = match connect_upstream(resolved.addr, &inner_metrics).await {
1977                    Ok(stream) => stream,
1978                    Err(e) => {
1979                        error!("failed to connect to upstream server: {e}");
1980                        fault_ref.server();
1981                        send_http_error(
1982                            &mut client_stream,
1983                            "502 Bad Gateway",
1984                            "upstream server not available",
1985                        )
1986                        .await;
1987                        return Ok(());
1988                    }
1989                };
1990
1991                // Every failure from here until proxying begins is on balancerd's side of the
1992                // connection or environmentd's.
1993                let mz_stream = async {
1994                    if inject_proxy_headers {
1995                        // Write the tcp proxy header
1996                        let addrs = ProxiedAddress::stream(peer_addr, resolved.addr);
1997                        let header = ProxyHeader::with_address(addrs);
1998                        let mut buf = [0u8; 1024];
1999                        let len = header.encode_to_slice_v2(&mut buf)?;
2000                        mz_stream.write_all(&buf[..len]).await?;
2001                    }
2002
2003                    let mz_stream = if let Some(internal_tls) = &internal_tls {
2004                        // do a TLS handshake
2005                        let mut ssl = internal_tls
2006                            .configure()?
2007                            .into_ssl(&resolved.addr.to_string())?;
2008                        ssl.set_connect_state();
2009                        Conn::Ssl(SslStream::new(ssl, mz_stream)?)
2010                    } else {
2011                        Conn::Unencrypted(mz_stream)
2012                    };
2013                    Ok::<_, anyhow::Error>(mz_stream)
2014                }
2015                .await;
2016                let mut mz_stream = mz_stream.inspect_err(|_| fault_ref.server())?;
2017                inner_metrics
2018                    .connection_setup_duration()
2019                    .observe(accepted.elapsed().as_secs_f64());
2020
2021                let mut client_counter = CountingConn::new(client_stream);
2022
2023                // Now blindly shuffle bytes back and forth until closed.
2024                // TODO: Limit total memory use.
2025                // See corresponding comment in pgwire implementation about ignoring the error.
2026                let _ = tokio::io::copy_bidirectional(&mut client_counter, &mut mz_stream).await;
2027                inner_metrics
2028                    .connection_duration()
2029                    .observe(accepted.elapsed().as_secs_f64());
2030                if let Some(tenant) = &resolved.tenant {
2031                    inner_metrics
2032                        .tenant_connections_tx(tenant)
2033                        .inc_by(u64::cast_from(client_counter.written));
2034                    inner_metrics
2035                        .tenant_connections_rx(tenant)
2036                        .inc_by(u64::cast_from(client_counter.read));
2037                }
2038                drop(inner_active_guard);
2039                Ok(())
2040            })
2041            .await;
2042            drop(active_guard);
2043            outer_metrics.connection_status(fault.is_ok()).inc();
2044            if let Err(e) = result {
2045                debug!("connection error: {e}");
2046            }
2047            Ok(())
2048        })
2049    }
2050}
2051
2052/// Template for constructing the destination hostname from a TLS SNI
2053/// servername. `{}` is replaced with the first label of the servername.
2054#[derive(Debug)]
2055pub struct SniTemplate {
2056    pub template: String,
2057    pub port: u16,
2058}
2059
2060trait ClientStream: AsyncRead + AsyncWrite + Unpin + Send {}
2061impl<T: AsyncRead + AsyncWrite + Unpin + Send> ClientStream for T {}
2062
2063#[derive(Debug)]
2064pub enum BalancerResolver {
2065    Static(String),
2066    MultiTenant {
2067        dns: Arc<TenantDnsResolver>,
2068        frontegg: FronteggResolver,
2069        sni: Option<SniTemplate>,
2070    },
2071}
2072
2073/// An error resolving a connection's destination.
2074///
2075/// The `Display` of this error is sent to unauthenticated clients, so it must
2076/// not contain internal details such as hostnames. Those belong in the source
2077/// error attached to each variant, which is only logged.
2078#[derive(Debug, thiserror::Error)]
2079enum ResolveError {
2080    #[error("invalid password")]
2081    InvalidPassword,
2082    /// A client protocol violation, e.g. sending the wrong message during
2083    /// startup. An unauthenticated client can trigger these at will, so they
2084    /// are logged at `warn!`, not `error!`, to avoid making client noise look
2085    /// like server faults and spamming the error log.
2086    #[error("internal error")]
2087    Client(#[source] anyhow::Error),
2088    /// The tenant's upstream backend could not be reached, e.g. its hostname
2089    /// did not resolve. Reported to the client with a distinct, non-leaking
2090    /// message rather than a generic internal error, so a down environment is
2091    /// not mistaken for a balancerd bug. Logged at `warn!`: a bogus SNI reaches
2092    /// this from an unauthenticated client, and a genuinely down environment is
2093    /// an operational condition, not a balancerd fault.
2094    #[error("upstream server not available")]
2095    Upstream(#[source] anyhow::Error),
2096    /// A server-side fault.
2097    #[error("internal error")]
2098    Internal(#[from] anyhow::Error),
2099}
2100
2101impl From<io::Error> for ResolveError {
2102    fn from(e: io::Error) -> Self {
2103        ResolveError::Internal(e.into())
2104    }
2105}
2106
2107impl BalancerResolver {
2108    /// Returns a clone of the shared DNS resolver if in multi-tenant mode.
2109    /// This allows sharing the resolver with other components like HttpsBalancer.
2110    pub fn shared_dns(&self) -> Option<Arc<TenantDnsResolver>> {
2111        match self {
2112            BalancerResolver::Static(_) => None,
2113            BalancerResolver::MultiTenant { dns, .. } => Some(Arc::clone(dns)),
2114        }
2115    }
2116
2117    async fn resolve<A>(
2118        &self,
2119        conn: &mut FramedConn<A>,
2120        user: &str,
2121        metrics: &ServerMetrics,
2122        fault: &ConnectionFault,
2123    ) -> Result<ResolvedAddr, ResolveError>
2124    where
2125        A: AsyncRead + AsyncWrite + Unpin,
2126    {
2127        match self {
2128            BalancerResolver::MultiTenant {
2129                dns: dns_resolver,
2130                frontegg:
2131                    FronteggResolver {
2132                        auth,
2133                        addr_template,
2134                    },
2135                sni: sni_resolver,
2136            } => {
2137                let servername = match conn.inner() {
2138                    Conn::Ssl(ssl_stream) => {
2139                        ssl_stream.ssl().servername(NameType::HOST_NAME).map(|sn| {
2140                            match sn.split_once('.') {
2141                                Some((left, _right)) => left,
2142                                None => sn,
2143                            }
2144                        })
2145                    }
2146                    Conn::Unencrypted(_) => None,
2147                };
2148                let has_sni = servername.is_some();
2149                let resolved_addr = match (servername, sni_resolver.as_ref()) {
2150                    (Some(servername), Some(SniTemplate { template, port })) => {
2151                        // Resolving the tenant's backend is balancerd's own
2152                        // work, so a failure is a server fault, unless the
2153                        // client's SNI named a host that doesn't exist
2154                        // (`UnknownHost`).
2155                        let (addr, tenant) = fault
2156                            .server_work(dns_resolver.resolve_sni(template, *port, servername))
2157                            .await
2158                            .map_err(ResolveError::Upstream)?;
2159                        debug!("pgwire SNI resolved tenant: {:?}", tenant);
2160                        ResolvedAddr {
2161                            addr,
2162                            password: None,
2163                            tenant,
2164                        }
2165                    }
2166                    _ => {
2167                        let client_io = |e: io::Error| ResolveError::Client(e.into());
2168                        conn.send(BackendMessage::AuthenticationCleartextPassword)
2169                            .await
2170                            .map_err(client_io)?;
2171                        conn.flush().await.map_err(client_io)?;
2172                        let password = match conn.recv().await.map_err(client_io)? {
2173                            Some(FrontendMessage::Password { password }) => password,
2174                            _ => {
2175                                return Err(ResolveError::Client(anyhow::anyhow!(
2176                                    "expected Password message"
2177                                )));
2178                            }
2179                        };
2180
2181                        // balancerd only needs the validated tenant_id to route
2182                        // the connection; group extraction happens in
2183                        // environmentd, so skip it here.
2184                        let auth_response = fault
2185                            .server_work(auth.authenticate(user, &password, None))
2186                            .await;
2187                        let auth_session = match auth_response {
2188                            Ok((auth_session, _)) => auth_session,
2189                            Err(e) => {
2190                                warn!("pgwire connection failed authentication: {}", e);
2191                                // The client sees the same rejection either way.
2192                                if !e.is_credential_rejection() {
2193                                    fault.server();
2194                                }
2195                                return Err(ResolveError::InvalidPassword);
2196                            }
2197                        };
2198
2199                        let hostname_with_port =
2200                            addr_template.replace("{}", &auth_session.tenant_id().to_string());
2201                        let (hostname, port_str) = hostname_with_port
2202                            .rsplit_once(':')
2203                            .ok_or_else(|| anyhow::anyhow!("port required in addr_template"))?;
2204                        let port: u16 = port_str.parse().with_context(|| {
2205                            format!("invalid port in addr_template: {}", port_str)
2206                        })?;
2207                        // The tenant is already known from authentication, so
2208                        // skip the CNAME lookup that resolve() would do. A
2209                        // failure here means the tenant's backend is unreachable.
2210                        let addr = fault
2211                            .server_work(dns_resolver.resolve_addr(hostname, port))
2212                            .await
2213                            .map_err(ResolveError::Upstream)?;
2214                        let tenant = auth_session.tenant_id().to_string();
2215                        debug!("Frontegg resolved tenant: {}", tenant);
2216                        ResolvedAddr {
2217                            addr,
2218                            password: Some(password),
2219                            tenant: Some(tenant),
2220                        }
2221                    }
2222                };
2223                metrics
2224                    .tenant_pgwire_sni_count(
2225                        resolved_addr.tenant.as_deref().unwrap_or("unknown"),
2226                        has_sni,
2227                    )
2228                    .inc();
2229
2230                Ok(resolved_addr)
2231            }
2232            BalancerResolver::Static(addr) => {
2233                // We don't want any caching here so we just use the standard
2234                // tokio resolver.
2235                let Some(addr) = fault
2236                    .server_work(tokio::net::lookup_host(addr))
2237                    .await?
2238                    .next()
2239                else {
2240                    return Err(anyhow::anyhow!("{addr} did not resolve to any addresses").into());
2241                };
2242
2243                Ok(ResolvedAddr {
2244                    addr,
2245                    password: None,
2246                    tenant: None,
2247                })
2248            }
2249        }
2250    }
2251}
2252
2253/// Creates a resolver from the system DNS configuration.
2254///
2255/// Caching is delegated to the infrastructure (node-local DNS), so this
2256/// resolver does no caching of its own. Fails if the system DNS configuration
2257/// cannot be read. We must not fall back to hickory's default config (Google
2258/// public DNS) here, that would leak internal hostnames to an external party
2259/// and could not resolve them anyway.
2260fn create_resolver() -> Result<TokioResolver, anyhow::Error> {
2261    let (config, mut opts) = read_system_conf().context("reading system DNS configuration")?;
2262    opts.cache_size = 0;
2263    // Query A records first and AAAA only on failure, rather than both.
2264    opts.ip_strategy = LookupIpStrategy::Ipv4thenIpv6;
2265
2266    Ok(
2267        Resolver::builder_with_config(config, TokioRuntimeProvider::default())
2268            .with_options(opts)
2269            .build()?,
2270    )
2271}
2272
2273/// Resolves tenant hostnames for pgwire and HTTPS routing.
2274///
2275/// Caching is delegated to the infrastructure (node-local DNS), so every
2276/// lookup issues a query. CNAMEs are resolved separately from A records only
2277/// because the CNAME carries the tenant, not for caching reasons.
2278#[derive(Debug)]
2279pub struct TenantDnsResolver {
2280    resolver: TokioResolver,
2281}
2282
2283impl TenantDnsResolver {
2284    /// Creates a new resolver. Fails if the system DNS configuration cannot be
2285    /// read.
2286    pub fn new() -> Result<Self, anyhow::Error> {
2287        Ok(Self {
2288            resolver: create_resolver()?,
2289        })
2290    }
2291
2292    /// Resolves the CNAME a hostname points at, if any.
2293    async fn resolve_cname(&self, hostname: &str) -> Option<String> {
2294        match self.resolver.lookup(hostname, RecordType::CNAME).await {
2295            Ok(cname_response) => {
2296                if let Some(cname_record) = cname_response.answers().first() {
2297                    if let RData::CNAME(cname_data) = &cname_record.data {
2298                        let cname = cname_data.to_string();
2299                        debug!("CNAME for {}: {}", hostname, cname);
2300                        return Some(cname);
2301                    }
2302                }
2303                None
2304            }
2305            Err(e) => {
2306                debug!("CNAME lookup failed for {}: {}", hostname, e);
2307                None
2308            }
2309        }
2310    }
2311
2312    /// Resolves the A records for a hostname.
2313    async fn resolve_a(&self, hostname: &str) -> Result<LookupIp, anyhow::Error> {
2314        self.resolver
2315            .lookup_ip(hostname)
2316            .await
2317            .with_context(|| format!("resolving A records for {}", hostname))
2318    }
2319
2320    /// Resolves the environment address for a TLS SNI servername.
2321    ///
2322    /// `servername` is the first label of the SNI host, e.g.
2323    /// `3dl07g8zmj91pntk4eo9cfvwe`. Substituting it into `template` (e.g.
2324    /// `blncr-{}`) yields a Kubernetes hostname like
2325    /// `blncr-3dl07g8zmj91pntk4eo9cfvwe`, which resolves via a CNAME to the
2326    /// environmentd service, e.g.
2327    /// `environmentd.environment-58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3-0.svc.cluster.local`.
2328    /// The tenant is extracted from that CNAME. See
2329    /// `extract_tenant_from_cname`.
2330    pub async fn resolve_sni(
2331        &self,
2332        template: &str,
2333        port: u16,
2334        servername: &str,
2335    ) -> Result<(SocketAddr, Option<String>), anyhow::Error> {
2336        let hostname = template.replace("{}", servername);
2337        debug!("SNI hostname: {}", hostname);
2338        // Without a placeholder the template names the backend itself, whose records vanish when
2339        // it is down: its headless Service answers NXDOMAIN without ready endpoints.
2340        let client_chosen = template.contains("{}");
2341        // The lookups parse the name the same way, and would fail before sending a query.
2342        if client_chosen && Name::from_utf8(&hostname).is_err() {
2343            return Err(UnknownHost::Named(hostname).into());
2344        }
2345        self.resolve(&hostname, port, client_chosen).await
2346    }
2347
2348    /// Resolves the address for a hostname, skipping CNAME resolution and
2349    /// tenant extraction. Use when the tenant is already known.
2350    async fn resolve_addr(&self, host: &str, port: u16) -> Result<SocketAddr, anyhow::Error> {
2351        let host = strip_ipv6_brackets(host);
2352        // IP literals need no resolution. Resolving them through hickory
2353        // would walk the search domain list first when ndots is large, as it
2354        // is in Kubernetes.
2355        if let Ok(ip) = host.parse::<IpAddr>() {
2356            return Ok(SocketAddr::new(ip, port));
2357        }
2358        Self::first_addr(self.resolve_a(host).await?, port)
2359    }
2360
2361    /// Resolves the address and tenant from a hostname and port.
2362    ///
2363    /// The tenant is extracted from the CNAME if the hostname points at one. When `client_chosen`,
2364    /// a hostname with neither a CNAME nor records fails with [`UnknownHost`] in its chain.
2365    async fn resolve(
2366        &self,
2367        host: &str,
2368        port: u16,
2369        client_chosen: bool,
2370    ) -> Result<(SocketAddr, Option<String>), anyhow::Error> {
2371        let host = strip_ipv6_brackets(host);
2372        // IP literals need no resolution and carry no tenant CNAME.
2373        if let Ok(ip) = host.parse::<IpAddr>() {
2374            return Ok((SocketAddr::new(ip, port), None));
2375        }
2376
2377        // The CNAME carries the tenant, so resolve it separately to extract it. A CNAME whose
2378        // target has no records is a tenant whose backend is down, so only a missing CNAME can
2379        // mean an unknown tenant.
2380        let (ips, tenant) = if let Some(cname) = self.resolve_cname(host).await {
2381            let tenant = extract_tenant_from_cname(&cname);
2382            (self.resolve_a(&cname).await?, tenant)
2383        } else {
2384            let ips = self.resolve_a(host).await.map_err(|e| {
2385                let no_records = e
2386                    .downcast_ref::<NetError>()
2387                    .is_some_and(NetError::is_no_records_found);
2388                if client_chosen && no_records {
2389                    e.context(UnknownHost::Named(host.to_owned()))
2390                } else {
2391                    e
2392                }
2393            })?;
2394            (ips, None)
2395        };
2396
2397        Ok((Self::first_addr(ips, port)?, tenant))
2398    }
2399
2400    /// Returns the first resolved IP as a socket address.
2401    fn first_addr(ips: LookupIp, port: u16) -> Result<SocketAddr, anyhow::Error> {
2402        ips.iter()
2403            .next()
2404            .map(|ip| SocketAddr::new(ip, port))
2405            .ok_or_else(|| anyhow::anyhow!("no A records found in DNS response"))
2406    }
2407}
2408
2409/// The client named no tenant, or one that does not exist, rather than one whose backend is
2410/// unavailable. Only a hostname the client's SNI chooses can produce one.
2411#[derive(Debug, thiserror::Error)]
2412enum UnknownHost {
2413    #[error("no SNI to route by")]
2414    NoSni,
2415    /// The hostname is not a valid DNS name, or has no CNAME and no records.
2416    #[error("no tenant at {0}")]
2417    Named(String),
2418}
2419
2420/// Whether `err` carries an [`UnknownHost`].
2421fn names_unknown_host(err: &anyhow::Error) -> bool {
2422    err.downcast_ref::<UnknownHost>().is_some()
2423}
2424
2425#[derive(Debug)]
2426pub struct FronteggResolver {
2427    pub auth: FronteggAuthentication,
2428    pub addr_template: String,
2429}
2430
2431#[derive(Debug)]
2432struct ResolvedAddr {
2433    addr: SocketAddr,
2434    password: Option<String>,
2435    tenant: Option<String>,
2436}
2437
2438#[cfg(test)]
2439mod tests {
2440    use mz_dyncfg::ConfigUpdates;
2441
2442    use super::*;
2443
2444    #[mz_ore::test]
2445    fn test_tenant() {
2446        let tests = vec![
2447            ("", None),
2448            (
2449                "environmentd.environment-58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3-0.svc.cluster.local",
2450                Some("58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3"),
2451            ),
2452            (
2453                // Trailing dot from an absolute DNS name, as returned by the
2454                // resolver.
2455                "environmentd.environment-58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3-0.svc.cluster.local.",
2456                Some("58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3"),
2457            ),
2458            (
2459                // Variously named parts.
2460                "service.something-58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3-0.ssvvcc.cloister.faraway",
2461                Some("58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3"),
2462            ),
2463            (
2464                // No dashes in uuid.
2465                "environmentd.environment-58cd23ffa4d74bd0ad85a6ff29cc86c3-0.svc.cluster.local",
2466                Some("58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3"),
2467            ),
2468            (
2469                // -1234 suffix.
2470                "environmentd.environment-58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3-1234.svc.cluster.local",
2471                Some("58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3"),
2472            ),
2473            (
2474                // Uppercase.
2475                "environmentd.environment-58CD23FF-A4D7-4BD0-AD85-A6FF29CC86C3-0.svc.cluster.local",
2476                Some("58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3"),
2477            ),
2478            (
2479                // No -number suffix.
2480                "environmentd.environment-58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3.svc.cluster.local",
2481                None,
2482            ),
2483            (
2484                // No service name.
2485                "environment-58cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3-0.svc.cluster.local",
2486                None,
2487            ),
2488            (
2489                // Invalid UUID.
2490                "environmentd.environment-8cd23ff-a4d7-4bd0-ad85-a6ff29cc86c3-0.svc.cluster.local",
2491                None,
2492            ),
2493        ];
2494        for (name, expect) in tests {
2495            let cname = extract_tenant_from_cname(name);
2496            assert_eq!(
2497                cname.as_deref(),
2498                expect,
2499                "{name} got {cname:?} expected {expect:?}"
2500            );
2501        }
2502    }
2503
2504    #[mz_ore::test]
2505    fn test_strip_ipv6_brackets() {
2506        assert_eq!(strip_ipv6_brackets("[::1]"), "::1");
2507        assert_eq!(strip_ipv6_brackets("[2001:db8::1]"), "2001:db8::1");
2508        // Hosts without a matched bracket pair are left untouched.
2509        assert_eq!(strip_ipv6_brackets("127.0.0.1"), "127.0.0.1");
2510        assert_eq!(strip_ipv6_brackets("host.example.com"), "host.example.com");
2511        assert_eq!(strip_ipv6_brackets("[unclosed"), "[unclosed");
2512        assert_eq!(strip_ipv6_brackets("unopened]"), "unopened]");
2513    }
2514
2515    #[mz_ore::test]
2516    fn test_connection_limiter() {
2517        let configs = dyncfgs::all_dyncfgs(ConfigSet::default());
2518        let set_max = |max: u32| {
2519            let mut updates = ConfigUpdates::default();
2520            updates.add(&MAX_CONNECTIONS, max);
2521            updates.apply(&configs);
2522        };
2523
2524        set_max(2);
2525        let limiter = ConnectionLimiter::new(&MetricsRegistry::new(), configs.clone());
2526        let first = limiter.acquire().expect("under the limit");
2527        let second = limiter.acquire().expect("at the limit");
2528        assert!(limiter.acquire().is_none());
2529        assert_eq!(limiter.rejected.get(), 1);
2530
2531        // A connection closing frees capacity for a new one.
2532        drop(second);
2533        let _third = limiter.acquire().expect("capacity freed");
2534        drop(first);
2535
2536        // Lowering the limit below the current count refuses new connections but leaves the
2537        // established ones alone.
2538        set_max(1);
2539        assert!(limiter.acquire().is_none());
2540
2541        // Zero disables the limit.
2542        set_max(0);
2543        assert!(limiter.acquire().is_some());
2544    }
2545
2546    /// balancerd ignores the sync error at startup, so a dyncfg type LaunchDarkly can't carry
2547    /// would silently stop every balancerd flag from syncing.
2548    #[mz_ore::test(tokio::test)]
2549    #[cfg_attr(miri, ignore)] // unsupported operation: can't call foreign function
2550    async fn test_dyncfgs_sync_from_launchdarkly() {
2551        mz_dyncfg_launchdarkly::sync_launchdarkly_to_configset(
2552            dyncfgs::all_dyncfgs(ConfigSet::default()),
2553            &BUILD_INFO,
2554            |_| Ok(()),
2555            None,
2556            Duration::ZERO,
2557            None,
2558            |_, _| {},
2559        )
2560        .await
2561        .expect("every balancerd dyncfg converts to a LaunchDarkly flag");
2562    }
2563
2564    #[mz_ore::test]
2565    fn test_connection_watermarks() {
2566        let configs = dyncfgs::all_dyncfgs(ConfigSet::default());
2567        let set = |low: usize, high: usize, max: u32| {
2568            let mut updates = ConfigUpdates::default();
2569            updates.add(&CONNECTION_LOW_WATERMARK, low);
2570            updates.add(&CONNECTION_HIGH_WATERMARK, high);
2571            updates.add(&MAX_CONNECTIONS, max);
2572            updates.apply(&configs);
2573        };
2574        let limiter = ConnectionLimiter::new(&MetricsRegistry::new(), configs.clone());
2575        let _first = limiter.acquire().expect("under the limit");
2576        let second = limiter.acquire().expect("under the limit");
2577        let third = limiter.acquire().expect("under the limit");
2578
2579        // Anything short of both watermarks, ordered, below an enabled hard limit is off.
2580        for (low, high, max) in [
2581            (0, 0, 5),
2582            (1, 0, 5),
2583            (0, 3, 5),
2584            (3, 3, 5),
2585            (4, 3, 5),
2586            (1, 5, 5),
2587            (1, 3, 0),
2588        ] {
2589            set(low, high, max);
2590            limiter.evaluate_watermarks();
2591            assert!(
2592                limiter.accepting(),
2593                "watermarks low={low} high={high} max={max} must be ignored",
2594            );
2595        }
2596        assert_eq!(limiter.watermark_transitions.get(), 0);
2597
2598        set(2, 3, 5);
2599        limiter.evaluate_watermarks();
2600        assert!(!limiter.accepting(), "at the high watermark");
2601        assert_eq!(limiter.watermark_transitions.get(), 1);
2602
2603        drop(third);
2604        limiter.evaluate_watermarks();
2605        assert!(
2606            !limiter.accepting(),
2607            "between the watermarks the state is kept"
2608        );
2609        drop(second);
2610        limiter.evaluate_watermarks();
2611        assert!(
2612            limiter.accepting(),
2613            "below the low watermark readiness returns"
2614        );
2615        assert_eq!(limiter.watermark_transitions.get(), 2);
2616
2617        // Disabling the watermarks while out of rotation restores readiness.
2618        let _fourth = limiter.acquire().expect("under the limit");
2619        let _fifth = limiter.acquire().expect("under the limit");
2620        limiter.evaluate_watermarks();
2621        assert!(!limiter.accepting(), "at the high watermark again");
2622        set(0, 0, 5);
2623        limiter.evaluate_watermarks();
2624        assert!(
2625            limiter.accepting(),
2626            "disabling the watermarks restores readiness"
2627        );
2628        assert_eq!(limiter.watermark_transitions.get(), 4);
2629    }
2630
2631    /// A zero timeout disables the deadline, which is what leaves the phase unbounded.
2632    #[mz_ore::test(tokio::test(start_paused = true))]
2633    async fn test_pre_resolved_timeout_zero_disables() {
2634        let metrics = ServerMetrics::new(
2635            ServerMetricsConfig::register_into(&MetricsRegistry::new()),
2636            "pgwire",
2637        );
2638
2639        // Zero lets the phase run as long as it likes. The paused clock makes the hour free.
2640        let slow = async {
2641            tokio::time::sleep(Duration::from_secs(60 * 60)).await;
2642            Ok::<(), io::Error>(())
2643        };
2644        let fault = ConnectionFault::default();
2645        assert!(
2646            under_pre_resolved_timeout(Duration::ZERO, &metrics, &fault, slow)
2647                .await
2648                .is_ok()
2649        );
2650        assert_eq!(metrics.pre_resolved_timeout().get(), 0);
2651
2652        // Anything else bounds it, and the overrun is counted.
2653        let stalled = std::future::pending::<Result<(), io::Error>>();
2654        let err = under_pre_resolved_timeout(Duration::from_secs(30), &metrics, &fault, stalled)
2655            .await
2656            .expect_err("a phase that never finishes must be cut short");
2657        assert_eq!(err.kind(), io::ErrorKind::TimedOut);
2658        assert_eq!(metrics.pre_resolved_timeout().get(), 1);
2659    }
2660
2661    /// An SNI that cannot name a tenant is the client's fault, and is settled without a DNS query.
2662    #[mz_ore::test(tokio::test)]
2663    #[cfg_attr(miri, ignore)] // reads the system DNS configuration
2664    async fn test_sni_naming_no_tenant() {
2665        let resolver = TenantDnsResolver::new().expect("system DNS configuration is readable");
2666        for servername in ["bad!name".to_owned(), "a".repeat(64)] {
2667            let err = resolver
2668                .resolve_sni("blncr-{}.invalid", 6876, &servername)
2669                .await
2670                .expect_err("not a valid hostname");
2671            assert!(names_unknown_host(&err), "{servername}: {err:#}");
2672        }
2673
2674        let err = HttpsBalancer::resolve(&resolver, "blncr-{}.invalid", 6876, None)
2675            .await
2676            .expect_err("no SNI to route by");
2677        assert!(names_unknown_host(&err), "{err:#}");
2678    }
2679
2680    /// A deadline is the server's fault only when it interrupts balancerd's own work.
2681    #[mz_ore::test(tokio::test(start_paused = true))]
2682    async fn test_pre_resolved_timeout_fault() {
2683        let metrics = ServerMetrics::new(
2684            ServerMetricsConfig::register_into(&MetricsRegistry::new()),
2685            "pgwire",
2686        );
2687
2688        let waiting_on_client = ConnectionFault::default();
2689        let stalled = std::future::pending::<Result<(), io::Error>>();
2690        under_pre_resolved_timeout(
2691            Duration::from_secs(30),
2692            &metrics,
2693            &waiting_on_client,
2694            stalled,
2695        )
2696        .await
2697        .expect_err("a phase that never finishes must be cut short");
2698        assert!(waiting_on_client.is_ok());
2699
2700        let resolving = ConnectionFault::default();
2701        let stalled = resolving.server_work(std::future::pending::<Result<(), io::Error>>());
2702        under_pre_resolved_timeout(Duration::from_secs(30), &metrics, &resolving, stalled)
2703            .await
2704            .expect_err("a phase that never finishes must be cut short");
2705        assert!(!resolving.is_ok());
2706
2707        // Finished server work leaves a later client stall the client's fault.
2708        let resolved = ConnectionFault::default();
2709        let phase = async {
2710            resolved.server_work(async {}).await;
2711            std::future::pending::<Result<(), io::Error>>().await
2712        };
2713        under_pre_resolved_timeout(Duration::from_secs(30), &metrics, &resolved, phase)
2714            .await
2715            .expect_err("a phase that never finishes must be cut short");
2716        assert!(resolved.is_ok());
2717    }
2718}