net: Allow passing in a configured tap fd on the command line

Allow the process that spawned crosvm to pass in a configured tap file
descriptor for networking.  If this option is provided then crosvm will
ignore the other networking related command line flags (like mac
address, netmask, etc).

Passing in a configured tap device allows us to run crosvm without
having to give it CAP_NET_ADMIN.

BUG=none
TEST=Start a container and verify that networking still works

Change-Id: I70b9e6ae030d66c4882e4e48804dc2f29d9874ba
Signed-off-by: Chirantan Ekbote <chirantan@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/1081394
Reviewed-by: Zach Reizner <zachr@chromium.org>
This commit is contained in:
Chirantan Ekbote 2018-05-31 15:31:31 -07:00 committed by chrome-bot
parent 92068bca00
commit 5f787217cc
4 changed files with 96 additions and 36 deletions

View file

@ -295,22 +295,22 @@ where
{
/// Create a new virtio network device with the given IP address and
/// netmask.
pub fn new(ip_addr: Ipv4Addr,
netmask: Ipv4Addr,
mac_addr: MacAddress) -> Result<Net<T>, NetError> {
let kill_evt = EventFd::new().map_err(NetError::CreateKillEventFd)?;
pub fn new(
ip_addr: Ipv4Addr,
netmask: Ipv4Addr,
mac_addr: MacAddress,
) -> Result<Net<T>, NetError> {
let tap: T = T::new(true).map_err(NetError::TapOpen)?;
tap.set_ip_addr(ip_addr).map_err(NetError::TapSetIp)?;
tap.set_netmask(netmask)
.map_err(NetError::TapSetNetmask)?;
tap.set_netmask(netmask).map_err(NetError::TapSetNetmask)?;
tap.set_mac_address(mac_addr)
.map_err(NetError::TapSetMacAddress)?;
// Set offload flags to match the virtio features below.
tap.set_offload(net_sys::TUN_F_CSUM | net_sys::TUN_F_UFO | net_sys::TUN_F_TSO4 |
net_sys::TUN_F_TSO6)
.map_err(NetError::TapSetOffload)?;
// Set offload flags to match the virtio features below. If you make any
// changes to this set, also change the corresponding feature set in vm_concierge.
tap.set_offload(
net_sys::TUN_F_CSUM | net_sys::TUN_F_UFO | net_sys::TUN_F_TSO4 | net_sys::TUN_F_TSO6,
).map_err(NetError::TapSetOffload)?;
let vnet_hdr_size = mem::size_of::<virtio_net_hdr_v1>() as i32;
tap.set_vnet_hdr_size(vnet_hdr_size)
@ -318,13 +318,21 @@ where
tap.enable().map_err(NetError::TapEnable)?;
let avail_features =
1 << virtio_net::VIRTIO_NET_F_GUEST_CSUM | 1 << virtio_net::VIRTIO_NET_F_CSUM |
1 << virtio_net::VIRTIO_NET_F_GUEST_TSO4 |
1 << virtio_net::VIRTIO_NET_F_GUEST_UFO |
1 << virtio_net::VIRTIO_NET_F_HOST_TSO4 |
1 << virtio_net::VIRTIO_NET_F_HOST_UFO | 1 << vhost::VIRTIO_F_VERSION_1;
Net::from(tap)
}
/// Creates a new virtio network device from a tap device that has already been
/// configured.
pub fn from(tap: T) -> Result<Net<T>, NetError> {
let avail_features = 1 << virtio_net::VIRTIO_NET_F_GUEST_CSUM
| 1 << virtio_net::VIRTIO_NET_F_CSUM
| 1 << virtio_net::VIRTIO_NET_F_GUEST_TSO4
| 1 << virtio_net::VIRTIO_NET_F_GUEST_UFO
| 1 << virtio_net::VIRTIO_NET_F_HOST_TSO4
| 1 << virtio_net::VIRTIO_NET_F_HOST_UFO
| 1 << vhost::VIRTIO_F_VERSION_1;
let kill_evt = EventFd::new().map_err(NetError::CreateKillEventFd)?;
Ok(Net {
workers_kill_evt: Some(kill_evt.try_clone().map_err(NetError::CloneKillEventFd)?),
kill_evt: kill_evt,

View file

@ -449,6 +449,15 @@ impl AsRawFd for Tap {
}
}
impl FromRawFd for Tap {
unsafe fn from_raw_fd(fd: RawFd) -> Tap {
Tap {
tap_file: File::from_raw_fd(fd),
if_name: [0; 16usize],
}
}
}
pub mod fakes {
use super::*;
use std::fs::OpenOptions;

View file

@ -191,6 +191,27 @@ impl Drop for UnlinkUnixDatagram {
}
}
// Verifies that |raw_fd| is actually owned by this process and duplicates it to ensure that
// we have a unique handle to it.
fn validate_raw_fd(raw_fd: RawFd) -> Result<RawFd> {
// Checking that close-on-exec isn't set helps filter out FDs that were opened by
// crosvm as all crosvm FDs are close on exec.
// Safe because this doesn't modify any memory and we check the return value.
let flags = unsafe { libc::fcntl(raw_fd, libc::F_GETFD) };
if flags < 0 || (flags & libc::FD_CLOEXEC) != 0 {
return Err(Error::FailedCLOEXECCheck);
}
// Duplicate the fd to ensure that we don't accidentally close an fd previously
// opened by another subsystem. Safe because this doesn't modify any memory and
// we check the return value.
let dup_fd = unsafe { libc::fcntl(raw_fd, libc::F_DUPFD_CLOEXEC, 0) };
if dup_fd < 0 {
return Err(Error::FailedToDupFd);
}
Ok(dup_fd as RawFd)
}
fn create_base_minijail(root: &Path, seccomp_policy: &Path) -> Result<Minijail> {
// All child jails run in a new user namespace without any users mapped,
// they run as nobody unless otherwise configured.
@ -248,24 +269,8 @@ fn setup_mmio_bus(cfg: &Config,
.and_then(|fd_osstr| fd_osstr.to_str())
.and_then(|fd_str| fd_str.parse::<c_int>().ok())
.ok_or(Error::InvalidFdPath)?;
unsafe {
// The FD is valid and this process owns it because it exists in /proc/self/fd.
// Ensure |raw_image| is the only owner by first duping it then closing the
// original.
// Checking that close-on-exec isn't set helps filter out FDs that were opened by
// crosvm as all crosvm FDs are close on exec.
let flags = libc::fcntl(raw_fd, libc::F_GETFD);
if flags < 0 || (flags & libc::FD_CLOEXEC) != 0 {
return Err(Error::FailedCLOEXECCheck);
}
let dup_fd = libc::fcntl(raw_fd, libc::F_DUPFD_CLOEXEC, 0) as RawFd;
if dup_fd < 0 {
return Err(Error::FailedToDupFd);
}
libc::close(raw_fd);
File::from_raw_fd(dup_fd)
}
// Safe because we will validate |raw_fd|.
unsafe { File::from_raw_fd(validate_raw_fd(raw_fd)?) }
} else {
OpenOptions::new()
.read(true)
@ -329,7 +334,24 @@ fn setup_mmio_bus(cfg: &Config,
.map_err(Error::RegisterBalloon)?;
// We checked above that if the IP is defined, then the netmask is, too.
if let Some(host_ip) = cfg.host_ip {
if let Some(tap_fd) = cfg.tap_fd {
// Safe because we ensure that we get a unique handle to the fd.
let tap = unsafe { Tap::from_raw_fd(validate_raw_fd(tap_fd)?) };
let net_box = Box::new(devices::virtio::Net::from(tap)
.map_err(|e| Error::NetDeviceNew(e))?);
let jail = if cfg.multiprocess {
let policy_path: PathBuf = cfg.seccomp_policy_dir.join("net_device.policy");
Some(create_base_minijail(empty_root_path, &policy_path)?)
} else {
None
};
device_manager
.register_mmio(net_box, jail, cmdline)
.map_err(Error::RegisterNet)?;
} else if let Some(host_ip) = cfg.host_ip {
if let Some(netmask) = cfg.netmask {
if let Some(mac_address) = cfg.mac_address {
let net_box: Box<devices::virtio::VirtioDevice> = if cfg.vhost_net {

View file

@ -38,6 +38,7 @@ pub mod linux;
pub mod plugin;
use std::net;
use std::os::unix::io::RawFd;
use std::os::unix::net::UnixDatagram;
use std::path::PathBuf;
use std::string::String;
@ -72,6 +73,7 @@ pub struct Config {
netmask: Option<net::Ipv4Addr>,
mac_address: Option<net_util::MacAddress>,
vhost_net: bool,
tap_fd: Option<RawFd>,
wayland_socket_path: Option<PathBuf>,
wayland_dmabuf: bool,
socket_path: Option<PathBuf>,
@ -94,6 +96,7 @@ impl Default for Config {
netmask: None,
mac_address: None,
vhost_net: false,
tap_fd: None,
wayland_socket_path: None,
wayland_dmabuf: false,
socket_path: None,
@ -335,6 +338,17 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
"vhost-net" => {
cfg.vhost_net = true
},
"tap-fd" => {
if cfg.tap_fd.is_some() {
return Err(argument::Error::TooManyArguments("`tap-fd` alread given".to_owned()));
}
cfg.tap_fd = Some(value.unwrap().parse().map_err(|_| {
argument::Error::InvalidValue {
value: value.unwrap().to_owned(),
expected: "this value for `tap-fd` must be an unsigned integer",
}
})?);
}
"help" => return Err(argument::Error::PrintHelp),
_ => unreachable!(),
}
@ -385,6 +399,9 @@ fn run_vm(args: std::env::Args) -> std::result::Result<(), ()> {
Argument::value("plugin", "PATH", "Absolute path to plugin process to run under crosvm."),
Argument::value("plugin-root", "PATH", "Absolute path to a directory that will become root filesystem for the plugin process."),
Argument::flag("vhost-net", "Use vhost for networking."),
Argument::value("tap-fd",
"fd",
"File descriptor for configured tap device. Mutually exclusive with `host_ip`, `netmask`, and `mac`."),
Argument::short_flag('h', "help", "Print help message.")];
let mut cfg = Config::default();
@ -406,6 +423,10 @@ fn run_vm(args: std::env::Args) -> std::result::Result<(), ()> {
if cfg.plugin_root.is_some() && cfg.plugin.is_none() {
return Err(argument::Error::ExpectedArgument("`plugin-root` requires `plugin`".to_owned()));
}
if cfg.tap_fd.is_some() && (cfg.host_ip.is_some() || cfg.netmask.is_some() || cfg.mac_address.is_some()) {
return Err(argument::Error::TooManyArguments(
"`tap_fd` and any of `host_ip`, `netmask`, or `mac` are mutually exclusive".to_owned()));
}
Ok(())
});