use std::collections::HashMap;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::net::{IpAddr, SocketAddr};
use std::os::unix::io::{RawFd, AsRawFd, FromRawFd};
use blake2::{self, Digest};
use failure::{Error, ResultExt};
use ipnetwork::IpNetwork;
use libc::{close};
use nix::sched::{setns};
use nix::sched::CloneFlags;
use nix::sys::socket::sockopt::{ReuseAddr, ReusePort};
use nix::sys::socket::{SockAddr, setsockopt, bind, listen};
use nix::sys::socket::{socket, AddressFamily, SockType, SockFlag, InetAddr};
use serde_json::to_vec;
use unshare::{self, Style};
use lithos::child_config::ChildInstance;
use lithos::container_config::{InstantiatedConfig, TcpPort, replace_vars};
use lithos::sandbox_config::{SandboxConfig, BridgedNetwork};
use lithos::utils;
pub fn setup(sandbox: &SandboxConfig, child: &ChildInstance,
_container: &InstantiatedConfig)
-> Result<(), String>
{
if let Some(ip) = child.ip_address {
_setup_bridged(sandbox, child, ip)
.map_err(|e| e.to_string())
} else {
_setup_isolated(sandbox, child)
.map_err(|e| e.to_string())
}
}
fn interface_name(network: &BridgedNetwork, ip: &IpAddr) -> String {
#[derive(Serialize)]
struct HashSource<'a> {
bridge: &'a str,
ip: &'a IpAddr,
}
let (ip1, ip2) = match *ip {
IpAddr::V4(ip) => (ip.octets()[2], ip.octets()[3]),
IpAddr::V6(ip) => (ip.octets()[14], ip.octets()[15]),
};
let name = format!("li_{:.6}_{:02x}{:02x}",
format!("{:06x}", blake2::Blake2b::digest(&to_vec(&HashSource {
bridge: &network.bridge,
ip: ip,
}).expect("can always serialize"))),
ip1, ip2);
assert!(name.len() <= 15);
return name;
}
fn get_real_pids() -> Result<(u32, u32), Error> {
let mut pid = None::<u32>;
let mut ppid = None::<u32>;
let f = BufReader::new(File::open("/proc/self/status")
.context("can open /proc/self/status")?);
for line in f.lines() {
let line = line.context("can open /proc/self/status")?;
if line.starts_with("Pid:") {
pid = Some(line[5..].trim().parse::<u32>()
.context("can parse pid in /proc/self/status")?);
} else if line.starts_with("PPid:") {
ppid = Some(line[5..].trim().parse::<u32>()
.context("can parse pid in /proc/self/status")?);
} else {
continue;
}
match (pid, ppid) {
(Some(pid), Some(ppid)) => return Ok((pid, ppid)),
_ => continue,
}
}
bail!("can't find ppid");
}
fn _setup_bridged(sandbox: &SandboxConfig, _child: &ChildInstance, ip: IpAddr)
-> Result<(), Error>
{
let net = sandbox.bridged_network.as_ref().expect("bridged network");
let (pid, ppid) = get_real_pids()?;
let my_ns = File::open("/proc/self/ns/net")
.context("can't open namespace")?;
let parent_ns = File::open(&format!("/proc/{}/ns/net", ppid))
.context("can't open parent namespace")?;
let interface = interface_name(net, &ip);
let iinterface = interface.replace("_", "-");
assert!(iinterface != interface);
let mut cmd = unshare::Command::new("/bin/ip");
cmd.arg("link").arg("add");
cmd.arg(&interface);
cmd.arg("type").arg("veth");
cmd.arg("peer").arg("name").arg(&iinterface);
debug!("Running {}", cmd.display(&Style::short()));
match cmd.status() {
Ok(s) if s.success() => {}
Ok(s) => bail!("ip link failed: {}", s),
Err(e) => bail!("ip link failed: {}", e),
}
let mut cmd = unshare::Command::new("/sbin/ip");
cmd.arg("link").arg("set");
cmd.arg("dev").arg(&interface);
cmd.arg("netns").arg(&format!("/proc/{}/fd/{}",
pid, parent_ns.as_raw_fd()));
debug!("Running {}", cmd.display(&Style::short()));
match cmd.status() {
Ok(s) if s.success() => {}
Ok(s) => bail!("ip link failed: {}", s),
Err(e) => bail!("ip link failed: {}", e),
}
setns(parent_ns.as_raw_fd(), CloneFlags::CLONE_NEWNET)?;
let mut cmd = unshare::Command::new("/sbin/brctl");
cmd.arg("addif").arg(&net.bridge).arg(&interface);
debug!("Running {}", cmd.display(&Style::short()));
match cmd.status() {
Ok(s) if s.success() => {}
Ok(s) => bail!("brctl failed: {}", s),
Err(e) => bail!("brctl failed: {}", e),
}
let mut cmd = unshare::Command::new("/sbin/ip");
cmd.arg("link").arg("set");
cmd.arg(&interface);
cmd.arg("up");
debug!("Running {}", cmd.display(&Style::short()));
match cmd.status() {
Ok(s) if s.success() => {}
Ok(s) => bail!("ip link failed: {}", s),
Err(e) => bail!("ip link failed: {}", e),
}
setns(my_ns.as_raw_fd(), CloneFlags::CLONE_NEWNET)?;
let mut cmd = unshare::Command::new("/sbin/ip");
cmd.arg("link").arg("set");
cmd.arg("lo").arg("up");
debug!("Running {}", cmd.display(&Style::short()));
match cmd.status() {
Ok(s) if s.success() => {}
Ok(s) => bail!("ip link failed: {}", s),
Err(e) => bail!("ip link failed: {}", e),
}
let mut cmd = unshare::Command::new("/sbin/ip");
cmd.arg("addr").arg("add");
cmd.arg(&format!("{}",
IpNetwork::new(ip, net.network.prefix())
.expect("network asways valid")));
cmd.arg("dev").arg(&iinterface);
debug!("Running {}", cmd.display(&Style::short()));
match cmd.status() {
Ok(s) if s.success() => {}
Ok(s) => bail!("ip link failed: {}", s),
Err(e) => bail!("ip link failed: {}", e),
}
let mut cmd = unshare::Command::new("/sbin/ip");
cmd.arg("link").arg("set");
cmd.arg(&iinterface);
cmd.arg("up");
debug!("Running {}", cmd.display(&Style::short()));
match cmd.status() {
Ok(s) if s.success() => {}
Ok(s) => bail!("ip link failed: {}", s),
Err(e) => bail!("ip link failed: {}", e),
}
if let Some(gw) = net.default_gateway {
let mut cmd = unshare::Command::new("/sbin/ip");
cmd.arg("route").arg("add");
cmd.arg("default");
cmd.arg("via").arg(&format!("{}", gw));
debug!("Running {}", cmd.display(&Style::short()));
match cmd.status() {
Ok(s) if s.success() => {}
Ok(s) => bail!("ip route failed: {}", s),
Err(e) => bail!("ip route failed: {}", e),
}
}
if net.after_setup_command.len() > 0 {
let mut cmd = unshare::Command::new(&net.after_setup_command[0]);
for item in &net.after_setup_command[1..] {
if item.contains('@') {
cmd.arg(&replace_vars(item, |v| {
match v {
"container_ip" => ip.to_string(),
_ => {
error!("No variable {:?} for after-setup-command. \
Using empty string.", v);
String::new()
}
}
}));
} else {
cmd.arg(item);
}
}
debug!("Running {}", cmd.display(&Style::short()));
match cmd.status() {
Ok(s) if s.success() => {}
Ok(s) => bail!("after-setup-command failed: {}", s),
Err(e) => bail!("after-setup-command failed: {}", e),
}
}
Ok(())
}
fn _setup_isolated(_sandbox: &SandboxConfig, _child: &ChildInstance)
-> Result<(), Error>
{
let mut cmd = unshare::Command::new("/sbin/ip");
cmd.arg("link").arg("set");
cmd.arg("lo").arg("up");
debug!("Running {}", cmd.display(&Style::short()));
match cmd.status() {
Ok(s) if s.success() => {}
Ok(s) => bail!("ip link failed: {}", s),
Err(e) => bail!("ip link failed: {}", e),
}
Ok(())
}
pub fn listen_fds(sandbox: &SandboxConfig, child: &ChildInstance,
container: &InstantiatedConfig)
-> Result<HashMap<RawFd, File>, String>
{
_listen_fds(sandbox, child, container)
.map_err(|e| e.to_string())
}
fn _listen_fds(sandbox: &SandboxConfig, _child: &ChildInstance,
container: &InstantiatedConfig)
-> Result<HashMap<RawFd, File>, Error>
{
let mut res = HashMap::new();
if sandbox.bridged_network.is_some() {
for (&port_no, port) in &container.tcp_ports {
if !port.external {
let sock = open_socket(port_no, port,
container.user_id.or(sandbox.default_user).unwrap_or(0),
container.group_id.or(sandbox.default_group).unwrap_or(0))?;
res.insert(port.fd, sock);
}
}
}
return Ok(res);
}
fn open_socket(port: u16, cfg: &TcpPort, uid: u32, gid: u32)
-> Result<File, Error>
{
let addr = InetAddr::from_std(&SocketAddr::new(cfg.host.0, port));
let sock = {
let _fsuid_guard = utils::FsUidGuard::set(uid, gid);
try!(socket(AddressFamily::Inet,
SockType::Stream, SockFlag::empty(), None)
.map_err(|e| format_err!("Can't create socket: {:?}", e)))
};
let mut result = Ok(());
if cfg.reuse_addr {
result = result.and_then(|_| setsockopt(sock, ReuseAddr, &true));
}
if cfg.reuse_port {
result = result.and_then(|_| setsockopt(sock, ReusePort, &true));
}
result = result.and_then(|_| bind(sock, &SockAddr::Inet(addr)));
result = result.and_then(|_| listen(sock, cfg.listen_backlog));
if let Err(e) = result {
unsafe { close(sock) };
Err(format_err!("Socket option error: {:?}", e))
} else {
Ok(unsafe { File::from_raw_fd(sock) })
}
}