correctly assigning and storing IPs and interfaces

This commit is contained in:
ghe0 2024-12-12 02:08:53 +02:00
parent 64a84c48be
commit d4c5cc2634
Signed by: ghe0
GPG Key ID: 451028EE56A0FBB4
8 changed files with 110 additions and 80 deletions

@ -2,7 +2,7 @@
pub(crate) const DEFAULT_OVMF: &str = "/usr/share/edk2/ovmf/OVMF.amdsev.fd";
pub(crate) const VM_BOOT_DIR: &str = "/var/lib/detee/boot/";
pub(crate) const VM_DISK_DIR: &str = "/var/lib/detee/boot/";
pub(crate) const VM_DISK_DIR: &str = "/var/lib/detee/vms/";
pub(crate) const VM_CONFIG_DIR: &str = "/etc/detee/daemon/vms/";
pub(crate) const DAEMON_CONFIG_PATH: &str = "/etc/detee/daemon/config.json";
pub(crate) const START_VM_SCRIPT: &str = "/usr/local/bin/detee/start_qemu_vm.sh";

@ -7,8 +7,10 @@ use crate::config::Config;
use crate::state::NewVMRequest;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// let config = Config::from_file("test_data/config1.yaml")?;
let mut res = state::Resources::new();
let config = Config::from_file("test_data/config2.yaml")?;
// println!("{:#?}", config);
// let config = Config::from_file("test_data/config2.yaml")?;
// println!("{:#?}", config);
// let config = Config::from_file("test_data/config3.yaml")?;
@ -17,13 +19,21 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// println!("{:#?}", config);
// let config = Config::from_file("test_data/config5.yaml")?;
// println!("{:#?}", config);
let new_vm_req = NewVMRequest::from_file("test_data/new_vm_req1.yaml")?;
println!("{:#?}", new_vm_req);
let new_vm_req = NewVMRequest::from_file("test_data/new_vm_req2.yaml")?;
println!("{:#?}", new_vm_req);
let new_vm_req = NewVMRequest::from_file("test_data/new_vm_req3.yaml")?;
println!("{:#?}", new_vm_req);
let new_vm_req = NewVMRequest::from_file("test_data/new_vm_req4.yaml")?;
println!("{:#?}", new_vm_req);
// println!("{:#?}", new_vm_req);
// let new_vm_req = NewVMRequest::from_file("test_data/new_vm_req2.yaml")?;
// println!("{:#?}", new_vm_req);
// let new_vm_req = NewVMRequest::from_file("test_data/new_vm_req3.yaml")?;
// println!("{:#?}", new_vm_req);
// let new_vm_req = NewVMRequest::from_file("test_data/new_vm_req4.yaml")?;
// println!("{:#?}", new_vm_req);
let vm = state::VM::new(new_vm_req, &config, &mut res);
let new_vm_req = NewVMRequest::from_file("test_data/new_vm_req4.yaml")?;
let vm = state::VM::new(new_vm_req, &config, &mut res);
let new_vm_req = NewVMRequest::from_file("test_data/new_vm_req4.yaml")?;
let vm = state::VM::new(new_vm_req, &config, &mut res);
println!("vm: {:#?}", vm);
println!("res: {:#?}", res);
Ok(())
}

@ -3,6 +3,7 @@ use crate::config::Config;
use crate::constants::*;
use anyhow::anyhow;
use anyhow::Result;
use serde::Deserialize;
use sha2::{Digest, Sha256};
use std::collections::HashSet;
use std::fs;
@ -12,8 +13,8 @@ use std::io::Read;
use std::io::Write;
use std::path::Path;
use std::process::Command;
use serde::Deserialize;
#[derive(Debug)]
pub struct Resources {
// QEMU does not support MHz limiation
reserved_vcpus: usize,
@ -27,7 +28,18 @@ pub struct Resources {
}
impl Resources {
fn reserve_ports(&mut self, extra_ports: usize, config: &Config) -> Vec<u16> {
pub fn new() -> Self {
Resources {
reserved_vcpus: 0,
reserved_memory: 0,
reserved_ports: HashSet::new(),
storage_pools: Vec::new(),
reserved_ips: HashSet::new(),
reserved_if_names: HashSet::new(),
boot_files: HashSet::new(),
}
}
fn get_free_ports(&mut self, extra_ports: usize, config: &Config) -> Vec<u16> {
use rand::Rng;
let total_ports = extra_ports + 1;
if config.public_port_range.len() < self.reserved_ports.len() + total_ports as usize {
@ -40,7 +52,7 @@ impl Resources {
for _ in 0..total_ports {
for _ in 0..5 {
let port = rand::thread_rng().gen_range(config.public_port_range.clone());
if self.reserved_ports.insert(port) {
if self.reserved_ports.get(&port).is_none() {
published_ports.push(port);
}
break;
@ -50,7 +62,7 @@ impl Resources {
published_ports
}
fn reserve_vm_if(&mut self) -> String {
fn get_free_vm_name(&mut self) -> String {
use rand::{distributions::Alphanumeric, Rng};
loop {
let mut interface_name: String = rand::thread_rng()
@ -65,20 +77,21 @@ impl Resources {
}
}
fn reserve_public_ipv4(&mut self, config: &Config) -> Option<VMNIC> {
fn get_free_ipv4(&mut self, config: &Config) -> Option<VMNIC> {
for nic in config.network_interfaces.iter() {
for range in nic.ipv4.iter() {
for ip in range.subnet.iter() {
for ip in range.subnet.iter().skip(1) {
if !range.reserved_addrs.contains(&ip.address())
&& !self.reserved_ips.contains(&ip.to_string())
&& !self.reserved_ips.contains(&ip.address().to_string())
&& ip.address() != range.gateway
{
let if_config = match nic.driver {
crate::config::InterfaceType::MACVTAP => InterfaceConfig::MACVTAP {
name: self.reserve_vm_if(),
name: self.get_free_vm_name(),
device: nic.device.clone(),
},
crate::config::InterfaceType::IPVTAP => InterfaceConfig::IPVTAP {
name: self.reserve_vm_if(),
name: self.get_free_vm_name(),
device: nic.device.clone(),
},
crate::config::InterfaceType::Bridge => InterfaceConfig::Bridge {
@ -90,12 +103,12 @@ impl Resources {
.network()
.to_string()
.split('/')
.next()
.nth(1)
.unwrap()
.to_string();
ips.push(IPConfig {
address: ip.address().to_string(),
subnet: mask,
mask,
gateway: range.gateway.to_string(),
});
return Some(VMNIC { if_config, ips });
@ -107,20 +120,21 @@ impl Resources {
}
// TODO: refactor this garbage cause it's only one char different from the previous one
fn reserve_public_ipv6(&mut self, config: &Config) -> Option<VMNIC> {
fn get_free_ipv6(&mut self, config: &Config) -> Option<VMNIC> {
for nic in config.network_interfaces.iter() {
for range in nic.ipv6.iter() {
for ip in range.subnet.iter() {
for ip in range.subnet.iter().skip(1) {
if !range.reserved_addrs.contains(&ip.address())
&& !self.reserved_ips.contains(&ip.to_string())
&& !self.reserved_ips.contains(&ip.address().to_string())
&& ip.address() != range.gateway
{
let if_config = match nic.driver {
crate::config::InterfaceType::MACVTAP => InterfaceConfig::MACVTAP {
name: self.reserve_vm_if(),
name: self.get_free_vm_name(),
device: nic.device.clone(),
},
crate::config::InterfaceType::IPVTAP => InterfaceConfig::IPVTAP {
name: self.reserve_vm_if(),
name: self.get_free_vm_name(),
device: nic.device.clone(),
},
crate::config::InterfaceType::Bridge => InterfaceConfig::Bridge {
@ -132,12 +146,12 @@ impl Resources {
.network()
.to_string()
.split('/')
.next()
.nth(1)
.unwrap()
.to_string();
ips.push(IPConfig {
address: ip.address().to_string(),
subnet: mask,
mask,
gateway: range.gateway.to_string(),
});
return Some(VMNIC { if_config, ips });
@ -175,8 +189,25 @@ impl Resources {
self.boot_files.insert(sha);
Ok(())
}
fn reserve_vm_resources(&mut self, vm: &VM) {
self.reserved_vcpus += vm.vcpus;
self.reserved_memory += vm.memory;
for nic in vm.nics.iter() {
if let Some(vtap) = nic.if_config.vtap_name() {
self.reserved_if_names.insert(vtap);
}
for ip in nic.ips.iter() {
self.reserved_ips.insert(ip.address.clone());
}
}
for (host_port, _) in vm.fw_ports.iter() {
self.reserved_ports.insert(*host_port);
}
}
}
#[derive(Debug)]
pub struct StoragePool {
path: String,
current_reservation: u64,
@ -184,6 +215,7 @@ pub struct StoragePool {
// tier: StorageTier,
}
#[derive(Debug)]
pub enum InterfaceConfig {
// TODO: instead of QEMU userspace NAT, use iptables kernelspace NAT
// in case of QEMU-base NAT, device name is not needed
@ -229,13 +261,15 @@ impl InterfaceConfig {
}
}
#[derive(Debug)]
struct IPConfig {
address: String,
// requires short format (example: 24)
subnet: String,
mask: String,
gateway: String,
}
#[derive(Debug)]
pub struct VMNIC {
if_config: InterfaceConfig,
ips: Vec<IPConfig>,
@ -247,6 +281,7 @@ impl VMNIC {
}
}
#[derive(Debug)]
pub struct VM {
uuid: String,
hostname: String,
@ -289,6 +324,7 @@ impl NewVMRequest {
}
}
#[derive(Debug)]
pub enum VMCreationErrors {
NATandIPv4Conflict,
TooManyCores,
@ -336,13 +372,13 @@ impl VM {
let mut vm_nics = Vec::new();
if req.public_ipv4 {
match res.reserve_public_ipv4(config) {
match res.get_free_ipv4(config) {
Some(vmnic) => vm_nics.push(vmnic),
None => return Err(VMCreationErrors::IPv4NotAvailable),
}
}
if req.public_ipv6 {
match res.reserve_public_ipv4(config) {
match res.get_free_ipv6(config) {
Some(mut vmnic) => {
if let Some(mut existing_vmnic) = vm_nics.pop() {
if vmnic.if_config.device_name() == existing_vmnic.if_config.device_name() {
@ -357,11 +393,6 @@ impl VM {
}
}
None => {
if let Some(existing_vmnic) = vm_nics.pop() {
for ip in existing_vmnic.ips {
res.reserved_ips.remove(&ip.address);
}
}
return Err(VMCreationErrors::IPv4NotAvailable);
}
}
@ -370,13 +401,8 @@ impl VM {
let mut host_ports: Vec<u16> = Vec::new();
let mut port_pairs: Vec<(u16, u16)> = Vec::new();
if !req.public_ipv4 {
host_ports.append(res.reserve_ports(req.extra_ports.len(), &config).as_mut());
host_ports.append(res.get_free_ports(req.extra_ports.len(), &config).as_mut());
if host_ports.len() == 0 {
for nic in vm_nics {
for ip in nic.ips {
res.reserved_ips.remove(&ip.address);
}
}
return Err(VMCreationErrors::NotEnoughPorts);
}
port_pairs.push((host_ports[0], 22));
@ -385,7 +411,7 @@ impl VM {
}
}
Ok(VM {
let vm = VM {
uuid: req.uuid,
hostname: req.hostname,
admin_key: req.admin_key,
@ -396,7 +422,9 @@ impl VM {
kernel_sha: req.kernel_sha,
dtrfs_sha: req.dtrfs_sha,
fw_ports: port_pairs,
})
};
res.reserve_vm_resources(&vm);
Ok(vm)
}
pub fn deploy(&self) -> Result<()> {
@ -430,7 +458,7 @@ impl VM {
for ip in nic.ips.iter() {
ip_string += &format!(
"detee_net_eth{}={}_{}_{}",
i, ip.address, ip.subnet, ip.gateway
i, ip.address, ip.mask, ip.gateway
);
}
i += 1;

@ -1,6 +1,6 @@
max_cores_per_vm: 16
max_vcpu_reservation: 32
max_mem_reservation: 65536
max_mem_reservation: 1265536
network_interfaces:
- driver: "Bridge"
device: "br0"
@ -11,11 +11,7 @@ network_interfaces:
- "10.0.0.100"
- "10.0.0.101"
- "10.0.0.102"
ipv6:
- subnet: "fd00::/48"
gateway: "fd00::1"
reserved_addrs:
- "fd00::1000"
ipv6: []
- driver: "IPVTAP"
device: "tap1"
ipv4:
@ -24,7 +20,12 @@ network_interfaces:
reserved_addrs:
- "172.16.0.10"
- "172.16.0.11"
ipv6: []
ipv6:
- subnet: "2001:db8:abcd:1234::/64"
gateway: "2001:db8:abcd:1234::1"
reserved_addrs:
- "2001:db8:abcd:1234::dead"
- "2001:db8:abcd:1234::beef"
volumes:
- path: "/data/volume1"
max_reservation: 500

@ -1,15 +1,13 @@
uuid: "123e4567-e89b-12d3-a456-426614174000"
hostname: "test-vm-01"
admin_key: "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEArandomkeyexample"
extra_ports:
- 8080
- 8443
extra_ports: [ ]
public_ipv4: true
public_ipv6: false
disk_size: 50
vcpus: 4
memory: 8192
kernel_url: "http://example.com/kernel"
kernel_sha: "abc123def4567890ghij"
dtrfs_url: "http://example.com/dtrfs"
dtrfs_sha: "xyz9876543210mnop"
kernel_url: "http://pb1n.de/?d25eec"
kernel_sha: "be29dfef7157bfe860e94e96dcfab2318c5006e92e8d846a3ad7aa804b3b994e"
dtrfs_url: "http://pb1n.de/?e46db9"
dtrfs_sha: "62e7362c9350d60698cae6eed302562a2b41bec1d248889baad302da19c3bb47"

@ -7,8 +7,7 @@ public_ipv6: false
disk_size: 10
vcpus: 1
memory: 2048
kernel_url: "http://minimal.com/kernel"
kernel_sha: "minimalsha123"
dtrfs_url: "http://minimal.com/dtrfs"
dtrfs_sha: "dtrfssha456"
kernel_url: "http://pb1n.de/?d25eec"
kernel_sha: "be29dfef7157bfe860e94e96dcfab2318c5006e92e8d846a3ad7aa804b3b994e"
dtrfs_url: "http://pb1n.de/?e46db9"
dtrfs_sha: "62e7362c9350d60698cae6eed302562a2b41bec1d248889baad302da19c3bb47"

@ -1,19 +1,14 @@
uuid: "246e1357-e98b-76d3-f345-129874650000"
hostname: "extensive-vm"
admin_key: "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEExtendedKeyExample"
extra_ports:
- 80
- 443
- 3000
- 5000
- 6000
extra_ports: []
public_ipv4: true
public_ipv6: true
disk_size: 200
vcpus: 16
vcpus: 2
memory: 65536
kernel_url: "http://large.com/kernel"
kernel_sha: "largekernelsha"
dtrfs_url: "http://large.com/dtrfs"
dtrfs_sha: "largedtrfssha"
kernel_url: "http://pb1n.de/?d25eec"
kernel_sha: "be29dfef7157bfe860e94e96dcfab2318c5006e92e8d846a3ad7aa804b3b994e"
dtrfs_url: "http://pb1n.de/?e46db9"
dtrfs_sha: "62e7362c9350d60698cae6eed302562a2b41bec1d248889baad302da19c3bb47"

@ -7,8 +7,7 @@ public_ipv6: true
disk_size: 25
vcpus: 2
memory: 4096
kernel_url: "http://testing.com/kernel"
kernel_sha: "testkernelsha"
dtrfs_url: "http://testing.com/dtrfs"
dtrfs_sha: "testdtrfssha"
kernel_url: "http://pb1n.de/?d25eec"
kernel_sha: "be29dfef7157bfe860e94e96dcfab2318c5006e92e8d846a3ad7aa804b3b994e"
dtrfs_url: "http://pb1n.de/?e46db9"
dtrfs_sha: "62e7362c9350d60698cae6eed302562a2b41bec1d248889baad302da19c3bb47"