01 ноября 2021

Как я Opentracker с https и кластером настраивал.

 
Opentracker -  это реализация BitTorrent-трекера на языке C. Программа имеет открытый исходный код и распространяется по лицензии "beer ware". Умеет работать по TCP и UDP, умеет работать со списками доступа для торрентов по info-hash и для пользователей по адресам. Программу я пробовал собирать на машинах с процессором x86_64 и ARM. Везде программа собралась и даже заработала.

Зачем оно мне надо? Просто захотел поднять трекер для личных нужд, а потом уже начал навешивать на него всякие рюшечки.
Далее я расскажу, как поднимал кластер подобных трекеров из трёх нод, помещал перед ними сервер Nginx с балансировкой и https.

Моя система костылей и подпорок заработала со следующими исходными условиями:
  • Наличие внешнего белого статического IP-адреса от провайдера;
  • Наличие сертификата Let’s Encrypt.

Сборка программы.

Сборку я выполнял по инструкции с сайта, но перед компиляцией раскомментировал в файле "Makefile" следующие строки:
FEATURES+=-DWANT_V6
FEATURES+=-DWANT_SYNC_LIVE
FEATURES+=-DWANT_COMPRESSION_GZIP
FEATURES+=-DWANT_IP_FROM_PROXY
FEATURES+=-DWANT_MODEST_FULLSCRAPES
FEATURES+=-DWANT_SYSLOGS
FEATURES+=-DWANT_FULLSCRAPE
Такими правками я включил поддержку протокола IPv6, работу в кластере, поддержку сжатия GZIP, поддержку syslog и scrape.

Настройка программы.

Я поместил всё необходимое для работы программы в каталог "/opt/opentracker". Туда я скопировал исполняемый файл программы "opentracker" и конфигурационный файл "opentracker.conf.sample" под именем "opentracker.conf".

В конфигурационном файле я изменил следующие параметры:
# адрес и порт для синхронизации нод
livesync.cluster.listen 192.168.1.30:9696

# список нод для синхронизации
# адреса всех трёх нод
livesync.cluster.node_ip 192.168.1.28
livesync.cluster.node_ip 192.168.1.30
livesync.cluster.node_ip 192.168.1.17

# каталог для данных программы
# пустой, но должен существовать
tracker.rootdir /opt/opentracker/data

# учётная запись для понижения прав
tracker.user    nobody

# адрес вышестоящего http-сервера
access.proxy 127.0.0.1
Последний параметр нужен, чтобы программа получала адрес пира не из свойств соединения, а из заголовка запроса "X-Forwarded-For". В документации об этом особо не написано, а параметр нашёл поиском по исходным текстам программы.

Установка и запуск программы.

После помещения всех необходимых файлов надо установить владельца каталога "data" в значение из параметра "tracker.user".

Для запуска через systemd я написал такой юнит:

#/opt/opentracker/opentracker.service
[Unit]
Description=Opentracker
Wants=network-online.target
After=network.target network-online.target

[Service]
ExecStart=/opt/opentracker/opentracker -f /opt/opentracker/opentracker.conf
ExecReload=/bin/kill -s HUP $MAINPID
Restart=on-abort

[Install]
WantedBy=multi-user.target
Установка и запуск юнита выполняется следующей командой:
sudo systemctl enable --now /opt/opentracker/opentracker.service
Программа должна появиться в списке процессов.

Подобную процедуру надо провести для всех трёх нод, заменив адрес в параметре "livesync.cluster.listen".

Настройка Nginx.

Nginx я выбрал из-за богатых возможностей настройки и хорошей документации.

Из-за того, что у меня трекер и клиент расположены в одной сети, а запросы к трекеру идёт через роутер, то в списке пиров вместо внешнего адреса я получаю внутренний адрес роутера, то есть "192.168.1.1". В таких условиях пользы от трекера примерно никакой. Чтобы это как-то исправить, я добавил в конфигурационный файл хоста некоторые костыли.

Сам файл выглядит так:

#/etc/nginx/sites-enabled/opentracker.conf
upstream opentracker {
        server 127.0.0.1:6969;
        server 192.168.1.17:6969;
        server 192.168.1.28:6969;
}

server {
        listen 9999 ssl http2;
        server_name bt.example.ru;

        ssl_certificate /etc/letsencrypt/live/example.ru/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/example.ru/privkey.pem;

        access_log /var/log/nginx/access.opentracker.log;

        location / {
                set $xff $proxy_add_x_forwarded_for;
                set $xrip $remote_addr;
                if ($remote_addr = "192.168.1.1") {
                        set $xff "внешний_IP-адрес";
                        set $xrip "внешний_IP-адрес";
                }
                proxy_set_header Host $http_host;
                proxy_set_header X-Forwarded-Proto $scheme;
                proxy_set_header X-Forwarded-For $xff;
                proxy_set_header X-Real-IP $xrip;
                proxy_redirect off;
                proxy_read_timeout 120;
                proxy_connect_timeout 10;
                proxy_pass http://opentracker;
        }
}
В секции "upstream" перечислены адреса всех нод. Сервер будет передавать им запросы по очереди, а синхронизироваться между собой ноды будут сами.

В секции "server" прописан порт и имя сервера, включена поддержка SSL и HTTP 2.0. Для SSL указаны пути к файлам ключа и сертификата. Указан путь к log-файлу конкретно для запросов этого хоста.

В секции "location" прописано условие изменения заголовков "X-Forwarded-For" и "X-Real-IP" в случае поступления запроса из локальной сети за роутером. Тут надо заменить текст "внешний_IP-адрес" на настоящий белый статический IP-адрес, который выдал провайдер. Я не уверен, что подойдёт доменное имя. Если адрес роутера другой, то его тоже надо исправить.

Решения для динамического белого IP-адреса я не придумал. Если у кого-то такое решение есть, то прошу им поделиться.
 
Для добавления в раздачи надо использовать адрес трекера "https://bt.example.ru:9999/announce". Имя сервера надо заменить на своё и указать порт, который проброшен на роутере.

Проверка решения.

Для проверки ответов трекера я добавил трекер в один из торрентов и подсмотрел строку запроса в логе сервера Nginx. Для запроса я использовал программу "curl", а ответ разбирал программой "hexdump" с подсмотренным в интернете шаблоном вывода.

curl --compressed 'https://bt.example.ru:9999/announce?info_hash=%6b%36%1a%4e%0a%24%c2%9c%85%e7%a7%a3%1f%5f%b2%7d%3c%e9%b1%d4&peer_id=-qB4400-)j!KFE-2Bfp0&port=65461&uploaded=1887594623&downloaded=0&left=0&corrupt=0&key=59E7D934&numwant=200&compact=1&no_peer_id=1&supportcrypto=1&redundant=0' --output - | hexdump -e'"%07.8_ad  " 8/1 "%03d " "  |"' -e'8/1  "%_p"  "|\n"'

00000000  100 056 058 099 111 109 112 108  |d8:compl|
00000008  101 116 101 105 051 101 049 048  |etei3e10|
00000016  058 100 111 119 110 108 111 097  |:downloa|
00000024  100 101 100 105 049 101 049 048  |dedi1e10|
00000032  058 105 110 099 111 109 112 108  |:incompl|
00000040  101 116 101 105 048 101 056 058  |etei0e8:|
00000048  105 110 116 101 114 118 097 108  |interval|
00000056  105 049 054 053 048 101 049 050  |i1650e12|
00000064  058 109 105 110 032 105 110 116  |:min int|
00000072  101 114 118 097 108 105 056 050  |ervali82|
00000080  053 101 054 058 112 101 101 114  |5e6:peer|
00000088  115 054 053 052 058 000 000 000  |s654:...|
... skipped ...
После последней строки идёт список адресов раздающих. Среди десятичного представления байтов должен быть внешний адрес, а не "127.000.000.001" или адрес из локальной сети.

Для более широкого вывода можно расширить шаблон до 16 байт на строку:
hexdump -e'"%07.8_ad  " 8/1 "%03d " "  " 8/1 "%03d " "  |"' -e'16/1  "%_p"  "|\n"'
Для проверки заголовков я просто останавливал ноду и запускал вместо неё программу "netcat" в режиме сервера с прослушиванием того же порта:
nc -l -p 6969
Запрос в этом случае будет зависать, но заголовки, которые передаёт Nginx в сторону OpenTracker, будут видны полностью.

Для теста я пробовал качать торрент по magnet-ссылке из другой сети с выключенным поиском пиров по DHT. Пир из локальной сети подхватывался по внешнему адресу, а закачка начиналась сразу после получения торрента от пира.