initial BitTorrent over SSL support

This commit is contained in:
Arvid Norberg 2011-09-12 03:51:49 +00:00
parent 90372b6caf
commit 469414d486
15 changed files with 688 additions and 182 deletions

View File

@ -2310,6 +2310,11 @@ Its declaration looks like this::
sha1_hash info_hash() const;
void set_ssl_certificate(std::string const& cert
, std::string const& private_key
, std::string const& dh_params
, std::string const& passphrase = "");
bool operator==(torrent_handle const&) const;
bool operator!=(torrent_handle const&) const;
bool operator<(torrent_handle const&) const;
@ -3300,6 +3305,35 @@ somehow invalid or if the filenames are not allowed (and hence cannot be opened/
your filesystem. If such an error occurs, a file_error_alert_ is generated and all handles
that refers to that torrent will become invalid.
set_ssl_certificate()
---------------------
::
void set_ssl_certificate(std::string const& cert, std::string const& private_key
, std::string const& dh_params, std::string const& passphrase = "");
For SSL torrents, use this to specify a path to a .pem file to use as this client's certificate.
The certificate must be signed by the certificate in the .torrent file to be valid.
``cert`` is a path to the (signed) certificate in .pem format corresponding to this torrent.
``private_key`` is a path to the private key for the specified certificate. This must be in .pem
format.
``dh_params`` is a path to the Diffie-Hellman parameter file, which needs to be in .pem format.
You can generate this file using the openssl command like this:
``openssl dhparam -outform PEM -out dhparams.pem 512``.
``passphrase`` may be specified if the private key is encrypted and requires a passphrase to
be decrypted.
Note that when a torrent first starts up, and it needs a certificate, it will suspend connecting
to any peers until it has one. It's typically desirable to resume the torrent after setting the
ssl certificate.
If you receive a torrent_need_cert_alert_, you need to call this to provide a valid cert. If you
don't have a cert you won't be allowed to connect to any peers.
torrent_status
==============
@ -3423,6 +3457,8 @@ It contains the following fields::
bool ip_filter_applies;
sha1_hash info_hash;
int listen_port;
};
``handle`` is a handle to the torrent whose status the object represents.
@ -3702,6 +3738,11 @@ to this torrent. This defaults to true.
``info_hash`` is the info-hash of the torrent.
``listen_port`` is the listen port this torrent is listening on for new
connections, if the torrent has its own listen socket. Only SSL torrents
have their own listen sockets. If the torrent doesn't have one, and is
accepting connections on the single listen socket, this is 0.
peer_info
=========
@ -7128,6 +7169,21 @@ cache flush is complete and the torrent does no longer have any files open.
// ...
};
torrent_need_cert_alert
-----------------------
This is always posted for SSL torrents. This is a reminder to the client that
the torrent won't work unless torrent_handle::set_ssl_certificate() is called with
a valid certificate. Valid certificates MUST be signed by the SSL certificate
in the .torrent file.
::
struct torrent_need_cert_alert: tracker_alert
{
// ...
};
dht_announce_alert
------------------
@ -8844,6 +8900,39 @@ This threshold is controlled by ``session_settings::whole_pieces_threshold``.
*TODO: piece affinity by speed category*
*TODO: piece priorities*
SSL torrents
============
Torrents may have an SSL root (CA) certificate embedded in them. Such torrents
are called *SSL torrents*. An SSL torrent talks to all bittorrent peers over SSL.
The protocols are layered like this::
+-----------------------+
| BitTorrent protocol |
+-----------------------+
| SSL |
+-----------+-----------+
| TCP | uTP |
| +-----------+
| | UDP |
+-----------+-----------+
During the SSL handshake, both peers need to authenticate by providing a certificate
that is signed by the private counterpart of the CA certificate found in the
.torrent file. These peer certificates are expected to be privided to peers through
some other means than bittorrent. Typically by a peer generating a certificate request
which is sent to the publisher of the torrent, and the publisher returning a signed
certificate.
In libtorrent, `set_ssl_certificate()`_ in torrent_handle_ is used to tell libtorrent where
to find the peer certificate and the private key for it. When an SSL torrent is loaded,
the torrent_need_cert_alert_ is posted to remind the user to provide a certificate.
In order for the client to know which torrent an incoming connection belongs to, in order
to provide the correct certificate, each SSL torrent opens their own dedicated listen socket.
This feature is only available if libtorrent is build with openssl support (``TORRENT_USE_OPENSSL``).
filename checks
===============

View File

@ -850,6 +850,40 @@ void handle_alert(libtorrent::session& ses, libtorrent::alert* a
{
using namespace libtorrent;
#ifdef TORRENT_USE_OPENSSL
if (torrent_need_cert_alert* p = alert_cast<torrent_need_cert_alert>(a))
{
torrent_handle h = p->handle;
error_code ec;
file_status st;
std::string cert = combine_path("certificates", to_hex(h.info_hash().to_string())) + ".pem";
std::string priv = combine_path("certificates", to_hex(h.info_hash().to_string())) + "_key.pem";
stat_file(cert, &st, ec);
if (ec)
{
char msg[256];
snprintf(msg, sizeof(msg), "ERROR. could not load certificate %s: %s\n", cert.c_str(), ec.message().c_str());
if (g_log_file) fprintf(g_log_file, "[%s] %s\n", time_now_string(), msg);
return;
}
stat_file(priv, &st, ec);
if (ec)
{
char msg[256];
snprintf(msg, sizeof(msg), "ERROR. could not load private key %s: %s\n", priv.c_str(), ec.message().c_str());
if (g_log_file) fprintf(g_log_file, "[%s] %s\n", time_now_string(), msg);
return;
}
char msg[256];
snprintf(msg, sizeof(msg), "loaded certificate %s and key %s\n", cert.c_str(), priv.c_str());
if (g_log_file) fprintf(g_log_file, "[%s] %s\n", time_now_string(), msg);
h.set_ssl_certificate(cert, priv, "certificates/dhparams.pem", "test");
h.resume();
}
#endif
if (torrent_finished_alert* p = alert_cast<torrent_finished_alert>(a))
{
p->handle.set_max_connections(max_connections_per_torrent / 2);

View File

@ -1263,6 +1263,21 @@ namespace libtorrent
error_code error;
};
struct TORRENT_EXPORT torrent_need_cert_alert: torrent_alert
{
torrent_need_cert_alert(torrent_handle const& h)
: torrent_alert(h)
{}
TORRENT_DEFINE_ALERT(torrent_need_cert_alert);
const static int static_category = alert::status_notification;
virtual std::string message() const;
virtual bool discardable() const { return false; }
error_code error;
};
}

View File

@ -119,6 +119,27 @@ namespace libtorrent
struct bencode_map_entry;
struct listen_socket_t
{
listen_socket_t(): external_port(0) {}
// this is typically empty but can be set
// to the WAN IP address of NAT-PMP or UPnP router
address external_address;
// this is typically set to the same as the local
// listen port. In case a NAT port forward was
// successfully opened, this will be set to the
// port that is open on the external (NAT) interface
// on the NAT box itself. This is the port that has
// to be published to peers, since this is the port
// the client is reachable through.
int external_port;
// the actual socket
boost::shared_ptr<socket_acceptor> sock;
};
namespace aux
{
struct session_impl;
@ -349,7 +370,7 @@ namespace libtorrent
torrent_handle find_torrent_handle(sha1_hash const& info_hash);
void announce_lsd(sha1_hash const& ih, bool broadcast = false);
void announce_lsd(sha1_hash const& ih, int port, bool broadcast = false);
void save_state(entry* e, boost::uint32_t flags) const;
void load_state(lazy_entry const* e);
@ -661,26 +682,6 @@ namespace libtorrent
tcp::endpoint m_ipv6_interface;
tcp::endpoint m_ipv4_interface;
struct listen_socket_t
{
listen_socket_t(): external_port(0) {}
// this is typically empty but can be set
// to the WAN IP address of NAT-PMP or UPnP router
address external_address;
// this is typically set to the same as the local
// listen port. In case a NAT port forward was
// successfully opened, this will be set to the
// port that is open on the external (NAT) interface
// on the NAT box itself. This is the port that has
// to be published to peers, since this is the port
// the client is reachable through.
int external_port;
// the actual socket
boost::shared_ptr<socket_acceptor> sock;
};
// since we might be listening on multiple interfaces
// we might need more than one listen socket
std::list<listen_socket_t> m_listen_sockets;
@ -697,7 +698,7 @@ namespace libtorrent
boost::shared_ptr<socket_type> m_i2p_listen_socket;
#endif
listen_socket_t setup_listener(tcp::endpoint ep, int retries
void setup_listener(listen_socket_t* s, tcp::endpoint ep, int retries
, bool v6_only, int flags, error_code& ec);
// the proxy used for bittorrent

View File

@ -90,7 +90,8 @@ namespace libtorrent
, boost::weak_ptr<torrent> t
, boost::shared_ptr<socket_type> s
, tcp::endpoint const& remote
, policy::peer* peerinfo);
, policy::peer* peerinfo
, bool outgoing = true);
// with this constructor we have been contacted and we still don't
// know which torrent the connection belongs to
@ -98,7 +99,8 @@ namespace libtorrent
aux::session_impl& ses
, boost::shared_ptr<socket_type> s
, tcp::endpoint const& remote
, policy::peer* peerinfo);
, policy::peer* peerinfo
, bool outgoing = false);
void start();

View File

@ -171,7 +171,8 @@ namespace libtorrent
, boost::weak_ptr<torrent> t
, boost::shared_ptr<socket_type> s
, tcp::endpoint const& remote
, policy::peer* peerinfo);
, policy::peer* peerinfo
, bool outgoing = true);
// with this constructor we have been contacted and we still don't
// know which torrent the connection belongs to
@ -179,7 +180,8 @@ namespace libtorrent
aux::session_impl& ses
, boost::shared_ptr<socket_type> s
, tcp::endpoint const& remote
, policy::peer* peerinfo);
, policy::peer* peerinfo
, bool outgoing = false);
// this function is called after it has been constructed and properly
// reference counted. It is safe to call self() in this function

View File

@ -94,6 +94,7 @@ namespace libtorrent
struct add_torrent_params;
struct storage_interface;
class bt_peer_connection;
struct listen_socket_t;
namespace aux
{
@ -839,7 +840,10 @@ namespace libtorrent
void dequeue_torrent_check();
#ifdef TORRENT_USE_OPENSSL
void set_ssl_cert(std::string const& certificate, error_code& ec);
void set_ssl_cert(std::string const& certificate
, std::string const& private_key
, std::string const& dh_params
, std::string const& passphrase);
bool is_ssl_torrent() const { return m_ssl_ctx; }
boost::asio::ssl::context* ssl_ctx() const { return m_ssl_ctx.get(); }
#endif
@ -928,7 +932,27 @@ namespace libtorrent
piece_manager* m_storage;
#ifdef TORRENT_USE_OPENSSL
// TODO: in order to save space, stick the ssl context
// and the listen_socket_t in a single struct and have
// a shared_pointer to that (or intrusive_ptr even)
boost::shared_ptr<asio::ssl::context> m_ssl_ctx;
// listen socket used to accept incoming ssl connections
boost::shared_ptr<listen_socket_t> m_ssl_acceptor;
// SSL torrents have their own listen socket, so that
// we know which certificate to use for incoming connections
// these function are used for handling the torrent specific
// listen socket
void async_accept(boost::shared_ptr<socket_acceptor> const& listener);
void on_accept_ssl_connection(boost::shared_ptr<socket_type> const& s
, boost::weak_ptr<socket_acceptor> listen_socket, error_code const& e);
void ssl_handshake(error_code const& ec, boost::shared_ptr<socket_type> s);
void init_ssl(std::string const& cert);
#endif
#ifdef TORRENT_DEBUG

View File

@ -252,8 +252,10 @@ namespace libtorrent
bool resolve_countries() const;
#endif
void set_ssl_certificates(std::string const& certificate
, error_code& ec);
void set_ssl_certificate(std::string const& certificate
, std::string const& private_key
, std::string const& dh_params
, std::string const& passphrase = "");
storage_interface* get_storage_impl() const;
@ -485,6 +487,7 @@ namespace libtorrent
, need_save_resume(false)
, ip_filter_applies(true)
, info_hash(0)
, listen_port(0)
{}
// handle to the torrent
@ -701,6 +704,10 @@ namespace libtorrent
// the info-hash for this torrent
sha1_hash info_hash;
// if this torrent has its own listen socket, this is
// the port it's listening on. Otherwise it's 0
int listen_port;
};
}

View File

@ -561,5 +561,10 @@ namespace libtorrent {
return torrent_alert::message() + " removed";
}
std::string torrent_need_cert_alert::message() const
{
return torrent_alert::message() + " needs SSL certificate";
}
} // namespace libtorrent

View File

@ -99,9 +99,10 @@ namespace libtorrent
, boost::weak_ptr<torrent> tor
, shared_ptr<socket_type> s
, tcp::endpoint const& remote
, policy::peer* peerinfo)
, policy::peer* peerinfo
, bool outgoing)
: peer_connection(ses, tor, s, remote
, peerinfo)
, peerinfo, outgoing)
, m_state(read_protocol_identifier)
#ifndef TORRENT_DISABLE_EXTENSIONS
, m_upload_only_id(0)
@ -152,8 +153,9 @@ namespace libtorrent
session_impl& ses
, boost::shared_ptr<socket_type> s
, tcp::endpoint const& remote
, policy::peer* peerinfo)
: peer_connection(ses, s, remote, peerinfo)
, policy::peer* peerinfo
, bool outgoing)
: peer_connection(ses, s, remote, peerinfo, outgoing)
, m_state(read_protocol_identifier)
#ifndef TORRENT_DISABLE_EXTENSIONS
, m_upload_only_id(0)
@ -225,6 +227,10 @@ namespace libtorrent
boost::shared_ptr<torrent> t = associated_torrent().lock();
std::string const key = t->torrent_file().encryption_key();
if (key.size() == 32) out_enc_policy = pe_settings::disabled;
// never try an encrypted connection when already using SSL
if (get_socket()->get<ssl_stream<stream_socket> >() || get_socket()->get<ssl_stream<utp_stream> >())
out_enc_policy = pe_settings::disabled;
#endif
#ifdef TORRENT_VERBOSE_LOGGING
char const* policy_name[] = {"forced", "enabled", "disabled"};
@ -2937,6 +2943,18 @@ namespace libtorrent
peer_log("*** unrecognized protocol header");
#endif
#ifdef TORRENT_USE_OPENSSL
if (get_socket()->get<ssl_stream<stream_socket> >()
|| get_socket()->get<ssl_stream<utp_stream> >())
{
#ifdef TORRENT_VERBOSE_LOGGING
peer_log("*** SSL peers are not allowed to use any other encryption");
#endif
disconnect(errors::invalid_info_hash, 1);
return;
}
#endif // TORRENT_USE_OPENSSL
bool found_encrypted_torrent = false;
#ifdef TORRENT_USE_OPENSSL
if (!is_local())
@ -3078,7 +3096,13 @@ namespace libtorrent
std::copy(recv_buffer.begin + 8, recv_buffer.begin + 28
, (char*)info_hash.begin());
attach_to_torrent(info_hash, m_encrypted && m_rc4_encrypted);
#ifndef TORRENT_DISABLE_ENCRYPTION
bool allow_encrypted = m_encrypted && m_rc4_encrypted;
#else
bool allow_encrypted = true;
#endif
attach_to_torrent(info_hash, allow_encrypted);
if (is_disconnecting()) return;
}
else

View File

@ -87,6 +87,10 @@ http_connection::http_connection(io_service& ios, connection_queue& cc
, m_abort(false)
{
TORRENT_ASSERT(!m_handler.empty());
// TODO: if we were handed an SSL context, we should really
// verify the hostname of the web server as well. This is supported
// in boost starting with version 1.47.0. See ssl::rfc2818_verification
// and ssl::context::set_verify_callback
}
http_connection::~http_connection()

View File

@ -79,7 +79,8 @@ namespace libtorrent
, boost::weak_ptr<torrent> tor
, shared_ptr<socket_type> s
, tcp::endpoint const& endp
, policy::peer* peerinfo)
, policy::peer* peerinfo
, bool outgoing)
:
#ifdef TORRENT_DEBUG
m_last_choke(time_now() - hours(1))
@ -139,7 +140,7 @@ namespace libtorrent
, m_choke_rejects(0)
, m_read_recurse(0)
, m_fast_reconnect(false)
, m_active(true)
, m_active(outgoing)
, m_peer_interested(false)
, m_peer_choked(true)
, m_interesting(false)
@ -149,8 +150,8 @@ namespace libtorrent
, m_ignore_unchoke_slots(false)
, m_have_all(false)
, m_disconnecting(false)
, m_connecting(true)
, m_queued(true)
, m_connecting(outgoing)
, m_queued(outgoing)
, m_request_large_blocks(false)
, m_share_mode(false)
, m_upload_only(false)
@ -203,9 +204,12 @@ namespace libtorrent
error_code ec;
m_logger = m_ses.create_log(m_remote.address().to_string(ec) + "_"
+ to_string(m_remote.port()).elems, m_ses.listen_port());
peer_log(">>> OUTGOING_CONNECTION [ ep: %s transport: %s seed: %d p: %p]"
peer_log(">>> %s [ ep: %s transport: %s seed: %d p: %p ]"
, outgoing ? "OUTGOING_CONNECTION" : "INCOMING CONNECTION"
, print_endpoint(m_remote).c_str()
, (m_socket->get<utp_stream>()) ? "uTP connection" : "TCP connection"
, m_socket->get<ssl_stream<stream_socket> >() ? "SSL/TCP"
: m_socket->get<ssl_stream<utp_stream> >() ? "SSL/uTP"
: m_socket->get<utp_stream>() ? "uTP" : "TCP"
, m_peer_info ? m_peer_info->seed : 0, m_peer_info);
#endif
#ifdef TORRENT_DEBUG
@ -223,7 +227,8 @@ namespace libtorrent
session_impl& ses
, shared_ptr<socket_type> s
, tcp::endpoint const& endp
, policy::peer* peerinfo)
, policy::peer* peerinfo
, bool outgoing)
:
#ifdef TORRENT_DEBUG
m_last_choke(time_now() - hours(1))
@ -282,7 +287,7 @@ namespace libtorrent
, m_choke_rejects(0)
, m_read_recurse(0)
, m_fast_reconnect(false)
, m_active(false)
, m_active(outgoing)
, m_peer_interested(false)
, m_peer_choked(true)
, m_interesting(false)
@ -292,8 +297,8 @@ namespace libtorrent
, m_ignore_unchoke_slots(false)
, m_have_all(false)
, m_disconnecting(false)
, m_connecting(false)
, m_queued(false)
, m_connecting(outgoing)
, m_queued(outgoing)
, m_request_large_blocks(false)
, m_share_mode(false)
, m_upload_only(false)
@ -347,7 +352,8 @@ namespace libtorrent
TORRENT_ASSERT(m_socket->remote_endpoint(ec) == m_remote || ec);
m_logger = m_ses.create_log(remote().address().to_string(ec) + "_"
+ to_string(remote().port()).elems, m_ses.listen_port());
peer_log("<<< INCOMING_CONNECTION [ ep: %s transport: %s ]"
peer_log("<<< %s [ ep: %s transport: %s ]"
, outgoing ? "OUTGOING_CONNECTION" : "INCOMING CONNECTION"
, print_endpoint(m_remote).c_str()
, (m_socket->get<utp_stream>()) ? "uTP connection" : "TCP connection");
#endif
@ -536,7 +542,7 @@ namespace libtorrent
TORRENT_ASSERT(m_peer_info == 0 || m_peer_info->connection == this);
boost::shared_ptr<torrent> t = m_torrent.lock();
if (!t)
if (!m_active)
{
tcp::socket::non_blocking_io ioc(true);
error_code ec;
@ -556,7 +562,8 @@ namespace libtorrent
if (m_remote.address().is_v4())
m_socket->set_option(type_of_service(m_ses.settings().peer_tos), ec);
}
else if (t->ready_for_connections())
if (t && t->ready_for_connections())
{
init();
}
@ -1132,6 +1139,16 @@ namespace libtorrent
t.reset();
}
#ifdef TORRENT_USE_OPENSSL
if (t && t->is_ssl_torrent())
{
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING
peer_log("*** can't attach to an ssl torrent");
#endif
t.reset();
}
#endif
if (!t)
{
// we couldn't find the torrent!
@ -3320,7 +3337,7 @@ namespace libtorrent
peer_log("CONNECTION FAILED: %s", print_endpoint(m_remote).c_str());
#endif
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING
(*m_ses.m_logger) << "CONNECTION FAILED: " << print_endpoint(m_remote) << "\n";
(*m_ses.m_logger) << time_now_string() << " CONNECTION FAILED: " << print_endpoint(m_remote) << "\n";
#endif
#ifdef TORRENT_STATS
@ -5234,7 +5251,7 @@ namespace libtorrent
return;
}
#if defined TORRENT_VERBOSE_LOGGING
peer_log(">>> ASYNC_CONENCT [ dst: %s ]", print_endpoint(m_remote).c_str());
peer_log(">>> ASYNC_CONNECT [ dst: %s ]", print_endpoint(m_remote).c_str());
#endif
#if defined TORRENT_ASIO_DEBUGGING
add_outstanding_async("peer_connection::on_connection_complete");
@ -5279,7 +5296,11 @@ namespace libtorrent
if (m_disconnecting) return;
m_last_receive = time_now();
if (m_socket->get<utp_stream>() && m_peer_info)
if ((m_socket->get<utp_stream>()
#ifdef TORRENT_USE_OPENSSL
|| m_socket->get<ssl_stream<utp_stream> >()
#endif
) && m_peer_info)
{
m_peer_info->confirmed_supports_utp = true;
m_peer_info->supports_utp = false;

View File

@ -1771,41 +1771,40 @@ namespace aux {
return m_ipv4_interface;
}
session_impl::listen_socket_t session_impl::setup_listener(tcp::endpoint ep
void session_impl::setup_listener(listen_socket_t* s, tcp::endpoint ep
, int retries, bool v6_only, int flags, error_code& ec)
{
listen_socket_t s;
s.sock.reset(new socket_acceptor(m_io_service));
s.sock->open(ep.protocol(), ec);
s->sock.reset(new socket_acceptor(m_io_service));
s->sock->open(ep.protocol(), ec);
if (ec)
{
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING
(*m_logger) << "failed to open socket: " << print_endpoint(ep)
<< ": " << ec.message() << "\n" << "\n";
#endif
return listen_socket_t();
return;
}
if (flags & session::listen_reuse_address)
{
error_code err; // ignore errors here
s.sock->set_option(socket_acceptor::reuse_address(true), err);
s->sock->set_option(socket_acceptor::reuse_address(true), err);
}
#if TORRENT_USE_IPV6
if (ep.protocol() == tcp::v6())
{
error_code err; // ignore errors here
s.sock->set_option(v6only(v6_only), err);
s->sock->set_option(v6only(v6_only), err);
#ifdef TORRENT_WINDOWS
#ifndef PROTECTION_LEVEL_UNRESTRICTED
#define PROTECTION_LEVEL_UNRESTRICTED 10
#endif
// enable Teredo on windows
s.sock->set_option(v6_protection_level(PROTECTION_LEVEL_UNRESTRICTED), err);
s->sock->set_option(v6_protection_level(PROTECTION_LEVEL_UNRESTRICTED), err);
#endif
}
#endif
s.sock->bind(ep, ec);
s->sock->bind(ep, ec);
while (ec && retries > 0)
{
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING
@ -1818,7 +1817,7 @@ namespace aux {
TORRENT_ASSERT_VAL(!ec, ec);
--retries;
ep.port(ep.port() + 1);
s.sock->bind(ep, ec);
s->sock->bind(ep, ec);
}
if (ec && !(flags & session::listen_no_system_port))
{
@ -1826,7 +1825,7 @@ namespace aux {
// let the OS pick a port
ep.port(0);
ec = error_code();
s.sock->bind(ep, ec);
s->sock->bind(ep, ec);
}
if (ec)
{
@ -1839,10 +1838,10 @@ namespace aux {
, print_endpoint(ep).c_str(), ec.message().c_str());
(*m_logger) << time_now_string() << msg << "\n";
#endif
return listen_socket_t();
return;
}
s.external_port = s.sock->local_endpoint(ec).port();
if (!ec) s.sock->listen(m_settings.listen_queue_size, ec);
s->external_port = s->sock->local_endpoint(ec).port();
if (!ec) s->sock->listen(m_settings.listen_queue_size, ec);
if (ec)
{
if (m_alerts.should_post<listen_failed_alert>())
@ -1853,22 +1852,21 @@ namespace aux {
, print_endpoint(ep).c_str(), ec.message().c_str());
(*m_logger) << time_now_string() << msg << "\n";
#endif
return listen_socket_t();
return;
}
// if we asked the system to listen on port 0, which
// socket did it end up choosing?
if (ep.port() == 0)
ep.port(s.sock->local_endpoint().port());
ep.port(s->sock->local_endpoint().port());
if (m_alerts.should_post<listen_succeeded_alert>())
m_alerts.post_alert(listen_succeeded_alert(ep));
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING
(*m_logger) << time_now_string() << " listening on: " << ep
<< " external port: " << s.external_port << "\n";
<< " external port: " << s->external_port << "\n";
#endif
return s;
}
void session_impl::open_listen_port(int flags, error_code& ec)
@ -1887,8 +1885,8 @@ namespace aux {
// this means we should open two listen sockets
// one for IPv4 and one for IPv6
listen_socket_t s = setup_listener(
tcp::endpoint(address_v4::any(), m_listen_interface.port())
listen_socket_t s;
setup_listener(&s, tcp::endpoint(address_v4::any(), m_listen_interface.port())
, m_listen_port_retries, false, flags, ec);
if (s.sock)
@ -1906,8 +1904,7 @@ namespace aux {
// only try to open the IPv6 port if IPv6 is installed
if (supports_ipv6())
{
s = setup_listener(
tcp::endpoint(address_v6::any(), m_listen_interface.port())
setup_listener(&s, tcp::endpoint(address_v6::any(), m_listen_interface.port())
, m_listen_port_retries, true, flags, ec);
if (s.sock)
@ -1936,8 +1933,8 @@ namespace aux {
// we should only open a single listen socket, that
// binds to the given interface
listen_socket_t s = setup_listener(
m_listen_interface, m_listen_port_retries, false, flags, ec);
listen_socket_t s;
setup_listener(&s, m_listen_interface, m_listen_port_retries, false, flags, ec);
if (s.sock)
{
@ -4470,11 +4467,11 @@ namespace aux {
return m_listen_sockets.front().external_port;
}
void session_impl::announce_lsd(sha1_hash const& ih, bool broadcast)
void session_impl::announce_lsd(sha1_hash const& ih, int port, bool broadcast)
{
// use internal listen port for local peers
if (m_lsd.get())
m_lsd->announce(ih, m_listen_interface.port(), broadcast);
m_lsd->announce(ih, port, broadcast);
}
void session_impl::on_lsd_peer(tcp::endpoint peer, sha1_hash const& ih)

View File

@ -1264,15 +1264,290 @@ namespace libtorrent
#endif
/*
#ifdef TORRENT_USE_OPENSSL
/*
bool verify_function(bool preverified, boost::asio::ssl::verify_context& ctx)
{
return false;
}
#endif
*/
void torrent::init_ssl(std::string const& cert)
{
using boost::asio::ssl::context;
// this is needed for openssl < 1.0 to decrypt keys created by openssl 1.0+
OpenSSL_add_all_algorithms();
// TODO: come up with something better
RAND_seed(&info_hash()[0], 20);
TORRENT_ASSERT(RAND_status() == 1);
// create the SSL context for this torrent. We need to
// inject the root certificate, and no other, to
// verify other peers against
boost::shared_ptr<context> ctx(
new (std::nothrow) context(m_ses.m_io_service, context::sslv23));
if (!ctx)
{
set_error(asio::error::no_memory, "SSL context");
pause();
return;
}
ctx->set_options(context::default_workarounds
| boost::asio::ssl::context::no_sslv2
| boost::asio::ssl::context::single_dh_use);
error_code ec;
ctx->set_verify_mode(context::verify_peer
| context::verify_fail_if_no_peer_cert
| context::verify_client_once, ec);
if (ec)
{
set_error(ec, "SSL verify mode");
pause();
return;
}
// this is used for debugging
/*
#error there's a bug where the async_handshake on the ssl_stream always succeeds, regardless of the certificate failing. It's not a trivial bug in asio, that's been tested with a small repro program.
ctx->set_verify_callback(verify_function, ec);
if (ec)
{
set_error(ec, "SSL verify callback");
pause();
return;
}
*/
SSL_CTX* ssl_ctx = ctx->impl();
// create a new x.509 certificate store
X509_STORE* cert_store = X509_STORE_new();
if (!cert_store)
{
set_error(asio::error::no_memory, "x.509 certificate store");
pause();
return;
}
// wrap the PEM certificate in a BIO, for openssl to read
BIO* bp = BIO_new_mem_buf((void*)cert.c_str(), cert.size());
// parse the certificate into OpenSSL's internal
// representation
X509* certificate = PEM_read_bio_X509_AUX(bp, 0, 0, 0);
BIO_free(bp);
if (!certificate)
{
X509_STORE_free(cert_store);
set_error(asio::error::no_memory, "x.509 certificate");
pause();
return;
}
// add cert to cert_store
X509_STORE_add_cert(cert_store, certificate);
// and lastly, replace the default cert store with ours
SSL_CTX_set_cert_store(ssl_ctx, cert_store);
#if 0
char filename[100];
snprintf(filename, sizeof(filename), "/tmp/%d.pem", rand());
FILE* f = fopen(filename, "w+");
fwrite(cert.c_str(), cert.size(), 1, f);
fclose(f);
ctx->load_verify_file(filename);
#endif
// if all went well, set the torrent ssl context to this one
m_ssl_ctx = ctx;
// tell the client we need a cert for this torrent
alerts().post_alert(torrent_need_cert_alert(get_handle()));
m_ssl_acceptor.reset(new listen_socket_t);
m_ses.setup_listener(m_ssl_acceptor.get()
, tcp::endpoint(address_v4::any(), m_ses.m_listen_interface.port())
, m_ses.m_listen_port_retries + 10, false, 0, ec);
if (!m_ssl_acceptor->sock)
{
set_error(ec, "ssl listen port");
pause();
return;
}
// TODO: issue UPnP and NAT-PMP for this socket
async_accept(m_ssl_acceptor->sock);
set_allow_peers(false);
}
void torrent::async_accept(boost::shared_ptr<socket_acceptor> const& listener)
{
boost::shared_ptr<socket_type> c(new socket_type(m_ses.m_io_service));
c->instantiate<ssl_stream<stream_socket> >(m_ses.m_io_service, m_ssl_ctx.get());
#if defined TORRENT_ASIO_DEBUGGING
add_outstanding_async("torrent::on_accept_ssl_connection");
#endif
listener->async_accept(c->get<ssl_stream<stream_socket> >()->next_layer()
, boost::bind(&torrent::on_accept_ssl_connection, shared_from_this(), c
, boost::weak_ptr<socket_acceptor>(listener), _1));
}
void torrent::on_accept_ssl_connection(boost::shared_ptr<socket_type> const& s
, boost::weak_ptr<socket_acceptor> listen_socket, error_code const& e)
{
#if defined TORRENT_ASIO_DEBUGGING
complete_async("torrent::on_accept_ssl_connection");
#endif
// TODO: there's some code duplication with session_impl::on_accept_connection
boost::shared_ptr<socket_acceptor> listener = listen_socket.lock();
if (!listener) return;
if (e == asio::error::operation_aborted) return;
if (m_abort) return;
error_code ec;
if (e)
{
tcp::endpoint ep = listener->local_endpoint(ec);
#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING)
std::string msg = "error accepting connection on '"
+ print_endpoint(ep) + "' " + e.message();
(*m_ses.m_logger) << msg << "\n";
#endif
#ifdef TORRENT_WINDOWS
// Windows sometimes generates this error. It seems to be
// non-fatal and we have to do another async_accept.
if (e.value() == ERROR_SEM_TIMEOUT)
{
async_accept(listener);
return;
}
#endif
#ifdef TORRENT_BSD
// Leopard sometimes generates an "invalid argument" error. It seems to be
// non-fatal and we have to do another async_accept.
if (e.value() == EINVAL)
{
async_accept(listener);
return;
}
#endif
if (alerts().should_post<listen_failed_alert>())
alerts().post_alert(listen_failed_alert(ep, e));
return;
}
async_accept(listener);
if (is_paused()) return;
// we got a connection request!
tcp::endpoint endp = s->remote_endpoint(ec);
if (ec)
{
#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING)
(*m_ses.m_logger) << endp << " <== INCOMING CONNECTION FAILED, could "
"not retrieve remote endpoint " << ec.message() << "\n";
#endif
return;
}
if (!settings().enable_incoming_tcp)
{
if (alerts().should_post<peer_blocked_alert>())
alerts().post_alert(peer_blocked_alert(torrent_handle(), endp.address()));
return;
}
if (m_apply_ip_filter
&& m_ses.m_ip_filter.access(endp.address()) & ip_filter::blocked)
{
#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING)
(*m_ses.m_logger) << "filtered blocked ip\n";
#endif
if (alerts().should_post<peer_blocked_alert>())
alerts().post_alert(peer_blocked_alert(get_handle(), endp.address()));
return;
}
if (m_connections.size() >= m_max_connections)
{
if (alerts().should_post<peer_disconnected_alert>())
{
alerts().post_alert(
peer_disconnected_alert(get_handle(), endp, peer_id()
, error_code(errors::too_many_connections, get_libtorrent_category())));
}
return;
}
m_ses.setup_socket_buffers(*s);
s->get<ssl_stream<stream_socket> >()->async_accept_handshake(boost::bind(&torrent::ssl_handshake
, shared_from_this(), _1, s));
}
void torrent::ssl_handshake(error_code const& ec, boost::shared_ptr<socket_type> s)
{
error_code e;
tcp::endpoint endp = s->remote_endpoint(e);
if (e) return;
#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING)
(*m_ses.m_logger) << time_now_string() << " *** peer SSL handshake done [ ip: "
<< endp << " ec: " << ec.message() << "]\n";
#endif
if (ec)
{
if (alerts().should_post<peer_error_alert>())
{
alerts().post_alert(peer_error_alert(get_handle(), endp
, peer_id(), ec));
}
return;
}
boost::intrusive_ptr<peer_connection> c(
new bt_peer_connection(m_ses, shared_from_this(), s, endp, 0, false));
#if defined TORRENT_DEBUG || TORRENT_RELEASE_ASSERTS
c->m_in_constructor = false;
#endif
if (c->is_disconnecting()) return;
if (!m_policy.new_connection(*c, m_ses.session_time()))
{
#if defined TORRENT_LOGGING
(*m_ses.m_logger) << time_now_string() << " CLOSING CONNECTION "
<< p->remote() << " policy::new_connection returned false (i.e. peer list full)\n";
#endif
c->disconnect(errors::too_many_connections);
return;
}
// add the newly connected peer to this torrent's peer list
m_connections.insert(boost::get_pointer(c));
m_ses.m_connections.insert(c);
c->start();
if (settings().default_peer_upload_rate)
c->set_upload_limit(settings().default_peer_upload_rate);
if (settings().default_peer_download_rate)
c->set_download_limit(settings().default_peer_download_rate);
}
#endif
// this may not be called from a constructor because of the call to
// shared_from_this()
void torrent::init()
@ -1284,88 +1559,7 @@ namespace libtorrent
#ifdef TORRENT_USE_OPENSSL
std::string cert = m_torrent_file->ssl_cert();
if (!cert.empty())
{
using boost::asio::ssl::context;
// create the SSL context for this torrent. We need to
// inject the root certificate, and no other, to
// verify other peers against
boost::shared_ptr<context> ctx(
new (std::nothrow) context(m_ses.m_io_service, context::sslv23));
if (!ctx)
{
set_error(asio::error::no_memory, "SSL context");
pause();
return;
}
error_code ec;
ctx->set_verify_mode(context::verify_peer
| context::verify_fail_if_no_peer_cert, ec);
if (ec)
{
set_error(ec, "SSL context");
pause();
return;
}
// this is used for debugging
/*
#error there's a bug where the async_handshake on the ssl_stream always succeeds, regardless of the certificate failing. It's not a trivial bug in asio, that's been tested with a small repro program.
ctx->set_verify_callback(verify_function, ec);
if (ec)
{
set_error(ec, "SSL verify callback");
pause();
return;
}
*/
SSL_CTX* ssl_ctx = ctx->impl();
// create a new x.509 certificate store
X509_STORE* cert_store = X509_STORE_new();
if (!cert_store)
{
set_error(asio::error::no_memory, "x.509 certificate store");
pause();
return;
}
// wrap the PEM certificate in a BIO, for openssl to read
BIO* bp = BIO_new_mem_buf((void*)cert.c_str(), cert.size());
// parse the certificate into OpenSSL's internal
// representation
X509* certificate = PEM_read_bio_X509_AUX(bp, 0, 0, 0);
BIO_free(bp);
if (!certificate)
{
X509_STORE_free(cert_store);
set_error(asio::error::no_memory, "x.509 certificate");
pause();
return;
}
// add cert to cert_store
X509_STORE_add_cert(cert_store, certificate);
// and lastly, replace the default cert store with ours
SSL_CTX_set_cert_store(ssl_ctx, cert_store);
#if 0
char filename[100];
snprintf(filename, sizeof(filename), "/tmp/%d.pem", rand());
FILE* f = fopen(filename, "w+");
fwrite(cert.c_str(), cert.size(), 1, f);
fclose(f);
ctx->load_verify_file(filename);
#endif
// if all went well, set the torrent ssl context to this one
m_ssl_ctx = ctx;
}
if (!cert.empty()) init_ssl(cert);
#endif
#ifdef TORRENT_USE_OPENSSL
@ -1968,8 +2162,14 @@ namespace libtorrent
if (is_paused()) return;
#ifdef TORRENT_USE_OPENSSL
int port = is_ssl_torrent() ? m_ssl_acceptor->external_port : m_ses.listen_port();
#else
int port = m_ses.listen_port();
#endif
// announce with the local discovery service
m_ses.announce_lsd(m_torrent_file->info_hash()
m_ses.announce_lsd(m_torrent_file->info_hash(), port
, m_ses.settings().broadcast_lsd && m_lsd_seq == 0);
++m_lsd_seq;
}
@ -1984,9 +2184,15 @@ namespace libtorrent
TORRENT_ASSERT(m_allow_peers);
#ifdef TORRENT_USE_OPENSSL
int port = is_ssl_torrent() ? m_ssl_acceptor->external_port : m_ses.listen_port();
#else
int port = m_ses.listen_port();
#endif
boost::weak_ptr<torrent> self(shared_from_this());
m_ses.m_dht->announce(m_torrent_file->info_hash()
, m_ses.listen_port(), is_seed()
, port, is_seed()
, boost::bind(&torrent::on_dht_announce_response_disp, self, _1));
}
@ -2073,6 +2279,14 @@ namespace libtorrent
req.num_want = (req.event == tracker_request::stopped)
?0:settings().num_want;
// SSL torrents use their own listen socket
#ifdef TORRENT_USE_OPENSSL
// TODO: this pattern is repeated in a few places. Factor this into
// a function and generalize the concept of a torrent having a
// dedicated listen port
if (is_ssl_torrent()) req.listen_port = m_ssl_acceptor->external_port;
else
#endif
req.listen_port = m_ses.listen_port();
req.key = m_ses.m_key;
@ -3207,6 +3421,12 @@ namespace libtorrent
m_ses.m_encrypted_torrents.erase(shared_from_this());
m_in_encrypted_list = false;
}
if (m_ssl_acceptor && m_ssl_acceptor->sock)
{
error_code ec;
m_ssl_acceptor->sock->close(ec);
}
#endif
m_abort = true;
@ -3973,22 +4193,51 @@ namespace libtorrent
}
#ifdef TORRENT_USE_OPENSSL
// certificate is a filename to a .pem file which is our
// certificate. root_cert is a filename to a certificate
// on disk which is the trusted certificate authority (CA)
// for this torrent. 'certificate' must be signed by the
// 'root_cert', and any peer we connect to or that connect
// to use must present a valid certificate signed by 'root_cert'
void torrent::set_ssl_cert(std::string const& certificate, error_code& ec)
std::string password_callback(int length, boost::asio::ssl::context::password_purpose p
, std::string pw)
{
ec.clear();
if (!m_ssl_ctx)
{
ec = asio::error::operation_not_supported;
return;
}
if (p != boost::asio::ssl::context::for_reading) return "";
return pw;
}
// certificate is a filename to a .pem file which is our
// certificate. The certificate must be signed by the root
// cert of the torrent file. any peer we connect to or that
// connect to use must present a valid certificate signed
// by the torrent root cert as well
void torrent::set_ssl_cert(std::string const& certificate
, std::string const& private_key
, std::string const& dh_params
, std::string const& passphrase)
{
if (!m_ssl_ctx) return;
using boost::asio::ssl::context;
error_code ec;
m_ssl_ctx->set_password_callback(boost::bind(&password_callback, _1, _2, passphrase), ec);
if (ec)
{
if (alerts().should_post<torrent_error_alert>())
alerts().post_alert(torrent_error_alert(get_handle(), ec));
}
m_ssl_ctx->use_certificate_file(certificate, context::pem, ec);
if (ec)
{
if (alerts().should_post<torrent_error_alert>())
alerts().post_alert(torrent_error_alert(get_handle(), ec));
}
m_ssl_ctx->use_private_key_file(private_key, context::pem, ec);
if (ec)
{
if (alerts().should_post<torrent_error_alert>())
alerts().post_alert(torrent_error_alert(get_handle(), ec));
}
m_ssl_ctx->use_tmp_dh_file(dh_params, ec);
if (ec)
{
if (alerts().should_post<torrent_error_alert>())
alerts().post_alert(torrent_error_alert(get_handle(), ec));
}
}
#endif
@ -4022,7 +4271,7 @@ namespace libtorrent
if (m_picker.get())
{
bitfield const& pieces = p->get_bitfield();
TORRENT_ASSERT(pieces.count() < int(pieces.size()));
TORRENT_ASSERT(pieces.count() <= int(pieces.size()));
m_picker->dec_refcount(pieces);
}
}
@ -5159,6 +5408,9 @@ namespace libtorrent
}
#endif
// extend connect timeout by this many seconds
int timeout_extend = 0;
TORRENT_ASSERT(want_more_peers() || ignore_limit);
TORRENT_ASSERT(m_ses.num_connections() < m_ses.settings().connections_limit || ignore_limit);
@ -5178,6 +5430,8 @@ namespace libtorrent
s->get<i2p_stream>()->set_destination(static_cast<policy::i2p_peer*>(peerinfo)->destination);
s->get<i2p_stream>()->set_command(i2p_stream::cmd_connect);
s->get<i2p_stream>()->set_session_id(m_ses.m_i2p_conn.session_id());
// i2p setups are slow
timeout_extend = 20;
}
else
#endif
@ -5196,7 +5450,20 @@ namespace libtorrent
// don't make a TCP connection if it's disabled
if (sm == 0 && !m_ses.m_settings.enable_outgoing_tcp) return false;
bool ret = instantiate_connection(m_ses.m_io_service, m_ses.proxy(), *s, 0, sm, true);
void* userdata = 0;
#ifdef TORRENT_USE_OPENSSL
if (is_ssl_torrent())
{
userdata = m_ssl_ctx.get();
// SSL handshakes are slow
timeout_extend = 10;
// we don't support SSL over uTP yet
sm = 0;
}
#endif
bool ret = instantiate_connection(m_ses.m_io_service, m_ses.proxy(), *s, userdata, sm, true);
(void)ret;
TORRENT_ASSERT(ret);
}
@ -5239,6 +5506,7 @@ namespace libtorrent
int timeout = settings().peer_connect_timeout;
if (peerinfo) timeout += 3 * peerinfo->failcount;
timeout += timeout_extend;
TORRENT_TRY
{
@ -5379,6 +5647,7 @@ namespace libtorrent
(*m_ses.m_logger) << time_now_string() << " CLOSING CONNECTION "
<< p->remote() << " policy::new_connection returned false (i.e. peer list full)\n";
#endif
p->disconnect(errors::too_many_connections);
return false;
}
}
@ -7675,6 +7944,11 @@ namespace libtorrent
st->handle = get_handle();
st->info_hash = info_hash();
st->listen_port = 0;
#ifdef TORRENT_USE_OPENSSL
if (is_ssl_torrent() && m_ssl_acceptor) st->listen_port = m_ssl_acceptor->external_port;
#endif
st->has_incoming = m_has_incoming;
if (m_error) st->error = m_error.message() + ": " + m_error_file;
st->seed_mode = m_seed_mode;

View File

@ -112,6 +112,12 @@ namespace libtorrent
session_impl& ses = t->session(); \
ses.m_io_service.post(boost::bind(&torrent:: x, t, a1, a2, a3))
#define TORRENT_ASYNC_CALL4(x, a1, a2, a3, a4) \
boost::shared_ptr<torrent> t = m_torrent.lock(); \
if (!t) return; \
session_impl& ses = t->session(); \
ses.m_io_service.post(boost::bind(&torrent:: x, t, a1, a2, a3, a4))
#define TORRENT_SYNC_CALL(x) \
boost::shared_ptr<torrent> t = m_torrent.lock(); \
if (!t) return; \
@ -375,13 +381,14 @@ namespace libtorrent
TORRENT_ASYNC_CALL(flush_cache);
}
void torrent_handle::set_ssl_certificates(
std::string const& certificate, error_code& ec)
void torrent_handle::set_ssl_certificate(
std::string const& certificate
, std::string const& private_key
, std::string const& dh_params
, std::string const& passphrase)
{
#ifdef TORRENT_USE_OPENSSL
TORRENT_ASYNC_CALL2(set_ssl_cert, certificate, boost::ref(ec));
#else
ec = boost::asio::error::operation_not_supported;
TORRENT_ASYNC_CALL4(set_ssl_cert, certificate, private_key, dh_params, passphrase);
#endif
}