mirror of
https://github.com/LowderPlay/cheburcheck.git
synced 2026-10-06 13:48:05 +03:00
feat: disable traceroute
This commit is contained in:
@@ -34,6 +34,7 @@ services:
|
||||
MQTT_ADMIN_TOKEN: "${MQTT_ADMIN_TOKEN}"
|
||||
MQTT_HOST: rmqtt
|
||||
MQTT_PORT: 11883
|
||||
PROBE_TRACEROUTE_ENABLED: "${PROBE_TRACEROUTE_ENABLED:-false}"
|
||||
PROBE_RATE_LIMIT_RPM: "${PROBE_RATE_LIMIT_RPM:-30}"
|
||||
volumes:
|
||||
- database-cache:/var/cache/cheburcheck/databases
|
||||
|
||||
@@ -98,16 +98,18 @@ const providerCidrs = (provider: Provider) =>
|
||||
|
||||
{#if result.reverseLookup.length > 0}
|
||||
<DetailRow label="Обратный DNS" icon={EthernetPort}>
|
||||
{#each result.reverseLookup as ptr}
|
||||
<span class={valueClass}>
|
||||
<a
|
||||
href={`/check?target=${ptr}`}
|
||||
class="text-neutral-100 underline decoration-neutral-500 transition-all hover:text-white hover:decoration-neutral-100"
|
||||
>
|
||||
{ptr}
|
||||
</a>
|
||||
</span>
|
||||
{/each}
|
||||
<div class="flex w-full flex-col items-end gap-2">
|
||||
{#each result.reverseLookup as ptr}
|
||||
<span class={valueClass}>
|
||||
<a
|
||||
href={`/check?target=${ptr}`}
|
||||
class="text-neutral-100 underline decoration-neutral-500 transition-all hover:text-white hover:decoration-neutral-100"
|
||||
>
|
||||
{ptr}
|
||||
</a>
|
||||
</span>
|
||||
{/each}
|
||||
</div>
|
||||
</DetailRow>
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
CircleX,
|
||||
LoaderCircle,
|
||||
ShieldCheck,
|
||||
TriangleAlert,
|
||||
} from "@lucide/svelte";
|
||||
import type { ProbeResult, ProbeStatus } from "$lib/api/probe";
|
||||
|
||||
@@ -59,7 +58,7 @@ const verdictStyles = {
|
||||
},
|
||||
whitelist: {
|
||||
icon: ShieldCheck,
|
||||
text: "Белый список",
|
||||
text: "Исключение для CDN",
|
||||
class: "text-amber-500",
|
||||
bg: "bg-amber-500/10",
|
||||
border: "border-amber-500/20",
|
||||
@@ -149,12 +148,23 @@ const verdictStyles = {
|
||||
<div class="text-xs text-neutral-500">{probe.asn || ""}</div>
|
||||
</td>
|
||||
<td class="p-3">
|
||||
<div
|
||||
class={`inline-flex items-center gap-1.5 px-2 py-1 rounded border ${style.bg} ${style.border} ${style.class} text-xs font-bold`}
|
||||
>
|
||||
<style.icon size={14} />
|
||||
{style.text}
|
||||
</div>
|
||||
{#if probe.verdict === "whitelist"}
|
||||
<a
|
||||
href="/kb/whitelist"
|
||||
onclick={(event) => event.stopPropagation()}
|
||||
class={`inline-flex items-center gap-1.5 px-2 py-1 rounded border ${style.bg} ${style.border} ${style.class} text-xs font-bold transition-colors hover:bg-amber-500/20 hover:border-amber-500/50 hover:text-amber-400`}
|
||||
>
|
||||
<style.icon size={14} />
|
||||
{style.text}
|
||||
</a>
|
||||
{:else}
|
||||
<div
|
||||
class={`inline-flex items-center gap-1.5 px-2 py-1 rounded border ${style.bg} ${style.border} ${style.class} text-xs font-bold`}
|
||||
>
|
||||
<style.icon size={14} />
|
||||
{style.text}
|
||||
</div>
|
||||
{/if}
|
||||
</td>
|
||||
<td class="p-3 text-right">
|
||||
{#if isExpanded}
|
||||
@@ -167,7 +177,7 @@ const verdictStyles = {
|
||||
{#if isExpanded}
|
||||
<tr class="bg-neutral-900/30">
|
||||
<td colspan="4" class="p-4 border-b border-neutral-800/50">
|
||||
{#if probe.verdict === "tspu_block"}
|
||||
<!-- {#if probe.verdict === "tspu_block"}
|
||||
<div
|
||||
class="mb-3 flex items-center gap-2 rounded-md border border-red-500/50 bg-red-500/15 px-3 py-2 text-red-200"
|
||||
>
|
||||
@@ -184,7 +194,7 @@ const verdictStyles = {
|
||||
{probe.target_hop}
|
||||
прыжков
|
||||
</div>
|
||||
{/if}
|
||||
{/if} -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{#each probe.host_results as host}
|
||||
<div
|
||||
|
||||
@@ -49,28 +49,19 @@ import KbNote from "$lib/components/kb/KbNote.svelte";
|
||||
<p>
|
||||
Сканер не загружает целевой сайт целиком. Для домена он делает небольшие
|
||||
контрольные HTTPS-загрузки с тестовых хостов, подставляя проверяемое имя в
|
||||
SNI, а до разрешённого IP-адреса выполняет короткую TCP-трассировку на порт
|
||||
443. Эти проверки позволяют независимо увидеть блокировку по имени домена и
|
||||
аномально раннее завершение маршрута до адреса.
|
||||
SNI.
|
||||
</p>
|
||||
|
||||
<KbHeading id="методика-сканирования" title="Методика сканирования">
|
||||
Методика сканирования
|
||||
</KbHeading>
|
||||
<p>
|
||||
Каждая задача содержит IP-адрес цели и, если пользователь проверяет домен,
|
||||
само доменное имя. SNI-проверка, трассировка цели и контрольные трассировки
|
||||
запускаются одновременно, чтобы состояние сети во время измерений было как
|
||||
можно более одинаковым.
|
||||
Сканер подключается к настроенным тестовым хостам, но передаёт проверяемый
|
||||
домен в TLS ClientHello и HTTP Host. Затем он оценивает, удалось ли
|
||||
завершить TLS-обмен и получить минимальный объём данных. Для проверки
|
||||
IP-адреса без домена этот этап пропускается.
|
||||
</p>
|
||||
<ol class="list-decimal">
|
||||
<li>
|
||||
Сканер подключается к настроенным тестовым хостам, но передаёт проверяемый
|
||||
домен в TLS ClientHello и HTTP Host. Затем он оценивает, удалось ли
|
||||
завершить TLS-обмен и получить минимальный объём данных. Для проверки
|
||||
IP-адреса без домена этот этап пропускается.
|
||||
</li>
|
||||
<li>
|
||||
<!-- <li>
|
||||
Сканер открывает TCP-соединения к разрешённому IP-адресу на порту 443,
|
||||
последовательно увеличивая TTL от 1 до 5. На каждом TTL по умолчанию
|
||||
одновременно отправляются три попытки: это уменьшает влияние потерь
|
||||
@@ -83,13 +74,12 @@ import KbNote from "$lib/components/kb/KbNote.svelte";
|
||||
IP-адресов той же версии протокола, что и цель. Все контрольные
|
||||
трассировки идут параллельно, а для сравнения используется наименьший
|
||||
измеренный контрольный TTL.
|
||||
</li>
|
||||
</ol>
|
||||
<p>
|
||||
</li> -->
|
||||
<!-- <p>
|
||||
Если трассировки цели и контроля завершились ответом ICMP Time Exceeded, но
|
||||
ответ для цели пришёл на меньшем TTL, сканер выставляет вердикт «ТСПУ Блок».
|
||||
В результатах показывается номер последнего достигнутого перехода для цели.
|
||||
</p>
|
||||
</p> -->
|
||||
|
||||
<KbHeading id="что-показывает-результат" title="Что показывает результат">
|
||||
Что показывает результат
|
||||
@@ -116,19 +106,19 @@ import KbNote from "$lib/components/kb/KbNote.svelte";
|
||||
практике это похоже на ситуацию, когда оборудование оператора (ТСПУ) видит
|
||||
имя домена и разрывает соединение до передачи полезных данных.
|
||||
</li>
|
||||
<li>
|
||||
<!-- <li>
|
||||
<b>ТСПУ Блок</b>
|
||||
– TCP-трассировка до цели остановилась раньше контрольной трассировки.
|
||||
Обычно означает, что соединение разрывается на оборудовании оператора
|
||||
(ТСПУ) до того как соединение дошло до цели.
|
||||
</li>
|
||||
</li> -->
|
||||
<li>
|
||||
<!-- biome-ignore format: link punctuation -->
|
||||
<a
|
||||
class="underline decoration-dotted underline-offset-2"
|
||||
href="/kb/whitelist"
|
||||
>Белый список</a> – данные передаются в полном объеме при подстановке
|
||||
домена в SNI при подключении к заблокированному серверу.
|
||||
>Исключение для CDN</a> – данные передаются в полном объеме при
|
||||
подстановке домена в SNI при подключении к заблокированному серверу.
|
||||
</li>
|
||||
|
||||
<li>
|
||||
|
||||
+27
-13
@@ -186,21 +186,23 @@ async fn update_config(
|
||||
let value: ProbeConfig = serde_json::from_slice(payload).context("decode probe config")?;
|
||||
let mut control_hosts_v4 = HashSet::new();
|
||||
let mut control_hosts_v6 = HashSet::new();
|
||||
for domain in &value.control_hosts {
|
||||
match tokio::net::lookup_host((domain.as_str(), 443)).await {
|
||||
Ok(addresses) => {
|
||||
for address in addresses {
|
||||
match address.ip() {
|
||||
IpAddr::V4(address) => {
|
||||
control_hosts_v4.insert(address);
|
||||
}
|
||||
IpAddr::V6(address) => {
|
||||
control_hosts_v6.insert(address);
|
||||
if value.traceroute_enabled {
|
||||
for domain in &value.control_hosts {
|
||||
match tokio::net::lookup_host((domain.as_str(), 443)).await {
|
||||
Ok(addresses) => {
|
||||
for address in addresses {
|
||||
match address.ip() {
|
||||
IpAddr::V4(address) => {
|
||||
control_hosts_v4.insert(address);
|
||||
}
|
||||
IpAddr::V6(address) => {
|
||||
control_hosts_v6.insert(address);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(error) => warn!("failed to resolve control host {domain}: {error}"),
|
||||
}
|
||||
Err(error) => warn!("failed to resolve control host {domain}: {error}"),
|
||||
}
|
||||
}
|
||||
*config.write().await = Some(LoadedProbeConfig {
|
||||
@@ -271,7 +273,13 @@ async fn handle_task(
|
||||
|
||||
let result_topic = format!("probe/results/v1/{job_id}/{}", args.probe_id);
|
||||
let config = config.read().await.clone();
|
||||
let traceroute_enabled = config
|
||||
.as_ref()
|
||||
.is_some_and(|config| config.config.traceroute_enabled);
|
||||
let control_targets = config.as_ref().map_or_else(Vec::new, |config| {
|
||||
if !traceroute_enabled {
|
||||
return Vec::new();
|
||||
}
|
||||
let mut rng = rand::thread_rng();
|
||||
match task.ip {
|
||||
IpAddr::V4(_) => config
|
||||
@@ -295,8 +303,14 @@ async fn handle_task(
|
||||
job_id,
|
||||
task.timeout_ms,
|
||||
);
|
||||
let target_traceroute =
|
||||
traceroute::tcp_traceroute(task.ip, args.traceroute_max_hops, args.traceroute_retries);
|
||||
let target_traceroute = async {
|
||||
if traceroute_enabled {
|
||||
traceroute::tcp_traceroute(task.ip, args.traceroute_max_hops, args.traceroute_retries)
|
||||
.await
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
let control_traceroute = async {
|
||||
join_all(control_targets.into_iter().map(|target| {
|
||||
traceroute::tcp_traceroute(target, args.traceroute_max_hops, args.traceroute_retries)
|
||||
|
||||
@@ -15,6 +15,8 @@ pub struct ProbeConfig {
|
||||
pub published_at: String,
|
||||
pub hosts: Vec<Host>,
|
||||
#[serde(default)]
|
||||
pub traceroute_enabled: bool,
|
||||
#[serde(default)]
|
||||
pub control_hosts: Vec<String>,
|
||||
}
|
||||
|
||||
|
||||
@@ -55,6 +55,10 @@ pub async fn probe_query(
|
||||
return Err(Status::BadRequest);
|
||||
}
|
||||
};
|
||||
let probe_config = mqtt.probe_config();
|
||||
if domain.is_none() && !probe_config.traceroute_enabled {
|
||||
return Err(Status::BadRequest);
|
||||
}
|
||||
let ip = resolved_ips
|
||||
.first()
|
||||
.and_then(|ip| ip.parse::<IpAddr>().ok())
|
||||
@@ -77,7 +81,6 @@ pub async fn probe_query(
|
||||
|
||||
let timeout = mqtt.task_timeout();
|
||||
let online_probes = mqtt.online_probe_count().await;
|
||||
let probe_config = mqtt.probe_config();
|
||||
let pool = pool.inner().clone();
|
||||
let query_id = id;
|
||||
let id = id.to_string();
|
||||
@@ -428,7 +431,7 @@ mod tests {
|
||||
let target = icmp_trace(5);
|
||||
assert_eq!(
|
||||
build_probe_verdict(&[], &empty_config(), Some(&target), Some(&control)),
|
||||
"uncertain"
|
||||
"ok"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -441,7 +444,7 @@ mod tests {
|
||||
let control = icmp_trace(5);
|
||||
assert_eq!(
|
||||
build_probe_verdict(&[], &empty_config(), Some(&target), Some(&control)),
|
||||
"uncertain"
|
||||
"ok"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -451,6 +454,7 @@ mod tests {
|
||||
task_timeout_ms: 0,
|
||||
published_at: String::new(),
|
||||
hosts: vec![],
|
||||
traceroute_enabled: true,
|
||||
control_hosts: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,6 +88,7 @@ impl MqttPublisher {
|
||||
task_timeout_ms,
|
||||
published_at: Utc::now().to_rfc3339(),
|
||||
hosts: Vec::new(),
|
||||
traceroute_enabled: false,
|
||||
control_hosts: Vec::new(),
|
||||
}
|
||||
}));
|
||||
@@ -267,6 +268,9 @@ fn load_probe_config(task_timeout_ms: u64) -> Result<ProbeConfig, PublishError>
|
||||
task_timeout_ms,
|
||||
published_at: Utc::now().to_rfc3339(),
|
||||
hosts: config.hosts,
|
||||
traceroute_enabled: std::env::var("PROBE_TRACEROUTE_ENABLED")
|
||||
.ok()
|
||||
.is_some_and(|value| value == "1" || value.eq_ignore_ascii_case("true")),
|
||||
control_hosts: config.control_hosts,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use rocket_cache_response::CacheResponse;
|
||||
use sqlx::postgres::PgPool;
|
||||
use std::io;
|
||||
|
||||
enum ExportType {
|
||||
pub(crate) enum ExportType {
|
||||
Full,
|
||||
Domains,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user