mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-24 08:21:39 +00:00
fix(gateway): guard interface discovery failures
Closes #44180. Refs #47590. Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
@@ -254,7 +254,9 @@ Docs: https://docs.openclaw.ai
|
||||
- Discord/ACP: forward worker abort signals into ACP turns so timed-out Discord jobs cancel the running turn instead of silently leaving the bound ACP session working in the background.
|
||||
- Gateway/openresponses: preserve assistant commentary and session continuity across hosted-tool `/v1/responses` turns, and emit streamed tool-call payloads before finalization so client tool loops stay resumable. (#52171) Thanks @CharZhou.
|
||||
- Android/Talk: serialize `TalkModeManager` player teardown so rapid interrupt/restart cycles stop double-releasing or overlapping TTS playback. (#52310) Thanks @Kaneki-x.
|
||||
<<<<<<< HEAD
|
||||
- WhatsApp/reconnect: preserve the last inbound timestamp across reconnect attempts so the watchdog can still recycle linked-but-dead listeners after a restart instead of leaving them stuck connected forever.
|
||||
- Gateway/network discovery: guard LAN, tailnet, and pairing interface enumeration so WSL2 and restricted hosts degrade to missing-address fallbacks instead of crashing on `uv_interface_addresses` errors. (#44180, #47590)
|
||||
|
||||
### Breaking
|
||||
|
||||
|
||||
@@ -360,6 +360,13 @@ describe("pickPrimaryLanIPv4", () => {
|
||||
vi.restoreAllMocks();
|
||||
}
|
||||
});
|
||||
|
||||
it("returns undefined when interface discovery throws", () => {
|
||||
vi.spyOn(os, "networkInterfaces").mockImplementation(() => {
|
||||
throw new Error("uv_interface_addresses failed");
|
||||
});
|
||||
expect(pickPrimaryLanIPv4()).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("isPrivateOrLoopbackAddress", () => {
|
||||
|
||||
@@ -15,7 +15,12 @@ import {
|
||||
* Prefers common interface names (en0, eth0) then falls back to any external IPv4.
|
||||
*/
|
||||
export function pickPrimaryLanIPv4(): string | undefined {
|
||||
const nets = os.networkInterfaces();
|
||||
let nets: ReturnType<typeof os.networkInterfaces>;
|
||||
try {
|
||||
nets = os.networkInterfaces();
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
const preferredNames = ["en0", "eth0"];
|
||||
for (const name of preferredNames) {
|
||||
const list = nets[name];
|
||||
|
||||
@@ -240,5 +240,13 @@ describe("infra runtime", () => {
|
||||
expect(out.ipv4).toEqual(["100.123.224.76"]);
|
||||
expect(out.ipv6).toEqual(["fd7a:115c:a1e0::8801:e04c"]);
|
||||
});
|
||||
|
||||
it("returns empty address lists when interface discovery throws", () => {
|
||||
vi.spyOn(os, "networkInterfaces").mockImplementation(() => {
|
||||
throw new Error("uv_interface_addresses failed");
|
||||
});
|
||||
|
||||
expect(listTailnetAddresses()).toEqual({ ipv4: [], ipv6: [] });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -25,7 +25,12 @@ export function listTailnetAddresses(): TailnetAddresses {
|
||||
const ipv4: string[] = [];
|
||||
const ipv6: string[] = [];
|
||||
|
||||
const ifaces = os.networkInterfaces();
|
||||
let ifaces: ReturnType<typeof os.networkInterfaces>;
|
||||
try {
|
||||
ifaces = os.networkInterfaces();
|
||||
} catch {
|
||||
return { ipv4, ipv6 };
|
||||
}
|
||||
for (const entries of Object.values(ifaces)) {
|
||||
if (!entries) {
|
||||
continue;
|
||||
|
||||
@@ -368,6 +368,27 @@ describe("pairing setup code", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("returns a bind-specific error when interface discovery throws", async () => {
|
||||
const resolved = await resolvePairingSetupFromConfig(
|
||||
{
|
||||
gateway: {
|
||||
bind: "lan",
|
||||
auth: { mode: "token", token: "tok" },
|
||||
},
|
||||
},
|
||||
{
|
||||
networkInterfaces: () => {
|
||||
throw new Error("uv_interface_addresses failed");
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(resolved).toEqual({
|
||||
ok: false,
|
||||
error: "gateway.bind=lan set, but no private LAN IP was found.",
|
||||
});
|
||||
});
|
||||
|
||||
it("prefers gateway.remote.url over tailscale when requested", async () => {
|
||||
const runCommandWithTimeout = createTailnetDnsRunner();
|
||||
|
||||
|
||||
@@ -118,7 +118,12 @@ function pickIPv4Matching(
|
||||
networkInterfaces: () => ReturnType<typeof os.networkInterfaces>,
|
||||
matches: (address: string) => boolean,
|
||||
): string | null {
|
||||
const nets = networkInterfaces();
|
||||
let nets: ReturnType<typeof os.networkInterfaces>;
|
||||
try {
|
||||
nets = networkInterfaces();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
for (const entries of Object.values(nets)) {
|
||||
if (!entries) {
|
||||
continue;
|
||||
|
||||
Reference in New Issue
Block a user