@@ -26,7 +26,7 @@ use hyperlight_unikraft::pyhl::{
2626 copy_replace, discover_source_artifacts, extract_from_ghcr, GHCR_INITRD_IMAGE ,
2727 GHCR_KERNEL_IMAGE ,
2828} ;
29- use hyperlight_unikraft:: { Preopen , Sandbox } ;
29+ use hyperlight_unikraft:: { AllowList , NetworkPolicy , Preopen , Sandbox } ;
3030use std:: fs;
3131use std:: path:: { Path , PathBuf } ;
3232use std:: time:: Instant ;
@@ -37,6 +37,16 @@ fn parse_mount(spec: &str) -> Result<Preopen> {
3737 Preopen :: parse_cli ( spec) . map_err ( |e| anyhow ! ( "invalid --mount {:?}: {}" , spec, e) )
3838}
3939
40+ fn build_network_policy ( net : bool , net_allow : & [ String ] ) -> Result < Option < NetworkPolicy > > {
41+ if !net_allow. is_empty ( ) {
42+ Ok ( Some ( NetworkPolicy :: AllowList ( AllowList :: from_hosts ( net_allow) ?) ) )
43+ } else if net {
44+ Ok ( Some ( NetworkPolicy :: AllowAll ) )
45+ } else {
46+ Ok ( None )
47+ }
48+ }
49+
4050/// Keep in sync with `py_initialize_once` in examples/python-agent-driver/
4151/// hl_pydriver.c. These modules are imported during `pyhl setup`'s warmup
4252/// so they're already in `sys.modules` in every `pyhl run` invocation —
@@ -145,6 +155,15 @@ struct SetupArgs {
145155 /// given to `setup`.
146156 #[ arg( long = "mount" , value_name = "HOST[:GUEST]" ) ]
147157 mounts : Vec < String > ,
158+
159+ /// Enable guest networking.
160+ #[ arg( long) ]
161+ net : bool ,
162+
163+ /// Restrict guest networking to the listed hosts/IPs.
164+ /// Implies --net. Repeatable.
165+ #[ arg( long = "net-allow" , value_name = "HOST_OR_IP" ) ]
166+ net_allow : Vec < String > ,
148167}
149168
150169#[ derive( Args ) ]
@@ -172,6 +191,15 @@ struct RunArgs {
172191 #[ arg( long = "mount" , value_name = "HOST[:GUEST]" ) ]
173192 mounts : Vec < String > ,
174193
194+ /// Enable guest networking.
195+ #[ arg( long) ]
196+ net : bool ,
197+
198+ /// Restrict guest networking to the listed hosts/IPs.
199+ /// Implies --net. Repeatable.
200+ #[ arg( long = "net-allow" , value_name = "HOST_OR_IP" ) ]
201+ net_allow : Vec < String > ,
202+
175203 /// Print evolve/warmup/per-run timing to stderr. Off by default so the
176204 /// user's script output is clean.
177205 #[ arg( short = 'v' , long = "verbose" ) ]
@@ -329,6 +357,7 @@ fn cmd_setup(args: SetupArgs) -> Result<()> {
329357 . iter ( )
330358 . map ( |m| parse_mount ( m) )
331359 . collect :: < Result < _ > > ( ) ?;
360+ let network = build_network_policy ( args. net , & args. net_allow ) ?;
332361
333362 eprintln ! ( "pyhl: warming up Python and persisting snapshot…" ) ;
334363 let t_warm = Instant :: now ( ) ;
@@ -339,6 +368,9 @@ fn cmd_setup(args: SetupArgs) -> Result<()> {
339368 for p in & setup_preopens {
340369 builder = builder. preopen ( p. clone ( ) ) ;
341370 }
371+ if let Some ( ref policy) = network {
372+ builder = builder. network ( policy. clone ( ) ) ;
373+ }
342374 let mut sbox = builder. build ( ) ?;
343375 sbox. restore ( ) ?;
344376 let _: ( ) = sbox. call_named ( "run" , "pass" . to_string ( ) ) ?;
@@ -463,17 +495,22 @@ fn cmd_run(args: RunArgs) -> Result<()> {
463495 . iter ( )
464496 . map ( |m| parse_mount ( m) )
465497 . collect :: < Result < _ > > ( ) ?;
498+ let network = build_network_policy ( args. net , & args. net_allow ) ?;
466499
467500 let initrd = home. join ( INITRD_FILE ) ;
468501
469502 let t_load = Instant :: now ( ) ;
470- let mut sandbox = if initrd. is_file ( ) {
471- Sandbox :: from_snapshot_file_with_initrd ( & snapshot, & run_preopens, & initrd) ?
472- } else if run_preopens. is_empty ( ) {
473- Sandbox :: from_snapshot_file ( & snapshot) ?
503+ let initrd_ref = if initrd. is_file ( ) {
504+ Some ( initrd. as_path ( ) )
474505 } else {
475- Sandbox :: from_snapshot_file_with ( & snapshot , & run_preopens ) ?
506+ None
476507 } ;
508+ let mut sandbox = Sandbox :: from_snapshot_file_configured (
509+ & snapshot,
510+ & run_preopens,
511+ initrd_ref,
512+ network. as_ref ( ) ,
513+ ) ?;
477514 if args. verbose {
478515 eprintln ! (
479516 "[pyhl] load_snapshot={:.1}ms" ,
0 commit comments