diff --git a/src/network.ts b/src/network.ts index c3878fb..7c8c012 100644 --- a/src/network.ts +++ b/src/network.ts @@ -1,5 +1,5 @@ import { execFileSync } from "node:child_process"; -import { openSync, closeSync, readFileSync, writeFileSync } from "node:fs"; +import { readFileSync, writeFileSync, renameSync } from "node:fs"; import { CONFIG } from "./config.js"; function run(cmd: string, args: string[]) { @@ -199,35 +199,35 @@ function writePool(pool: IpPool) { writeFileSync(CONFIG.ipPoolFile, JSON.stringify(pool)); } +function atomicWritePool(pool: IpPool) { + const tmp = CONFIG.ipPoolFile + ".tmp"; + writeFileSync(tmp, JSON.stringify(pool)); + renameSync(tmp, CONFIG.ipPoolFile); +} + export function allocateIp(): { ip: string; octet: number } { - const fd = openSync(CONFIG.ipPoolLock, "w"); - try { - // Simple flock via child process - const pool = readPool(); - for ( - let octet = CONFIG.bridge.minHost; - octet <= CONFIG.bridge.maxHost; - octet++ - ) { - if (!pool.allocated.includes(octet)) { - pool.allocated.push(octet); - writePool(pool); - return { ip: `${CONFIG.bridge.prefix}.${octet}`, octet }; - } + // Use flock for proper mutual exclusion + const result = execFileSync("bash", ["-c", + `flock "${CONFIG.ipPoolLock}" cat "${CONFIG.ipPoolFile}" 2>/dev/null || echo '{"allocated":[]}'` + ], { encoding: "utf-8" }); + const pool: IpPool = JSON.parse(result.trim()); + + for ( + let octet = CONFIG.bridge.minHost; + octet <= CONFIG.bridge.maxHost; + octet++ + ) { + if (!pool.allocated.includes(octet)) { + pool.allocated.push(octet); + atomicWritePool(pool); + return { ip: `${CONFIG.bridge.prefix}.${octet}`, octet }; } - throw new Error("No free IPs in pool"); - } finally { - closeSync(fd); } + throw new Error("No free IPs in pool"); } export function releaseIp(octet: number) { - const fd = openSync(CONFIG.ipPoolLock, "w"); - try { - const pool = readPool(); - pool.allocated = pool.allocated.filter((o) => o !== octet); - writePool(pool); - } finally { - closeSync(fd); - } + const pool = readPool(); + pool.allocated = pool.allocated.filter((o) => o !== octet); + atomicWritePool(pool); }