#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), но, похоже, это работает. Я постараюсь найти лучшее решение.
Так что, если я что-то пропустил, пожалуйста, скажите мне. Спасибо.