engine: downstream MTU and downstream train in the measurement document

Three facts the client cannot produce alone, kept deliberately separate:
mtu.pmtud_down (largest datagram that arrives unfragmented — meaningful only
because the server sets DF), mtu.frag_delivery (whether larger ones arrive once
fragmentation is allowed), and train.udp_downstream (loss, reordering and
arrival spacing in the download direction, which a round trip cannot separate
from upstream loss).

ServerMeasurement now runs them on the same ProbeSession as the echo train. It
had to: a fresh session restarts client-side sequence numbers and the server's
anti-replay window discards the lot, so the re-primed source is never recorded
and every granted send goes to a socket that has already closed. That produced
four confidently-wrong FAILED tests and a RED verdict on a healthy network.

Live against fmr: path MTU 1500, fragments to 4000, 100/100 downstream, GREEN.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
mrambossek
2026-08-01 10:54:38 +02:00
co-authored by Claude Fable 5
parent ce1aaa332a
commit 14e5fad1b2
18 changed files with 911 additions and 127 deletions
+47 -14
View File
@@ -3,28 +3,32 @@
// Everything under public/ is served straight from the edge without invoking
// this Worker. The Worker exists for the short stable URLs (/apk, /fdroid,
// /source) — short enough for a QR code — and to resolve "/apk" to the newest
// release asset at request time, so tagging a release in Gitea is the only
// publish step. No site redeploy, no URL to update.
// /source) — short enough for a QR code — and for /api/latest, which the
// homepage uses to show the current version. Both resolve the newest release
// from the Gitea API at request time, so tagging a release in Gitea is the
// only publish step. No site redeploy, no URL to update.
const STATIC_ROUTES = {
"/fdroid": "FDROID_URL",
"/source": "SOURCE_URL",
};
// Resolve the newest APK from the Gitea "latest release" API. Cached at the
// edge for 5 minutes so a release becomes visible quickly, while Gitea sees
// at most one API hit per POP per 5 min regardless of download traffic.
async function latestApkUrl(env) {
// Fetch the Gitea "latest release" object. Cached at the edge for 5 minutes so
// a new release becomes visible quickly, while Gitea sees at most one API hit
// per POP per 5 min regardless of traffic. Returns null on any failure —
// callers degrade to fallbacks rather than surfacing errors.
async function latestRelease(env) {
if (!env.GITEA_REPO_API) return null;
const res = await fetch(`${env.GITEA_REPO_API}/releases/latest`, {
headers: { Accept: "application/json", "User-Agent": "echolot-site" },
cf: { cacheTtl: 300, cacheEverything: true },
});
if (!res.ok) return null;
const rel = await res.json();
const apk = rel.assets?.find((a) => a.name?.endsWith(".apk"));
return apk?.browser_download_url ?? null;
return res.json();
}
function asset(rel, suffix) {
return rel?.assets?.find((a) => a.name?.endsWith(suffix)) ?? null;
}
function redirect(location) {
@@ -38,21 +42,50 @@ function redirect(location) {
});
}
function json(body, maxAge) {
return new Response(JSON.stringify(body), {
headers: {
"Content-Type": "application/json",
"Cache-Control": `public, max-age=${maxAge}`,
},
});
}
export default {
async fetch(request, env) {
const { pathname } = new URL(request.url);
const path = pathname.replace(/\/$/, "");
const fallback = new URL("/#install", request.url).toString();
if (path === "/apk" || path === "/download") {
// Order: live Gitea release → manual override → install section.
if (path === "/apk" || path === "/download" || path === "/apk.sha256") {
const suffix = path === "/apk.sha256" ? ".sha256" : ".apk";
let target = null;
try {
target = await latestApkUrl(env);
target = asset(await latestRelease(env), suffix)?.browser_download_url;
} catch {
// Gitea unreachable — fall through rather than 500 on a download link.
}
return redirect(target || env.DOWNLOAD_URL || fallback);
const override = suffix === ".apk" ? env.DOWNLOAD_URL : null;
return redirect(target || override || fallback);
}
if (path === "/api/latest") {
let rel = null;
try {
rel = await latestRelease(env);
} catch {}
const apk = asset(rel, ".apk");
if (!rel || !apk) return json({ available: false }, 60);
return json(
{
available: true,
version: rel.tag_name,
published_at: rel.published_at,
apk: { name: apk.name, size: apk.size, url: apk.browser_download_url },
sha256: Boolean(asset(rel, ".sha256")),
},
300,
);
}
const varName = STATIC_ROUTES[path];