[skip ci] blueprint config execution

This commit is contained in:
Christien Rioux 2025-01-14 20:23:09 -05:00
parent 54005b1aec
commit a5846af804
12 changed files with 1343 additions and 800 deletions

View File

@ -80,9 +80,9 @@ fn main() -> Result<(), String> {
let router_server = virtual_network::RouterServer::new();
if let Err(e) = router_server.execute_config(initial_config) {
xxx continue here
}
router_server
.execute_config(initial_config)
.map_err(|e| format!("Error executing config: {}", e))?;
let _ss_tcp = if !args.no_tcp {
Some(

View File

@ -120,10 +120,10 @@ pub struct TemplateLimits {
/// maximum number of machines this template will generate
#[validate(nested)]
#[serde(default)]
pub machine_count: Option<WeightedList<u32>>,
pub machine_count: Option<WeightedList<usize>>,
#[validate(nested)]
#[serde(default)]
pub machines_per_network: Option<WeightedList<u32>>,
pub machines_per_network: Option<WeightedList<usize>>,
}
fn validate_template_limits(limits: &TemplateLimits) -> Result<(), ValidationError> {
@ -255,7 +255,7 @@ pub struct BlueprintLimits {
/// maximum number of networks this blueprint will generate
#[validate(nested)]
#[serde(default)]
pub network_count: Option<WeightedList<u32>>,
pub network_count: Option<WeightedList<usize>>,
}
fn validate_blueprint_limits(limits: &BlueprintLimits) -> Result<(), ValidationError> {
@ -329,7 +329,7 @@ fn validate_blueprint_ipv4(blueprint_ipv4: &BlueprintIpv4) -> Result<(), Validat
pub struct BlueprintIpv6 {
#[serde(flatten)]
#[validate(nested)]
pub allocation: BlueprintLocation,
pub location: BlueprintLocation,
#[validate(nested)]
pub prefix: WeightedList<u8>,
#[serde(default)]
@ -354,29 +354,27 @@ pub struct BlueprintGateway {
pub translation: WeightedList<Translation>,
#[validate(range(min = 0.0, max = 1.0))]
pub upnp: Probability,
#[serde(flatten)]
pub location: TemplateLocation,
#[serde(default, flatten)]
#[validate(nested)]
pub location: Option<TemplateLocation>,
}
////////////////////////////////////////////////////////////////
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
#[validate(schema(function = "validate_subnets"))]
pub struct Subnets {
pub struct Scope4 {
#[validate(length(min = 1))]
pub scope4: Vec<Ipv4Net>,
#[serde(default)]
#[validate(nested)]
pub subnet4: Option<WeightedList<Ipv4Net>>,
#[serde(default)]
#[validate(nested)]
pub subnet6: Option<WeightedList<Ipv6Net>>,
pub pool4: Option<String>,
}
fn validate_subnets(subnets: &Subnets) -> Result<(), ValidationError> {
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()));
}
Ok(())
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
pub struct Scope6 {
#[validate(length(min = 1))]
pub scope6: Vec<Ipv6Net>,
#[serde(default)]
pub pool6: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
@ -398,7 +396,7 @@ fn validate_distance(distance: &Distance) -> Result<(), ValidationError> {
Ok(())
}
#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
#[derive(Debug, Clone, Serialize, Deserialize, Validate, Default)]
#[validate(schema(function = "validate_distribution"))]
pub struct Distribution {
pub mean: f32,
@ -455,7 +453,10 @@ pub struct Model {
pub struct Allocation {
#[serde(flatten)]
#[validate(nested)]
pub subnets: Subnets,
pub scope4: Option<Scope4>,
#[serde(flatten)]
#[validate(nested)]
pub scope6: Option<Scope6>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
@ -467,6 +468,8 @@ pub struct Config {
#[serde(default)]
pub default_model: Option<String>,
#[serde(default)]
pub default_pool: Option<String>,
#[serde(default)]
pub profiles: HashMap<String, Profile>,
#[serde(default)]
pub machines: HashMap<String, Machine>,
@ -509,6 +512,17 @@ impl Validate for Config {
}
}
if let Some(default_pool) = self.default_pool.as_ref() {
if default_pool.is_empty() {
errors.add(
"default_pool",
ValidationError::new("badlen").with_message(
"Config must have non-empty default pool if specified".into(),
),
);
}
}
errors.merge_self("profiles", validate_hash_map(&self.profiles));
errors.merge_self("machines", validate_hash_map(&self.machines));
errors.merge_self("templates", validate_hash_map(&self.templates));
@ -575,6 +589,7 @@ impl Config {
seed: other.seed.or(self.seed),
default_network: other.default_network.or(self.default_network),
default_model: other.default_model.or(self.default_model),
default_pool: other.default_pool.or(self.default_pool),
profiles: self.profiles.into_iter().chain(other.profiles).collect(),
machines: self.machines.into_iter().chain(other.machines).collect(),
templates: self.templates.into_iter().chain(other.templates).collect(),

View File

@ -14,6 +14,9 @@
# this is '$lan')
# default_model: "$lan"
# The name of the default allocation pool that subnets are allocated from
# default_pool: "$internet"
#################################################################
# Profiles
#
@ -174,56 +177,58 @@ blueprints:
allocations:
# Custom network allocations
boot:
subnet4: ["170.64.128.0/24"]
subnet6: ["2a03:b0c0:2::/48"]
# # Predefined networks
# $internet: {}
scope4: ["170.64.128.0/24"]
scope6: ["2a03:b0c0:2::/48"]
# # Predefined allocations
# $internet:
# scope4: ["0.0.0.0/0"]
# scope6: ["::/0"]
# $private:
# subnet4: ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
# subnet6: ["fc00::/7"]
# scope4: ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
# scope6: ["fc00::/7"]
# $cgnat:
# subnet4: ["100.64.0.0/10"]
# scope4: ["100.64.0.0/10"]
# $linklocal:
# subnet4: ["169.254.0.0/16"]
# subnet6: ["fe80::/10"]
# scope4: ["169.254.0.0/16"]
# scope6: ["fe80::/10"]
# $localhost:
# subnet4: ["127.0.0.0/8"]
# subnet6: ["::1/128"]
# scope4: ["127.0.0.0/8"]
# scope6: ["::1/128"]
# $ietf:
# subnet4: ["192.0.0.0/24"]
# scope4: ["192.0.0.0/24"]
# $cellnat:
# subnet4: ["192.0.0.0/29"]
# scope4: ["192.0.0.0/29"]
# $documentation:
# subnet4: ["192.0.2.0/24", "198.51.100.0/24", "203.0.113.0/24"]
# subnet6: ["2001:db8::/32", "3fff::/20"]
# scope4: ["192.0.2.0/24", "198.51.100.0/24", "203.0.113.0/24"]
# scope6: ["2001:db8::/32", "3fff::/20"]
# $benchmark:
# subnet4: ["198.18.0.0/15"]
# scope4: ["198.18.0.0/15"]
# $mulitcast:
# subnet4: ["224.0.0.0/4"]
# scope4: ["224.0.0.0/4"]
# $mulitcasttest:
# subnet4: ["233.252.0.0/24"]
# subnet6: ["ff00::/8"]
# scope4: ["233.252.0.0/24"]
# scope6: ["ff00::/8"]
# $unspecified:
# subnet4: ["0.0.0.0/8"]
# subnet6: ["::/128"]
# scope4: ["0.0.0.0/8"]
# scope6: ["::/128"]
# $reserved:
# subnet4: ["192.88.99.0/24", "240.0.0.0/4"]
# scope4: ["192.88.99.0/24", "240.0.0.0/4"]
# $broadcast:
# subnet4: ["255.255.255.255/32"]
# scope4: ["255.255.255.255/32"]
# $mapped:
# subnet6: ["::ffff:0:0/96", "::ffff:0:0:0/96"]
# scope6: ["::ffff:0:0/96", "::ffff:0:0:0/96"]
# $translation:
# subnet6: ["64:ff9b::/96", "64:ff9b:1::/48"]
# scope6: ["64:ff9b::/96", "64:ff9b:1::/48"]
# $discard:
# subnet6: ["100::/64"]
# scope6: ["100::/64"]
# $teredo:
# subnet6: ["2001::/32"]
# scope6: ["2001::/32"]
# $orchidv2:
# subnet6: ["2001:20::/28"]
# scope6: ["2001:20::/28"]
# $6to4:
# subnet6: ["2002::/16"]
# scope6: ["2002::/16"]
# $srv6:
# subnet6: ["5f00::/16"]
# scope6: ["5f00::/16"]
#################################################################
# Models
#

View File

@ -26,6 +26,19 @@ impl<T: fmt::Debug + Clone> AddressPool<T> {
/////////////////////////////////////////////////////////////////////
pub fn scopes_v4(&self) -> Vec<Ipv4Net> {
self.scope_v4.iter().cloned().collect()
}
pub fn allocations_v4(&self) -> Vec<Ipv4Net> {
self.allocated_v4.iter().cloned().collect()
}
pub fn scopes_v6(&self) -> Vec<Ipv6Net> {
self.scope_v6.iter().cloned().collect()
}
pub fn allocations_v6(&self) -> Vec<Ipv6Net> {
self.allocated_v6.iter().cloned().collect()
}
pub fn add_scope_v4(&mut self, allocation: Ipv4Net) {
let mut scopes = self.scope_v4.iter().copied().collect::<Vec<_>>();
scopes.push(allocation);
@ -40,22 +53,22 @@ impl<T: fmt::Debug + Clone> AddressPool<T> {
self.scope_v6 = scopes.into();
}
pub fn is_in_scope_v4(&self, allocation: Ipv4Net) -> bool {
pub fn find_scope_v4(&self, allocation: Ipv4Net) -> Option<Ipv4Net> {
for x in &self.scope_v4 {
if x.contains(&allocation) {
return true;
return Some(*x);
}
}
false
None
}
pub fn is_in_scope_v6(&self, allocation: Ipv6Net) -> bool {
pub fn find_scope_v6(&self, allocation: Ipv6Net) -> Option<Ipv6Net> {
for x in &self.scope_v6 {
if x.contains(&allocation) {
return true;
return Some(*x);
}
}
false
None
}
pub fn can_allocate_v6(&self, prefix: u8) -> GlobalStateManagerResult<bool> {
@ -82,11 +95,11 @@ impl<T: fmt::Debug + Clone> AddressPool<T> {
&mut self,
allocation: Ipv4Net,
opt_tag: Option<T>,
) -> GlobalStateManagerResult<()> {
) -> GlobalStateManagerResult<Ipv4Net> {
// Ensure the allocation is in our scope
if !self.is_in_scope_v4(allocation) {
let Some(scope) = self.find_scope_v4(allocation) else {
return Err(GlobalStateManagerError::NoAllocation);
}
};
// Only reserve if it's not overlapping an allocation
if !self.get_overlaps_v4(allocation).is_empty() {
@ -97,18 +110,18 @@ impl<T: fmt::Debug + Clone> AddressPool<T> {
self.allocated_v4.insert_ord(allocation);
self.owner_tags_v4.insert(allocation, opt_tag);
Ok(())
Ok(scope)
}
pub fn reserve_allocation_v6(
&mut self,
allocation: Ipv6Net,
opt_tag: Option<T>,
) -> GlobalStateManagerResult<()> {
) -> GlobalStateManagerResult<Ipv6Net> {
// Ensure the allocation is in our scope
if !self.is_in_scope_v6(allocation) {
let Some(scope) = self.find_scope_v6(allocation) else {
return Err(GlobalStateManagerError::NoAllocation);
}
};
// Only reserve if it's not overlapping an allocation
if !self.get_overlaps_v6(allocation).is_empty() {
@ -119,7 +132,7 @@ impl<T: fmt::Debug + Clone> AddressPool<T> {
self.allocated_v6.insert_ord(allocation);
self.owner_tags_v6.insert(allocation, opt_tag);
Ok(())
Ok(scope)
}
pub fn get_overlaps_v4(&self, allocation: Ipv4Net) -> Vec<Ipv4Net> {

View File

@ -48,14 +48,20 @@ pub enum GlobalStateManagerError {
BlueprintNotFound(String),
#[error("Model not found: {0}")]
ModelNotFound(String),
#[error("Allocation not found: {0}")]
AllocationNotFound(String),
#[error("No default model")]
NoDefaultModel,
#[error("No default network")]
NoDefaultNetwork,
#[error("No default pool")]
NoDefaultPool,
#[error("No allocation available")]
NoAllocation,
#[error("Resource in use: {0}")]
ResourceInUse(String),
#[error("Invalid gateway")]
InvalidGateway,
}
pub type GlobalStateManagerResult<T> = Result<T, GlobalStateManagerError>;

View File

@ -69,8 +69,8 @@ pub struct BlueprintState {
pub type BlueprintStateId = StateId<BlueprintState>;
impl BlueprintState {
pub fn new(id: BlueprintStateId, name: String) -> GlobalStateManagerResult<BlueprintState> {
Ok(Self {
pub fn new(id: BlueprintStateId, name: String) -> Self {
Self {
immutable: Arc::new(BlueprintStateImmutable { id, name }),
fields: Arc::new(BlueprintStateFields {
limit_network_count: None,
@ -79,7 +79,7 @@ impl BlueprintState {
ipv4: None,
ipv6: None,
}),
})
}
}
pub fn set_limit_network_count(&mut self, limit_network_count: Option<usize>) {
@ -99,12 +99,56 @@ impl BlueprintState {
});
}
pub fn clear_ipv4(
&mut self,
gsm_inner: &mut GlobalStateManagerInner,
) -> GlobalStateManagerResult<()> {
self.clear_ipv4_gateway(gsm_inner)?;
if self.fields.ipv4.is_none() {
return Ok(());
};
// Update fields
self.fields = Arc::new(BlueprintStateFields {
ipv4: None,
..(*self.fields).clone()
});
Ok(())
}
pub fn clear_ipv4_gateway(
&mut self,
_gsm_inner: &mut GlobalStateManagerInner,
) -> GlobalStateManagerResult<()> {
let Some(mut ipv4) = self.fields.ipv4.clone() else {
return Ok(());
};
let Some(_gateway) = ipv4.gateway else {
return Ok(());
};
// Clear gateway
ipv4.gateway = None;
// Update fields
self.fields = Arc::new(BlueprintStateFields {
ipv4: Some(ipv4),
..(*self.fields).clone()
});
Ok(())
}
pub fn set_ipv4(
&mut self,
gsm_inner: &mut GlobalStateManagerInner,
params: BlueprintStateIpv4Params,
gateway_params: Option<BlueprintStateGatewayParams>,
) {
let mut ipv4 = if let Some(ipv4) = self.fields.ipv4.clone() {
) -> GlobalStateManagerResult<()> {
self.clear_ipv4(gsm_inner)?;
let ipv4 = if let Some(ipv4) = self.fields.ipv4.clone() {
BlueprintStateIpv4 { params, ..ipv4 }
} else {
BlueprintStateIpv4 {
@ -113,6 +157,26 @@ impl BlueprintState {
}
};
// Update fields
self.fields = Arc::new(BlueprintStateFields {
ipv4: Some(ipv4),
..(*self.fields).clone()
});
Ok(())
}
pub fn set_ipv4_gateway(
&mut self,
gsm_inner: &mut GlobalStateManagerInner,
gateway_params: Option<BlueprintStateGatewayParams>,
) -> GlobalStateManagerResult<()> {
self.clear_ipv4_gateway(gsm_inner)?;
let Some(mut ipv4) = self.fields.ipv4.clone() else {
return Err(GlobalStateManagerError::InvalidGateway);
};
if ipv4.gateway.is_some() {
if let Some(gateway_params) = gateway_params {
ipv4.gateway.as_mut().expect("must exist").params = gateway_params;
@ -130,14 +194,60 @@ impl BlueprintState {
ipv4: Some(ipv4),
..(*self.fields).clone()
});
Ok(())
}
pub fn clear_ipv6(
&mut self,
gsm_inner: &mut GlobalStateManagerInner,
) -> GlobalStateManagerResult<()> {
self.clear_ipv6_gateway(gsm_inner)?;
if self.fields.ipv6.is_none() {
return Ok(());
};
// Update fields
self.fields = Arc::new(BlueprintStateFields {
ipv6: None,
..(*self.fields).clone()
});
Ok(())
}
pub fn clear_ipv6_gateway(
&mut self,
_gsm_inner: &mut GlobalStateManagerInner,
) -> GlobalStateManagerResult<()> {
let Some(mut ipv6) = self.fields.ipv6.clone() else {
return Ok(());
};
let Some(_gateway) = ipv6.gateway else {
return Ok(());
};
// Clear gateway
ipv6.gateway = None;
// Update fields
self.fields = Arc::new(BlueprintStateFields {
ipv6: Some(ipv6),
..(*self.fields).clone()
});
Ok(())
}
pub fn set_ipv6(
&mut self,
gsm_inner: &mut GlobalStateManagerInner,
params: BlueprintStateIpv6Params,
gateway_params: Option<BlueprintStateGatewayParams>,
) {
let mut ipv6 = if let Some(ipv6) = self.fields.ipv6.clone() {
) -> GlobalStateManagerResult<()> {
self.clear_ipv6(gsm_inner)?;
let ipv6 = if let Some(ipv6) = self.fields.ipv6.clone() {
BlueprintStateIpv6 { params, ..ipv6 }
} else {
BlueprintStateIpv6 {
@ -146,6 +256,26 @@ impl BlueprintState {
}
};
// Update fields
self.fields = Arc::new(BlueprintStateFields {
ipv6: Some(ipv6),
..(*self.fields).clone()
});
Ok(())
}
pub fn set_ipv6_gateway(
&mut self,
gsm_inner: &mut GlobalStateManagerInner,
gateway_params: Option<BlueprintStateGatewayParams>,
) -> GlobalStateManagerResult<()> {
self.clear_ipv6_gateway(gsm_inner)?;
let Some(mut ipv6) = self.fields.ipv6.clone() else {
return Err(GlobalStateManagerError::InvalidGateway);
};
if ipv6.gateway.is_some() {
if let Some(gateway_params) = gateway_params {
ipv6.gateway.as_mut().expect("must exist").params = gateway_params;
@ -163,6 +293,8 @@ impl BlueprintState {
ipv6: Some(ipv6),
..(*self.fields).clone()
});
Ok(())
}
pub fn is_active(&self, gsm_inner: &mut GlobalStateManagerInner) -> bool {
@ -197,7 +329,7 @@ impl BlueprintState {
};
let params = NetworkStateModelParams {
latency: Some(model.latency.clone()),
latency: model.latency.clone(),
distance: model.distance.clone(),
loss: model.loss,
};
@ -246,7 +378,11 @@ impl BlueprintState {
};
// Get addresses for network
let Some(NetworkLocation { subnet, super_net }) = ipv4
let Some(NetworkLocation {
scope,
reserve,
super_net,
}) = ipv4
.params
.locations
.pick_v4(gsm_inner, &ipv4.params.prefix)?
@ -257,7 +393,8 @@ impl BlueprintState {
};
let params = NetworkStateIpv4Params {
allocation: subnet,
scope,
reserve,
super_net,
};
@ -320,7 +457,10 @@ impl BlueprintState {
None => None,
};
network_state.set_ipv4(gsm_inner, params, gateway_params)?;
network_state.set_ipv4(gsm_inner, params)?;
if let Some(gateway_params) = gateway_params {
network_state.set_ipv4_gateway(gsm_inner, gateway_params)?;
}
Ok(())
}
@ -335,7 +475,11 @@ impl BlueprintState {
};
// Get addresses for network
let Some(NetworkLocation { subnet, super_net }) = ipv6
let Some(NetworkLocation {
scope,
reserve,
super_net,
}) = ipv6
.params
.locations
.pick_v6(gsm_inner, &ipv6.params.prefix)?
@ -346,7 +490,8 @@ impl BlueprintState {
};
let params = NetworkStateIpv6Params {
allocation: subnet,
scope,
reserve,
super_net,
};
@ -409,7 +554,10 @@ impl BlueprintState {
None => None,
};
network_state.set_ipv6(gsm_inner, params, gateway_params)?;
network_state.set_ipv6(gsm_inner, params)?;
if let Some(gateway_params) = gateway_params {
network_state.set_ipv6_gateway(gsm_inner, gateway_params)?;
}
Ok(())
}

View File

@ -13,7 +13,8 @@ pub enum NetworkLocationsList {
#[derive(Debug, Clone)]
pub struct NetworkLocation<T> {
pub subnet: T,
pub scope: Vec<T>,
pub reserve: Vec<T>,
pub super_net: Option<NetworkStateId>,
}
@ -35,37 +36,31 @@ impl NetworkLocationsList {
NetworkLocationsList::Allocations { allocations } => {
// Get allocations which have subnets that would fit
// our maximum requested prefix
let Some(alloc_subnets) = allocations.try_filter_map(|allocation_name| {
let Some(address_pools) = allocations.try_filter_map(|allocation_name| {
let allocation = gsm_inner
.allocations()
.get(allocation_name)
.expect("must exist");
Ok(allocation
.subnets
.subnet4
.as_ref()
.and_then(|subnet| subnet.filter(|p| p.prefix_len() <= max_prefix)))
if allocation.address_pool.can_allocate_v4(max_prefix)? {
Ok(Some(allocation.address_pool.clone()))
} else {
Ok(None)
}
})?
else {
return Ok(None);
};
// Pick an allocation
let subnets = gsm_inner.srng().weighted_choice_ref(&alloc_subnets);
// Pick a subnet
let net = *gsm_inner.srng().weighted_choice_ref(subnets);
// Pick an address pool
let mut address_pool = gsm_inner.srng().weighted_choice(address_pools);
// Pick a prefix length that would fit in the subnet
let opt_subnet = prefix
.filter(|p| *p >= net.prefix_len())
.try_filter(|p| address_pool.can_allocate_v4(*p))?
.as_ref()
.map(|wl| {
let subnet_prefix = *gsm_inner.srng().weighted_choice_ref(wl);
// Use an address pool temporarily to pick a subnet
let mut address_pool = AddressPool::<()>::new();
address_pool.add_scope_v4(net);
address_pool.allocate_random_v4(gsm_inner.srng(), subnet_prefix, ())
})
.transpose()?
@ -74,7 +69,8 @@ impl NetworkLocationsList {
return Ok(None);
};
Ok(Some(NetworkLocation {
subnet,
scope: vec![subnet],
reserve: Vec::new(),
super_net: None,
}))
}
@ -126,7 +122,8 @@ impl NetworkLocationsList {
.set_state(super_network_state);
Ok(Some(NetworkLocation {
subnet,
scope: vec![subnet],
reserve: Vec::new(),
super_net: Some(super_network_id),
}))
}
@ -150,37 +147,31 @@ impl NetworkLocationsList {
NetworkLocationsList::Allocations { allocations } => {
// Get allocations which have subnets that would fit
// our maximum requested prefix
let Some(alloc_subnets) = allocations.try_filter_map(|allocation_name| {
let Some(address_pools) = allocations.try_filter_map(|allocation_name| {
let allocation = gsm_inner
.allocations()
.get(allocation_name)
.expect("must exist");
Ok(allocation
.subnets
.subnet6
.as_ref()
.and_then(|subnet| subnet.filter(|p| p.prefix_len() <= max_prefix)))
if allocation.address_pool.can_allocate_v6(max_prefix)? {
Ok(Some(allocation.address_pool.clone()))
} else {
Ok(None)
}
})?
else {
return Ok(None);
};
// Pick an allocation
let subnets = gsm_inner.srng().weighted_choice_ref(&alloc_subnets);
// Pick a subnet
let net = *gsm_inner.srng().weighted_choice_ref(subnets);
// Pick an address pool
let mut address_pool = gsm_inner.srng().weighted_choice(address_pools);
// Pick a prefix length that would fit in the subnet
let opt_subnet = prefix
.filter(|p| *p >= net.prefix_len())
.try_filter(|p| address_pool.can_allocate_v6(*p))?
.as_ref()
.map(|wl| {
let subnet_prefix = *gsm_inner.srng().weighted_choice_ref(wl);
// Use an address pool temporarily to pick a subnet
let mut address_pool = AddressPool::<()>::new();
address_pool.add_scope_v6(net);
address_pool.allocate_random_v6(gsm_inner.srng(), subnet_prefix, ())
})
.transpose()?
@ -189,7 +180,8 @@ impl NetworkLocationsList {
return Ok(None);
};
Ok(Some(NetworkLocation {
subnet,
scope: vec![subnet],
reserve: Vec::new(),
super_net: None,
}))
}
@ -241,7 +233,8 @@ impl NetworkLocationsList {
.set_state(super_network_state);
Ok(Some(NetworkLocation {
subnet,
scope: vec![subnet],
reserve: Vec::new(),
super_net: Some(super_network_id),
}))
}

View File

@ -44,7 +44,7 @@ struct NetworkStateModel {
#[derive(Debug, Clone)]
pub struct NetworkStateModelParams {
/// Network latency distribution
pub latency: Option<config::Distribution>,
pub latency: config::Distribution,
/// Distance simulation metric
pub distance: Option<config::Distance>,
/// Packet loss probability
@ -53,7 +53,8 @@ pub struct NetworkStateModelParams {
#[derive(Debug, Clone)]
pub struct NetworkStateIpv4Params {
pub allocation: Ipv4Net,
pub scope: Vec<Ipv4Net>,
pub reserve: Vec<Ipv4Net>,
pub super_net: Option<NetworkStateId>,
}
@ -65,7 +66,8 @@ struct NetworkStateIpv4 {
#[derive(Debug, Clone)]
pub struct NetworkStateIpv6Params {
pub allocation: Ipv6Net,
pub scope: Vec<Ipv6Net>,
pub reserve: Vec<Ipv6Net>,
pub super_net: Option<NetworkStateId>,
}
#[derive(Debug, Clone)]
@ -126,7 +128,7 @@ impl NetworkState {
address_pool: AddressPool::new(),
model: NetworkStateModel {
params: NetworkStateModelParams {
latency: None,
latency: config::Distribution::default(),
distance: None,
loss: 0.0,
},
@ -144,9 +146,7 @@ impl NetworkState {
.get_state(generating_blueprint)
.expect("must exist");
blueprint_state.on_network_released(self.id());
gsm_inner
.blueprint_states_mut()
.set_state(blueprint_state)
gsm_inner.blueprint_states_mut().set_state(blueprint_state)
}
}
@ -161,7 +161,9 @@ impl NetworkState {
&mut self,
gsm_inner: &mut GlobalStateManagerInner,
) -> GlobalStateManagerResult<()> {
let Some(ipv4) = self.fields.ipv4.clone() else {
self.clear_ipv4_gateway(gsm_inner)?;
if self.fields.ipv4.is_none() {
return Ok(());
};
@ -176,28 +178,6 @@ impl NetworkState {
GlobalStateManagerError::ResourceInUse(format!("{}-v4", self.debug_name()))
})?;
// If we have a gateway, release its external address
// if it belongs to a different network
if let Some(gateway) = ipv4.gateway.as_ref() {
if gateway.params.external_network != self.id() {
// Get the external network state
let mut external_network_state = gsm_inner
.network_states()
.get_state(gateway.params.external_network)
.expect("must succeed");
// Release external address
external_network_state
.release_address_v4(gateway.external_interface_address.ip)
.expect("must succeed");
// Update external network
gsm_inner
.network_states_mut()
.set_state(external_network_state);
}
}
// Update fields
self.fields = Arc::new(NetworkStateFields {
ipv4: None,
@ -208,88 +188,168 @@ impl NetworkState {
Ok(())
}
pub fn clear_ipv4_gateway(
&mut self,
gsm_inner: &mut GlobalStateManagerInner,
) -> GlobalStateManagerResult<()> {
let Some(mut ipv4) = self.fields.ipv4.clone() else {
return Ok(());
};
let Some(gateway) = ipv4.gateway else {
return Ok(());
};
if gateway.params.external_network != self.id() {
// Get the external network state
let mut external_network_state = gsm_inner
.network_states()
.get_state(gateway.params.external_network)
.expect("must succeed");
// Release external address
external_network_state
.release_address_v4(gateway.external_interface_address.ip)
.expect("must succeed");
// Update external network
gsm_inner
.network_states_mut()
.set_state(external_network_state);
}
// Release internal address
self.release_address_v4(gateway.internal_interface_address.ip)
.expect("must succeed");
// Clear gateway
ipv4.gateway = None;
// Update fields
self.fields = Arc::new(NetworkStateFields {
ipv4: Some(ipv4),
..(*self.fields).clone()
});
Ok(())
}
pub fn set_ipv4(
&mut self,
gsm_inner: &mut GlobalStateManagerInner,
params: NetworkStateIpv4Params,
gateway_params: Option<NetworkStateIpv4GatewayParams>,
) -> GlobalStateManagerResult<()> {
self.clear_ipv4(gsm_inner)?;
let mut address_pool = self.fields.address_pool.clone();
address_pool.add_scope_v4(params.allocation);
for scope in &params.scope {
address_pool.add_scope_v4(*scope);
}
for reserve in &params.reserve {
address_pool.reserve_allocation_v4(*reserve, None)?;
}
let gateway = match gateway_params {
Some(gateway_params) => {
// Allocate or reserve an internal network address for the gateway
let internal_address =
if let Some(internal_address) = gateway_params.internal_address {
address_pool.reserve_allocation_v4(
Ipv4Net::new(internal_address, 32).expect("must succeed"),
Some(OwnerTag::Gateway(self.id())),
)?;
internal_address
} else {
let Some(internal_address) = address_pool.allocate_random_v4(
gsm_inner.srng(),
32,
OwnerTag::Gateway(self.id()),
)?
else {
return Err(GlobalStateManagerError::NoAllocation);
};
internal_address.addr()
};
// Make internal interface address
let internal_interface_address = Ifv4Addr {
ip: internal_address,
netmask: params.allocation.netmask(),
broadcast: Some(params.allocation.broadcast()),
};
// Get the external network state
let mut external_network_state = gsm_inner
.network_states()
.get_state(gateway_params.external_network)
.expect("must succeed");
// Allocate or reserve an external network address for the gateway
let external_interface_address =
if matches!(gateway_params.translation, config::Translation::None) {
// If the translation mode is 'none', then the external and internal
// addresses must be the same
external_network_state.allocate_address_v4(
gsm_inner,
OwnerTag::Gateway(self.id()),
Some(internal_address),
)?
} else {
// Network translation means the internal and external addresses
// will be different
external_network_state.allocate_address_v4(
gsm_inner,
OwnerTag::Gateway(self.id()),
None,
)?
};
// Update external network
gsm_inner
.network_states_mut()
.set_state(external_network_state);
// Return the gateway state
Some(NetworkStateIpv4Gateway {
params: gateway_params,
internal_interface_address,
external_interface_address,
})
}
None => None,
let ipv4 = NetworkStateIpv4 {
params,
gateway: None,
};
let ipv4 = NetworkStateIpv4 { params, gateway };
// Update fields
self.fields = Arc::new(NetworkStateFields {
ipv4: Some(ipv4),
address_pool,
..(*self.fields).clone()
});
Ok(())
}
pub fn set_ipv4_gateway(
&mut self,
gsm_inner: &mut GlobalStateManagerInner,
gateway_params: NetworkStateIpv4GatewayParams,
) -> GlobalStateManagerResult<()> {
self.clear_ipv4_gateway(gsm_inner)?;
let Some(mut ipv4) = self.fields.ipv4.clone() else {
return Err(GlobalStateManagerError::InvalidGateway);
};
let mut address_pool = self.fields.address_pool.clone();
// Allocate or reserve an internal network address for the gateway
let internal_interface_address =
if let Some(internal_address) = gateway_params.internal_address {
let scope = address_pool.reserve_allocation_v4(
Ipv4Net::new(internal_address, 32).expect("must succeed"),
Some(OwnerTag::Gateway(self.id())),
)?;
// Make interface address
Ifv4Addr {
ip: internal_address,
netmask: scope.netmask(),
broadcast: Some(scope.broadcast()),
}
} else {
let Some(internal_address) = address_pool.allocate_random_v4(
gsm_inner.srng(),
32,
OwnerTag::Gateway(self.id()),
)?
else {
return Err(GlobalStateManagerError::NoAllocation);
};
// Get the scope this allocation fits in
let scope = address_pool
.find_scope_v4(internal_address)
.expect("must succeed");
// Make interface address
let internal_address = internal_address.addr();
Ifv4Addr {
ip: internal_address,
netmask: scope.netmask(),
broadcast: Some(scope.broadcast()),
}
};
// Get the external network state
let mut external_network_state = gsm_inner
.network_states()
.get_state(gateway_params.external_network)
.expect("must succeed");
// Allocate or reserve an external network address for the gateway
let external_interface_address =
if matches!(gateway_params.translation, config::Translation::None) {
// If the translation mode is 'none', then the external and internal
// addresses must be the same
external_network_state.allocate_address_v4(
gsm_inner,
OwnerTag::Gateway(self.id()),
Some(internal_interface_address.ip),
)?
} else {
// Network translation means the internal and external addresses
// will be different
external_network_state.allocate_address_v4(
gsm_inner,
OwnerTag::Gateway(self.id()),
None,
)?
};
// Update external network
gsm_inner
.network_states_mut()
.set_state(external_network_state);
// Set the gateway state
ipv4.gateway = Some(NetworkStateIpv4Gateway {
params: gateway_params,
internal_interface_address,
external_interface_address,
});
// Update fields
self.fields = Arc::new(NetworkStateFields {
@ -305,7 +365,9 @@ impl NetworkState {
&mut self,
gsm_inner: &mut GlobalStateManagerInner,
) -> GlobalStateManagerResult<()> {
let Some(ipv6) = self.fields.ipv6.clone() else {
self.clear_ipv6_gateway(gsm_inner)?;
if self.fields.ipv6.is_none() {
return Ok(());
};
@ -320,28 +382,6 @@ impl NetworkState {
GlobalStateManagerError::ResourceInUse(format!("{}-v6", self.debug_name()))
})?;
// If we have a gateway, release its external address
// if it belongs to a different network
if let Some(gateway) = ipv6.gateway.as_ref() {
if gateway.params.external_network != self.id() {
// Get the external network state
let mut external_network_state = gsm_inner
.network_states()
.get_state(gateway.params.external_network)
.expect("must succeed");
// Release external address
external_network_state
.release_address_v6(gateway.external_interface_address.ip)
.expect("must succeed");
// Update external network
gsm_inner
.network_states_mut()
.set_state(external_network_state);
}
}
// Update fields
self.fields = Arc::new(NetworkStateFields {
ipv6: None,
@ -351,88 +391,166 @@ impl NetworkState {
Ok(())
}
pub fn clear_ipv6_gateway(
&mut self,
gsm_inner: &mut GlobalStateManagerInner,
) -> GlobalStateManagerResult<()> {
let Some(mut ipv6) = self.fields.ipv6.clone() else {
return Ok(());
};
let Some(gateway) = ipv6.gateway else {
return Ok(());
};
if gateway.params.external_network != self.id() {
// Get the external network state
let mut external_network_state = gsm_inner
.network_states()
.get_state(gateway.params.external_network)
.expect("must succeed");
// Release external address
external_network_state
.release_address_v6(gateway.external_interface_address.ip)
.expect("must succeed");
// Update external network
gsm_inner
.network_states_mut()
.set_state(external_network_state);
}
// Release internal address
self.release_address_v6(gateway.internal_interface_address.ip)
.expect("must succeed");
// Clear gateway
ipv6.gateway = None;
// Update fields
self.fields = Arc::new(NetworkStateFields {
ipv6: Some(ipv6),
..(*self.fields).clone()
});
Ok(())
}
pub fn set_ipv6(
&mut self,
gsm_inner: &mut GlobalStateManagerInner,
params: NetworkStateIpv6Params,
gateway_params: Option<NetworkStateIpv6GatewayParams>,
) -> GlobalStateManagerResult<()> {
self.clear_ipv6(gsm_inner)?;
let mut address_pool = self.fields.address_pool.clone();
address_pool.add_scope_v6(params.allocation);
let gateway = match gateway_params {
Some(gateway_params) => {
// Allocate or reserve an internal network address for the gateway
let internal_address =
if let Some(internal_address) = gateway_params.internal_address {
address_pool.reserve_allocation_v6(
Ipv6Net::new(internal_address, 128).expect("must succeed"),
Some(OwnerTag::Gateway(self.id())),
)?;
internal_address
} else {
let Some(internal_address) = address_pool.allocate_random_v6(
gsm_inner.srng(),
128,
OwnerTag::Gateway(self.id()),
)?
else {
return Err(GlobalStateManagerError::NoAllocation);
};
internal_address.addr()
};
// Make internal interface address
let internal_interface_address = Ifv6Addr {
ip: internal_address,
netmask: params.allocation.netmask(),
broadcast: Some(params.allocation.broadcast()),
};
// Get the external network state
let mut external_network_state = gsm_inner
.network_states()
.get_state(gateway_params.external_network)
.expect("must succeed");
// Allocate or reserve an external network address for the gateway
let external_interface_address =
if matches!(gateway_params.translation, config::Translation::None) {
// If the translation mode is 'none', then the external and internal
// addresses must be the same
external_network_state.allocate_address_v6(
gsm_inner,
OwnerTag::Gateway(self.id()),
Some(internal_address),
)?
} else {
// Network translation means the internal and external addresses
// will be different
external_network_state.allocate_address_v6(
gsm_inner,
OwnerTag::Gateway(self.id()),
None,
)?
};
// Update external network
gsm_inner
.network_states_mut()
.set_state(external_network_state);
// Return the gateway state
Some(NetworkStateIpv6Gateway {
params: gateway_params,
internal_interface_address,
external_interface_address,
})
}
None => None,
for scope in &params.scope {
address_pool.add_scope_v6(*scope);
}
for reserve in &params.reserve {
address_pool.reserve_allocation_v6(*reserve, None)?;
}
let ipv6 = NetworkStateIpv6 {
params,
gateway: None,
};
let ipv6 = NetworkStateIpv6 { params, gateway };
// Update fields
self.fields = Arc::new(NetworkStateFields {
ipv6: Some(ipv6),
address_pool,
..(*self.fields).clone()
});
Ok(())
}
pub fn set_ipv6_gateway(
&mut self,
gsm_inner: &mut GlobalStateManagerInner,
gateway_params: NetworkStateIpv6GatewayParams,
) -> GlobalStateManagerResult<()> {
self.clear_ipv6_gateway(gsm_inner)?;
let Some(mut ipv6) = self.fields.ipv6.clone() else {
return Err(GlobalStateManagerError::InvalidGateway);
};
let mut address_pool = self.fields.address_pool.clone();
// Allocate or reserve an internal network address for the gateway
let internal_interface_address =
if let Some(internal_address) = gateway_params.internal_address {
let scope = address_pool.reserve_allocation_v6(
Ipv6Net::new(internal_address, 128).expect("must succeed"),
Some(OwnerTag::Gateway(self.id())),
)?;
// Make interface address
Ifv6Addr {
ip: internal_address,
netmask: scope.netmask(),
broadcast: Some(scope.broadcast()),
}
} else {
let Some(internal_address) = address_pool.allocate_random_v6(
gsm_inner.srng(),
128,
OwnerTag::Gateway(self.id()),
)?
else {
return Err(GlobalStateManagerError::NoAllocation);
};
// Get the scope this allocation fits in
let scope = address_pool
.find_scope_v6(internal_address)
.expect("must succeed");
// Make interface address
let internal_address = internal_address.addr();
Ifv6Addr {
ip: internal_address,
netmask: scope.netmask(),
broadcast: Some(scope.broadcast()),
}
};
// Get the external network state
let mut external_network_state = gsm_inner
.network_states()
.get_state(gateway_params.external_network)
.expect("must succeed");
// Allocate or reserve an external network address for the gateway
let external_interface_address =
if matches!(gateway_params.translation, config::Translation::None) {
// If the translation mode is 'none', then the external and internal
// addresses must be the same
external_network_state.allocate_address_v6(
gsm_inner,
OwnerTag::Gateway(self.id()),
Some(internal_interface_address.ip),
)?
} else {
// Network translation means the internal and external addresses
// will be different
external_network_state.allocate_address_v6(
gsm_inner,
OwnerTag::Gateway(self.id()),
None,
)?
};
// Update external network
gsm_inner
.network_states_mut()
.set_state(external_network_state);
// Set the gateway state
ipv6.gateway = Some(NetworkStateIpv6Gateway {
params: gateway_params,
internal_interface_address,
external_interface_address,
});
// Update fields
self.fields = Arc::new(NetworkStateFields {
@ -477,11 +595,14 @@ impl NetworkState {
opt_address: Option<Ipv4Addr>,
) -> GlobalStateManagerResult<Ifv4Addr> {
let net = self.allocate_subnet_v4(gsm_inner, owner_tag, opt_address, 32)?;
let scope = self
.fields
.address_pool
.find_scope_v4(net)
.expect("must succeed");
let ip = net.addr();
let ipv4 = self.fields.ipv4.as_ref().unwrap();
let netmask = ipv4.params.allocation.netmask();
let broadcast = ipv4.params.allocation.broadcast();
let netmask = scope.netmask();
let broadcast = scope.broadcast();
let ifaddr = Ifv4Addr {
ip,
@ -517,11 +638,8 @@ impl NetworkState {
net
} else {
// Get a random address if available
let Some(allocation) = address_pool.allocate_random_v4(
gsm_inner.srng(),
prefix,
owner_tag,
)?
let Some(allocation) =
address_pool.allocate_random_v4(gsm_inner.srng(), prefix, owner_tag)?
else {
return Err(GlobalStateManagerError::NoAllocation);
};
@ -585,11 +703,15 @@ impl NetworkState {
opt_address: Option<Ipv6Addr>,
) -> GlobalStateManagerResult<Ifv6Addr> {
let net = self.allocate_subnet_v6(gsm_inner, owner_tag, opt_address, 128)?;
let ip = net.addr();
let scope = self
.fields
.address_pool
.find_scope_v6(net)
.expect("must succeed");
let ipv6 = self.fields.ipv6.as_ref().unwrap();
let netmask = ipv6.params.allocation.netmask();
let broadcast = ipv6.params.allocation.broadcast();
let ip = net.addr();
let netmask = scope.netmask();
let broadcast = scope.broadcast();
let ifaddr = Ifv6Addr {
ip,
@ -625,11 +747,8 @@ impl NetworkState {
net
} else {
// Get a random address if available
let Some(allocation) = address_pool.allocate_random_v6(
gsm_inner.srng(),
prefix,
owner_tag,
)?
let Some(allocation) =
address_pool.allocate_random_v6(gsm_inner.srng(), prefix, owner_tag)?
else {
return Err(GlobalStateManagerError::NoAllocation);
};

View File

@ -65,20 +65,6 @@ impl<S: State> StateRegistry<S> {
}
}
pub fn get_or_create_by_name<F: FnOnce(StateId<S>, String) -> S>(
&mut self,
name: String,
create: F,
) -> StateId<S> {
if let Some(id) = self.get_state_id_by_name(&name) {
return id;
}
let id = self.allocate_id();
let state = create(id, name);
self.attach_state(state).expect("should always attach");
id
}
pub fn allocate_id(&mut self) -> StateId<S> {
// Allocate new internal id
let state_id = self.free_state_ids.pop_back().unwrap_or_else(|| {

View File

@ -14,6 +14,9 @@ default_network: "$internet"
# this is '$lan')
default_model: "$lan"
# The name of the default allocation pool that subnets are allocated from
default_pool: "$internet"
#################################################################
# Networks
#
@ -38,54 +41,56 @@ networks:
# will be used (on the 'public internet').
allocations:
# Predefined networks
$internet: {}
# Predefined allocations
$internet:
scope4: ["0.0.0.0/0"]
scope6: ["::/0"]
$private:
subnet4: ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
subnet6: ["fc00::/7"]
scope4: ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
scope6: ["fc00::/7"]
$cgnat:
subnet4: ["100.64.0.0/10"]
scope4: ["100.64.0.0/10"]
$linklocal:
subnet4: ["169.254.0.0/16"]
subnet6: ["fe80::/10"]
scope4: ["169.254.0.0/16"]
scope6: ["fe80::/10"]
$localhost:
subnet4: ["127.0.0.0/8"]
subnet6: ["::1/128"]
scope4: ["127.0.0.0/8"]
scope6: ["::1/128"]
$ietf:
subnet4: ["192.0.0.0/24"]
scope4: ["192.0.0.0/24"]
$cellnat:
subnet4: ["192.0.0.0/29"]
scope4: ["192.0.0.0/29"]
$documentation:
subnet4: ["192.0.2.0/24", "198.51.100.0/24", "203.0.113.0/24"]
subnet6: ["2001:db8::/32", "3fff::/20"]
scope4: ["192.0.2.0/24", "198.51.100.0/24", "203.0.113.0/24"]
scope6: ["2001:db8::/32", "3fff::/20"]
$benchmark:
subnet4: ["198.18.0.0/15"]
scope4: ["198.18.0.0/15"]
$mulitcast:
subnet4: ["224.0.0.0/4"]
scope4: ["224.0.0.0/4"]
$mulitcasttest:
subnet4: ["233.252.0.0/24"]
subnet6: ["ff00::/8"]
scope4: ["233.252.0.0/24"]
scope6: ["ff00::/8"]
$unspecified:
subnet4: ["0.0.0.0/8"]
subnet6: ["::/128"]
scope4: ["0.0.0.0/8"]
scope6: ["::/128"]
$reserved:
subnet4: ["192.88.99.0/24", "240.0.0.0/4"]
scope4: ["192.88.99.0/24", "240.0.0.0/4"]
$broadcast:
subnet4: ["255.255.255.255/32"]
scope4: ["255.255.255.255/32"]
$mapped:
subnet6: ["::ffff:0:0/96", "::ffff:0:0:0/96"]
scope6: ["::ffff:0:0/96", "::ffff:0:0:0/96"]
$translation:
subnet6: ["64:ff9b::/96", "64:ff9b:1::/48"]
scope6: ["64:ff9b::/96", "64:ff9b:1::/48"]
$discard:
subnet6: ["100::/64"]
scope6: ["100::/64"]
$teredo:
subnet6: ["2001::/32"]
scope6: ["2001::/32"]
$orchidv2:
subnet6: ["2001:20::/28"]
scope6: ["2001:20::/28"]
$6to4:
subnet6: ["2002::/16"]
scope6: ["2002::/16"]
$srv6:
subnet6: ["5f00::/16"]
scope6: ["5f00::/16"]
#################################################################
# Models

View File

@ -156,6 +156,34 @@ impl<T: fmt::Debug + Clone> WeightedList<T> {
}
}
pub fn try_map<F, S, E>(&self, mut filter: F) -> Result<WeightedList<S>, E>
where
F: FnMut(&T) -> Result<S, E>,
S: fmt::Debug + Clone,
{
match self {
WeightedList::Single(v) => {
let item = filter(v)?;
Ok(WeightedList::Single(item))
}
WeightedList::List(vec) => {
let mut out = Vec::<Weighted<S>>::with_capacity(vec.len());
for v in vec {
let item = filter(v.item())?;
out.push(match v {
Weighted::Weighted { item: _, weight } => Weighted::Weighted {
item,
weight: *weight,
},
Weighted::Unweighted(_) => Weighted::Unweighted(item),
});
}
Ok(WeightedList::List(out))
}
}
}
pub fn iter(&self) -> WeightedListIter<'_, T> {
WeightedListIter {
values: self,