@@ -28,6 +28,7 @@ use tokio_util::sync::CancellationToken;
2828use crate :: {
2929 config:: { FabricHome , Peer , PeerBook , load_or_create_identity, validate_protocol} ,
3030 control:: { ControlRequest , ControlResponse , PeerReachability } ,
31+ shell,
3132} ;
3233
3334const BUILTIN_ECHO_ALPN : & [ u8 ] = b"fabric/echo/0" ;
@@ -64,18 +65,23 @@ pub struct DaemonState {
6465 exposures : RwLock < HashMap < Vec < u8 > , PathBuf > > ,
6566 dial_sockets : Mutex < HashMap < ( String , String ) , PathBuf > > ,
6667 builtin_echo_hits : AtomicUsize ,
68+ allow_shell : bool ,
6769 cancel : CancellationToken ,
6870}
6971
7072impl DaemonState {
71- async fn new ( home : FabricHome , cancel : CancellationToken ) -> Result < Arc < Self > > {
73+ async fn new (
74+ home : FabricHome ,
75+ cancel : CancellationToken ,
76+ allow_shell : bool ,
77+ ) -> Result < Arc < Self > > {
7278 home. prepare ( ) ?;
7379 let secret_key = load_or_create_identity ( & home) ?;
7480 let peer_book = PeerBook :: load ( & home) ?;
7581 let allowed = Arc :: new ( RwLock :: new ( peer_book. trusted_ids ( ) ) ) ;
7682 let endpoint = Endpoint :: builder ( presets:: N0 )
7783 . secret_key ( secret_key)
78- . alpns ( vec ! [ BUILTIN_ECHO_ALPN . to_vec ( ) ] )
84+ . alpns ( accepted_alpns ( & HashMap :: new ( ) ) )
7985 . hooks ( AllowListHook {
8086 allowed : allowed. clone ( ) ,
8187 } )
@@ -92,6 +98,7 @@ impl DaemonState {
9298 exposures : RwLock :: new ( HashMap :: new ( ) ) ,
9399 dial_sockets : Mutex :: new ( HashMap :: new ( ) ) ,
94100 builtin_echo_hits : AtomicUsize :: new ( 0 ) ,
101+ allow_shell,
95102 cancel,
96103 } ) )
97104 }
@@ -113,8 +120,8 @@ impl DaemonState {
113120
114121 pub async fn expose ( & self , protocol : & str , socket : PathBuf ) -> Result < ( ) > {
115122 let alpn = validate_protocol ( protocol) ?;
116- if alpn == BUILTIN_ECHO_ALPN {
117- bail ! ( "{protocol:?} is reserved for fabric's built-in echo protocol " ) ;
123+ if matches_reserved_alpn ( & alpn) {
124+ bail ! ( "{protocol:?} is reserved for fabric's built-in protocols " ) ;
118125 }
119126 if !socket. is_absolute ( ) {
120127 bail ! ( "expose socket must be an absolute path" ) ;
@@ -170,6 +177,10 @@ impl DaemonState {
170177
171178 pub async fn dial ( & self , peer : & str , protocol : & str ) -> Result < PathBuf > {
172179 let alpn = validate_protocol ( protocol) ?;
180+ self . dial_alpn ( peer, protocol, alpn) . await
181+ }
182+
183+ async fn dial_alpn ( & self , peer : & str , protocol : & str , alpn : Vec < u8 > ) -> Result < PathBuf > {
173184 let peer_addr = self . peer_book . read ( ) . await . resolve ( peer) ?;
174185 let key = ( peer_addr. id . to_string ( ) , protocol. to_string ( ) ) ;
175186
@@ -314,8 +325,12 @@ pub struct FabricNode {
314325
315326impl FabricNode {
316327 pub async fn start ( home : FabricHome ) -> Result < Self > {
328+ Self :: start_with_options ( home, false ) . await
329+ }
330+
331+ pub async fn start_with_options ( home : FabricHome , allow_shell : bool ) -> Result < Self > {
317332 let cancel = CancellationToken :: new ( ) ;
318- let state = DaemonState :: new ( home, cancel) . await ?;
333+ let state = DaemonState :: new ( home, cancel, allow_shell ) . await ?;
319334 let task = tokio:: spawn ( serve ( state. clone ( ) ) ) ;
320335 Ok ( Self { state, task } )
321336 }
@@ -354,8 +369,11 @@ impl FabricNode {
354369 }
355370}
356371
357- pub async fn run_daemon ( home : FabricHome ) -> Result < ( ) > {
358- FabricNode :: start ( home) . await ?. wait ( ) . await
372+ pub async fn run_daemon ( home : FabricHome , allow_shell : bool ) -> Result < ( ) > {
373+ FabricNode :: start_with_options ( home, allow_shell)
374+ . await ?
375+ . wait ( )
376+ . await
359377}
360378
361379pub async fn send_control ( home : & FabricHome , request : ControlRequest ) -> Result < ControlResponse > {
@@ -464,6 +482,12 @@ async fn process_control_request(
464482 transport : pong. transport ,
465483 }
466484 }
485+ ControlRequest :: Shell { peer } => {
486+ let socket = state
487+ . dial_alpn ( & peer, shell:: SHELL_PROTOCOL , shell:: SHELL_ALPN . to_vec ( ) )
488+ . await ?;
489+ ControlResponse :: Shell { socket }
490+ }
467491 ControlRequest :: Shutdown => {
468492 state. cancel . cancel ( ) ;
469493 ControlResponse :: Ok
@@ -501,6 +525,11 @@ async fn process_incoming_iroh(incoming: Incoming, state: Arc<DaemonState>) -> R
501525 handle_builtin_echo ( connection, state) . await ?;
502526 return Ok ( ( ) ) ;
503527 }
528+ if alpn == shell:: SHELL_ALPN {
529+ let connection = accepting. await ?;
530+ handle_builtin_shell ( connection, state) . await ?;
531+ return Ok ( ( ) ) ;
532+ }
504533
505534 let socket = {
506535 let exposures = state. exposures . read ( ) . await ;
@@ -528,13 +557,30 @@ async fn handle_builtin_echo(connection: Connection, state: Arc<DaemonState>) ->
528557 Ok ( ( ) )
529558}
530559
560+ async fn handle_builtin_shell ( connection : Connection , state : Arc < DaemonState > ) -> Result < ( ) > {
561+ let ( mut send, mut recv) = connection. accept_bi ( ) . await ?;
562+ if state. allow_shell {
563+ shell:: serve_shell_session ( & mut recv, & mut send) . await ?;
564+ } else {
565+ shell:: serve_shell_disabled ( & mut send) . await ?;
566+ }
567+ send. finish ( ) ?;
568+ connection. closed ( ) . await ;
569+ Ok ( ( ) )
570+ }
571+
531572fn accepted_alpns ( exposures : & HashMap < Vec < u8 > , PathBuf > ) -> Vec < Vec < u8 > > {
532- let mut alpns = Vec :: with_capacity ( exposures. len ( ) + 1 ) ;
573+ let mut alpns = Vec :: with_capacity ( exposures. len ( ) + 2 ) ;
533574 alpns. push ( BUILTIN_ECHO_ALPN . to_vec ( ) ) ;
575+ alpns. push ( shell:: SHELL_ALPN . to_vec ( ) ) ;
534576 alpns. extend ( exposures. keys ( ) . cloned ( ) ) ;
535577 alpns
536578}
537579
580+ fn matches_reserved_alpn ( alpn : & [ u8 ] ) -> bool {
581+ alpn == BUILTIN_ECHO_ALPN || alpn == shell:: SHELL_ALPN
582+ }
583+
538584fn classify_connection_transport ( connection : & Connection ) -> Option < String > {
539585 let paths = connection. paths ( ) ;
540586 let mut selected_ip = false ;
0 commit comments