Repository navigation
feat(dht): verify inbound-only query sources before routing-table insert (BEP 5) - #37
Merged
Merged
Conversation
BEP 5 calls a node good only once it has answered one of our queries. A first-seen node that we have only seen as the source of an inbound query is no longer inserted on that query: it becomes a DHT.Candidates entry, we ping the address the query came from, and only a valid response inserts it via the normal response path (BEP 42 check, learn_remote_contact, ping-before-evict). - The query is answered first; the verification ping is queued after it and the reply never depends on it. - A node already held at the same address refreshes as before. A known id claimed from a new address is a candidate too, and an unanswered or BEP 42-rejected candidate ping never marks a table entry failed or bad. - Bounded: 64 candidates, 2 per source IP, 30 s TTL, 16 new pings per second, and candidate pings hold at most a quarter of the in-flight query slots. - Works for the v4 and v6 tables (BEP 32). - DHT.routing_stats/0 and a once-a-minute [dht] routing_stats debug line show table size per family and candidate counters. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J7n1Zz63wKTZ8xWKnbaAvT
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Какво прави
Затваря „Remaining strict gap“ на BEP 5 от PROTOCOL.md. До сега възел, който виждаме за пръв път само като източник на входяща KRPC заявка, влизаше в routing table-а веднага като
:good. Сега той е кандидат. Отговаряме на заявката му както досега, после пращамеpingна адреса, от който е дошла заявката. В таблицата влиза само след валиден отговор на този ping, и то по обичайния път за отговори: BEP 42 проверка,learn_remote_contact/3, ping-before-evict.Нов модул
DHT.Candidates(чист, всички функции приематnow_ms). Държи кандидатите с лимити:Пази и броячи:
admitted,verified,unanswered,rejected,unsent,expired,dropped_{pending,full,per_ip,rate,busy}.DHT.handle_query/5: първо се праща отговорът (или грешка 203/204), послеobserve_query_source/2. Нашият отговор не чака проверката и не зависи от нея. Проверката е само един допълнителен ping в опашката.Pending слотове: проверъчните ping-ове заемат най-много ¼ от
@max_pending(64 от 256). Така поток от непознати не може да изтощи слотовете за нашите lookup-и.Наблюдаемост:
DHT.routing_stats/0връщаrouting_v4,routing_v6,candidatesи броячите. Същото се логва на debug всяка минута като[dht] routing_stats ….BEP 32: работи и за v4, и за v6 таблицата. Кандидатът се проверява и вкарва в таблицата на собственото си семейство.
Защо
BEP 5: „A good node is a node has responded to one of our queries within the last 15 minutes. A node is also good if it has ever responded to one of our queries and has sent us a query within the last 15 minutes.“ Възел, който само ни е питал, не отговаря на нито едно от двете. Освен това source адресът на UDP заявка може да е подправен, а възел зад NAT, който ни пише, може изобщо да не е достижим отвън. Такива възли не трябва да заемат място в k-bucket-ите и да се раздават на други възли.
Ключови решения (избрани без питане)
last_seen,last_query_ms), без проба. Възлите, научени от наш отговор (nodes/nodes6,add_node, bootstrap), не са засегнати.:badи той би бил изхвърлен. Има тест точно за това.find_node: най-евтиният въпрос, по-малък отговор, а BEP 5 ползваpingи за ping-before-evict.:telemetry, затоваDHT.routing_stats/0плюс периодичен debug ред.Как е тествано
test/dht_candidates_test.exs(14 теста):DHT.Candidates: допускане, повторна проба на същия адрес (:pending), лимити на размер и на IP, rate прозорец и превъртането му, изтичане по TTL (включително приadmit).unanswered = 1;:good,verified = 1;DHT.RoutingMaintenanceTest: ping-before-evict сега започва след отговора на кандидата, и това вече се проверява.DHT.BEP42InboundTest: exempt адрес влиза в таблицата след отговор, а не веднага.DHT.AnnouncePeerTest,DHT.CoverageBatchTest,DHTIPv6RoutingTest: пропускат нашия проверъчен ping, когато четат отговора от сокета.mix format --check-formatted,compile --warnings-as-errors(dev и test),scripts/compile-property-deps.sh,mix dialyzer,mix credo --all,mix sobelowиtimeout 60s mix coveralls.jsonминават. 8 последователни пускания на DHT тестовете са стабилни.master(наборът преди и след е идентичен). Сред тях са четирите вDHTIPv6RoutingTest, на които обнових helper-а. GitHub CI е проверката за тях.Какво не е проверено
dropped_rate/dropped_fullрастат бързо при реална работа, лимитите са константи вDHT.Candidatesи лесно се вдигат.Локална валидация
0. Подготовка
Нужни са един популярен публичен торент (например Ubuntu/Debian ISO) и един магнет без
tr=, така че peers да идват само от DHT. Например същият info hash катоmagnet:?xt=urn:btih:<hash>.1. Чист старт (за честно сравнение на скоростта на пълнене)
Routing table-ът се пази в
.elixir_torrent/dht_routing_table.binв текущата директория. За сравнение „от нула“ го премести временно:2. 5–10 минути с реален торент
Или от друг терминал:
grep "\[dht\] routing_stats" et-dht.logОчаквано:
routing_v4расте през първите минути и стига до стотици, лимитът е 400 на семейство. С глобален IPv6 расте иrouting_v6.verifiedрасте: входящи възли, които са минали проверката.unansweredсъщо расте, защото част от входящите са зад NAT или подправени. Съотношението е информативно, не е критерий.candidatesвъв всеки момент е малко (обикновено под 64) и не расте безкрайно.expiredостава около 0 (ping timeout-ът ги приключва първи).dropped_rate/dropped_full/dropped_busyса 0 или ниски при обикновен клиент.3. Сравнение със
master(не по-бавно от преди)Повтори стъпки 1–2 на
masterсъс същия торент, по възможност по същото време. Наmasterнямаrouting_stats, затова:Записвай броя на 1, 3, 5 и 10 минута и при двата клона. Очакването е сходни числа. Разлика от няколко възела в първата минута е нормална (един RTT забавяне за входящите източници). Устойчиво по-ниско пълнене след 5–10 минути е регресия и спира merge-а.
След теста върни стария table:
mv /tmp/dht_routing_table.bin.bak .elixir_torrent/dht_routing_table.bin.4. DHT-only магнет
Metadata трябва да пристигне и свалянето да тръгне само с DHT peers, за време сходно на
master. В лога търси[peer_dial] … dht=Nс N > 0.5. Сваляне и seed (регресия)
sha256sum /tmp/et-dht/<файл>срещу публикувания).ElixirTorrent.stats(pid, [:speed, :downloaded])трябва да показва upload > 0 при достатъчно голям swarm.DHT.routing_stats()трябва да остава стабилен (без растящcandidates, без[error]в лога).ElixirTorrent.stop_and_serialize(hash), после отновоElixirTorrent.download/2: resume трябва да е нормален, а routing table-ът да се зареди от диска ([dht] listening … routing_v4=Nс N > 0).Generated by Claude Code