feat(#2363): DHCP pool for bridge service containers
Add a DHCP pool to the gateway's dnsmasq so bridge-attached service containers (hive-ci and future equivalents) get their addresses from a proper DHCP server instead of a brittle static derivation. gateway (hive-gateway.nix): - Add IPv4 arithmetic helpers (ipToInt, intToIp, pow2) to compute the DHCP pool range at nix eval time from bridgeIp + bridgePrefixLength. - Reserve the last dhcpPoolSize (14) usable host addresses as the DHCP pool (e.g. .241-.254 on a /24 with 10.42.0.0 network). - Add dhcp-range and dhcp-leasefile to the dnsmasq settings block. The pool is active whenever services.hyperhive.network.enable is true. hive-ci (hive-ci.nix): - Remove the ciBridgeIp / ciBridgeOctets static derivation and the brittle top-of-/24 comment block. - Switch networking.interfaces.eth0 to useDHCP = true so hive-ci gets its address from the gateway DHCP pool. lifecycle (mod.rs, tests.rs): - Add DHCP_POOL_SIZE = 14 constant (must stay in sync with dhcpPoolSize in hive-gateway.nix). - Remap agents whose FNV-1a hash falls in the DHCP pool into the agent-only window [2, dhcp_start - 1]. Only the rare agent whose name hashes into the pool is affected; all others keep their IPs. - Update and extend tests: agent range is now .2-.240 on /24; add agent_network_ip_never_in_dhcp_pool covering 18 agent names.
This commit is contained in:
parent
16f69ca890
commit
a3796890f5
4 changed files with 114 additions and 36 deletions
|
|
@ -90,6 +90,12 @@ pub fn agent_web_port(name: &str) -> u16 {
|
|||
WEB_PORT_BASE + u16::try_from(fnv1a(name) % u32::from(WEB_PORT_RANGE)).unwrap_or(0)
|
||||
}
|
||||
|
||||
/// Number of IP addresses at the top of each subnet reserved for the DHCP
|
||||
/// pool (bridge-attached service containers such as hive-ci). Agents are
|
||||
/// excluded from this range by the remap in `agent_network_ip`. Must stay
|
||||
/// in sync with `dhcpPoolSize` in `nix/modules/hive-gateway.nix`.
|
||||
const DHCP_POOL_SIZE: u32 = 14;
|
||||
|
||||
/// Deterministic IPv4 address for an agent inside an isolated subnet.
|
||||
///
|
||||
/// Parses `subnet_cidr` as `<network_ip>/<prefix_len>` (e.g.
|
||||
|
|
@ -97,11 +103,18 @@ pub fn agent_web_port(name: &str) -> u16 {
|
|||
///
|
||||
/// ```text
|
||||
/// host_count = 2^(32 - prefix_len)
|
||||
/// usable = host_count - 3 // skip .0 (network), .1 (gateway), .255 (broadcast)
|
||||
/// usable = host_count - 3 // skip .0 (network), .1 (gateway), last (broadcast)
|
||||
/// offset = FNV-1a(name) % usable + 2 // .2 is the first agent slot
|
||||
/// agent_ip = network_base_u32 + offset
|
||||
/// ```
|
||||
///
|
||||
/// The last `DHCP_POOL_SIZE` usable host addresses are reserved for the
|
||||
/// bridge DHCP pool (service containers). When the primary hash falls in
|
||||
/// that range the offset is remapped into the agent-only window `[2,
|
||||
/// dhcp_start - 1]` so no agent is ever assigned a DHCP-pool address.
|
||||
/// Only the rare agent whose name hashes into the pool is affected; all
|
||||
/// other agents keep their original IPs.
|
||||
///
|
||||
/// Returns `None` when `subnet_cidr` can't be parsed (invalid format,
|
||||
/// prefix out of range, etc.) so callers can fall back gracefully.
|
||||
/// Collisions are possible (birthday paradox) and the operator resolves
|
||||
|
|
@ -138,7 +151,22 @@ pub fn agent_network_ip(name: &str, subnet_cidr: &str) -> Option<String> {
|
|||
if usable == 0 {
|
||||
return None;
|
||||
}
|
||||
let offset = fnv1a(name) % usable + 2; // +2: skip .0 and .1
|
||||
let mut offset = fnv1a(name) % usable + 2; // +2: skip .0 and .1
|
||||
|
||||
// Remap agents whose primary hash falls in the DHCP pool (the last
|
||||
// DHCP_POOL_SIZE usable addresses). Only has an effect when the
|
||||
// subnet is large enough to hold both agent slots AND a pool.
|
||||
if usable > DHCP_POOL_SIZE {
|
||||
// First offset that belongs to the DHCP pool.
|
||||
let dhcp_start = usable + 2 - DHCP_POOL_SIZE;
|
||||
if offset >= dhcp_start {
|
||||
// Secondary hash into the agent-only window [2, dhcp_start - 1].
|
||||
// Result is guaranteed < dhcp_start (no overlap with pool).
|
||||
let agent_only = dhcp_start - 2;
|
||||
offset = fnv1a(name) % agent_only + 2;
|
||||
}
|
||||
}
|
||||
|
||||
let ip_u32 = network_base + offset;
|
||||
let [a, b, c, d] = ip_u32.to_be_bytes();
|
||||
Some(format!("{a}.{b}.{c}.{d}"))
|
||||
|
|
|
|||
Loading…
Reference in a new issue