33// SPDX-License-Identifier: Apache-2.0
44
55use std:: convert:: Infallible ;
6+ use std:: fmt;
7+ use std:: io;
8+ use std:: path:: PathBuf ;
9+ use std:: pin:: Pin ;
10+ use std:: task:: { Context as TaskContext , Poll } ;
611
712#[ cfg( all( feature = "rocket" , feature = "openapi" ) ) ]
813use crate :: openapi:: { OpenApiDoc , RenderedDoc } ;
@@ -13,6 +18,12 @@ use std::{borrow::Cow, sync::Arc};
1318
1419use anyhow:: { Context , Result } ;
1520use ra_tls:: traits:: CertExt ;
21+ #[ cfg( unix) ]
22+ use rocket:: listener:: unix:: UnixStream ;
23+ #[ cfg( unix) ]
24+ use rocket:: listener:: { Connection , Listener } ;
25+ #[ cfg( unix) ]
26+ use rocket:: tokio:: io:: { AsyncRead , AsyncWrite , ReadBuf } ;
1627use rocket:: {
1728 data:: { ByteUnit , Data , Limits , ToByteUnit } ,
1829 http:: { uri:: Origin , ContentType , Method , Status } ,
@@ -25,7 +36,7 @@ use rocket::{
2536use rocket_vsock_listener:: VsockEndpoint ;
2637use tracing:: warn;
2738
28- use crate :: { encode_error, CallContext , RemoteEndpoint , RpcCall } ;
39+ use crate :: { encode_error, CallContext , RemoteEndpoint , RpcCall , UnixPeerCred } ;
2940
3041pub struct RpcResponse {
3142 is_json : bool ,
@@ -48,6 +59,141 @@ impl<'r> Responder<'r, 'static> for RpcResponse {
4859 }
4960}
5061
62+ #[ cfg( unix) ]
63+ #[ derive( Debug , Clone ) ]
64+ struct UnixPeerEndpoint {
65+ path : PathBuf ,
66+ peer : Option < UnixPeerCred > ,
67+ }
68+
69+ #[ cfg( unix) ]
70+ impl fmt:: Display for UnixPeerEndpoint {
71+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
72+ write ! ( f, "unix:{}" , self . path. display( ) )
73+ }
74+ }
75+
76+ #[ cfg( unix) ]
77+ pub struct UnixPeerCredListener < L > {
78+ inner : L ,
79+ }
80+
81+ #[ cfg( unix) ]
82+ impl < L > UnixPeerCredListener < L > {
83+ pub fn new ( inner : L ) -> Self {
84+ Self { inner }
85+ }
86+ }
87+
88+ #[ cfg( unix) ]
89+ pub struct UnixPeerCredConnection {
90+ stream : UnixStream ,
91+ endpoint : rocket:: listener:: Endpoint ,
92+ }
93+
94+ #[ cfg( unix) ]
95+ impl AsyncRead for UnixPeerCredConnection {
96+ fn poll_read (
97+ mut self : Pin < & mut Self > ,
98+ cx : & mut TaskContext < ' _ > ,
99+ buf : & mut ReadBuf < ' _ > ,
100+ ) -> Poll < io:: Result < ( ) > > {
101+ Pin :: new ( & mut self . stream ) . poll_read ( cx, buf)
102+ }
103+ }
104+
105+ #[ cfg( unix) ]
106+ impl AsyncWrite for UnixPeerCredConnection {
107+ fn poll_write (
108+ mut self : Pin < & mut Self > ,
109+ cx : & mut TaskContext < ' _ > ,
110+ buf : & [ u8 ] ,
111+ ) -> Poll < io:: Result < usize > > {
112+ Pin :: new ( & mut self . stream ) . poll_write ( cx, buf)
113+ }
114+
115+ fn poll_flush ( mut self : Pin < & mut Self > , cx : & mut TaskContext < ' _ > ) -> Poll < io:: Result < ( ) > > {
116+ Pin :: new ( & mut self . stream ) . poll_flush ( cx)
117+ }
118+
119+ fn poll_shutdown ( mut self : Pin < & mut Self > , cx : & mut TaskContext < ' _ > ) -> Poll < io:: Result < ( ) > > {
120+ Pin :: new ( & mut self . stream ) . poll_shutdown ( cx)
121+ }
122+
123+ fn is_write_vectored ( & self ) -> bool {
124+ self . stream . is_write_vectored ( )
125+ }
126+
127+ fn poll_write_vectored (
128+ mut self : Pin < & mut Self > ,
129+ cx : & mut TaskContext < ' _ > ,
130+ bufs : & [ io:: IoSlice < ' _ > ] ,
131+ ) -> Poll < io:: Result < usize > > {
132+ Pin :: new ( & mut self . stream ) . poll_write_vectored ( cx, bufs)
133+ }
134+ }
135+
136+ #[ cfg( unix) ]
137+ impl Connection for UnixPeerCredConnection {
138+ fn endpoint ( & self ) -> io:: Result < rocket:: listener:: Endpoint > {
139+ Ok ( self . endpoint . clone ( ) )
140+ }
141+ }
142+
143+ #[ cfg( unix) ]
144+ impl < L > Listener for UnixPeerCredListener < L >
145+ where
146+ L : Listener < Accept = UnixStream , Connection = UnixStream > ,
147+ {
148+ type Accept = UnixStream ;
149+ type Connection = UnixPeerCredConnection ;
150+
151+ async fn accept ( & self ) -> io:: Result < Self :: Accept > {
152+ self . inner . accept ( ) . await
153+ }
154+
155+ async fn connect ( & self , accept : Self :: Accept ) -> io:: Result < Self :: Connection > {
156+ let path = accept
157+ . local_addr ( ) ?
158+ . as_pathname ( )
159+ . map ( PathBuf :: from)
160+ . or_else ( || {
161+ self . inner
162+ . endpoint ( )
163+ . ok ( )
164+ . and_then ( |e| e. unix ( ) . map ( PathBuf :: from) )
165+ } ) ;
166+
167+ let endpoint = match path {
168+ Some ( path) => rocket:: listener:: Endpoint :: new ( UnixPeerEndpoint {
169+ path,
170+ peer : unix_peer_cred ( & accept) ,
171+ } ) ,
172+ None => accept. local_addr ( ) ?. try_into ( ) ?,
173+ } ;
174+
175+ Ok ( UnixPeerCredConnection {
176+ stream : accept,
177+ endpoint,
178+ } )
179+ }
180+
181+ fn endpoint ( & self ) -> io:: Result < rocket:: listener:: Endpoint > {
182+ self . inner . endpoint ( )
183+ }
184+ }
185+
186+ #[ cfg( unix) ]
187+ fn unix_peer_cred ( stream : & UnixStream ) -> Option < UnixPeerCred > {
188+ let cred = stream. peer_cred ( ) . ok ( ) ?;
189+ let pid = cred. pid ( ) ?;
190+ Some ( UnixPeerCred {
191+ pid : pid as u64 ,
192+ uid : cred. uid ( ) as u64 ,
193+ gid : cred. gid ( ) as u64 ,
194+ } )
195+ }
196+
51197#[ derive( Debug , Clone ) ]
52198pub struct QuoteVerifier {
53199 pccs_url : Option < String > ,
@@ -266,6 +412,27 @@ impl From<Endpoint> for RemoteEndpoint {
266412 Endpoint :: Tcp ( addr) => RemoteEndpoint :: Tcp ( addr) ,
267413 Endpoint :: Quic ( addr) => RemoteEndpoint :: Quic ( addr) ,
268414 Endpoint :: Unix ( path) => RemoteEndpoint :: Unix { path, peer : None } ,
415+ #[ cfg( unix) ]
416+ Endpoint :: Custom ( endpoint) => {
417+ if let Some ( endpoint) =
418+ ( endpoint. as_ref ( ) as & dyn std:: any:: Any ) . downcast_ref :: < UnixPeerEndpoint > ( )
419+ {
420+ RemoteEndpoint :: Unix {
421+ path : endpoint. path . clone ( ) ,
422+ peer : endpoint. peer . clone ( ) ,
423+ }
424+ } else {
425+ let address = endpoint. to_string ( ) ;
426+ match address. parse :: < VsockEndpoint > ( ) {
427+ Ok ( addr) => RemoteEndpoint :: Vsock {
428+ cid : addr. cid ,
429+ port : addr. port ,
430+ } ,
431+ Err ( _) => RemoteEndpoint :: Other ( address) ,
432+ }
433+ }
434+ }
435+ Endpoint :: Tls ( inner, _) => RemoteEndpoint :: from ( ( * inner) . clone ( ) ) ,
269436 _ => {
270437 let address = endpoint. to_string ( ) ;
271438 match address. parse :: < VsockEndpoint > ( ) {
@@ -280,6 +447,74 @@ impl From<Endpoint> for RemoteEndpoint {
280447 }
281448}
282449
450+ #[ cfg( all( test, unix) ) ]
451+ mod tests {
452+ use super :: * ;
453+ use rocket:: listener:: unix:: UnixListener ;
454+ use rocket:: tokio;
455+ use std:: time:: { SystemTime , UNIX_EPOCH } ;
456+
457+ #[ test]
458+ fn custom_unix_endpoint_maps_to_remote_endpoint ( ) {
459+ let endpoint = Endpoint :: new ( UnixPeerEndpoint {
460+ path : PathBuf :: from ( "/tmp/test.sock" ) ,
461+ peer : Some ( UnixPeerCred {
462+ pid : 1 ,
463+ uid : 2 ,
464+ gid : 3 ,
465+ } ) ,
466+ } ) ;
467+
468+ let remote = RemoteEndpoint :: from ( endpoint) ;
469+ assert_eq ! (
470+ remote,
471+ RemoteEndpoint :: Unix {
472+ path: PathBuf :: from( "/tmp/test.sock" ) ,
473+ peer: Some ( UnixPeerCred {
474+ pid: 1 ,
475+ uid: 2 ,
476+ gid: 3 ,
477+ } ) ,
478+ }
479+ ) ;
480+ }
481+
482+ #[ tokio:: test]
483+ async fn unix_peer_cred_listener_populates_peer ( ) {
484+ let unique = SystemTime :: now ( )
485+ . duration_since ( UNIX_EPOCH )
486+ . unwrap ( )
487+ . as_nanos ( ) ;
488+ let path = std:: env:: temp_dir ( ) . join ( format ! ( "ra-rpc-peer-{unique}.sock" ) ) ;
489+
490+ let listener = UnixListener :: bind ( & path, false ) . await . unwrap ( ) ;
491+ let listener = UnixPeerCredListener :: new ( listener) ;
492+
493+ let client = tokio:: spawn ( {
494+ let path = path. clone ( ) ;
495+ async move { tokio:: net:: UnixStream :: connect ( path) . await }
496+ } ) ;
497+ let accepted = listener. accept ( ) . await . unwrap ( ) ;
498+ let _client = client. await . unwrap ( ) . unwrap ( ) ;
499+ let conn = listener. connect ( accepted) . await . unwrap ( ) ;
500+
501+ let remote = RemoteEndpoint :: from ( conn. endpoint ( ) . unwrap ( ) ) ;
502+ match remote {
503+ RemoteEndpoint :: Unix {
504+ path : got_path,
505+ peer,
506+ } => {
507+ assert_eq ! ( got_path, path) ;
508+ let peer = peer. expect ( "expected unix peer credentials" ) ;
509+ assert_eq ! ( peer. pid, std:: process:: id( ) as u64 ) ;
510+ }
511+ other => panic ! ( "unexpected remote endpoint: {other:?}" ) ,
512+ }
513+
514+ let _ = std:: fs:: remove_file ( path) ;
515+ }
516+ }
517+
283518pub async fn handle_prpc_impl < S , Call : RpcCall < S > > (
284519 args : PrpcHandler < ' _ , ' _ , S > ,
285520) -> Result < RpcResponse > {
0 commit comments