Compare commits

...
Author SHA1 Message Date
Cursor Agentand陈大猫 287d0c0698 fix(ssh): forward resolved Local Network first hop on direct starts
When the terminal worker is disabled, startSSHSessionWrapper probed LAN
access but dropped the resolved address before startSSHSessionWithRetries.
Share attachMacLocalNetworkProbeResult with the worker path so split-DNS
FQDN first hops still get Local Network error guidance.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
2026-08-06 07:10:37 +00:00
3 changed files with 51 additions and 11 deletions

No files matched your search

@@ -298,6 +298,20 @@ function extractRemoteUnreachableAddresses(text) {
return [...new Set(found)];
}
/**
* Attach the LAN address resolved by ensureAccess onto start options so both
* the worker path and the direct main-process fallback can annotate errors.
*/
function attachMacLocalNetworkProbeResult(options, probeResult) {
const base = options && typeof options === "object" ? { ...options } : {};
const resolvedFirstHop = probeResult && typeof probeResult.hostname === "string"
? String(probeResult.hostname).trim()
: "";
if (!resolvedFirstHop) return base;
base._macLocalNetworkResolvedFirstHop = resolvedFirstHop;
return base;
}
function annotateMacLocalNetworkErrorMessage(message, options = {}) {
const platform = options.platform || process.platform;
const text = String(message || "");
@@ -559,6 +573,7 @@ module.exports = {
resolveFirstTcpEndpoint,
resolveLanProbeTarget,
annotateMacLocalNetworkErrorMessage,
attachMacLocalNetworkProbeResult,
createMacLocalNetworkAccessGate,
ensureMacLocalNetworkAccess: (options) => defaultGate.ensureAccess(options),
annotateMacLocalNetworkError: (message, options) => defaultGate.annotateErrorMessage(message, options),
@@ -13,6 +13,7 @@ const {
resolveFirstTcpEndpoint,
resolveLanProbeTarget,
annotateMacLocalNetworkErrorMessage,
attachMacLocalNetworkProbeResult,
createMacLocalNetworkAccessGate,
} = require("./macLocalNetworkAccess.cjs");
@@ -402,6 +403,32 @@ test("annotateMacLocalNetworkErrorMessage uses carried resolved first-hop addres
);
});
test("attachMacLocalNetworkProbeResult forwards resolved first-hop for direct starts", () => {
const options = {
hostname: "app.example.com",
jumpHosts: [{ hostname: "bastion.example.com", port: 22 }],
};
const attached = attachMacLocalNetworkProbeResult(options, {
probed: true,
hostname: "192.168.1.20",
});
assert.equal(attached._macLocalNetworkResolvedFirstHop, "192.168.1.20");
assert.match(
annotateMacLocalNetworkErrorMessage("connect EHOSTUNREACH 192.168.1.20:22", {
platform: "darwin",
hostname: attached.hostname,
firstHopHostname: "bastion.example.com",
firstHopResolvedAddress: attached._macLocalNetworkResolvedFirstHop,
}),
/Local Network/i,
);
assert.equal(
attachMacLocalNetworkProbeResult(options, { skipped: true, reason: "not-local-network" })
._macLocalNetworkResolvedFirstHop,
undefined,
);
});
test("annotateMacLocalNetworkErrorMessage ignores ProxyCommand errors even with LAN evidence", () => {
const localName = "Network is unreachable";
assert.equal(
+9 -11
View File
@@ -68,7 +68,7 @@ const {
createStartSessionApi,
resolveSshConnectionTimeouts,
} = require("./sshBridge/startSession.cjs");
const { ensureMacLocalNetworkAccess } = require("./macLocalNetworkAccess.cjs");
const { ensureMacLocalNetworkAccess, attachMacLocalNetworkProbeResult } = require("./macLocalNetworkAccess.cjs");
function quoteShellArg(value) {
return "'" + String(value).replace(/'/g, "'\\''") + "'";
@@ -1320,10 +1320,11 @@ async function startSSHSessionWrapper(event, options) {
try {
// Main-process UDP Local Network probe (TN3179 discard-port connect) so
// TCC attributes to Netcatty before the (possibly worker-hosted) SSH dial.
// See #2663 / #2673.
await ensureMacLocalNetworkAccess(options);
// See #2663 / #2673. Carry the resolved first-hop address so direct-start
// annotation (no terminal worker) still sees split-DNS LAN evidence.
const probeResult = await ensureMacLocalNetworkAccess(options);
return await startSSHSessionWithRetries(event, {
...options,
...attachMacLocalNetworkProbeResult(options, probeResult),
...(sourceReuseState ? { _sourceReuseState: sourceReuseState } : {}),
}, pendingDialState);
} catch (err) {
@@ -1408,15 +1409,12 @@ function registerWorkerHandle(ipcMain, terminalWorkerManager, channel) {
let workerPayload = payload;
if (channel === "netcatty:start") {
const probeResult = await ensureMacLocalNetworkAccess(payload);
const resolvedFirstHop = probeResult && typeof probeResult.hostname === "string"
? String(probeResult.hostname).trim()
: "";
workerPayload = {
...(payload && typeof payload === "object" ? payload : {}),
...attachMacLocalNetworkProbeResult(
payload && typeof payload === "object" ? payload : {},
probeResult,
),
_macLocalNetworkMainProbed: true,
...(resolvedFirstHop
? { _macLocalNetworkResolvedFirstHop: resolvedFirstHop }
: {}),
};
}
return terminalWorkerManager.request(channel, workerPayload, {