diff --git a/Justfile b/Justfile index cfd7891..12b3d68 100644 --- a/Justfile +++ b/Justfile @@ -13,7 +13,7 @@ run: 'RUST_BACKTRACE=1 '$CARGO_PATH' run --bin vmm -- cli --memory 512 --cpus 1 \ --kernel tools/kernel/linux-cloud-hypervisor/arch/x86/boot/compressed/vmlinux.bin \ --iface-host-addr 172.29.0.1 --netmask 255.255.0.0 --iface-guest-addr 172.29.0.2 \ - --initramfs=tools/rootfs/initramfs.img' + --initramfs=../virt-do/initramfs.img' build-kernel: #!/bin/bash diff --git a/src/fs-gen/resources/initfile b/src/fs-gen/resources/initfile index 4d72bc6..14945cd 100644 --- a/src/fs-gen/resources/initfile +++ b/src/fs-gen/resources/initfile @@ -14,6 +14,8 @@ export RUST_VERSION='1.77.2' export PATH=$CARGO_HOME/bin:$PATH +ln -s /proc/net/pnp /etc/resolv.conf + /agent reboot \ No newline at end of file diff --git a/src/vmm/Cargo.toml b/src/vmm/Cargo.toml index 6f5aa30..c57316c 100644 --- a/src/vmm/Cargo.toml +++ b/src/vmm/Cargo.toml @@ -14,6 +14,8 @@ clap = { version = "4.5.1", features = ["derive", "env"] } clap-verbosity-flag = "2.2.0" epoll = "4.3.3" event-manager = { version = "0.4.0", features = ["remote_endpoint"] } +futures = "0.3.30" +iptables = "0.5.1" kvm-bindings = { version = "0.7.0", features = ["fam-wrappers"] } kvm-ioctls = "0.16.0" libc = "0.2.153" @@ -22,7 +24,9 @@ log = "0.4.20" nix = { version = "0.28.0", features = ["term"] } openpty = "0.2.0" prost = "0.11" +rtnetlink = "0.14.1" tokio = { version = "1.37.0", features = ["full"] } +tokio-stream = "0.1.15" tonic = "0.9" tracing = "0.1.40" tracing-subscriber = "0.3.18" @@ -34,7 +38,6 @@ vm-device = "0.1.0" vm-memory = { version = "0.14.1", features = ["backend-mmap"] } vm-superio = "0.7.0" vmm-sys-util = "0.12.1" -tokio-stream = "0.1.15" [build-dependencies] tonic-build = "0.9" diff --git a/src/vmm/src/core/devices/virtio/net/bridge.rs b/src/vmm/src/core/devices/virtio/net/bridge.rs new file mode 100644 index 0000000..a0f1500 --- /dev/null +++ b/src/vmm/src/core/devices/virtio/net/bridge.rs @@ -0,0 +1,137 @@ +use std::net::{IpAddr, Ipv4Addr}; + +use futures::stream::TryStreamExt; +use rtnetlink::{new_connection, Error, Handle}; + +use super::xx_netmask_width; + +#[derive(Clone)] +pub struct Bridge { + name: String, + handle: Handle, +} + +impl Bridge { + pub fn new(name: String) -> Self { + let (connection, handle, _) = new_connection().unwrap(); + tokio::spawn(connection); + + let br = Self { name, handle }; + br.create_bridge_if_not_exist(); + + br + } + + fn create_bridge_if_not_exist(&self) { + futures::executor::block_on(async { + let mut bridge_names = self + .handle + .link() + .get() + .match_name(self.name.clone()) + .execute(); + + let _ = match bridge_names.try_next().await { + Ok(_) => Ok(()), + Err(_) => self + .handle + .link() + .add() + .bridge(self.name.clone()) + .execute() + .await + .map_err(|_| Error::RequestFailed), + }; + }); + } + + pub fn set_addr(&self, addr: Ipv4Addr, netmask: Ipv4Addr) { + futures::executor::block_on(async { + let mut bridge_names = self + .handle + .link() + .get() + .match_name(self.name.clone()) + .execute(); + + let bridge_index = match bridge_names.try_next().await { + Ok(Some(link)) => link.header.index, + Ok(None) => panic!(), + Err(_) => panic!(), + }; + + let prefix_len = xx_netmask_width(netmask.octets()); + + let _ = self + .handle + .address() + .add(bridge_index, IpAddr::V4(addr), prefix_len) + .execute() + .await + .map_err(|_| Error::RequestFailed); + }); + } + + pub fn set_up(&self) { + futures::executor::block_on(async { + let mut bridge_names = self + .handle + .link() + .get() + .match_name(self.name.clone()) + .execute(); + + let bridge_index = match bridge_names.try_next().await { + Ok(Some(link)) => link.header.index, + Ok(None) => panic!(), + Err(_) => panic!(), + }; + + let _ = self + .handle + .link() + .set(bridge_index) + .up() + .execute() + .await + .map_err(|_| Error::RequestFailed); + }); + } + + pub fn attach_link(&self, link_name: String) { + futures::executor::block_on(async { + let mut link_names = self + .handle + .link() + .get() + .match_name(link_name.clone()) + .execute(); + let mut master_names = self + .handle + .link() + .get() + .match_name(self.name.clone()) + .execute(); + + let link_index = match link_names.try_next().await { + Ok(Some(link)) => link.header.index, + Ok(None) => panic!(), + Err(_) => panic!(), + }; + let master_index = match master_names.try_next().await { + Ok(Some(link)) => link.header.index, + Ok(None) => panic!(), + Err(_) => panic!(), + }; + + let _ = self + .handle + .link() + .set(link_index) + .controller(master_index) + .execute() + .await + .map_err(|_| Error::RequestFailed); + }); + } +} diff --git a/src/vmm/src/core/devices/virtio/net/device.rs b/src/vmm/src/core/devices/virtio/net/device.rs index bf8acf8..5432cce 100644 --- a/src/vmm/src/core/devices/virtio/net/device.rs +++ b/src/vmm/src/core/devices/virtio/net/device.rs @@ -1,3 +1,5 @@ +use super::bridge::Bridge; +use super::iptables::iptables_ip_masq; use super::queue_handler::QueueHandler; use super::{ simple_handler::SimpleHandler, tuntap::tap::Tap, Error, Result, NET_DEVICE_ID, @@ -34,6 +36,7 @@ pub struct Net { mem: Arc, pub config: Config, tap: Arc>, + _bridge: Bridge, } impl Net { @@ -42,7 +45,7 @@ impl Net { mem: Arc, device_mgr: Arc>, mmio_cfg: MmioConfig, - tap_addr: Ipv4Addr, + iface_host_addr: Ipv4Addr, netmask: Ipv4Addr, iface_guest_addr: Ipv4Addr, irq: u32, @@ -74,25 +77,34 @@ impl Net { // Set offload flags to match the relevant virtio features of the device (for now, // statically set in the constructor. - let tap = open_tap(None, Some(tap_addr), Some(netmask), &mut None, None, None) - .map_err(Error::TunTap)?; + let tap = open_tap(None, None, None, &mut None, None, None).map_err(Error::TunTap)?; // The layout of the header is specified in the standard and is 12 bytes in size. We // should define this somewhere. tap.set_vnet_hdr_size(VIRTIO_NET_HDR_SIZE as i32) .map_err(Error::Tap)?; + let bridge_name = "br0".to_string(); + let bridge = Bridge::new(bridge_name.clone()); + bridge.set_addr(iface_host_addr, netmask); + bridge.attach_link(tap.get_name().map_err(Error::Tap)?); + bridge.set_up(); + + // Get internet access + iptables_ip_masq(iface_host_addr & netmask, netmask, bridge_name); + let net = Arc::new(Mutex::new(Net { mem, config: cfg, - tap: Arc::new(Mutex::new(tap)), + tap: Arc::new(Mutex::new(tap.clone())), + _bridge: bridge, })); let vmmio_param = register_mmio_device(mmio_cfg, device_mgr, irq, None, net.clone()) .map_err(Error::Virtio)?; let ip_pnp_param: String = format!( - "ip={}::{}:{}::eth0:off", - iface_guest_addr, tap_addr, netmask + "ip={}::{}:{}::eth0:off:1.1.1.1", + iface_guest_addr, iface_host_addr, netmask ); cmdline_extra_parameters.push(vmmio_param); diff --git a/src/vmm/src/core/devices/virtio/net/iptables.rs b/src/vmm/src/core/devices/virtio/net/iptables.rs new file mode 100644 index 0000000..2c61579 --- /dev/null +++ b/src/vmm/src/core/devices/virtio/net/iptables.rs @@ -0,0 +1,16 @@ +use std::net::Ipv4Addr; + +use super::xx_netmask_width; + +pub fn iptables_ip_masq(network: Ipv4Addr, netmask: Ipv4Addr, link_name: String) { + let prefix_len = xx_netmask_width(netmask.octets()); + let source = format!("{}/{}", network, prefix_len); + + let ipt = iptables::new(false).unwrap(); + let rule = format!("-s {} ! -o {} -j MASQUERADE", source, link_name); + + let exists = ipt.exists("nat", "POSTROUTING", rule.as_str()).unwrap(); + if !exists { + let _ = ipt.insert_unique("nat", "POSTROUTING", rule.as_str(), 1); + } +} diff --git a/src/vmm/src/core/devices/virtio/net/mod.rs b/src/vmm/src/core/devices/virtio/net/mod.rs index 765986e..1277932 100644 --- a/src/vmm/src/core/devices/virtio/net/mod.rs +++ b/src/vmm/src/core/devices/virtio/net/mod.rs @@ -1,4 +1,6 @@ +mod bridge; pub mod device; +pub mod iptables; mod queue_handler; mod simple_handler; pub mod tuntap; @@ -20,3 +22,7 @@ pub enum Error { } pub type Result = std::result::Result; + +pub fn xx_netmask_width(netmask: [u8; SZ]) -> u8 { + netmask.iter().map(|x| x.count_ones() as u8).sum() +} diff --git a/src/vmm/src/core/devices/virtio/net/tuntap/tap.rs b/src/vmm/src/core/devices/virtio/net/tuntap/tap.rs index 0eb1ec3..1dbabb9 100644 --- a/src/vmm/src/core/devices/virtio/net/tuntap/tap.rs +++ b/src/vmm/src/core/devices/virtio/net/tuntap/tap.rs @@ -200,6 +200,12 @@ impl Tap { unsafe { Self::ioctl_with_ref(&sock, net_gen::sockios::SIOCSIFHWADDR as c_ulong, &ifreq) } } + /// Get tap name + pub fn get_name(&self) -> Result { + let name = String::from_utf8(self.if_name.clone()).map_err(|_| Error::InvalidIfname)?; + Ok(name) + } + /// Get mac addr for tap interface. pub fn get_mac_addr(&self) -> Result { let sock = create_unix_socket().map_err(Error::NetUtil)?; diff --git a/src/vmm/src/core/vmm.rs b/src/vmm/src/core/vmm.rs index 722d70f..d3e6ca5 100644 --- a/src/vmm/src/core/vmm.rs +++ b/src/vmm/src/core/vmm.rs @@ -58,7 +58,7 @@ pub struct VMM { event_mgr: EventMgr, vcpus: Vec, - tap_addr: Ipv4Addr, + iface_host_addr: Ipv4Addr, netmask: Ipv4Addr, iface_guest_addr: Ipv4Addr, net_devices: Vec>>, @@ -69,7 +69,11 @@ pub struct VMM { impl VMM { /// Create a new VMM. - pub fn new(tap_addr: Ipv4Addr, netmask: Ipv4Addr, iface_guest_addr: Ipv4Addr) -> Result { + pub fn new( + iface_host_addr: Ipv4Addr, + netmask: Ipv4Addr, + iface_guest_addr: Ipv4Addr, + ) -> Result { // Open /dev/kvm and get a file descriptor to it. let kvm = Kvm::new().map_err(Error::KvmIoctl)?; @@ -111,7 +115,7 @@ impl VMM { )), slip_pty: Arc::new(Mutex::new(slip_pty)), epoll, - tap_addr, + iface_host_addr, netmask, iface_guest_addr, net_devices: Vec::new(), @@ -382,7 +386,7 @@ impl VMM { mem, self.device_mgr.clone(), mmio_cfg, - self.tap_addr, + self.iface_host_addr, self.netmask, self.iface_guest_addr, irq,