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}