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