mod.rs 10.7 KB
Newer Older
1 2 3 4 5 6
//! Wrapper around the freebsd jail commands

use std::error::Error;
use errors::GenericError;
use std::collections::HashMap;
use std::process::Command;
Heinz N. Gies's avatar
Heinz N. Gies committed
7
use jail_config::IFace;
Heinz N. Gies's avatar
Heinz N. Gies committed
8
use config::Config;
9
use uuid::Uuid;
Heinz N. Gies's avatar
Heinz N. Gies committed
10 11
use jdb::IdxEntry;
use jail_config::JailConfig;
Heinz N. Gies's avatar
Heinz N. Gies committed
12
use brand::Brand;
13 14 15 16 17 18 19 20 21

#[derive(Debug)]
/// Basic information about a ZFS dataset
pub struct JailOSEntry {
    /// uuid of the jail
    pub uuid: String,
    /// os id of the jail
    pub id: u64,
}
22

23 24 25 26 27
struct CreateArgs {
    args: Vec<String>,
    ifs: Vec<IFace>,
}

28
#[cfg(target_os = "freebsd")]
Heinz N. Gies's avatar
Heinz N. Gies committed
29 30 31 32 33 34 35
static RCTL: &'static str = "rctl";
#[cfg(target_os = "freebsd")]
static JAIL: &'static str = "jail";
#[cfg(not(target_os = "freebsd"))]
static RCTL: &'static str = "echo";
#[cfg(not(target_os = "freebsd"))]
static JAIL: &'static str = "echo";
36 37 38 39 40 41
#[cfg(target_os = "freebsd")]
static IFCONFIG: &'static str = "/sbin/ifconfig";
#[cfg(not(target_os = "freebsd"))]
static IFCONFIG: &'static str = "echo";


Heinz N. Gies's avatar
Heinz N. Gies committed
42 43 44 45 46 47 48 49 50 51 52 53 54 55
/// Jail config
pub struct Jail<'a> {
    /// Index refference
    pub idx: &'a IdxEntry,
    /// Jail configuration
    pub config: JailConfig,
    /// Record from the OS
    pub inner: Option<&'a JailOSEntry>,
    /// Record from the outer OS jail
    pub outer: Option<&'a JailOSEntry>,
}

impl<'a> Jail<'a> {
    /// starts a jail
56 57 58 59

    pub fn brand(&self, config: &Config) -> Result<Brand, Box<Error>> {
        Brand::load(self.config.brand.as_str(), config)
    }
Heinz N. Gies's avatar
Heinz N. Gies committed
60 61
    pub fn start(&self, config: &Config) -> Result<i32, Box<Error>> {
        self.set_rctl()?;
62
        let brand = self.brand(config)?;
Heinz N. Gies's avatar
Heinz N. Gies committed
63

Heinz N. Gies's avatar
Heinz N. Gies committed
64
        brand.init.output(self, config).expect("brand init failed");
Heinz N. Gies's avatar
Heinz N. Gies committed
65

66
        let CreateArgs { args, ifs } = self.create_args(config)?;
Heinz N. Gies's avatar
Heinz N. Gies committed
67 68 69 70 71 72 73 74 75 76 77 78
        debug!("Start jail"; "vm" => self.idx.uuid.hyphenated().to_string(), "args" => args.clone().join(" "));
        let id = start_jail(&self.idx.uuid, args)?;
        let id_str = id.to_string();
        let mut jprefix = String::from("j");
        jprefix.push_str(id_str.as_str());
        jprefix.push(':');
        for iface in ifs.iter() {
            let mut epair = String::from(iface.epair.clone());
            epair.push('a');
            let mut target_name = jprefix.clone();
            target_name.push_str(iface.iface.as_str());
            let args = vec![epair, String::from("name"), target_name];
79
            debug!("destroying epair"; "vm" => self.idx.uuid.hyphenated().to_string(), "args" => args.clone().join(" "));
Heinz N. Gies's avatar
Heinz N. Gies committed
80 81 82 83
            let output = Command::new(IFCONFIG).args(args.clone()).output().expect(
                "ifconfig failed",
            );
            if !output.status.success() {
84
                crit!("failed to destroy interface"; "vm" => self.idx.uuid.hyphenated().to_string());
Heinz N. Gies's avatar
Heinz N. Gies committed
85 86 87
            }
        }
        Ok(0)
Heinz N. Gies's avatar
Heinz N. Gies committed
88
    }
Heinz N. Gies's avatar
Heinz N. Gies committed
89

90 91

    /// stops a jail
Heinz N. Gies's avatar
Heinz N. Gies committed
92
    pub fn stop(&self, config: &Config) -> Result<i32, Box<Error>> {
93
        debug!("Dleting jail"; "vm" => self.idx.uuid.hyphenated().to_string());
94
        let brand = self.brand(config)?;
Heinz N. Gies's avatar
Heinz N. Gies committed
95

Heinz N. Gies's avatar
Heinz N. Gies committed
96
        brand.halt.output(self, config).expect("brand halt failed");;
Heinz N. Gies's avatar
Heinz N. Gies committed
97

98 99 100 101 102 103 104 105
        let output = Command::new(JAIL)
            .args(&["-r", self.idx.uuid.hyphenated().to_string().as_str()])
            .output()
            .expect("jail stop failed");
        if !output.status.success() {
            crit!("Failed to stop jail"; "vm" => self.idx.uuid.hyphenated().to_string());
            return Err(GenericError::bx("Could not stop jail"));
        }
Heinz N. Gies's avatar
Heinz N. Gies committed
106

Heinz N. Gies's avatar
Heinz N. Gies committed
107 108
        brand.halted.output(self, config).expect("brand halted failed");;

Heinz N. Gies's avatar
Heinz N. Gies committed
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
        let _ = self.remove_rctl();
        match self.outer {
            Some(outer) => {
                let id_str = outer.id.to_string();
                let mut jprefix = String::from("j");
                jprefix.push_str(id_str.as_str());
                jprefix.push(':');
                for nic in self.config.nics.clone() {
                    let mut target_name = jprefix.clone();
                    target_name.push_str(nic.interface.as_str());
                    let args = vec![target_name, String::from("destroy")];
                    debug!("renaiming epair"; "vm" => self.idx.uuid.hyphenated().to_string(), "args" => args.clone().join(" "));
                    let output = Command::new(IFCONFIG).args(args.clone()).output().expect(
                        "ifconfig failed",
                    );
                    if !output.status.success() {
                        crit!("failed to rename interface"; "vm" => self.idx.uuid.hyphenated().to_string());
                    }
                }
            }
            None => {
            crit!("Failed to get outer jail id to delete interfaces"; "vm" => self.idx.uuid.hyphenated().to_string())
            }
        }
Heinz N. Gies's avatar
Heinz N. Gies committed
133

Heinz N. Gies's avatar
Heinz N. Gies committed
134 135 136 137 138 139 140 141
        Ok(0)
    }

    fn set_rctl(&self) -> Result<i32, Box<Error>> {
        let limits = self.config.rctl_limits();
        debug!("Setting jail limits"; "vm" => self.idx.uuid.hyphenated().to_string(), "limits" => limits.clone().join(" "));
        let output = Command::new(RCTL).args(limits.clone()).output().expect(
            "limit failed",
142 143
        );
        if !output.status.success() {
Heinz N. Gies's avatar
Heinz N. Gies committed
144 145
            crit!("failed to set resource limits"; "vm" => self.idx.uuid.hyphenated().to_string());
            return Err(GenericError::bx("Could not set jail limits"));
146
        }
Heinz N. Gies's avatar
Heinz N. Gies committed
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
        Ok(0)
    }

    fn remove_rctl(&self) -> Result<i32, Box<Error>> {
        let mut prefix = String::from("jail:");
        prefix.push_str(self.idx.uuid.hyphenated().to_string().as_str());
        let limit_args = vec!["-r", prefix.as_str()];
        debug!("removing rctl limits"; "vm" => self.idx.uuid.hyphenated().to_string(), "args" => limit_args.clone().join(" "));
        let output = Command::new(RCTL).args(limit_args).output().expect(
            "rctl failed",
        );

        if !output.status.success() {
            crit!("failed to remove resource limits"; "vm" => self.idx.uuid.hyphenated().to_string());
            return Err(GenericError::bx("Could not remove resource limits"));
        }
        Ok(0)
164
    }
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228

    fn create_args(&self, config: &Config) -> Result<CreateArgs, Box<Error>> {

        let brand = self.brand(config)?;


        let uuid = self.idx.uuid.hyphenated().to_string();
        let mut name = String::from("name=");
        name.push_str(uuid.as_str());
        let mut path = String::from("path=/");
        path.push_str(self.idx.root.as_str());
        path.push_str("/root");
        let mut hostuuid = String::from("host.hostuuid=");
        hostuuid.push_str(uuid.as_str());
        let mut hostname = String::from("host.hostname=");
        hostname.push_str(self.config.hostname.as_str());
        let mut args = vec![
            String::from("-i"),
            String::from("-c"),
            String::from("persist"),
            name,
            path,
            hostuuid,
            hostname,
        ];
        let mut ifs = Vec::new();

        // Basic stuff I don't know what it does
        let mut devfs_ruleset = String::from("devfs_ruleset=");
        devfs_ruleset.push_str(config.settings.devfs_ruleset.to_string().as_str());
        args.push(devfs_ruleset);
        args.push(String::from("securelevel=2"));
        args.push(String::from("sysvmsg=new"));
        args.push(String::from("sysvsem=new"));
        args.push(String::from("sysvshm=new"));

        // for nested jails
        args.push(String::from("allow.raw_sockets"));
        args.push(String::from("children.max=1"));

        // let mut exec_stop = String::from("exec.stop=");
        let mut exec_start = String::from("exec.start=");
        args.push(String::from("vnet=new"));
        for nic in self.config.nics.iter() {
            // see https://lists.freebsd.org/pipermail/freebsd-jail//2016-December/003305.html
            let iface: IFace = nic.get_iface(config, &self.idx.uuid)?;
            ifs.push(iface.clone());
            let mut vnet_iface = String::from("vnet.interface=");
            vnet_iface.push_str(iface.epair.as_str());
            vnet_iface.push('b');

            args.push(vnet_iface);

            exec_start.push_str(iface.start_script.as_str());
        }
        if !self.config.nics.is_empty() {
            exec_start.push_str("/sbin/ifconfig lo0 127.0.0.1 up; ");
        };

        // inner jail configuration
        exec_start.push_str(brand.boot.to_string(self, config).as_str());
        args.push(exec_start);
        Ok(CreateArgs { args, ifs })
    }
229
}
Heinz N. Gies's avatar
Heinz N. Gies committed
230

231
#[cfg(not(target_os = "freebsd"))]
232
fn start_jail(_uuid: &Uuid, _args: Vec<String>) -> Result<u64, Box<Error>> {
233 234 235 236
    Ok(42)
}

#[cfg(target_os = "freebsd")]
Heinz N. Gies's avatar
Heinz N. Gies committed
237
fn start_jail(uuid: &Uuid, args: Vec<String>) -> Result<u64, Box<Error>> {
Heinz N. Gies's avatar
Heinz N. Gies committed
238 239 240
    let output = Command::new(JAIL).args(args.clone()).output().expect(
        "jail failed",
    );
241
    let reply = String::from_utf8_lossy(&output.stdout).into_owned();
242
    if output.status.success() {
243 244 245
        // the Jail command has a bug that it will not honor -q
        // so everything but the first line might be garbage we have to
        // ignore.
Heinz N. Gies's avatar
Heinz N. Gies committed
246
        let mut lines = reply.lines();
247
        let first = lines.next().unwrap();
248
        // this seems odd but we guarnatee our ID is a int this way
249
        let id: u64 = first.trim().parse().unwrap();
250
        Ok(id)
251
    } else {
252
        crit!("Failed to start jail"; "vm" => uuid.hyphenated().to_string().as_str());
253
        Err(GenericError::bx(reply.as_str()))
254 255 256 257
    }
}


258 259 260 261 262 263 264 265 266 267 268 269 270 271
/// reads the zfs datasets in a pool
#[cfg(target_os = "freebsd")]
pub fn list() -> Result<HashMap<String, JailOSEntry>, Box<Error>> {
    debug!("Listing jails");
    let output = Command::new("jls")
        .args(&["-q", "jid", "name"])
        .output()
        .expect("zfs list failed");
    let reply = String::from_utf8_lossy(&output.stdout);
    let mut res = HashMap::new();


    for line in reply.split('\n').filter(|x| *x != "") {
        let entry = deconstruct_entry(line)?;
Heinz N. Gies's avatar
Heinz N. Gies committed
272
        res.insert(entry.uuid.clone(), entry);
273 274 275 276 277 278 279 280
        ()
    }
    Ok(res)
}

/// Reads a dummy jail
#[cfg(not(target_os = "freebsd"))]
pub fn list() -> Result<HashMap<String, JailOSEntry>, Box<Error>> {
281
    let reply = "1 00000000-1f3e-4b11-b0ae-8494bb6ecd52\n2 00000000-1f3e-4b11-b0ae-8494bb6ecd52.00000000-1f3e-4b11-b0ae-8494bb6ecd52\n";
282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306
    let mut res = HashMap::new();

    for line in reply.split('\n').filter(|x| *x != "") {
        let entry = deconstruct_entry(line)?;
        res.insert(entry.uuid.clone(), entry);
        ()
    }
    Ok(res)
}

/// deconstructs a line from zfs list into an `ZFSEntry`.
fn deconstruct_entry(line: &str) -> Result<JailOSEntry, Box<Error>> {
    let mut parts = line.split(' ');
    let n0 = parts.next().ok_or_else(
        || GenericError::bx("JID field missing"),
    )?;
    let id: u64 = n0.parse()?;
    let uuid = parts.next().ok_or_else(
        || GenericError::bx("NAME field missing"),
    )?;

    Ok(JailOSEntry {
        uuid: String::from(uuid),
        id: id,
    })
Heinz N. Gies's avatar
Heinz N. Gies committed
307
}