container/
lib.rs

1mod net;
2use std::collections::HashMap;
3use std::ffi::CString;
4use std::fmt::{Display, Formatter};
5use std::{fs};
6use std::os::unix::fs::{PermissionsExt, symlink};
7use std::path::{Path};
8#[allow(unused_imports)]
9use log::{debug, info};
10use nix::{mount, sched, sys, unistd};
11use nix::unistd::ForkResult;
12use anyhow;
13use std::os::unix::io::AsRawFd;
14use env_logger::{fmt::Color, Builder};
15use std::io::Write;
16use log::Level;
17use anyhow::{Context};
18
19const BIND_DEV_NODES: [&str; 6] = [
20    "dev/tty",
21    "dev/null",
22    "dev/zero",
23    "dev/random",
24    "dev/urandom",
25    "dev/full",
26];
27const SYMBOLIC_LINK: [(&str, &str); 5] = [
28    ("/dev/pts/ptmx", "/dev/ptmx"),
29    ("/proc/self/fd", "/dev/fd"),
30    ("/proc/self/fd/0", "/dev/stdin"),
31    ("/proc/self/fd/1", "/dev/stdout"),
32    ("/proc/self/fd/2", "/dev/stderr")
33];
34const NS_TYPE: [(&str, sched::CloneFlags); 7] = [
35    ("pid", sched::CloneFlags::CLONE_NEWPID),
36    ("net", sched::CloneFlags::CLONE_NEWNET),
37    ("uts", sched::CloneFlags::CLONE_NEWUTS),
38    ("cgroup", sched::CloneFlags::CLONE_NEWCGROUP),
39    ("ipc", sched::CloneFlags::CLONE_NEWIPC),
40    ("user", sched::CloneFlags::CLONE_NEWUSER),
41    ("mnt", sched::CloneFlags::CLONE_NEWNS)
42];
43#[derive(Debug, Clone)]
44pub struct Env {
45    record: HashMap<String, String>,
46}
47
48impl Env {
49    #[allow(dead_code)]
50    pub fn new() -> Env {
51        Env { record: HashMap::new() }
52    }
53    #[allow(dead_code)]
54    pub fn len(&self) -> usize {
55        self.record.len()
56    }
57    #[allow(dead_code)]
58    pub fn insert(&mut self, key: String, value: String) -> () {
59        self.record.insert(key, value);
60    }
61}
62
63impl Display for Env {
64    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
65        write!(f, "{:?}", self.as_env())
66    }
67}
68
69impl Default for Env {
70    #[allow(dead_code)]
71    fn default() -> Self {
72        let mut default = HashMap::new();
73        default.extend([
74            ("HOME".to_string(), "/root".to_string()),
75            ("TERM".to_string(), "linux".to_string()),
76            ("USER".to_string(), "root".to_string()),
77            ("PWD".to_string(), "/root".to_string()),
78            ("PATH".to_string(), "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin".to_string())
79        ]);
80        return Env { record: default };
81    }
82}
83
84trait Convert2env {
85    fn as_env(&self) -> Vec<CString>;
86}
87
88impl Convert2env for Env {
89    #[allow(dead_code)]
90    fn as_env(&self) -> Vec<CString> {
91        self.record.iter()
92            .map(|(k, v)| CString::new(format!("{}={}", k, v))
93                .unwrap())
94            .collect::<Vec<CString>>()
95    }
96}
97
98#[derive(Debug, Clone)]
99pub struct Args {
100    pub record: Vec<String>,
101}
102
103impl Args {
104    #[allow(dead_code)]
105    pub fn new() -> Args {
106        Args { record: Vec::<String>::new() }
107    }
108    #[allow(dead_code)]
109    pub fn len(&self) -> usize {
110        self.record.len()
111    }
112    #[allow(dead_code)]
113    pub fn insert(&mut self, arg: String) -> () {
114        self.record.insert(self.record.len(), arg)
115    }
116}
117
118impl Display for Args {
119    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
120        write!(f, "{:?}", self.as_args())
121    }
122}
123
124trait Convert2args {
125    fn as_args(&self) -> Vec<CString>;
126}
127
128impl Convert2args for Args {
129    #[allow(dead_code)]
130    fn as_args(&self) -> Vec<CString> {
131        self.record.iter()
132            .map(|s| CString::new(s.as_str()).unwrap())
133            .collect::<Vec<CString>>()
134    }
135}
136
137pub struct Container<'a> {
138    name: &'a str,
139    root: &'a str,
140    init: &'a str,
141    args: Args,
142    env: Env,
143    pid: unistd::Pid,
144    uid: unistd::Uid,
145    gid: unistd::Gid,
146    out_address: String,
147    ns_address: String
148}
149
150impl<'a> Container<'a> {
151    pub fn new(name: &'a str,
152               root: &'a str,
153               init: &'a str,
154               args: Args,
155               env: Env,
156               out_addr: String,
157               ns_addr: String
158    ) -> Self {
159        Container {
160            name,
161            root,
162            init,
163            args,
164            env,
165            pid: unistd::getpid(),
166            uid: unistd::geteuid(),
167            gid: unistd::getegid(),
168            out_address: out_addr,
169            ns_address: ns_addr
170        }
171    }
172    fn map_usr_grp(&self)->Result<(), anyhow::Error> {
173        debug!("{}",format!("write /proc/{}/setgroups!", self.pid));
174        let mut f = fs::File::create(format!("/proc/{}/setgroups", self.pid))?;
175        f.write("deny".as_bytes())?;
176        debug!("{}", format!("write /proc/{}/uid_map!", self.pid));
177        let mut f = fs::File::create(format!("/proc/{}/uid_map", self.pid))?;
178        f.write(format!("0 {} 1", self.uid).as_bytes())?;
179        debug!("{}", format!("write /proc/{}/gid_map!", self.pid));
180        let mut f = fs::File::create(format!("/proc/{}/gid_map", self.pid))?;
181        f.write(format!("0 {} 1", self.gid).as_bytes())?;
182        Ok(())
183    }
184    fn setup_fs(&self)-> Result<(), anyhow::Error>{
185        info!("root filesystem {}!", self.root);
186        mount::mount(Some(self.root),
187                     self.root,
188                     Some(""),
189                     mount::MsFlags::MS_REC | mount::MsFlags::MS_BIND,
190                     Some(""))
191            .with_context(||format!("mount {} to {} failed!", self.root, self.root))?;
192        let tmp_dir = Path::new("tmp");
193        let mut old_root = Path::new(self.root).join(tmp_dir);
194        debug!("old root: {}", old_root.to_str().unwrap());
195        debug!("pivot root!");
196        unistd::pivot_root(self.root, old_root.to_str().unwrap())
197            .with_context(||format!("pivot root {} to {} failed!", self.root, old_root.display()))?;
198        unistd::chdir("/")?;
199        old_root = Path::new("/").join(tmp_dir);
200        debug!("old root: {}", old_root.to_str().unwrap());
201        debug!("mount proc!");
202        mount::mount(Some("proc"),
203                     "/proc",
204                     Some("proc"),
205                     mount::MsFlags::MS_MGC_VAL,
206                     Some(""))
207            .with_context(||"mount {} procfs failed!")?;
208        debug!("mount /dev!");
209        mount::mount(Some("tmpfs"),
210                     "/dev",
211                     Some("tmpfs"),
212                     mount::MsFlags::MS_NOSUID | mount::MsFlags::MS_STRICTATIME,
213                     Some("mode=755"))
214            .with_context(||"mount tmpfs to /dev with mode=755 failed!")?;
215        debug!("mount /dev/shm!");
216        fs::create_dir("/dev/shm")?;
217        fs::set_permissions("/dev/shm", fs::Permissions::from_mode(0o755))?;
218        mount::mount(Some("tmpfs"),
219                     "/dev/shm",
220                     Some("tmpfs"),
221                     mount::MsFlags::MS_NOSUID | mount::MsFlags::MS_STRICTATIME,
222                     Some("mode=755"))
223            .with_context(||"mount tmpfs to /dev/shm with mode=755 failed!")?;
224
225        for dev in BIND_DEV_NODES {
226            let new_dev = Path::new("/").join(dev);
227            let host_dev = Path::new(old_root.as_path()).join(dev);
228            debug!("bind {} to {}!", host_dev.to_str().unwrap(), new_dev.to_str().unwrap());
229            new_dev.is_file().then(|| fs::remove_file(&new_dev));
230            fs::File::create(&new_dev)?;
231            mount::mount(Some(host_dev.to_str().unwrap()),
232                         &new_dev,
233                         Some("bind"),
234                         mount::MsFlags::MS_BIND,
235                         Some(""))
236                .with_context(||format!("mount bind {} to {} failed!", host_dev.display(), new_dev.display()))?;
237        }
238        Path::new("/dev/pts").exists().eq(&false).then(|| fs::create_dir("/dev/pts").unwrap());
239        fs::set_permissions("/dev/pts", fs::Permissions::from_mode(0o755))?;
240        mount::mount(Some("devpts"),
241                     "/dev/pts",
242                     Some("devpts"),
243                     mount::MsFlags::MS_NOSUID | mount::MsFlags::MS_NOEXEC,
244                     Some("newinstance,ptmxmode=0666,mode=620"))
245            .with_context(||"mount bind devpts to /dev/pts with newinstance,ptmxmode=0666,mode=620 failed!")?;
246        for (src, dst) in SYMBOLIC_LINK {
247            debug!("create symbolic link {} to {}", src, dst);
248            symlink(src, dst)
249                .with_context(||format!("create symbolic link {} to {} failed!", src, dst))?;
250        }
251        debug!("mount sysfs!");
252        Path::new("/sys").exists().eq(&false).then(|| fs::create_dir("/sys").unwrap());
253        mount::mount(Some("sysfs"),
254                     "/sys",
255                     Some("sysfs"),
256                     mount::MsFlags::MS_RDONLY | mount::MsFlags::MS_NOSUID | mount::MsFlags::MS_NOEXEC | mount::MsFlags::MS_NODEV,
257                     Some(""))
258            .with_context(||"mount sysfs to /sys failed!")?;
259        debug!("umount old root {}", old_root.to_str().unwrap());
260        mount::umount2(old_root.to_str().unwrap(), mount::MntFlags::MNT_DETACH)
261            .with_context(||format!("umount old root {} failed!", old_root.display()))?;
262        Ok(())
263    }
264    pub fn start(&'a mut self) -> Result<(), anyhow::Error>{
265        for sys_path in ["proc", "dev", "tmp", "sys"]{
266            Path::new(self.root)
267                .join(sys_path)
268                .exists().eq(&false)
269                .then(||fs::create_dir(Path::new(self.root)
270                    .join(sys_path)));
271        }
272        #[allow(unused_assignments)]
273        let mut net_pid = unistd::Pid::from_raw(0);
274        match unsafe { unistd::fork() }? {
275            ForkResult::Parent { child, .. } => {
276                net_pid = child
277            }
278            ForkResult::Child => {
279                let n = net::Network::new(self.name.to_string(),
280                                              self.out_address.clone(),
281                                              self.ns_address.clone(),
282                                              self.pid.as_raw() as i32);
283                n.start()?;
284                Enter::new(self.pid.as_raw(),
285                           Args::new(),
286                           Default::default(),
287                           false).start(||n.enable_network().unwrap())?;
288
289                std::process::exit(0);
290            }
291        }
292        let flags = sched::CloneFlags::CLONE_NEWPID |
293            sched::CloneFlags::CLONE_NEWNET |
294            sched::CloneFlags::CLONE_NEWNS |
295            sched::CloneFlags::CLONE_NEWUTS |
296            sched::CloneFlags::CLONE_NEWCGROUP |
297            sched::CloneFlags::CLONE_NEWIPC |
298            sched::CloneFlags::CLONE_NEWUSER;
299        debug!("unshare!");
300        sched::unshare(flags)?;
301        mount::mount(Some("none"),
302                     "/",
303                     Some(""),
304                     mount::MsFlags::MS_REC | mount::MsFlags::MS_PRIVATE,
305                     Some(""))?;
306        self.map_usr_grp().with_context(||"failed to map_usr_grp!")?;
307        info!("set hostname to {}", self.name);
308        unistd::sethostname(self.name).with_context(||"failed to set hostname!")?;
309        debug!("fork!");
310        match unsafe { unistd::fork() }? {
311            ForkResult::Parent { child, .. } => {
312                info!("container pid: {}", child);
313                info!("wait net pid {} to setup network!", net_pid);
314                if net_pid.as_raw() != 0 {
315                    sys::wait::waitpid(net_pid, None)?;
316                }
317                sys::wait::waitpid(child, None)?;
318            }
319            ForkResult::Child => {
320                match self.setup_fs(){
321                    Ok(_) => {
322                    }
323                    Err(e) => {
324                        println!("{}", e)
325                    }
326                };
327                let cmd = CString::new(self.init).unwrap();
328                info!("start init {}!", self.init);
329                info!("arguments: {}", self.args);
330                info!("environment : {}", self.env);
331                unistd::execve(cmd.as_c_str().as_ref(),
332                               self.args.as_args().as_slice(),
333                               self.env.as_env().as_slice()).with_context(||format!("failed to execve {:?}!", cmd))?;
334                std::process::exit(0)
335            }
336
337        }
338        Ok(())
339    }
340}
341
342pub struct Enter {
343    pid: i32,
344    cmd: Args,
345    env: Env,
346    console: bool,
347}
348
349impl Enter {
350    pub fn new(pid: i32, cmd: Args, env: Env, console: bool) -> Enter {
351        Enter {
352            pid,
353            cmd,
354            env,
355            console,
356        }
357    }
358    pub fn start<F>(&self, f: F)-> Result<(), anyhow::Error>
359        where F: Fn(){
360        for (name, flag) in NS_TYPE{
361            sched::setns(fs::File::open(format!("/proc/{}/ns/{}", self.pid, name)).unwrap().as_raw_fd(),
362                         flag)?;
363        }
364        unistd::chroot(".")?;
365        unistd::chdir("/")?;
366        unistd::fchdir(fs::File::open("../../..").unwrap().as_raw_fd())?;
367        match unsafe { unistd::fork() }? {
368            ForkResult::Parent { child, .. } => {
369                sys::wait::waitpid(child, None)?;
370            }
371            ForkResult::Child => {
372                if self.console {
373                    let cmd = CString::new(self.cmd.record.first().unwrap().as_str()).unwrap();
374                    info!("start init {}!", self.cmd.record.first().unwrap());
375                    info!("arguments: {}", self.cmd);
376                    info!("environment : {}", self.env);
377                    unistd::execve(cmd.as_c_str().as_ref(),
378                                   self.cmd.as_args().as_slice(),
379                                   self.env.as_env().as_slice())?;
380                }
381                f()
382            }
383        }
384        Ok(())
385    }
386}
387
388pub fn init_logger() {
389    let env = env_logger::Env::default()
390        .filter_or("log", "info")
391        .write_style_or("log", "always");
392    Builder::from_env(env)
393        .format(|buf, record| {
394            let mut style = buf.style();
395            let color = match record.level(){
396                Level::Error => {
397                    Color::Red
398                }
399                Level::Warn => {
400                    Color::Yellow
401                }
402                Level::Info => {
403                    Color::Green
404                }
405                Level::Debug => {
406                    Color::Blue
407                }
408                Level::Trace => {
409                    Color::Magenta
410                }
411            };
412            style.set_color(color).set_intense(false);
413            let timestamp = buf.timestamp();
414            writeln!(
415                buf,
416                "[{} {} {}]: {}",
417                style.clone()
418                    .set_intense(true)
419                    .set_color(Color::Rgb(100, 100, 100))
420                    .set_bold(true)
421                    .value("container"),
422                timestamp,
423                style.clone()
424                    .set_intense(true)
425                    .set_bold(true)
426                    .value(record.level()),
427                style.value(record.args())
428            )
429        })
430        .init();
431}