renew_dhcp_lease
renew_dhcp_lease
Releases the current DHCP IP address and requests a new lease. Use when the device has an incorrect IP, APIPA address (169.254.x.x), or network access was just restored.
Metadata
Name
renew_dhcp_lease
Updated
2 months ago
Source
Risk
Medium
Requires consent
false
Affected scope
networksystem
Code
/**
* mcp/skills/renewDhcpLease.ts — renew_dhcp_lease skill
*
* Releases the current DHCP IP address and requests a new lease.
* Use when the device has an incorrect IP, APIPA address (169.254.x.x),
* or network access was just restored.
*
* Platform strategy
* -----------------
* darwin `sudo ipconfig set {iface} DHCP` or ifconfig down/up cycle
* win32 PowerShell ipconfig /release && ipconfig /renew
*
* Smoke test
* npx tsx -r dotenv/config mcp/skills/renewDhcpLease.ts
*/
import * as os from "os";
import { exec } from "child_process";
import { promisify } from "util";
import { z } from "zod";
const execAsync = promisify(exec);
// -- Meta ---------------------------------------------------------------------
export const meta = {
name: "renew_dhcp_lease",
description:
"Releases the current DHCP IP address and requests a new lease. " +
"Use when the device has an incorrect IP, APIPA address (169.254.x.x), " +
"or network access was just restored.",
riskLevel: "medium",
destructive: false,
requiresConsent: false,
supportsDryRun: false,
affectedScope: ["network", "system"],
auditRequired: true,
escalationHint: {
darwin: "sudo ipconfig set en0 DHCP # substitute the active interface name",
win32: "ipconfig /release && ipconfig /renew # run from elevated Command Prompt",
},
schema: {
interface: z
.string()
.optional()
.describe("Network interface name (e.g. 'en0', 'Wi-Fi'). Omit to renew all active interfaces"),
},
} as const;
// -- Types --------------------------------------------------------------------
interface RenewResult {
interface: string;
previousIp: string | null;
newIp: string | null;
renewed: boolean;
message: string;
}
// -- PowerShell helper --------------------------------------------------------
async function runPS(script: string): Promise<string> {
const encoded = Buffer.from(script, "utf16le").toString("base64");
const { stdout } = await execAsync(
`powershell.exe -NoProfile -NonInteractive -EncodedCommand ${encoded}`,
{ maxBuffer: 10 * 1024 * 1024 },
);
return stdout.trim();
}
// -- Helpers ------------------------------------------------------------------
async function getCurrentIpDarwin(iface: string): Promise<string | null> {
try {
const { stdout } = await execAsync(
`ifconfig '${iface.replace(/'/g, "'\\'\'")}'' 2>/dev/null`,
{ timeout: 5_000 },
);
const m = stdout.match(/inet (\d+\.\d+\.\d+\.\d+)/);
return m?.[1] ?? null;
} catch {
return null;
}
}
async function getActiveInterfacesDarwin(): Promise<string[]> {
try {
const { stdout } = await execAsync("ifconfig -l 2>/dev/null", { timeout: 5_000 });
const all = stdout.trim().split(/\s+/);
const dhcp = all.filter((n) => /^en\\d+$/.test(n));
return dhcp.length > 0 ? dhcp : ["en0"];
} catch {
return ["en0"];
}
}
// -- darwin implementation ----------------------------------------------------
async function renewDarwin(iface: string | undefined): Promise<RenewResult[]> {
const interfaces = iface ? [iface] : await getActiveInterfacesDarwin();
const results: RenewResult[] = [];
for (const ifName of interfaces) {
const safeName = ifName.replace(/'/g, "'\\'\'");
const previousIp = await getCurrentIpDarwin(ifName);
try {
await execAsync(
`sudo ipconfig set '${safeName}' DHCP 2>/dev/null`,
{ timeout: 15000 },
);
await new Promise(r => setTimeout(r, 2000));
const newIp = await getCurrentIpDarwin(ifName);
results.push({
interface: ifName,
previousIp,
newIp,
renewed: true,
message: newIp
? `DHCP lease renewed on ${ifName}. New IP: ${newIp}`
: `DHCP renewal sent on ${ifName} but no IP assigned yet`,
});
} catch (err) {
const msg = (err as Error).message ?? String(err);
results.push({
interface: ifName,
previousIp,
newIp: null,
renewed: false,
message: `Failed to renew DHCP on ${ifName}: ${msg}`,
});
}
}
return results;
}
// -- win32 implementation -----------------------------------------------------
async function renewWin32(iface: string | undefined): Promise<RenewResult[]> {
const adapterFilter = iface ? `"${iface}"` : "*";
let previousIp: string | null = null;
try {
const ps = `(Get-NetIPAddress -InterfaceAlias ${adapterFilter} -AddressFamily IPv4 -ErrorAction SilentlyContinue | Select-Object -First 1).IPAddress`;
previousIp = (await runPS(ps)) || null;
} catch { /* ignore */ }
try {
const releaseCmd = iface
? `ipconfig /release "${iface}"`
: "ipconfig /release";
const renewCmd = iface
? `ipconfig /renew "${iface}"`
: "ipconfig /renew";
await execAsync(`${releaseCmd} && ${renewCmd}`, { timeout: 30000 });
let newIp: string | null = null;
try {
const ps2 = `(Get-NetIPAddress -InterfaceAlias ${adapterFilter} -AddressFamily IPv4 -ErrorAction SilentlyContinue | Select-Object -First 1).IPAddress`;
newIp = (await runPS(ps2)) || null;
} catch { /* ignore */ }
return [{
interface: iface ?? "all",
previousIp,
newIp,
renewed: true,
message: newIp
? `DHCP lease renewed. New IP: ${newIp}`
: "DHCP renewal completed but no IP assigned yet",
}];
} catch (err) {
return [{
interface: iface ?? "all",
previousIp,
newIp: null,
renewed: false,
message: `Failed to renew DHCP: ${(err as Error).message}`,
}];
}
}
// -- Exported run function ----------------------------------------------------
export async function run({
interface: iface,
}: {
interface?: string;
} = {}): Promise<{ platform: string; results: RenewResult[] }> {
const platform = os.platform();
const results = platform === "win32"
? await renewWin32(iface)
: await renewDarwin(iface);
return { platform, results };
}
// -- CLI smoke test -----------------------------------------------------------
if (false) {
run({})
.then(r => console.log(JSON.stringify(r, null, 2)))
.catch((err: Error) => { console.error(err.message); process.exit(1); });
}
``