1#[cfg(all(target_os = "linux", feature = "native-io-uring"))]
4use super::helpers::should_try_uring_plain;
5use super::helpers::{
6 connect_backend_for_stream, plain_connect_attempt_backend, record_connect_attempt,
7 record_connect_result,
8};
9use super::types::{
10 BUFFER_CAPACITY, CONNECT_BACKEND_TOKIO, CONNECT_TRANSPORT_GSSENC, CONNECT_TRANSPORT_MTLS,
11 CONNECT_TRANSPORT_PLAIN, CONNECT_TRANSPORT_TLS, ConnectParams, DEFAULT_CONNECT_TIMEOUT,
12 GSSENC_REQUEST, GssEncNegotiationResult, PgConnection, SSL_REQUEST, STMT_CACHE_CAPACITY,
13 StatementCache, TlsConfig, has_logical_replication_startup_mode,
14};
15use crate::driver::stream::PgStream;
16use crate::driver::{AuthSettings, ConnectOptions, GssEncMode, PgError, PgResult, TlsMode};
17use crate::protocol::PROTOCOL_VERSION_3_0;
18use crate::protocol::wire::FrontendMessage;
19use bytes::BytesMut;
20use std::collections::{HashMap, VecDeque};
21use std::sync::Arc;
22use std::time::Instant;
23use tokio::io::AsyncWriteExt;
24use tokio::net::TcpStream;
25
26#[inline]
27fn protocol_version_from_minor(minor: u16) -> i32 {
28 ((3i32) << 16) | i32::from(minor)
29}
30
31fn ensure_crypto_provider() {
38 use tokio_rustls::rustls::crypto::{CryptoProvider, aws_lc_rs};
39 if CryptoProvider::get_default().is_none() {
40 let _ = aws_lc_rs::default_provider().install_default();
41 }
42}
43
44fn socket_addr(host: &str, port: u16) -> String {
45 if host.contains(':') && !host.starts_with('[') {
46 format!("[{}]:{}", host, port)
47 } else {
48 format!("{}:{}", host, port)
49 }
50}
51
52fn is_explicit_protocol_version_rejection(err: &PgError) -> bool {
53 let msg = match err {
54 PgError::Connection(msg) | PgError::Protocol(msg) | PgError::Auth(msg) => msg,
55 PgError::Query(msg) => msg,
56 PgError::QueryServer(server) => &server.message,
57 _ => return false,
58 };
59
60 let lower = msg.to_ascii_lowercase();
61 lower.contains("unsupported frontend protocol")
62 || lower.contains("frontend protocol") && lower.contains("unsupported")
63 || lower.contains("protocol version") && lower.contains("not support")
64}
65
66impl PgConnection {
67 pub async fn connect(host: &str, port: u16, user: &str, database: &str) -> PgResult<Self> {
76 Self::connect_with_password(host, port, user, database, None).await
77 }
78
79 pub async fn connect_with_password(
86 host: &str,
87 port: u16,
88 user: &str,
89 database: &str,
90 password: Option<&str>,
91 ) -> PgResult<Self> {
92 Self::connect_with_password_and_auth(
93 host,
94 port,
95 user,
96 database,
97 password,
98 AuthSettings::default(),
99 )
100 .await
101 }
102
103 pub async fn connect_with_options(
114 host: &str,
115 port: u16,
116 user: &str,
117 database: &str,
118 password: Option<&str>,
119 options: ConnectOptions,
120 ) -> PgResult<Self> {
121 let ConnectOptions {
122 tls_mode,
123 gss_enc_mode,
124 tls_ca_cert_pem,
125 mtls,
126 gss_token_provider,
127 auth,
128 io_uring,
129 startup_params,
130 } = options;
131
132 if mtls.is_some() && matches!(tls_mode, TlsMode::Disable) {
133 return Err(PgError::Connection(
134 "Invalid connect options: mTLS requires tls_mode=Prefer or Require".to_string(),
135 ));
136 }
137
138 if gss_enc_mode == GssEncMode::Require && mtls.is_some() {
142 return Err(PgError::Connection(
143 "gssencmode=require is incompatible with mTLS — both provide \
144 transport encryption; use one or the other"
145 .to_string(),
146 ));
147 }
148
149 if let Some(mtls_config) = mtls {
150 return Self::connect_mtls_with_password_and_auth_and_gss(
153 ConnectParams {
154 host,
155 port,
156 user,
157 database,
158 password,
159 auth_settings: auth,
160 gss_token_provider,
161 io_uring,
162 protocol_minor: Self::default_protocol_minor(),
163 startup_params: startup_params.clone(),
164 },
165 mtls_config,
166 )
167 .await;
168 }
169
170 if gss_enc_mode != GssEncMode::Disable {
172 match Self::try_gssenc_request(host, port).await {
173 Ok(GssEncNegotiationResult::Accepted(tcp_stream)) => {
174 let connect_started = Instant::now();
175 record_connect_attempt(CONNECT_TRANSPORT_GSSENC, CONNECT_BACKEND_TOKIO);
176 #[cfg(all(feature = "enterprise-gssapi", target_os = "linux"))]
177 {
178 let default_minor = Self::default_protocol_minor();
179 let gss_params = ConnectParams {
180 host,
181 port,
182 user,
183 database,
184 password,
185 auth_settings: auth,
186 gss_token_provider: gss_token_provider.clone(),
187 io_uring,
188 protocol_minor: default_minor,
189 startup_params: startup_params.clone(),
190 };
191 let mut result = Self::connect_gssenc_accepted_with_timeout(
192 tcp_stream,
193 gss_params.clone(),
194 )
195 .await;
196 if let Err(err) = &result
197 && default_minor > 0
198 && is_explicit_protocol_version_rejection(err)
199 {
200 let downgrade_minor = (PROTOCOL_VERSION_3_0 & 0xFFFF) as u16;
201 let retry_stream = match Self::try_gssenc_request(host, port).await {
202 Ok(GssEncNegotiationResult::Accepted(stream)) => stream,
203 Ok(GssEncNegotiationResult::Rejected) => {
204 return Err(PgError::Connection(
205 "Protocol downgrade retry failed: server rejected GSSENCRequest"
206 .to_string(),
207 ));
208 }
209 Ok(GssEncNegotiationResult::ServerError) => {
210 return Err(PgError::Connection(
211 "Protocol downgrade retry failed: server returned error to GSSENCRequest"
212 .to_string(),
213 ));
214 }
215 Err(e) => {
216 return Err(e);
217 }
218 };
219 let mut retry_params = gss_params;
220 retry_params.protocol_minor = downgrade_minor;
221 result = Self::connect_gssenc_accepted_with_timeout(
222 retry_stream,
223 retry_params,
224 )
225 .await;
226 }
227 record_connect_result(
228 CONNECT_TRANSPORT_GSSENC,
229 CONNECT_BACKEND_TOKIO,
230 &result,
231 connect_started.elapsed(),
232 );
233 return result;
234 }
235 #[cfg(not(all(feature = "enterprise-gssapi", target_os = "linux")))]
236 {
237 let _ = tcp_stream;
238 let err = PgError::Connection(
239 "Server accepted GSSENCRequest but GSSAPI encryption requires \
240 feature enterprise-gssapi on Linux"
241 .to_string(),
242 );
243 metrics::histogram!(
244 "qail_pg_connect_duration_seconds",
245 "transport" => CONNECT_TRANSPORT_GSSENC,
246 "backend" => CONNECT_BACKEND_TOKIO,
247 "outcome" => "error"
248 )
249 .record(connect_started.elapsed().as_secs_f64());
250 metrics::counter!(
251 "qail_pg_connect_failure_total",
252 "transport" => CONNECT_TRANSPORT_GSSENC,
253 "backend" => CONNECT_BACKEND_TOKIO,
254 "error_kind" => super::helpers::connect_error_kind(&err)
255 )
256 .increment(1);
257 return Err(err);
258 }
259 }
260 Ok(GssEncNegotiationResult::Rejected)
261 | Ok(GssEncNegotiationResult::ServerError) => {
262 if gss_enc_mode == GssEncMode::Require {
263 return Err(PgError::Connection(
264 "gssencmode=require but server rejected GSSENCRequest".to_string(),
265 ));
266 }
267 }
269 Err(e) => {
270 if gss_enc_mode == GssEncMode::Require {
271 return Err(e);
272 }
273 tracing::debug!(
275 host = %host,
276 port = %port,
277 error = %e,
278 "gssenc_prefer_fallthrough"
279 );
280 }
281 }
282 }
283
284 match tls_mode {
286 TlsMode::Disable => {
287 Self::connect_with_password_and_auth_and_gss(ConnectParams {
288 host,
289 port,
290 user,
291 database,
292 password,
293 auth_settings: auth,
294 gss_token_provider,
295 io_uring,
296 protocol_minor: Self::default_protocol_minor(),
297 startup_params: startup_params.clone(),
298 })
299 .await
300 }
301 TlsMode::Require => {
302 Self::connect_tls_with_auth_and_gss(
303 ConnectParams {
304 host,
305 port,
306 user,
307 database,
308 password,
309 auth_settings: auth,
310 gss_token_provider,
311 io_uring,
312 protocol_minor: Self::default_protocol_minor(),
313 startup_params: startup_params.clone(),
314 },
315 tls_ca_cert_pem.as_deref(),
316 )
317 .await
318 }
319 TlsMode::Prefer => {
320 match Self::connect_tls_with_auth_and_gss(
321 ConnectParams {
322 host,
323 port,
324 user,
325 database,
326 password,
327 auth_settings: auth,
328 gss_token_provider: gss_token_provider.clone(),
329 io_uring,
330 protocol_minor: Self::default_protocol_minor(),
331 startup_params: startup_params.clone(),
332 },
333 tls_ca_cert_pem.as_deref(),
334 )
335 .await
336 {
337 Ok(conn) => Ok(conn),
338 Err(e) if e.is_tls_unsupported_by_server() => {
342 Self::connect_with_password_and_auth_and_gss(ConnectParams {
343 host,
344 port,
345 user,
346 database,
347 password,
348 auth_settings: auth,
349 gss_token_provider,
350 io_uring,
351 protocol_minor: Self::default_protocol_minor(),
352 startup_params: startup_params.clone(),
353 })
354 .await
355 }
356 Err(e) => Err(e),
357 }
358 }
359 }
360 }
361
362 async fn try_gssenc_request(host: &str, port: u16) -> PgResult<GssEncNegotiationResult> {
369 tokio::time::timeout(
370 DEFAULT_CONNECT_TIMEOUT,
371 Self::try_gssenc_request_inner(host, port),
372 )
373 .await
374 .map_err(|_| {
375 PgError::Connection(format!(
376 "GSSENCRequest timeout after {:?}",
377 DEFAULT_CONNECT_TIMEOUT
378 ))
379 })?
380 }
381
382 async fn try_gssenc_request_inner(host: &str, port: u16) -> PgResult<GssEncNegotiationResult> {
384 use tokio::io::AsyncReadExt;
385
386 let addr = socket_addr(host, port);
387 let mut tcp_stream = TcpStream::connect(&addr).await?;
388 tcp_stream.set_nodelay(true)?;
389
390 tcp_stream.write_all(&GSSENC_REQUEST).await?;
392 tcp_stream.flush().await?;
393
394 let mut response = [0u8; 1];
398 tcp_stream.read_exact(&mut response).await?;
399
400 match response[0] {
401 b'G' => {
402 let mut peek_buf = [0u8; 1];
405 match tcp_stream.try_read(&mut peek_buf) {
406 Ok(0) => {} Ok(_n) => {
408 return Err(PgError::Connection(
410 "Protocol violation: extra bytes after GSSENCRequest 'G' response \
411 (possible CVE-2021-23222 buffer-stuffing attack)"
412 .to_string(),
413 ));
414 }
415 Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {
416 }
418 Err(e) => {
419 return Err(PgError::Io(e));
420 }
421 }
422 Ok(GssEncNegotiationResult::Accepted(tcp_stream))
423 }
424 b'N' => Ok(GssEncNegotiationResult::Rejected),
425 b'E' => {
426 tracing::trace!(
430 host = %host,
431 port = %port,
432 "gssenc_request_server_error (suppressed per CVE-2024-10977)"
433 );
434 Ok(GssEncNegotiationResult::ServerError)
435 }
436 other => Err(PgError::Connection(format!(
437 "Unexpected response to GSSENCRequest: 0x{:02X} \
438 (expected 'G'=0x47 or 'N'=0x4E)",
439 other
440 ))),
441 }
442 }
443
444 #[cfg(all(feature = "enterprise-gssapi", target_os = "linux"))]
445 async fn connect_gssenc_accepted_with_timeout(
446 tcp_stream: TcpStream,
447 params: ConnectParams<'_>,
448 ) -> PgResult<Self> {
449 let gssenc_fut = async {
450 let gss_stream = super::super::gss::gssenc_handshake(tcp_stream, params.host)
451 .await
452 .map_err(PgError::Auth)?;
453 let mut conn = Self {
454 stream: PgStream::GssEnc(gss_stream),
455 buffer: BytesMut::with_capacity(BUFFER_CAPACITY),
456 write_buf: BytesMut::with_capacity(BUFFER_CAPACITY),
457 sql_buf: BytesMut::with_capacity(512),
458 params_buf: Vec::with_capacity(16),
459 prepared_statements: HashMap::new(),
460 stmt_cache: StatementCache::new(STMT_CACHE_CAPACITY),
461 column_info_cache: HashMap::new(),
462 process_id: 0,
463 cancel_key_bytes: Vec::new(),
464 requested_protocol_minor: params.protocol_minor,
465 negotiated_protocol_minor: params.protocol_minor,
466 notifications: VecDeque::new(),
467 replication_stream_active: false,
468 replication_mode_enabled: has_logical_replication_startup_mode(
469 ¶ms.startup_params,
470 ),
471 last_replication_wal_end: None,
472 io_desynced: false,
473 pending_statement_closes: Vec::new(),
474 draining_statement_closes: false,
475 };
476 conn.send(FrontendMessage::Startup {
477 user: params.user.to_string(),
478 database: params.database.to_string(),
479 protocol_version: protocol_version_from_minor(params.protocol_minor),
480 startup_params: params.startup_params.clone(),
481 })
482 .await?;
483 conn.handle_startup(
484 params.user,
485 params.password,
486 params.auth_settings,
487 params.gss_token_provider,
488 )
489 .await?;
490 Ok(conn)
491 };
492 tokio::time::timeout(DEFAULT_CONNECT_TIMEOUT, gssenc_fut)
493 .await
494 .map_err(|_| {
495 PgError::Connection(format!(
496 "GSSENC connection timeout after {:?} (handshake + auth)",
497 DEFAULT_CONNECT_TIMEOUT
498 ))
499 })?
500 }
501
502 pub async fn connect_with_password_and_auth(
504 host: &str,
505 port: u16,
506 user: &str,
507 database: &str,
508 password: Option<&str>,
509 auth_settings: AuthSettings,
510 ) -> PgResult<Self> {
511 Self::connect_with_password_and_auth_and_gss(ConnectParams {
512 host,
513 port,
514 user,
515 database,
516 password,
517 auth_settings,
518 gss_token_provider: None,
519 io_uring: false,
520 protocol_minor: Self::default_protocol_minor(),
521 startup_params: Vec::new(),
522 })
523 .await
524 }
525
526 async fn connect_with_password_and_auth_and_gss(params: ConnectParams<'_>) -> PgResult<Self> {
527 let first = Self::connect_with_password_and_auth_and_gss_once(params.clone()).await;
528 if let Err(err) = &first
529 && params.protocol_minor > 0
530 && is_explicit_protocol_version_rejection(err)
531 {
532 let mut downgraded = params;
533 downgraded.protocol_minor = (PROTOCOL_VERSION_3_0 & 0xFFFF) as u16;
534 return Self::connect_with_password_and_auth_and_gss_once(downgraded).await;
535 }
536 first
537 }
538
539 async fn connect_with_password_and_auth_and_gss_once(
540 params: ConnectParams<'_>,
541 ) -> PgResult<Self> {
542 let connect_started = Instant::now();
543 let attempt_backend = plain_connect_attempt_backend(params.io_uring);
544 record_connect_attempt(CONNECT_TRANSPORT_PLAIN, attempt_backend);
545 let result = tokio::time::timeout(
546 DEFAULT_CONNECT_TIMEOUT,
547 Self::connect_with_password_inner(params),
548 )
549 .await
550 .map_err(|_| {
551 PgError::Connection(format!(
552 "Connection timeout after {:?} (TCP connect + handshake)",
553 DEFAULT_CONNECT_TIMEOUT
554 ))
555 })?;
556 let backend = result
557 .as_ref()
558 .map(|conn| connect_backend_for_stream(&conn.stream))
559 .unwrap_or(attempt_backend);
560 record_connect_result(
561 CONNECT_TRANSPORT_PLAIN,
562 backend,
563 &result,
564 connect_started.elapsed(),
565 );
566 result
567 }
568
569 async fn connect_with_password_inner(params: ConnectParams<'_>) -> PgResult<Self> {
571 let ConnectParams {
572 host,
573 port,
574 user,
575 database,
576 password,
577 auth_settings,
578 gss_token_provider,
579 io_uring,
580 protocol_minor,
581 startup_params,
582 } = params;
583 let replication_mode_enabled = has_logical_replication_startup_mode(&startup_params);
584 let addr = socket_addr(host, port);
585 let stream = Self::connect_plain_stream(&addr, io_uring).await?;
586
587 let mut conn = Self {
588 stream,
589 buffer: BytesMut::with_capacity(BUFFER_CAPACITY),
590 write_buf: BytesMut::with_capacity(BUFFER_CAPACITY), sql_buf: BytesMut::with_capacity(512),
592 params_buf: Vec::with_capacity(16), prepared_statements: HashMap::new(),
594 stmt_cache: StatementCache::new(STMT_CACHE_CAPACITY),
595 column_info_cache: HashMap::new(),
596 process_id: 0,
597 cancel_key_bytes: Vec::new(),
598 requested_protocol_minor: protocol_minor,
599 negotiated_protocol_minor: protocol_minor,
600 notifications: VecDeque::new(),
601 replication_stream_active: false,
602 replication_mode_enabled,
603 last_replication_wal_end: None,
604 io_desynced: false,
605 pending_statement_closes: Vec::new(),
606 draining_statement_closes: false,
607 };
608
609 conn.send(FrontendMessage::Startup {
610 user: user.to_string(),
611 database: database.to_string(),
612 protocol_version: protocol_version_from_minor(protocol_minor),
613 startup_params,
614 })
615 .await?;
616
617 conn.handle_startup(user, password, auth_settings, gss_token_provider)
618 .await?;
619
620 Ok(conn)
621 }
622
623 async fn connect_plain_stream(addr: &str, io_uring: bool) -> PgResult<PgStream> {
624 let tcp_stream = TcpStream::connect(addr).await?;
625 tcp_stream.set_nodelay(true)?;
626
627 #[cfg(all(target_os = "linux", feature = "native-io-uring"))]
628 {
629 if should_try_uring_plain(io_uring) {
630 let std_stream = tcp_stream.into_std()?;
631 let fallback_std = std_stream.try_clone()?;
632 match super::super::uring::UringTcpStream::from_std(std_stream) {
633 Ok(uring_stream) => {
634 tracing::info!(
635 addr = %addr,
636 "qail-pg: using io_uring plain TCP transport"
637 );
638 return Ok(PgStream::Uring(uring_stream));
639 }
640 Err(e) => {
641 tracing::warn!(
642 addr = %addr,
643 error = %e,
644 "qail-pg: io_uring stream conversion failed; falling back to tokio TCP"
645 );
646 fallback_std.set_nonblocking(true)?;
647 let fallback = TcpStream::from_std(fallback_std)?;
648 return Ok(PgStream::Tcp(fallback));
649 }
650 }
651 }
652 }
653 #[cfg(not(all(target_os = "linux", feature = "native-io-uring")))]
654 {
655 let _ = io_uring;
656 }
657
658 Ok(PgStream::Tcp(tcp_stream))
659 }
660
661 pub async fn connect_tls(
664 host: &str,
665 port: u16,
666 user: &str,
667 database: &str,
668 password: Option<&str>,
669 ) -> PgResult<Self> {
670 Self::connect_tls_with_auth(
671 host,
672 port,
673 user,
674 database,
675 password,
676 AuthSettings::default(),
677 None,
678 )
679 .await
680 }
681
682 pub async fn connect_tls_with_auth(
684 host: &str,
685 port: u16,
686 user: &str,
687 database: &str,
688 password: Option<&str>,
689 auth_settings: AuthSettings,
690 ca_cert_pem: Option<&[u8]>,
691 ) -> PgResult<Self> {
692 Self::connect_tls_with_auth_and_gss(
693 ConnectParams {
694 host,
695 port,
696 user,
697 database,
698 password,
699 auth_settings,
700 gss_token_provider: None,
701 io_uring: false,
702 protocol_minor: Self::default_protocol_minor(),
703 startup_params: Vec::new(),
704 },
705 ca_cert_pem,
706 )
707 .await
708 }
709
710 async fn connect_tls_with_auth_and_gss(
711 params: ConnectParams<'_>,
712 ca_cert_pem: Option<&[u8]>,
713 ) -> PgResult<Self> {
714 let first = Self::connect_tls_with_auth_and_gss_once(params.clone(), ca_cert_pem).await;
715 if let Err(err) = &first
716 && params.protocol_minor > 0
717 && is_explicit_protocol_version_rejection(err)
718 {
719 let mut downgraded = params;
720 downgraded.protocol_minor = (PROTOCOL_VERSION_3_0 & 0xFFFF) as u16;
721 return Self::connect_tls_with_auth_and_gss_once(downgraded, ca_cert_pem).await;
722 }
723 first
724 }
725
726 async fn connect_tls_with_auth_and_gss_once(
727 params: ConnectParams<'_>,
728 ca_cert_pem: Option<&[u8]>,
729 ) -> PgResult<Self> {
730 let connect_started = Instant::now();
731 record_connect_attempt(CONNECT_TRANSPORT_TLS, CONNECT_BACKEND_TOKIO);
732 let result = tokio::time::timeout(
733 DEFAULT_CONNECT_TIMEOUT,
734 Self::connect_tls_inner(params, ca_cert_pem),
735 )
736 .await
737 .map_err(|_| {
738 PgError::Connection(format!(
739 "TLS connection timeout after {:?}",
740 DEFAULT_CONNECT_TIMEOUT
741 ))
742 })?;
743 record_connect_result(
744 CONNECT_TRANSPORT_TLS,
745 CONNECT_BACKEND_TOKIO,
746 &result,
747 connect_started.elapsed(),
748 );
749 result
750 }
751
752 async fn connect_tls_inner(
754 params: ConnectParams<'_>,
755 ca_cert_pem: Option<&[u8]>,
756 ) -> PgResult<Self> {
757 let ConnectParams {
758 host,
759 port,
760 user,
761 database,
762 password,
763 auth_settings,
764 gss_token_provider,
765 io_uring: _,
766 protocol_minor,
767 startup_params,
768 } = params;
769 let replication_mode_enabled = has_logical_replication_startup_mode(&startup_params);
770 use tokio::io::AsyncReadExt;
771 use tokio_rustls::TlsConnector;
772 use tokio_rustls::rustls::ClientConfig;
773 use tokio_rustls::rustls::pki_types::{CertificateDer, ServerName, pem::PemObject};
774
775 let addr = socket_addr(host, port);
776 let mut tcp_stream = TcpStream::connect(&addr).await?;
777
778 tcp_stream.write_all(&SSL_REQUEST).await?;
780
781 let mut response = [0u8; 1];
783 tcp_stream.read_exact(&mut response).await?;
784
785 if response[0] != b'S' {
786 return Err(PgError::tls_unsupported_by_server());
787 }
788
789 let mut root_cert_store = tokio_rustls::rustls::RootCertStore::empty();
790
791 if let Some(ca_pem) = ca_cert_pem {
792 let certs = CertificateDer::pem_slice_iter(ca_pem)
793 .collect::<Result<Vec<_>, _>>()
794 .map_err(|e| PgError::Connection(format!("Invalid CA certificate PEM: {}", e)))?;
795 if certs.is_empty() {
796 return Err(PgError::Connection(
797 "No CA certificates found in provided PEM".to_string(),
798 ));
799 }
800 for cert in certs {
801 let _ = root_cert_store.add(cert);
802 }
803 } else {
804 let certs = rustls_native_certs::load_native_certs();
805 for cert in certs.certs {
806 let _ = root_cert_store.add(cert);
807 }
808 }
809
810 ensure_crypto_provider();
811 let config = ClientConfig::builder()
812 .with_root_certificates(root_cert_store)
813 .with_no_client_auth();
814
815 let connector = TlsConnector::from(Arc::new(config));
816 let server_name = ServerName::try_from(host.to_string())
817 .map_err(|_| PgError::Connection("Invalid hostname for TLS".to_string()))?;
818
819 let tls_stream = connector
820 .connect(server_name, tcp_stream)
821 .await
822 .map_err(|e| PgError::Connection(format!("TLS handshake failed: {}", e)))?;
823
824 let mut conn = Self {
825 stream: PgStream::Tls(Box::new(tls_stream)),
826 buffer: BytesMut::with_capacity(BUFFER_CAPACITY),
827 write_buf: BytesMut::with_capacity(BUFFER_CAPACITY),
828 sql_buf: BytesMut::with_capacity(512),
829 params_buf: Vec::with_capacity(16),
830 prepared_statements: HashMap::new(),
831 stmt_cache: StatementCache::new(STMT_CACHE_CAPACITY),
832 column_info_cache: HashMap::new(),
833 process_id: 0,
834 cancel_key_bytes: Vec::new(),
835 requested_protocol_minor: protocol_minor,
836 negotiated_protocol_minor: protocol_minor,
837 notifications: VecDeque::new(),
838 replication_stream_active: false,
839 replication_mode_enabled,
840 last_replication_wal_end: None,
841 io_desynced: false,
842 pending_statement_closes: Vec::new(),
843 draining_statement_closes: false,
844 };
845
846 conn.send(FrontendMessage::Startup {
847 user: user.to_string(),
848 database: database.to_string(),
849 protocol_version: protocol_version_from_minor(protocol_minor),
850 startup_params,
851 })
852 .await?;
853
854 conn.handle_startup(user, password, auth_settings, gss_token_provider)
855 .await?;
856
857 Ok(conn)
858 }
859
860 pub async fn connect_mtls(
877 host: &str,
878 port: u16,
879 user: &str,
880 database: &str,
881 config: TlsConfig,
882 ) -> PgResult<Self> {
883 Self::connect_mtls_with_password_and_auth(
884 host,
885 port,
886 user,
887 database,
888 None,
889 config,
890 AuthSettings::default(),
891 )
892 .await
893 }
894
895 pub async fn connect_mtls_with_password_and_auth(
897 host: &str,
898 port: u16,
899 user: &str,
900 database: &str,
901 password: Option<&str>,
902 config: TlsConfig,
903 auth_settings: AuthSettings,
904 ) -> PgResult<Self> {
905 Self::connect_mtls_with_password_and_auth_and_gss(
906 ConnectParams {
907 host,
908 port,
909 user,
910 database,
911 password,
912 auth_settings,
913 gss_token_provider: None,
914 io_uring: false,
915 protocol_minor: Self::default_protocol_minor(),
916 startup_params: Vec::new(),
917 },
918 config,
919 )
920 .await
921 }
922
923 async fn connect_mtls_with_password_and_auth_and_gss(
924 params: ConnectParams<'_>,
925 config: TlsConfig,
926 ) -> PgResult<Self> {
927 let first =
928 Self::connect_mtls_with_password_and_auth_and_gss_once(params.clone(), config.clone())
929 .await;
930 if let Err(err) = &first
931 && params.protocol_minor > 0
932 && is_explicit_protocol_version_rejection(err)
933 {
934 let mut downgraded = params;
935 downgraded.protocol_minor = (PROTOCOL_VERSION_3_0 & 0xFFFF) as u16;
936 return Self::connect_mtls_with_password_and_auth_and_gss_once(downgraded, config)
937 .await;
938 }
939 first
940 }
941
942 async fn connect_mtls_with_password_and_auth_and_gss_once(
943 params: ConnectParams<'_>,
944 config: TlsConfig,
945 ) -> PgResult<Self> {
946 let connect_started = Instant::now();
947 record_connect_attempt(CONNECT_TRANSPORT_MTLS, CONNECT_BACKEND_TOKIO);
948 let result = tokio::time::timeout(
949 DEFAULT_CONNECT_TIMEOUT,
950 Self::connect_mtls_inner(params, config),
951 )
952 .await
953 .map_err(|_| {
954 PgError::Connection(format!(
955 "mTLS connection timeout after {:?}",
956 DEFAULT_CONNECT_TIMEOUT
957 ))
958 })?;
959 record_connect_result(
960 CONNECT_TRANSPORT_MTLS,
961 CONNECT_BACKEND_TOKIO,
962 &result,
963 connect_started.elapsed(),
964 );
965 result
966 }
967
968 async fn connect_mtls_inner(params: ConnectParams<'_>, config: TlsConfig) -> PgResult<Self> {
970 let ConnectParams {
971 host,
972 port,
973 user,
974 database,
975 password,
976 auth_settings,
977 gss_token_provider,
978 io_uring: _,
979 protocol_minor,
980 startup_params,
981 } = params;
982 let replication_mode_enabled = has_logical_replication_startup_mode(&startup_params);
983 use tokio::io::AsyncReadExt;
984 use tokio_rustls::TlsConnector;
985 use tokio_rustls::rustls::{
986 ClientConfig,
987 pki_types::{CertificateDer, PrivateKeyDer, ServerName, pem::PemObject},
988 };
989
990 let addr = socket_addr(host, port);
991 let mut tcp_stream = TcpStream::connect(&addr).await?;
992
993 tcp_stream.write_all(&SSL_REQUEST).await?;
995
996 let mut response = [0u8; 1];
998 tcp_stream.read_exact(&mut response).await?;
999
1000 if response[0] != b'S' {
1001 return Err(PgError::tls_unsupported_by_server());
1002 }
1003
1004 let mut root_cert_store = tokio_rustls::rustls::RootCertStore::empty();
1005
1006 if let Some(ca_pem) = &config.ca_cert_pem {
1007 let certs = CertificateDer::pem_slice_iter(ca_pem)
1008 .collect::<Result<Vec<_>, _>>()
1009 .map_err(|e| PgError::Connection(format!("Invalid CA certificate PEM: {}", e)))?;
1010 if certs.is_empty() {
1011 return Err(PgError::Connection(
1012 "No CA certificates found in provided PEM".to_string(),
1013 ));
1014 }
1015 for cert in certs {
1016 let _ = root_cert_store.add(cert);
1017 }
1018 } else {
1019 let certs = rustls_native_certs::load_native_certs();
1021 for cert in certs.certs {
1022 let _ = root_cert_store.add(cert);
1023 }
1024 }
1025
1026 let client_certs: Vec<CertificateDer<'static>> =
1027 CertificateDer::pem_slice_iter(&config.client_cert_pem)
1028 .collect::<Result<Vec<_>, _>>()
1029 .map_err(|e| PgError::Connection(format!("Invalid client cert PEM: {}", e)))?;
1030 if client_certs.is_empty() {
1031 return Err(PgError::Connection(
1032 "No client certificates found in PEM".to_string(),
1033 ));
1034 }
1035
1036 let client_key = PrivateKeyDer::from_pem_slice(&config.client_key_pem)
1037 .map_err(|e| PgError::Connection(format!("Invalid client key PEM: {}", e)))?;
1038
1039 ensure_crypto_provider();
1040 let tls_config = ClientConfig::builder()
1041 .with_root_certificates(root_cert_store)
1042 .with_client_auth_cert(client_certs, client_key)
1043 .map_err(|e| PgError::Connection(format!("Invalid client cert/key: {}", e)))?;
1044
1045 let connector = TlsConnector::from(Arc::new(tls_config));
1046 let server_name = ServerName::try_from(host.to_string())
1047 .map_err(|_| PgError::Connection("Invalid hostname for TLS".to_string()))?;
1048
1049 let tls_stream = connector
1050 .connect(server_name, tcp_stream)
1051 .await
1052 .map_err(|e| PgError::Connection(format!("mTLS handshake failed: {}", e)))?;
1053
1054 let mut conn = Self {
1055 stream: PgStream::Tls(Box::new(tls_stream)),
1056 buffer: BytesMut::with_capacity(BUFFER_CAPACITY),
1057 write_buf: BytesMut::with_capacity(BUFFER_CAPACITY),
1058 sql_buf: BytesMut::with_capacity(512),
1059 params_buf: Vec::with_capacity(16),
1060 prepared_statements: HashMap::new(),
1061 stmt_cache: StatementCache::new(STMT_CACHE_CAPACITY),
1062 column_info_cache: HashMap::new(),
1063 process_id: 0,
1064 cancel_key_bytes: Vec::new(),
1065 requested_protocol_minor: protocol_minor,
1066 negotiated_protocol_minor: protocol_minor,
1067 notifications: VecDeque::new(),
1068 replication_stream_active: false,
1069 replication_mode_enabled,
1070 last_replication_wal_end: None,
1071 io_desynced: false,
1072 pending_statement_closes: Vec::new(),
1073 draining_statement_closes: false,
1074 };
1075
1076 conn.send(FrontendMessage::Startup {
1077 user: user.to_string(),
1078 database: database.to_string(),
1079 protocol_version: protocol_version_from_minor(protocol_minor),
1080 startup_params,
1081 })
1082 .await?;
1083
1084 conn.handle_startup(user, password, auth_settings, gss_token_provider)
1085 .await?;
1086
1087 Ok(conn)
1088 }
1089
1090 #[cfg(unix)]
1092 pub async fn connect_unix(
1093 socket_path: &str,
1094 user: &str,
1095 database: &str,
1096 password: Option<&str>,
1097 ) -> PgResult<Self> {
1098 let default_minor = Self::default_protocol_minor();
1099 let first =
1100 Self::connect_unix_with_protocol(socket_path, user, database, password, default_minor)
1101 .await;
1102 if let Err(err) = &first
1103 && default_minor > 0
1104 && is_explicit_protocol_version_rejection(err)
1105 {
1106 let downgrade_minor = (PROTOCOL_VERSION_3_0 & 0xFFFF) as u16;
1107 return Self::connect_unix_with_protocol(
1108 socket_path,
1109 user,
1110 database,
1111 password,
1112 downgrade_minor,
1113 )
1114 .await;
1115 }
1116 first
1117 }
1118
1119 #[cfg(unix)]
1120 async fn connect_unix_with_protocol(
1121 socket_path: &str,
1122 user: &str,
1123 database: &str,
1124 password: Option<&str>,
1125 protocol_minor: u16,
1126 ) -> PgResult<Self> {
1127 use tokio::net::UnixStream;
1128
1129 let unix_stream = UnixStream::connect(socket_path).await?;
1130
1131 let mut conn = Self {
1132 stream: PgStream::Unix(unix_stream),
1133 buffer: BytesMut::with_capacity(BUFFER_CAPACITY),
1134 write_buf: BytesMut::with_capacity(BUFFER_CAPACITY),
1135 sql_buf: BytesMut::with_capacity(512),
1136 params_buf: Vec::with_capacity(16),
1137 prepared_statements: HashMap::new(),
1138 stmt_cache: StatementCache::new(STMT_CACHE_CAPACITY),
1139 column_info_cache: HashMap::new(),
1140 process_id: 0,
1141 cancel_key_bytes: Vec::new(),
1142 requested_protocol_minor: protocol_minor,
1143 negotiated_protocol_minor: protocol_minor,
1144 notifications: VecDeque::new(),
1145 replication_stream_active: false,
1146 replication_mode_enabled: false,
1147 last_replication_wal_end: None,
1148 io_desynced: false,
1149 pending_statement_closes: Vec::new(),
1150 draining_statement_closes: false,
1151 };
1152
1153 conn.send(FrontendMessage::Startup {
1154 user: user.to_string(),
1155 database: database.to_string(),
1156 protocol_version: protocol_version_from_minor(protocol_minor),
1157 startup_params: Vec::new(),
1158 })
1159 .await?;
1160
1161 conn.handle_startup(user, password, AuthSettings::default(), None)
1162 .await?;
1163
1164 Ok(conn)
1165 }
1166}
1167
1168#[cfg(test)]
1169mod tests {
1170 use super::{is_explicit_protocol_version_rejection, protocol_version_from_minor, socket_addr};
1171 use crate::driver::PgError;
1172
1173 #[test]
1174 fn protocol_version_from_minor_encodes_major_3() {
1175 assert_eq!(protocol_version_from_minor(2), 196610);
1176 assert_eq!(protocol_version_from_minor(0), 196608);
1177 }
1178
1179 #[test]
1180 fn socket_addr_brackets_ipv6_hosts() {
1181 assert_eq!(socket_addr("127.0.0.1", 5432), "127.0.0.1:5432");
1182 assert_eq!(socket_addr("::1", 5432), "[::1]:5432");
1183 assert_eq!(socket_addr("[::1]", 5432), "[::1]:5432");
1184 }
1185
1186 #[test]
1187 fn explicit_protocol_rejection_detection_is_case_insensitive() {
1188 let err = PgError::Connection("Unsupported frontend protocol 3.2".to_string());
1189 assert!(is_explicit_protocol_version_rejection(&err));
1190
1191 let err = PgError::Protocol("server: Protocol VERSION not supported".to_string());
1192 assert!(is_explicit_protocol_version_rejection(&err));
1193 }
1194
1195 #[test]
1196 fn explicit_protocol_rejection_does_not_match_unrelated_errors() {
1197 let err = PgError::Connection("connection reset by peer".to_string());
1198 assert!(!is_explicit_protocol_version_rejection(&err));
1199 }
1200}