Repository: cloud-hypervisor/cloud-hypervisor
Pull request #7048: net_util: add support for IPv6 addresses on tap interfaces
Author: login: gpanders
Created: 2025-05-02T19:05:06Z | Merged: 2025-05-20T17:28:38Z

Allow tap interfaces to be configured with an IPv6 address. The change is fairly straightforward: we need to update the API types and CLI parsing to accept either an IPv6 or IPv4 and then match on the IP address type when the tap device is configured.

For IPv6 addresses, the netmask (prefix) must be provided at the same time as the address itself (in the SIOCSIFADDR ioctl). They cannot be configured separately. So we remove the separate "set_netmask" function and convert "set_ip_addr" to also accept a netmask and then set both the IP address and netmask together. For IPv4 addresses, the IP address and netmask were already always set together, so this should have no functional impact for users of IPv4 addresses.


Diff:
diff --git a/net_gen/src/ipv6.rs b/net_gen/src/ipv6.rs
new file mode 100644
index 0000000000..65d9349ec3
--- /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 3ad1b89876..bb0148a004 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 9470671480..554ea71345 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<net::UdpSocket> {
+fn create_inet_socket(addr: IpAddr) -> Result<net::UdpSocket> {
+    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 4fc8851e28..c652f0c405 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<Ipv4Addr>,
-    netmask: Option<Ipv4Addr>,
+    ip_addr: Option<IpAddr>,
+    netmask: Option<IpAddr>,
     host_mac: &mut Option<MacAddr>,
     mtu: Option<u16>,
     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 07a6e072cb..665e8a72f8 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<T> = ::std::result::Result<T, Error>;
@@ -88,6 +90,37 @@ fn build_terminated_if_name(if_name: &str) -> Result<Vec<u8>> {
     Ok(terminated_if_name)
 }
 
+fn ipv6_mask_to_prefix(mask: Ipv6Addr) -> Result<u8> {
+    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<F: AsRawFd, T>(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<IpAddr>) -> 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<i32> {
         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, 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).unwrap();
-        tap.set_netmask(netmask).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 714ac77f1e..69501ed6c4 100644
--- a/tests/integration.rs
+++ b/tests/integration.rs
@@ -6330,7 +6330,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,
@@ -7597,7 +7599,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,
@@ -7695,7 +7699,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 4f252dcd7b..d44dcd5ab2 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<u16>,
         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<u16>,
     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 a390804851..d1a8920c4e 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<Ipv4Addr>,
-        netmask: Option<Ipv4Addr>,
+        ip_addr: Option<IpAddr>,
+        netmask: Option<IpAddr>,
         guest_mac: Option<MacAddr>,
         host_mac: &mut Option<MacAddr>,
         mtu: Option<u16>,
diff --git a/vmm/src/api/openapi/cloud-hypervisor.yaml b/vmm/src/api/openapi/cloud-hypervisor.yaml
index 15f99d3035..80a4fa2572 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 0cf2979257..3372dc4ffd 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 326c443231..680272af6a 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 1888b90b32..a2c5b996b4 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<String>,
     #[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<String> {
     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 {


Reviews:
user: login: iggy
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T20:17:06Z
user: login: jinankjain
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-09T04:35:15Z
user: login: rbradford
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-09T09:38:35Z
user: login: rbradford
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-20T16:03:37Z
user: login: alyssais
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-24T15:30:44Z

Review comments:
diff_hunk: @@ -0,0 +1,41 @@
+// Copyright © 2021 Intel Corporation
path: net_gen/src/ipv6.rs
user: login: iggy
body: I'm not sure what the rules are here. I'd think it'd be either copyright the project or the company you work for.

Most of the original files were written before cloud-hypervisor became part of Linux Foundation. Not sure if Linux Foundation has rules about copyright ownership/CLAs/etc.

@rbradford @liuw @likebreath @cloud-hypervisor/cloud-hypervisor-reviewers thoughts?
created_at: 2025-05-02T20:17:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,41 @@
+// Copyright © 2021 Intel Corporation
path: net_gen/src/ipv6.rs
user: login: jinankjain
body: I think we are still doing the company copyright for new files that we are adding. Is there a policy change when moved to Linux Foundation?

For this file it is even more tricky since the code is generated via bindgen.
created_at: 2025-05-09T04:35:15Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,41 @@
+// Copyright © 2021 Intel Corporation
path: net_gen/src/ipv6.rs
user: login: rbradford
body: You can just put it "Copyright 2025 Cloud Hypervisor Authors" - the git history is the canonical source of copyright information (which is why it's important to correctly cite in there where stuff is copied from if its imported.)
created_at: 2025-05-09T09:38:35Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,41 @@
+// Copyright © 2021 Intel Corporation
path: net_gen/src/ipv6.rs
user: login: alyssais
body: > You can just put it "Copyright 2025 Cloud Hypervisor Authors" - the git history is the canonical source of copyright information (which is why it's important to correctly cite in there where stuff is copied from if its imported.)

This concerns me a bit — how do I tell from git history whether copyright is owned by an individual or their employer, for example?
created_at: 2025-05-24T15:30:44Z
author_association: MEMBER

PR comments:
user: login: rbradford
created_at: 2025-05-09T12:22:00Z
body: Code looks good - i've always sort of assumed that folks were happy with manually setting up the IP on the TAP for IPV64 and that the complexity added would be a bit too much to add to CH. But i'm very pleasantly surprised - lets see how the CI handles it.
author_association: MEMBER
user: login: gpanders
created_at: 2025-05-09T15:42:46Z
body: Updated the copyright header and addressed the clippy warnings in [`d0550346cd7d`](https://github.com/cloud-hypervisor/cloud-hypervisor/compare/576203d3f014d9fdb4d304a07b6ce3cfe10743dc..d0550346cd7dcb6b7e99133b292ec76c61ade9cc)
author_association: CONTRIBUTOR
user: login: gpanders
created_at: 2025-05-15T14:41:20Z
body: I updated the commit message to satisfy the commit message linter. Can a maintainer re-trigger CI?
author_association: CONTRIBUTOR
user: login: rbradford
created_at: 2025-05-15T19:53:56Z
body: It looks like all the integration tests are failing - I guess network is failing to come up for the VMs. Perhaps you could try running just one test directly.
author_association: MEMBER
user: login: gpanders
created_at: 2025-05-16T16:00:32Z
body: Figured out what the issue was. I was setting both `ifr_addr` and `ifr_netmask` in the `ifreq` struct and then making back to back ioctls to set the IP address and netmask of the device.

However, `ifr_addr` and `ifr_netmask` are two fields of a union, so setting `ifr_netmask` clobbers the value of `ifr_addr`. This is a bug because now the device is using the netmask as its IP address.

We were also unconditionally calling the SIOCSIFNETMASK ioctl even if no netmask was specified. Again because `ifr_addr` is a union, this meant that an invalid address was being set as the netmask.

The fix is to:

1. Call the ioctls "separately" and set `ifr_netmask` after the first ioctl succeeds
2. Only call the 2nd ioctl if there is a valid netmask

---

(Below is my incorrect original explanation, keeping for posterity).

<details>

~~While working on this patch I was consulting the [`netdevice(7)`](https://man7.org/linux/man-pages/man7/netdevice.7.html) man page, which describes the different ioctls on Linux. This man page says the following for SIOCSIFNETMASK:~~

>```
>SIOCGIFNETMASK
>SIOCSIFNETMASK
>               Get or set the network mask for a device using ifr_netmask.
>               For compatibility, only AF_INET addresses are accepted or
>               returned.  Setting the network mask is a privileged
>               operation.
>```

~~I noticed that Cloud Hypervisor was setting the `ifr_addr` field before setting the netmask, not `ifr_netmask`. I assumed this was a (subtle) bug, and "fixed" it in this PR.~~

~~But this was causing the SIOCSIFNETMASK ioctl to fail with EINVAL. Consulting the actual [Linux source code](https://elixir.bootlin.com/linux/v6.14.6/source/net/ipv4/devinet.c#L1276-L1306) reveals that `ifr_netmask` is actually not being used, it actually is using `ifr_addr`. So Cloud Hypervisor was doing the right thing and it is the man page that is wrong.~~

~~Reverting that "fix" makes the tests pass for me locally.~~

</details>
author_association: CONTRIBUTOR
user: login: rbradford
created_at: 2025-05-20T16:04:00Z
body: Thank you!
author_association: MEMBER