DHT в торрент-обмене: от теории к своей k-v сети

в стиле “любопытный факт”

16 глав о том, как работает DHT, зачем он нужен, и как построить на нём децентрализованный discovery-слой для P2P-синхронизации файлов.


1. DHT в торрент-обмене: важность этой штуки 9 из 10

DHT (Distributed Hash Table) в BitTorrent — это реализация Kademlia, которая позволяет пирам находить друг друга без центрального трекера.

С DHT Без DHT
Поиск пиров без трекера Полная зависимость от трекера
Magnet-ссылки работают из коробки Magnet не работает без трекера
Рой живёт, даже если трекер упал Трекер лёг = рой мёртв
Нельзя заблокировать централизованно DNS-блокировка убивает всё
20 миллионов узлов в сети

Единственная причина не ставить 10 — в частных/закрытых роях DHT намеренно отключают (флаг private), и холодный старт (первый пир без трекера) всё ещё представляет проблему.

Оценка: 9/10.


2. Есть другие реализации DHT, кроме Kademlia

Kademlia — самая известная, но не единственная:

DHT Авторы Ключевая идея Где используется
Chord MIT (Stoica, 2001) Кольцо: каждый узел знает O(log N) соседей. Поиск за O(log N) прыжков. Ранние P2P-проекты
Pastry Microsoft/Rice (2001) Префиксная маршрутизация. Узлы с общим префиксом IP — ближе. PAST, Scribe
CAN UC Berkeley (2001) Узлы отвечают за зоны в d-мерном пространстве. Исследования
Tapestry UC Berkeley (2001) Как Pastry, но с locality-aware маршрутизацией. OceanStore
Mainline DHT BitTorrent (2005) Вариант Kademlia с XOR-метрикой, адаптированный под торренты. Все торрент-клиенты
S/Kademlia (2007) Kademlia + защита от Sybil-атак. Безопасные системы
Coral NYU (2004) DHT с кластеризацией. Распределённый CDN

Всё это в основном академическая чепуха , кроме Pastry , который очень крутой, но к сожалению не популярен - не успел.


3. Mainline DHT — что доступно через API торрент-клиентов

DHT внутри торрент-клиентов (Transmission, qBittorrent, Deluge) — чёрный ящик. Через стандартный API доступен минимум:

Операция Transmission qBittorrent Deluge
Включить/выключить DHT session-set setPreferences
Количество DHT-узлов session-stats transfer/info get_session_status
Добавить magnet-ссылку torrent-add torrents/add add_torrent_url
DHT port (firewall check) session-set setPreferences

Ни один клиент не даёт прямого доступа к dht_put() / dht_get() — DHT используется только для поиска пиров.


4. Можно ли использовать DHT «не по назначению»?

Magnet-ссылка как «публикация в DHT»:

1
2
3
4
5
6
7
Peer A (push):  создаёт .torrent → magnet:?xt=urn:btih:INFO_HASH
→ НЕ добавляет в клиент
→ отдаёт magnet соседу через любой канал

Peer B (pull): добавляет magnet в torrent-клиент
→ клиент делает DHT-lookup по info_hash
→ находит Peer A → только если Peer A ТОЖЕ добавил торрент!

Работает только если оба пира добавили торрент. DHT внутри клиента автоматически находит пиров по info_hash.

DHT как health check:

1
2
3
4
5
6
7
func Test() error {
stats, _ := client.GetSessionStats()
if stats.DHTNodes < 10 {
return fmt.Errorf("DHT: only %d nodes (firewall?)", stats.DHTNodes)
}
return nil
}

Полезно: проверяем что DHT жив и файрвол не блокирует UDP.


5. BEP-44: произвольные ключ-значения в DHT

BEP-44 — расширение Mainline DHT, позволяющее хранить произвольные данные:

Это расширение очень даже полезно , само по себе.

Режим Ключ Размер значения TTL
Immutable SHA1(данных) — 20 байт ≤ 1000 байт 2 часа
Mutable Ed25519 публичный ключ — 32 байта ≤ 1000 байт 2 часа (можно переиздавать)

Mutable items поддерживают:

  • Salt (до 64 байт) — чтобы иметь несколько записей на одном ключе
  • Sequence number — монотонный счётчик, защита от повторов
  • Ed25519 подпись — проверка подлинности

Именно BEP-44 позволяет построить децентрализованный discovery-слой: кладём в DHT magnet-ссылку, подписанную приватным ключом, и любой пир может её найти и проверить подпись.


6. Открытые исходники: Transmission, qBittorrent, Deluge

Все три торрент-клиента — open source, но с разными DHT-движками:

Клиент Лицензия Язык DHT-движок
Transmission MIT / GPLv2 C Собственный (libtransmission)
qBittorrent GPLv2+ C++ (Qt) libtorrent (rasterbar)
Deluge GPLv3 Python libtorrent (rasterbar)

qBittorrent и Deluge используют общий libtorrent, который уже реализует BEP-44dht_put_item() и dht_get_item() — но не выставляет их через Web API.


7. Форк клиента для доступа к BEP-44 через API

Можно добавить в Web API qBittorrent:

1
2
3
4
5
6
7
8
9
10
11
12
POST /api/v2/dht/put
Body: {
"key": "<base64-ed25519-pubkey>",
"salt": "sync-folders:my-project",
"seq": 42,
"value": "<base64-encoded-data>",
"private_key": "<base64-ed25519-privkey>"
}
Response: {"success": true, "nodes": 15}

GET /api/v2/dht/get?key=<base64-pubkey>&salt=<str>
Response: {"value": "<base64>", "seq": 42, "sig": "<base64>", "found": true}

Внутри — обёртка над готовыми функциями libtorrent. Transmission для этого хуже: его libtransmission не реализует BEP-44, пришлось бы писать с нуля.

Это реально полезно будет.


8. Инфраструктура DHT: bootstrap-узлы, размер сети, кто держит

Bootstrap-узлы (точки входа в DHT)

Провайдер Запись Кто держит
libtorrent (rasterbar) dht.libtorrent.org:25401 Arvid Norberg
Transmission dht.transmissionbt.com:6881 Команда Transmission
BitTorrent Inc. router.bittorrent.com:6881 Rainberry (ex BitTorrent Inc.)
uTorrent router.utorrent.com:6881 Rainberry

Итого: ~10 публичных bootstrap-узлов от 2-3 организаций.

Важно: bootstrap нужен только при первом запуске. После того как узел набрал таблицу маршрутизации (8-32 узла), он сам её поддерживает.

Размер сети

Метрика Значение
Всего узлов 15-25 миллионов одновременно
С публичным IP ~10-15% (1.5-3.5M)
За NAT ~85-90%
Гео-распределение 200+ стран

Для сравнения: IPFS DHT — 50-100 тыс. узлов. Mainline DHT на 2 порядка больше (в 100+ раз больше).


9. NAT-типы узлов и их распределение

NAT-тип % узлов Прямое соединение UDP hole punching
No NAT / Full Cone (публичный IP) ~12%
Restricted Cone ~15% 🟡 после исходящего
Port-Restricted Cone ~20% 🟡 сложнее 🟡 иногда
Symmetric NAT ~40%
UDP-blocked / CGNAT ~13%

Ключевой вывод: ~47% узлов могут установить прямое P2P-соединение. Остальные 53% — только через relay или никак.


10. Ограничения DHT: размер, TTL, rate limit

BEP-44 накладывает жёсткие ограничения:

Параметр Значение
Размер значения ≤ 1000 байт
TTL 2 часа (потом удаляется)
Rate limit Зависит от узла, обычно ~10 запросов/сек с одного IP

Что происходит при нарушении:

  • Запись > 1000 байт → молча отбрасывается
  • Flood (слишком частые put) → временный бан IP на узле
  • Ответ без подписи (mutable) → не проходит проверку, отбрасывается

Вывод: 1000 байт достаточно для magnet-ссылки (200 байт) или JSON-манифеста (300 байт). Данные передаются через сам торрент, в DHT кладём только указатели.

т.е., в принципе, этого может хватить для наладки каналов связи без серверов и точек отказа.


11. Алгоритмы DHT для своего k-v store

Готовые Go-реализации Kademlia:

Библиотека Плюсы Минусы
go-libp2p-kad-dht NAT traversal (AutoNAT, Relay, Hole punching), изолированная сеть, зрелая (IPFS) Тянет libp2p-стек зависимостей
anacrolix/dht Mainline-совместима (20M узлов), чистый Go Документация скудная
OpenDHT C++, REST API, шифрование CGo, сложнее сборка

Для sync-folders можно использовать форк qBittorrent (быстрый доступ к Mainline DHT), или свою DHT-сеть на go-libp2p-kad-dht.


12. Сравнение Kademlia / Chord / Pastry / CAN — потребительский взгляд

Критерий Kademlia Chord Pastry CAN
Живые реализации ✅ Десятки 🟡 1-2 🟡 FreePastry (Java) ❌ Нет
NAT traversal ✅ Встроен
Churn (узлы приходят/уходят) 🟢 Самовосстанавливается 🟡 Нужен stabilization 🟡 Нужен repair 🔴 Дыры в пространстве
Скорость поиска O(log N), ~4-6 прыжков O(log N) O(log N) O(d·N^(1/d))
Гео-близость ❌ XOR игнорирует топологию ✅ Префиксная маршрутизация 🟡
Bootstrap Просто: любой IP Нужен successor Нужны соседи Нужны соседи

Итоговый ranking:

# Алгоритм Оценка Для кого
1 Kademlia 9/10 Для всех. Единственный с живыми реализациями и NAT traversal
2 Pastry 5/10 Если нужна гео-близость и готов писать реализацию сам
3 Chord 4/10 Если нужна математическая строгость, а не надёжность
4 CAN 1/10 Для курсовой, не для продакшена

13. NAT traversal в Kademlia: что встроено и эффективность

Оригинальная статья Kademlia (2002) про NAT ничего не знает. Современные реализации добавили:

Реализация Механизм Как работает
go-libp2p-kad-dht AutoNAT + Circuit Relay v2 + Hole punching Определяет NAT-тип → пробивает через relay → прямой канал
anacrolix/dht (Mainline) UDP hole punching NAT открывает «дырку» при исходящем запросе
OpenDHT UPnP/NAT-PMP + ICE-lite Просит роутер открыть порт

Эффективность по типам NAT:

NAT-тип Hole punching UPnP Circuit Relay Вердикт
Full Cone (12%) 🟢 Всегда
Restricted Cone (15%) 🟢 Работает
Port-Restricted (20%) 🟡 🟡 Иногда
Symmetric (40%) 🟡 🔴 Только relay
CGNAT (13%) 🔴 Только relay

14. Надёжность когда почти все за NAT

DHT ищет не один узел, а много. При k=20 репликах:

1
2
3
4
5
6
7
Из 20 ближайших узлов в среднем:
- 2-3 с публичным IP (12%) → отвечают всегда
- 3 с Restricted Cone (15%) → отвечают
- 4 с Port-Restricted (20%) → отвечают с вероятностью ~50%
- 11 с Symmetric/CGNAT (53%) → не отвечают

Получаем ~7-9 ответов из 20 — достаточно.

Защитные механизмы:

  • Репликация на k узлов (по умолчанию k=8-20)
  • Параллельные запросы (α=3, ждём первого ответа)
  • Republish каждый час (данные переезжают на новых соседей)
  • Кеширование по пути (популярные ключи размножаются далеко за k)

Реальный бенчмарк (Mainline DHT): 94% ключей находятся за < 1 сек, 99% — за < 5 сек.


15. Как DHT выбирает узлы для репликации

Стандартный Kademlia: k ближайших по XOR-расстоянию. Проблема: XOR не учитывает NAT-тип или географию.

Улучшения:

Реализация Стратегия
Mainline DHT k=8 + агрессивное кеширование по пути поиска
go-libp2p-kad-dht k=20 + приоритет «стабильным» узлам (дольше в сети)
IPFS k=20 + Reprovider каждые 12 часов
OpenDHT k=8 + разнообразие по /16 подсетям

Идеальная стратегия для своей сети:

  • k узлов из разных /16 подсетей (защита от потери ЦОДа)
  • Приоритет узлам с публичным IP
  • Исключать узлы за symmetric NAT если есть альтернативы

16. План: torrent-транспорт для sync-folders

Три компонента:

  1. Форк qBittorrent — добавить BEP-44 API (dht/put, dht/get) поверх libtorrent
  2. sync-folders DHT-утилита — команды dht-put (ждёт k реплик), dht-get, dht-watch
  3. TorrentTransport — один .torrent на папку, DHT для discovery, торрент для передачи

Ключевая идея:

  • Ed25519-ключ как «странный неподбираемый идентификатор» проекта
  • Подпись приватным ключом гарантирует подлинность манифеста
  • BEP-44 mutable item хранит magnet-ссылку последней версии
  • Торрент-клиент автоматически использует Mainline DHT для поиска пиров

Подробный план реализации: планы/draft/torrent-transport.md

Если реализовать все три плана - гибкость p2p будет максимальной.