mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-02 21:32:10 +03:00
feat(proxy): 1-proxy-per-account distribution planner (anti shared-IP)
planProxyDistribution() assigns one DISTINCT proxy per connection so no two accounts of the same rotation group share an egress IP (the codex anomaly trigger). Strict 1:1 by default — extras are left UNASSIGNED rather than sharing an IP; allowSharing round-robins and flags sharingRisk. The healthy .17 servers already had distinct proxies per account; this makes that the enforced, auditable default once live proxies exist. TDD: 5 tests.
This commit is contained in:
@@ -316,3 +316,73 @@ export async function validateProxyPool(deps?: {
|
||||
|
||||
return report;
|
||||
}
|
||||
|
||||
export interface DistributionPlan {
|
||||
assignments: Array<{ connectionId: string; account: string; proxyId: string }>;
|
||||
unassigned: Array<{ connectionId: string; account: string }>;
|
||||
sharingRisk: boolean;
|
||||
note: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* PURE: plan a 1-proxy-per-connection assignment so no two accounts of the same
|
||||
* rotation group share an egress IP (the codex anomaly trigger). Default is
|
||||
* strict 1:1 — extras are left UNASSIGNED (better unrouted than sharing an IP).
|
||||
* allowSharing=true round-robins instead, flagging sharingRisk.
|
||||
*/
|
||||
export function planProxyDistribution(
|
||||
connections: Array<{ id: string; account?: string }>,
|
||||
liveProxyIds: string[],
|
||||
opts: { allowSharing?: boolean } = {}
|
||||
): DistributionPlan {
|
||||
const assignments: DistributionPlan["assignments"] = [];
|
||||
const unassigned: DistributionPlan["unassigned"] = [];
|
||||
let sharingRisk = false;
|
||||
|
||||
connections.forEach((c, i) => {
|
||||
const account = c.account || c.id.slice(0, 8);
|
||||
if (liveProxyIds.length === 0) {
|
||||
unassigned.push({ connectionId: c.id, account });
|
||||
return;
|
||||
}
|
||||
if (opts.allowSharing) {
|
||||
assignments.push({ connectionId: c.id, account, proxyId: liveProxyIds[i % liveProxyIds.length] });
|
||||
} else if (i < liveProxyIds.length) {
|
||||
assignments.push({ connectionId: c.id, account, proxyId: liveProxyIds[i] });
|
||||
} else {
|
||||
unassigned.push({ connectionId: c.id, account });
|
||||
}
|
||||
});
|
||||
|
||||
if (opts.allowSharing && liveProxyIds.length < connections.length) sharingRisk = true;
|
||||
|
||||
const note =
|
||||
liveProxyIds.length === 0
|
||||
? "No live proxies available — add working proxies before distributing."
|
||||
: liveProxyIds.length < connections.length && !opts.allowSharing
|
||||
? `Only ${liveProxyIds.length} live proxies for ${connections.length} accounts — ${unassigned.length} left unassigned (avoid shared-IP anomaly).`
|
||||
: "1 distinct proxy per account.";
|
||||
|
||||
return { assignments, unassigned, sharingRisk, note };
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a distribution plan: assign each proxy to its connection (account scope).
|
||||
*/
|
||||
export async function applyProxyDistribution(
|
||||
plan: DistributionPlan,
|
||||
deps?: { assign?: (connectionId: string, proxyId: string) => Promise<void> }
|
||||
): Promise<{ applied: number }> {
|
||||
const assign =
|
||||
deps?.assign ??
|
||||
(async (connectionId: string, proxyId: string) => {
|
||||
const { assignProxyToScope } = await import("./db/proxies");
|
||||
await assignProxyToScope("account", connectionId, proxyId);
|
||||
});
|
||||
let applied = 0;
|
||||
for (const a of plan.assignments) {
|
||||
await assign(a.connectionId, a.proxyId);
|
||||
applied++;
|
||||
}
|
||||
return { applied };
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ const {
|
||||
_setEgressProbeForTests: (fn: any) => void;
|
||||
clearEgressCache: () => void;
|
||||
};
|
||||
const { validateProxyPool } = egress as any;
|
||||
const { validateProxyPool, planProxyDistribution, applyProxyDistribution } = egress as any;
|
||||
|
||||
test("resolveEgressIp returns the probed IP and caches by proxy URL", async () => {
|
||||
clearEgressCache();
|
||||
@@ -133,3 +133,58 @@ test("validateProxyPool marks live proxies active and dead proxies error", async
|
||||
|
||||
_setEgressProbeForTests(null);
|
||||
});
|
||||
|
||||
test("planProxyDistribution: strict 1:1, extras left unassigned (no shared IP)", () => {
|
||||
const plan = planProxyDistribution(
|
||||
[{ id: "c1", account: "a1" }, { id: "c2", account: "a2" }, { id: "c3", account: "a3" }],
|
||||
["p1", "p2"]
|
||||
);
|
||||
assert.equal(plan.assignments.length, 2);
|
||||
assert.deepEqual(plan.assignments.map((a: any) => a.proxyId), ["p1", "p2"]);
|
||||
assert.equal(plan.unassigned.length, 1, "c3 has no proxy → unassigned, not sharing");
|
||||
assert.equal(plan.unassigned[0].connectionId, "c3");
|
||||
assert.equal(plan.sharingRisk, false);
|
||||
});
|
||||
|
||||
test("planProxyDistribution: enough proxies → 1 distinct per account", () => {
|
||||
const plan = planProxyDistribution(
|
||||
[{ id: "c1", account: "a1" }, { id: "c2", account: "a2" }],
|
||||
["p1", "p2", "p3"]
|
||||
);
|
||||
assert.equal(plan.assignments.length, 2);
|
||||
assert.equal(plan.unassigned.length, 0);
|
||||
assert.match(plan.note, /1 distinct proxy/);
|
||||
});
|
||||
|
||||
test("planProxyDistribution: allowSharing round-robins and flags sharingRisk", () => {
|
||||
const plan = planProxyDistribution(
|
||||
[{ id: "c1", account: "a1" }, { id: "c2", account: "a2" }, { id: "c3", account: "a3" }],
|
||||
["p1", "p2"],
|
||||
{ allowSharing: true }
|
||||
);
|
||||
assert.equal(plan.assignments.length, 3);
|
||||
assert.deepEqual(plan.assignments.map((a: any) => a.proxyId), ["p1", "p2", "p1"]);
|
||||
assert.equal(plan.sharingRisk, true);
|
||||
});
|
||||
|
||||
test("planProxyDistribution: no live proxies → all unassigned with guidance", () => {
|
||||
const plan = planProxyDistribution([{ id: "c1", account: "a1" }], []);
|
||||
assert.equal(plan.assignments.length, 0);
|
||||
assert.equal(plan.unassigned.length, 1);
|
||||
assert.match(plan.note, /No live proxies/);
|
||||
});
|
||||
|
||||
test("applyProxyDistribution assigns each proxy to its connection", async () => {
|
||||
const calls: Array<[string, string]> = [];
|
||||
const plan = planProxyDistribution(
|
||||
[{ id: "c1", account: "a1" }, { id: "c2", account: "a2" }],
|
||||
["p1", "p2"]
|
||||
);
|
||||
const res = await applyProxyDistribution(plan, {
|
||||
assign: async (connectionId: string, proxyId: string) => {
|
||||
calls.push([connectionId, proxyId]);
|
||||
},
|
||||
});
|
||||
assert.equal(res.applied, 2);
|
||||
assert.deepEqual(calls, [["c1", "p1"], ["c2", "p2"]]);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user