1#![deny(missing_docs)]
2
3#[cfg(feature = "tracing")]
4use crate::telemetry::Telemetry;
5use crate::{
6 child_reaper::ChildReaper,
7 config::{Commands, Config, LogDriver, LogLevel, Verbosity},
8 container_io::{ContainerIO, ContainerIOType},
9 fd_socket::FdSocket,
10 init::{DefaultInit, Init},
11 journal::Journal,
12 listener::{DefaultListener, Listener},
13 pause::Pause,
14 streaming_server::StreamingServer,
15 version::Version,
16};
17use anyhow::{Context, Result, format_err};
18use capnp::text_list::Reader;
19use capnp_rpc::{RpcSystem, rpc_twoparty_capnp::Side, twoparty};
20#[cfg(feature = "tracing")]
21use clap::crate_name;
22use conmon_common::conmon_capnp::conmon::{self, CgroupManager};
23use futures::{AsyncReadExt, FutureExt};
24use libc::_exit;
25use nix::{
26 errno::Errno,
27 sys::signal::Signal,
28 unistd::{ForkResult, fork},
29};
30#[cfg(feature = "tracing")]
31use opentelemetry::trace::{FutureExt as OpenTelemetryFutureExt, TracerProvider};
32#[cfg(feature = "tracing")]
33use opentelemetry_sdk::trace::SdkTracerProvider;
34use std::{fs::File, io::Write, os::fd::RawFd, path::Path, process, str::FromStr, sync::Arc};
35use tokio::{
36 fs,
37 runtime::{Builder, Handle},
38 signal::unix::{SignalKind, signal},
39 sync::{RwLock, oneshot},
40 task::{self, LocalSet},
41};
42use tokio_util::compat::TokioAsyncReadCompatExt;
43use tracing::{Instrument, debug, debug_span, info};
44#[cfg(feature = "tracing")]
45use tracing_opentelemetry::OpenTelemetrySpanExt;
46use tracing_subscriber::{filter::LevelFilter, layer::SubscriberExt, prelude::*};
47use twoparty::VatNetwork;
48
49#[derive(Debug)]
50pub struct Server {
52 config: Arc<Config>,
54
55 reaper: Arc<ChildReaper>,
57
58 fd_socket: Arc<FdSocket>,
60
61 #[cfg(feature = "tracing")]
63 tracer: Option<SdkTracerProvider>,
64
65 streaming_server: Arc<RwLock<StreamingServer>>,
67}
68
69impl Server {
70 pub(crate) fn config(&self) -> &Arc<Config> {
72 &self.config
73 }
74
75 pub(crate) fn reaper(&self) -> &Arc<ChildReaper> {
77 &self.reaper
78 }
79
80 pub(crate) fn fd_socket(&self) -> &Arc<FdSocket> {
82 &self.fd_socket
83 }
84
85 #[cfg(feature = "tracing")]
87 pub(crate) fn tracer(&self) -> &Option<SdkTracerProvider> {
88 &self.tracer
89 }
90
91 pub(crate) fn streaming_server(&self) -> &Arc<RwLock<StreamingServer>> {
93 &self.streaming_server
94 }
95
96 pub fn new() -> Result<Self> {
98 let server = Self {
99 config: Arc::new(Config::default()),
100 reaper: Default::default(),
101 fd_socket: Default::default(),
102 #[cfg(feature = "tracing")]
103 tracer: Default::default(),
104 streaming_server: Default::default(),
105 };
106
107 if let Some(v) = server.config().version() {
108 Version::new(v == Verbosity::Full).print();
109 process::exit(0);
110 }
111
112 if let Some(v) = server.config().version_json() {
113 Version::new(v == Verbosity::Full).print_json()?;
114 process::exit(0);
115 }
116
117 if let Some(Commands::Pause {
118 base_path,
119 pod_id,
120 ipc,
121 pid,
122 net,
123 user,
124 uts,
125 uid_mappings,
126 gid_mappings,
127 }) = server.config().command()
128 {
129 Pause::run(
130 base_path,
131 pod_id,
132 *ipc,
133 *pid,
134 *net,
135 *user,
136 *uts,
137 uid_mappings,
138 gid_mappings,
139 )
140 .context("run pause")?;
141 process::exit(0);
142 }
143
144 server.config().validate().context("validate config")?;
145
146 Self::init().context("init self")?;
147 Ok(server)
148 }
149
150 fn close_inherited_fds() {
163 for fd in Self::inherited_fds() {
164 if unsafe { libc::fcntl(fd, libc::F_GETFD) } != -1 {
179 unsafe { libc::close(fd) };
180 }
181 }
182 }
183
184 fn inherited_fds() -> Vec<RawFd> {
194 const {
195 assert!(
196 cfg!(target_os = "linux"),
197 "inherited fds are enumerated through /proc/self/fd, which is Linux only"
198 )
199 };
200
201 let mut fds = Vec::new();
202
203 if let Ok(entries) = std::fs::read_dir("/proc/self/fd") {
204 for entry in entries.flatten() {
205 if let Some(fd) = entry
206 .file_name()
207 .to_str()
208 .and_then(|name| name.parse::<RawFd>().ok())
209 && fd > 2
210 {
211 fds.push(fd);
212 }
213 }
214 }
215
216 fds
217 }
218
219 pub fn start(self) -> Result<()> {
221 if !self.config().skip_fork() {
227 match unsafe { fork()? } {
228 ForkResult::Parent { child, .. } => {
229 write!(File::create(self.config().conmon_pidfile())?, "{child}")?;
230 unsafe { _exit(0) };
231 }
232 ForkResult::Child => (),
233 }
234 }
235
236 Self::close_inherited_fds();
239
240 let ret = unsafe { libc::prctl(libc::PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0) };
242 if ret != 0 {
243 return Err(Errno::last()).context("set child subreaper");
244 }
245
246 #[cfg(feature = "tracing")]
247 let tracer = self.tracer().clone();
248
249 debug!("Configuring Tokio runtime with current_thread");
250 let rt = Builder::new_current_thread()
251 .enable_io()
252 .enable_time()
253 .build()?;
254 rt.block_on(self.spawn_tasks())?;
255
256 #[cfg(feature = "tracing")]
257 if let Some(tracer) = tracer {
258 tracer.shutdown().context("shutdown tracer")?;
259 }
260
261 rt.shutdown_timeout(std::time::Duration::from_secs(15));
262 Ok(())
263 }
264
265 fn init() -> Result<()> {
266 let init = Init::<DefaultInit>::default();
267 init.unset_locale()?;
268 init.set_default_umask();
269 init.set_oom_score("-1000")
272 }
273
274 fn init_logging(&mut self) -> Result<()> {
275 let level = LevelFilter::from_str(self.config().log_level().as_ref())
276 .context("convert log level filter")?;
277
278 #[cfg(feature = "tracing")]
279 let telemetry_layer = if self.config().enable_tracing() {
280 let tracer = Telemetry::layer(self.config().tracing_endpoint())
281 .context("build telemetry layer")?;
282
283 self.tracer = Some(tracer.clone());
284
285 tracing_opentelemetry::layer()
286 .with_tracer(tracer.tracer(crate_name!()))
287 .into()
288 } else {
289 None
290 };
291
292 let registry = tracing_subscriber::registry();
293 #[cfg(feature = "tracing")]
294 let registry = registry.with(telemetry_layer);
295
296 match self.config().log_driver() {
297 LogDriver::None => {}
298 LogDriver::Stdout => {
299 let layer = tracing_subscriber::fmt::layer()
300 .with_target(true)
301 .with_line_number(true)
302 .with_filter(level);
303 registry
304 .with(layer)
305 .try_init()
306 .context("init stdout fmt layer")?;
307 info!("Using stdout logger");
308 }
309 LogDriver::Systemd => {
310 let layer = tracing_subscriber::fmt::layer()
311 .with_target(true)
312 .with_line_number(true)
313 .without_time()
314 .with_writer(Journal)
315 .with_filter(level);
316 registry
317 .with(layer)
318 .try_init()
319 .context("init journald fmt layer")?;
320 info!("Using systemd/journald logger");
321 }
322 }
323 info!("Set log level to: {}", self.config().log_level());
324 Ok(())
325 }
326
327 async fn spawn_tasks(mut self) -> Result<()> {
329 self.init_logging().context("init logging")?;
330
331 let (shutdown_tx, shutdown_rx) = oneshot::channel();
332 let socket = self.config().socket();
333 let fd_socket = self.config().fd_socket();
334 let reaper = self.reaper.clone();
335
336 let signal_handler_span = debug_span!("signal_handler");
337 let backend_span = debug_span!("backend");
338
339 #[cfg(feature = "tracing")]
343 let result = task::spawn_blocking(move || {
344 Handle::current().block_on(async move {
345 let local = LocalSet::new();
346
347 local.spawn_local(
349 Self::start_signal_handler(reaper, socket, fd_socket, shutdown_tx)
350 .with_context(signal_handler_span.context())
351 .instrument(signal_handler_span),
352 );
353
354 local
356 .run_until(
357 self.start_backend(shutdown_rx)
358 .with_context(backend_span.context())
359 .instrument(backend_span),
360 )
361 .await
362 })
363 })
364 .await?;
365 #[cfg(not(feature = "tracing"))]
366 let result = task::spawn_blocking(move || {
367 Handle::current().block_on(async move {
368 let local = LocalSet::new();
369
370 local.spawn_local(
372 Self::start_signal_handler(reaper, socket, fd_socket, shutdown_tx)
373 .instrument(signal_handler_span),
374 );
375
376 local
378 .run_until(self.start_backend(shutdown_rx).instrument(backend_span))
379 .await
380 })
381 })
382 .await?;
383 result
384 }
385
386 async fn start_signal_handler<T: AsRef<Path>>(
387 reaper: Arc<ChildReaper>,
388 socket: T,
389 fd_socket: T,
390 shutdown_tx: oneshot::Sender<()>,
391 ) -> Result<()> {
392 let mut sigterm = signal(SignalKind::terminate())?;
393 let mut sigint = signal(SignalKind::interrupt())?;
394
395 tokio::select! {
396 _ = sigterm.recv() => {
397 info!("Received SIGTERM");
398 }
399 _ = sigint.recv() => {
400 info!("Received SIGINT");
401 }
402 }
403
404 if let Some(pause) = Pause::maybe_shared() {
405 pause.stop();
406 }
407
408 debug!("Starting grandchildren cleanup task");
409 reaper
411 .kill_grandchildren(Signal::SIGKILL)
412 .await
413 .context("unable to kill grandchildren")?;
414
415 debug!("Sending shutdown message");
416 shutdown_tx
417 .send(())
418 .map_err(|_| format_err!("unable to send shutdown message"))?;
419
420 debug!("Removing socket file {}", socket.as_ref().display());
421 fs::remove_file(socket)
422 .await
423 .context("remove existing socket file")?;
424
425 debug!("Removing fd socket file {}", fd_socket.as_ref().display());
426 fs::remove_file(fd_socket)
427 .await
428 .or_else(|err| {
429 if err.kind() == std::io::ErrorKind::NotFound {
430 Ok(())
431 } else {
432 Err(err)
433 }
434 })
435 .context("remove existing fd socket file")
436 }
437
438 async fn start_backend(self, mut shutdown_rx: oneshot::Receiver<()>) -> Result<()> {
439 let listener =
440 Listener::<DefaultListener>::default().bind_long_path(self.config().socket())?;
441 let client: conmon::Client = capnp_rpc::new_client(self);
442
443 loop {
444 let stream = tokio::select! {
445 _ = &mut shutdown_rx => {
446 debug!("Received shutdown message");
447 return Ok(())
448 }
449 stream = listener.accept() => {
450 stream?.0
451 },
452 };
453 let (reader, writer) = TokioAsyncReadCompatExt::compat(stream).split();
454 let network = Box::new(VatNetwork::new(
455 reader,
456 writer,
457 Side::Server,
458 Default::default(),
459 ));
460 let rpc_system = RpcSystem::new(network, Some(client.clone().client));
461 task::spawn_local(Box::pin(rpc_system.map(|_| ())));
462 }
463 }
464}
465
466pub(crate) struct GenerateRuntimeArgs<'a> {
467 pub(crate) config: &'a Config,
468 pub(crate) id: &'a str,
469 pub(crate) container_io: &'a ContainerIO,
470 pub(crate) pidfile: &'a Path,
471 pub(crate) cgroup_manager: CgroupManager,
472}
473
474impl GenerateRuntimeArgs<'_> {
475 const SYSTEMD_CGROUP_ARG: &'static str = "--systemd-cgroup";
476 const RUNTIME_CRUN: &'static str = "crun";
477 const LOG_LEVEL_FLAG_CRUN: &'static str = "--log-level";
478
479 pub fn create_args(
481 self,
482 bundle_path: &Path,
483 global_args: Reader,
484 command_args: Reader,
485 ) -> Result<Vec<String>> {
486 let mut args = Vec::with_capacity(16);
488 args.extend(self.default_args().context("build default runtime args")?);
489
490 if let Some(rr) = self.config.runtime_root() {
491 args.push(format!("--root={}", rr.display()));
492 }
493
494 if self.cgroup_manager == CgroupManager::Systemd {
495 args.push(Self::SYSTEMD_CGROUP_ARG.into());
496 }
497
498 for arg in global_args {
499 args.push(arg?.to_string()?);
500 }
501
502 args.push("create".into());
504 args.push("--bundle".into());
505 args.push(bundle_path.display().to_string());
506 args.push("--pid-file".into());
507 args.push(self.pidfile.display().to_string());
508
509 for arg in command_args {
510 args.push(arg?.to_string()?);
511 }
512
513 if let ContainerIOType::Terminal(terminal) = self.container_io.typ() {
514 args.push(format!("--console-socket={}", terminal.path().display()));
515 }
516
517 args.push(self.id.into());
518
519 debug!("Runtime args {:?}", args.join(" "));
520 Ok(args)
521 }
522
523 pub(crate) fn exec_sync_args(&self, command: Reader) -> Result<Vec<String>> {
525 let mut args = self
526 .exec_sync_args_without_command()
527 .context("exec sync args without command")?;
528
529 for arg in command {
530 args.push(arg?.to_string()?);
531 }
532
533 debug!("Exec args {:?}", args.join(" "));
534 Ok(args)
535 }
536
537 pub(crate) fn exec_sync_args_without_command(&self) -> Result<Vec<String>> {
538 let mut args = Vec::with_capacity(12);
540 args.extend(self.default_args().context("build default runtime args")?);
541
542 if let Some(rr) = self.config.runtime_root() {
543 args.push(format!("--root={}", rr.display()));
544 }
545
546 if self.cgroup_manager == CgroupManager::Systemd {
547 args.push(Self::SYSTEMD_CGROUP_ARG.into());
548 }
549
550 args.push("exec".into());
552 args.push("-d".into());
553
554 if let ContainerIOType::Terminal(terminal) = self.container_io.typ() {
555 args.push(format!("--console-socket={}", terminal.path().display()));
556 args.push("--tty".into());
557 }
558
559 args.push(format!("--pid-file={}", self.pidfile.display()));
560 args.push(self.id.into());
561
562 Ok(args)
563 }
564
565 fn default_args(&self) -> Result<Vec<String>> {
567 let mut args = vec![];
568
569 if self
570 .config
571 .runtime()
572 .file_name()
573 .context("no filename in path")?
574 == Self::RUNTIME_CRUN
575 {
576 debug!("Found crun used as runtime");
577 args.push(format!("--log=journald:{}", self.id));
578
579 match self.config.log_level() {
580 &LogLevel::Debug | &LogLevel::Error => args.push(format!(
581 "{}={}",
582 Self::LOG_LEVEL_FLAG_CRUN,
583 self.config.log_level()
584 )),
585 &LogLevel::Warn => args.push(format!("{}=warning", Self::LOG_LEVEL_FLAG_CRUN)),
586 _ => {}
587 }
588 }
589
590 if let Some(rr) = self.config.runtime_root() {
591 args.push(format!("--root={}", rr.display()));
592 }
593
594 if self.cgroup_manager == CgroupManager::Systemd {
595 args.push(Self::SYSTEMD_CGROUP_ARG.into());
596 }
597
598 Ok(args)
599 }
600}
601
602#[cfg(test)]
603mod tests {
604 use super::*;
605 use std::os::fd::AsRawFd;
606
607 #[test]
608 fn inherited_fds_reports_descriptors_above_stderr() -> Result<()> {
609 let file = tempfile::tempfile()?;
610 let fd = file.as_raw_fd();
611
612 let fds = Server::inherited_fds();
613
614 assert!(
615 fds.contains(&fd),
616 "expected an open descriptor {fd} to be reported, got {fds:?}"
617 );
618 assert!(!fds.contains(&0));
620 assert!(!fds.contains(&1));
621 assert!(!fds.contains(&2));
622
623 Ok(())
624 }
625}