diff --git a/src/ffi/vmnet.rs b/src/ffi/vmnet.rs index 95fc192..693ef86 100644 --- a/src/ffi/vmnet.rs +++ b/src/ffi/vmnet.rs @@ -45,6 +45,16 @@ extern "C" { client: *const [u8; 6], reservation: *const in_addr, ) -> VmnetReturnT; + // internal_port comes before external_port here, unlike in + // vmnet_interface_add_ip_port_forwarding_rule + pub fn vmnet_network_configuration_add_port_forwarding_rule( + config: NetworkConfigurationRef, + protocol: u8, + address_family: u8, + internal_port: u16, + external_port: u16, + internal_address: *const c_void, + ) -> VmnetReturnT; // Network functions pub fn vmnet_network_create( diff --git a/src/interface.rs b/src/interface.rs index c2a0d23..9e4a2cf 100644 --- a/src/interface.rs +++ b/src/interface.rs @@ -844,6 +844,33 @@ mod tests { iface.finalize().unwrap(); } + #[test] + fn network_port_forwarding() { + let addr: IpAddr = Ipv4Addr::new(10, 88, 12, 2).into(); + + // Configure port forwarding on the network + let mut configuration = NetworkConfiguration::new(NetworkMode::Shared).unwrap(); + configuration + .set_ipv4_subnet( + Ipv4Addr::new(10, 88, 12, 1), + Ipv4Addr::new(255, 255, 255, 248), + ) + .unwrap(); + configuration + .add_port_forwarding_rule(AddressFamily::Ipv4, Protocol::Tcp, 2222, addr, 22) + .unwrap(); + configuration + .add_port_forwarding_rule(AddressFamily::Ipv4, Protocol::Tcp, 8080, addr, 80) + .unwrap(); + + // Create the network and attach an interface. The interface port + // forwarding functions fail on it, so the rules can't be listed back + let network = Network::new(&configuration).unwrap(); + let mut iface = Interface::with_network(&network, Options::default()).unwrap(); + + iface.finalize().unwrap(); + } + #[test] fn test_batch() { // Create two interfaces in the same broadcast domain diff --git a/src/network.rs b/src/network.rs index 1c80c2a..5d20ddb 100644 --- a/src/network.rs +++ b/src/network.rs @@ -1,7 +1,8 @@ use crate::ffi::vmnet::{self, NetworkRef, Status}; -use crate::Result; +use crate::port_forwarding::{AddressFamily, Protocol}; +use crate::{Error, Result}; use objc2_core_foundation::{CFRetained, CFType}; -use std::net::Ipv4Addr; +use std::net::{IpAddr, Ipv4Addr}; use std::ptr::NonNull; /// Modes supported by explicit networks. @@ -67,6 +68,46 @@ impl NetworkConfiguration { Ok(()) } + + /// Adds a port forwarding rule to the network. + /// + /// Rules must be added before the network is created: + /// [`Interface::port_forwarding_rule_add`] can't add rules to an interface + /// attached to a network. `internal_addr` must be inside the network's + /// subnet; use [`set_ipv4_subnet`](Self::set_ipv4_subnet) and + /// [`add_dhcp_reservation`](Self::add_dhcp_reservation) to give a guest a + /// known address. + /// + /// [`Interface::port_forwarding_rule_add`]: crate::Interface::port_forwarding_rule_add + pub fn add_port_forwarding_rule( + &mut self, + address_family: AddressFamily, + protocol: Protocol, + external_port: u16, + internal_addr: IpAddr, + internal_port: u16, + ) -> Result<()> { + let internal_addr_ffi = match (address_family, internal_addr) { + (AddressFamily::Ipv4, IpAddr::V4(addr)) => Vec::from(addr.octets()), + (AddressFamily::Ipv6, IpAddr::V6(addr)) => Vec::from(addr.octets()), + _ => return Err(Error::AddressFamilyMismatch), + }; + + let status = unsafe { + vmnet::vmnet_network_configuration_add_port_forwarding_rule( + CFRetained::as_ptr(&self.handle).as_ptr().cast(), + protocol as u8, + address_family as u8, + internal_port, + external_port, + internal_addr_ffi.as_ptr().cast(), + ) + }; + + Status::from_ffi(status)?; + + Ok(()) + } } /// A reserved network to which multiple interfaces can attach. @@ -128,4 +169,30 @@ mod tests { assert!(Network::new(&second_configuration).is_err()); } + + #[test] + fn port_forwarding_rule_address_family_mismatch() { + let mut configuration = NetworkConfiguration::new(Mode::Shared).unwrap(); + + assert!(matches!( + configuration.add_port_forwarding_rule( + AddressFamily::Ipv6, + Protocol::Tcp, + 2222, + Ipv4Addr::new(192, 168, 0, 2).into(), + 22, + ), + Err(Error::AddressFamilyMismatch) + )); + assert!(matches!( + configuration.add_port_forwarding_rule( + AddressFamily::Ipv4, + Protocol::Tcp, + 2222, + "fd00::2".parse::().unwrap().into(), + 22, + ), + Err(Error::AddressFamilyMismatch) + )); + } }