diff --git a/net_gen/src/ipv6.rs b/net_gen/src/ipv6.rs new file mode 100644 index 000000000..65d9349ec --- /dev/null +++ b/net_gen/src/ipv6.rs @@ -0,0 +1,41 @@ +// Copyright © 2025 Cloud Hypervisor Authors +// +// SPDX-License-Identifier: Apache-2.0 + +// bindgen /usr/include/linux/ipv6.h --no-layout-tests --constified-enum '*' --allowlist-type 'sockaddr_in6|in6_ifreq' + +/* automatically generated by rust-bindgen 0.71.1 */ + +pub type __u8 = ::std::os::raw::c_uchar; +pub type __u16 = ::std::os::raw::c_ushort; +pub type __u32 = ::std::os::raw::c_uint; +pub type __be16 = __u16; +pub type __be32 = __u32; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct in6_addr { + pub in6_u: in6_addr__bindgen_ty_1, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub union in6_addr__bindgen_ty_1 { + pub u6_addr8: [__u8; 16usize], + pub u6_addr16: [__be16; 8usize], + pub u6_addr32: [__be32; 4usize], +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct sockaddr_in6 { + pub sin6_family: ::std::os::raw::c_ushort, + pub sin6_port: __be16, + pub sin6_flowinfo: __be32, + pub sin6_addr: in6_addr, + pub sin6_scope_id: __u32, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct in6_ifreq { + pub ifr6_addr: in6_addr, + pub ifr6_prefixlen: __u32, + pub ifr6_ifindex: ::std::os::raw::c_int, +} diff --git a/net_gen/src/lib.rs b/net_gen/src/lib.rs index 3ad1b8987..bb0148a00 100644 --- a/net_gen/src/lib.rs +++ b/net_gen/src/lib.rs @@ -26,6 +26,9 @@ pub mod if_tun; // --constified-enum '*' --with-derive-default // Name is "inn" to avoid conflicting with "in" keyword. pub mod inn; +// generated with bindgen /usr/include/linux/ipv6.h --no-layout-tests --constified-enum '*' +// --allowlist-type 'sockaddr_in6|in6_ifreq' +pub mod ipv6; // generated with bindgen /usr/include/linux/sockios.h --no-unstable-rust // --constified-enum '*' --with-derive-default pub mod sockios; @@ -35,6 +38,7 @@ pub use if_tun::{ }; pub use iff::{ifreq, net_device_flags_IFF_UP, setsockopt, sockaddr, AF_INET}; pub use inn::sockaddr_in; +pub use ipv6::{in6_ifreq, sockaddr_in6}; pub const TUNTAP: ::std::os::raw::c_uint = 84; diff --git a/net_util/src/lib.rs b/net_util/src/lib.rs index 947067148..554ea7134 100644 --- a/net_util/src/lib.rs +++ b/net_util/src/lib.rs @@ -15,6 +15,7 @@ mod queue_pair; mod tap; use std::io::Error as IoError; +use std::net::IpAddr; use std::os::raw::c_uint; use std::os::unix::io::{FromRawFd, RawFd}; use std::{io, mem, net}; @@ -76,9 +77,14 @@ fn create_sockaddr(ip_addr: net::Ipv4Addr) -> net_gen::sockaddr { unsafe { mem::transmute(addr_in) } } -fn create_inet_socket() -> Result { +fn create_inet_socket(addr: IpAddr) -> Result { + let domain = match addr { + IpAddr::V4(_) => libc::AF_INET, + IpAddr::V6(_) => libc::AF_INET6, + }; + // SAFETY: we check the return value. - let sock = unsafe { libc::socket(libc::AF_INET, libc::SOCK_DGRAM, 0) }; + let sock = unsafe { libc::socket(domain, libc::SOCK_DGRAM, 0) }; if sock < 0 { return Err(Error::CreateSocket(IoError::last_os_error())); } diff --git a/net_util/src/open_tap.rs b/net_util/src/open_tap.rs index 4fc8851e2..c652f0c40 100644 --- a/net_util/src/open_tap.rs +++ b/net_util/src/open_tap.rs @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause -use std::net::Ipv4Addr; +use std::net::IpAddr; use std::path::Path; use std::{fs, io}; @@ -22,10 +22,8 @@ pub enum Error { ReadSysfsTunFlags(io::Error), #[error("Open tap device failed: {0}")] TapOpen(TapError), - #[error("Setting tap IP failed: {0}")] - TapSetIp(TapError), - #[error("Setting tap netmask failed: {0}")] - TapSetNetmask(TapError), + #[error("Setting tap IP and/or netmask failed: {0}")] + TapSetIpNetmask(TapError), #[error("Setting MAC address failed: {0}")] TapSetMac(TapError), #[error("Getting MAC address failed: {0}")] @@ -64,8 +62,8 @@ fn check_mq_support(if_name: &Option<&str>, queue_pairs: usize) -> Result<()> { /// netmask. pub fn open_tap( if_name: Option<&str>, - ip_addr: Option, - netmask: Option, + ip_addr: Option, + netmask: Option, host_mac: &mut Option, mtu: Option, num_rx_q: usize, @@ -94,10 +92,8 @@ pub fn open_tap( // Don't overwrite ip configuration of existing interfaces: if !tap_existed { if let Some(ip) = ip_addr { - tap.set_ip_addr(ip).map_err(Error::TapSetIp)?; - } - if let Some(mask) = netmask { - tap.set_netmask(mask).map_err(Error::TapSetNetmask)?; + tap.set_ip_addr(ip, netmask) + .map_err(Error::TapSetIpNetmask)?; } } else { warn!( diff --git a/net_util/src/tap.rs b/net_util/src/tap.rs index 07a6e072c..665e8a72f 100644 --- a/net_util/src/tap.rs +++ b/net_util/src/tap.rs @@ -7,7 +7,7 @@ use std::fs::File; use std::io::{Error as IoError, Read, Result as IoResult, Write}; -use std::net; +use std::net::{IpAddr, Ipv6Addr}; use std::os::raw::*; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; @@ -38,6 +38,8 @@ pub enum Error { InvalidIfname, #[error("Error parsing MAC data: {0}")] MacParsing(IoError), + #[error("Invalid netmask")] + InvalidNetmask, } pub type Result = ::std::result::Result; @@ -88,6 +90,37 @@ fn build_terminated_if_name(if_name: &str) -> Result> { Ok(terminated_if_name) } +fn ipv6_mask_to_prefix(mask: Ipv6Addr) -> Result { + let mask = mask.segments(); + let mut iter = mask.iter(); + + let mut prefix = 0; + for &segment in &mut iter { + if segment == 0xffff { + prefix += 16; + } else if segment == 0 { + break; + } else { + let prefix_bits = segment.leading_ones() as u8; + if segment << prefix_bits != 0 { + return Err(Error::InvalidNetmask); + } + + prefix += prefix_bits; + break; + } + } + + // Check that remaining bits are all unset + for &segment in iter { + if segment != 0 { + return Err(Error::InvalidNetmask); + } + } + + Ok(prefix) +} + impl Tap { unsafe fn ioctl_with_mut_ref(fd: &F, req: c_ulong, arg: &mut T) -> Result<()> { let ret = ioctl_with_mut_ref(fd, req, arg); @@ -235,16 +268,78 @@ impl Tap { } /// Set the host-side IP address for the tap interface. - pub fn set_ip_addr(&self, ip_addr: net::Ipv4Addr) -> Result<()> { - let sock = create_inet_socket().map_err(Error::NetUtil)?; - let addr = create_sockaddr(ip_addr); + pub fn set_ip_addr(&self, ip_addr: IpAddr, netmask: Option) -> Result<()> { + let sock = create_inet_socket(ip_addr).map_err(Error::NetUtil)?; let mut ifreq = self.get_ifreq(); - ifreq.ifr_ifru.ifru_addr = addr; + match ip_addr { + IpAddr::V4(addr) => { + let addr = create_sockaddr(addr); - // SAFETY: ioctl is safe. Called with a valid sock fd, and we check the return. - unsafe { Self::ioctl_with_ref(&sock, net_gen::sockios::SIOCSIFADDR as c_ulong, &ifreq) } + ifreq.ifr_ifru.ifru_addr = addr; + + // SAFETY: ioctl is safe. Called with a valid sock fd, and we check the return. + unsafe { + Self::ioctl_with_ref(&sock, net_gen::sockios::SIOCSIFADDR as c_ulong, &ifreq)?; + } + + if let Some(IpAddr::V4(mask)) = netmask { + ifreq.ifr_ifru.ifru_netmask = create_sockaddr(mask); + + // SAFETY: ioctl is safe. Called with a valid sock fd, and we check the return. + unsafe { + Self::ioctl_with_ref( + &sock, + net_gen::sockios::SIOCSIFNETMASK as c_ulong, + &ifreq, + )?; + } + }; + + Ok(()) + } + IpAddr::V6(addr) => { + let ifindex = { + // SAFETY: ioctl is safe. Called with a valid sock fd, and we check the return. + unsafe { + Self::ioctl_with_ref( + &sock, + net_gen::sockios::SIOCGIFINDEX as c_ulong, + &ifreq, + )?; + } + + // SAFETY: ifru_ivalue contains the ifindex and is set by the previous ioctl + unsafe { + match ifreq.ifr_ifru.ifru_ivalue { + 0 => return Err(Error::InvalidIfname), + i => i, + } + } + }; + + let prefixlen = match netmask { + Some(IpAddr::V6(netmask)) => ipv6_mask_to_prefix(netmask)?, + Some(IpAddr::V4(_)) => return Err(Error::InvalidNetmask), + None => 0, + }; + + let ifreq = net_gen::in6_ifreq { + // SAFETY: addr can be safely transmuted to in6_addr + ifr6_addr: unsafe { + std::mem::transmute::<[u8; 16], net_gen::ipv6::in6_addr>(addr.octets()) + }, + ifr6_prefixlen: prefixlen as u32, + ifr6_ifindex: ifindex, + }; + + // SAFETY: ioctl is safe. Called with a valid sock fd, and we check the return. + unsafe { + Self::ioctl_with_ref(&sock, net_gen::sockios::SIOCSIFADDR as c_ulong, &ifreq) + } + } + } } /// Set mac addr for tap interface. @@ -294,19 +389,6 @@ impl Tap { Ok(addr) } - /// Set the netmask for the subnet that the tap interface will exist on. - pub fn set_netmask(&self, netmask: net::Ipv4Addr) -> Result<()> { - let sock = create_inet_socket().map_err(Error::NetUtil)?; - let addr = create_sockaddr(netmask); - - let mut ifreq = self.get_ifreq(); - - ifreq.ifr_ifru.ifru_addr = addr; - - // SAFETY: ioctl is safe. Called with a valid sock fd, and we check the return. - unsafe { Self::ioctl_with_ref(&sock, net_gen::sockios::SIOCSIFNETMASK as c_ulong, &ifreq) } - } - #[cfg(not(fuzzing))] pub fn mtu(&self) -> Result { let sock = create_unix_socket().map_err(Error::NetUtil)?; @@ -602,11 +684,22 @@ mod tests { let tap_ip_guard = TAP_IP_LOCK.lock().unwrap(); let tap = Tap::new(1).unwrap(); - let ip_addr: net::Ipv4Addr = (*tap_ip_guard).parse().unwrap(); - let netmask: net::Ipv4Addr = SUBNET_MASK.parse().unwrap(); + let ip_addr = IpAddr::V4((*tap_ip_guard).parse().unwrap()); + let netmask = IpAddr::V4(SUBNET_MASK.parse().unwrap()); - tap.set_ip_addr(ip_addr).unwrap(); - tap.set_netmask(netmask).unwrap(); + tap.set_ip_addr(ip_addr, Some(netmask)).unwrap(); + } + + #[test] + fn test_tap_configure_ipv6() { + let tap_ip6_lock: Mutex<&'static str> = Mutex::new("2001:db8:85a3::8a2e:370:7334"); + let tap_ip6_guard = tap_ip6_lock.lock().unwrap(); + + let tap = Tap::new(1).unwrap(); + let ip_addr = IpAddr::V6((*tap_ip6_guard).parse().unwrap()); + let netmask = IpAddr::V6("ffff:ffff::".parse().unwrap()); + + tap.set_ip_addr(ip_addr, Some(netmask)).unwrap(); } #[test] @@ -640,8 +733,9 @@ mod tests { let tap_ip_guard = TAP_IP_LOCK.lock().unwrap(); let mut tap = Tap::new(1).unwrap(); - tap.set_ip_addr((*tap_ip_guard).parse().unwrap()).unwrap(); - tap.set_netmask(SUBNET_MASK.parse().unwrap()).unwrap(); + let ip_addr = IpAddr::V4((*tap_ip_guard).parse().unwrap()); + let netmask = IpAddr::V4(SUBNET_MASK.parse().unwrap()); + tap.set_ip_addr(ip_addr, Some(netmask)).unwrap(); tap.enable().unwrap(); // Send a packet to the interface. We expect to be able to receive it on the associated fd. @@ -698,8 +792,9 @@ mod tests { let tap_ip_guard = TAP_IP_LOCK.lock().unwrap(); let mut tap = Tap::new(1).unwrap(); - tap.set_ip_addr((*tap_ip_guard).parse().unwrap()).unwrap(); - tap.set_netmask(SUBNET_MASK.parse().unwrap()).unwrap(); + let ip_addr = IpAddr::V4((*tap_ip_guard).parse().unwrap()); + let netmask = IpAddr::V4(SUBNET_MASK.parse().unwrap()); + tap.set_ip_addr(ip_addr, Some(netmask)).unwrap(); tap.enable().unwrap(); let (mac, _, mut rx) = pnet_get_mac_tx_rx(tap_name_to_string(&tap)); diff --git a/tests/integration.rs b/tests/integration.rs index 8b7450087..074270ba5 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -6345,7 +6345,9 @@ mod common_parallel { use std::str::FromStr; let taps = net_util::open_tap( Some("chtap0"), - Some(std::net::Ipv4Addr::from_str(&guest.network.host_ip).unwrap()), + Some(std::net::IpAddr::V4( + std::net::Ipv4Addr::from_str(&guest.network.host_ip).unwrap(), + )), None, &mut None, None, @@ -7612,7 +7614,9 @@ mod common_sequential { use std::str::FromStr; let taps = net_util::open_tap( Some(tap_name), - Some(std::net::Ipv4Addr::from_str(&guest.network.host_ip).unwrap()), + Some(std::net::IpAddr::V4( + std::net::Ipv4Addr::from_str(&guest.network.host_ip).unwrap(), + )), None, &mut None, None, @@ -7710,7 +7714,9 @@ mod common_sequential { let taps = net_util::open_tap( Some(tap_name), - Some(std::net::Ipv4Addr::from_str(&guest.network.host_ip).unwrap()), + Some(std::net::IpAddr::V4( + std::net::Ipv4Addr::from_str(&guest.network.host_ip).unwrap(), + )), None, &mut None, None, diff --git a/vhost_user_net/src/lib.rs b/vhost_user_net/src/lib.rs index 4f252dcd7..d44dcd5ab 100644 --- a/vhost_user_net/src/lib.rs +++ b/vhost_user_net/src/lib.rs @@ -6,7 +6,7 @@ // // SPDX-License-Identifier: (Apache-2.0 AND BSD-3-Clause) -use std::net::Ipv4Addr; +use std::net::{IpAddr, Ipv4Addr}; use std::ops::Deref; use std::os::unix::io::{AsRawFd, RawFd}; use std::sync::{Arc, Mutex, RwLock}; @@ -118,9 +118,9 @@ pub struct VhostUserNetBackend { impl VhostUserNetBackend { #[allow(clippy::too_many_arguments)] fn new( - ip_addr: Ipv4Addr, + ip_addr: IpAddr, host_mac: MacAddr, - netmask: Ipv4Addr, + netmask: IpAddr, mtu: Option, num_queues: usize, queue_size: u16, @@ -271,9 +271,9 @@ impl VhostUserBackendMut for VhostUserNetBackend { } pub struct VhostUserNetBackendConfig { - pub ip: Ipv4Addr, + pub ip: IpAddr, pub host_mac: MacAddr, - pub mask: Ipv4Addr, + pub mask: IpAddr, pub mtu: Option, pub socket: String, pub num_queues: usize, @@ -303,7 +303,7 @@ impl VhostUserNetBackendConfig { let ip = parser .convert("ip") .map_err(Error::FailedConfigParse)? - .unwrap_or_else(|| Ipv4Addr::new(192, 168, 100, 1)); + .unwrap_or_else(|| IpAddr::V4(Ipv4Addr::new(192, 168, 100, 1))); let host_mac = parser .convert("host_mac") .map_err(Error::FailedConfigParse)? @@ -311,7 +311,7 @@ impl VhostUserNetBackendConfig { let mask = parser .convert("mask") .map_err(Error::FailedConfigParse)? - .unwrap_or_else(|| Ipv4Addr::new(255, 255, 255, 0)); + .unwrap_or_else(|| IpAddr::V4(Ipv4Addr::new(255, 255, 255, 0))); let mtu = parser.convert("mtu").map_err(Error::FailedConfigParse)?; let queue_size = parser .convert("queue_size") diff --git a/virtio-devices/src/net.rs b/virtio-devices/src/net.rs index a39080485..d1a8920c4 100644 --- a/virtio-devices/src/net.rs +++ b/virtio-devices/src/net.rs @@ -6,7 +6,7 @@ // found in the THIRD-PARTY file. use std::collections::HashMap; -use std::net::Ipv4Addr; +use std::net::IpAddr; use std::num::Wrapping; use std::ops::Deref; use std::os::unix::io::{AsRawFd, RawFd}; @@ -547,8 +547,8 @@ impl Net { pub fn new( id: String, if_name: Option<&str>, - ip_addr: Option, - netmask: Option, + ip_addr: Option, + netmask: Option, guest_mac: Option, host_mac: &mut Option, mtu: Option, diff --git a/vmm/src/api/openapi/cloud-hypervisor.yaml b/vmm/src/api/openapi/cloud-hypervisor.yaml index 15f99d303..80a4fa257 100644 --- a/vmm/src/api/openapi/cloud-hypervisor.yaml +++ b/vmm/src/api/openapi/cloud-hypervisor.yaml @@ -935,9 +935,11 @@ components: ip: type: string default: "192.168.249.1" + description: IPv4 or IPv6 address mask: type: string default: "255.255.255.0" + description: Must be a valid IPv4 netmask if ip is an IPv4 address or a valid IPv6 netmask if ip is an IPv6 address. mac: type: string host_mac: diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 0cf297925..3372dc4ff 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -3138,7 +3138,7 @@ impl Drop for VmConfig { #[cfg(test)] mod tests { use std::fs::File; - use std::net::Ipv4Addr; + use std::net::{IpAddr, Ipv4Addr}; use std::os::unix::io::AsRawFd; use net_util::MacAddr; @@ -3468,8 +3468,8 @@ mod tests { fn net_fixture() -> NetConfig { NetConfig { tap: None, - ip: Ipv4Addr::new(192, 168, 249, 1), - mask: Ipv4Addr::new(255, 255, 255, 0), + ip: IpAddr::V4(Ipv4Addr::new(192, 168, 249, 1)), + mask: IpAddr::V4(Ipv4Addr::new(255, 255, 255, 0)), mac: MacAddr::parse_str("de:ad:be:ef:12:34").unwrap(), host_mac: Some(MacAddr::parse_str("12:34:de:ad:be:ef").unwrap()), mtu: None, diff --git a/vmm/src/seccomp_filters.rs b/vmm/src/seccomp_filters.rs index 66b91c399..a0efc6a68 100644 --- a/vmm/src/seccomp_filters.rs +++ b/vmm/src/seccomp_filters.rs @@ -67,13 +67,14 @@ const TUNGETFEATURES: u64 = 0x8004_54cf; // See include/uapi/linux/sockios.h in the kernel code. const SIOCGIFFLAGS: u64 = 0x8913; -const SIOCGIFHWADDR: u64 = 0x8927; const SIOCSIFFLAGS: u64 = 0x8914; const SIOCSIFADDR: u64 = 0x8916; +const SIOCSIFNETMASK: u64 = 0x891c; const SIOCGIFMTU: u64 = 0x8921; const SIOCSIFMTU: u64 = 0x8922; const SIOCSIFHWADDR: u64 = 0x8924; -const SIOCSIFNETMASK: u64 = 0x891c; +const SIOCGIFHWADDR: u64 = 0x8927; +const SIOCGIFINDEX: u64 = 0x8933; // See include/uapi/linux/vfio.h in the kernel code. const VFIO_GET_API_VERSION: u64 = 0x3b64; @@ -303,6 +304,7 @@ fn create_vmm_ioctl_seccomp_rule_common( and![Cond::new(1, ArgLen::Dword, Eq, SIOCGIFFLAGS)?], and![Cond::new(1, ArgLen::Dword, Eq, SIOCGIFHWADDR)?], and![Cond::new(1, ArgLen::Dword, Eq, SIOCGIFMTU)?], + and![Cond::new(1, ArgLen::Dword, Eq, SIOCGIFINDEX)?], and![Cond::new(1, ArgLen::Dword, Eq, SIOCSIFADDR)?], and![Cond::new(1, ArgLen::Dword, Eq, SIOCSIFFLAGS)?], and![Cond::new(1, ArgLen::Dword, Eq, SIOCSIFHWADDR)?], @@ -672,6 +674,7 @@ fn vmm_thread_rules( or![ and![Cond::new(0, ArgLen::Dword, Eq, libc::AF_UNIX as u64)?], and![Cond::new(0, ArgLen::Dword, Eq, libc::AF_INET as u64)?], + and![Cond::new(0, ArgLen::Dword, Eq, libc::AF_INET6 as u64)?], ], ), (libc::SYS_socketpair, vec![]), diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index 1888b90b3..a2c5b996b 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 // -use std::net::Ipv4Addr; +use std::net::{IpAddr, Ipv4Addr}; use std::path::PathBuf; use std::{fs, result}; @@ -301,9 +301,9 @@ pub struct NetConfig { #[serde(default = "default_netconfig_tap")] pub tap: Option, #[serde(default = "default_netconfig_ip")] - pub ip: Ipv4Addr, + pub ip: IpAddr, #[serde(default = "default_netconfig_mask")] - pub mask: Ipv4Addr, + pub mask: IpAddr, #[serde(default = "default_netconfig_mac")] pub mac: MacAddr, #[serde(default)] @@ -349,12 +349,12 @@ pub fn default_netconfig_tap() -> Option { None } -pub fn default_netconfig_ip() -> Ipv4Addr { - Ipv4Addr::new(192, 168, 249, 1) +pub fn default_netconfig_ip() -> IpAddr { + IpAddr::V4(Ipv4Addr::new(192, 168, 249, 1)) } -pub fn default_netconfig_mask() -> Ipv4Addr { - Ipv4Addr::new(255, 255, 255, 0) +pub fn default_netconfig_mask() -> IpAddr { + IpAddr::V4(Ipv4Addr::new(255, 255, 255, 0)) } pub fn default_netconfig_mac() -> MacAddr {