From da693e01c7237477530237aba5930afcc3dedbc6 Mon Sep 17 00:00:00 2001 From: iotux <46082385+iotux@users.noreply.github.com> Date: Sun, 11 Jan 2026 18:28:07 +0700 Subject: [PATCH] fix: idn ping errors (#6662) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- server/util-server.js | 10 +++++ test/backend-test/test-util-server.js | 57 +++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 test/backend-test/test-util-server.js diff --git a/server/util-server.js b/server/util-server.js index ae792ad85..d7316dee3 100644 --- a/server/util-server.js +++ b/server/util-server.js @@ -157,6 +157,16 @@ exports.pingAsync = function ( deadline = PING_GLOBAL_TIMEOUT_DEFAULT, timeout = PING_PER_REQUEST_TIMEOUT_DEFAULT ) { + try { + const url = new URL(`http://${destAddr}`); + destAddr = url.hostname; + if (destAddr.startsWith("[") && destAddr.endsWith("]")) { + destAddr = destAddr.slice(1, -1); + } + } catch (e) { + // ignore + } + return new Promise((resolve, reject) => { ping.promise .probe(destAddr, { diff --git a/test/backend-test/test-util-server.js b/test/backend-test/test-util-server.js new file mode 100644 index 000000000..29628d845 --- /dev/null +++ b/test/backend-test/test-util-server.js @@ -0,0 +1,57 @@ +const { describe, test } = require("node:test"); +const assert = require("node:assert"); +const { pingAsync } = require("../../server/util-server"); + +describe("Server Utilities: pingAsync", () => { + test("should convert IDN domains to Punycode before pinging", async () => { + const idnDomain = "münchen.de"; + const punycodeDomain = "xn--mnchen-3ya.de"; + + await assert.rejects(pingAsync(idnDomain, false, 1, "", true, 56, 1, 1), (err) => { + if (err.message.includes("Parameter string not correctly encoded")) { + assert.fail("Ping failed with encoding error: IDN was not converted"); + } + assert.ok( + err.message.includes(punycodeDomain), + `Error message should contain the Punycode domain "${punycodeDomain}". Got: ${err.message}` + ); + return true; + }); + }); + + test("should strip brackets from IPv6 addresses before pinging", async () => { + const ipv6WithBrackets = "[2606:4700:4700::1111]"; + const ipv6Raw = "2606:4700:4700::1111"; + + await assert.rejects(pingAsync(ipv6WithBrackets, true, 1, "", true, 56, 1, 1), (err) => { + assert.strictEqual( + err.message.includes(ipv6WithBrackets), + false, + "Error message should not contain brackets" + ); + // Allow either the IP in the message (local) OR "Network is unreachable" + const containsIP = err.message.includes(ipv6Raw); + const isUnreachable = + err.message.includes("Network is unreachable") || err.message.includes("Network unreachable"); + // macOS error when IPv6 stack is missing + const isMacOSError = err.message.includes("nodename nor servname provided"); + assert.ok( + containsIP || isUnreachable || isMacOSError, + `Ping failed correctly, but error message format was unexpected.\nGot: "${err.message}"\nExpected to contain IP "${ipv6Raw}" OR be a standard network error.` + ); + return true; + }); + }); + + test("should handle standard ASCII domains correctly", async () => { + const domain = "invalid-domain.test"; + await assert.rejects(pingAsync(domain, false, 1, "", true, 56, 1, 1), (err) => { + assert.strictEqual(err.message.includes("Parameter string not correctly encoded"), false); + assert.ok( + err.message.includes(domain), + `Error message should contain the domain "${domain}". Got: ${err.message}` + ); + return true; + }); + }); +});