From 93da42d4512838c1a49a57fdf6a773fe823ba935 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Wed, 22 May 2024 19:55:41 +0200 Subject: [PATCH] use smaller Docker networks --- packages/dns-test/src/container/network.rs | 111 ++++++++++++++++++++- 1 file changed, 109 insertions(+), 2 deletions(-) diff --git a/packages/dns-test/src/container/network.rs b/packages/dns-test/src/container/network.rs index ebe9c6d..3315950 100644 --- a/packages/dns-test/src/container/network.rs +++ b/packages/dns-test/src/container/network.rs @@ -1,8 +1,9 @@ use std::{ + net::Ipv4Addr, process::{self, Command, Stdio}, sync::{ atomic::{self, AtomicUsize}, - Arc, + Arc, Mutex, }, }; @@ -50,17 +51,23 @@ impl Drop for NetworkInner { impl NetworkInner { pub fn new(pid: u32, network_name: &str) -> Result { + static LOCK: Mutex<()> = Mutex::new(()); + + let guard = LOCK.lock()?; let count = network_count(); let network_name = format!("{network_name}-{pid}-{count}"); + let in_use = in_use()?; + let subnet = format!("{}/24", choose_network(&in_use)); let mut command = Command::new("docker"); command .args(["network", "create"]) - .args(["--internal", "--attachable"]) + .args(["--internal", "--attachable", "--subnet", &subnet]) .arg(&network_name); // create network let output = command.output()?; + drop(guard); let stdout = String::from_utf8_lossy(&output.stdout); let stderr = String::from_utf8_lossy(&output.stderr); @@ -78,6 +85,96 @@ impl NetworkInner { } } +fn choose_network(in_use: &[(Ipv4Addr, u32)]) -> Ipv4Addr { + for c in 0..=255 { + let candidate = Ipv4Addr::new(192, 168, c, 0); + + if !overlaps_with_any(candidate, in_use) { + return candidate; + } + } + + for b in 16..=31 { + for c in 0..=255 { + let candidate = Ipv4Addr::new(172, b, c, 0); + + if !overlaps_with_any(candidate, in_use) { + return candidate; + } + } + } + + for b in 0..=255 { + for c in 0..=255 { + let candidate = Ipv4Addr::new(10, b, c, 0); + + if !overlaps_with_any(candidate, in_use) { + return candidate; + } + } + } + + // should wait until a docker network is released + unimplemented!() +} + +fn overlaps_with(lhs: Ipv4Addr, rhs: Ipv4Addr, rhs_netmask_bits: u32) -> bool { + // LHS has netmask /24 + if rhs_netmask_bits == 24 { + let [a1, b1, c1, _] = lhs.octets(); + let [a2, b2, c2, _] = rhs.octets(); + + (a1, b1, c1) == (a2, b2, c2) + } else if rhs_netmask_bits == 16 { + let [a1, b1, _, _] = lhs.octets(); + let [a2, b2, _, _] = rhs.octets(); + + (a1, b1) == (a2, b2) + } else if rhs_netmask_bits == 8 { + let [a1, _, _, _] = lhs.octets(); + let [a2, _, _, _] = rhs.octets(); + + a1 == a2 + } else { + unreachable!() + } +} + +fn overlaps_with_any(lhs: Ipv4Addr, in_use: &[(Ipv4Addr, u32)]) -> bool { + for (rhs, rhs_netmask_bits) in in_use { + if overlaps_with(lhs, *rhs, *rhs_netmask_bits) { + return true; + } + } + + false +} + +fn in_use() -> Result> { + let ifconfig = String::from_utf8(Command::new("ifconfig").output()?.stdout)?; + let mut in_use = vec![]; + for line in ifconfig.lines() { + if let Some((_before, after)) = line.split_once("inet ") { + let mut parts = after.split_whitespace(); + if let (Some(ip_addr), Some(_), Some(netmask)) = + (parts.next(), parts.next(), parts.next()) + { + if let Ok(ip_addr) = ip_addr.parse() { + let netmask_bits = netmask + .split('.') + .filter_map(|part| part.parse::().ok()) + .map(|mask| mask.count_ones()) + .sum::(); + + in_use.push((ip_addr, netmask_bits)) + } + } + } + } + + Ok(in_use) +} + /// Collects all important configs. pub struct NetworkConfig { /// The CIDR subnet mask, e.g. "172.21.0.0/16" @@ -153,4 +250,14 @@ mod tests { Ok(()) } + + #[test] + fn stress() { + let mut networks = vec![]; + for index in 0..256 { + let network = Network::new().unwrap_or_else(|e| panic!("{}: {e}", index)); + eprintln!("{}", network.0.config.subnet); + networks.push(network); + } + } }