Pull to refresh

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

Reading time 5 min
Views 7.7K
Допустим, у Вас есть некий сайт, написанный на языке 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 и выкусывать оттуда код, непосредственно реализующий функции Трекера.

Надеюсь, мой опыт кому-то пригодится.
Tags:
Hubs:
+3
Comments 0
Comments Leave a comment

Articles