mirror of
https://gitlab.com/veilid/veilid.git
synced 2025-03-15 10:26:33 -04:00
[ci skip]
checkpoint (doesn't compile)
This commit is contained in:
parent
ea53ad5980
commit
9b516f902b
623
Cargo.lock
generated
623
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -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]
|
||||
|
@ -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;
|
||||
|
@ -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))?;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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.
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BlueprintState {
|
||||
pub name: String,
|
||||
pub blueprint_def: config::Blueprint,
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
@ -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::*;
|
@ -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)
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ProfileState {
|
||||
pub next_instance_index: usize,
|
||||
}
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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};
|
||||
|
@ -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"
|
||||
|
||||
#################################################################
|
||||
|
85
veilid-tools/src/virtual_network/router_server/stable_rng.rs
Normal file
85
veilid-tools/src/virtual_network/router_server/stable_rng.rs
Normal 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)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user