summary refs log tree commit diff
path: root/net_util
diff options
context:
space:
mode:
authorStephen Barber <smbarber@chromium.org>2018-02-13 22:47:07 -0800
committerchrome-bot <chrome-bot@chromium.org>2018-02-21 01:06:42 -0800
commit308ff60601994ece51e94c1afa3b0e4d0beaea33 (patch)
tree917593cc724d9cdd42563fd2bd8b003bb1ee2089 /net_util
parent8f002f5c4a4c294b8838560948649b655dd3d772 (diff)
downloadcrosvm-308ff60601994ece51e94c1afa3b0e4d0beaea33.tar
crosvm-308ff60601994ece51e94c1afa3b0e4d0beaea33.tar.gz
crosvm-308ff60601994ece51e94c1afa3b0e4d0beaea33.tar.bz2
crosvm-308ff60601994ece51e94c1afa3b0e4d0beaea33.tar.lz
crosvm-308ff60601994ece51e94c1afa3b0e4d0beaea33.tar.xz
crosvm-308ff60601994ece51e94c1afa3b0e4d0beaea33.tar.zst
crosvm-308ff60601994ece51e94c1afa3b0e4d0beaea33.zip
net_util: add tap support for mac address
Allow get/set for the host mac on the tap interface. Also add read accessors
for the host IP address and netmask, and make using IFF_VNET_HDR optional.

BUG=none
TEST=./build_test

Change-Id: I9999bf5aa8aa35b8cae702d9bc6f94602d6fe32e
Reviewed-on: https://chromium-review.googlesource.com/918406
Commit-Ready: Stephen Barber <smbarber@chromium.org>
Tested-by: Stephen Barber <smbarber@chromium.org>
Reviewed-by: Dylan Reid <dgreid@chromium.org>
Diffstat (limited to 'net_util')
-rw-r--r--net_util/src/lib.rs225
1 files changed, 213 insertions, 12 deletions
diff --git a/net_util/src/lib.rs b/net_util/src/lib.rs
index 34cc686..fde576a 100644
--- a/net_util/src/lib.rs
+++ b/net_util/src/lib.rs
@@ -6,12 +6,15 @@ extern crate libc;
 extern crate net_sys;
 extern crate sys_util;
 
+use std::fmt;
 use std::fs::File;
 use std::io::{Read, Write, Result as IoResult, Error as IoError, ErrorKind};
 use std::mem;
 use std::net;
+use std::num::ParseIntError;
 use std::os::raw::*;
 use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
+use std::str::FromStr;
 
 use sys_util::Pollable;
 use sys_util::{ioctl_with_val, ioctl_with_ref, ioctl_with_mut_ref};
@@ -44,6 +47,15 @@ fn create_sockaddr(ip_addr: net::Ipv4Addr) -> net_sys::sockaddr {
     unsafe { mem::transmute(addr_in) }
 }
 
+/// Extract the IPv4 address from a sockaddr. Assumes the sockaddr is a sockaddr_in.
+fn read_ipv4_addr(addr: &net_sys::sockaddr) -> net::Ipv4Addr {
+    debug_assert_eq!(addr.sa_family as u32, net_sys::AF_INET);
+    // This is safe because sockaddr and sockaddr_in are the same size, and we've checked that
+    // this address is AF_INET.
+    let in_addr: net_sys::sockaddr_in = unsafe { mem::transmute(*addr) };
+    net::Ipv4Addr::from(in_addr.sin_addr.s_addr)
+}
+
 fn create_socket() -> Result<net::UdpSocket> {
     // This is safe since we check the return value.
     let sock = unsafe { libc::socket(libc::AF_INET, libc::SOCK_DGRAM, 0) };
@@ -55,6 +67,74 @@ fn create_socket() -> Result<net::UdpSocket> {
     Ok(unsafe { net::UdpSocket::from_raw_fd(sock) })
 }
 
+#[derive(Debug)]
+pub enum MacAddressError {
+    /// Invalid number of octets.
+    InvalidNumOctets(usize),
+    /// Failed to parse octet.
+    ParseOctet(ParseIntError),
+
+}
+
+impl fmt::Display for MacAddressError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match *self {
+            MacAddressError::InvalidNumOctets(n) => write!(f, "invalid number of octets: {}", n),
+            MacAddressError::ParseOctet(ref e) => write!(f, "failed to parse octet: {:?}", e),
+        }
+    }
+}
+/// An Ethernet mac address. This struct is compatible with the C `struct sockaddr`.
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct MacAddress {
+    family: net_sys::sa_family_t,
+    addr: [u8; 6usize],
+    __pad: [u8; 8usize],
+}
+
+impl MacAddress {
+    pub fn octets(&self) -> [u8; 6usize] {
+        self.addr.clone()
+    }
+}
+
+impl FromStr for MacAddress {
+    type Err = MacAddressError;
+
+    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
+        let octets: Vec<&str> = s.split(":").collect();
+        if octets.len() != 6usize {
+            return Err(MacAddressError::InvalidNumOctets(octets.len()));
+        }
+
+        let mut result = MacAddress {
+            family: net_sys::ARPHRD_ETHER,
+            addr: [0; 6usize],
+            __pad: [0; 8usize],
+        };
+
+        for (i, octet) in octets.iter().enumerate() {
+            result.addr[i] = u8::from_str_radix(octet, 16).map_err(MacAddressError::ParseOctet)?;
+        }
+
+        Ok(result)
+    }
+}
+
+impl fmt::Display for MacAddress {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f,
+               "{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
+               self.addr[0],
+               self.addr[1],
+               self.addr[2],
+               self.addr[3],
+               self.addr[4],
+               self.addr[5])
+    }
+}
+
 /// Handle for a network tap interface.
 ///
 /// For now, this simply wraps the file descriptor for the tap device so methods
@@ -68,15 +148,29 @@ pub struct Tap {
 }
 
 pub trait TapT: Read + Write + AsRawFd + Pollable + Send + Sized {
-    /// Create a new tap interface.
-    fn new() -> Result<Self>;
+    /// Create a new tap interface. Set the `vnet_hdr` flag to true to allow offloading on this tap,
+    /// which will add an extra 12 byte virtio net header to incoming frames. Offloading cannot
+    /// be used if `vnet_hdr` is false.
+    fn new(vnet_hdr: bool) -> Result<Self>;
+
+    /// Get the host-side IP address for the tap interface.
+    fn ip_addr(&self) -> Result<net::Ipv4Addr>;
 
     /// Set the host-side IP address for the tap interface.
     fn set_ip_addr(&self, ip_addr: net::Ipv4Addr) -> Result<()>;
 
+    /// Get the netmask for the tap interface's subnet.
+    fn netmask(&self) -> Result<net::Ipv4Addr>;
+
     /// Set the netmask for the subnet that the tap interface will exist on.
     fn set_netmask(&self, netmask: net::Ipv4Addr) -> Result<()>;
 
+    /// Get the mac address for the tap interface.
+    fn mac_address(&self) -> Result<MacAddress>;
+
+    /// Set the mac address for the tap interface.
+    fn set_mac_address(&self, mac_addr: MacAddress) -> Result<()>;
+
     /// Set the offload flags for the tap interface.
     fn set_offload(&self, flags: c_uint) -> Result<()>;
 
@@ -90,7 +184,7 @@ pub trait TapT: Read + Write + AsRawFd + Pollable + Send + Sized {
 }
 
 impl TapT for Tap {
-    fn new() -> Result<Tap> {
+    fn new(vnet_hdr: bool) -> Result<Tap> {
         // Open calls are safe because we give a constant nul-terminated
         // string and verify the result.
         let fd = unsafe {
@@ -115,8 +209,9 @@ impl TapT for Tap {
             let ifru_flags = ifreq.ifr_ifru.ifru_flags.as_mut();
             let name_slice = &mut ifrn_name[..TUNTAP_DEV_FORMAT.len()];
             name_slice.copy_from_slice(TUNTAP_DEV_FORMAT);
-            *ifru_flags = (net_sys::IFF_TAP | net_sys::IFF_NO_PI | net_sys::IFF_VNET_HDR) as
-                          c_short;
+            *ifru_flags = (net_sys::IFF_TAP |
+                           net_sys::IFF_NO_PI |
+                           if vnet_hdr { net_sys::IFF_VNET_HDR } else { 0 }) as c_short;
         }
 
         // ioctl is safe since we call it with a valid tap fd and check the return
@@ -139,6 +234,24 @@ impl TapT for Tap {
            })
     }
 
+    fn ip_addr(&self) -> Result<net::Ipv4Addr> {
+        let sock = create_socket()?;
+        let mut ifreq = self.get_ifreq();
+
+        // ioctl is safe. Called with a valid sock fd, and we check the return.
+        let ret = unsafe { ioctl_with_mut_ref(&sock,
+                                              net_sys::sockios::SIOCGIFADDR as c_ulong,
+                                              &mut ifreq) };
+        if ret < 0 {
+            return Err(Error::IoctlError(IoError::last_os_error()));
+        }
+
+        // We only access one field of the ifru union, hence this is safe.
+        let addr = unsafe { ifreq.ifr_ifru.ifru_addr.as_ref() };
+
+        Ok(read_ipv4_addr(addr))
+    }
+
     fn set_ip_addr(&self, ip_addr: net::Ipv4Addr) -> Result<()> {
         let sock = create_socket()?;
         let addr = create_sockaddr(ip_addr);
@@ -161,6 +274,24 @@ impl TapT for Tap {
         Ok(())
     }
 
+    fn netmask(&self) -> Result<net::Ipv4Addr> {
+        let sock = create_socket()?;
+        let mut ifreq = self.get_ifreq();
+
+        // ioctl is safe. Called with a valid sock fd, and we check the return.
+        let ret = unsafe { ioctl_with_mut_ref(&sock,
+                                              net_sys::sockios::SIOCGIFNETMASK as c_ulong,
+                                              &mut ifreq) };
+        if ret < 0 {
+            return Err(Error::IoctlError(IoError::last_os_error()));
+        }
+
+        // We only access one field of the ifru union, hence this is safe.
+        let addr = unsafe { ifreq.ifr_ifru.ifru_netmask.as_ref() };
+
+        Ok(read_ipv4_addr(addr))
+    }
+
     fn set_netmask(&self, netmask: net::Ipv4Addr) -> Result<()> {
         let sock = create_socket()?;
         let addr = create_sockaddr(netmask);
@@ -169,7 +300,7 @@ impl TapT for Tap {
 
         // We only access one field of the ifru union, hence this is safe.
         unsafe {
-            let ifru_addr = ifreq.ifr_ifru.ifru_addr.as_mut();
+            let ifru_addr = ifreq.ifr_ifru.ifru_netmask.as_mut();
             *ifru_addr = addr;
         }
 
@@ -183,6 +314,49 @@ impl TapT for Tap {
         Ok(())
     }
 
+    fn mac_address(&self) -> Result<MacAddress> {
+        let sock = create_socket()?;
+        let mut ifreq = self.get_ifreq();
+
+        // ioctl is safe. Called with a valid sock fd, and we check the return.
+        let ret = unsafe { ioctl_with_mut_ref(&sock,
+                                              net_sys::sockios::SIOCGIFHWADDR as c_ulong,
+                                              &mut ifreq) };
+        if ret < 0 {
+            return Err(Error::IoctlError(IoError::last_os_error()));
+        }
+
+        // We only access one field of the ifru union, hence this is safe.
+        let ifru_hwaddr = unsafe { ifreq.ifr_ifru.ifru_hwaddr.as_ref() };
+
+        // This is safe since the MacAddress struct is already sized to match the C sockaddr
+        // struct. The address family has also been checked.
+        Ok(unsafe { mem::transmute(*ifru_hwaddr)} )
+    }
+
+    fn set_mac_address(&self, mac_addr: MacAddress) -> Result<()> {
+        let sock = create_socket()?;
+
+        let mut ifreq = self.get_ifreq();
+
+        // We only access one field of the ifru union, hence this is safe.
+        unsafe {
+            let ifru_hwaddr = ifreq.ifr_ifru.ifru_hwaddr.as_mut();
+            // This is safe since the MacAddress struct is already sized to match the C sockaddr
+            // struct.
+            *ifru_hwaddr = std::mem::transmute(mac_addr);
+        }
+
+        // ioctl is safe. Called with a valid sock fd, and we check the return.
+        let ret =
+            unsafe { ioctl_with_ref(&sock, net_sys::sockios::SIOCSIFHWADDR as c_ulong, &ifreq) };
+        if ret < 0 {
+            return Err(Error::IoctlError(IoError::last_os_error()));
+        }
+
+        Ok(())
+    }
+
     fn set_offload(&self, flags: c_uint) -> Result<()> {
         // ioctl is safe. Called with a valid tap fd, and we check the return.
         let ret =
@@ -282,7 +456,7 @@ pub mod fakes {
     }
 
     impl TapT for FakeTap {
-        fn new() -> Result<FakeTap> {
+        fn new(_: bool) -> Result<FakeTap> {
             Ok(FakeTap {
                 tap_file: OpenOptions::new()
                     .read(true)
@@ -293,14 +467,30 @@ pub mod fakes {
             })
         }
 
+        fn ip_addr(&self) -> Result<net::Ipv4Addr> {
+            Ok(net::Ipv4Addr::new(1, 2, 3, 4))
+        }
+
         fn set_ip_addr(&self, _: net::Ipv4Addr) -> Result<()> {
             Ok(())
         }
 
+        fn netmask(&self) -> Result<net::Ipv4Addr> {
+            Ok(net::Ipv4Addr::new(255, 255, 255, 252))
+        }
+
         fn set_netmask(&self, _: net::Ipv4Addr) -> Result<()> {
             Ok(())
         }
 
+        fn mac_address(&self) -> Result<MacAddress> {
+            Ok("01:02:03:04:05:06".parse().unwrap())
+        }
+
+        fn set_mac_address(&self, _: MacAddress) -> Result<()> {
+            Ok(())
+        }
+
         fn set_offload(&self, _: c_uint) -> Result<()> {
             Ok(())
         }
@@ -359,20 +549,31 @@ mod tests {
     use super::*;
 
     #[test]
+    fn parse_mac_address() {
+        assert!("01:02:03:04:05:06".parse::<MacAddress>().is_ok());
+        assert!("01:06".parse::<MacAddress>().is_err());
+        assert!("01:02:03:04:05:06:07:08:09".parse::<MacAddress>().is_err());
+        assert!("not a mac address".parse::<MacAddress>().is_err());
+    }
+
+    #[test]
     fn tap_create() {
-        Tap::new().unwrap();
+        Tap::new(true).unwrap();
     }
 
     #[test]
     fn tap_configure() {
-        let tap = Tap::new().unwrap();
+        let tap = Tap::new(true).unwrap();
         let ip_addr: net::Ipv4Addr = "100.115.92.5".parse().unwrap();
         let netmask: net::Ipv4Addr = "255.255.255.252".parse().unwrap();
+        let mac_addr: MacAddress = "a2:06:b9:3d:68:4d".parse().unwrap();
 
         let ret = tap.set_ip_addr(ip_addr);
         assert_ok_or_perm_denied(ret);
         let ret = tap.set_netmask(netmask);
         assert_ok_or_perm_denied(ret);
+        let ret = tap.set_mac_address(mac_addr);
+        assert_ok_or_perm_denied(ret);
     }
 
     /// This test will only work if the test is run with root permissions and, unlike other tests
@@ -382,14 +583,14 @@ mod tests {
     #[ignore]
     fn root_only_tests() {
         // This line will fail to provide an initialized FD if the test is not run as root.
-        let tap = Tap::new().unwrap();
+        let tap = Tap::new(true).unwrap();
         tap.set_vnet_hdr_size(16).unwrap();
         tap.set_offload(0).unwrap();
     }
 
     #[test]
     fn tap_enable() {
-        let tap = Tap::new().unwrap();
+        let tap = Tap::new(true).unwrap();
 
         let ret = tap.enable();
         assert_ok_or_perm_denied(ret);
@@ -397,7 +598,7 @@ mod tests {
 
     #[test]
     fn tap_get_ifreq() {
-        let tap = Tap::new().unwrap();
+        let tap = Tap::new(true).unwrap();
         let ret = tap.get_ifreq();
         assert_eq!("__BindgenUnionField", format!("{:?}", ret.ifr_ifrn.ifrn_name));
     }