[ci skip]

checkpoint (doesn't compile)
This commit is contained in:
Christien Rioux 2024-12-06 19:47:10 -05:00
parent ea53ad5980
commit 9b516f902b
19 changed files with 1660 additions and 561 deletions

623
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -52,6 +52,7 @@ virtual-network-server = [
"dep:serde_yaml",
"dep:validator",
"dep:ws_stream_tungstenite",
"dep:rand_chacha",
]
[dependencies]
@ -113,6 +114,7 @@ config = { version = "^0", default-features = false, features = [
ipnet = { version = "2", features = ["serde"], optional = true }
serde_yaml = { package = "serde_yaml_ng", version = "^0.10.0", optional = true }
validator = { version = "0.19.0", features = ["derive"], optional = true }
rand_chacha = { version = "0.3.1", optional = true }
# Dependencies for WASM builds only
[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies]

View File

@ -101,8 +101,8 @@ pub struct AddressFlags {
#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)]
pub struct InterfaceAddress {
if_addr: IfAddr,
flags: AddressFlags,
pub if_addr: IfAddr,
pub flags: AddressFlags,
}
use core::cmp::Ordering;

View File

@ -76,6 +76,33 @@ impl<T: fmt::Debug + Clone> WeightedList<T> {
.try_for_each(f),
}
}
pub fn filter<F>(&self, mut filter: F) -> Option<WeightedList<T>>
where
F: FnMut(&T) -> bool,
{
match self {
WeightedList::Single(v) => {
if filter(v) {
return Some(self.clone());
}
return None;
}
WeightedList::List(vec) => {
let mut out = Vec::<Weighted<T>>::with_capacity(vec.len());
for v in vec {
if filter(v.item()) {
out.push(v.clone());
}
}
if out.is_empty() {
None
} else {
Some(WeightedList::List(out))
}
}
}
}
}
pub type Probability = f32;
@ -147,12 +174,8 @@ pub enum Instance {
)]
pub struct Machine {
#[serde(flatten)]
#[validate(custom(function = "validate_location_exists", use_context))]
pub location: Location,
#[serde(default)]
pub address4: Option<Ipv4Addr>,
#[serde(default)]
pub address6: Option<Ipv6Addr>,
#[validate(custom(function = "validate_machine_location_exists", use_context))]
pub location: MachineLocation,
#[serde(default)]
pub disable_capabilities: Vec<String>,
#[serde(default)]
@ -178,8 +201,8 @@ fn validate_machine(machine: &Machine, _context: &ValidateContext) -> Result<(),
)]
pub struct Template {
#[serde(flatten)]
#[validate(custom(function = "validate_location_exists", use_context))]
pub location: Location,
#[validate(custom(function = "validate_template_location_exists", use_context))]
pub location: TemplateLocation,
#[serde(flatten)]
#[validate(nested)]
pub limits: Limits,
@ -219,7 +242,25 @@ fn validate_limits(limits: &Limits) -> Result<(), ValidationError> {
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Location {
pub enum MachineLocation {
Specific {
network: String,
#[serde(default)]
address4: Option<Ipv4Addr>,
#[serde(default)]
address6: Option<Ipv6Addr>,
},
Network {
network: WeightedList<String>,
},
Blueprint {
blueprint: WeightedList<String>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum TemplateLocation {
Network { network: WeightedList<String> },
Blueprint { blueprint: WeightedList<String> },
}
@ -313,7 +354,7 @@ fn validate_network_gateway(
pub struct Blueprint {
#[serde(default)]
#[validate(custom(function = "validate_models_exist", use_context))]
pub model: WeightedList<String>,
pub model: Option<WeightedList<String>>,
#[serde(default)]
#[validate(custom(function = "validate_blueprint_ipv4", use_context))]
pub ipv4: Option<BlueprintIpv4>,
@ -337,7 +378,7 @@ fn validate_blueprint(
pub struct BlueprintIpv4 {
#[serde(default)]
pub allocation: Option<String>,
pub prefix: u8,
pub additional_prefix: u8,
#[serde(default)]
pub gateway: Option<BlueprintGateway>,
}
@ -350,10 +391,9 @@ fn validate_blueprint_ipv4(
validate_allocation_exists(allocation, context)?;
}
if blueprint_ipv4.prefix > 32 {
return Err(
ValidationError::new("badprefix").with_message("ipv4 blueprint prefix too long".into())
);
if blueprint_ipv4.additional_prefix > 32 {
return Err(ValidationError::new("badprefix")
.with_message("ipv4 blueprint additional prefix too long".into()));
}
if let Some(gateway) = &blueprint_ipv4.gateway {
@ -366,7 +406,7 @@ fn validate_blueprint_ipv4(
pub struct BlueprintIpv6 {
#[serde(default)]
pub allocation: Option<String>,
pub prefix: u8,
pub additional_prefix: u8,
#[serde(default)]
pub gateway: Option<BlueprintGateway>,
}
@ -379,10 +419,9 @@ fn validate_blueprint_ipv6(
validate_allocation_exists(allocation, context)?;
}
if blueprint_ipv6.prefix > 128 {
return Err(
ValidationError::new("badprefix").with_message("ipv6 blueprint prefix too long".into())
);
if blueprint_ipv6.additional_prefix > 128 {
return Err(ValidationError::new("badprefix")
.with_message("ipv6 blueprint additional prefix too long".into()));
}
if let Some(gateway) = &blueprint_ipv6.gateway {
@ -414,13 +453,13 @@ fn validate_blueprint_gateway(
#[validate(schema(function = "validate_subnets"))]
pub struct Subnets {
#[serde(default)]
pub subnet4: Vec<Ipv4Net>,
pub subnet4: Option<WeightedList<Ipv4Net>>,
#[serde(default)]
pub subnet6: Vec<Ipv6Net>,
pub subnet6: Option<WeightedList<Ipv6Net>>,
}
fn validate_subnets(subnets: &Subnets) -> Result<(), ValidationError> {
if subnets.subnet4.is_empty() && subnets.subnet6.is_empty() {
if subnets.subnet4.is_none() && subnets.subnet6.is_none() {
return Err(ValidationError::new("badsub")
.with_message("subnets must support at least one address type".into()));
}
@ -472,7 +511,7 @@ fn validate_distribution(distribution: &Distribution) -> Result<(), ValidationEr
Ok(())
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Translation {
None,
@ -515,7 +554,7 @@ struct ValidateContext<'a> {
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
#[validate(context = "ValidateContext<'v_a>")]
pub struct Config {
pub seed: Option<u32>,
pub seed: Option<u64>,
#[validate(
length(min = 1),
custom(function = "validate_network_exists", use_context)
@ -596,15 +635,42 @@ fn validate_instances_exist(
Ok(())
}
fn validate_location_exists(
value: &Location,
fn validate_machine_location_exists(
value: &MachineLocation,
context: &ValidateContext,
) -> Result<(), ValidationError> {
match value {
Location::Network { network } => {
MachineLocation::Specific {
network,
address4,
address6,
} => {
if address4.is_none() && address6.is_none() {
return Err(ValidationError::new("badaddr")
.with_message("machine must have at least one address".into()));
}
validate_network_exists(network, context)?;
}
MachineLocation::Network { network } => {
network.try_for_each(|n| validate_network_exists(n, context))?;
}
MachineLocation::Blueprint { blueprint } => {
blueprint.try_for_each(|b| validate_blueprint_exists(b, context))?;
}
}
Ok(())
}
fn validate_template_location_exists(
value: &TemplateLocation,
context: &ValidateContext,
) -> Result<(), ValidationError> {
match value {
TemplateLocation::Network { network } => {
network.try_for_each(|m| validate_network_exists(m, context))?;
}
Location::Blueprint { blueprint } => {
TemplateLocation::Blueprint { blueprint } => {
blueprint.try_for_each(|t| validate_blueprint_exists(t, context))?;
}
}

View File

@ -125,22 +125,22 @@ blueprints:
# with both ipv4 and ipv6 networking
direct:
ipv4:
prefix: 24
additional_prefix: 24
ipv6:
prefix: 64
additional_prefix: 64
# An ipv4-only subnet of the internet directly attached with no translation
direct_ipv4_no_ipv6:
ipv4:
prefix: 24
additional_prefix: 24
# An ipv6-only subnet of the internet directly attached with no translation
direct_ipv6_no_ipv4:
ipv6:
prefix: 64
additional_prefix: 64
# An ipv4-only subnet of the internet attached via NAT
nat_ipv4_no_ipv6:
ipv4:
allocation: "$private"
prefix: 0
additional_prefix: 0
gateway:
translation: "port_restricted"
upnp: 0.25
@ -149,12 +149,12 @@ blueprints:
nat_ipv4_direct_ipv6:
ipv4:
allocation: "$private"
prefix: 0
additional_prefix: 0
gateway:
translation: "port_restricted"
upnp: 0.25
ipv6:
prefix: 56
additional_prefix: 56
#################################################################
# Allocations

View File

@ -1,189 +0,0 @@
use super::*;
use rand::Rng;
#[derive(Debug)]
struct Machine {}
#[derive(Debug)]
struct MachineRegistryUnlockedInner {
config: config::Config,
}
#[derive(Debug, Default)]
struct ProfileState {
next_instance_index: usize,
}
#[derive(Debug)]
struct MachineRegistryInner {
machines_by_id: HashMap<MachineId, Machine>,
current_profile_state: HashMap<String, ProfileState>,
}
#[derive(Debug, Clone)]
pub enum MachineRegistryError {
InvalidMachineId,
ProfileNotFound,
ProfileComplete,
}
pub type MachineRegistryResult<T> = Result<T, MachineRegistryError>;
#[derive(Debug, Clone)]
pub struct MachineRegistry {
unlocked_inner: Arc<MachineRegistryUnlockedInner>,
inner: Arc<Mutex<MachineRegistryInner>>,
}
impl MachineRegistry {
///////////////////////////////////////////////////////////
/// Public Interface
pub fn new(config: config::Config) -> Self {
Self {
unlocked_inner: Arc::new(MachineRegistryUnlockedInner { config }),
inner: Arc::new(Mutex::new(MachineRegistryInner {
machines_by_id: HashMap::new(),
current_profile_state: HashMap::new(),
})),
}
}
pub async fn allocate(&self, profile: String) -> MachineRegistryResult<MachineId> {
// Get profile definition
let Some(profile_def) = self.unlocked_inner.config.profiles.get(&profile) else {
return Err(MachineRegistryError::ProfileNotFound);
};
// Get current profile state, creating one if we have not yet started executing the profile
let mut inner = self.inner.lock();
let current_profile_state = inner
.current_profile_state
.entry(profile)
.or_insert_with(|| ProfileState::default());
// Get the next instance from the definition
let Some(instance_def) = profile_def
.instances
.get(current_profile_state.next_instance_index)
else {
//
return Err(MachineRegistryError::ProfileComplete);
};
match instance_def {
config::Instance::Machine { machine } => {
let machine = self.weighted_choice(machine);
let machine_def = self
.unlocked_inner
.config
.machines
.get(machine)
.expect("config validation is broken");
self.create_machine(machine_def).await
}
config::Instance::Template { template } => {
let template = self.weighted_choice(template);
let template_def = self
.unlocked_inner
.config
.templates
.get(template)
.expect("config validation is broken");
self.create_machine_from_template(template_def).await
}
}
}
pub async fn release(&self, machine_id: MachineId) -> MachineRegistryResult<()> {}
///////////////////////////////////////////////////////////
/// Private Implementation
async fn create_machine(
&self,
machine_def: &config::Machine,
) -> MachineRegistryResult<MachineId> {
// Get network from location
Ok(0)
}
async fn create_machine_from_template(
&self,
template_def: &config::Template,
) -> MachineRegistryResult<MachineId> {
Ok(0)
}
async fn get_or_create_network_from_location(
&self,
location_def: &config::Location,
) -> MachineRegistryResult<NetworkId> {
match location_def {
config::Location::Network { network } => {
let network = self.weighted_choice(network);
let network_def = self
.unlocked_inner
.config
.networks
.get(network)
.expect("config validation is broken");
self.get_or_create_network(network, network_def).await
}
config::Location::Blueprint { blueprint } => {
let blueprint = self.weighted_choice(blueprint);
let blueprint_def = self
.unlocked_inner
.config
.blueprints
.get(blueprint)
.expect("config validation is broken");
self.get_or_create_network_from_blueprint(blueprint, blueprint_def)
.await
}
}
}
async fn get_or_create_network(
&self,
network: &String,
network_def: &config::Network,
) -> MachineRegistryResult<NetworkId> {
Ok(0)
}
async fn get_or_create_network_from_blueprint(
&self,
blueprint: &String,
blueprint_def: &config::Blueprint,
) -> MachineRegistryResult<NetworkId> {
Ok(0)
}
fn weighted_choice<'a, T: fmt::Debug + Clone>(
&self,
weighted_list: &'a config::WeightedList<T>,
) -> &'a T {
match weighted_list {
config::WeightedList::Single(x) => x,
config::WeightedList::List(vec) => {
let total_weight = vec
.iter()
.map(|x| x.weight())
.reduce(|acc, x| acc + x)
.expect("config validation broken");
let r = rand::thread_rng().gen_range(0.0..=total_weight);
let mut current_weight = 0.0f32;
for x in vec {
current_weight += x.weight();
if r < current_weight {
return x.item();
}
}
// Catch f32 imprecision
vec.last().expect("config validation broken").item()
}
}
}
}

View File

@ -0,0 +1,101 @@
use super::*;
use ipnet::*;
#[derive(Debug)]
pub struct AddressPool {
srng: StableRng,
allocated_v4: Vec<Ipv4Net>,
allocated_v6: Vec<Ipv6Net>,
}
impl AddressPool {
pub fn new(srng: StableRng) -> Self {
Self {
srng,
allocated_v4: Vec::new(),
allocated_v6: Vec::new(),
}
}
pub fn contains_v4(&self, allocation: &Ipv4Net) -> bool {
for x in &self.allocated_v4 {
if x.contains(allocation) {
return true;
}
}
false
}
pub fn contains_v6(&self, allocation: &Ipv6Net) -> bool {
for x in &self.allocated_v6 {
if x.contains(allocation) {
return true;
}
}
false
}
pub fn add_v4(&mut self, allocation: Ipv4Net) {
self.allocated_v4.push(allocation);
self.allocated_v4 = Ipv4Net::aggregate(&self.allocated_v4);
}
pub fn add_v6(&mut self, allocation: Ipv6Net) {
self.allocated_v6.push(allocation);
self.allocated_v6 = Ipv6Net::aggregate(&self.allocated_v6);
}
pub fn add_random_subnet_v4(
&mut self,
allocation: &Ipv4Net,
additional_prefix: u8,
) -> Option<Ipv4Net> {
// Apply the additional prefix
let prefix = u8::max(
allocation.prefix_len() + additional_prefix,
allocation.max_prefix_len(),
);
// Get the subnets with this prefix
let mut subnets = allocation.subnets(prefix).ok()?.collect::<Vec<Ipv4Net>>();
// Randomize the subnets
self.srng.shuffle_vec(&mut subnets);
// Pick the first available subnet
for subnet in subnets {
if !self.contains_v4(&subnet) {
self.add_v4(subnet);
return Some(subnet);
}
}
None
}
pub fn add_random_subnet_v6(
&mut self,
allocation: &Ipv6Net,
additional_prefix: u8,
) -> Option<Ipv6Net> {
// Apply the additional prefix
let prefix = u8::max(
allocation.prefix_len() + additional_prefix,
allocation.max_prefix_len(),
);
// Get the subnets with this prefix
let mut subnets = allocation.subnets(prefix).ok()?.collect::<Vec<Ipv6Net>>();
// Randomize the subnets
self.srng.shuffle_vec(&mut subnets);
// Pick the first available subnet
for subnet in subnets {
if !self.contains_v6(&subnet) {
self.add_v6(subnet);
return Some(subnet);
}
}
None
}
}

View File

@ -0,0 +1,417 @@
use super::*;
#[derive(Debug)]
pub(super) struct MachineRegistryInner {
pub unlocked_inner: Arc<MachineRegistryUnlockedInner>,
pub resolve_to_manager_machine: ResolveToManager<String, MachineId>,
pub resolve_to_manager_network: ResolveToManager<String, NetworkId>,
//
profile_state_by_name: HashMap<String, ProfileState>,
machine_state_by_id: HashMap<MachineId, MachineState>,
network_state_by_id: HashMap<NetworkId, NetworkState>,
template_state_by_name: HashMap<String, TemplateState>,
blueprint_state_by_name: HashMap<String, BlueprintState>,
next_machine_id: u64,
free_machine_ids: Vec<u64>,
next_network_id: u64,
free_network_ids: Vec<u64>,
address_pool: AddressPool,
}
impl MachineRegistryInner {
///////////////////////////////////////////////////////////
/// Public Interface
pub fn new(unlocked_inner: Arc<MachineRegistryUnlockedInner>) -> Self {
let srng = unlocked_inner.srng.clone();
MachineRegistryInner {
unlocked_inner,
machine_state_by_id: HashMap::new(),
profile_state_by_name: HashMap::new(),
network_state_by_id: HashMap::new(),
template_state_by_name: HashMap::new(),
blueprint_state_by_name: HashMap::new(),
next_machine_id: 0,
free_machine_ids: Vec::new(),
next_network_id: 0,
free_network_ids: Vec::new(),
resolve_to_manager_machine: ResolveToManager::new(),
resolve_to_manager_network: ResolveToManager::new(),
address_pool: AddressPool::new(srng),
}
}
pub fn allocate(&mut self, profile: String) -> MachineRegistryResult<MachineId> {
// Get profile definition
let Some(profile_def) = self.unlocked_inner.config.profiles.get(&profile) else {
return Err(MachineRegistryError::ProfileNotFound);
};
// Get current profile state, creating one if we have not yet started executing the profile
let profile_state = self
.profile_state_by_name
.entry(profile)
.or_insert_with(|| ProfileState::default());
// Get the next instance from the definition
let Some(instance_def) = profile_def
.instances
.get(profile_state.next_instance_index)
else {
//
return Err(MachineRegistryError::ProfileComplete);
};
match instance_def {
config::Instance::Machine { machine } => {
let machine = self.unlocked_inner.srng.weighted_choice(machine);
let unlocked_inner = self.unlocked_inner.clone();
let machine_def = unlocked_inner
.config
.machines
.get(machine).cloned()
.expect("config validation is broken");
self.get_or_create_machine_state_id(machine.clone(), machine_def)
}
config::Instance::Template { template } => {
let template = self.unlocked_inner.srng.weighted_choice(template);
let unlocked_inner = self.unlocked_inner.clone();
let template_def = unlocked_inner
.config
.templates
.get(template).cloned()
.expect("config validation is broken");
self.create_machine_state_from_template(template.clone(), template_def)
}
}
}
pub fn release(&self, machine_id: MachineId) -> MachineRegistryResult<()> {
// xxx
Ok(())
}
///////////////////////////////////////////////////////////
/// Private Implementation
pub(super) fn get_or_create_machine_state_id(
&mut self,
name: String,
machine_def: config::Machine,
) -> MachineRegistryResult<MachineId> {
// Ensure we don't already have this machine created (name must be unique)
if let Some(machine_id) = self.resolve_to_manager_machine.add(name.clone()).get() {
return Ok(machine_id);
}
// Allocate a machine id
let machine_id = self.free_machine_ids.pop().unwrap_or_else(|| {
let x = self.next_machine_id;
self.next_machine_id += 1;
x
});
// Create a new machine state
let machine_state = match MachineState::try_new(self,
MachineStateName::Machine(name.clone()),
machine_def.clone(),
machine_id,
) {
Ok(v) => v,
Err(e) => {
// Release the machine id
self.free_machine_ids.push(machine_id);
return Err(e);
}
};
// Store the machine state with its unique id
self.machine_state_by_id.insert(machine_id, machine_state);
// Bind the name to the id
self.resolve_to_manager_machine.resolve(&name, machine_id).expect("must resolve");
// Return the unique id
Ok(machine_id)
}
pub(super) fn get_machine_state_by_id(
&mut self,
machine_id: MachineId,
) -> MachineRegistryResult<&mut MachineState> {
self
.machine_state_by_id
.get_mut(&machine_id).ok_or_else(|| MachineRegistryError::MachineNotFound)
}
pub(super) fn get_or_create_network_state_id(
&mut self,
name: String,
network_def: config::Network,
) -> MachineRegistryResult<NetworkId> {
// Ensure we don't already have this network created (name must be unique)
if let Some(network_id) = self.resolve_to_manager_network.add(name.clone()).get() {
return Ok(network_id);
}
// Allocate a network id
let network_id = self.free_network_ids.pop().unwrap_or_else(|| {
let x = self.next_network_id;
self.next_network_id += 1;
x
});
// Create a new network state
let network_state = match NetworkState::try_new(self,
NetworkStateName::Network(name.clone()),
network_def.clone(),
network_id,
){
Ok(v) => v,
Err(e) => {
// Release the network id
self.free_network_ids.push(network_id);
return Err(e);
}
};
// Store the network state with its unique id
self.network_state_by_id.insert(network_id, network_state);
// Bind the name to the id
self.resolve_to_manager_network.resolve(&name, network_id).expect("must resolve");
// Return the unique id
Ok(network_id)
}
pub(super) fn get_network_state_by_id (
&mut self,
network_id: NetworkId,
) -> MachineRegistryResult<&mut NetworkState> {
self
.network_state_by_id
.get_mut(&network_id).ok_or_else(|| MachineRegistryError::NetworkNotFound)
}
pub(super) fn get_or_create_template_state(
&mut self,
name: &String,
template_def: config::Template,
) -> MachineRegistryResult<&mut TemplateState> {
// Ensure we don't already have this template created (name must be unique)
if self.template_state_by_name.contains_key(name) {
return Ok(self.template_state_by_name.get_mut(name).expect("must exist"));
}
// Create a new template state
let template_state = match TemplateState::try_new(self,
name.clone(),
template_def.clone(),
) {
Ok(v) => v,
Err(e) => {
return Err(e);
}
};
// Store the template state with its name
self.template_state_by_name.insert(name.clone(), template_state);
Ok(self.template_state_by_name.get_mut(name).expect("must exist"))
}
pub(super) fn create_machine_state_from_template(
&mut self,
name: String,
template_def: config::Template,
) -> MachineRegistryResult<MachineId> {
// Get the active template state
let template_state = self.get_or_create_template_state(&name, template_def)?;
if !template_state.is_active() {
return Err(MachineRegistryError::TemplateComplete);
}
// Make machine def from current template state
let machine_def = config::Machine {
location: template_state.template_def.location.clone(),
address4: template_state.template_def.
address6: todo!(),
disable_capabilities: todo!(),
bootstrap: todo!(),
};
}
pub(super) fn get_template_state(
&mut self,
name: &String,
) -> MachineRegistryResult<&mut TemplateState> {
self
.template_state_by_name
.get_mut(name).ok_or_else(|| MachineRegistryError::TemplateNotFound)
}
pub(super) fn get_or_create_network_state_from_location(
&mut self,
location_def: &config::Location,
) -> MachineRegistryResult<NetworkId> {
match location_def {
config::Location::Network { network } => {
let name = self.unlocked_inner.srng.weighted_choice(network);
let network_def = self
.unlocked_inner
.config
.networks
.get(name).cloned()
.expect("config validation is broken");
self.get_or_create_network_state(
name.clone(),
network_def,
)
}
config::Location::Blueprint { blueprint } => {
let name = self.unlocked_inner.srng.weighted_choice(blueprint);
let blueprint_def = self
.unlocked_inner
.config
.blueprints
.get(name).cloned()
.expect("config validation is broken");
self.get_or_create_network_state_from_blueprint(
name.clone(),
blueprint_def)
}
}
}
pub(super) fn get_blueprint_state(
&mut self,
name: &String,
) -> MachineRegistryResult<&mut BlueprintState> {
self
.blueprint_state_by_name
.get_mut(name).ok_or_else(|| MachineRegistryError::BlueprintNotFound)
}
pub(super) fn choose_allocation_v4(
&mut self,
allocation: config::Allocation,
additional_prefix: u8,
) -> MachineRegistryResult<Ipv4Net> {
// Get allocation subnet candidates
let mut subnet4 = allocation
.subnets
.subnet4
.clone()
.ok_or(MachineRegistryError::NoAllocation)?;
loop {
// Pick a compatible subnet from the allocation
let subnet = self.unlocked_inner.srng.weighted_choice(&subnet4);
// Allocate within the subnet
match self
.address_pool
.add_random_subnet_v4(subnet, additional_prefix)
{
Some(a) => {
// Got a sub-allocation
return Ok(a);
}
None => {
// No sub-allocation left in this subnet,
// remove the subnet so we can choose again
let Some(next_subnet4) = subnet4.filter(|x| x == subnet) else {
// No subnets left
break;
};
subnet4 = next_subnet4;
}
}
}
// No available allocations left
Err(MachineRegistryError::NoAllocation)
}
pub(super) fn choose_allocation_v6(
&mut self,
allocation: config::Allocation,
additional_prefix: u8,
) -> MachineRegistryResult<Ipv6Net> {
// Get allocation subnet candidates
let mut subnet6 = allocation
.subnets
.subnet6
.clone()
.ok_or(MachineRegistryError::NoAllocation)?;
loop {
// Pick a compatible subnet from the allocation
let subnet = self.unlocked_inner.srng.weighted_choice(&subnet6);
// Allocate within the subnet
match self
.address_pool
.add_random_subnet_v6(subnet, additional_prefix)
{
Some(a) => {
// Got a sub-allocation
return Ok(a);
}
None => {
// No sub-allocation left in this subnet,
// remove the subnet so we can choose again
let Some(next_subnet6) = subnet6.filter(|x| x == subnet) else {
// No subnets left
break;
};
subnet6 = next_subnet6;
}
}
}
// No available allocations left
Err(MachineRegistryError::NoAllocation)
}
pub(super) fn get_or_create_network_state_from_blueprint(
&mut self,
name: String,
blueprint_def: config::Blueprint,
) -> MachineRegistryResult<NetworkId> {
xxx
self.with_blueprint_state(
name,
blueprint_def,
|blueprint_state| {
// Make network def from current blueprint state
let network_def = config::Network {
model: blueprint_state
.blueprint_def
.model
.as_ref()
.map(|model| self.unlocked_inner.srng.weighted_choice(model).clone()),
ipv4: blueprint_state
.blueprint_def
.ipv4
.as_ref()
.map(|bpv4| self.unlocked_inner.srng.weighted_choice(bpv4).clone()),
ipv6: blueprint_def
.ipv6
.as_ref()
.map(|bpv6| self.unlocked_inner.srng.weighted_choice(bpv6).clone()),
};
//xxx self.
},
)
}
}

View File

@ -0,0 +1,67 @@
mod address_pool;
mod machine_registry_inner;
mod state;
use super::*;
use ipnet::*;
use address_pool::*;
use machine_registry_inner::*;
use state::*;
#[derive(Debug)]
struct Machine {}
#[derive(Debug)]
struct MachineRegistryUnlockedInner {
config: config::Config,
srng: StableRng,
}
#[derive(Debug, Clone)]
pub enum MachineRegistryError {
InvalidMachineId,
InvalidAllocationName,
ProfileNotFound,
ProfileComplete,
TemplateComplete,
BlueprintComplete,
MachineNotFound,
NetworkNotFound,
TemplateNotFound,
BlueprintNotFound,
NoAllocation,
}
pub type MachineRegistryResult<T> = Result<T, MachineRegistryError>;
#[derive(Debug, Clone)]
pub struct MachineRegistry {
inner: Arc<Mutex<MachineRegistryInner>>,
unlocked_inner: Arc<MachineRegistryUnlockedInner>,
}
impl MachineRegistry {
///////////////////////////////////////////////////////////
/// Public Interface
pub fn new(config: config::Config) -> Self {
let srng = StableRng::new(config.seed.unwrap_or_default());
let unlocked_inner = Arc::new(MachineRegistryUnlockedInner { srng, config });
Self {
inner: Arc::new(Mutex::new(MachineRegistryInner::new(
unlocked_inner.clone(),
))),
unlocked_inner,
}
}
pub fn allocate(&self, profile: String) -> MachineRegistryResult<MachineId> {
let mut inner = self.inner.lock();
inner.allocate(profile)
}
pub fn release(&self, machine_id: MachineId) -> MachineRegistryResult<()> {
let mut inner = self.inner.lock();
inner.release(machine_id)
}
}

View File

@ -0,0 +1,7 @@
use super::*;
#[derive(Debug)]
pub struct BlueprintState {
pub name: String,
pub blueprint_def: config::Blueprint,
}

View File

@ -0,0 +1,154 @@
use super::*;
#[derive(Debug)]
pub enum MachineStateName {
Machine(String),
Template(String),
}
#[derive(Debug)]
pub struct MachineState {
/// The name of this machine state if it was made directly
/// or the name of the template used to create it
pub name: MachineStateName,
/// The definition this machine was created with
pub machine_def: config::Machine,
/// The current network interfaces definition
pub interfaces: Vec<MachineStateInterface>,
}
#[derive(Debug)]
pub struct MachineStateInterface {
/// The network id
pub network_id: NetworkId,
/// The veilid NetworkInterface state
pub network_interface: NetworkInterface,
}
impl MachineState {
pub fn try_new(
machine_registry_inner: &mut MachineRegistryInner,
name: MachineStateName,
machine_def: config::Machine,
machine_id: MachineId,
) -> MachineRegistryResult<Self> {
// Build list of machinestate interfaces
let mut interfaces = Vec::<MachineStateInterface>::new();
// Make default route interface
{
// Find existing network or create a new one from network or blueprint definition
let network_id = machine_registry_inner
.get_or_create_network_state_from_location(&machine_def.location)?;
let srng = machine_registry_inner.unlocked_inner.srng.clone();
let network_state = machine_registry_inner
.get_network_state_by_id(network_id)
.expect("must exist");
// Build list of default route interface addresses
let mut addrs = Vec::<InterfaceAddress>::new();
// Make the default route interface
let machine_location = machine_def.location;
let (allocate_v4, opt_address4, allocate_v6, opt_address6) = match machine_location {
config::MachineLocation::Specific {
network: _,
address4,
address6,
} => (
network_state.ipv4.is_some() && address4.is_some(),
address4,
network_state.ipv6.is_some() && address6.is_some(),
address6,
),
config::MachineLocation::Network { network: _ }
| config::MachineLocation::Blueprint { blueprint: _ } => (
network_state.ipv4.is_some(),
None,
network_state.ipv6.is_some(),
None,
),
};
if allocate_v4 {
let if_addr4 =
match network_state.allocate_address_v4(srng.clone(), machine_id, opt_address4)
{
Ok(v) => v,
Err(e) => {
network_state
.release_all_addresses(addrs.iter().map(|x| x.if_addr.ip()))
.expect("must succeed");
return Err(e);
}
};
addrs.push(InterfaceAddress {
if_addr: IfAddr::V4(if_addr4),
flags: AddressFlags {
is_dynamic: false,
is_temporary: false,
is_preferred: true,
},
});
}
if allocate_v6 {
let if_addr6 =
match network_state.allocate_address_v6(srng.clone(), machine_id, opt_address6)
{
Ok(v) => v,
Err(e) => {
network_state
.release_all_addresses(addrs.iter().map(|x| x.if_addr.ip()))
.expect("must succeed");
return Err(e);
}
};
addrs.push(InterfaceAddress {
if_addr: IfAddr::V6(if_addr6),
flags: AddressFlags {
is_dynamic: false,
is_temporary: false,
is_preferred: true,
},
});
}
// Allocate an address on the network and make an veilid-style interface record for it
let network_interface = NetworkInterface {
name: "vin0".to_owned(),
flags: InterfaceFlags {
is_loopback: false,
is_running: true,
is_point_to_point: false,
has_default_route: true,
},
addrs,
};
interfaces.push(MachineStateInterface {
network_id,
network_interface,
});
}
// Create a localhost interface for this machine
Ok(Self {
name,
machine_def,
interfaces,
})
}
pub fn release(self, machine_registry_inner: &mut MachineRegistryInner) {
for intf in self.interfaces {
let network_state = machine_registry_inner
.get_network_state_by_id(intf.network_id)
.expect("must exist");
let addrs = &intf.network_interface.addrs;
network_state
.release_all_addresses(addrs.iter().map(|x| x.if_addr.ip()))
.expect("must succeed");
}
}
}

View File

@ -0,0 +1,15 @@
mod blueprint_state;
mod machine_state;
mod network_state;
mod profile_state;
mod resolves_to;
mod template_state;
use super::*;
pub use blueprint_state::*;
pub use machine_state::*;
pub use network_state::*;
pub use profile_state::*;
pub use resolves_to::*;
pub use template_state::*;

View File

@ -0,0 +1,306 @@
use super::*;
use ipnet::*;
#[derive(Debug)]
pub enum NetworkStateName {
Network(String),
Blueprint(String),
}
#[derive(Debug)]
pub struct NetworkState {
/// The name of this network state if it was made directly
/// or the name of the blueprint used to create it
pub name: NetworkStateName,
pub network_def: config::Network,
pub model: String,
pub ipv4: Option<NetworkStateIpv4>,
pub ipv6: Option<NetworkStateIpv6>,
}
#[derive(Debug)]
pub struct NetworkStateIpv4 {
pub allocation: Ipv4Net,
pub gateway: Option<NetworkGatewayState>,
pub machine_addresses: HashMap<Ipv4Addr, MachineId>,
}
#[derive(Debug)]
pub struct NetworkStateIpv6 {
pub allocation: Ipv6Net,
pub gateway: Option<NetworkGatewayState>,
pub machine_addresses: HashMap<Ipv6Addr, MachineId>,
}
#[derive(Debug)]
pub struct NetworkGatewayState {
pub translation: config::Translation, // xxx replace with translation state
pub upnp: bool,
pub network: Option<ResolvesTo<NetworkId>>,
}
pub type NetworkId = u64;
impl NetworkState {
pub fn try_new(
machine_registry_inner: &mut MachineRegistryInner,
name: NetworkStateName,
network_def: config::Network,
network_id: NetworkId,
) -> MachineRegistryResult<Self> {
let model = network_def.model.clone().unwrap_or_else(|| {
machine_registry_inner
.unlocked_inner
.config
.default_model
.clone()
});
let ipv4 = match network_def.ipv4.as_ref() {
Some(ipv4) => Some(NetworkStateIpv4 {
allocation: machine_registry_inner.choose_allocation_v4(
machine_registry_inner
.unlocked_inner
.config
.allocations
.get(&ipv4.allocation)
.cloned()
.ok_or(MachineRegistryError::InvalidAllocationName)?,
0,
)?,
gateway: match ipv4.gateway.as_ref() {
Some(v4gw) => Some(NetworkGatewayState {
translation: v4gw.translation,
upnp: v4gw.upnp,
network: v4gw.network.clone().map(|gwname| {
machine_registry_inner
.resolve_to_manager_network
.add(gwname)
}),
}),
None => None,
},
machine_addresses: HashMap::new(),
}),
None => None,
};
let ipv6 = match network_def.ipv6.as_ref() {
Some(ipv6) => Some(NetworkStateIpv6 {
allocation: machine_registry_inner.choose_allocation_v6(
machine_registry_inner
.unlocked_inner
.config
.allocations
.get(&ipv6.allocation)
.cloned()
.ok_or(MachineRegistryError::InvalidAllocationName)?,
0,
)?,
gateway: match ipv6.gateway.as_ref() {
Some(v6gw) => Some(NetworkGatewayState {
translation: v6gw.translation,
upnp: v6gw.upnp,
network: v6gw.network.clone().map(|gwname| {
machine_registry_inner
.resolve_to_manager_network
.add(gwname)
}),
}),
None => None,
},
machine_addresses: HashMap::new(),
}),
None => None,
};
Ok(Self {
name,
network_def,
model,
ipv4,
ipv6,
})
}
pub(super) fn allocate_address_v4(
&mut self,
srng: StableRng,
machine_id: MachineId,
opt_address: Option<Ipv4Addr>,
) -> MachineRegistryResult<Ifv4Addr> {
let Some(network_state_ipv4) = &mut self.ipv4 else {
return Err(MachineRegistryError::NoAllocation);
};
// for now we just pick randomly and then increment until we find a free allocation
// not enough addresses in any address space are allocated to warrant a more efficient algorithm
let hosts_range = network_state_ipv4.allocation.hosts();
let Some(first_host) = std::iter::Iterator::min(hosts_range) else {
return Err(MachineRegistryError::NoAllocation);
};
let Some(last_host) = std::iter::Iterator::max(hosts_range) else {
return Err(MachineRegistryError::NoAllocation);
};
let first_host_bits = first_host.to_bits();
let last_host_bits = last_host.to_bits();
// Check if a specific address is required
let ip = if let Some(ip_addr) = opt_address {
// Specific address required
let addr_bits = ip_addr.to_bits();
if addr_bits < first_host_bits || addr_bits > last_host_bits {
return Err(MachineRegistryError::NoAllocation);
}
if network_state_ipv4.machine_addresses.contains_key(&ip_addr) {
return Err(MachineRegistryError::NoAllocation);
}
ip_addr
} else {
// Any address will do
let addr_end = srng.next_u32(first_host_bits, last_host_bits);
// Find a free address starting from here
let mut addr = addr_end;
loop {
let ip_addr = Ipv4Addr::from(addr);
if network_state_ipv4.machine_addresses.contains_key(&ip_addr) {
addr += 1;
if addr > last_host_bits {
addr = first_host_bits;
}
if addr == addr_end {
return Err(MachineRegistryError::NoAllocation);
}
} else {
break ip_addr;
}
}
};
// Store the address
network_state_ipv4.machine_addresses.insert(ip, machine_id);
// Make interface address
let ifaddr = Ifv4Addr {
ip,
netmask: network_state_ipv4.allocation.netmask(),
broadcast: Some(network_state_ipv4.allocation.broadcast()),
};
Ok(ifaddr)
}
pub(super) fn release_address_v4(&mut self, addr: Ipv4Addr) -> MachineRegistryResult<()> {
if let Some(ipv4) = self.ipv4.as_mut() {
if ipv4.machine_addresses.remove(&addr).is_some() {
return Ok(());
}
}
Err(MachineRegistryError::NoAllocation)
}
pub(super) fn release_address_v6(&mut self, addr: Ipv6Addr) -> MachineRegistryResult<()> {
if let Some(ipv6) = self.ipv6.as_mut() {
if ipv6.machine_addresses.remove(&addr).is_some() {
return Ok(());
}
}
Err(MachineRegistryError::NoAllocation)
}
pub(super) fn release_all_addresses<I: Iterator<Item = IpAddr>>(
&mut self,
addrs: I,
) -> MachineRegistryResult<()> {
let mut ok = true;
for addr in addrs {
match addr {
IpAddr::V4(ipv4_addr) => {
if self.release_address_v4(ipv4_addr).is_err() {
ok = false;
}
}
IpAddr::V6(ipv6_addr) => {
if self.release_address_v6(ipv6_addr).is_err() {
ok = false;
}
}
}
}
if ok {
Ok(())
} else {
Err(MachineRegistryError::NoAllocation)
}
}
pub(super) fn allocate_address_v6(
&mut self,
srng: StableRng,
machine_id: MachineId,
opt_address: Option<Ipv6Addr>,
) -> MachineRegistryResult<Ifv6Addr> {
let Some(network_state_ipv6) = &mut self.ipv6 else {
return Err(MachineRegistryError::NoAllocation);
};
// for now we just pick randomly and then increment until we find a free allocation
// not enough addresses in any address space are allocated to warrant a more efficient algorithm
let hosts_range = network_state_ipv6.allocation.hosts();
let Some(first_host) = std::iter::Iterator::min(hosts_range) else {
return Err(MachineRegistryError::NoAllocation);
};
let Some(last_host) = std::iter::Iterator::max(hosts_range) else {
return Err(MachineRegistryError::NoAllocation);
};
let first_host_bits = first_host.to_bits();
let last_host_bits = last_host.to_bits();
// Check if a specific address is required
let ip = if let Some(ip_addr) = opt_address {
// Specific address required
let addr_bits = ip_addr.to_bits();
if addr_bits < first_host_bits || addr_bits > last_host_bits {
return Err(MachineRegistryError::NoAllocation);
}
if network_state_ipv6.machine_addresses.contains_key(&ip_addr) {
return Err(MachineRegistryError::NoAllocation);
}
ip_addr
} else {
// Any address will do
let addr_end = srng.next_u128(first_host_bits, last_host_bits);
// Find a free address starting from here
let mut addr = addr_end;
loop {
let ip_addr = Ipv6Addr::from(addr);
if network_state_ipv6.machine_addresses.contains_key(&ip_addr) {
addr += 1;
if addr > last_host_bits {
addr = first_host_bits;
}
if addr == addr_end {
return Err(MachineRegistryError::NoAllocation);
}
} else {
break ip_addr;
}
}
};
// Store the address
network_state_ipv6.machine_addresses.insert(ip, machine_id);
// Make interface address
let ifaddr = Ifv6Addr {
ip,
netmask: network_state_ipv6.allocation.netmask(),
broadcast: Some(network_state_ipv6.allocation.broadcast()),
};
Ok(ifaddr)
}
}

View File

@ -0,0 +1,4 @@
#[derive(Debug, Default)]
pub struct ProfileState {
pub next_instance_index: usize,
}

View File

@ -0,0 +1,72 @@
use super::*;
#[derive(Debug)]
pub enum ResolveToError {
MissingSymbol,
AlreadyResolved,
}
pub type ResolveToResult<T> = Result<T, ResolveToError>;
#[derive(Clone, Debug)]
pub struct ResolvesTo<I>
where
I: Clone + fmt::Debug,
{
value: Arc<Mutex<Option<I>>>,
}
impl<I> ResolvesTo<I>
where
I: Clone + fmt::Debug,
{
pub fn get(&self) -> Option<I> {
self.value.lock().clone()
}
}
#[derive(Debug)]
pub struct ResolveToManager<T, I>
where
T: PartialEq + Eq + PartialOrd + Ord + fmt::Debug,
I: Clone + fmt::Debug,
{
symbols: BTreeMap<Arc<T>, Arc<Mutex<Option<I>>>>,
}
impl<T, I> ResolveToManager<T, I>
where
T: PartialEq + Eq + PartialOrd + Ord + fmt::Debug,
I: Clone + fmt::Debug,
{
pub fn new() -> Self {
Self {
symbols: BTreeMap::new(),
}
}
pub fn add(&mut self, symbol: T) -> ResolvesTo<I> {
let symbol = Arc::new(symbol);
let value = self
.symbols
.entry(symbol.clone())
.or_insert_with(|| Arc::new(Mutex::new(None)))
.clone();
ResolvesTo { value }
}
pub fn resolve(&mut self, symbol: &T, value: I) -> ResolveToResult<()> {
match self.symbols.get_mut(symbol) {
Some(s) => {
let mut inner = s.lock();
if inner.is_some() {
return Err(ResolveToError::AlreadyResolved);
}
*inner = Some(value);
Ok(())
}
None => Err(ResolveToError::MissingSymbol),
}
}
}

View File

@ -0,0 +1,33 @@
use super::*;
#[derive(Debug)]
pub struct TemplateState {
pub name: String,
pub template_def: config::Template,
pub limit_machine_count: u32,
pub machines: HashSet<MachineId>,
}
impl TemplateState {
pub fn try_new(
machine_registry_inner: &mut MachineRegistryInner,
name: String,
template_def: config::Template,
) -> MachineRegistryResult<TemplateState> {
let limit_machine_count = *machine_registry_inner
.unlocked_inner
.srng
.weighted_choice(&template_def.limits.machine_count);
Ok(Self {
name,
template_def,
limit_machine_count,
machines: HashSet::new(),
})
}
pub fn is_active(&self) -> bool {
self.machines.len() < self.limit_machine_count as usize
}
}

View File

@ -1,11 +1,13 @@
pub mod config;
mod machine_registry;
mod server_processor;
mod stable_rng;
use super::*;
use machine_registry::*;
use server_processor::*;
use stable_rng::*;
use async_tungstenite::accept_async;
use futures_codec::{Bytes, BytesCodec, FramedRead, FramedWrite};

View File

@ -11,7 +11,7 @@ seed: 0
default_network: "$internet"
# The name of the predefined performance model to use by default (typically
# this is '$')
# this is '$lan')
default_model: "$lan"
#################################################################

View File

@ -0,0 +1,85 @@
use super::*;
use rand::{seq::SliceRandom, Rng, SeedableRng};
use rand_chacha::ChaCha20Rng;
struct StableRngInner {
srng: ChaCha20Rng,
count: usize,
}
impl fmt::Debug for StableRngInner {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("StableRngInner")
.field("count", &self.count)
.finish()
}
}
#[derive(Clone, Debug)]
pub struct StableRng {
inner: Arc<Mutex<StableRngInner>>,
}
impl StableRng {
////////////////////////////////////////////////////////
// Public Interface
pub fn new(seed: u64) -> Self {
Self {
inner: Arc::new(Mutex::new(StableRngInner {
srng: ChaCha20Rng::seed_from_u64(seed),
count: 0,
})),
}
}
pub fn weighted_choice<'a, T: fmt::Debug + Clone>(
&self,
weighted_list: &'a config::WeightedList<T>,
) -> &'a T {
match weighted_list {
config::WeightedList::Single(x) => x,
config::WeightedList::List(vec) => {
let total_weight = vec
.iter()
.map(|x| x.weight())
.reduce(|acc, x| acc + x)
.expect("config validation broken");
let r = self.next_f32(0.0, total_weight);
let mut current_weight = 0.0f32;
for x in vec {
current_weight += x.weight();
if r < current_weight {
return x.item();
}
}
// Catch f32 imprecision
vec.last().expect("config validation broken").item()
}
}
}
pub fn shuffle_vec<T>(&self, v: &mut Vec<T>) {
let mut inner = self.inner.lock();
inner.count += 1;
v.shuffle(&mut inner.srng);
}
pub fn next_u32(&self, min: u32, max: u32) -> u32 {
let mut inner = self.inner.lock();
inner.count += 1;
inner.srng.gen_range(min..=max)
}
pub fn next_u128(&self, min: u128, max: u128) -> u128 {
let mut inner = self.inner.lock();
inner.count += 1;
inner.srng.gen_range(min..=max)
}
pub fn next_f32(&self, min: f32, max: f32) -> f32 {
let mut inner = self.inner.lock();
inner.count += 1;
inner.srng.gen_range(min..=max)
}
}