@@ -31,7 +31,7 @@ use std::net::IpAddr;
3131use std:: { pin:: Pin , sync:: Arc , time:: Duration } ;
3232use tokio:: {
3333 io:: { AsyncRead , AsyncWrite , BufStream } ,
34- net:: { TcpListener , TcpStream } ,
34+ net:: { TcpListener , TcpStream , UnixListener , UnixStream } ,
3535} ;
3636use tokio_openssl:: SslStream ;
3737use tokio_util:: sync:: CancellationToken ;
@@ -61,6 +61,71 @@ const STREAM_WRITE_BUFFER_SIZE: usize = 8 * 1024;
6161// TLS detection timeout
6262const TLS_PEEK_TIMEOUT_SECS : u64 = 5 ;
6363
64+ /// Applies configurable permissions to Unix domain socket file.
65+ ///
66+ /// On Unix systems: Sets permissions to the specified octal value
67+ /// On other platforms: No-op (permissions handled by OS defaults)
68+ #[ cfg( unix) ]
69+ fn apply_socket_permissions ( path : & str , permissions : u32 ) -> std:: io:: Result < ( ) > {
70+ use std:: os:: unix:: fs:: PermissionsExt ;
71+ let file_permissions = std:: fs:: Permissions :: from_mode ( permissions) ;
72+ std:: fs:: set_permissions ( path, file_permissions)
73+ }
74+
75+ #[ cfg( not( unix) ) ]
76+ fn apply_socket_permissions ( _path : & str , _permissions : u32 ) -> std:: io:: Result < ( ) > {
77+ Ok ( ( ) )
78+ }
79+
80+ /// Creates and configures a Unix domain socket listener.
81+ ///
82+ /// This function handles the complete Unix socket setup:
83+ /// 1. Removes stale socket file from previous run (crash recovery)
84+ /// 2. Binds to the socket path
85+ /// 3. Sets appropriate permissions via platform-specific logic
86+ ///
87+ /// # Arguments
88+ ///
89+ /// * `socket_path` - Path where the Unix socket should be created
90+ /// * `permissions` - Octal file permissions (e.g., 0o660)
91+ ///
92+ /// # Returns
93+ ///
94+ /// Returns the configured `UnixListener` or an error if setup fails.
95+ ///
96+ /// # Errors
97+ ///
98+ /// This function will return an error if:
99+ /// * Failed to bind to the socket path
100+ /// * Failed to set socket file permissions
101+ fn create_unix_socket_listener ( socket_path : & str , permissions : u32 ) -> Result < UnixListener > {
102+ // Attempt to remove stale socket file from previous run (e.g., after crash).
103+ // This is standard Unix socket practice - PostgreSQL, MySQL, Redis all use this pattern.
104+ // Socket files cannot be cleaned up during crash (SIGKILL, segfault, power loss, etc.),
105+ // so cleanup at startup is the only reliable approach for crash recovery.
106+ // If the file couldn't be removed, bind() will fail with clear error "Address already in use".
107+ if let Err ( e) = std:: fs:: remove_file ( socket_path) {
108+ if e. kind ( ) != std:: io:: ErrorKind :: NotFound {
109+ tracing:: warn!(
110+ "Could not remove existing socket file {}: {}." ,
111+ socket_path,
112+ e
113+ ) ;
114+ }
115+ }
116+
117+ let listener = UnixListener :: bind ( socket_path) ?;
118+
119+ apply_socket_permissions ( socket_path, permissions) ?;
120+
121+ tracing:: info!(
122+ "Unix socket listener bound to {} with permissions {:o}" ,
123+ socket_path,
124+ permissions
125+ ) ;
126+ Ok ( listener)
127+ }
128+
64129/// Runs the DocumentDB gateway server, accepting and handling incoming connections.
65130///
66131/// This function sets up a TCP listener and SSL context, then continuously accepts
@@ -104,7 +169,24 @@ where
104169 ) )
105170 . await ?;
106171
107- // Listen for new tcp connections until token is not cancelled
172+ tracing:: info!(
173+ "TCP listener bound to port {}" ,
174+ service_context. setup_configuration( ) . gateway_listen_port( )
175+ ) ;
176+
177+ let unix_listener =
178+ if let Some ( unix_socket_path) = service_context. setup_configuration ( ) . unix_socket_path ( ) {
179+ let permissions = service_context
180+ . setup_configuration ( )
181+ . unix_socket_file_permissions ( ) ;
182+ let unix_listener = create_unix_socket_listener ( unix_socket_path, permissions) ?;
183+ Some ( unix_listener)
184+ } else {
185+ tracing:: info!( "Unix socket disabled (not configured)" ) ;
186+ None
187+ } ;
188+
189+ // Listen for new tcp and unix socket connections
108190 loop {
109191 tokio:: select! {
110192 stream_and_address = tcp_listener. accept( ) => {
@@ -119,7 +201,28 @@ where
119201 . await ;
120202
121203 if let Err ( conn_err) = conn_res {
122- tracing:: error!( "Failed to accept a connection: {conn_err:?}." ) ;
204+ tracing:: error!( "Failed to accept a TCP connection: {conn_err:?}." ) ;
205+ }
206+ } ) ;
207+ }
208+ stream_result = async {
209+ match & unix_listener {
210+ Some ( listener) => listener. accept( ) . await ,
211+ None => std:: future:: pending( ) . await ,
212+ }
213+ } , if unix_listener. is_some( ) => {
214+ let conn_service_context = service_context. clone( ) ;
215+ let conn_telemetry = telemetry. clone( ) ;
216+ tokio:: spawn( async move {
217+ let conn_res = handle_unix_connection:: <T >(
218+ stream_result,
219+ conn_service_context,
220+ conn_telemetry,
221+ )
222+ . await ;
223+
224+ if let Err ( conn_err) = conn_res {
225+ tracing:: error!( "Failed to accept a Unix socket connection: {conn_err:?}." ) ;
123226 }
124227 } ) ;
125228 }
@@ -337,6 +440,63 @@ where
337440 Ok ( ( ) )
338441}
339442
443+ /// Handles a single Unix socket connection without TLS.
444+ ///
445+ /// Unix socket connections are local-only and don't require TLS encryption.
446+ /// This function creates a connection context and processes the connection.
447+ ///
448+ /// # Arguments
449+ ///
450+ /// * `stream_result` - Result containing the Unix stream from accept()
451+ /// * `service_context` - Service configuration and shared state
452+ /// * `telemetry` - Optional telemetry provider for metrics collection
453+ ///
454+ /// # Returns
455+ ///
456+ /// Returns `Ok(())` on successful connection handling, or an error if connection
457+ /// setup fails.
458+ async fn handle_unix_connection < T > (
459+ stream_result : std:: result:: Result < ( UnixStream , tokio:: net:: unix:: SocketAddr ) , std:: io:: Error > ,
460+ service_context : ServiceContext ,
461+ telemetry : Option < Box < dyn TelemetryProvider > > ,
462+ ) -> Result < ( ) >
463+ where
464+ T : PgDataClient ,
465+ {
466+ let ( unix_stream, _socket_addr) = stream_result?;
467+
468+ let connection_id = Uuid :: new_v4 ( ) ;
469+ tracing:: info!(
470+ activity_id = connection_id. to_string( ) . as_str( ) ,
471+ "New Unix socket connection established"
472+ ) ;
473+
474+ // For Unix sockets, use localhost as the address since they don't have IP addresses
475+
476+ let connection_context = ConnectionContext :: new (
477+ service_context,
478+ telemetry,
479+ "localhost" . to_string ( ) ,
480+ None , // No TLS for Unix sockets
481+ connection_id,
482+ "UnixSocket" . to_string ( ) ,
483+ ) ;
484+
485+ let buffered_stream = BufStream :: with_capacity (
486+ STREAM_READ_BUFFER_SIZE ,
487+ STREAM_WRITE_BUFFER_SIZE ,
488+ unix_stream,
489+ ) ;
490+
491+ tracing:: info!(
492+ activity_id = connection_id. to_string( ) . as_str( ) ,
493+ "Unix socket connection established - Connection Id {connection_id}"
494+ ) ;
495+
496+ handle_stream :: < T , _ > ( buffered_stream, connection_context) . await ;
497+ Ok ( ( ) )
498+ }
499+
340500async fn handle_stream < T , S > ( mut stream : S , mut connection_context : ConnectionContext )
341501where
342502 T : PgDataClient ,
0 commit comments