SSL-торрент по магнитной ссылке

#ssl #lib #libtorrent #magnet-uri #libtorrent-rasterbar

Вопрос:

Я настраиваю свою систему с помощью SSL-торрентов. Если у обоих моих клиентов есть .torrent, все это прекрасно работает даже без трекера благодаря обнаружению локальной службы. Так что теперь следующим шагом для меня было бы использовать магнитную ссылку для клиента, у которого нет торрента. Оба моих клиента прослушивают только порт для SSL (на самом деле они путаются, когда прослушивают более 1 порта…). Но в такой обстановке они, похоже, не могут общаться. Это отчасти ожидаемо, поскольку сокет ssl должен принимать соединения только от однорангового узла с сертификатом…

Но тогда, что нужно в моей настройке, чтобы клиенты могли загружать метаданные торрента SSL (торрент-файл)?

Мы будем очень признательны за любую помощь. Вот мой экспериментальный код

 #include <iostream>
#include <chrono>
#include <fstream>
#include <csignal>
#include <cstring>

#include <libtorrent/session.hpp>
#include <libtorrent/session_params.hpp>
#include <libtorrent/add_torrent_params.hpp>
#include <libtorrent/torrent_handle.hpp>
#include <libtorrent/alert_types.hpp>
#include <libtorrent/bencode.hpp>
#include <libtorrent/torrent_status.hpp>
#include <libtorrent/read_resume_data.hpp>
#include <libtorrent/write_resume_data.hpp>
#include <libtorrent/error_code.hpp>
#include <libtorrent/magnet_uri.hpp>
#include <libtorrent/torrent.hpp>

using namespace std::chrono_literals;

namespace {

// return the name of a torrent status enum
char const* state(lt::torrent_status::state_t s)
{
  switch(s) {
    case lt::torrent_status::checking_files: return "checking";
    case lt::torrent_status::downloading_metadata: return "dl metadata";
    case lt::torrent_status::downloading: return "downloading";
    case lt::torrent_status::finished: return "finished";
    case lt::torrent_status::seeding: return "seeding";
    case lt::torrent_status::checking_resume_data: return "checking resume";
    default: return "<>";
    }
}

std::vector<char> load_file(char const* filename) {
    std::ifstream ifs(filename, std::ios_base::binary);
    ifs.unsetf(std::ios_base::skipws);
    return {std::istream_iterator<char>(ifs), std::istream_iterator<char>()};
}

//// set when we're exiting
std::atomic<bool> shut_down{false};

void sighandler(int) { shut_down = true; }

[[noreturn]] void print_usage(const char* msg) {
    if ( msg )
        std::cerr << msg << std::endl << std::endl;
    std::cerr << R"(
usage: torrent_client [OPTIONS]

--magnet magnet_link   Magnet link URI
--cert_path PATH       path where to find client.crt/key and dhparams.pem
--torrent_path PATH    path to torrent file
--data_path PATH       path where the data is or will be stored
--dht_nodes            csv list of host tuples. e.g., 10.1:4444,10.2:5678[,...]
--listen_port port     port the client is listening too
--server               determines if this is a server (and does not require checking the files on disk)
)";
    std::exit(1);
}

} // anonymous namespace


int main(int argc_, char* argv_[]) try {
    lt::span<char *> args(argv_, argc_);
    args = args.subspan(1);
    if ( args.size() < 2 )
        print_usage("");

    std::vector<std::pair<std::string, int>> dht_nodes;
    auto session_params = load_file(".session");
    lt::session_params params = (session_params.empty()
      ? lt::session_params()
      : lt::read_session_params(session_params)
    );

    params.settings.set_int(lt::settings_pack::alert_mask, lt::alert_category_t::all()
//        lt::alert_category::error
//      | lt::alert_category::storage
//      | lt::alert_category::status
//      | lt::alert_category::connect
//     // | lt::alert_category::dht
    );

    //trying with tracker lsd only...
    params.settings.set_bool(lt::settings_pack::enable_dht, false);
    params.settings.set_bool(lt::settings_pack::enable_lsd, true);
    params.settings.set_bool(lt::settings_pack::enable_upnp, false);
    params.settings.set_bool(lt::settings_pack::enable_natpmp, false);

    //params.settings.set_str(params.settings.dht_bootstrap_nodes, ""); //do not default to libtorrent's own dht nodes...


    lt::add_torrent_params atp;
    bool has_torrent_info = false;
    bool has_magnet_link = false;
    bool is_server = false;
    std::string certPath;
    atp.save_path = ".";
    int port = 0;

    bool param_has_value = true;
    for (; !args.empty(); args = args.subspan(1   param_has_value)) {
        if ( 0 == strcmp(args[0], "--server") ) {
            is_server = true;
            param_has_value = false;
        } else  {
            param_has_value = true;
            if ( args.size() < 2 )
                print_usage("Missing value.");

            if ( 0 == strcmp(args[0], "--torrent_path") ) {
                printf("Torrent path: %sn", args[1]);
                atp.ti = std::make_shared<lt::torrent_info>(std::string(args[1]));
                has_torrent_info = true;
            }
            else if ( 0 == strcmp(args[0], "--data_path") ) {
                printf("Data path: %sn", args[1]);
                atp.save_path = std::string(args[1]);
            }
            else if ( 0 == strcmp(args[0], "--dht_nodes") ) {
                printf("Settings DHT node: %sn", args[1]);
                params.settings.set_str(params.settings.dht_bootstrap_nodes, args[1]);
            }
            else if ( 0 == strcmp(args[0], "--magnet") ) {
                lt::error_code ec;
                lt::parse_magnet_uri(args[1], atp, ec);
                has_magnet_link = true;
            }
            else if ( 0 == strcmp(args[0], "--cert_path") ) {
                printf("Torrent certpath: %sn", args[1]);
                certPath = std::string(args[1]);
            }
            else if ( 0 == strcmp(args[0], "--listen_port") ) {
                printf("Torrent listen port: %sn", args[1]);
                port = atoi(args[1]);
            }
            else {
                print_usage(args[0]);
            }
        }
    }

    if (port) { //setting the listening interface
        std::ostringstream stringStream;
        stringStream << "0.0.0.0:" << port;
        if (!certPath.empty()) {
            stringStream << "s"; //,127.0.0.1:" << port 1 << "s";
        }

        std::string copyOfStr = stringStream.str();
        params.settings.set_str(params.settings.listen_interfaces, copyOfStr);
        std::cout << "LISTENING INTERFACES " << copyOfStr << std::endl;
    } else {
        std::cout << "no port was specified, that client will not be listening" << std::endl;
    }

    lt::session ses(std::move(params));

    if ( has_magnet_link amp;amp; has_torrent_info ) {
        printf("Stupid PoC code.  For now, --magnet and --torrent_path are mutually exclusive.n");
        std::exit(1);
    }

    // load resume data from disk and pass it in as we add the magnet link
    auto buf = load_file(".resume_file");

    if ( buf.size() ) {
        lt::add_torrent_params resume = lt::read_resume_data(buf);
        if (atp.info_hashes == resume.info_hashes) {
            atp = std::move(resume);
        }
    }

    if (is_server) {
        printf("Seeding mode, No need to check pieces.n");
        atp.flags |= lt::torrent_flags::seed_mode; //seeding only (no check at startup);
    }

    ses.async_add_torrent(std::move(atp));

    std::signal(SIGINT, amp;sighandler);

    // set when we're exiting
    bool done = false;
    for (;ses.wait_for_alert(1s);) {
        std::vector<lt::alert*> alerts;
        ses.pop_alerts(amp;alerts);

        if (shut_down) {
            shut_down = false;
            auto const handles = ses.get_torrents();
            if (handles.size() == 1) {
                handles[0].save_resume_data(lt::torrent_handle::save_info_dict);
                done = true;
            }
        }

        for (lt::alert * a : alerts) {
            if (auto at = lt::alert_cast<lt::add_torrent_alert>(a)) {
                std::cout << "add_torrent_alert, torrent file " << at->handle.torrent_file() << std::endl;
            }
            // if we receive the finished alert or an error, we're done
            else if (auto fal = lt::alert_cast<lt::torrent_finished_alert>(a)) {
                if (!is_server) {
                    fal->handle.save_resume_data(lt::torrent_handle::save_info_dict);
                }
            }
            else if (auto error_alert = lt::alert_cast<lt::torrent_error_alert>(a)) {
                std::cout << "nnnn ERRORnnnn";
                std::cout << a->message() << std::endl;
                done = true;
                error_alert->handle.save_resume_data(lt::torrent_handle::save_info_dict);
            } else if (auto rd = lt::alert_cast<lt::save_resume_data_alert>(a)) {
                std::ofstream of(".resume_file", std::ios_base::binary);
                of.unsetf(std::ios_base::skipws);
                auto const b = write_resume_data_buf(rd->params);
                of.write(b.data(), int(b.size()));
                if (done)
                    goto done;
            } else if (lt::alert_cast<lt::save_resume_data_failed_alert>(a)) {
                if (done)
                    goto done;
            } else if (auto st = lt::alert_cast<lt::state_update_alert>(a)) {
                if (st->status.empty())
                    continue;

                // we only have a single torrent, so we know which one
                // the status is for
                lt::torrent_status constamp; s = st->status[0];
                std::cout << 'r' << state(s.state) << ' '
                          << (s.download_payload_rate / 1000) << " kB/s "
                          << (s.total_done / 1000) << " kB ("
                          << (s.progress_ppm / 10000) << "%) downloaded ("
                          << s.num_peers << " peers)x1b[K";
                std::cout.flush();
            } else if (auto ssl_alert = lt::alert_cast<lt::torrent_need_cert_alert>(a)) {
                std::cout << a->message() << std::endl;
                if (!certPath.empty()) {
                    std::string clientCert = certPath   "/client.crt";
                    std::string clientPKey = certPath   "/client.key";
                    std::string dhparams = certPath   "/dhparams.pem";
                    std::cout << "ADDING TORRENT, setting client certificate " << clientCert << std::endl;
                    lt::torrent_handle t = ssl_alert->handle;
                    t.set_ssl_certificate(clientCert, clientPKey, dhparams);
                }
            }else {
                std::cout << "unhandled alert " << a->message() << std::endl;
            }
        }

        // ask the session to post a state_update_alert, to update our
        // state output for the torrent
        ses.post_torrent_updates();
    }

done:
    std::cout << "nsaving session state" << std::endl;
    {
        std::ofstream of(".session", std::ios_base::binary);
        of.unsetf(std::ios_base::skipws);
        auto const b = write_session_params_buf(ses.session_state()
                                                , lt::save_state_flags_t::all());
        of.write(b.data(), int(b.size()));
    }

    std::cout << "ndone, shutting down" << std::endl;
}
catch (std::exceptionamp; e) {
  std::cerr << "Error: " << e.what() << std::endl;
}

 

Поэтому я начинаю своего клиента-посевщика с

 torrent_client --cert_path <cert_path> --torrent_path foo.ssltorrent --listen_port 27400 --server
 

…и мой нуждающийся клиент с

 torrent_client --cert_path {cert_path} --torrent_path toto.ssltorrent --listen_port 27300
 

Это действует как заклинание. Не то чтобы cert_path-это путь, по которому у меня есть сертификат клиента с альтернативным именем субъекта, равным*, и файл параметров sh, созданный с помощью openssl.

But then if I do:

 torrent_client --cert_path {cert_path} --magnet "{magnet link}" --listen_port 27300
 

this gets stuck on «dl metadata».

Of course trying the same thing with non SSL torrent works like a charm.

——EDIT AFTER MORE THOUGHT——

As this made a lot of sense: being able to join the swarm means that one needs to use a proper certificate and know that the underlying torrent is an SSL one, I went ahead and changed a bit my code in the management of 1 alert

            if (auto at = lt::alert_cast<lt::add_torrent_alert>(a)) {
                std::cout << "add_torrent_alert, torrent file " << at->handle.torrent_file() << std::endl;
                if (!at->handle.torrent_file() amp;amp; !certPath.empty()) {
                   auto contents = load_file(certPath   "/ca.crt");

                   lt::string_view c{contents.data(), contents.size()};

                   //TODO: find a better way
                   at->handle.native_handle()->m_ssl_torrent = 1;
                   at->handle.native_handle()->init_ssl(c);
                   std::cout << "setting the certificate of the magnet torrent" << std::endl;
                }

            }
 

это совершенно некрасиво (с использованием частных API), но, похоже, это работает. Я постараюсь найти лучшее решение.

Так что, если я что-то пропустил, пожалуйста, скажите мне. Спасибо.