Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/test-examples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,10 @@ jobs:
memory: "512Mi"
args: "--net -- /urllib_get.py"
expect: "SUCCESS: urllib GET worked!"
- example: networking-py
memory: "512Mi"
args: "--port 8080 -- /echo_server_test.py"
expect: "SUCCESS: bind\\+listen on port 8080 allowed"
steps:
- uses: actions/checkout@v4

Expand Down Expand Up @@ -569,6 +573,10 @@ jobs:
memory: "512Mi"
args: "--net -- /urllib_get.py"
expect: "SUCCESS: urllib GET worked!"
- example: networking-py
memory: "512Mi"
args: "--port 8080 -- /echo_server_test.py"
expect: "SUCCESS: bind\\+listen on port 8080 allowed"
steps:
- uses: actions/checkout@v4

Expand Down
1 change: 1 addition & 0 deletions examples/networking-py/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ COPY http_get.py /http_get.py
COPY echo_server.py /echo_server.py
COPY urllib_get.py /urllib_get.py
COPY https_test.py /https_test.py
COPY echo_server_test.py /echo_server_test.py

# --- CPIO rootfs builder ---
FROM alpine:3.20 AS cpio
Expand Down
4 changes: 2 additions & 2 deletions examples/networking-py/Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ run-urllib:
run-https:
hyperlight-unikraft {{kernel}} --initrd {{initrd}} --memory {{memory}} --net -- /https_test.py

# Run echo server
# Run echo server (--port implies --net)
run-echo:
hyperlight-unikraft {{kernel}} --initrd {{initrd}} --memory {{memory}} --net -- /echo_server.py
hyperlight-unikraft {{kernel}} --initrd {{initrd}} --memory {{memory}} --port 8080 -- /echo_server.py

# Build rootfs via Docker (cross-platform)
rootfs:
Expand Down
17 changes: 17 additions & 0 deletions examples/networking-py/echo_server_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""Test that --port allows bind+listen and no --port would reject it.

This is a run-to-completion test for CI: it binds, listens, then exits.
The fact that bind+listen succeed (instead of raising OSError) proves
the --port allowlist is working.
"""
import socket

HOST = "0.0.0.0"
PORT = 8080

srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind((HOST, PORT))
srv.listen(1)
srv.close()
print("SUCCESS: bind+listen on port 8080 allowed")
2 changes: 1 addition & 1 deletion host/examples/pyhl_as_library.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ fn main() -> anyhow::Result<()> {
// expose host directories via the guest's hostfs.
let mounts: &[Preopen] = &[];

let mut rt = pyhl::Runtime::new(&home, mounts, None)?;
let mut rt = pyhl::Runtime::new(&home, mounts, None, None)?;

eprintln!("-- first run (hermetic from loaded snapshot) --");
let t1 = rt.run_code(&code)?;
Expand Down
45 changes: 41 additions & 4 deletions host/src/bin/pyhl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use hyperlight_unikraft::pyhl::{
copy_replace, discover_source_artifacts, extract_from_ghcr, GHCR_INITRD_IMAGE,
GHCR_KERNEL_IMAGE,
};
use hyperlight_unikraft::{AllowList, BlockList, NetworkPolicy, Preopen, Sandbox};
use hyperlight_unikraft::{AllowList, BlockList, ListenPorts, NetworkPolicy, Preopen, Sandbox};
use std::fs;
use std::path::{Path, PathBuf};
use std::time::Instant;
Expand All @@ -41,6 +41,7 @@ fn build_network_policy(
net: bool,
net_allow: &[String],
net_block: &[String],
has_ports: bool,
) -> Result<Option<NetworkPolicy>> {
if !net_allow.is_empty() {
Ok(Some(NetworkPolicy::AllowList(AllowList::from_hosts(
Expand All @@ -50,13 +51,21 @@ fn build_network_policy(
Ok(Some(NetworkPolicy::BlockList(BlockList::from_hosts(
net_block,
)?)))
} else if net {
} else if net || has_ports {
Ok(Some(NetworkPolicy::AllowAll))
} else {
Ok(None)
}
}

fn build_listen_ports(ports: &[u16]) -> Option<ListenPorts> {
if ports.is_empty() {
None
} else {
Some(ListenPorts::from_ports(ports.iter().copied()))
}
}

/// Keep in sync with `py_initialize_once` in examples/python-agent-driver/
/// hl_pydriver.c. These modules are imported during `pyhl setup`'s warmup
/// so they're already in `sys.modules` in every `pyhl run` invocation —
Expand Down Expand Up @@ -187,6 +196,12 @@ struct SetupArgs {
conflicts_with = "net_allow"
)]
net_block: Vec<String>,

/// Allow the guest to bind (listen) on the given port. Implies --net.
/// Without this flag, `net_bind` is rejected (outbound-only).
/// Repeatable: `--port 8080 --port 3000`.
#[arg(long, value_name = "PORT")]
port: Vec<u16>,
}

#[derive(Args)]
Expand Down Expand Up @@ -236,6 +251,12 @@ struct RunArgs {
)]
net_block: Vec<String>,

/// Allow the guest to bind (listen) on the given port. Implies --net.
/// Without this flag, `net_bind` is rejected (outbound-only).
/// Repeatable: `--port 8080 --port 3000`.
#[arg(long, value_name = "PORT")]
port: Vec<u16>,

/// Print evolve/warmup/per-run timing to stderr. Off by default so the
/// user's script output is clean.
#[arg(short = 'v', long = "verbose")]
Expand Down Expand Up @@ -393,7 +414,13 @@ fn cmd_setup(args: SetupArgs) -> Result<()> {
.iter()
.map(|m| parse_mount(m))
.collect::<Result<_>>()?;
let network = build_network_policy(args.net, &args.net_allow, &args.net_block)?;
let listen_ports = build_listen_ports(&args.port);
let network = build_network_policy(
args.net,
&args.net_allow,
&args.net_block,
listen_ports.is_some(),
)?;

eprintln!("pyhl: warming up Python and persisting snapshot…");
let t_warm = Instant::now();
Expand All @@ -407,6 +434,9 @@ fn cmd_setup(args: SetupArgs) -> Result<()> {
if let Some(ref policy) = network {
builder = builder.network(policy.clone());
}
if let Some(ref lp) = listen_ports {
builder = builder.listen_ports(lp.clone());
}
let mut sbox = builder.build()?;
sbox.restore()?;
let _: () = sbox.call_named("run", "pass".to_string())?;
Expand Down Expand Up @@ -531,7 +561,13 @@ fn cmd_run(args: RunArgs) -> Result<()> {
.iter()
.map(|m| parse_mount(m))
.collect::<Result<_>>()?;
let network = build_network_policy(args.net, &args.net_allow, &args.net_block)?;
let listen_ports = build_listen_ports(&args.port);
let network = build_network_policy(
args.net,
&args.net_allow,
&args.net_block,
listen_ports.is_some(),
)?;

let initrd = home.join(INITRD_FILE);

Expand All @@ -546,6 +582,7 @@ fn cmd_run(args: RunArgs) -> Result<()> {
&run_preopens,
initrd_ref,
network.as_ref(),
listen_ports.as_ref(),
)?;
if args.verbose {
eprintln!(
Expand Down
Loading
Loading