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