This commit is contained in:
(null)
2023-09-08 04:42:06 -04:00
parent 543b7133cf
commit d3b493b961
22 changed files with 820 additions and 552 deletions

View File

@@ -192,7 +192,9 @@ impl PatchActions {
destination: mount_point.to_path_buf(),
required: *required,
};
config.mounts.insert(key.to_string_lossy().to_string(), mountspec);
config
.mounts
.insert(key.to_string_lossy().to_string(), mountspec);
}
PatchActions::ModAllow { allows } => {
for allow in allows.iter() {

View File

@@ -114,7 +114,9 @@ impl ConfigMod {
}
}
Self::Volume(name, mount_spec) => {
config.mounts.insert(name.to_string_lossy().to_string(), mount_spec.clone());
config
.mounts
.insert(name.to_string_lossy().to_string(), mount_spec.clone());
}
Self::WorkDir(entry_point, dir) => {
let work_dir = dir.to_string();

View File

@@ -39,6 +39,7 @@ pub(crate) enum RdrAction {
#[arg(short = 'H', action)]
without_header: bool,
},
ShowRules,
}
pub(crate) fn use_rdr_action(
@@ -73,6 +74,14 @@ pub(crate) fn use_rdr_action(
}
}
}
RdrAction::ShowRules => {
let response = do_list_rdr_rules(conn, ())?;
if let Ok(response) = response {
for rule in response.iter() {
println!("{}", rule.to_pf_rule());
}
}
}
}
Ok(())
}

View File

@@ -30,7 +30,7 @@ use oci_util::image_reference::ImageReference;
use std::collections::HashMap;
use std::ffi::OsString;
use xcd::ipc::*;
use xcd::volume::VolumeDriverKind;
use xcd::resources::volume::VolumeDriverKind;
#[derive(Parser, Debug)]
pub(crate) enum VolumeAction {

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2023 Yan Ka, Chiu.
// Copyright (c), , Yan Ka, Chiu.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
@@ -21,11 +21,20 @@
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
use freebsd::libc::*;
use std::io::ErrorKind;
use thiserror::Error;
#[macro_export]
macro_rules! precondition_failure {
macro_rules! errx {
($errno:expr, $($t:tt)*) => {
return Err(xc::container::error::PreconditionFailure::new($errno, anyhow::anyhow!($($t)*)).into())
return Err(xc::container::error::Error::new($errno, anyhow::anyhow!($($t)*)).into())
}
}
#[macro_export]
macro_rules! err {
($errno:expr, $($t:tt)*) => {
xc::container::error::Error::new($errno, anyhow::anyhow!($($t)*))
}
}
@@ -50,27 +59,344 @@ pub enum ExecError {
}
#[derive(Error, Debug)]
pub struct PreconditionFailure {
pub struct Error {
errno: i32,
source: anyhow::Error,
}
impl std::fmt::Display for PreconditionFailure {
impl std::fmt::Display for Error {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(fmt, "{}", self.source)
}
}
impl PreconditionFailure {
pub fn new(errno: i32, source: anyhow::Error) -> PreconditionFailure {
PreconditionFailure { errno, source }
impl Error {
pub fn new(errno: i32, source: anyhow::Error) -> Error {
Error { errno, source }
}
pub fn errno(&self) -> i32 {
self.errno
}
pub fn with_errno(self, errno: i32) -> Error {
Self { errno, ..self }
}
pub fn error_message(&self) -> String {
format!("{:#}", self.source)
}
}
pub trait WithErrno {
fn with_errno(self, errno: i32) -> Error;
}
impl<E: std::error::Error + Send + Sync + 'static> WithErrno for E {
fn with_errno(self, errno: i32) -> Error {
Error {
errno,
source: anyhow::Error::from(self),
}
}
}
impl From<anyhow::Error> for Error {
fn from(error: anyhow::Error) -> Error {
Error {
errno: 128,
source: error,
}
}
}
impl From<std::io::Error> for Error {
fn from(error: std::io::Error) -> Error {
let errno = match error.kind() {
ErrorKind::NotFound => ENOENT,
ErrorKind::OutOfMemory => ENOMEM,
ErrorKind::Interrupted => EINTR,
ErrorKind::Other => ETIMEDOUT,
#[cfg(feature = "io_error_more")]
ErrorKind::Deadlock => EDEADLK,
ErrorKind::AddrInUse => EADDRINUSE,
ErrorKind::BrokenPipe => EPIPE,
ErrorKind::WouldBlock => EWOULDBLOCK,
#[cfg(feature = "io_error_more")]
ErrorKind::NetworkDown => ENETDOWN,
#[cfg(feature = "io_error_more")]
ErrorKind::InvalidData => EINVAL,
#[cfg(feature = "io_error_more")]
ErrorKind::StorageFull => ENOSPC,
#[cfg(feature = "io_error_more")]
ErrorKind::NotSeekable => ENXIO,
#[cfg(feature = "io_error_more")]
ErrorKind::Unsupported => ENOTSUP,
ErrorKind::NotConnected => ENOTCONN,
#[cfg(feature = "io_error_more")]
ErrorKind::IsADirectory => EISDIR,
#[cfg(feature = "io_error_more")]
ErrorKind::InvalidInput => EINVAL,
#[cfg(feature = "io_error_more")]
ErrorKind::FileTooLarge => EFBIG,
#[cfg(feature = "io_error_more")]
ErrorKind::ResourceBusy => EBUSY,
#[cfg(feature = "io_error_more")]
ErrorKind::TooManyLinks => ELOOP,
ErrorKind::AlreadyExists => EEXIST,
ErrorKind::TimedOut => ETIMEDOUT,
#[cfg(feature = "io_error_more")]
ErrorKind::NotADirectory => ENOTDIR,
#[cfg(feature = "io_error_more")]
ErrorKind::FilesystemLoop => ELOOP,
#[cfg(feature = "io_error_more")]
ErrorKind::CrossesDevices => ENOTSUP,
ErrorKind::ConnectionReset => ECONNRESET,
#[cfg(feature = "io_error_more")]
ErrorKind::HostUnreachable => EHOSTUNREACH,
#[cfg(feature = "io_error_more")]
ErrorKind::InvalidFilename => ENAMETOOLONG,
ErrorKind::PermissionDenied => EPERM,
ErrorKind::AddrNotAvailable => EADDRNOTAVAIL,
ErrorKind::ConnectionRefused => ECONNREFUSED,
ErrorKind::ConnectionAborted => ECONNABORTED,
#[cfg(feature = "io_error_more")]
ErrorKind::DirectoryNotEmpty => ENOTEMPTY,
#[cfg(feature = "io_error_more")]
ErrorKind::NetworkUnreachable => ENETUNREACH,
#[cfg(feature = "io_error_more")]
ErrorKind::ReadOnlyFilesystem => EROFS,
#[cfg(feature = "io_error_more")]
ErrorKind::ExecutableFileBusy => ETXTBSY,
#[cfg(feature = "io_error_more")]
ErrorKind::ArgumentListTooLong => E2BIG,
#[cfg(feature = "io_error_more")]
ErrorKind::StaleNetworkFileHandle => ESTALE,
#[cfg(feature = "io_error_more")]
ErrorKind::FilesystemQuotaExceeded => EDQUOT,
_ => 128,
};
Error {
errno,
source: error.into(),
}
}
}
pub mod errno {
macro_rules! export {
($($name:ident, $desc:expr),*) => {
$(pub const $name: i32 = freebsd::libc::$name;)*
pub fn errno_desc(v: i32) -> &'static str {
#[allow(unreachable_patterns)]
match v {
$($name => $desc,)*
_ => "unknown"
}
}
}
}
export!(
EPERM,
"Operation not permitted",
ENOENT,
"No such file or directory",
ESRCH,
"No such process",
EINTR,
"Interrupted system call",
EIO,
"Input/output error",
ENXIO,
"Device not configured",
E2BIG,
"Argument list too long",
ENOEXEC,
"Exec format error",
EBADF,
"Bad file descriptor",
ECHILD,
"No child processes",
EDEADLK,
"Resource deadlock avoided",
ENOMEM,
"Cannot allocate memory",
EACCES,
"Permission denied",
EFAULT,
"Bad address",
ENOTBLK,
"Block device required",
EBUSY,
"Device busy",
EEXIST,
"File exists",
EXDEV,
"Cross-device link",
ENODEV,
"Operation not supported by device",
ENOTDIR,
"Not a directory",
EISDIR,
"Is a directory",
EINVAL,
"Invalid argument",
ENFILE,
"Too many open files in system",
EMFILE,
"Too many open files",
ENOTTY,
"Inappropriate ioctl for device",
ETXTBSY,
"Text file busy",
EFBIG,
"File too large",
ENOSPC,
"No space left on device",
ESPIPE,
"Illegal seek",
EROFS,
"Read-only filesystem",
EMLINK,
"Too many links",
EPIPE,
"Broken pipe",
EDOM,
"Numerical argument out of domain",
ERANGE,
"Result too large",
EAGAIN,
"Resource temporarily unavailable",
EWOULDBLOCK,
"Operation would block",
EINPROGRESS,
"Operation now in progress",
EALREADY,
"Operation already in progress",
ENOTSOCK,
"Socket operation on non-socket",
EDESTADDRREQ,
"Destination address required",
EMSGSIZE,
"Message too long",
EPROTOTYPE,
"Protocol wrong type for socket",
ENOPROTOOPT,
"Protocol not available",
EPROTONOSUPPORT,
"Protocol not supported",
ESOCKTNOSUPPORT,
"Socket type not supported",
EOPNOTSUPP,
"Operation not supported",
ENOTSUP,
"Operation not supported",
EPFNOSUPPORT,
"Protocol family not supported",
EAFNOSUPPORT,
"Address family not supported by protocol family",
EADDRINUSE,
"Address already in use",
EADDRNOTAVAIL,
"Can't assign requested address",
ENETDOWN,
"Network is down",
ENETUNREACH,
"Network is unreachable",
ENETRESET,
"Network dropped connection on reset",
ECONNABORTED,
"Software caused connection abort",
ECONNRESET,
"Connection reset by peer",
ENOBUFS,
"No buffer space available",
EISCONN,
"Socket is already connected",
ENOTCONN,
"Socket is not connected",
ESHUTDOWN,
"Can't send after socket shutdown",
ETOOMANYREFS,
"Too many references: can't splice",
ETIMEDOUT,
"Operation timed out",
ECONNREFUSED,
"Connection refused",
ELOOP,
"Too many levels of symbolic links",
ENAMETOOLONG,
"File name too long",
EHOSTDOWN,
"Host is down",
EHOSTUNREACH,
"No route to host",
ENOTEMPTY,
"Directory not empty",
EPROCLIM,
"Too many processes",
EUSERS,
"Too many users",
EDQUOT,
"Disc quota exceeded",
ESTALE,
"Stale NFS file handle",
EREMOTE,
"Too many levels of remote in path",
EBADRPC,
"RPC struct is bad",
ERPCMISMATCH,
"RPC version wrong",
EPROGUNAVAIL,
"RPC prog. not avail",
EPROGMISMATCH,
"Program version wrong",
EPROCUNAVAIL,
"Bad procedure for program",
ENOLCK,
"No locks available",
ENOSYS,
"Function not implemented",
EFTYPE,
"Inappropriate file type or format",
EAUTH,
"Authentication error",
ENEEDAUTH,
"Need authenticator",
EIDRM,
"Identifier removed",
ENOMSG,
"No message of desired type",
EOVERFLOW,
"Value too large to be stored in data type",
ECANCELED,
"Operation canceled",
EILSEQ,
"Illegal byte sequence",
ENOATTR,
"Attribute not found",
EDOOFUS,
"Programming error",
EBADMSG,
"Bad message",
EMULTIHOP,
"Multihop attempted",
ENOLINK,
"Link has been severed",
EPROTO,
"Protocol error",
ENOTCAPABLE,
"Capabilities insufficient",
ECAPMODE,
"Not permitted in capability mode",
ENOTRECOVERABLE,
"State not recoverable",
EOWNERDEAD,
"Previous owner died",
EINTEGRITY,
"Integrity check failed"
);
}

View File

@@ -105,7 +105,7 @@ impl Exec {
&self,
envs: &HashMap<String, String>,
args: &[String],
) -> Result<Jexec, crate::container::error::PreconditionFailure> {
) -> Result<Jexec, crate::container::error::Error> {
let mut argv = Vec::new();
let mut resolved_envs = if self.clear_env {
HashMap::new()
@@ -117,7 +117,7 @@ impl Exec {
for env in self.required_envs.iter() {
if !resolved_envs.contains_key(env.as_str()) {
return Err(crate::container::error::PreconditionFailure::new(
return Err(crate::container::error::Error::new(
freebsd::libc::ENOENT,
anyhow::anyhow!("missing required environment variable {env}"),
));

View File

@@ -291,7 +291,8 @@ impl CompressionFormatExt for std::fs::File {
pub fn elf_abi_fallback_brand() -> String {
Ctl::new("kern.elf64.fallback_brand")
.expect("cannot sysctl").value_string()
.expect("cannot sysctl")
.value_string()
.expect("cannot get sysctl output as string")
}

View File

@@ -1,81 +0,0 @@
// Copyright (c) 2023 Yan Ka, Chiu.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions, and the following disclaimer,
// without modification, immediately at the beginning of the file.
// 2. The name of the author may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
use anyhow::Context;
use std::path::{Path, PathBuf};
use tracing::error;
use crate::config::inventory::Inventory;
pub(crate) struct InventoryManager {
path: PathBuf,
cached: Inventory,
}
impl InventoryManager {
pub(crate) fn load_from_path(
path: impl AsRef<Path>,
) -> Result<InventoryManager, anyhow::Error> {
let path = path.as_ref().to_path_buf();
let data = std::fs::read_to_string(&path).context("Cannot read config file")?;
let cached: Inventory = match serde_json::from_str(&data).context("Cannot parse config") {
Ok(inventory) => inventory,
Err(_) => {
let default = Inventory::default();
std::fs::write(&path, serde_json::to_vec_pretty(&default).unwrap())
.context("cannot write default inventory")?;
default
}
};
Ok(InventoryManager { path, cached })
}
pub(crate) fn borrow(&self) -> &Inventory {
&self.cached
}
/// Modify the underlying config, and synchronize the changes to underlying config file
pub(crate) fn modify<F>(&mut self, f: F)
where
F: FnOnce(&mut Inventory),
{
let mut config = self.cached.clone();
let old_config = config.clone();
f(&mut config);
if old_config != config {
if let Ok(serialized) = serde_json::to_vec_pretty(&config) {
if std::fs::write(&self.path, serialized).is_err() {
error!(
"failed to write new config to config file at {:?}",
self.path
);
}
} else {
error!("failed to serialize new config to bytes");
}
self.cached = config
}
}
}

View File

@@ -21,11 +21,14 @@
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
use anyhow::Context;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use tracing::error;
use crate::network::Network;
use crate::volume::Volume;
use crate::resources::network::Network;
use crate::resources::volume::Volume;
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
pub(crate) struct Inventory {
@@ -34,3 +37,61 @@ pub(crate) struct Inventory {
#[serde(default)]
pub volumes: HashMap<String, Volume>,
}
/// Handle to access an on disk inventory storage.
///
/// This structure act as a middleman between the real, on disk inventory manifest and logics want
/// to access and modify the inventories. On modification to the inventory via this structure, it
/// automatically serialize the updated inventory and store on disk
pub(crate) struct InventoryManager {
path: PathBuf,
cached: Inventory,
}
impl InventoryManager {
pub(crate) fn load_from_path(
path: impl AsRef<Path>,
) -> Result<InventoryManager, anyhow::Error> {
let path = path.as_ref().to_path_buf();
let data = std::fs::read_to_string(&path).context("Cannot read config file")?;
let cached: Inventory = match serde_json::from_str(&data).context("Cannot parse config") {
Ok(inventory) => inventory,
Err(_) => {
let default = Inventory::default();
std::fs::write(&path, serde_json::to_vec_pretty(&default).unwrap())
.context("cannot write default inventory")?;
default
}
};
Ok(InventoryManager { path, cached })
}
pub(crate) fn borrow(&self) -> &Inventory {
&self.cached
}
/// Modify the underlying config, and synchronize the changes to underlying config file
pub(crate) fn modify<F>(&mut self, f: F)
where
F: FnOnce(&mut Inventory),
{
let mut config = self.cached.clone();
let old_config = config.clone();
f(&mut config);
if old_config != config {
if let Ok(serialized) = serde_json::to_vec_pretty(&config) {
if std::fs::write(&self.path, serialized).is_err() {
error!(
"failed to write new config to config file at {:?}",
self.path
);
}
} else {
error!("failed to serialize new config to bytes");
}
self.cached = config
}
}
}

View File

@@ -21,7 +21,6 @@
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
pub mod config_manager;
pub mod inventory;
use anyhow::{bail, Context};

View File

@@ -23,25 +23,23 @@
// SUCH DAMAGE.
use crate::auth::Credential;
use crate::config::config_manager::InventoryManager;
use crate::config::inventory::Inventory;
use crate::config::XcConfig;
use crate::database::Database;
use crate::dataset::JailedDatasetTracker;
use crate::devfs_store::DevfsRulesetStore;
use crate::image::pull::PullImageError;
use crate::image::ImageManager;
use crate::instantiate::{AppliedInstantiateRequest, InstantiateBlueprint};
use crate::ipc::InstantiateRequest;
use crate::network_manager::NetworkManager;
use crate::port::PortForwardTable;
use crate::registry::JsonRegistryProvider;
use crate::resources::volume::{Volume, VolumeDriverKind};
use crate::resources::Resources;
use crate::site::Site;
use crate::util::TwoWayMap;
use crate::volume::{Volume, VolumeDriverKind, VolumeManager};
use anyhow::Context;
use freebsd::fs::zfs::ZfsHandle;
use freebsd::libc::EIO;
use freebsd::net::pf;
use oci_util::digest::OciDigest;
use oci_util::image_reference::{ImageReference, ImageTag};
@@ -52,8 +50,9 @@ use std::str::FromStr;
use std::sync::Arc;
use tokio::sync::{Mutex, RwLock};
use tracing::{debug, error, info, warn};
use xc::container::error::PreconditionFailure;
use xc::container::error::Error;
use xc::container::ContainerManifest;
use xc::errx;
use xc::image_store::sqlite::SqliteImageStore;
use xc::image_store::ImageRecord;
use xc::models::jail_image::{JailConfig, JailImage};
@@ -66,7 +65,6 @@ mod context_provider {
}
pub struct ServerContext {
pub(crate) network_manager: Arc<Mutex<NetworkManager>>,
pub(crate) sites: HashMap<String, Arc<RwLock<Site>>>,
pub(crate) terminated_sites: Vec<(String, Arc<RwLock<Site>>)>,
@@ -82,10 +80,7 @@ pub struct ServerContext {
pub(crate) ng2jails: HashMap<String, Vec<String>>,
pub(crate) jail2ngs: HashMap<String, Vec<String>>,
pub(crate) inventory: Arc<std::sync::Mutex<InventoryManager>>,
pub(crate) volume_manager: Arc<Mutex<VolumeManager>>,
pub(crate) dataset_tracker: Arc<RwLock<JailedDatasetTracker>>,
pub(crate) resources: Arc<RwLock<Resources>>,
}
impl ServerContext {
@@ -105,14 +100,9 @@ impl ServerContext {
db.perform(xc::res::create_tables)
.expect("cannot create tables");
let inventory = Arc::new(std::sync::Mutex::new(
InventoryManager::load_from_path(&config.inventory).expect("cannot write inventory"),
));
let network_manager = Arc::new(Mutex::new(NetworkManager::new(db, inventory.clone())));
let is = Arc::new(Mutex::new(image_store));
let provider = JsonRegistryProvider::from_path(&config.registries).unwrap();
let resources = Arc::new(RwLock::new(Resources::new(db, &config)));
let image_manager = ImageManager::new(
is,
@@ -121,14 +111,7 @@ impl ServerContext {
Arc::new(Mutex::new(Box::new(provider))),
);
let volume_manager = Arc::new(Mutex::new(VolumeManager::new(
inventory.clone(),
config.default_volume_dataset.clone(),
None,
)));
ServerContext {
network_manager,
alias_map: TwoWayMap::new(),
devfs_store: DevfsRulesetStore::new(config.devfs_id_offset, config.force_devfs_ruleset),
image_manager: Arc::new(RwLock::new(image_manager)),
@@ -138,9 +121,7 @@ impl ServerContext {
port_forward_table: PortForwardTable::new(),
ng2jails: HashMap::new(),
jail2ngs: HashMap::new(),
inventory,
volume_manager,
dataset_tracker: Arc::new(RwLock::new(JailedDatasetTracker::default())),
resources,
}
}
@@ -148,8 +129,13 @@ impl ServerContext {
self.config.clone()
}
pub(crate) fn inventory(&self) -> Inventory {
self.inventory.lock().unwrap().borrow().clone()
pub(crate) async fn inventory(&self) -> crate::config::inventory::Inventory {
self.resources
.read()
.await
.inventory_manager
.borrow()
.clone()
}
pub(crate) fn create_channel(
@@ -380,17 +366,21 @@ impl ServerContext {
}
/// Reload the entry pf anchor "xc-rdr"
pub fn reload_pf_rdr_anchor(&self) -> Result<(), std::io::Error> {
pub fn reload_pf_rdr_anchor(&self) -> Result<(), Error> {
let rules = self.port_forward_table.generate_rdr_rules();
info!(rules, "reloading xc-rdr anchor");
pf::is_pf_enabled().and_then(|enabled| {
if let Err(error) = pf::is_pf_enabled().and_then(|enabled| {
if enabled {
pf::set_rules_unchecked(Some("xc-rdr".to_string()), &rules)
} else {
warn!("pf is not enabled");
Ok(())
}
})
}) {
errx!(EIO, "pfctl error: {error:?}")
} else {
Ok(())
}
}
pub(crate) async fn terminate(&mut self, id: &str) -> Result<(), anyhow::Error> {
@@ -409,9 +399,10 @@ impl ServerContext {
return Err(err);
}
let mut nm = self.network_manager.lock().await;
let mut resources = self.resources.write().await;
// let mut nm = self.network_manager.lock().await;
let addresses = nm
let addresses = resources
.release_addresses(id)
.context("sqlite failure on ip address release")?;
@@ -429,7 +420,9 @@ impl ServerContext {
self.port_forward_table.remove_rules(id);
self.reload_pf_rdr_anchor()?;
self.alias_map.remove_all_referenced(id);
self.dataset_tracker.write().await.release_container(id);
resources.dataset_tracker.release_container(id);
// self.dataset_tracker.write().await.release_container(id);
if let Some(networks) = self.jail2ngs.get(id) {
for network in networks.iter() {
@@ -559,47 +552,25 @@ impl ServerContext {
site.stage(image)?;
let name = request.name.clone();
let applied = {
let network_manager = this.network_manager.clone();
let network_manager = network_manager.lock().await;
let volume_manager = this.volume_manager.clone();
let volume_manager = volume_manager.lock().await;
AppliedInstantiateRequest::new(
request,
image,
&cred,
&network_manager,
&volume_manager,
)?
};
let resources_ref = this.resources.clone();
let mut resources = resources_ref.write().await;
let applied =
{ AppliedInstantiateRequest::new(request, image, &cred, &mut resources)? };
let blueprint = {
let network_manager = this.network_manager.clone();
let mut network_manager = network_manager.lock().await;
let volume_manager = this.volume_manager.clone();
let volume_manager = volume_manager.lock().await;
let dataset_tracker = this.dataset_tracker.clone();
let mut dataset_tracker = dataset_tracker.write().await;
InstantiateBlueprint::new(
id,
image,
applied,
&mut this.devfs_store,
&cred,
&mut network_manager,
&volume_manager,
&mut dataset_tracker,
&mut resources,
)?
};
if pf::is_pf_enabled().unwrap_or_default() {
if let Some(map) = this
.network_manager
.lock()
.await
.get_allocated_addresses(id)
{
if let Some(map) = resources.get_allocated_addresses(id) {
for (network, addresses) in map.iter() {
let table = format!("xc:network:{network}");
let result = pf::table_add_addresses(None, &table, addresses);
@@ -688,7 +659,7 @@ impl ServerContext {
}
pub(crate) async fn list_volumes(&self) -> HashMap<String, Volume> {
self.volume_manager.lock().await.list_volumes()
self.resources.read().await.list_volumes()
}
pub(crate) async fn create_volume(
@@ -698,9 +669,9 @@ impl ServerContext {
kind: VolumeDriverKind,
source: Option<std::path::PathBuf>,
zfs_props: HashMap<String, String>,
) -> Result<(), PreconditionFailure> {
self.volume_manager
.lock()
) -> Result<(), Error> {
self.resources
.write()
.await
.create_volume(kind, name, template, source, zfs_props)
}

View File

@@ -25,7 +25,7 @@ use rusqlite::{Connection, OptionalExtension, Params, Row};
use std::net::IpAddr;
use std::sync::Mutex;
use crate::network::AddressStore;
use crate::resources::network::AddressStore;
pub struct Database {
db: Mutex<Connection>,

View File

@@ -23,11 +23,10 @@
// SUCH DAMAGE.
use crate::auth::Credential;
use crate::dataset::JailedDatasetTracker;
use crate::devfs_store::DevfsRulesetStore;
use crate::ipc::InstantiateRequest;
use crate::network_manager::NetworkManager;
use crate::volume::{Volume, VolumeManager};
use crate::resources::volume::Volume;
use crate::resources::Resources;
use anyhow::Context;
use freebsd::event::EventFdNotify;
@@ -39,12 +38,12 @@ use std::net::IpAddr;
use std::os::fd::{AsRawFd, RawFd};
use varutil::string_interpolation::InterpolatedString;
use xc::container::request::{CopyFileReq, Mount, NetworkAllocRequest};
use xc::errx;
use xc::format::devfs_rules::DevfsRule;
use xc::models::exec::{Jexec, StdioMode};
use xc::models::jail_image::JailImage;
use xc::models::network::{DnsSetting, IpAssign};
use xc::models::EnforceStatfs;
use xc::precondition_failure;
pub struct AppliedInstantiateRequest {
base: InstantiateRequest,
@@ -63,8 +62,7 @@ impl AppliedInstantiateRequest {
mut request: InstantiateRequest,
oci_config: &JailImage,
cred: &Credential,
network_manager: &NetworkManager,
volume_manager: &VolumeManager,
resources: &mut Resources,
) -> anyhow::Result<AppliedInstantiateRequest> {
let existing_ifaces = freebsd::net::ifconfig::interfaces()?;
let available_allows = xc::util::jail_allowables();
@@ -82,7 +80,7 @@ impl AppliedInstantiateRequest {
.as_ref()
.map(|d| format!(" - {d}"))
.unwrap_or_default();
precondition_failure!(
errx!(
ENOENT,
"missing required environment variable: {key}{extra_info}"
);
@@ -142,7 +140,7 @@ impl AppliedInstantiateRequest {
for assign in request.ips.iter() {
let iface = &assign.interface;
if !existing_ifaces.contains(iface) {
precondition_failure!(ENOENT, "missing network interface {iface}");
errx!(ENOENT, "missing network interface {iface}");
}
}
@@ -152,7 +150,7 @@ impl AppliedInstantiateRequest {
if available_allows.contains(allow) {
allows.push(allow.to_string());
} else {
precondition_failure!(EIO, "allow.{allow} is not available on this system");
errx!(EIO, "allow.{allow} is not available on this system");
}
}
allows
@@ -186,16 +184,13 @@ impl AppliedInstantiateRequest {
if !source_path.is_absolute() {
let name = source_path.to_string_lossy().to_string();
match volume_manager.query_volume(&name) {
match resources.query_volume(&name) {
None => {
precondition_failure!(ENOENT, "no such volume {name}")
errx!(ENOENT, "no such volume {name}")
}
Some(volume) => {
if !volume.can_mount(cred.uid()) {
precondition_failure!(
EPERM,
"this user is not allowed to mount the volume"
)
errx!(EPERM, "this user is not allowed to mount the volume")
}
}
}
@@ -206,14 +201,14 @@ impl AppliedInstantiateRequest {
for (key, spec) in mount_specs.iter() {
if spec.required {
precondition_failure!(ENOENT, "Required volume {key:?} is not mounted");
errx!(ENOENT, "Required volume {key:?} is not mounted");
}
}
for req in request.ipreq.iter() {
let network = req.network();
if !network_manager.has_network(&network) {
precondition_failure!(ENOENT, "no such network: {network}");
if !resources.has_network(&network) {
errx!(ENOENT, "no such network: {network}");
}
}
@@ -236,7 +231,7 @@ impl AppliedInstantiateRequest {
let applied = rule.apply(&envs);
match applied.parse::<xc::format::devfs_rules::DevfsRule>() {
Err(error) => {
precondition_failure!(EINVAL, "invaild devfs rule: [{applied}], {error}")
errx!(EINVAL, "invaild devfs rule: [{applied}], {error}")
}
Ok(rule) => devfs_rules.push(rule),
}
@@ -302,9 +297,12 @@ impl InstantiateBlueprint {
request: AppliedInstantiateRequest,
devfs_store: &mut DevfsRulesetStore,
cred: &Credential,
/*
network_manager: &mut NetworkManager,
volume_manager: &VolumeManager,
dataset_tracker: &mut JailedDatasetTracker,
*/
resources: &mut Resources,
) -> anyhow::Result<InstantiateBlueprint> {
let existing_ifaces = freebsd::net::ifconfig::interfaces()?;
let config = oci_config.jail_config();
@@ -312,9 +310,9 @@ impl InstantiateBlueprint {
None => format!("xc-{id}"),
Some(name) => {
if name.parse::<isize>().is_ok() {
precondition_failure!(EINVAL, "Jail name cannot be numeric")
errx!(EINVAL, "Jail name cannot be numeric")
} else if name.contains('.') {
precondition_failure!(EINVAL, "Jail name cannot contain dot (.)")
errx!(EINVAL, "Jail name cannot contain dot (.)")
} else {
name
}
@@ -326,15 +324,12 @@ impl InstantiateBlueprint {
if config.linux {
if !freebsd::exists_kld("linux64") {
precondition_failure!(
errx!(
EIO,
"Linux image require linux64 kmod but it is missing from the system"
);
} else if xc::util::elf_abi_fallback_brand() != "3" {
precondition_failure!(
EIO,
"kern.elf64.fallback_brand did not set to 3 (Linux)"
);
errx!(EIO, "kern.elf64.fallback_brand did not set to 3 (Linux)");
}
}
@@ -348,14 +343,10 @@ impl InstantiateBlueprint {
let mut default_router = None;
for req in request.base.ipreq.iter() {
match network_manager.allocate(vnet, req, id) {
match resources.allocate(vnet, req, id) {
Ok((alloc, router)) => {
if !existing_ifaces.contains(&alloc.interface) {
precondition_failure!(
ENOENT,
"missing network interface {}",
&alloc.interface
);
errx!(ENOENT, "missing network interface {}", &alloc.interface);
}
if let Some(router) = router {
if default_router.is_none() {
@@ -365,28 +356,22 @@ impl InstantiateBlueprint {
ip_alloc.push(alloc);
}
Err(error) => match error {
crate::network_manager::Error::Sqlite(error) => {
crate::resources::network::Error::Sqlite(error) => {
Err(error).context("sqlite error on address allocation")?;
}
crate::network_manager::Error::AllocationFailure(network) => {
precondition_failure!(
ENOENT,
"cannot allocate address from network {network}"
)
crate::resources::network::Error::AllocationFailure(network) => {
errx!(ENOENT, "cannot allocate address from network {network}")
}
crate::network_manager::Error::AddressUsed(addr) => {
precondition_failure!(ENOENT, "address {addr} already consumed")
crate::resources::network::Error::AddressUsed(addr) => {
errx!(ENOENT, "address {addr} already consumed")
}
crate::network_manager::Error::InvalidAddress(addr, network) => {
precondition_failure!(EINVAL, "{addr} is not in the subnet of {network}")
crate::resources::network::Error::InvalidAddress(addr, network) => {
errx!(EINVAL, "{addr} is not in the subnet of {network}")
}
crate::network_manager::Error::NoSuchNetwork(network) => {
precondition_failure!(
ENOENT,
"network {network} is missing from config file"
)
crate::resources::network::Error::NoSuchNetwork(network) => {
errx!(ENOENT, "network {network} is missing from config file")
}
crate::network_manager::Error::Other(error) => {
crate::resources::network::Error::Other(error) => {
Err(error).context("error occured during address allocation")?;
}
},
@@ -411,16 +396,13 @@ impl InstantiateBlueprint {
let volume = if !source_path.is_absolute() {
let name = source_path.to_string_lossy().to_string();
match volume_manager.query_volume(&name) {
match resources.query_volume(&name) {
None => {
precondition_failure!(ENOENT, "no such volume {name}")
errx!(ENOENT, "no such volume {name}")
}
Some(volume) => {
if !volume.can_mount(cred.uid()) {
precondition_failure!(
EPERM,
"this user is not allowed to mount the volume"
)
errx!(EPERM, "this user is not allowed to mount the volume")
} else {
volume
}
@@ -428,18 +410,18 @@ impl InstantiateBlueprint {
}
} else {
match &req.evid {
Maybe::None => precondition_failure!(ENOENT, "missing evidence"),
Maybe::None => errx!(ENOENT, "missing evidence"),
Maybe::Some(fd) => {
println!("process to check evidence");
let Ok(stat) = freebsd::nix::sys::stat::fstat(fd.as_raw_fd()) else {
println!("cannot stat evidence");
precondition_failure!(ENOENT, "cannot stat evidence")
errx!(ENOENT, "cannot stat evidence")
};
let check_stat = freebsd::nix::sys::stat::stat(source_path).unwrap();
println!("c: {}", stat.st_ino);
println!("n: {}", check_stat.st_ino);
if stat.st_ino != check_stat.st_ino {
precondition_failure!(ENOENT, "evidence inode mismatch")
errx!(ENOENT, "evidence inode mismatch")
}
freebsd::nix::unistd::close(fd.as_raw_fd());
@@ -455,18 +437,18 @@ impl InstantiateBlueprint {
added_mount_specs.insert(&req.dest, mount_spec.clone().unwrap());
}
let mount = volume_manager.mount(id, cred, req, mount_spec.as_ref(), &volume)?;
let mount = resources.mount(id, cred, req, mount_spec.as_ref(), &volume)?;
mount_req.push(mount);
}
for dataset in request.base.jail_datasets.iter() {
if dataset_tracker.is_jailed(dataset) {
precondition_failure!(
if resources.dataset_tracker.is_jailed(dataset) {
errx!(
EPERM,
"another container is using this dataset: {dataset:?}"
)
} else {
dataset_tracker.set_jailed(id, dataset)
resources.dataset_tracker.set_jailed(id, dataset)
}
}

View File

@@ -26,8 +26,8 @@ use crate::auth::Credential;
use crate::context::ServerContext;
use crate::image::pull::PullImageError;
use crate::image::push::{PushImageError, PushImageStatusDesc};
use crate::network::Network;
use crate::volume::{Volume, VolumeDriverKind};
use crate::resources::network::Network;
use crate::resources::volume::{Volume, VolumeDriverKind};
use freebsd::event::EventFdNotify;
use freebsd::libc::{EINVAL, EIO};
@@ -333,8 +333,7 @@ async fn instantiate(
.await;
if let Err(error) = instantiate_result {
tracing::error!("instantiate error: {error:#?}");
if let Some(err) = error.downcast_ref::<xc::container::error::PreconditionFailure>()
{
if let Some(err) = error.downcast_ref::<xc::container::error::Error>() {
ipc_err(err.errno(), &err.error_message())
} else {
enoent(error.to_string().as_str())
@@ -525,7 +524,7 @@ pub struct ListNetworkRequest {}
#[derive(Serialize, Deserialize, Debug)]
pub struct ListNetworkResponse {
pub network_info: Vec<crate::network_manager::NetworkInfo>,
pub network_info: Vec<crate::resources::network::NetworkInfo>,
}
#[ipc_method(method = "list_networks")]
@@ -535,9 +534,8 @@ async fn list_networks(
request: ListNetworkRequest,
) -> GenericResult<ListNetworkResponse> {
let context = context.read().await;
let network_manager = context.network_manager.clone();
let network_manager = network_manager.lock().await;
match network_manager.get_network_info() {
let resources = context.resources.read().await;
match resources.get_network_info() {
Ok(network_info) => Ok(ListNetworkResponse { network_info }),
Err(e) => ipc_err(EIO, e.to_string().as_str()),
}
@@ -546,7 +544,6 @@ async fn list_networks(
#[derive(Serialize, Deserialize, Debug)]
pub struct CreateNetworkRequest {
pub name: String,
// pub ext_if: Option<String>,
pub alias_iface: String,
pub bridge_iface: String,
pub subnet: ipcidr::IpCidr,
@@ -562,13 +559,13 @@ async fn create_network(
request: CreateNetworkRequest,
) -> GenericResult<()> {
let context = context.write().await;
let config = context.inventory();
let config = context.inventory().await;
let existing_ifaces = freebsd::net::ifconfig::interfaces().unwrap();
if config.networks.contains_key(&request.name) {
ipc_err(EINVAL, "Network with such name already exists")
} else {
let nm = context.network_manager.clone();
let nm = nm.lock().await;
let nm = context.resources.clone();
let mut nm = nm.write().await;
if !existing_ifaces.contains(&request.alias_iface) {
return enoent(format!("interface {} not found", request.alias_iface).as_str());
@@ -722,6 +719,17 @@ async fn list_site_rdr(
}
}
#[ipc_method(method = "list_rdr_rules")]
async fn list_rdr_rules(
context: Arc<RwLock<ServerContext>>,
local_context: &mut ConnectionContext<Variables>,
request: (),
) -> GenericResult<Vec<PortRedirection>> {
let context = context.read().await;
let rdr_rules = context.port_forward_table.all_rules();
Ok(rdr_rules)
}
#[derive(FromPacket)]
pub struct FdImport {
pub fd: Fd,
@@ -1239,6 +1247,7 @@ pub(crate) async fn register_to_service(
service: &mut Service<tokio::sync::RwLock<ServerContext>, Variables>,
) {
service.register_event_delegate(on_channel_closed).await;
service.register(list_rdr_rules).await;
service.register(create_volume).await;
service.register(list_volumes).await;
service.register(commit_netgroup).await;

View File

@@ -29,16 +29,13 @@ mod dataset;
mod devfs_store;
mod image;
mod instantiate;
mod network;
mod network_manager;
pub mod ipc;
mod port;
mod registry;
pub mod resources;
mod site;
mod task;
mod util;
pub mod volume;
pub mod ipc;
use crate::config::XcConfig;

View File

@@ -1,239 +0,0 @@
// Copyright (c) 2023 Yan Ka, Chiu.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions, and the following disclaimer,
// without modification, immediately at the beginning of the file.
// 2. The name of the author may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
use anyhow::Context;
use ipcidr::IpCidr;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::net::IpAddr;
use std::sync::{Arc, Mutex};
use thiserror::Error;
use xc::container::request::NetworkAllocRequest;
use xc::models::network::IpAssign;
use crate::config::config_manager::InventoryManager;
use crate::database::Database;
use crate::network::{AddressStore, Network};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct NetworkInfo {
pub name: String,
pub subnet: IpCidr,
pub start_addr: IpAddr,
pub end_addr: IpAddr,
pub last_addr: Option<IpAddr>,
pub alias_iface: String,
pub bridge_iface: String,
}
impl NetworkInfo {
fn new(db: &impl AddressStore, name: String, network: &Network) -> NetworkInfo {
let netpool = network.parameterize(&name, db);
NetworkInfo {
name,
subnet: netpool.network.subnet.clone(),
start_addr: netpool.start_addr,
end_addr: netpool.end_addr,
last_addr: netpool.last_addr,
alias_iface: network.alias_iface.clone(),
bridge_iface: network.bridge_iface.clone(),
}
}
}
pub(crate) struct NetworkManager {
db: Arc<Database>,
inventory_manager: Arc<Mutex<InventoryManager>>,
table_cache: HashMap<String, HashMap<String, Vec<IpAddr>>>,
}
#[derive(Error, Debug)]
pub(crate) enum Error {
#[error("sqlite error: {0}")]
Sqlite(rusqlite::Error),
#[error("cannot allocate address from network {0}")]
AllocationFailure(String),
#[error("address {0} is being used")]
AddressUsed(std::net::IpAddr),
#[error("address {0} and network {1} in different subnet")]
InvalidAddress(IpAddr, String),
#[error("no such network {0}")]
NoSuchNetwork(String),
#[error("{0}")]
Other(anyhow::Error),
}
impl From<rusqlite::Error> for Error {
fn from(error: rusqlite::Error) -> Error {
Error::Sqlite(error)
}
}
impl From<anyhow::Error> for Error {
fn from(error: anyhow::Error) -> Error {
Error::Other(error)
}
}
impl NetworkManager {
pub(crate) fn new(
db: Arc<Database>,
inventory_manager: Arc<Mutex<InventoryManager>>,
) -> NetworkManager {
NetworkManager {
db,
inventory_manager,
table_cache: HashMap::new(),
}
}
pub(crate) fn has_network(&self, name: &str) -> bool {
self.inventory_manager
.lock()
.unwrap()
.borrow()
.networks
.contains_key(name)
}
pub(crate) fn get_network_info(&self) -> Result<Vec<NetworkInfo>, anyhow::Error> {
let config = self.inventory_manager.lock().unwrap().borrow().clone();
let mut info = Vec::new();
for (name, network) in config.networks.iter() {
let db: &Database = &self.db;
info.push(NetworkInfo::new(db, name.to_string(), network));
}
Ok(info)
}
pub(crate) fn create_network(
&self,
name: &str,
network: &Network,
) -> Result<(), rusqlite::Error> {
self.inventory_manager.lock().unwrap().modify(|inventory| {
inventory.networks.insert(name.to_string(), network.clone());
});
Ok(())
}
pub(crate) fn release_addresses(
&mut self,
token: &str,
) -> anyhow::Result<HashMap<String, Vec<IpAddr>>> {
self.db
.release_addresses(token)
.context("fail to release addresses")?;
let networks = self.table_cache.remove(token).unwrap_or_default();
Ok(networks)
}
pub(crate) fn get_allocated_addresses(
&self,
token: &str,
) -> Option<&HashMap<String, Vec<IpAddr>>> {
self.table_cache.get(token)
}
/// Request allociation using `req`, and return (an_assignment, default_router)
pub(crate) fn allocate(
&mut self,
vnet: bool,
req: &NetworkAllocRequest,
token: &str,
) -> Result<(IpAssign, Option<IpAddr>), Error> {
let network_name = req.network();
let config = self.inventory_manager.lock().unwrap().borrow().clone();
let network = config
.networks
.get(&network_name)
.ok_or_else(|| Error::NoSuchNetwork(network_name.to_string()))?
.clone();
let interface = if vnet {
network.bridge_iface.to_string()
} else {
network.alias_iface.to_string()
};
let db: &Database = &self.db;
let mut netpool = network.parameterize(&network_name, db);
match req {
NetworkAllocRequest::Any { .. } => {
let Some(address) = netpool.next_cidr(token)? else {
return Err(Error::AllocationFailure(network_name));
};
self.insert_to_cache(token, &network_name, &address.addr());
Ok((
IpAssign {
network: Some(network_name),
interface,
addresses: vec![address],
},
network.default_router,
))
}
NetworkAllocRequest::Explicit { ip, .. } => {
let address = IpCidr::from_addr(*ip, netpool.network.subnet.mask()).unwrap();
if netpool.network.subnet.network_addr() == address.network_addr() {
if netpool.is_address_consumed(ip)? {
return Err(Error::AddressUsed(*ip));
}
netpool.register_address(ip, token)?;
self.insert_to_cache(token, &network_name, ip);
Ok((
IpAssign {
network: Some(network_name),
interface,
addresses: vec![address],
},
network.default_router,
))
} else {
Err(Error::InvalidAddress(*ip, network_name))
}
}
}
}
#[inline]
fn insert_to_cache(&mut self, token: &str, network: &str, address: &IpAddr) {
if let Some(network_address) = self.table_cache.get_mut(token) {
if let Some(addresses) = network_address.get_mut(network) {
addresses.push(*address);
} else {
network_address.insert(network.to_string(), vec![*address]);
}
} else {
let mut hmap = HashMap::new();
hmap.insert(network.to_string(), vec![*address]);
self.table_cache.insert(token.to_string(), hmap);
}
}
}

65
xcd/src/resources/mod.rs Normal file
View File

@@ -0,0 +1,65 @@
// Copyright (c) 2023 Yan Ka, Chiu.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions, and the following disclaimer,
// without modification, immediately at the beginning of the file.
// 2. The name of the author may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
use crate::config::inventory::InventoryManager;
use crate::config::XcConfig;
use crate::database::Database;
use crate::dataset::JailedDatasetTracker;
use crate::resources::volume::VolumeShareMode;
use std::collections::HashMap;
use std::net::IpAddr;
use std::path::PathBuf;
use std::sync::Arc;
pub(crate) mod network;
pub mod volume;
pub(crate) struct Resources {
pub(crate) db: Arc<Database>,
pub(crate) inventory_manager: InventoryManager,
// network: { container: [address] }
pub(crate) net_addr_alloc_cache: HashMap<String, HashMap<String, Vec<IpAddr>>>,
pub(crate) default_volume_dataset: Option<PathBuf>,
pub(crate) default_volume_dir: Option<PathBuf>,
pub(crate) constrained_shares: HashMap<String, VolumeShareMode>,
pub(crate) dataset_tracker: JailedDatasetTracker,
}
impl Resources {
pub(crate) fn new(database: Arc<Database>, config: &XcConfig) -> Resources {
let inventory_manager =
InventoryManager::load_from_path(&config.inventory).expect("cannot read inventory");
Resources {
db: database,
inventory_manager,
default_volume_dataset: config.default_volume_dataset.clone(),
default_volume_dir: None,
net_addr_alloc_cache: HashMap::new(),
constrained_shares: HashMap::new(),
dataset_tracker: JailedDatasetTracker::default(),
}
}
}

View File

@@ -22,9 +22,45 @@
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
use anyhow::Context;
use ipcidr::IpCidr;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::net::IpAddr;
use thiserror::Error;
use xc::container::request::NetworkAllocRequest;
use xc::models::network::IpAssign;
use crate::database::Database;
use crate::resources::Resources;
#[derive(Error, Debug)]
pub(crate) enum Error {
#[error("sqlite error: {0}")]
Sqlite(rusqlite::Error),
#[error("cannot allocate address from network {0}")]
AllocationFailure(String),
#[error("address {0} is being used")]
AddressUsed(std::net::IpAddr),
#[error("address {0} and network {1} in different subnet")]
InvalidAddress(IpAddr, String),
#[error("no such network {0}")]
NoSuchNetwork(String),
#[error("{0}")]
Other(anyhow::Error),
}
impl From<rusqlite::Error> for Error {
fn from(error: rusqlite::Error) -> Error {
Error::Sqlite(error)
}
}
impl From<anyhow::Error> for Error {
fn from(error: anyhow::Error) -> Error {
Error::Other(error)
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct Network {
@@ -145,6 +181,158 @@ impl<'a, 'b, A: AddressStore> Netpool<'a, 'b, A> {
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct NetworkInfo {
pub name: String,
pub subnet: IpCidr,
pub start_addr: IpAddr,
pub end_addr: IpAddr,
pub last_addr: Option<IpAddr>,
pub alias_iface: String,
pub bridge_iface: String,
}
impl NetworkInfo {
fn new(db: &impl AddressStore, name: String, network: &Network) -> NetworkInfo {
let netpool = network.parameterize(&name, db);
NetworkInfo {
name,
subnet: netpool.network.subnet.clone(),
start_addr: netpool.start_addr,
end_addr: netpool.end_addr,
last_addr: netpool.last_addr,
alias_iface: network.alias_iface.clone(),
bridge_iface: network.bridge_iface.clone(),
}
}
}
impl Resources {
pub(crate) fn has_network(&self, name: &str) -> bool {
self.inventory_manager.borrow().networks.contains_key(name)
}
pub(crate) fn get_network_info(&self) -> Result<Vec<NetworkInfo>, anyhow::Error> {
let config = self.inventory_manager.borrow().clone();
let mut info = Vec::new();
for (name, network) in config.networks.iter() {
let db: &Database = &self.db;
info.push(NetworkInfo::new(db, name.to_string(), network));
}
Ok(info)
}
pub(crate) fn create_network(
&mut self,
name: &str,
network: &Network,
) -> Result<(), rusqlite::Error> {
self.inventory_manager.modify(|inventory| {
inventory.networks.insert(name.to_string(), network.clone());
});
Ok(())
}
pub(crate) fn release_addresses(
&mut self,
token: &str,
) -> anyhow::Result<HashMap<String, Vec<IpAddr>>> {
self.db
.release_addresses(token)
.context("fail to release addresses")?;
let networks = self.net_addr_alloc_cache.remove(token).unwrap_or_default();
Ok(networks)
}
pub(crate) fn get_allocated_addresses(
&self,
token: &str,
) -> Option<&HashMap<String, Vec<IpAddr>>> {
self.net_addr_alloc_cache.get(token)
}
/// Request allociation using `req`, and return (an_assignment, default_router)
pub(crate) fn allocate(
&mut self,
vnet: bool,
req: &NetworkAllocRequest,
token: &str,
) -> Result<(IpAssign, Option<IpAddr>), Error> {
let network_name = req.network();
let config = self.inventory_manager.borrow().clone();
let network = config
.networks
.get(&network_name)
.ok_or_else(|| Error::NoSuchNetwork(network_name.to_string()))?
.clone();
let interface = if vnet {
network.bridge_iface.to_string()
} else {
network.alias_iface.to_string()
};
let db: &Database = &self.db;
let mut netpool = network.parameterize(&network_name, db);
match req {
NetworkAllocRequest::Any { .. } => {
let Some(address) = netpool.next_cidr(token)? else {
return Err(Error::AllocationFailure(network_name));
};
self.insert_to_cache(token, &network_name, &address.addr());
Ok((
IpAssign {
network: Some(network_name),
interface,
addresses: vec![address],
},
network.default_router,
))
}
NetworkAllocRequest::Explicit { ip, .. } => {
let address = IpCidr::from_addr(*ip, netpool.network.subnet.mask()).unwrap();
if netpool.network.subnet.network_addr() == address.network_addr() {
if netpool.is_address_consumed(ip)? {
return Err(Error::AddressUsed(*ip));
}
netpool.register_address(ip, token)?;
self.insert_to_cache(token, &network_name, ip);
Ok((
IpAssign {
network: Some(network_name),
interface,
addresses: vec![address],
},
network.default_router,
))
} else {
Err(Error::InvalidAddress(*ip, network_name))
}
}
}
}
#[inline]
fn insert_to_cache(&mut self, token: &str, network: &str, address: &IpAddr) {
if let Some(network_address) = self.net_addr_alloc_cache.get_mut(token) {
if let Some(addresses) = network_address.get_mut(network) {
addresses.push(*address);
} else {
network_address.insert(network.to_string(), vec![*address]);
}
} else {
let mut hmap = HashMap::new();
hmap.insert(network.to_string(), vec![*address]);
self.net_addr_alloc_cache.insert(token.to_string(), hmap);
}
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -27,13 +27,13 @@ use std::collections::{HashMap, HashSet};
use std::path::PathBuf;
use xc::models::MountSpec;
use xc::{
container::{error::PreconditionFailure, request::Mount},
precondition_failure,
container::{error::Error, request::Mount},
errx,
};
use crate::auth::Credential;
use crate::ipc::MountReq;
use crate::volume::VolumeDriverKind;
use crate::{auth::Credential, volume::Volume};
use crate::resources::volume::{Volume, VolumeDriverKind};
use super::VolumeDriver;
@@ -48,23 +48,23 @@ impl VolumeDriver for LocalDriver {
_template: Option<MountSpec>,
source: Option<std::path::PathBuf>,
_props: HashMap<String, String>,
) -> Result<Volume, PreconditionFailure> {
) -> Result<Volume, Error> {
let path = match source {
None => {
let Some(mut parent) = self.default_subdir.clone() else {
precondition_failure!(ENOENT, "Default volume directory not found")
errx!(ENOENT, "Default volume directory not found")
};
parent.push(name);
if parent.exists() {
precondition_failure!(ENOENT, "Target directory already exists")
errx!(ENOENT, "Target directory already exists")
}
parent
}
Some(path) => {
if !path.exists() {
precondition_failure!(ENOENT, "No such directory")
errx!(ENOENT, "No such directory")
} else if !path.is_dir() {
precondition_failure!(ENOTDIR, "Source path is not a directory")
errx!(ENOTDIR, "Source path is not a directory")
}
path
}
@@ -86,23 +86,23 @@ impl VolumeDriver for LocalDriver {
mount_req: &MountReq,
mount_spec: Option<&MountSpec>,
volume: &Volume,
) -> Result<Mount, PreconditionFailure> {
) -> Result<Mount, Error> {
let source_path = &volume.device;
if !&source_path.exists() {
precondition_failure!(ENOENT, "source mount point does not exist: {source_path:?}");
errx!(ENOENT, "source mount point does not exist: {source_path:?}");
}
if !&source_path.is_dir() && !source_path.is_file() {
precondition_failure!(
errx!(
ENOTDIR,
"mount point source is not a file nor directory: {source_path:?}"
)
}
let Ok(meta) = std::fs::metadata(&volume.device) else {
precondition_failure!(ENOENT, "invalid nullfs mount source")
errx!(ENOENT, "invalid nullfs mount source")
};
if !cred.can_mount(&meta, false) {
precondition_failure!(EPERM, "permission denied: {source_path:?}")
errx!(EPERM, "permission denied: {source_path:?}")
}
let mut mount_options = HashSet::new();

View File

@@ -28,7 +28,7 @@ use super::Volume;
use crate::auth::Credential;
use crate::ipc::MountReq;
use std::collections::HashMap;
use xc::container::{error::PreconditionFailure, request::Mount};
use xc::container::{error::Error, request::Mount};
use xc::models::MountSpec;
pub trait VolumeDriver {
@@ -38,7 +38,7 @@ pub trait VolumeDriver {
mount_req: &MountReq,
mount_spec: Option<&MountSpec>,
volume: &Volume,
) -> Result<Mount, PreconditionFailure>;
) -> Result<Mount, Error>;
fn create(
&self,
@@ -46,5 +46,5 @@ pub trait VolumeDriver {
template: Option<MountSpec>,
source: Option<std::path::PathBuf>,
props: HashMap<String, String>,
) -> Result<Volume, PreconditionFailure>;
) -> Result<Volume, Error>;
}

View File

@@ -22,17 +22,17 @@
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
use crate::auth::Credential;
use crate::ipc::MountReq;
use crate::volume::VolumeDriverKind;
use crate::{auth::Credential, volume::Volume};
use crate::resources::volume::{Volume, VolumeDriverKind};
use freebsd::fs::zfs::{ZfsCreate, ZfsHandle};
use freebsd::libc::{EEXIST, EIO, ENOENT, EPERM};
use std::collections::{HashMap, HashSet};
use std::path::PathBuf;
use xc::models::MountSpec;
use xc::{
container::{error::PreconditionFailure, request::Mount},
precondition_failure,
container::{error::Error, request::Mount},
errx,
};
use super::VolumeDriver;
@@ -50,7 +50,7 @@ impl VolumeDriver for ZfsDriver {
template: Option<MountSpec>,
source: Option<std::path::PathBuf>,
props: HashMap<String, String>,
) -> Result<Volume, PreconditionFailure> {
) -> Result<Volume, Error> {
let mut zfs_props = props;
if let Some(template) = template {
@@ -66,26 +66,26 @@ impl VolumeDriver for ZfsDriver {
let dataset = match source {
None => {
let Some(mut dataset) = self.default_dataset.clone() else {
precondition_failure!(ENOENT, "Default volume dataset not set")
errx!(ENOENT, "Default volume dataset not set")
};
dataset.push(name);
if self.handle.exists(&dataset) {
precondition_failure!(EEXIST, "Dataset already exist")
errx!(EEXIST, "Dataset already exist")
}
let mut zfs_create = ZfsCreate::new(&dataset, true, false);
zfs_create.set_props(zfs_props.clone());
if let Err(error) = self.handle.create(zfs_create) {
precondition_failure!(EIO, "Cannot create zfs dataset: {error:?}")
errx!(EIO, "Cannot create zfs dataset: {error:?}")
}
dataset
}
Some(dataset) => {
if !self.handle.exists(&dataset) {
precondition_failure!(ENOENT, "Requested dataset does not exist")
errx!(ENOENT, "Requested dataset does not exist")
}
dataset
}
@@ -108,22 +108,22 @@ impl VolumeDriver for ZfsDriver {
mount_req: &MountReq,
mount_spec: Option<&MountSpec>,
volume: &Volume,
) -> Result<Mount, PreconditionFailure> {
) -> Result<Mount, Error> {
if !self.handle.exists(&volume.device) {
precondition_failure!(ENOENT, "No such dataset: {:?}", volume.device);
errx!(ENOENT, "No such dataset: {:?}", volume.device);
}
let Ok(Some(mount_point)) = self.handle.mount_point(&volume.device) else {
precondition_failure!(
errx!(
ENOENT,
"Dataset {:?} does not have a mount point",
volume.device
);
};
let Ok(meta) = std::fs::metadata(&mount_point) else {
precondition_failure!(EIO, "cannot get metadata of {mount_point:?}");
errx!(EIO, "cannot get metadata of {mount_point:?}");
};
if !cred.can_mount(&meta, false) {
precondition_failure!(EPERM, "permission denied: {mount_point:?}");
errx!(EPERM, "permission denied: {mount_point:?}");
}
let mut mount_options = HashSet::new();

View File

@@ -24,22 +24,20 @@
pub mod drivers;
use crate::auth::Credential;
use crate::config::config_manager::InventoryManager;
use crate::ipc::MountReq;
use crate::volume::drivers::local::LocalDriver;
use crate::volume::drivers::zfs::ZfsDriver;
use crate::volume::drivers::VolumeDriver;
use crate::resources::volume::drivers::local::LocalDriver;
use crate::resources::volume::drivers::zfs::ZfsDriver;
use crate::resources::volume::drivers::VolumeDriver;
use freebsd::libc::EPERM;
use serde::de::Deserializer;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use xc::container::error::PreconditionFailure;
use xc::container::error::Error;
use xc::container::request::Mount;
use xc::errx;
use xc::models::MountSpec;
use xc::precondition_failure;
#[derive(Default, PartialEq, Eq, Debug, Clone)]
pub enum VolumeDriverKind {
@@ -188,37 +186,17 @@ pub enum VolumeShareMode {
SingleWriter,
}
pub(crate) struct VolumeManager {
inventory: Arc<Mutex<InventoryManager>>,
default_volume_dataset: Option<PathBuf>,
default_volume_dir: Option<PathBuf>,
constrained_shares: HashMap<String, VolumeShareMode>,
}
impl VolumeManager {
pub(crate) fn new(
inventory: Arc<Mutex<InventoryManager>>,
default_volume_dataset: Option<PathBuf>,
default_volume_dir: Option<PathBuf>,
) -> VolumeManager {
VolumeManager {
inventory,
default_volume_dataset,
default_volume_dir,
constrained_shares: HashMap::new(),
}
}
impl crate::resources::Resources {
// insert or override a volume
pub(crate) fn add_volume(&mut self, name: &str, volume: &Volume) {
self.inventory.lock().unwrap().modify(|inventory| {
self.inventory_manager.modify(|inventory| {
inventory.volumes.insert(name.to_string(), volume.clone());
});
}
pub(crate) fn list_volumes(&self) -> HashMap<String, Volume> {
let mut hm = HashMap::new();
for (name, volume) in self.inventory.lock().unwrap().borrow().volumes.iter() {
for (name, volume) in self.inventory_manager.borrow().volumes.iter() {
let mut vol = volume.clone();
vol.name = Some(name.to_string());
hm.insert(name.to_string(), vol);
@@ -227,9 +205,7 @@ impl VolumeManager {
}
pub(crate) fn query_volume(&self, name: &str) -> Option<Volume> {
self.inventory
.lock()
.unwrap()
self.inventory_manager
.borrow()
.volumes
.get(name)
@@ -247,7 +223,7 @@ impl VolumeManager {
template: Option<MountSpec>,
source: Option<PathBuf>,
props: HashMap<String, String>,
) -> Result<(), PreconditionFailure> {
) -> Result<(), Error> {
let volume = match kind {
VolumeDriverKind::Directory => {
let local_driver = LocalDriver {
@@ -276,16 +252,16 @@ impl VolumeManager {
mount_req: &MountReq,
mount_spec: Option<&MountSpec>,
volume: &Volume,
) -> Result<Mount, PreconditionFailure> {
) -> Result<Mount, Error> {
for (name, share) in self.constrained_shares.iter() {
if volume.name.as_ref().unwrap() == name {
if share == &VolumeShareMode::Exclusive {
precondition_failure!(
errx!(
EPERM,
"The volume has been mounted exclusively by other container"
)
} else if share == &VolumeShareMode::SingleWriter && !mount_req.read_only {
precondition_failure!(
errx!(
EPERM,
"The volume has been mounted for exclusively write by other container"
)