Как прикрутить BitTorrent трекер к Python-сайту: интеграция с XBT Tracker

от автора

Допустим, у Вас есть некий сайт, написанный на языке Python и Вы хотите прикрутить к нему BitTorrent tracker, наподобие rutracker.org.

Разделение задачи

Задачу можно разделить на две большие функциональности:

  1. Каталог torrent-раздач на сайте (исторически обычно реализуется в виде форума),
  2. Сам Трекер, непосредственно участвующий в процессе раздач.

Трекер представляет собой http-приложение, согласно спецификации протокола BitTorrent сообщающее клиенту по запросу обо всех участниках раздачи. Поскольку клиенты шлют запросы постоянно периодически, то Трекер должен быть производительным: время ответа должно быть минимальным.

В мире PHP Каталог и Трекер зачастую не разделяются на два выделенных приложения. Например, популярный TBDev Tracker существует в виде приложения, объединяющего Каталог-форум и Трекер. (Автор видимо так устал от популярности своего приложения, что сайт давно не обновляется и скачать приложение с него нереально. Однако в Сети можно найти множество клонов.)

Некоторые реализации Трекера изначально были написаны на Python, но затем переписаны на C++ из соображений производительности. Так что в наши дни Python-трекеров не существует (по крайней мере мне найти не удалось). Поэтому единственное, что остается — установить отдельное приложение Трекера и интегрировать его с Python-Каталогом.

Контроль и защита

Если Вам никакой контроль не нужен, то есть:

  • Вам все равно, какие раздачи будет поддерживать Ваш tracker — анонимный пользователь может добавить свою раздачу на Ваш Трекер,
  • и Вы считаете в порядке вещей, что анонимный пользователь может получить доступ к любой существующей раздаче,

то Вам достаточно установить одну из программ, выбрав среди них самую легкую и самую производительную.
Далее, Вы просто даете возможность своим пользователям создавать записи каталога и загружать и скачивать созданные ими самими .torrent-файлы. Предварительно сообщив им правильный announce-адрес Вашего tracker-а, который они запишут в .torrent-файл при создании раздачи.

Если же Вы хотите установить хоть какой-то контроль, а тем более, как в моем случае, захотите ограничить доступ некоторых групп пользователей к некоторым раздачам, то для этого Вам понадобится так называемый «частный трекер».

В теории это делается следующим образом:

  • Предотвращение добавления анонимных раздач: трекер должен поддерживать только те раздачи, которые Ваши авторизованные пользователи добавили в каталог. При создании записи Каталога и присоединении к ней .torrent-файла код Вашего сайта должен добавить раздачу в список разрешенных раздач для tracker-а.
  • Предотвращение получения .torrent — файла анонимным пользователем — реализуется средствами Каталога
  • Ограничение доступа пользователей к раздаче (а также отслеживание пользователей, участвующих в раздаче) выполняется по принципу: если некто может видеть запись каталога и скачать присоединенный .torrent-файл, то ему участие в раздаче разрешается. При этом трекер должен идентифицировать пользователя. И это делается путем прошивки уникального кода пользователя в announce URL трэкера — на лету, когда пользователь скачивает .torrent-файл. То есть это должен делать код Каталога.
  • Запрет протоколов «широковещательного обмена раздачами»: Distribute Hash Table (DHT), Peer EXchange (PEX), Local Service Discovery (LSD), — позволяющих работать вообще без Трекера, а значит и без контроля доступа. Реализуется путем установки флага «private=1» в .torrent-файле.

Реализация

Учитывая все вышесказанное, мой выбор пал на XBT Tracker, как единственную реализацию частного Трекера, рассчитанную на интеграцию с любым сайтом-Каталогом.
XBT Tracker написан на C++ и устанавливается путем сборки из исходников. На Ubuntu Server 12.04 64-bit собирается с первого раза.

Зависимости

Взаимодействие с XBT Tracker предполагается через MySQL-базу данных Трекера, поэтому нашему Python-Каталогу понадобится уметь писать-читать БД MySQL. Для этого я использовал пакет pymysql.
Для разбора и модификации .torrent-файлов также понадобится пакет BitTorrent-bencode

import bencode, pymysql 

Функции

Добавление авторизованного пользователя, если еще такого нет. Здесь поле torrent_pass таблицы xbt_users используется для связки ID пользователя Вашего сайта и ID пользователя XBT Tracker. Ранее torrent_pass использовался для авторизации пользователя по ключу, прописанному в announce URL, однако теперь XBT Tracker использует другой алгоритм — см. ниже. Поле uid — автоинкрементное.

    c = db.cursor()     c.execute("SELECT uid, torrent_pass, torrent_pass_version, downloaded, uploaded FROM xbt_users WHERE torrent_pass = %s",         (request.user.id,))     rec = c.fetchone()     if not rec:         # insert a new user         c.execute("INSERT INTO xbt_users(torrent_pass) VALUES(%s)", (request.user.id))         c.execute("SELECT uid, torrent_pass, torrent_pass_version, downloaded, uploaded FROM xbt_users WHERE torrent_pass = %s",             (request.user.id,)) #rowcount =0         rec = c.fetchone()         db.commit()         uid, torrent_pass, torrent_pass_version, downloaded, uploaded = rec 

Чтение и разбор .torrent-файла:

        with open(fpath, 'rb') as f:             buf = f.read()         bt = bencode.bdecode(buf) 

Насильно установить private-флаг и рассчитать info_hash (private-флаг входит в раздел info и влияет на его хэш):

    if xbt_force_private:         bt['info']['private'] = 1     info_hash_obj = hashlib.sha1(bencode.bencode(bt['info'])) 

Регистрация раздачи в таблице xbt_files если еще таковой нет.

    sha = info_hash_obj.hexdigest()     c = db.cursor()     c.execute("SELECT flags FROM xbt_files WHERE info_hash=UNHEX(%s)", (sha,))     flag = c.fetchone()     if flag is None:         c.execute("INSERT INTO xbt_files(info_hash, mtime, ctime) VALUES(UNHEX(%s), UNIX_TIMESTAMP(), UNIX_TIMESTAMP())",             (sha,))         db.commit()     elif flag[0] % 2 :         error_msg(pagename, request, _("Torrent is marked for deletion!"))         return 

Вычисление ключа авторизации для announce URL

XBT Tracker использует весьма хитрый алгоритм вычисления ключа. Берется набор значений:

  • ID пользователя: xbt_users.uid,
  • версия ключа пользователя: xbt_users.torrent_pass_version,
  • секретный ключ сервера (генерируется автоматически при первом старте XBT Tracker): таблица xbt_config, значение torrent_pass_private_key,
  • значение info_hash данной раздачи: xbt_files.info_hash.

Всё это магическим образом перемешивается по принципу «кручу-верчу, запутать хочу»:

    c.execute("select value from xbt_config where name = 'torrent_pass_private_key'")     torrent_pass_private_key = c.fetchone()[0]     s = "%s %d %d %s" % (torrent_pass_private_key, torrent_pass_version, uid, info_hash_obj.digest())     sha = hashlib.sha1(s).hexdigest()[0:24]     pwd = "%08x%s" % (uid,  sha)     bt['announce'] = 'http://xbt.host:port/%s/announce' % pwd     # remove other trackers     if bt.has_key('announce-list'):         del bt['announce-list'] 

Отсюда становится понятно назначение xbt_users.torrent_pass_version: изменив это значение, можно сделать все ранее скачанные .torrent-файлы инвалидными. То есть это — некий аналог сброса пароля.
И, наконец, кодируем .torrent-файл обратно в строку, которую и будем посылать клиенту:

    buf = bencode.bencode(bt) 

Удаление раздачи

При удалении присоединенного файла мы должны удалить раздачу из списка зарегистрированных раздач (таблицы xbt_files).
Есть способ вежливого удаления, при котором мы помечаем раздачу как удаленную, а реально она удаляется трекером, когда ее скачает полностью последний лич.

c.execute("update xbt_files set flags = 1 where info_hash = UNHEX(%s)", (info_hash, )) 

Ну вот и всё. Дальнейшая докрутка сайта: отображение количества и списка раздающих, подсчет рейтинга, отображение списка файлов в раздаче я отношу к украшательствам. Они реализуются достаточно очевидно: вся необходимая информация, включая статистику, имеется в БД трекера, — и мне, вслед за Пьером Ферма, жаль тратить на них место.

Изложенное решение воплощено в виде плагина к популярному вики-движку: MoinMoin

Необходимо отметить, что предложенное решение имеет существенный недостаток: виртуальный хостинг с поддержкой Python вообще сравнительно редок, а уж возможность сборки C++ приложения в природе не встречал. Единственный, как мне представляется, альтернативный вариант — брать какой-нибудь клон TBDev Tracker и выкусывать оттуда код, непосредственно реализующий функции Трекера.

Надеюсь, мой опыт кому-то пригодится.

ссылка на оригинал статьи http://habrahabr.ru/post/175449/