9s: Server binary for the 9p file system

Add the 9s crate, which provides an executable that can serve the 9p
file system protocol.  It initially only supports connections over vsock
but can easily be extended to support network and unix domain socket
based connections.

BUG=chromium:703939
TEST=Run the server, have maitred connect to it over vsock, mount the
     9p file system in the guest kernel, share it with the penguin
     container, and run `bonnie++ -r 256 -s 512`
CQ-DEPEND=CL:1121550, CL:1166446

Change-Id: Ia0c72bcf29188bba4c07b6c0a2dd5a83d02339b5
Signed-off-by: Chirantan Ekbote <chirantan@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/1112870
Reviewed-by: Dylan Reid <dgreid@chromium.org>
This commit is contained in:
Chirantan Ekbote 2018-06-22 19:00:30 -07:00 committed by chrome-bot
parent f226e28632
commit a79073ad7d
7 changed files with 553 additions and 0 deletions

10
9s/Cargo.toml Normal file
View file

@ -0,0 +1,10 @@
[package]
name = "9s"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
[dependencies]
getopts = "=0.2.17"
libc = "=0.2.40"
p9 = { path = "../p9" }
sys_util = { path = "../sys_util" }

208
9s/src/main.rs Normal file
View file

@ -0,0 +1,208 @@
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/// Runs a [9P] server.
///
/// [9P]: http://man.cat-v.org/plan_9/5/0intro
extern crate getopts;
extern crate libc;
extern crate p9;
#[macro_use]
extern crate sys_util;
mod vsock;
use std::fmt;
use std::io::{self, BufReader, BufWriter};
use std::net;
use std::num::ParseIntError;
use std::os::raw::c_uint;
use std::result;
use std::str::FromStr;
use std::string;
use std::sync::Arc;
use std::thread;
use sys_util::syslog;
use vsock::*;
const DEFAULT_BUFFER_SIZE: usize = 8192;
// Address family identifiers.
const VSOCK: &'static str = "vsock:";
const UNIX: &'static str = "unix:";
// Usage for this program.
const USAGE: &'static str = "9s [options] {vsock:<port>|unix:<path>|<ip>:<port>}";
enum ListenAddress {
Net(net::SocketAddr),
Unix(String),
Vsock(c_uint),
}
#[derive(Debug)]
enum ParseAddressError {
MissingUnixPath,
MissingVsockPort,
Net(net::AddrParseError),
Unix(string::ParseError),
Vsock(ParseIntError),
}
impl fmt::Display for ParseAddressError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
&ParseAddressError::MissingUnixPath => write!(f, "missing unix path"),
&ParseAddressError::MissingVsockPort => write!(f, "missing vsock port number"),
&ParseAddressError::Net(ref e) => e.fmt(f),
&ParseAddressError::Unix(ref e) => write!(f, "invalid unix path: {}", e),
&ParseAddressError::Vsock(ref e) => write!(f, "invalid vsock port number: {}", e),
}
}
}
impl FromStr for ListenAddress {
type Err = ParseAddressError;
fn from_str(s: &str) -> result::Result<Self, Self::Err> {
if s.starts_with(VSOCK) {
if s.len() > VSOCK.len() {
Ok(ListenAddress::Vsock(s[VSOCK.len()..]
.parse()
.map_err(ParseAddressError::Vsock)?))
} else {
Err(ParseAddressError::MissingVsockPort)
}
} else if s.starts_with(UNIX) {
if s.len() > UNIX.len() {
Ok(ListenAddress::Unix(s[UNIX.len()..]
.parse()
.map_err(ParseAddressError::Unix)?))
} else {
Err(ParseAddressError::MissingUnixPath)
}
} else {
Ok(ListenAddress::Net(
s.parse().map_err(ParseAddressError::Net)?
))
}
}
}
#[derive(Debug)]
enum Error {
Address(ParseAddressError),
Argument(getopts::Fail),
Cid(ParseIntError),
IO(io::Error),
MissingAcceptCid,
Syslog(syslog::Error),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
&Error::Address(ref e) => e.fmt(f),
&Error::Argument(ref e) => e.fmt(f),
&Error::Cid(ref e) => write!(f, "invalid cid value: {}", e),
&Error::IO(ref e) => e.fmt(f),
&Error::MissingAcceptCid => write!(f, "`accept_cid` is required for vsock servers"),
&Error::Syslog(ref e) => write!(f, "failed to initialize syslog: {:?}", e),
}
}
}
type Result<T> = result::Result<T, Error>;
fn handle_client<R: io::Read, W: io::Write>(
root: Arc<str>,
mut reader: R,
mut writer: W,
) -> io::Result<()> {
let mut server = p9::Server::new(&*root);
loop {
server.handle_message(&mut reader, &mut writer)?;
}
}
fn run_vsock_server(root: Arc<str>, port: c_uint, accept_cid: c_uint) -> io::Result<()> {
let listener = VsockListener::bind(port)?;
loop {
let (stream, peer) = listener.accept()?;
if accept_cid != peer.cid {
warn!("ignoring connection from {}:{}", peer.cid, peer.port);
continue;
}
info!("accepted connection from {}:{}", peer.cid, peer.port);
let reader = BufReader::with_capacity(DEFAULT_BUFFER_SIZE, stream.try_clone()?);
let writer = BufWriter::with_capacity(DEFAULT_BUFFER_SIZE, stream);
let server_root = root.clone();
thread::spawn(move || {
if let Err(e) = handle_client(server_root, reader, writer) {
error!(
"error while handling client {}:{}: {}",
peer.cid, peer.port, e
);
}
});
}
}
fn main() -> Result<()> {
let mut opts = getopts::Options::new();
opts.optopt(
"",
"accept_cid",
"only accept connections from this vsock context id",
"CID",
);
opts.optopt(
"r",
"root",
"root directory for clients (default is \"/\")",
"PATH",
);
opts.optflag("h", "help", "print this help menu");
let matches = opts.parse(std::env::args_os().skip(1))
.map_err(Error::Argument)?;
if matches.opt_present("h") || matches.free.len() == 0 {
print!("{}", opts.usage(USAGE));
return Ok(());
}
syslog::init().map_err(Error::Syslog)?;
let root: Arc<str> = Arc::from(matches.opt_str("r").unwrap_or_else(|| "/".into()));
// We already checked that |matches.free| has at least one item.
match matches.free[0]
.parse::<ListenAddress>()
.map_err(Error::Address)?
{
ListenAddress::Vsock(port) => {
let accept_cid = if let Some(cid) = matches.opt_str("accept_cid") {
cid.parse::<c_uint>().map_err(Error::Cid)
} else {
Err(Error::MissingAcceptCid)
}?;
run_vsock_server(root, port, accept_cid).map_err(Error::IO)?;
}
ListenAddress::Net(_) => {
error!("Network server unimplemented");
}
ListenAddress::Unix(_) => {
error!("Unix server unimplemented");
}
}
Ok(())
}

197
9s/src/vsock.rs Normal file
View file

@ -0,0 +1,197 @@
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/// Support for virtual sockets.
use std::io;
use std::mem::{self, size_of};
use std::os::raw::{c_int, c_uchar, c_uint, c_ushort};
use std::os::unix::io::RawFd;
use libc::{self, c_void, sa_family_t, size_t, sockaddr, socklen_t};
// The domain for vsock sockets.
const AF_VSOCK: sa_family_t = 40;
// Vsock equivalent of INADDR_ANY. Indicates the context id of the current endpoint.
const VMADDR_CID_ANY: c_uint = c_uint::max_value();
// The number of bytes of padding to be added to the sockaddr_vm struct. Taken directly
// from linux/vm_sockets.h.
const PADDING: usize = size_of::<sockaddr>()
- size_of::<sa_family_t>()
- size_of::<c_ushort>()
- (2 * size_of::<c_uint>());
#[repr(C)]
struct sockaddr_vm {
svm_family: sa_family_t,
svm_reserved1: c_ushort,
svm_port: c_uint,
svm_cid: c_uint,
svm_zero: [c_uchar; PADDING],
}
/// An address associated with a virtual socket.
pub struct SocketAddr {
pub cid: c_uint,
pub port: c_uint,
}
/// A virtual stream socket.
pub struct VsockStream {
fd: RawFd,
}
impl VsockStream {
pub fn try_clone(&self) -> io::Result<VsockStream> {
// Safe because this doesn't modify any memory and we check the return value.
let dup_fd = unsafe { libc::fcntl(self.fd, libc::F_DUPFD_CLOEXEC, 0) };
if dup_fd < 0 {
return Err(io::Error::last_os_error());
}
Ok(VsockStream { fd: dup_fd })
}
}
impl io::Read for VsockStream {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
// Safe because this will only modify the contents of |buf| and we check the return value.
let ret = unsafe {
handle_eintr_errno!(libc::read(
self.fd,
buf as *mut [u8] as *mut c_void,
buf.len() as size_t
))
};
if ret < 0 {
return Err(io::Error::last_os_error());
}
Ok(ret as usize)
}
}
impl io::Write for VsockStream {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
// Safe because this doesn't modify any memory and we check the return value.
let ret = unsafe {
handle_eintr_errno!(libc::write(
self.fd,
buf as *const [u8] as *const c_void,
buf.len() as size_t,
))
};
if ret < 0 {
return Err(io::Error::last_os_error());
}
Ok(ret as usize)
}
fn flush(&mut self) -> io::Result<()> {
// No buffered data so nothing to do.
Ok(())
}
}
impl Drop for VsockStream {
fn drop(&mut self) {
// Safe because this doesn't modify any memory and we are the only
// owner of the file descriptor.
unsafe { libc::close(self.fd) };
}
}
/// Represents a virtual socket server.
pub struct VsockListener {
fd: RawFd,
}
impl VsockListener {
/// Creates a new `VsockListener` bound to the specified port on the current virtual socket
/// endpoint.
pub fn bind(port: c_uint) -> io::Result<VsockListener> {
// The compiler should optimize this out since these are both compile-time constants.
assert_eq!(size_of::<sockaddr_vm>(), size_of::<sockaddr>());
// Safe because this doesn't modify any memory and we check the return value.
let fd: RawFd =
unsafe { libc::socket(AF_VSOCK as c_int, libc::SOCK_STREAM | libc::SOCK_CLOEXEC, 0) };
if fd < 0 {
return Err(io::Error::last_os_error());
}
// Safe because we are zero-initializing a struct with only integer fields.
let mut svm: sockaddr_vm = unsafe { mem::zeroed() };
svm.svm_family = AF_VSOCK;
svm.svm_cid = VMADDR_CID_ANY;
svm.svm_port = port;
// Safe because this doesn't modify any memory and we check the return value.
let ret = unsafe {
libc::bind(
fd,
&svm as *const sockaddr_vm as *const sockaddr,
size_of::<sockaddr_vm>() as socklen_t,
)
};
if ret < 0 {
return Err(io::Error::last_os_error());
}
// Safe because this doesn't modify any memory and we check the return value.
let ret = unsafe { libc::listen(fd, 1) };
if ret < 0 {
return Err(io::Error::last_os_error());
}
Ok(VsockListener { fd: fd })
}
/// Accepts a new incoming connection on this listener. Blocks the calling thread until a
/// new connection is established. When established, returns the corresponding `VsockStream`
/// and the remote peer's address.
pub fn accept(&self) -> io::Result<(VsockStream, SocketAddr)> {
// Safe because we are zero-initializing a struct with only integer fields.
let mut svm: sockaddr_vm = unsafe { mem::zeroed() };
// Safe because this will only modify |svm| and we check the return value.
let mut socklen: socklen_t = size_of::<sockaddr_vm>() as socklen_t;
let fd = unsafe {
libc::accept4(
self.fd,
&mut svm as *mut sockaddr_vm as *mut sockaddr,
&mut socklen as *mut socklen_t,
libc::SOCK_CLOEXEC,
)
};
if fd < 0 {
return Err(io::Error::last_os_error());
}
if svm.svm_family != AF_VSOCK {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("unexpected address family: {}", svm.svm_family),
));
}
Ok((
VsockStream { fd: fd },
SocketAddr {
cid: svm.svm_cid,
port: svm.svm_port,
},
))
}
}
impl Drop for VsockListener {
fn drop(&mut self) {
// Safe because this doesn't modify any memory and we are the only
// owner of the file descriptor.
unsafe { libc::close(self.fd) };
}
}

16
Cargo.lock generated
View file

@ -1,3 +1,13 @@
[[package]]
name = "9s"
version = "0.1.0"
dependencies = [
"getopts 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
"p9 0.1.0",
"sys_util 0.1.0",
]
[[package]]
name = "aarch64"
version = "0.1.0"
@ -144,6 +154,11 @@ name = "fuchsia-zircon-sys"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "getopts"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "gpu_buffer"
version = "0.1.0"
@ -437,6 +452,7 @@ dependencies = [
"checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de"
"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
"checksum getopts 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)" = "b900c08c1939860ce8b54dc6a89e26e00c04c380fd0e09796799bd7f12861e05"
"checksum libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "6fd41f331ac7c5b8ac259b8bf82c75c0fb2e469bbf37d2becbba9a6a2221965b"
"checksum log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "89f010e843f2b1a31dbd316b3b8d443758bc634bed37aabade59c686d644e0a2"
"checksum proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cd07deb3c6d1d9ff827999c7f9b04cdfd66b1b17ae508e14fe47b620f2282ae0"

View file

@ -8,6 +8,7 @@ lto = true
panic = 'abort'
[workspace]
members = ["9s"]
[features]
plugin = ["plugin_proto", "crosvm_plugin", "protobuf"]

61
seccomp/aarch64/9s.policy Normal file
View file

@ -0,0 +1,61 @@
# Copyright 2018 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
read: 1
write: 1
stat64: 1
open: 1
close: 1
fstat64: 1
lstat64: 1
getdents64: 1
ioctl: arg1 == FIOCLEX
pread64: 1
pwrite64: 1
# Disallow mmap with PROT_EXEC set. The syntax here doesn't allow bit
# negation, thus the manually negated mask constant.
mmap2: arg2 in 0xfffffffb
mprotect: arg2 in 0xfffffffb
rt_sigaction: 1
sigaltstack: 1
munmap: 1
utimensat: 1
brk: 1
uname: 1
accept4: 1
mkdir: 1
sched_getaffinity: 1
getpid: 1
ugetrlimit: 1
set_robust_list: 1
fcntl64: 1
socket: arg0 == AF_UNIX || arg0 == AF_VSOCK
gettimeofday: 1
restart_syscall: 1
exit_group: 1
rt_sigreturn: 1
rename: 1
ftruncate64: 1
connect: 1
madvise: 1
rt_sigprocmask: 1
access: 1
ARM_set_tls: 1
_llseek: 1
exit: 1
fdatasync: 1
set_tid_address: 1
listen: 1
# Disallow clone's other than new threads.
clone: arg0 & 0x00010000
statfs64: 1
link: 1
unlink: 1
fsync: 1
futex: 1
bind: 1
rmdir: 1
# Calling fchown with -1 as the uid/gid will change the ctime but do nothing else.
fchown: arg1 == 0xffffffff && arg2 == 0xffffffff
mremap: 1

60
seccomp/x86_64/9s.policy Normal file
View file

@ -0,0 +1,60 @@
# Copyright 2018 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
read: 1
write: 1
lstat: 1
stat: 1
open: 1
close: 1
fstat: 1
getdents: 1
ioctl: arg1 == FIOCLEX
pwrite64: 1
pread64: 1
# Disallow mmap with PROT_EXEC set. The syntax here doesn't allow bit
# negation, thus the manually negated mask constant.
mmap: arg2 in 0xfffffffb
mprotect: arg2 in 0xfffffffb
utimensat: 1
rt_sigaction: 1
statfs: 1
sigaltstack: 1
munmap: 1
brk: 1
accept4: 1
sched_getaffinity: 1
getpid: 1
getrlimit: 1
fcntl: 1
set_robust_list: 1
link: 1
socket: arg0 == AF_UNIX || arg0 == AF_VSOCK
restart_syscall: 1
exit_group: 1
rt_sigreturn: 1
lseek: 1
uname: 1
connect: 1
rt_sigprocmask: 1
arch_prctl: 1
access: 1
exit: 1
set_tid_address: 1
listen: 1
# Disallow clone's other than new threads.
clone: arg0 & 0x00010000
unlink: 1
madvise: 1
futex: 1
bind: 1
rmdir: 1
# Calling fchown with -1 as the uid/gid will change the ctime but do nothing else.
fchown: arg1 == 0xffffffff && arg2 == 0xffffffff
fsync: 1
fdatasync: 1
ftruncate: 1
mkdir: 1
mremap: 1
rename: 1