mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-26 17:12:27 +03:00
Obrigado — bug real e bem raiz-causado: api64.ipify.org é IPv6-first e derruba tunnels IPv4-only, o que estava reportando proxies vivos como mortos. Validação (worktree combinado a partir de origin/release/v3.8.50, 0 conflitos): - typecheck:core limpo, complexity 2563/2774, cognitive-complexity 1155/1223 (baseline) - tests/unit/proxy-echo-ipv4-fallback-9694.test.ts — 8/8 passando (cobre ordem, split de budget, override, proxy morto de verdade) - Suítes proxy-relacionadas: 805/817 na branch vs 797/809 no release, as 11 falhas são idênticas em ambos os lados e não relacionadas (TLS transport, tproxy CA, SSRF fallback)
139 lines
4.9 KiB
TypeScript
139 lines
4.9 KiB
TypeScript
/**
|
|
* #9694 — proxy "Test connection" false-negative on IPv4-only SOCKS5/SSH proxies.
|
|
*
|
|
* #1255 moved every egress probe to `api64.ipify.org`, which is IPv6-first, so a
|
|
* proxy with no IPv6 route has nothing to connect to and the probe hangs until the
|
|
* caller's deadline — a healthy proxy reported dead. Swapping the target to
|
|
* `api4.ipify.org` fixes that case and breaks #1255's.
|
|
*
|
|
* The probe now tries the targets in order inside the budget the caller already
|
|
* enforced. `api64` stays FIRST so a proxy with working IPv6 behaves exactly as it
|
|
* did after #1255 — including which of its addresses is reported, which matters
|
|
* because the egress IP is used as an identity to detect accounts sharing an address.
|
|
*/
|
|
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
const {
|
|
probeEchoTargets,
|
|
resolveEgressEchoUrls,
|
|
splitEchoAttemptBudget,
|
|
EGRESS_ECHO_URL_DUAL,
|
|
EGRESS_ECHO_URL_V4,
|
|
EGRESS_ECHO_URL_ENV,
|
|
MIN_ECHO_ATTEMPT_MS,
|
|
} = await import("../../src/lib/proxyEchoTarget.ts");
|
|
|
|
test("#9694: the IPv6-first target is still tried first", () => {
|
|
assert.deepEqual(resolveEgressEchoUrls({}), [EGRESS_ECHO_URL_DUAL, EGRESS_ECHO_URL_V4]);
|
|
assert.equal(EGRESS_ECHO_URL_DUAL, "https://api64.ipify.org?format=json");
|
|
assert.equal(EGRESS_ECHO_URL_V4, "https://api4.ipify.org?format=json");
|
|
});
|
|
|
|
test("#9694: an operator override pins exactly one target", () => {
|
|
const env = { [EGRESS_ECHO_URL_ENV]: " https://echo.internal/ip " };
|
|
assert.deepEqual(resolveEgressEchoUrls(env), ["https://echo.internal/ip"]);
|
|
for (const blank of ["", " "]) {
|
|
assert.deepEqual(resolveEgressEchoUrls({ [EGRESS_ECHO_URL_ENV]: blank }), [
|
|
EGRESS_ECHO_URL_DUAL,
|
|
EGRESS_ECHO_URL_V4,
|
|
]);
|
|
}
|
|
});
|
|
|
|
test("#9694: attempts share the caller's budget instead of extending it", () => {
|
|
// The real call sites use 5s, 6s and 10s.
|
|
assert.deepEqual(splitEchoAttemptBudget(10000, 2), [5000, 5000]);
|
|
assert.deepEqual(splitEchoAttemptBudget(6000, 2), [3000, 3000]);
|
|
assert.deepEqual(splitEchoAttemptBudget(5000, 2), [2500, 2500]);
|
|
for (const total of [10000, 6000, 5000]) {
|
|
const budgets = splitEchoAttemptBudget(total, 2);
|
|
assert.ok(
|
|
budgets.reduce((a, b) => a + b, 0) <= total,
|
|
"the sum must never exceed the deadline the caller already enforced"
|
|
);
|
|
}
|
|
});
|
|
|
|
test("#9694: a budget too small to split is spent on one attempt, not two useless ones", () => {
|
|
assert.deepEqual(splitEchoAttemptBudget(MIN_ECHO_ATTEMPT_MS * 2 - 2, 2), [
|
|
MIN_ECHO_ATTEMPT_MS * 2 - 2,
|
|
]);
|
|
assert.deepEqual(splitEchoAttemptBudget(0, 2), []);
|
|
assert.deepEqual(splitEchoAttemptBudget(-1, 2), []);
|
|
assert.deepEqual(splitEchoAttemptBudget(10000, 0), []);
|
|
assert.deepEqual(splitEchoAttemptBudget(10000, 1), [10000]);
|
|
});
|
|
|
|
test("#9694: a reachable IPv6-first target is used and the IPv4 target is never touched", async () => {
|
|
const tried: string[] = [];
|
|
const outcome = await probeEchoTargets(
|
|
async (url) => {
|
|
tried.push(url);
|
|
return '{"ip":"2001:db8::1"}';
|
|
},
|
|
10000,
|
|
{}
|
|
);
|
|
assert.deepEqual(tried, [EGRESS_ECHO_URL_DUAL], "no extra request for a healthy IPv6 proxy");
|
|
assert.equal(outcome.url, EGRESS_ECHO_URL_DUAL);
|
|
assert.equal(outcome.result, '{"ip":"2001:db8::1"}');
|
|
});
|
|
|
|
test("#9694: an IPv4-only proxy reaches the IPv4 target and succeeds", async () => {
|
|
const tried: Array<{ url: string; timeoutMs: number }> = [];
|
|
const outcome = await probeEchoTargets(
|
|
async (url, timeoutMs) => {
|
|
tried.push({ url, timeoutMs });
|
|
// What an IPv4-only SOCKS5 tunnel does with an IPv6-first host: nothing,
|
|
// until the attempt budget aborts it.
|
|
if (url === EGRESS_ECHO_URL_DUAL) throw new Error("This operation was aborted");
|
|
return '{"ip":"203.0.113.7"}';
|
|
},
|
|
10000,
|
|
{}
|
|
);
|
|
assert.deepEqual(
|
|
tried.map((t) => t.url),
|
|
[EGRESS_ECHO_URL_DUAL, EGRESS_ECHO_URL_V4],
|
|
"the IPv6 attempt must not end the probe"
|
|
);
|
|
assert.deepEqual(
|
|
tried.map((t) => t.timeoutMs),
|
|
[5000, 5000],
|
|
"each attempt gets half of the caller's 10s budget"
|
|
);
|
|
assert.equal(outcome.url, EGRESS_ECHO_URL_V4);
|
|
assert.equal(outcome.result, '{"ip":"203.0.113.7"}');
|
|
});
|
|
|
|
test("#9694: a genuinely dead proxy still fails, with the last real error", async () => {
|
|
await assert.rejects(
|
|
() =>
|
|
probeEchoTargets(
|
|
async (url) => {
|
|
throw new Error(`ECONNREFUSED ${url}`);
|
|
},
|
|
10000,
|
|
{}
|
|
),
|
|
/ECONNREFUSED .*api4\.ipify\.org/,
|
|
"the surfaced error must describe a network failure, not internal bookkeeping"
|
|
);
|
|
});
|
|
|
|
test("#9694: an override that fails is not silently retried against ipify", async () => {
|
|
const tried: string[] = [];
|
|
await assert.rejects(() =>
|
|
probeEchoTargets(
|
|
async (url) => {
|
|
tried.push(url);
|
|
throw new Error("nope");
|
|
},
|
|
10000,
|
|
{ [EGRESS_ECHO_URL_ENV]: "https://echo.internal/ip" }
|
|
)
|
|
);
|
|
assert.deepEqual(tried, ["https://echo.internal/ip"]);
|
|
});
|