@@ -4,19 +4,27 @@ use crate::helpers::OutputLimitedBuffer;
44use crate :: ruby_api:: convert:: ToValType ;
55use crate :: { define_rb_intern, helpers:: SymbolEnum } ;
66use lazy_static:: lazy_static;
7+ use magnus:: block:: Proc ;
8+ use magnus:: value:: ReprValue ;
79use magnus:: {
810 class, function, gc:: Marker , method, typed_data:: Obj , value:: Opaque , DataTypeFunctions , Error ,
911 IntoValue , Module , Object , RArray , RHash , RString , Ruby , Symbol , TryConvert , TypedData , Value ,
1012} ;
1113use rb_sys:: ruby_rarray_flags:: RARRAY_EMBED_FLAG ;
14+ use rb_sys:: VALUE ;
1215use std:: cell:: RefCell ;
1316use std:: convert:: TryFrom ;
1417use std:: fs;
18+ use std:: future:: Future ;
19+ use std:: net:: SocketAddr ;
1520use std:: path:: Path ;
21+ use std:: pin:: Pin ;
22+ use std:: sync:: Arc ;
1623use std:: { fs:: File , path:: PathBuf } ;
1724use wasmtime_wasi:: cli:: { InputFile , OutputFile } ;
1825use wasmtime_wasi:: p1:: WasiP1Ctx ;
1926use wasmtime_wasi:: p2:: pipe:: MemoryInputPipe ;
27+ use wasmtime_wasi:: sockets:: SocketAddrUse ;
2028use wasmtime_wasi:: { DirPerms , FilePerms , WasiCtx , WasiCtxBuilder } ;
2129
2230define_rb_intern ! (
@@ -96,6 +104,43 @@ impl MappedDirectory {
96104 }
97105}
98106
107+ struct SocketAddrProc {
108+ proc : Proc ,
109+ }
110+
111+ impl SocketAddrProc {
112+ fn call ( & self , addr : SocketAddr , use_ : SocketAddrUse ) -> bool {
113+ let ruby = Ruby :: get ( ) . unwrap ( ) ;
114+
115+ // Convert arguments to Ruby values
116+ let addr_str = ruby. str_new ( & addr. to_string ( ) ) ;
117+ let use_sym = socket_addr_use_to_symbol ( & ruby, use_) ;
118+
119+ match self . proc . call :: < _ , Value > ( ( addr_str, use_sym) ) {
120+ Ok ( result) => bool:: try_convert ( result) . unwrap_or ( false ) ,
121+ Err ( _) => {
122+ // Exception in Ruby block, deny access
123+ false
124+ }
125+ }
126+ }
127+ }
128+
129+ // SAFETY: We only access the Ruby proc when we have the GVL (during WASI operations).
130+ // The Proc is kept alive by the Store's refs field, which is marked during GC.
131+ unsafe impl Send for SocketAddrProc { }
132+ unsafe impl Sync for SocketAddrProc { }
133+
134+ fn socket_addr_use_to_symbol ( ruby : & Ruby , use_ : SocketAddrUse ) -> Symbol {
135+ match use_ {
136+ SocketAddrUse :: TcpBind => ruby. to_symbol ( "tcp_bind" ) ,
137+ SocketAddrUse :: TcpConnect => ruby. to_symbol ( "tcp_connect" ) ,
138+ SocketAddrUse :: UdpBind => ruby. to_symbol ( "udp_bind" ) ,
139+ SocketAddrUse :: UdpConnect => ruby. to_symbol ( "udp_connect" ) ,
140+ SocketAddrUse :: UdpOutgoingDatagram => ruby. to_symbol ( "udp_outgoing_datagram" ) ,
141+ }
142+ }
143+
99144#[ derive( Default ) ]
100145struct WasiConfigInner {
101146 stdin : Option < ReadStream > ,
@@ -109,6 +154,7 @@ struct WasiConfigInner {
109154 allow_tcp : Option < bool > ,
110155 allow_udp : Option < bool > ,
111156 allow_ip_name_lookup : Option < bool > ,
157+ socket_addr_check : Option < Opaque < Proc > > ,
112158}
113159
114160impl WasiConfigInner {
@@ -131,6 +177,9 @@ impl WasiConfigInner {
131177 for v in & self . mapped_directories {
132178 v. mark ( marker) ;
133179 }
180+ if let Some ( v) = self . socket_addr_check . as_ref ( ) {
181+ marker. mark ( * v) ;
182+ }
134183 }
135184}
136185
@@ -384,21 +433,47 @@ impl WasiConfig {
384433 rb_self
385434 }
386435
387- pub fn build_p1 ( & self , ruby : & Ruby ) -> Result < WasiP1Ctx , Error > {
388- let mut builder = self . build_impl ( ruby) ?;
436+ /// @yard
437+ /// Set a custom check function for socket address access control.
438+ /// The block will be called for each socket operation with the socket address (as a String)
439+ /// and the operation type (as a Symbol: :tcp_bind, :tcp_connect, :udp_bind, :udp_connect,
440+ /// :udp_outgoing_datagram).
441+ /// The block should return true to allow the operation or false to deny it.
442+ /// If the block raises an exception, the operation will be denied.
443+ ///
444+ /// Note: any network access happens while the Global VM Lock (GVL) is held, so other
445+ /// threads will be blocked in the meantime.
446+ ///
447+ /// @yieldparam addr [String] The socket address (e.g., "127.0.0.1:8080")
448+ /// @yieldparam use [Symbol] The type of socket operation
449+ /// @yieldreturn [Boolean] true to allow the operation, false to deny it
450+ /// @def socket_addr_check
451+ /// @return [WasiConfig] +self+
452+ pub fn socket_addr_check ( ruby : & Ruby , rb_self : RbSelf ) -> RbSelf {
453+ if ruby. block_given ( ) {
454+ let proc = ruby. block_proc ( ) . unwrap ( ) ;
455+ let mut inner = rb_self. inner . borrow_mut ( ) ;
456+ inner. socket_addr_check = Some ( proc. into ( ) ) ;
457+ }
458+ rb_self
459+ }
460+
461+ pub fn build_p1 ( & self , ruby : & Ruby ) -> Result < ( WasiP1Ctx , Option < Value > ) , Error > {
462+ let ( mut builder, proc_value) = self . build_impl ( ruby) ?;
389463 let ctx = builder. build_p1 ( ) ;
390- Ok ( ctx)
464+ Ok ( ( ctx, proc_value ) )
391465 }
392466
393- pub fn build ( & self , ruby : & Ruby ) -> Result < WasiCtx , Error > {
394- let mut builder = self . build_impl ( ruby) ?;
467+ pub fn build ( & self , ruby : & Ruby ) -> Result < ( WasiCtx , Option < Value > ) , Error > {
468+ let ( mut builder, proc_value ) = self . build_impl ( ruby) ?;
395469 let ctx = builder. build ( ) ;
396- Ok ( ctx)
470+ Ok ( ( ctx, proc_value ) )
397471 }
398472
399- fn build_impl ( & self , ruby : & Ruby ) -> Result < WasiCtxBuilder , Error > {
473+ fn build_impl ( & self , ruby : & Ruby ) -> Result < ( WasiCtxBuilder , Option < Value > ) , Error > {
400474 let mut builder = WasiCtxBuilder :: new ( ) ;
401475 let inner = self . inner . borrow ( ) ;
476+ let mut proc_to_retain = None ;
402477
403478 if let Some ( stdin) = inner. stdin . as_ref ( ) {
404479 match stdin {
@@ -487,6 +562,20 @@ impl WasiConfig {
487562 }
488563 }
489564
565+ if let Some ( check_proc) = inner. socket_addr_check . as_ref ( ) {
566+ let proc = ruby. get_inner ( * check_proc) ;
567+ let socket_addr_proc = Arc :: new ( SocketAddrProc { proc } ) ;
568+
569+ builder. socket_addr_check ( move |addr, use_| {
570+ let socket_addr_proc = socket_addr_proc. clone ( ) ;
571+ Box :: pin ( async move { socket_addr_proc. call ( addr, use_) } )
572+ as Pin < Box < dyn Future < Output = bool > + Send + Sync > >
573+ } ) ;
574+
575+ // Store the Proc as a Value so the Store can retain it
576+ proc_to_retain = Some ( proc. as_value ( ) ) ;
577+ }
578+
490579 for mapped_dir in & inner. mapped_directories {
491580 let host_path = ruby. get_inner ( mapped_dir. host_path ) . to_string ( ) ?;
492581 let guest_path = ruby. get_inner ( mapped_dir. guest_path ) . to_string ( ) ?;
@@ -503,7 +592,7 @@ impl WasiConfig {
503592 . map_err ( |e| error ! ( "{}" , e) ) ?;
504593 }
505594
506- Ok ( builder)
595+ Ok ( ( builder, proc_to_retain ) )
507596 }
508597}
509598
@@ -558,6 +647,10 @@ pub fn init(ruby: &Ruby) -> Result<(), Error> {
558647 "allow_ip_name_lookup" ,
559648 method ! ( WasiConfig :: allow_ip_name_lookup, 1 ) ,
560649 ) ?;
650+ class. define_method (
651+ "socket_addr_check" ,
652+ method ! ( WasiConfig :: socket_addr_check, 0 ) ,
653+ ) ?;
561654
562655 Ok ( ( ) )
563656}
0 commit comments