// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Unlicense use anyhow::Result; use serde::Deserialize; use std::{ net::{Ipv4Addr, Ipv6Addr}, ops::Range, }; #[derive(Deserialize, Debug)] pub struct IPv4Range { pub first_ip: Ipv4Addr, pub last_ip: Ipv4Addr, pub netmask: String, pub gateway: Ipv4Addr, } #[derive(Deserialize, Debug)] pub struct IPv6Range { pub first_ip: Ipv6Addr, pub last_ip: Ipv6Addr, pub netmask: String, pub gateway: Ipv6Addr, } #[derive(Deserialize, Debug)] pub struct Interface { pub driver: InterfaceType, pub device: String, pub ipv4_ranges: Vec, pub ipv6_ranges: Vec, } #[derive(Deserialize, Debug)] pub enum InterfaceType { MACVTAP, IPVTAP, Bridge, } #[derive(Deserialize, Debug)] pub struct Offer { // price per unit per minute pub price: u64, pub max_vcpu_reservation: usize, pub max_mem_reservation_mib: usize, pub storage_path: String, pub max_storage_mib: usize, } #[derive(Deserialize, Debug)] pub struct Config { pub owner_wallet: String, pub network: String, pub network_interfaces: Vec, pub offers: Vec, #[serde(with = "range_format")] pub public_port_range: Range, pub max_ports_per_vm: u16, } mod range_format { use serde::{Deserialize, Deserializer, Serialize}; use std::ops::Range; pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { let range_repr = RangeRepr::deserialize(deserializer)?; Ok(range_repr.start..range_repr.end) } #[derive(Serialize, Deserialize)] struct RangeRepr { start: u16, end: u16, } } impl Config { pub fn load_from_disk(path: &str) -> Result { let content = std::fs::read_to_string(path)?; let config: Config = serde_yaml::from_str(&content)?; let offers_path_set: std::collections::HashSet = config.offers.iter().map(|offer| offer.storage_path.clone()).collect(); if offers_path_set.len() != config.offers.len() { return Err(anyhow::anyhow!( "Each offer must have a unique folder for saving VM disk files." )); } for nic in &config.network_interfaces { for range in &nic.ipv4_ranges { let ipv4_netmask = range.netmask.parse::()?; if ipv4_netmask > 32 { return Err(anyhow::anyhow!( "IPv4 netmask must be in short format: a number from 1 to 32" )); } if range.first_ip.to_bits() > range.last_ip.to_bits() { return Err(anyhow::anyhow!( "For range {range:?} first ip is bigger than last ip." )); } let expected_netmask = std::cmp::min( calc_ipv4_netmask(range.first_ip, range.gateway), calc_ipv4_netmask(range.last_ip, range.gateway), ); if expected_netmask < ipv4_netmask as u32 { return Err(anyhow::anyhow!( "Your netmask is too small to include the IPs and also the gateway: {range:?}" )); } } for range in &nic.ipv6_ranges { let ipv6_netmask = range.netmask.parse::()?; if ipv6_netmask > 128 { return Err(anyhow::anyhow!( "IPv6 netmask must be in short format: a number from 1 to 128" )); } if range.first_ip.to_bits() > range.last_ip.to_bits() { return Err(anyhow::anyhow!( "For range {range:?} first ip is bigger than last ip." )); } let expected_netmask = std::cmp::min( calc_ipv6_netmask(range.first_ip, range.gateway), calc_ipv6_netmask(range.last_ip, range.gateway), ); if expected_netmask < ipv6_netmask as u128 { return Err(anyhow::anyhow!( "Your netmask is too small to include the IPs and also the gateway: {range:?}" )); } } } Ok(config) } } fn calc_ipv4_netmask(ip: Ipv4Addr, gateway: Ipv4Addr) -> u32 { // Convert the IPs to u32 for easier bit manipulation let ip_u32 = u32::from(ip); let gateway_u32 = u32::from(gateway); // Find the smallest common prefix let mut prefix_len = 0; for i in 1..=32 { if (ip_u32 >> (32 - i)) == (gateway_u32 >> (32 - i)) { prefix_len = i; } else { break; } } // Return the mask as a string prefix_len } fn calc_ipv6_netmask(ip: Ipv6Addr, gateway: Ipv6Addr) -> u128 { // Convert the IPs to u128 for easier bit manipulation let ip_u128 = u128::from(ip); let gateway_u128 = u128::from(gateway); // Find the smallest common prefix let mut prefix_len = 0; for i in 1..=128 { if (ip_u128 >> (128 - i)) == (gateway_u128 >> (128 - i)) { prefix_len = i; } else { break; } } // Return the mask as a string prefix_len }