diff --git a/litebox_platform_windows_userland/Cargo.toml b/litebox_platform_windows_userland/Cargo.toml index 2e827d498..c4d3f3b8f 100644 --- a/litebox_platform_windows_userland/Cargo.toml +++ b/litebox_platform_windows_userland/Cargo.toml @@ -7,6 +7,8 @@ edition = "2024" litebox = { path = "../litebox/", version = "0.1.0" } litebox_common_linux = { path = "../litebox_common_linux", version = "0.1.0" } +[target.'cfg(windows)'.dependencies] +wintun = "0.5.1" windows-sys = { version = "0.60.2", features = [ "Win32_Foundation", "Win32_System_Memory", "Win32_System_Threading", "Win32_System_SystemInformation", "Win32_System_Performance", "Win32_Security", "Win32_System_Diagnostics", "Win32_System_Kernel", "Win32_System_Diagnostics_Debug" ] } [lints] diff --git a/litebox_platform_windows_userland/src/lib.rs b/litebox_platform_windows_userland/src/lib.rs index e4f9189c3..6c8bc7cf2 100644 --- a/litebox_platform_windows_userland/src/lib.rs +++ b/litebox_platform_windows_userland/src/lib.rs @@ -9,6 +9,7 @@ use core::sync::atomic::AtomicU32; use core::time::Duration; use std::cell::Cell; use std::os::raw::c_void; +use std::sync::Arc; use std::sync::atomic::Ordering::SeqCst; use litebox::platform::RawConstPointer; @@ -117,6 +118,8 @@ pub struct WindowsUserland { tls_slot: TlsSlot, reserved_pages: alloc::vec::Vec>, sys_info: std::sync::RwLock, + /// WinTun session for networking + wintun_session: std::sync::RwLock>>, } // Safety: Given that SYSTEM_INFO is not Send/Sync (it contains *mut c_void), we use RwLock to @@ -181,10 +184,13 @@ unsafe extern "system" fn exception_handler(exception_info: *mut EXCEPTION_POINT impl WindowsUserland { /// Create a new userland-Windows platform for use in `LiteBox`. /// + /// Takes an optional WinTun adapter name (such as `"LiteBox"`) to connect networking (if + /// not specified, networking is disabled). + /// /// # Panics /// - /// Panics if the TLS slot cannot be created. - pub fn new() -> &'static Self { + /// Panics if the TLS slot cannot be created or if WinTun adapter cannot be created. + pub fn new(wintun_adapter_name: Option<&str>) -> &'static Self { let mut sys_info = Win32_SysInfo::SYSTEM_INFO::default(); Self::get_system_information(&mut sys_info); @@ -204,10 +210,35 @@ impl WindowsUserland { let reserved_pages = Self::read_memory_maps(); + // Initialize WinTun if adapter name is provided + let wintun_session = wintun_adapter_name.map(|adapter_name| { + // Load the WinTun library from the system + let wintun = unsafe { wintun::load().expect("Failed to load WinTun library") }; + + // Try to open an existing adapter, or create a new one + let adapter = match wintun::Adapter::open(&wintun, adapter_name) { + Ok(adapter) => adapter, + Err(_) => { + // If opening failed (adapter doesn't exist), create a new one. + // TODO: The tunnel_type "LiteBox" is a placeholder name for now. + wintun::Adapter::create(&wintun, adapter_name, "LiteBox", None) + .expect("Failed to create WinTun adapter") + } + }; + + // Start a session with maximum ring buffer capacity + Arc::new( + adapter + .start_session(wintun::MAX_RING_CAPACITY) + .expect("Failed to start WinTun session"), + ) + }); + let platform = Self { tls_slot: TlsSlot::new().expect("Failed to create TLS slot!"), reserved_pages, sys_info: std::sync::RwLock::new(sys_info), + wintun_session: std::sync::RwLock::new(wintun_session), }; platform.set_init_tls(); @@ -326,7 +357,15 @@ impl litebox::platform::ExitProvider for WindowsUserland { tls_slot: _, sys_info: _, reserved_pages: _, + wintun_session, } = self; + + // Close WinTun session if it exists + if let Some(session) = wintun_session.write().unwrap().take() { + // The session will automatically close when dropped + drop(session); + } + unsafe { windows_sys::Win32::System::Threading::ExitProcess(code) } } } @@ -547,20 +586,69 @@ impl litebox::platform::RawMutex for RawMutex { impl litebox::platform::IPInterfaceProvider for WindowsUserland { fn send_ip_packet(&self, packet: &[u8]) -> Result<(), litebox::platform::SendError> { - unimplemented!( - "send_ip_packet is not implemented for Windows yet. packet length: {}", - packet.len() - ); + let session_guard = self.wintun_session.read().unwrap(); + let Some(session) = session_guard.as_ref() else { + unimplemented!("networking without WinTun is unimplemented") + }; + + // Allocate a send packet in the WinTun ring buffer + let mut wintun_packet = session + .allocate_send_packet(u16::try_from(packet.len()).unwrap()) + .map_err(|_| { + // Since SendError is an empty enum, we can't create values of it + // We'll panic if allocation fails for now, similar to Linux implementation + panic!("Failed to allocate WinTun send packet") + })?; + + // Copy the IP packet data into the WinTun packet + let packet_bytes = wintun_packet.bytes_mut(); + + assert_eq!(packet_bytes.len(), packet.len()); + packet_bytes[..packet.len()].copy_from_slice(packet); + + // Send the packet through WinTun + session.send_packet(wintun_packet); + + Ok(()) } fn receive_ip_packet( &self, packet: &mut [u8], ) -> Result { - unimplemented!( - "receive_ip_packet is not implemented for Windows yet. packet length: {}", - packet.len() - ); + let session_guard = self.wintun_session.read().unwrap(); + let Some(session) = session_guard.as_ref() else { + unimplemented!("networking without WinTun is unimplemented") + }; + + // Try to receive a packet from WinTun (non-blocking) + session + .try_receive() + .map_err(|_| litebox::platform::ReceiveError::WouldBlock) + .and_then(|maybe_packet| { + match maybe_packet { + Some(wintun_packet) => { + let received_data = wintun_packet.bytes(); + let packet_size = received_data.len(); + + // Ensure the received packet fits in our buffer + if packet_size > packet.len() { + unimplemented!( + "Received packet size {packet_size} exceeds buffer size {}", + packet.len() + ); + } + + // Copy the received data into our buffer + packet[..packet_size].copy_from_slice(received_data); + Ok(packet_size) + } + None => { + // No packet available - return WouldBlock + Err(litebox::platform::ReceiveError::WouldBlock) + } + } + }) } } @@ -1250,7 +1338,7 @@ mod tests { use crate::WindowsUserland; use litebox::platform::PageManagementProvider; - use litebox::platform::{RawMutex, ThreadLocalStorageProvider as _}; + use litebox::platform::{IPInterfaceProvider, RawMutex, ThreadLocalStorageProvider as _}; #[test] fn test_raw_mutex() { @@ -1272,7 +1360,7 @@ mod tests { #[test] fn test_reserved_pages() { - let platform = WindowsUserland::new(); + let platform = WindowsUserland::new(None); let reserved_pages: Vec<_> = >::reserved_pages(platform).collect(); @@ -1290,7 +1378,7 @@ mod tests { #[test] fn test_tls() { - let platform = WindowsUserland::new(); + let platform = WindowsUserland::new(None); let tls = platform.get_thread_local_storage(); assert!(!tls.is_null(), "TLS should not be null"); let tid = unsafe { (*tls).current_task.tid }; @@ -1311,4 +1399,377 @@ mod tests { let tls = platform.get_thread_local_storage(); assert!(tls.is_null(), "TLS should be null after releasing it"); } + + #[test] + fn test_ip_interface_with_wintun() { + // Test IP interface functionality with WinTun enabled + // Note: This test requires Administrator privileges to create network adapters + let platform = WindowsUserland::new(Some("LiteBoxTest")); + + // Create a sample IPv4 packet + let test_packet = create_sample_ipv4_packet(); + + // Test sending a packet + match platform.send_ip_packet(&test_packet) { + Ok(()) => { + println!("Successfully sent IP packet through WinTun"); + } + Err(_) => { + // SendError is an empty enum, so we can't actually get here + // The implementation should either succeed or panic + panic!("Unexpected error from send_ip_packet"); + } + } + + // Test receiving a packet (non-blocking) + let mut receive_buffer = [0u8; 1500]; + match platform.receive_ip_packet(&mut receive_buffer) { + Ok(size) => { + println!("Received IP packet of size: {}", size); + assert!(size > 0, "Received packet should have non-zero size"); + assert!( + size <= receive_buffer.len(), + "Received packet size should not exceed buffer" + ); + } + Err(litebox::platform::ReceiveError::WouldBlock) => { + println!("No packets available to receive (expected for non-blocking operation)"); + } + Err(_) => { + // Handle any other potential errors + println!("Other receive error occurred"); + } + } + + // Test with various packet sizes + for size in [20, 64, 128, 256, 512, 1024, 1400] { + let packet = create_sample_ipv4_packet_with_size(size); + match platform.send_ip_packet(&packet) { + Ok(()) => { + println!("Successfully sent IP packet of size {}", size); + } + Err(_) => panic!("Failed to send packet of size {}", size), + } + } + } + + /// Creates a sample IPv4 packet for testing + fn create_sample_ipv4_packet() -> Vec { + create_sample_ipv4_packet_with_size(64) + } + + /// Creates a sample IPv4 packet with specified total size + fn create_sample_ipv4_packet_with_size(total_size: usize) -> Vec { + let mut packet = vec![0u8; total_size]; + + // IPv4 header (20 bytes minimum) + packet[0] = 0x45; // Version 4, Header length 20 bytes + packet[1] = 0x00; // Type of Service + packet[2] = (total_size >> 8) as u8; // Total Length (high byte) + packet[3] = (total_size & 0xFF) as u8; // Total Length (low byte) + packet[4] = 0x12; // Identification (high byte) + packet[5] = 0x34; // Identification (low byte) + packet[6] = 0x40; // Flags (Don't Fragment) + packet[7] = 0x00; // Fragment Offset + packet[8] = 64; // Time to Live + packet[9] = 0x06; // Protocol: TCP + packet[10] = 0x00; // Header Checksum (high byte) - normally calculated + packet[11] = 0x00; // Header Checksum (low byte) + + // Source IP: 10.0.0.1 + packet[12] = 10; + packet[13] = 0; + packet[14] = 0; + packet[15] = 1; + + // Destination IP: 10.0.0.2 + packet[16] = 10; + packet[17] = 0; + packet[18] = 0; + packet[19] = 2; + + // Fill the rest with test data if packet is larger than header + if total_size > 20 { + for i in 20..total_size { + packet[i] = (i % 256) as u8; + } + } + + packet + } + + #[test] + fn test_ip_packet_size_validation() { + let platform = WindowsUserland::new(None); + + // Test with empty packet + let empty_packet = []; + let _result = std::panic::catch_unwind(|| platform.send_ip_packet(&empty_packet)); + // Should handle empty packet appropriately (panic with unimplemented since no WinTun) + + // Test with maximum size packet + let max_packet = vec![0u8; 65535]; // Maximum IP packet size + let _result = std::panic::catch_unwind(|| platform.send_ip_packet(&max_packet)); + // Should either succeed or fail gracefully (with unimplemented since no WinTun) + // The actual behavior depends on WinTun availability + } + + #[test] + // This test requires Administrator privileges and may need external network activity + fn test_ip_interface_packet_reception() { + use std::thread; + use std::time::Duration; + + // Test IP interface packet reception with WinTun + let platform = WindowsUserland::new(Some("LiteBoxReceiveTest")); + + println!("Testing packet reception with WinTun adapter 'LiteBoxReceiveTest'"); + + // Send multiple packets to increase chance of reception + let test_packets = vec![ + create_ping_packet("10.0.0.1", "10.0.0.2"), + create_ping_packet("10.0.0.1", "8.8.8.8"), + create_tcp_packet("10.0.0.1", "10.0.0.2", 12345, 80), + ]; + + // Send packets + for (i, packet) in test_packets.iter().enumerate() { + match platform.send_ip_packet(packet) { + Ok(()) => println!("Sent test packet {} successfully", i + 1), + Err(_) => println!("Failed to send test packet {}", i + 1), + } + } + + // Try to receive packets multiple times with small delays + let mut received_count = 0; + let max_attempts = 50; + let mut receive_buffer = [0u8; 1500]; + + for attempt in 1..=max_attempts { + match platform.receive_ip_packet(&mut receive_buffer) { + Ok(size) => { + received_count += 1; + println!("Attempt {}: Received packet of {} bytes", attempt, size); + + // Basic validation of received packet + if size >= 20 { + let version = (receive_buffer[0] >> 4) & 0x0F; + let protocol = receive_buffer[9]; + let src_ip = format!( + "{}.{}.{}.{}", + receive_buffer[12], + receive_buffer[13], + receive_buffer[14], + receive_buffer[15] + ); + let dst_ip = format!( + "{}.{}.{}.{}", + receive_buffer[16], + receive_buffer[17], + receive_buffer[18], + receive_buffer[19] + ); + + println!( + " IPv{} packet, protocol {}, {} -> {}", + version, protocol, src_ip, dst_ip + ); + } + } + Err(litebox::platform::ReceiveError::WouldBlock) => { + // No packet available, this is normal for non-blocking operation + } + Err(_) => { + println!("Attempt {}: Other receive error", attempt); + } + } + + // Small delay between attempts + thread::sleep(Duration::from_millis(10)); + } + + println!( + "Total packets received: {} out of {} attempts", + received_count, max_attempts + ); + + // Note: In a real network environment, we might receive packets from other sources + // The test validates that the receive mechanism works, even if we don't receive + // our own sent packets (which is normal for many network configurations) + } + + #[test] + // This test requires Administrator privileges + fn test_ip_interface_bidirectional_with_threading() { + use std::sync::{Arc, Mutex}; + use std::thread; + use std::time::Duration; + + println!("Testing bidirectional IP interface with threading"); + + // Create two platforms with different adapter names for bidirectional testing + let sender_platform = WindowsUserland::new(Some("LiteBoxSender")); + let receiver_platform = WindowsUserland::new(Some("LiteBoxReceiver")); + + let received_packets = Arc::new(Mutex::new(Vec::new())); + let received_packets_clone = received_packets.clone(); + + // Receiver thread + let receiver_handle = thread::spawn(move || { + let mut receive_buffer = [0u8; 1500]; + let mut receive_count = 0; + + // Try to receive packets for 5 seconds + let start_time = std::time::Instant::now(); + while start_time.elapsed() < Duration::from_secs(5) && receive_count < 10 { + match receiver_platform.receive_ip_packet(&mut receive_buffer) { + Ok(size) => { + receive_count += 1; + let packet_data = receive_buffer[..size].to_vec(); + received_packets_clone.lock().unwrap().push(packet_data); + println!("Receiver: Got packet {} of size {}", receive_count, size); + } + Err(litebox::platform::ReceiveError::WouldBlock) => { + // No packet available, continue + } + Err(_) => { + println!("Receiver: Error receiving packet"); + } + } + thread::sleep(Duration::from_millis(50)); + } + + println!( + "Receiver thread finished, received {} packets", + receive_count + ); + }); + + // Sender thread + let sender_handle = thread::spawn(move || { + // Send packets with delays + for i in 0..5 { + let packet = create_sample_ipv4_packet_with_size(64 + i * 10); + match sender_platform.send_ip_packet(&packet) { + Ok(()) => println!("Sender: Sent packet {}", i + 1), + Err(_) => println!("Sender: Failed to send packet {}", i + 1), + } + thread::sleep(Duration::from_millis(200)); + } + }); + + // Wait for both threads + sender_handle.join().unwrap(); + receiver_handle.join().unwrap(); + + let final_received = received_packets.lock().unwrap(); + println!( + "Final result: {} packets captured by receiver", + final_received.len() + ); + + // The test validates that both send and receive operations work + // Even if no packets are received (common in isolated test environments), + // the absence of panics indicates the interface is working correctly + } + + /// Creates a sample ICMP ping packet + fn create_ping_packet(src_ip: &str, dst_ip: &str) -> Vec { + let mut packet = vec![0u8; 64]; // 20 bytes IP header + 8 bytes ICMP header + 36 bytes data + + // IPv4 header + packet[0] = 0x45; // Version 4, Header length 20 bytes + packet[1] = 0x00; // Type of Service + packet[2] = 0x00; // Total Length (high byte) + packet[3] = 64; // Total Length (low byte) + packet[4] = 0x12; // Identification (high byte) + packet[5] = 0x34; // Identification (low byte) + packet[6] = 0x40; // Flags (Don't Fragment) + packet[7] = 0x00; // Fragment Offset + packet[8] = 64; // Time to Live + packet[9] = 0x01; // Protocol: ICMP + packet[10] = 0x00; // Header Checksum (high byte) + packet[11] = 0x00; // Header Checksum (low byte) + + // Parse and set source IP + let src_parts: Vec = src_ip.split('.').map(|s| s.parse().unwrap()).collect(); + packet[12..16].copy_from_slice(&src_parts); + + // Parse and set destination IP + let dst_parts: Vec = dst_ip.split('.').map(|s| s.parse().unwrap()).collect(); + packet[16..20].copy_from_slice(&dst_parts); + + // ICMP header (Echo Request) + packet[20] = 0x08; // Type: Echo Request + packet[21] = 0x00; // Code + packet[22] = 0x00; // Checksum (high byte) + packet[23] = 0x00; // Checksum (low byte) + packet[24] = 0x12; // Identifier (high byte) + packet[25] = 0x34; // Identifier (low byte) + packet[26] = 0x00; // Sequence Number (high byte) + packet[27] = 0x01; // Sequence Number (low byte) + + // Fill with test data + for i in 28..64 { + packet[i] = (i % 256) as u8; + } + + packet + } + + /// Creates a sample TCP packet + fn create_tcp_packet(src_ip: &str, dst_ip: &str, src_port: u16, dst_port: u16) -> Vec { + let mut packet = vec![0u8; 60]; // 20 bytes IP header + 20 bytes TCP header + 20 bytes data + + // IPv4 header + packet[0] = 0x45; // Version 4, Header length 20 bytes + packet[1] = 0x00; // Type of Service + packet[2] = 0x00; // Total Length (high byte) + packet[3] = 60; // Total Length (low byte) + packet[4] = 0x12; // Identification (high byte) + packet[5] = 0x34; // Identification (low byte) + packet[6] = 0x40; // Flags (Don't Fragment) + packet[7] = 0x00; // Fragment Offset + packet[8] = 64; // Time to Live + packet[9] = 0x06; // Protocol: TCP + packet[10] = 0x00; // Header Checksum (high byte) + packet[11] = 0x00; // Header Checksum (low byte) + + // Parse and set source IP + let src_parts: Vec = src_ip.split('.').map(|s| s.parse().unwrap()).collect(); + packet[12..16].copy_from_slice(&src_parts); + + // Parse and set destination IP + let dst_parts: Vec = dst_ip.split('.').map(|s| s.parse().unwrap()).collect(); + packet[16..20].copy_from_slice(&dst_parts); + + // TCP header + packet[20] = (src_port >> 8) as u8; // Source Port (high byte) + packet[21] = (src_port & 0xFF) as u8; // Source Port (low byte) + packet[22] = (dst_port >> 8) as u8; // Destination Port (high byte) + packet[23] = (dst_port & 0xFF) as u8; // Destination Port (low byte) + packet[24] = 0x00; // Sequence Number (bytes 24-27) + packet[25] = 0x00; + packet[26] = 0x00; + packet[27] = 0x01; + packet[28] = 0x00; // Acknowledgment Number (bytes 28-31) + packet[29] = 0x00; + packet[30] = 0x00; + packet[31] = 0x00; + packet[32] = 0x50; // Header Length (5 * 4 = 20 bytes) + Reserved + packet[33] = 0x02; // Flags (SYN) + packet[34] = 0x20; // Window Size (high byte) + packet[35] = 0x00; // Window Size (low byte) + packet[36] = 0x00; // Checksum (high byte) + packet[37] = 0x00; // Checksum (low byte) + packet[38] = 0x00; // Urgent Pointer (high byte) + packet[39] = 0x00; // Urgent Pointer (low byte) + + // Fill with test data + for i in 40..60 { + packet[i] = (i % 256) as u8; + } + + packet + } } diff --git a/litebox_platform_windows_userland/wintun.dll b/litebox_platform_windows_userland/wintun.dll new file mode 100644 index 000000000..aee04e77b Binary files /dev/null and b/litebox_platform_windows_userland/wintun.dll differ diff --git a/litebox_runner_linux_on_windows_userland/src/lib.rs b/litebox_runner_linux_on_windows_userland/src/lib.rs index 022f11168..98f79f9a5 100644 --- a/litebox_runner_linux_on_windows_userland/src/lib.rs +++ b/litebox_runner_linux_on_windows_userland/src/lib.rs @@ -129,7 +129,7 @@ pub fn run(cli_args: CliArgs) -> Result<()> { litebox::fs::tar_ro::EMPTY_TAR_FILE.into() }; - let platform = Platform::new(); + let platform = Platform::new(None); let litebox = LiteBox::new(platform); let prog = PathBuf::from(&cli_args.program_and_arguments[0]); let prog_unix_path = windows_path_to_unix(&prog); diff --git a/litebox_runner_linux_on_windows_userland/tests/common/mod.rs b/litebox_runner_linux_on_windows_userland/tests/common/mod.rs index 06c19b1a6..d0b87726b 100644 --- a/litebox_runner_linux_on_windows_userland/tests/common/mod.rs +++ b/litebox_runner_linux_on_windows_userland/tests/common/mod.rs @@ -36,7 +36,7 @@ unsafe extern "C" { } pub fn init_platform(tar_data: &'static [u8], initial_dirs: &[&str], initial_files: &[&str]) { - let platform = Platform::new(); + let platform = Platform::new(None); set_platform(platform); let platform = litebox_platform_multiplex::platform(); let litebox = LiteBox::new(platform);