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