From 3cda44ac08d673f0d9b8fd6cb74fa4860f1b0154 Mon Sep 17 00:00:00 2001 From: LowderPlay Date: Fri, 2 Oct 2026 20:45:31 +0500 Subject: [PATCH] feat: dpi traceroute commands and debug (#102) --- Cargo.lock | 4 +- frontend/src/routes/admin/probes/+page.svelte | 136 ++++++++++- probe/Cargo.toml | 2 +- probe/src/dpi_hop.rs | 104 ++++++-- probe/src/main.rs | 222 +++++++++++++++++- reports/src/probe.rs | 114 +++++++++ website/Cargo.toml | 2 +- website/probe-hosts.toml | 3 + website/src/admin.rs | 38 ++- website/src/api/node_stats.rs | 19 ++ website/src/mqtt.rs | 81 ++++++- 11 files changed, 680 insertions(+), 45 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ddacb2b..b0b98c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2488,7 +2488,7 @@ dependencies = [ [[package]] name = "probe" -version = "0.6.8" +version = "0.7.0" dependencies = [ "anyhow", "clap", @@ -4509,7 +4509,7 @@ dependencies = [ [[package]] name = "website" -version = "1.4.3" +version = "1.4.4" dependencies = [ "anyhow", "dotenvy", diff --git a/frontend/src/routes/admin/probes/+page.svelte b/frontend/src/routes/admin/probes/+page.svelte index 1489b4e..b195f20 100644 --- a/frontend/src/routes/admin/probes/+page.svelte +++ b/frontend/src/routes/admin/probes/+page.svelte @@ -40,9 +40,30 @@ type Hop = { reverse_names: string[]; outcome: string; }; +type DpiHop = { ttl: number; src: string | null; outcome: string }; +type CommandType = + | "resubscribe_tasks" + | "traceroute" + | "remeasure_dpi_hop" + | "sni_traceroute"; type CommandResult = | { type: "traceroute"; target: string; hops: Hop[] } | { type: "resubscribe_tasks"; requested: boolean } + | { + type: "remeasure_dpi_hop"; + dpi_hop_v4: number | null; + dpi_hop_v6: number | null; + dpi_hops_v4: DpiHop[]; + dpi_hops_v6: DpiHop[]; + } + | { + type: "sni_traceroute"; + host: string; + sni: string; + target: string; + dpi_hop: number | null; + hops: DpiHop[]; + } | { type: "error"; message: string }; type Form = Pick< Probe, @@ -77,6 +98,8 @@ let created = $state<{ id: number; token: string } | null>(null); let selected = $state(null); let target = $state(""); let maxHops = $state(30); +let traceType = $state<"traceroute" | "sni_traceroute">("traceroute"); +let sni = $state(""); let result = $state(null); const queryClient = useQueryClient(); @@ -133,16 +156,22 @@ const sendProbeCommand = createMutation(() => ({ type, target, maxHops, + sni, }: { id: number; - type: "resubscribe_tasks" | "traceroute"; + type: CommandType; target: string; maxHops: number; + sni: string; }) => api( "/probes/" + id + "/commands", "POST", - type === "traceroute" ? { type, target, max_hops: maxHops } : { type }, + type === "traceroute" + ? { type, target, max_hops: maxHops } + : type === "sni_traceroute" + ? { type, host: target, sni, max_hops: maxHops } + : { type }, ), })); $effect(() => { @@ -324,7 +353,7 @@ async function updateCheck(id: number | null) { busy = ""; } } -async function command(id: number, type: "resubscribe_tasks" | "traceroute") { +async function command(id: number, type: CommandType) { selected = id; result = null; busy = `${id}:command`; @@ -335,7 +364,11 @@ async function command(id: number, type: "resubscribe_tasks" | "traceroute") { type, target: target.trim(), maxHops, + sni: sni.trim(), }); + if (result.type === "remeasure_dpi_hop") { + await queryClient.invalidateQueries({ queryKey: probeKey }); + } } catch (e) { error = String(e instanceof Error ? e.message : e); } finally { @@ -354,10 +387,33 @@ const outcomeLabel: Record = { icmp_time_exceeded: "Промежуточный узел", rst: "TCP RST", connected: "Подключено", + tcp_closed: "TCP закрыт", + tcp_acknowledged: "TCP подтверждён", timeout: "Нет ответа", }; +{#snippet dpiHops(hops: DpiHop[])} +
    + {#each hops as hop} +
  1. + {hop.ttl} + {hop.src ?? "* * *"} + {outcomeLabel[hop.outcome] ?? hop.outcome} +
  2. + {/each} +
+ {#if hops.length === 0} +

+ Измерение не удалось: ответов нет. Подробности в логах сканера. +

+ {/if} +{/snippet} + Сканеры · Cheburcheck = { {probe.version ?? "Версия неизвестна"} {probe.bundle_type ? `· ${probe.bundle_type}` : ""} - {#if probe.dpi_hop_v4 || probe.dpi_hop_v6} + {#if probe.dpi_hop_v4 !== null || probe.dpi_hop_v6 !== null}
v4: {probe.dpi_hop_v4 ?? "—"}; v6: {probe.dpi_hop_v6 ?? "—"} @@ -559,6 +615,14 @@ const outcomeLabel: Record = { > Переподписать + +
+ {#if traceType === "sni_traceroute"} + +

+ TCP-соединение к хосту на порту 443, ClientHello с указанным SNI, + затем пакеты с возрастающим TTL. Хост разрешается сканером; + используется первый IP адрес. +

+ {/if}