fix(nat): use the full bounded NAT window (#371)

This commit is contained in:
AAA authored and GitHub committed 2026-09-01 23:57:44 +08:00
1 parent b2f6d33630
commit 1ed7ffc84b
3 files changed
+37 -29

No files matched your search

@@ -35,10 +35,11 @@ state, or media path.
candidates and cannot enter the arithmetic sequence. If three or more
distinct survey candidates for one media section and public address form an
arithmetic port sequence, the adapter appends at most eight bounded
candidates around the sequence's high endpoint. Every ordinary candidate
keeps normal Trickle ICE timing; no ordinary candidate is delayed, rejected,
or replaced. A Browser that does not expose candidate source URLs keeps
ordinary ICE but does not mix independent observations into prediction.
candidates outward from both sequence endpoints: four above the high endpoint
and four below the low endpoint. Every ordinary candidate keeps normal
Trickle ICE timing; no ordinary candidate is delayed, rejected, or replaced.
A Browser that does not expose candidate source URLs keeps ordinary ICE but
does not mix independent observations into prediction.
4. Candidate observations remain in memory for that connection only. No NAT
label, raw address, port, score, hard candidate skip, periodic probe, or
route-controller branch is introduced. Sanitized diagnostics record only
+11 -16
View File
@@ -244,23 +244,21 @@ function stableStep(observations: readonly SrflxObservation[]): number | null {
return step;
}
function predictedFrom(
anchor: SrflxObservation,
function predictedAround(
low: SrflxObservation,
high: SrflxObservation,
step: number,
occupiedPorts: ReadonlySet<number>,
): SignalCandidate[] {
const predicted: SignalCandidate[] = [];
const seenPorts = new Set(occupiedPorts);
for (const direction of [1, -1] as const) {
for (const [anchor, direction] of [
[high, 1],
[low, -1],
] as const) {
for (let index = 1; index <= NAT_PREDICTION_STEPS; index += 1) {
const port = anchor.port + direction * step * index;
if (port < MIN_PREDICTABLE_PORT || port > MAX_PREDICTABLE_PORT) {
continue;
}
if (seenPorts.has(port)) {
continue;
}
seenPorts.add(port);
const fields = [...anchor.fields];
fields[0] = `candidate:s${direction > 0 ? "p" : "m"}${index}`;
fields[5] = String(port);
@@ -298,14 +296,11 @@ export function predictSrflxCandidates(
if (step === null) {
continue;
}
const anchor = distinct.reduce((latest, observation) =>
observation.port > latest.port ? observation : latest,
const ordered = [...distinct].sort((left, right) => left.port - right.port);
return predictedAround(ordered[0]!, ordered.at(-1)!, step).slice(
0,
MAX_NAT_PREDICTION_CANDIDATES,
);
return predictedFrom(
anchor,
step,
new Set(distinct.map((observation) => observation.port)),
).slice(0, MAX_NAT_PREDICTION_CANDIDATES);
}
return [];
}
+21 -9
View File
@@ -118,12 +118,17 @@ describe("NAT prediction ICE adapter", () => {
candidate(40_006),
]);
expect(predictions.length).toBeLessThanOrEqual(
MAX_NAT_PREDICTION_CANDIDATES,
);
expect(predictions.map(portOf)).toEqual(
expect.arrayContaining([40_009, 39_997]),
);
expect(predictions).toHaveLength(MAX_NAT_PREDICTION_CANDIDATES);
expect(predictions.map(portOf)).toEqual([
40_009,
40_012,
40_015,
40_018,
39_997,
39_994,
39_991,
39_988,
]);
expect(predictions.every((value) => value?.sdpMid === "0")).toBe(true);
});
@@ -134,9 +139,16 @@ describe("NAT prediction ICE adapter", () => {
candidate(40_000),
]);
expect(predictions.map(portOf)).toEqual(
expect.arrayContaining([40_009, 39_997]),
);
expect(predictions.map(portOf)).toEqual([
40_009,
40_012,
40_015,
40_018,
39_997,
39_994,
39_991,
39_988,
]);
});
it("does not predict an unstable or incomplete mapping shape", () => {