merged uTP branch into trunk (yay)

This commit is contained in:
Arvid Norberg 2010-11-29 01:33:05 +00:00
parent 5a1669cf03
commit e5f980d80d
77 changed files with 7497 additions and 630 deletions

View File

@ -31,6 +31,7 @@ set(sources
http_seed_connection
instantiate_connection
natpmp
packet_buffer
piece_picker
policy
puff
@ -206,6 +207,7 @@ endif(MSVC)
add_definitions(-D_FILE_OFFSET_BITS=64)
add_definitions(-DBOOST_DISABLE_EXCEPTION)
add_definitions(-DBOOST_ASIO_ENABLE_CANCELIO)
if (tcmalloc)
target_link_libraries(torrent-rasterbar tcmalloc)

View File

@ -4,6 +4,9 @@
* support trackerid tracker extension
* graceful peer disconnect mode which finishes transactions before disconnecting peers
* support chunked encoding for web seeds
* uTP protocol support
* resistance towards certain flood attacks
* support chunked encoding for web seeds (only for BEP 19, web seeds)
* optimized session startup time
* support SSL for web seeds, through all proxies
* support extending web seeds with custom authorization and extra headers

10
Jamfile
View File

@ -81,8 +81,9 @@ rule linking ( properties * )
{
result += <library>ws2_32
<library>wsock32
<library>iphlpapi
<define>WIN32_LEAN_AND_MEAN
<define>_WIN32_WINNT=0x0500
<define>_WIN32_WINNT=0x0600
<define>__USE_W32_SOCKETS
<define>WIN32
<define>_WIN32
@ -353,6 +354,7 @@ lib GeoIP : : <name>GeoIP <link>shared ;
# socket libraries on windows
lib wsock32 : : <name>wsock32 <link>shared ;
lib ws2_32 : : <name>ws2_32 <link>shared ;
lib iphlpapi : : <name>iphlpapi <link>shared ;
SOURCES =
alert
@ -384,6 +386,7 @@ SOURCES =
i2p_stream
instantiate_connection
natpmp
packet_buffer
piece_picker
policy
puff
@ -403,8 +406,11 @@ SOURCES =
http_tracker_connection
udp_tracker_connection
sha1
timestamp_history
udp_socket
upnp
utp_socket_manager
utp_stream
logger
file_pool
lsd
@ -443,6 +449,8 @@ local usage-requirements =
<variant>debug:<define>TORRENT_DEBUG
<define>_FILE_OFFSET_BITS=64
<define>BOOST_EXCEPTION_DISABLE
# enable cancel support in asio
<define>BOOST_ASIO_ENABLE_CANCELIO
<conditional>@linking
# these compiler settings just makes the compiler standard conforming
<toolset>msvc:<cflags>/Zc:wchar_t

View File

@ -593,6 +593,9 @@ COMPILETIME_OPTIONS+="-DBOOST_ASIO_HASH_MAP_BUCKETS=1021 "
AC_DEFINE([BOOST_EXCEPTION_DISABLE],[1],[Define to disable the boost.exception features.])
COMPILETIME_OPTIONS+="-DBOOST_EXCEPTION_DISABLE "
AC_DEFINE([BOOST_ASIO_ENABLE_CANCELIO],[1],[Define to enable cancel support in asio on windows XP and older.])
COMPILETIME_OPTIONS+="-DBOOST_ASIO_ENABLE_CANCELIO "
dnl Use possibly specific python install params
AC_ARG_VAR([PYTHON_INSTALL_PARAMS], [Set specific install parameters for python bindings.])
AS_IF([test "x$PYTHON_INSTALL_PARAMS" = "x"],

BIN
docs/cwnd.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
docs/cwnd_thumb.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
docs/delays.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
docs/delays_thumb.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -56,12 +56,13 @@
<li><a class="reference internal" href="#high-performance-disk-subsystem" id="id9">high performance disk subsystem</a></li>
<li><a class="reference internal" href="#network-buffers" id="id10">network buffers</a></li>
<li><a class="reference internal" href="#piece-picker" id="id11">piece picker</a></li>
<li><a class="reference internal" href="#merkle-hash-tree-torrents" id="id12">merkle hash tree torrents</a></li>
<li><a class="reference internal" href="#customizable-file-storage" id="id13">customizable file storage</a></li>
<li><a class="reference internal" href="#easy-to-use-api" id="id14">easy to use API</a></li>
<li><a class="reference internal" href="#share-mode" id="id12">share mode</a></li>
<li><a class="reference internal" href="#merkle-hash-tree-torrents" id="id13">merkle hash tree torrents</a></li>
<li><a class="reference internal" href="#customizable-file-storage" id="id14">customizable file storage</a></li>
<li><a class="reference internal" href="#easy-to-use-api" id="id15">easy to use API</a></li>
</ul>
</li>
<li><a class="reference internal" href="#portability" id="id15">portability</a></li>
<li><a class="reference internal" href="#portability" id="id16">portability</a></li>
</ul>
</div>
<div class="section" id="introduction">
@ -97,9 +98,12 @@ uTorrent interpretation).</li>
<li>supports the <tt class="docutils literal"><span class="pre">compact=1</span></tt> tracker parameter.</li>
<li>super seeding/initial seeding (<a class="reference external" href="http://bittorrent.org/beps/bep_0016.html">BEP 16</a>).</li>
<li>private torrents (<a class="reference external" href="http://bittorrent.org/beps/bep_0027.html">BEP 27</a>).</li>
<li>upload-only extension (<a class="reference external" href="http://bittorrent.org/beps/bep_0021.html">BEP 21</a>).</li>
<li>support for IPv6, including <a class="reference external" href="http://bittorrent.org/beps/bep_0007.html">BEP 7</a> and <a class="reference external" href="http://bittorrent.org/beps/bep_0024.html">BEP 24</a>.</li>
<li>support for merkle hash tree torrents. This makes the size of torrent files
scale well with the size of the content.</li>
<li>share-mode. This is a special mode torrents can be put in to optimize share
ratio rather than downloading the torrent.</li>
</ul>
</div>
<div class="section" id="disk-management">
@ -124,6 +128,8 @@ piece's hash is verified the first time it is requested.</li>
<div class="section" id="network">
<h2>network</h2>
<ul class="simple">
<li>a high quality uTP implementation (<a href="#id17"><span class="problematic" id="id18">BEP29_</span></a>). A transport protocol with
delay based congestion control. See separate <a class="reference external" href="utp.html">article</a>.</li>
<li>adjusts the length of the request queue depending on download rate.</li>
<li>serves multiple torrents on a single port and in a single thread</li>
<li>piece picking on block-level (as opposed to piece-level).
@ -253,6 +259,15 @@ makes slow peers pick blocks from the same piece, and fast peers pick from the s
and hence decreasing the likelihood of slow peers blocking the completion of pieces.</p>
<p>The piece picker can also be set to download pieces in sequential order.</p>
</div>
<div class="section" id="share-mode">
<h2>share mode</h2>
<p>The share mode feature in libtorrent is intended for users who are only interested in
helping out swarms, not downloading the torrents.</p>
<p>It works by predicting the demand for pieces, and only download pieces if there is enough
demand. New pieces will only be downloaded once the share ratio has hit a certain target.</p>
<p>This feature is especially useful when combined with RSS, so that a client can be set up
to provide additional bandwidth to an entire feed.</p>
</div>
<div class="section" id="merkle-hash-tree-torrents">
<h2>merkle hash tree torrents</h2>
<p>Merkle hash tree torrents is an extension that lets a torrent file only contain the
@ -363,6 +378,12 @@ epoll on linux and kqueue on MacOS X and BSD.</p>
<li>GCC 2.95.4</li>
<li>msvc6</li>
</ul>
</div>
<div class="system-messages section">
<h1>Docutils System Messages</h1>
<div class="system-message" id="id17">
<p class="system-message-title">System Message: ERROR/3 (<tt class="docutils">features.rst</tt>, line 82); <em><a href="#id18">backlink</a></em></p>
Unknown target name: &quot;bep29&quot;.</div>
</div>
</div>
<div id="footer">

View File

@ -52,6 +52,7 @@ extensions
* share-mode. This is a special mode torrents can be put in to optimize share
ratio rather than downloading the torrent.
.. _article: utp.html
.. _extensions: manual.html#extensions
.. _`http seeding`: manual.html#http-seeding
@ -78,6 +79,8 @@ disk management
network
-------
* a high quality uTP implementation (BEP29_). A transport protocol with
delay based congestion control. See separate article_.
* adjusts the length of the request queue depending on download rate.
* serves multiple torrents on a single port and in a single thread
* piece picking on block-level (as opposed to piece-level).
@ -116,6 +119,7 @@ network
.. _`BEP 21`: http://bittorrent.org/beps/bep_0021.html
.. _`BEP 24`: http://bittorrent.org/beps/bep_0024.html
.. _`BEP 27`: http://bittorrent.org/beps/bep_0027.html
.. _`BEP 29`: http://bittorrent.org/beps/bep_0029.html
.. _`extension protocol`: extension_protocol.html
highlighted features

View File

@ -51,6 +51,7 @@
<hr class="docutils" />
<p>Extensions</p>
<ul class="simple">
<li><a class="reference external" href="utp.html">uTP</a></li>
<li><a class="reference external" href="extension_protocol.html">extensions protocol</a></li>
<li><a class="reference external" href="libtorrent_plugins.html">plugin interface</a></li>
<li><a class="reference external" href="dht_extensions.html">DHT extensions</a></li>

View File

@ -22,6 +22,7 @@
Extensions
* `uTP`_
* `extensions protocol`_
* `plugin interface`_
* `DHT extensions`_
@ -59,6 +60,7 @@ libtorrent
.. _`running tests`: running_tests.html
.. _`tuning`: tuning.html
.. _screenshot: client_test.png
.. _`uTP`: utp.html
.. _`extensions protocol`: extension_protocol.html
.. _`plugin interface`: libtorrent_plugins.html
.. _`DHT extensions`: dht_extensions.html

View File

@ -37,6 +37,8 @@
<tbody valign="top">
<tr><th class="docinfo-name">Author:</th>
<td>Arvid Norberg, <a class="last reference external" href="mailto:arvid&#64;rasterbar.com">arvid&#64;rasterbar.com</a></td></tr>
<tr><th class="docinfo-name">Version:</th>
<td>0.16.0</td></tr>
</tbody>
</table>
<div class="contents topic" id="table-of-contents">
@ -58,7 +60,7 @@
<li><a class="reference internal" href="#set-creator" id="id14">set_creator()</a></li>
<li><a class="reference internal" href="#set-hash" id="id15">set_hash()</a></li>
<li><a class="reference internal" href="#set-file-hash" id="id16">set_file_hash()</a></li>
<li><a class="reference internal" href="#add-url-seed" id="id17">add_url_seed()</a></li>
<li><a class="reference internal" href="#add-url-seed-add-http-seed" id="id17">add_url_seed() add_http_seed()</a></li>
<li><a class="reference internal" href="#add-node" id="id18">add_node()</a></li>
<li><a class="reference internal" href="#add-tracker" id="id19">add_tracker()</a></li>
<li><a class="reference internal" href="#set-priv-priv" id="id20">set_priv() priv()</a></li>
@ -291,6 +293,7 @@ struct create_torrent
void set_hash(int index, sha1_hash const&amp; h);
void set_file_hash(int index, sha1_hash const&amp; h);
void add_url_seed(std::string const&amp; url);
void add_http_seed(std::string const&amp; url);
void add_node(std::pair&lt;std::string, int&gt; const&amp; node);
void add_tracker(std::string const&amp; url, int tier = 0);
void set_priv(bool p);
@ -433,11 +436,12 @@ void set_file_hash(int index, sha1_hash const&amp; h);
associated with this file (for multi-file torrents) or in the root info dictionary
for single-file torrents.</p>
</div>
<div class="section" id="add-url-seed">
<h2>add_url_seed()</h2>
<div class="section" id="add-url-seed-add-http-seed">
<h2>add_url_seed() add_http_seed()</h2>
<blockquote>
<pre class="literal-block">
void add_url_seed(std::string const&amp; url);
void add_http_seed(std::string const&amp; url);
</pre>
</blockquote>
<p>This adds a url seed to the torrent. You can have any number of url seeds. For a
@ -445,6 +449,7 @@ single file torrent, this should be an HTTP url, pointing to a file with identic
content as the file of the torrent. For a multi-file torrent, it should point to
a directory containing a directory with the same name as this torrent, and all the
files of the torrent in it.</p>
<p>The second function, <tt class="docutils literal"><span class="pre">add_http_seed()</span></tt> adds an HTTP seed instead.</p>
</div>
<div class="section" id="add-node">
<h2>add_node()</h2>

View File

@ -3,6 +3,7 @@ creating torrents
=================
:Author: Arvid Norberg, arvid@rasterbar.com
:Version: 0.16.0
.. contents:: Table of contents
:depth: 2

View File

@ -17,6 +17,7 @@ TARGETS = index \
python_binding \
projects \
running_tests \
utp \
tuning
FIGURES = read_disk_buffers write_disk_buffers

File diff suppressed because it is too large Load Diff

View File

@ -618,6 +618,15 @@ struct has the following members::
int branch_factor;
};
struct utp_status
{
int num_idle;
int num_syn_sent;
int num_connected;
int num_fin_sent;
int num_close_wait;
};
struct session_status
{
bool has_incoming_connections;
@ -663,6 +672,8 @@ struct has the following members::
size_type dht_global_nodes;
std::vector<dht_lookup> active_requests;
int dht_total_allocations;
utp_status utp_stats;
};
``has_incoming_connections`` is false as long as no incoming connections have been
@ -730,6 +741,8 @@ network.
particular DHT lookup. This represents roughly the amount of memory used
by the DHT.
``utp_stats`` contains statistics on the uTP sockets.
get_cache_status()
------------------
@ -1074,7 +1087,6 @@ struct has the following members::
{
int max_peers_reply;
int search_branching;
int service_port;
int max_fail_count;
};
@ -1085,19 +1097,18 @@ response to a ``get_peers`` message from another node.
send when announcing and refreshing the routing table. This parameter is
called alpha in the kademlia paper.
``service_port`` is the udp port the node will listen to. This will default
to 0, which means the udp listen port will be the same as the tcp listen
port. This is in general a good idea, since some NAT implementations
reserves the udp port for any mapped tcp port, and vice versa. NAT-PMP
guarantees this for example.
``max_fail_count`` is the maximum number of failed tries to contact a node
before it is removed from the routing table. If there are known working nodes
that are ready to replace a failing node, it will be replaced immediately,
this limit is only used to clear out nodes that don't have any node that can
replace them.
``is_dht_running`` returns true if the DHT support has been started and false
The ``dht_settings`` struct used to contain a ``service_port`` member to control
which port the DHT would listen on and send messages from. This field is deprecated
and ignored. libtorrent always tries to open the UDP socket on the same port
as the TCP socket.
``is_dht_running()`` returns true if the DHT support has been started and false
otherwise.
@ -3387,6 +3398,7 @@ It contains the following fields::
optimistic_unchoke = 0x800,
snubbed = 0x1000,
upload_only = 0x2000,
holepunched = 0x4000,
rc4_encrypted = 0x100000,
plaintext_encrypted = 0x200000
};
@ -3535,6 +3547,11 @@ any combination of the enums above. The following table describes each flag:
| | will not downloading anything more, regardless of |
| | which pieces we have. |
+-------------------------+-------------------------------------------------------+
| ``holepunched`` | This flag is set if the peer was in holepunch mode |
| | when the connection succeeded. This typically only |
| | happens if both peers are behind a NAT and the peers |
| | connect via the NAT holepunch mechanism. |
+-------------------------+-------------------------------------------------------+
__ extension_protocol.html
@ -3673,8 +3690,19 @@ that may give away something about which software is running in the other end.
In the case of a web seed, the server type and version will be a part of this
string.
``connection_type`` can currently be one of ``standard_bittorrent`` or
``web_seed``. These are currently the only implemented protocols.
``connection_type`` can currently be one of:
+---------------------------------------+-------------------------------------------------------+
| type | meaning |
+=======================================+=======================================================+
| ``peer_info::standard_bittorrent`` | Regular bittorrent connection over TCP |
+---------------------------------------+-------------------------------------------------------+
| ``peer_info::bittorrent_utp`` | Bittorrent connection over uTP |
+---------------------------------------+-------------------------------------------------------+
| ``peer_info::web_sesed`` | HTTP connection using the `BEP 19`_ protocol |
+---------------------------------------+-------------------------------------------------------+
| ``peer_info::http_seed`` | HTTP connection using the `BEP 17`_ protocol |
+---------------------------------------+-------------------------------------------------------+
``remote_dl_rate`` is an estimate of the rate this peer is downloading at, in
bytes per second.
@ -3927,10 +3955,17 @@ session_settings
int default_peer_upload_rate;
int default_peer_download_rate;
bool broadcast_lsd;
bool enable_outgoing_utp;
bool enable_incoming_utp;
bool enable_outgoing_tcp;
bool enable_incoming_tcp;
int max_pex_peers;
bool ignore_resume_timestamps;
bool anonymous_mode;
int tick_interval;
int share_mode_target;
int upload_rate_limit;
int download_rate_limit;
int local_upload_rate_limit;
@ -3939,6 +3974,24 @@ session_settings
int half_open_limit;
int connections_limit;
int utp_target_delay;
int utp_gain_factor;
int utp_min_timeout;
int utp_syn_resends;
int utp_num_resends;
int utp_connect_timeout;
int utp_delayed_ack;
bool utp_dynamic_sock_buf;
enum bandwidth_mixed_algo_t
{
prefer_tcp = 0,
peer_proportional = 1
};
int mixed_mode_algorithm;
bool rate_limit_utp;
int listen_queue_size;
};
@ -4604,6 +4657,11 @@ if ``broadcast_lsd`` is set to true, the local peer discovery
broadcast its messages. This can be useful when running on networks
that don't support multicast. It's off by default since it's inefficient.
``enable_outgoing_utp``, ``enable_incoming_utp``, ``enable_outgoing_tcp``,
``enable_incoming_tcp`` all determines if libtorrent should attempt to make
outgoing connections of the specific type, or allow incoming connection. By
default all of them are enabled.
``ignore_resume_timestamps`` determines if the storage, when loading
resume data files, should verify that the file modification time
with the timestamps in the resume data. This defaults to false, which
@ -4673,6 +4731,58 @@ opened. The number of connections is set to a hard minimum of at least two per
torrent, so if you set a too low connections limit, and open too many torrents,
the limit will not be met.
``utp_target_delay`` is the target delay for uTP sockets in milliseconds. A high
value will make uTP connections more aggressive and cause longer queues in the upload
bottleneck. It cannot be too low, since the noise in the measurements would cause
it to send too slow. The default is 50 milliseconds.
``utp_gain_factor`` is the number of bytes the uTP congestion window can increase
at the most in one RTT. This defaults to 300 bytes. If this is set too high,
the congestion controller reacts too hard to noise and will not be stable, if it's
set too low, it will react slow to congestion and not back off as fast.
``utp_min_timeout`` is the shortest allowed uTP socket timeout, specified in milliseconds.
This defaults to 500 milliseconds. The timeout depends on the RTT of the connection, but
is never smaller than this value. A connection times out when every packet in a window
is lost, or when a packet is lost twice in a row (i.e. the resent packet is lost as well).
The shorter the timeout is, the faster the connection will recover from this situation,
assuming the RTT is low enough.
``utp_syn_resends`` is the number of SYN packets that are sent (and timed out) before
giving up and closing the socket.
``utp_num_resends`` is the number of times a packet is sent (and lossed or timed out)
before giving up and closing the connection.
``utp_connect_timeout`` is the number of milliseconds of timeout for the initial SYN
packet for uTP connections. For each timed out packet (in a row), the timeout is doubled.
``utp_delayed_ack`` is the number of milliseconds to delay ACKs the most. Delaying ACKs
significantly helps reducing the amount of protocol overhead in the reverse direction
from downloads. It defaults to 100 milliseconds. If set to 0, delayed ACKs are disabled
and every incoming payload packet is ACKed. The granularity of this timer is capped by
the tick interval (as specified by ``tick_interval``).
``utp_dynamic_sock_buf`` controls if the uTP socket manager is allowed to increase
the socket buffer if a network interface with a large MTU is used (such as loopback
or ethernet jumbo frames). This defaults to true and might improve uTP throughput.
For RAM constrained systems, disabling this typically saves around 30kB in user space
and probably around 400kB in kernel socket buffers (it adjusts the send and receive
buffer size on the kernel socket, both for IPv4 and IPv6).
The ``mixed_mode_algorithm`` determines how to treat TCP connections when there are
uTP connections. Since uTP is designed to yield to TCP, there's an inherent problem
when using swarms that have both TCP and uTP connections. If nothing is done, uTP
connections would often be starved out for bandwidth by the TCP connections. This mode
is ``prefer_tcp``. The ``peer_proportional`` mode simply looks at the current throughput
and rate limits all TCP connections to their proportional share based on how many of
the connections are TCP. This works best if uTP connections are not rate limited by
the global rate limiter (which they aren't by default).
``rate_limit_utp`` determines if uTP connections should be throttled by the global rate
limiter or not. By default they are not, since uTP manages its own rate.
``listen_queue_size`` is the value passed in to listen() for the listen socket.
It is the number of outstanding incoming connections to queue up while we're not
actively waiting for a connection to be accepted. The default is 5 which should
@ -6797,6 +6907,9 @@ code symbol description
106 invalid_pex_message The peer sent an invalid peer exchange message
------ ----------------------------------------- -----------------------------------------------------------------
107 invalid_lt_tracker_message The peer sent an invalid tracker exchange message
------ ----------------------------------------- -----------------------------------------------------------------
108 too_frequent_pex The peer sent an pex messages too often. This is a possible
attempt of and attack
====== ========================================= =================================================================
NAT-PMP errors:

BIN
docs/our_delay_base.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View File

@ -203,6 +203,16 @@ of C++ exceptions. By disabling exceptions (-fno-exceptions on GCC), you can
reduce the executable size with up to 45%. In order to build without exception
support, you need to patch parts of boost.</p>
<p>Also make sure to optimize for size when compiling.</p>
<p>Another way of reducing the executable size is to disable code that isn't used.
There are a number of <tt class="docutils literal"><span class="pre">TORRENT_*</span></tt> macros that control which features are included
in libtorrent. If these macros are used to strip down libtorrent, make sure the same
macros are defined when building libtorrent as when linking against it. If these
are different the structures will look different from the libtorrent side and from
the client side and memory corruption will follow.</p>
<p>One, probably, safe macro to define is <tt class="docutils literal"><span class="pre">TORRENT_NO_DEPRECATE</span></tt> which removes all
deprecated functions and struct members. As long as no deprecated functions are
relied upon, this should be a simple way to eliminate a little bit of code.</p>
<p>For all available options, see the <a class="reference external" href="building.html">building libtorrent</a> secion.</p>
</div>
<div class="section" id="reduce-statistics">
<h2>reduce statistics</h2>

342
docs/utp.html Normal file
View File

@ -0,0 +1,342 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.5: http://docutils.sourceforge.net/" />
<title>libtorrent manual</title>
<meta name="author" content="Arvid Norberg, arvid&#64;rasterbar.com" />
<link rel="stylesheet" type="text/css" href="../../css/base.css" />
<link rel="stylesheet" type="text/css" href="../../css/rst.css" />
<link rel="stylesheet" href="style.css" type="text/css" />
<style type="text/css">
/* Hides from IE-mac \*/
* html pre { height: 1%; }
/* End hide from IE-mac */
</style>
</head>
<body>
<div class="document" id="libtorrent-manual">
<div id="container">
<div id="headerNav">
<ul>
<li class="first"><a href="/">Home</a></li>
<li><a href="../../products.html">Products</a></li>
<li><a href="../../contact.html">Contact</a></li>
</ul>
</div>
<div id="header">
<h1><span>Rasterbar Software</span></h1>
<h2><span>Software developement and consulting</span></h2>
</div>
<div id="main">
<h1 class="title">libtorrent manual</h1>
<table class="docinfo" frame="void" rules="none">
<col class="docinfo-name" />
<col class="docinfo-content" />
<tbody valign="top">
<tr><th class="docinfo-name">Author:</th>
<td>Arvid Norberg, <a class="last reference external" href="mailto:arvid&#64;rasterbar.com">arvid&#64;rasterbar.com</a></td></tr>
<tr><th class="docinfo-name">Version:</th>
<td>0.16.0</td></tr>
</tbody>
</table>
<div class="contents topic" id="table-of-contents">
<p class="topic-title first">Table of contents</p>
<ul class="simple">
<li><a class="reference internal" href="#utp" id="id1">uTP</a><ul>
<li><a class="reference internal" href="#rationale" id="id2">rationale</a></li>
<li><a class="reference internal" href="#tcp" id="id3">TCP</a></li>
<li><a class="reference internal" href="#ledbat-congestion-controller" id="id4">LEDBAT congestion controller</a></li>
<li><a class="reference internal" href="#one-way-delays" id="id5">one way delays</a></li>
<li><a class="reference internal" href="#path-mtu-discovery" id="id6">Path MTU discovery</a></li>
<li><a class="reference internal" href="#clock-drift" id="id7">clock drift</a></li>
<li><a class="reference internal" href="#features" id="id8">features</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="utp">
<h1>uTP</h1>
<p>uTP (uTorrent transport protocol) is a transport protocol which uses one-way
delay measurements for its congestion controller. This article is about uTP
in general and specifically about libtorrent's implementation of it.</p>
<div class="section" id="rationale">
<h2>rationale</h2>
<p>One of the most common problems users are experiencing using bittorrent is
that their internet &quot;stops working&quot;. This can be caused by a number of things,
for example:</p>
<ol class="arabic simple">
<li>a home router that crashes or slows down when its NAT pin-hole
table overflows, triggered by DHT or simply many TCP connections.</li>
<li>a home router that crashes or slows down by UDP traffic (caused by
the DHT)</li>
<li>a home DSL or cable modem having its send buffer filled up by outgoing
data, and the buffer fits seconds worth of bytes. This adds seconds
of delay on interactive traffic. For a web site that needs 10 round
trips to load this may mean 10s of seconds of delay to load compared
to without bittorrent. Skype or other delay sensitive applications
would be affected even more.</li>
</ol>
<p>This document will cover (3).</p>
<p>Typically this is solved by asking the user to enter a number of bytes
that the client is allowed to send per second (i.e. setting an upload
rate limit). The common recommendation is to set this limit to 80% of the
uplink's capacity. This is to leave some headroom for things like TCP
ACKs as well as the user's interactive use of the connection such as
browsing the web or checking email.</p>
<p>There are two major drawbacks with this technique:</p>
<ol class="arabic simple">
<li>The user needs to actively make this setting (very few protocols
require the user to provide this sort of information). This also
means the user needs to figure out what its up-link capacity is.
This is unfortunately a number that many ISPs are not advertizing
(because it's often much lower than the download capacity) which
might make it hard to find.</li>
<li>The 20% headroom is wasted most of the time. Whenever the user
is not using the internet connection for anything, those extra 20%
could have been used by bittorrent to upload, but they're already
allocated for interactive traffic. On top of that, 20% of the up-link
is often not enough to give a good and responsive browsing experience.</li>
</ol>
<p>The ideal bandwidth allocation would be to use 100% for bittorrent when
there is no interactive cross traffic, and 100% for interactive traffic
whenever there is any. This would not waste any bandwidth while the user
is idling, and it would make for a much better experience when the user
is using the internet connection for other things.</p>
<p>This is what uTP does.</p>
</div>
<div class="section" id="tcp">
<h2>TCP</h2>
<p>The reason TCP will fill the send buffer, and cause the delay on all traffic,
is because its congestion control is <em>only</em> based on packet loss (and timeout).</p>
<p>Since the modem is buffering, packets won't get dropped until the entire queue
is full, and no more packets will fit. The packets will be dropped, TCP will
detect this within an RTT or so. When TCP notices a packet loss, it will slow
down its send rate and the queue will start to drain again. However, TCP will
immediately start to ramp up its send rate again until the buffer is full and
it detects packet loss again.</p>
<p>TCP is designed to fully utilize the link capacity, without causing congestion.
Whenever it sense congestion (through packet loss) it backs off. TCP is not
designed to keep delays low. When you get the first packet loss (assuming the
kind of queue described above, tail-queue) it is already too late. Your queue
is full and you have the maximum amount of delay your modem can provide.</p>
<p>TCP controls its send rate by limiting the number of bytes in-flight at any
given time. This limit is called congestion window (<em>cwnd</em> for short). During
steady state, the congestion window is constantly increasing linearly. Each
packet that is successfully transferred will increase cwnd.</p>
<pre class="literal-block">
cwnd
send_rate = ----
RTT
</pre>
<p>Send rate is proportional to cwnd divided by RTT. A smaller cwnd will cause
the send rate to be lower and a larger cwnd will cause the send rate to be
higher.</p>
<p>Using a congestion window instead of controlling the rate directly is simple
because it also introduces an upper bound for memory usage for packets that
haven't been ACKed yet and needs to be kept around.</p>
<p>The behavior of TCP, where it bumps up against the ceiling, backs off and then
starts increasing again until it hits the ceiling again, forms a saw tooth shape.
If the modem wouldn't have any send buffer at all, a single TCP stream would
not be able to fully utilize the link because of this behavior, since it would
only fully utilize the link right before the packet loss and the back-off.</p>
</div>
<div class="section" id="ledbat-congestion-controller">
<h2>LEDBAT congestion controller</h2>
<p>The congestion controller in uTP is called <a class="reference external" href="https://datatracker.ietf.org/doc/draft-ietf-ledbat-congestion/">LEDBAT</a>, which also is an IETF working
group attempting to standardize it. The congestion controller, on top of reacting
to packet loss the same way TCP does, also reacts to changes in delays.</p>
<p>For any uTP (or <a class="reference external" href="https://datatracker.ietf.org/doc/draft-ietf-ledbat-congestion/">LEDBAT</a>) implementation, there is a target delay. This is the
amount of delay that is acceptable, and is in fact targeted for the connection.
The target delay is defined to 25 ms in <a class="reference external" href="https://datatracker.ietf.org/doc/draft-ietf-ledbat-congestion/">LEDBAT</a>, uTorrent uses 100 ms and
libtorrent uses 75 ms. Whenever a delay measurement is lower than the target,
cwnd is increased proportional to (target_delay - delay). Whenever the measurement
is higher than the target, cwnd is decreased proportional to (delay - target_delay).</p>
<p>It can simply be expressed as:</p>
<pre class="literal-block">
cwnd += gain * (target_delay - delay)
</pre>
<a class="reference external image-reference" href="cwnd.png"><img align="right" alt="cwnd_thumb.png" class="align-right" src="cwnd_thumb.png" /></a>
<p>Similarly to TCP, this is scaled so that the increase is evened out over one RTT.</p>
<p>The linear controller will adjust the cwnd more for delays that are far off the
target, and less for delays that are close to the target. This makes it converge
at the target delay. Although, due to noise there is almost always some amount of
oscillation. This oscillation is typically smaller than the saw tooth TCP forms.</p>
<p>The figure to the right shows how (TCP) cross traffic causese uTP to essentially
entirely stop sending anything. Its delay measurements are mostly well above the target
during this time. The cross traffic is only a single TCP stream in this test.</p>
<p>As soon as the cross traffic ceases, uTP will pick up its original send rate within
a second.</p>
<p>Since uTP constantly measures the delay, with every single packet, the reaction time
to cross traffic causing delays is a single RTT (typically a fraction of a second).</p>
</div>
<div class="section" id="one-way-delays">
<h2>one way delays</h2>
<p>uTP measures the delay imposed on packets being sent to the other end
of the connection. This measurement only includes buffering delay along
the link, not propagation delay (the speed of light times distance) nor
the routing delay (the time routers spend figuring out where to forward
the packet). It does this by always comparing all measurements to a
baseline measurement, to cancel out any fixed delay. By focusing on the
variable delay along a link, it will specifically detect points where
there might be congestion, since those points will have buffers.</p>
<a class="reference external image-reference" href="delays.png"><img align="right" alt="delays_thumb.png" class="align-right" src="delays_thumb.png" /></a>
<p>Delay on the return link is explicitly not included in the delay measurement.
This is because in a peer-to-peer application, the other end is likely to also
be connected via a modem, with the same send buffer restrictions as we assume
for the sending side. The other end having its send queue full is not an indication
of congestion on the path going the other way.</p>
<p>In order to measure one way delays for packets, we cannot rely on clocks being
synchronized, especially not at the microsecond level. Instead, the actual time
it takes for a packet to arrive at the destination is not measured, only the changes
in the transit time is measured.</p>
<p>Each packet that is sent includes a time stamp of the current time, in microseconds,
of the sending machine. The receiving machine calculates the difference between its
own timestamp and the one in the packet and sends this back in the ACK. This difference,
since it is in microseconds, will essentially be a random 32 bit number. However,
the difference will stay somewhat similar over time. Any changes in this difference
indicates that packets are either going through faster or slower.</p>
<p>In order to measure the one-way buffering delay, a base delay is established. The
base delay is the lowest ever seen value of the time stamp difference. Each delay
sample we receive back, is compared against the base delay and the delay is the
difference.</p>
<p>This is the delay that's fed into the congestion controller.</p>
<p>A histogram of typical delay measurements is shown to the right. This is from
a transfer between a cable modem connection and a DSL connection.</p>
<p>The details of the delay measurements are slightly more complicated since the
values needs to be able to wrap (cross the 2^32 boundry and start over at 0).</p>
</div>
<div class="section" id="path-mtu-discovery">
<h2>Path MTU discovery</h2>
<p>MTU is short for <em>Maximum Transfer Unit</em> and describes the largest packet size that
can be sent over a link. Any datagrams which size exceeds this limit will either
be <em>fragmented</em> or dropped. A fragmented datagram means that the payload is split up
in multiple packets, each with its own individual packet header.</p>
<p>There are several reasons to avoid sending datagrams that get fragmented:</p>
<ol class="arabic simple">
<li>A fragmented datagram is more likely to be lost. If any fragment is lost,
the whole datagram is dropped.</li>
<li>Bandwidth is likely to be wasted. If the datagram size is not divisible
by the MTU the last packet will not contain as much payload as it could, and the
payload over protocol header ratio decreases.</li>
<li>It's expensive to fragment datagrams. Few routers are optimized to handle large
numbers of fragmented packets. Datagrams that have to fragment are likely to
be delayed significantly, and contribute to more CPU being used on routers.
Typically fragmentation (and other advanced IP features) are implemented in
software (slow) and not hardware (fast).</li>
</ol>
<p>The path MTU is the lowest MTU of any link along a path from two endpoints on the
internet. The MTU bottleneck isn't necessarily at one of the endpoints, but can
be anywhere in between.</p>
<p>The most common MTU is 1500 bytes, which is the largest packet size for ethernet
networks. Many home DSL connections, however, tunnel IP through PPPoE (Point to
Point Protocol over Ethernet. Yes, that is the old dial-up modem protocol). This
protocol uses up 8 bytes per packet for its own header.</p>
<p>If the user happens to be on an internet connection over a VPN, it will add another
layer, with its own packet headers.</p>
<p>In short; if you would pick the largest possible packet size on an ethernet network,
1472, and stick with it, you would be quite likely to generate fragments for a lot
of connections. The fragments that will be created will be very small and especially
inflate the overhead waste.</p>
<p>The other approach of picking a very conservative packet size, that would be very
unlikely to get fragmented has the following drawbacks:</p>
<ol class="arabic simple">
<li>People on good, normal, networks will be penalized with a small packet size.
Both in terms of router load but also bandwidth waste.</li>
<li>Software routers are typically not limited by the number of bytes they can route,
but the number of packets. Small packets means more of them, and more load on
software routers.</li>
</ol>
<p>The solution to the problem of finding the optimal packet size, is to dynamically
adjust the packet size and search for the largest size that can make it through
without being fragmented along the path.</p>
<p>To help do this, you can set the DF bit (Don't Fragment) in your Datagrams. This
asks routers that otherwise would fragment packets to instead drop them, and send
back an ICMP message reporting the MTU of the link the packet couldn't fit. With
this message, it's very simple to discover the path MTU. You simply mark your packets
not to be fragmented, and change your packet size whenever you receive the ICMP
packet-too-big message.</p>
<p>Unfortunately it's not quite that simple. There are a significant number of firewalls
in the wild blocking all ICMP messages. This means we can't rely on them, we also have
to guess that a packet was dropped because of its size. This is done by only marking
certain packets with DF, and if all other packets go through, except for the MTU probes,
we know that we need to lower our packet sizes.</p>
<p>If we set up bounds for the path MTU (say the minimum internet MTU, 576 and ethernet's 1500),
we can do a binary search for the MTU. This would let us find it in just a few round-trips.</p>
<p>On top of this, libtorrent has an optimization where it figures out which interface a
uTP connection will be sent over, and initialize the MTU ceiling to that interface's MTU.
This means that a VPN tunnel would advertize its MTU as lower, and the uTP connection would
immediately know to send smaller packets, no search required. It also has the side-effect
of being able to use much larger packet sizes for non-ethernet interfaces or ethernet links
with jumbo frames.</p>
</div>
<div class="section" id="clock-drift">
<h2>clock drift</h2>
<a class="reference external image-reference" href="our_delay_base.png"><img align="right" alt="our_delay_base_thumb.png" class="align-right" src="our_delay_base_thumb.png" /></a>
<p>Clock drift is clocks progressing at different rates. It's different from clock
skew which means clocks set to different values (but which may progress at the same
rate).</p>
<p>Any clock drift between the two machines involved in a uTP transfer will result
in systematically inflated or deflated delay measurements.</p>
<p>This can be solved by letting the base delay be the lowest seen sample in the last
<em>n</em> minutes. This is a trade-off between seeing a single packet go straight through
the queue, with no delay, and the amount of clock drift one can assume on normal computers.</p>
<p>It turns out that it's fairly safe to assume that one of your packets will in fact go
straight through without any significant delay, once every 20 minutes or so. However,
the clock drift between normal computers can be as much as 17 ms in 10 minutes. 17 ms
is quite significant, especially if your target delay is 25 ms (as in the <a class="reference external" href="https://datatracker.ietf.org/doc/draft-ietf-ledbat-congestion/">LEDBAT</a> spec).</p>
<p>Clocks progresses at different rates depending on temperature. This means computers
running hot are likely to have a clock drift compared to computers running cool.</p>
<p>So, by updating the delay base periodically based on the lowest seen sample, you'll either
end up changing it upwards (artificaially making the delay samples appear small) without
the congestion or delay actually having changed, or you'll end up with a significant clock
drift and have artificially low samples because of that.</p>
<p>The solution to this problem is based on the fact that the clock drift is only a problem
for one of the sides of the connection. Only when your delay measurements keep increasing
is it a problem. If your delay measurements keep decreasing, the samples will simply push
down the delay base along with it. With this in mind, we can simply keep track of the
other end's delay measurements as well, applying the same logic to it. Whenever the
other end's base delay is adjusted downwards, we adjust our base delay upwards by the same
amount.</p>
<p>This will accurately keep the base delay updated with the clock drift and improve
the delay measurements. The figure on the right shows the absolute timestamp differences
along with the base delay. The slope of the measurements is caused by clock drift.</p>
<p>For more information on the clock drift compensation, see the slides from BitTorrent's
presentation at <a class="reference external" href="http://www.usenix.org/event/iptps10/tech/slides/cohen.pdf">IPTPS10</a>.</p>
</div>
<div class="section" id="features">
<h2>features</h2>
<p>libtorrent's uTP implementation includes the following features:</p>
<ul class="simple">
<li>Path MTU discovery, including jumbo frames and detecting restricted
MTU tunnels. Binary search packet sizes to find the largest non-fragmented.</li>
<li>Selective ACK. The ability to acknowledge individual packets in the
event of packet loss</li>
<li>Fast resend. The first time a packet is lost, it's resent immediately.
Triggered by duplicate ACKs.</li>
<li>Nagle's algorithm. Minimize protocol overhead by attempting to lump
full packets of payload together before sending a packet.</li>
<li>Delayed ACKs to minimize protocol overhead.</li>
<li>Microsecond resolution timestamps.</li>
<li>Advertised receive window, to support download rate limiting.</li>
<li>Correct handling of wrapping sequence numbers.</li>
<li>Easy configuration of target-delay, gain-factor, timeouts, delayed-ack
and socket buffers.</li>
</ul>
</div>
</div>
</div>
<div id="footer">
<span>Copyright &copy; 2005 Rasterbar Software.</span>
</div>
</div>
<script src="http://www.google-analytics.com/urchin.js" type="text/javascript">
</script>
<script type="text/javascript">
_uacct = "UA-1599045-1";
urchinTracker();
</script>
</div>
</body>
</html>

347
docs/utp.rst Normal file
View File

@ -0,0 +1,347 @@
=================
libtorrent manual
=================
:Author: Arvid Norberg, arvid@rasterbar.com
:Version: 0.16.0
.. contents:: Table of contents
:depth: 2
:backlinks: none
uTP
===
uTP (uTorrent transport protocol) is a transport protocol which uses one-way
delay measurements for its congestion controller. This article is about uTP
in general and specifically about libtorrent's implementation of it.
rationale
---------
One of the most common problems users are experiencing using bittorrent is
that their internet "stops working". This can be caused by a number of things,
for example:
1. a home router that crashes or slows down when its NAT pin-hole
table overflows, triggered by DHT or simply many TCP connections.
2. a home router that crashes or slows down by UDP traffic (caused by
the DHT)
3. a home DSL or cable modem having its send buffer filled up by outgoing
data, and the buffer fits seconds worth of bytes. This adds seconds
of delay on interactive traffic. For a web site that needs 10 round
trips to load this may mean 10s of seconds of delay to load compared
to without bittorrent. Skype or other delay sensitive applications
would be affected even more.
This document will cover (3).
Typically this is solved by asking the user to enter a number of bytes
that the client is allowed to send per second (i.e. setting an upload
rate limit). The common recommendation is to set this limit to 80% of the
uplink's capacity. This is to leave some headroom for things like TCP
ACKs as well as the user's interactive use of the connection such as
browsing the web or checking email.
There are two major drawbacks with this technique:
1. The user needs to actively make this setting (very few protocols
require the user to provide this sort of information). This also
means the user needs to figure out what its up-link capacity is.
This is unfortunately a number that many ISPs are not advertizing
(because it's often much lower than the download capacity) which
might make it hard to find.
2. The 20% headroom is wasted most of the time. Whenever the user
is not using the internet connection for anything, those extra 20%
could have been used by bittorrent to upload, but they're already
allocated for interactive traffic. On top of that, 20% of the up-link
is often not enough to give a good and responsive browsing experience.
The ideal bandwidth allocation would be to use 100% for bittorrent when
there is no interactive cross traffic, and 100% for interactive traffic
whenever there is any. This would not waste any bandwidth while the user
is idling, and it would make for a much better experience when the user
is using the internet connection for other things.
This is what uTP does.
TCP
---
The reason TCP will fill the send buffer, and cause the delay on all traffic,
is because its congestion control is *only* based on packet loss (and timeout).
Since the modem is buffering, packets won't get dropped until the entire queue
is full, and no more packets will fit. The packets will be dropped, TCP will
detect this within an RTT or so. When TCP notices a packet loss, it will slow
down its send rate and the queue will start to drain again. However, TCP will
immediately start to ramp up its send rate again until the buffer is full and
it detects packet loss again.
TCP is designed to fully utilize the link capacity, without causing congestion.
Whenever it sense congestion (through packet loss) it backs off. TCP is not
designed to keep delays low. When you get the first packet loss (assuming the
kind of queue described above, tail-queue) it is already too late. Your queue
is full and you have the maximum amount of delay your modem can provide.
TCP controls its send rate by limiting the number of bytes in-flight at any
given time. This limit is called congestion window (*cwnd* for short). During
steady state, the congestion window is constantly increasing linearly. Each
packet that is successfully transferred will increase cwnd.
::
cwnd
send_rate = ----
RTT
Send rate is proportional to cwnd divided by RTT. A smaller cwnd will cause
the send rate to be lower and a larger cwnd will cause the send rate to be
higher.
Using a congestion window instead of controlling the rate directly is simple
because it also introduces an upper bound for memory usage for packets that
haven't been ACKed yet and needs to be kept around.
The behavior of TCP, where it bumps up against the ceiling, backs off and then
starts increasing again until it hits the ceiling again, forms a saw tooth shape.
If the modem wouldn't have any send buffer at all, a single TCP stream would
not be able to fully utilize the link because of this behavior, since it would
only fully utilize the link right before the packet loss and the back-off.
LEDBAT congestion controller
----------------------------
The congestion controller in uTP is called LEDBAT_, which also is an IETF working
group attempting to standardize it. The congestion controller, on top of reacting
to packet loss the same way TCP does, also reacts to changes in delays.
For any uTP (or LEDBAT_) implementation, there is a target delay. This is the
amount of delay that is acceptable, and is in fact targeted for the connection.
The target delay is defined to 25 ms in LEDBAT_, uTorrent uses 100 ms and
libtorrent uses 75 ms. Whenever a delay measurement is lower than the target,
cwnd is increased proportional to (target_delay - delay). Whenever the measurement
is higher than the target, cwnd is decreased proportional to (delay - target_delay).
It can simply be expressed as::
cwnd += gain * (target_delay - delay)
.. image:: cwnd_thumb.png
:target: cwnd.png
:align: right
Similarly to TCP, this is scaled so that the increase is evened out over one RTT.
The linear controller will adjust the cwnd more for delays that are far off the
target, and less for delays that are close to the target. This makes it converge
at the target delay. Although, due to noise there is almost always some amount of
oscillation. This oscillation is typically smaller than the saw tooth TCP forms.
The figure to the right shows how (TCP) cross traffic causese uTP to essentially
entirely stop sending anything. Its delay measurements are mostly well above the target
during this time. The cross traffic is only a single TCP stream in this test.
As soon as the cross traffic ceases, uTP will pick up its original send rate within
a second.
Since uTP constantly measures the delay, with every single packet, the reaction time
to cross traffic causing delays is a single RTT (typically a fraction of a second).
one way delays
--------------
uTP measures the delay imposed on packets being sent to the other end
of the connection. This measurement only includes buffering delay along
the link, not propagation delay (the speed of light times distance) nor
the routing delay (the time routers spend figuring out where to forward
the packet). It does this by always comparing all measurements to a
baseline measurement, to cancel out any fixed delay. By focusing on the
variable delay along a link, it will specifically detect points where
there might be congestion, since those points will have buffers.
.. image:: delays_thumb.png
:target: delays.png
:align: right
Delay on the return link is explicitly not included in the delay measurement.
This is because in a peer-to-peer application, the other end is likely to also
be connected via a modem, with the same send buffer restrictions as we assume
for the sending side. The other end having its send queue full is not an indication
of congestion on the path going the other way.
In order to measure one way delays for packets, we cannot rely on clocks being
synchronized, especially not at the microsecond level. Instead, the actual time
it takes for a packet to arrive at the destination is not measured, only the changes
in the transit time is measured.
Each packet that is sent includes a time stamp of the current time, in microseconds,
of the sending machine. The receiving machine calculates the difference between its
own timestamp and the one in the packet and sends this back in the ACK. This difference,
since it is in microseconds, will essentially be a random 32 bit number. However,
the difference will stay somewhat similar over time. Any changes in this difference
indicates that packets are either going through faster or slower.
In order to measure the one-way buffering delay, a base delay is established. The
base delay is the lowest ever seen value of the time stamp difference. Each delay
sample we receive back, is compared against the base delay and the delay is the
difference.
This is the delay that's fed into the congestion controller.
A histogram of typical delay measurements is shown to the right. This is from
a transfer between a cable modem connection and a DSL connection.
The details of the delay measurements are slightly more complicated since the
values needs to be able to wrap (cross the 2^32 boundry and start over at 0).
Path MTU discovery
------------------
MTU is short for *Maximum Transfer Unit* and describes the largest packet size that
can be sent over a link. Any datagrams which size exceeds this limit will either
be *fragmented* or dropped. A fragmented datagram means that the payload is split up
in multiple packets, each with its own individual packet header.
There are several reasons to avoid sending datagrams that get fragmented:
1. A fragmented datagram is more likely to be lost. If any fragment is lost,
the whole datagram is dropped.
2. Bandwidth is likely to be wasted. If the datagram size is not divisible
by the MTU the last packet will not contain as much payload as it could, and the
payload over protocol header ratio decreases.
3. It's expensive to fragment datagrams. Few routers are optimized to handle large
numbers of fragmented packets. Datagrams that have to fragment are likely to
be delayed significantly, and contribute to more CPU being used on routers.
Typically fragmentation (and other advanced IP features) are implemented in
software (slow) and not hardware (fast).
The path MTU is the lowest MTU of any link along a path from two endpoints on the
internet. The MTU bottleneck isn't necessarily at one of the endpoints, but can
be anywhere in between.
The most common MTU is 1500 bytes, which is the largest packet size for ethernet
networks. Many home DSL connections, however, tunnel IP through PPPoE (Point to
Point Protocol over Ethernet. Yes, that is the old dial-up modem protocol). This
protocol uses up 8 bytes per packet for its own header.
If the user happens to be on an internet connection over a VPN, it will add another
layer, with its own packet headers.
In short; if you would pick the largest possible packet size on an ethernet network,
1472, and stick with it, you would be quite likely to generate fragments for a lot
of connections. The fragments that will be created will be very small and especially
inflate the overhead waste.
The other approach of picking a very conservative packet size, that would be very
unlikely to get fragmented has the following drawbacks:
1. People on good, normal, networks will be penalized with a small packet size.
Both in terms of router load but also bandwidth waste.
2. Software routers are typically not limited by the number of bytes they can route,
but the number of packets. Small packets means more of them, and more load on
software routers.
The solution to the problem of finding the optimal packet size, is to dynamically
adjust the packet size and search for the largest size that can make it through
without being fragmented along the path.
To help do this, you can set the DF bit (Don't Fragment) in your Datagrams. This
asks routers that otherwise would fragment packets to instead drop them, and send
back an ICMP message reporting the MTU of the link the packet couldn't fit. With
this message, it's very simple to discover the path MTU. You simply mark your packets
not to be fragmented, and change your packet size whenever you receive the ICMP
packet-too-big message.
Unfortunately it's not quite that simple. There are a significant number of firewalls
in the wild blocking all ICMP messages. This means we can't rely on them, we also have
to guess that a packet was dropped because of its size. This is done by only marking
certain packets with DF, and if all other packets go through, except for the MTU probes,
we know that we need to lower our packet sizes.
If we set up bounds for the path MTU (say the minimum internet MTU, 576 and ethernet's 1500),
we can do a binary search for the MTU. This would let us find it in just a few round-trips.
On top of this, libtorrent has an optimization where it figures out which interface a
uTP connection will be sent over, and initialize the MTU ceiling to that interface's MTU.
This means that a VPN tunnel would advertize its MTU as lower, and the uTP connection would
immediately know to send smaller packets, no search required. It also has the side-effect
of being able to use much larger packet sizes for non-ethernet interfaces or ethernet links
with jumbo frames.
clock drift
-----------
.. image:: our_delay_base_thumb.png
:target: our_delay_base.png
:align: right
Clock drift is clocks progressing at different rates. It's different from clock
skew which means clocks set to different values (but which may progress at the same
rate).
Any clock drift between the two machines involved in a uTP transfer will result
in systematically inflated or deflated delay measurements.
This can be solved by letting the base delay be the lowest seen sample in the last
*n* minutes. This is a trade-off between seeing a single packet go straight through
the queue, with no delay, and the amount of clock drift one can assume on normal computers.
It turns out that it's fairly safe to assume that one of your packets will in fact go
straight through without any significant delay, once every 20 minutes or so. However,
the clock drift between normal computers can be as much as 17 ms in 10 minutes. 17 ms
is quite significant, especially if your target delay is 25 ms (as in the LEDBAT_ spec).
Clocks progresses at different rates depending on temperature. This means computers
running hot are likely to have a clock drift compared to computers running cool.
So, by updating the delay base periodically based on the lowest seen sample, you'll either
end up changing it upwards (artificaially making the delay samples appear small) without
the congestion or delay actually having changed, or you'll end up with a significant clock
drift and have artificially low samples because of that.
The solution to this problem is based on the fact that the clock drift is only a problem
for one of the sides of the connection. Only when your delay measurements keep increasing
is it a problem. If your delay measurements keep decreasing, the samples will simply push
down the delay base along with it. With this in mind, we can simply keep track of the
other end's delay measurements as well, applying the same logic to it. Whenever the
other end's base delay is adjusted downwards, we adjust our base delay upwards by the same
amount.
This will accurately keep the base delay updated with the clock drift and improve
the delay measurements. The figure on the right shows the absolute timestamp differences
along with the base delay. The slope of the measurements is caused by clock drift.
For more information on the clock drift compensation, see the slides from BitTorrent's
presentation at IPTPS10_.
.. _IPTPS10: http://www.usenix.org/event/iptps10/tech/slides/cohen.pdf
.. _LEDBAT: https://datatracker.ietf.org/doc/draft-ietf-ledbat-congestion/
features
--------
libtorrent's uTP implementation includes the following features:
* Path MTU discovery, including jumbo frames and detecting restricted
MTU tunnels. Binary search packet sizes to find the largest non-fragmented.
* Selective ACK. The ability to acknowledge individual packets in the
event of packet loss
* Fast resend. The first time a packet is lost, it's resent immediately.
Triggered by duplicate ACKs.
* Nagle's algorithm. Minimize protocol overhead by attempting to lump
full packets of payload together before sending a packet.
* Delayed ACKs to minimize protocol overhead.
* Microsecond resolution timestamps.
* Advertised receive window, to support download rate limiting.
* Correct handling of wrapping sequence numbers.
* Easy configuration of target-delay, gain-factor, timeouts, delayed-ack
and socket buffers.

View File

@ -3,7 +3,8 @@ example_programs = \
dump_torrent \
enum_if \
make_torrent \
simple_client
simple_client \
utp_test
if ENABLE_EXAMPLES
bin_PROGRAMS = $(example_programs)

View File

@ -174,6 +174,7 @@ bool print_file_progress = false;
bool show_pad_files = false;
bool show_dht_status = false;
bool sequential_download = false;
bool print_utp_stats = false;
bool print_ip = true;
bool print_as = false;
@ -384,7 +385,7 @@ int peer_index(libtorrent::tcp::endpoint addr, std::vector<libtorrent::peer_info
void print_peer_info(std::string& out, std::vector<libtorrent::peer_info> const& peers)
{
using namespace libtorrent;
if (print_ip) out += "IP ";
if (print_ip) out += "IP ";
#ifndef TORRENT_DISABLE_GEO_IP
if (print_as) out += "AS ";
#endif
@ -409,8 +410,8 @@ void print_peer_info(std::string& out, std::vector<libtorrent::peer_info> const&
if (print_ip)
{
error_code ec;
snprintf(str, sizeof(str), "%-22s %22s ", print_endpoint(i->ip).c_str()
snprintf(str, sizeof(str), "%-30s %-22s", (print_endpoint(i->ip) +
(i->connection_type == peer_info::bittorrent_utp ? " [uTP]" : "")).c_str()
, print_endpoint(i->local_endpoint).c_str());
out += str;
}
@ -425,7 +426,7 @@ void print_peer_info(std::string& out, std::vector<libtorrent::peer_info> const&
#endif
snprintf(str, sizeof(str)
, "%s%s (%s|%s) %s%s (%s|%s) %s%3d (%3d) %3d %c%c%c%c%c%c%c%c%c%c%c%c%c%c %c%c%c%c%c%c "
, "%s%s (%s|%s) %s%s (%s|%s) %s%3d (%3d) %3d %c%c%c%c%c%c%c%c%c%c%c%c%c%c%c %c%c%c%c%c%c "
, esc("32"), add_suffix(i->down_speed, "/s").c_str()
, add_suffix(i->total_download).c_str(), add_suffix(i->download_rate_peak, "/s").c_str()
, esc("31"), add_suffix(i->up_speed, "/s").c_str(), add_suffix(i->total_upload).c_str()
@ -456,6 +457,8 @@ void print_peer_info(std::string& out, std::vector<libtorrent::peer_info> const&
#else
, '.'
#endif
, (i->flags & peer_info::holepunched)?'h':'.'
, (i->source & peer_info::tracker)?'T':'_'
, (i->source & peer_info::pex)?'P':'_'
, (i->source & peer_info::dht)?'D':'_'
@ -833,9 +836,12 @@ int main(int argc, char* argv[])
" -L <user:passwd> Use the specified username and password for the\n"
" proxy specified by -P\n"
" -H Don't start DHT\n"
" -M Disable TCP/uTP bandwidth balancing\n"
" -W <num peers> Set the max number of peers to keep in the peer list\n"
" -N Do not attempt to use UPnP and NAT-PMP to forward ports\n"
" -Y Rate limit local peers\n"
" -y Disable TCP connections (disable outgoing TCP and reject\n"
" incoming TCP connections)\n"
" -q <num loops> automatically quit the client after <num loops> of refreshes\n"
" this is useful for scripting tests\n"
" "
@ -903,8 +909,6 @@ int main(int argc, char* argv[])
{
if (argv[i][0] != '-')
{
// interpret this as a torrent
// match it against the <hash>@<tracker> format
if (strlen(argv[i]) > 45
&& is_hex(argv[i], 40)
@ -955,7 +959,7 @@ int main(int argc, char* argv[])
case 'U': torrent_upload_limit = atoi(arg) * 1000; break;
case 'D': torrent_download_limit = atoi(arg) * 1000; break;
case 'm': monitor_dir = arg; break;
case 'M': share_mode = true; --i; break;
case 'Q': share_mode = true; --i; break;
case 'b': bind_to_interface = arg; break;
case 'w': settings.urlseed_wait_retry = atoi(arg); break;
case 't': poll_interval = atoi(arg); break;
@ -1006,6 +1010,8 @@ int main(int argc, char* argv[])
case 'A': settings.allowed_fast_set_size = atoi(arg); break;
case 'R': settings.read_cache_line_size = atoi(arg); break;
case 'O': settings.allow_reordered_disk_operations = false; --i; break;
case 'M': settings.mixed_mode_algorithm = session_settings::prefer_tcp; --i; break;
case 'y': settings.enable_outgoing_tcp = false; settings.enable_incoming_tcp = false; --i; break;
case 'P':
{
char* port = (char*) strrchr(arg, ':');
@ -1297,6 +1303,7 @@ int main(int argc, char* argv[])
if (c == 'h') show_pad_files = !show_pad_files;
if (c == 'a') print_piece_bar = !print_piece_bar;
if (c == 'g') show_dht_status = !show_dht_status;
if (c == 'u') print_utp_stats = !print_utp_stats;
// toggle columns
if (c == '1') print_ip = !print_ip;
if (c == '2') print_as = !print_as;
@ -1566,6 +1573,15 @@ int main(int argc, char* argv[])
}
#endif
if (print_utp_stats)
{
snprintf(str, sizeof(str), "uTP idle: %d syn: %d est: %d fin: %d wait: %d\n"
, sess_stat.utp_stats.num_idle, sess_stat.utp_stats.num_syn_sent
, sess_stat.utp_stats.num_connected, sess_stat.utp_stats.num_fin_sent
, sess_stat.utp_stats.num_close_wait);
out += str;
}
if (active_handle.is_valid())
{
torrent_handle h = active_handle;

View File

@ -61,15 +61,16 @@ int main()
return 1;
}
printf("%-18s%-18s%-35sinterface name\n", "destination", "network", "gateway");
printf("%-18s%-18s%-35s%-7sinterface\n", "destination", "network", "gateway", "mtu");
for (std::vector<ip_route>::const_iterator i = routes.begin()
, end(routes.end()); i != end; ++i)
{
printf("%-18s%-18s%-35s%s\n"
printf("%-18s%-18s%-35s%-7d%s\n"
, i->destination.to_string(ec).c_str()
, i->netmask.to_string(ec).c_str()
, i->gateway.to_string(ec).c_str()
, i->mtu
, i->name);
}
@ -82,15 +83,16 @@ int main()
return 1;
}
printf("%-18s%-18s%-35sflags\n", "address", "netmask", "name");
printf("%-35s%-18s%-40s%-8sflags\n", "address", "netmask", "name", "mtu");
for (std::vector<ip_interface>::const_iterator i = net.begin()
, end(net.end()); i != end; ++i)
{
printf("%-18s%-18s%-35s%s%s%s\n"
printf("%-35s%-18s%-40s%-8d%s%s%s\n"
, i->interface_address.to_string(ec).c_str()
, i->netmask.to_string(ec).c_str()
, i->name
, i->mtu
, (is_multicast(i->interface_address)?"multicast ":"")
, (is_local(i->interface_address)?"local ":"")
, (is_loopback(i->interface_address)?"loopback ":"")

48
examples/utp_test.cpp Normal file
View File

@ -0,0 +1,48 @@
#include "libtorrent/error_code.hpp"
#include "libtorrent/session.hpp"
#include "libtorrent/socket_type.hpp"
#include "libtorrent/utp_socket_manager.hpp"
#include "libtorrent/utp_stream.hpp"
using namespace libtorrent;
void on_connect(error_code const& e)
{
}
void on_udp_receive(error_code const& e, udp::endpoint const& ep
, char const* buf, int size)
{
}
void on_utp_incoming(void* userdata
, boost::shared_ptr<utp_stream> const& utp_sock)
{
}
int main(int argc, char* argv[])
{
//int rtt, rtt_var;
//int max_window, cur_window;
//int delay_factor, window_factor, scaled_gain;
/*session s;
s.listen_on(std::make_pair(6881, 6889));*/
io_service ios;
connection_queue cc(ios);
udp_socket udp_sock(ios, boost::bind(&on_udp_receive, _1, _2, _3, _4), cc);
void* userdata;
utp_socket_manager utp_sockets(udp_sock, boost::bind(&on_utp_incoming, _1, _2), userdata);
/*error_code ec;
utp_stream sock(ios, cc);
sock.bind(udp::endpoint(address_v4::any(), 0), ec);
tcp::endpoint ep(address_v4::from_string("239.192.152.143", ec), 6771);
sock.async_connect(ep, boost::bind(on_connect, _1));*/
return 0;
}

View File

@ -94,6 +94,7 @@ nobase_include_HEADERS = \
storage_defs.hpp \
thread.hpp \
time.hpp \
timestamp_history.hpp \
torrent_handle.hpp \
torrent.hpp \
torrent_info.hpp \
@ -102,6 +103,8 @@ nobase_include_HEADERS = \
udp_tracker_connection.hpp \
union_endpoint.hpp \
upnp.hpp \
utp_socket_manager.hpp \
utp_stream.hpp \
utf8.hpp \
version.hpp \
web_peer_connection.hpp \

View File

@ -51,11 +51,17 @@ std::string demangle(char const* name);
#if (defined __linux__ || defined __MACH__) && defined __GNUC__
#if TORRENT_USE_IOSTREAM
#include <sstream>
#endif
TORRENT_EXPORT void assert_fail(const char* expr, int line, char const* file, char const* function, char const* val);
#define TORRENT_ASSERT(x) do { if (x) {} else assert_fail(#x, __LINE__, __FILE__, __PRETTY_FUNCTION__, 0); } while (false)
#if TORRENT_USE_IOSTREAM
#define TORRENT_ASSERT_VAL(x, y) do { if (x) {} else { std::stringstream __s__; __s__ << #y ": " << y; assert_fail(#x, __LINE__, __FILE__, __PRETTY_FUNCTION__, __s__.str().c_str()); } } while (false)
#else
#define TORRENT_ASSERT_VAL(x, y) TORRENT_ASSERT(x)
#endif
#else
#include <cassert>

View File

@ -82,6 +82,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include "libtorrent/deadline_timer.hpp"
#include "libtorrent/socket_io.hpp" // for print_address
#include "libtorrent/address.hpp"
#include "libtorrent/utp_socket_manager.hpp"
#ifdef TORRENT_STATS
#include <fstream>
@ -522,6 +523,15 @@ namespace libtorrent
bandwidth_channel m_local_download_channel;
bandwidth_channel m_local_upload_channel;
// all tcp peer connections are subject to these
// bandwidth limits. Local peers are excempted
// from this limit. The purpose is to be able to
// throttle TCP that passes over the internet
// bottleneck (i.e. modem) to avoid starving out
// uTP connections.
bandwidth_channel m_tcp_download_channel;
bandwidth_channel m_tcp_upload_channel;
bandwidth_channel* m_bandwidth_channel[2];
tracker_manager m_tracker_manager;
@ -725,6 +735,8 @@ namespace libtorrent
rate_limited_udp_socket m_udp_socket;
utp_socket_manager m_utp_socket_manager;
#ifndef TORRENT_DISABLE_ENCRYPTION
pe_settings m_pe_settings;
#endif

View File

@ -49,6 +49,7 @@ namespace libtorrent
TORRENT_EXPORT bool is_loopback(address const& addr);
TORRENT_EXPORT bool is_multicast(address const& addr);
TORRENT_EXPORT bool is_any(address const& addr);
TORRENT_EXPORT bool is_teredo(address const& addr);
TORRENT_EXPORT int cidr_distance(address const& a1, address const& a2);
// determines if the operating system supports IPv6

View File

@ -102,7 +102,12 @@ namespace libtorrent
void start();
enum { upload_only_msg = 2, share_mode_msg = 3 };
enum
{
upload_only_msg = 2,
holepunch_msg = 3,
share_mode_msg = 4
};
~bt_peer_connection();
@ -140,6 +145,20 @@ namespace libtorrent
num_supported_messages
};
enum hp_message_t
{
// msg_types
hp_rendezvous = 0,
hp_connect = 1,
hp_failed = 2,
// error codes
hp_no_such_peer = 1,
hp_not_connected = 2,
hp_no_support = 3,
hp_no_self = 4
};
// called from the main loop when this connection has any
// work to do.
@ -151,6 +170,9 @@ namespace libtorrent
virtual void get_specific_peer_info(peer_info& p) const;
virtual bool in_handshake() const;
bool supports_holepunch() const { return m_holepunch_id != 0; }
void write_holepunch_msg(int type, tcp::endpoint const& ep, int error);
#ifndef TORRENT_DISABLE_EXTENSIONS
bool support_extensions() const { return m_supports_extensions; }
#endif
@ -183,6 +205,7 @@ namespace libtorrent
void on_have_none(int received);
void on_reject_request(int received);
void on_allowed_fast(int received);
void on_holepunch();
void on_extended(int received);
@ -283,7 +306,7 @@ public:
#endif
}
#endif
peer_connection::append_send_buffer(buffer, size, destructor);
peer_connection::append_send_buffer(buffer, size, destructor, true);
}
void setup_send();
@ -366,6 +389,9 @@ private:
// 0 if not supported
int m_upload_only_id;
// the message ID for holepunch messages
int m_holepunch_id;
// the message ID for share mode message
// 0 if not supported
int m_share_mode_id;

View File

@ -42,11 +42,13 @@ POSSIBILITY OF SUCH DAMAGE.
namespace libtorrent
{
// the interface should not have a netmask
struct ip_interface
{
address interface_address;
address netmask;
char name[64];
int mtu;
};
struct ip_route
@ -55,6 +57,7 @@ namespace libtorrent
address netmask;
address gateway;
char name[64];
int mtu;
};
// returns a list of the configured IP interfaces
@ -64,9 +67,8 @@ namespace libtorrent
TORRENT_EXPORT std::vector<ip_route> enum_routes(io_service& ios, error_code& ec);
// returns true if the specified address is on the same
// local network as the specified interface
TORRENT_EXPORT bool in_subnet(address const& addr, ip_interface const& iface);
// return (a1 & mask) == (a2 & mask)
TORRENT_EXPORT bool match_addr_mask(address const& a1, address const& a2, address const& mask);
// returns true if the specified address is on the same
// local network as us

View File

@ -165,7 +165,7 @@ namespace libtorrent
pex_message_too_large,
invalid_pex_message,
invalid_lt_tracker_message,
reserved108,
too_frequent_pex,
reserved109,
reserved110,
reserved111,

View File

@ -90,6 +90,8 @@ namespace libtorrent
{
virtual ~peer_plugin() {}
virtual char const* type() const { return ""; }
// can add entries to the extension handshake
// this is not called for web seeds
virtual void add_handshake(entry&) {}

View File

@ -39,10 +39,12 @@ POSSIBILITY OF SUCH DAMAGE.
namespace libtorrent
{
struct proxy_settings;
struct utp_socket_manager;
bool instantiate_connection(io_service& ios
, proxy_settings const& ps, socket_type& s
, void* ssl_context = 0);
, void* ssl_context = 0
, utp_socket_manager* sm = 0);
}
#endif

View File

@ -93,6 +93,18 @@ namespace libtorrent
value = max3<temp1, temp2, temp3>::value
};
};
template<int v1, int v2, int v3, int v4, int v5, int v6, int v7, int v8>
struct max8
{
enum
{
temp1 = max<v1,v2>::value,
temp2 = max3<v3,v4,v5>::value,
temp3 = max3<v6,v7,v8>::value,
value = max3<temp1, temp2, temp3>::value
};
};
}
#endif

View File

@ -0,0 +1,108 @@
/*
Copyright (c) 2010, Arvid Norberg, Daniel Wallin.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
* Neither the name of the author nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef TORRENT_PACKET_BUFFER_HPP_INCLUDED
#define TORRENT_PACKET_BUFFER_HPP_INCLUDED
#include "boost/cstdint.hpp"
#include <cstddef>
namespace libtorrent
{
// this is a circular buffer that automatically resizes
// itself as elements are inserted. Elements are indexed
// by integers and are assumed to be sequential. Unless the
// old elements are removed when new elements are inserted,
// the buffer will be resized.
// if m_mask is 0xf, m_array has 16 elements
// m_cursor is the lowest index that has an element
// it also determines which indices the other slots
// refers to. Since it's a circular buffer, it wraps
// around. For example
// m_cursor = 9
// | refers to index 14
// | |
// V V
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | | | | | | | | | | | | | | | | | m_mask = 0xf
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// ^
// |
// refers to index 15
// whenever the element at the cursor is removed, the
// cursor is bumped to the next occupied element
class packet_buffer
{
public:
typedef boost::uint32_t index_type;
packet_buffer();
~packet_buffer();
void* insert(index_type idx, void* value);
std::size_t size() const
{ return m_size; }
std::size_t capacity() const
{ return m_capacity; }
void* at(index_type idx) const;
void* remove(index_type idx);
void reserve(std::size_t size);
index_type cursor() const
{ return m_first; }
index_type span() const
{ return (m_last - m_first) & 0xffff; }
private:
void** m_storage;
std::size_t m_capacity;
std::size_t m_size;
// This defines the first index that is part of the m_storage.
// The last index is (m_first + (m_capacity - 1)) & 0xffff.
index_type m_first;
index_type m_last;
};
}
#endif // TORRENT_PACKET_BUFFER_HPP_INCLUDED

View File

@ -78,6 +78,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include "libtorrent/bandwidth_socket.hpp"
#include "libtorrent/socket_type_fwd.hpp"
#include "libtorrent/error_code.hpp"
#include "libtorrent/sliding_average.hpp"
#ifdef TORRENT_STATS
#include "libtorrent/aux_/session_impl.hpp"
@ -86,9 +87,11 @@ POSSIBILITY OF SUCH DAMAGE.
namespace libtorrent
{
class torrent;
struct peer_plugin;
struct peer_info;
struct disk_io_job;
#ifndef TORRENT_DISABLE_EXTENSIONS
struct peer_plugin;
#endif
namespace detail
{
@ -202,6 +205,7 @@ namespace libtorrent
#ifndef TORRENT_DISABLE_EXTENSIONS
void add_extension(boost::shared_ptr<peer_plugin>);
peer_plugin const* find_plugin(char const* type);
#endif
// this function is called once the torrent associated
@ -284,6 +288,14 @@ namespace libtorrent
void set_upload_only(bool u);
bool upload_only() const { return m_upload_only; }
void set_holepunch_mode()
{
m_holepunch_mode = true;
#ifdef TORRENT_VERBOSE_LOGGING
(*m_logger) << time_now_string() << "*** HOLEPUNCH MODE ***\n";
#endif
}
// will send a keep-alive message to the peer
void keep_alive();
@ -341,6 +353,9 @@ namespace libtorrent
void on_timeout();
// this will cause this peer_connection to be disconnected.
virtual void disconnect(error_code const& ec, int error = 0);
// called when a connect attempt fails (not when an
// established connection fails)
void connect_failed(error_code const& e);
bool is_disconnecting() const { return m_disconnecting; }
// this is called when the connection attempt has succeeded
@ -522,11 +537,17 @@ namespace libtorrent
#endif
template <class Destructor>
void append_send_buffer(char* buffer, int size, Destructor const& destructor)
void append_send_buffer(char* buffer, int size, Destructor const& destructor
, bool encrypted = false)
{
#if defined TORRENT_STATS && defined TORRENT_DISK_STATS
log_buffer_usage(buffer, size, "queued send buffer");
#endif
// bittorrent connections should never use this function, since
// they might be encrypted and this would circumvent the actual
// encryption. bt_peer_connection overrides this function with
// its own version.
TORRENT_ASSERT(encrypted || type() != bittorrent_connection);
m_send_buffer.append_buffer(buffer, size, size, destructor);
}
@ -634,6 +655,8 @@ namespace libtorrent
bool verify_piece(peer_request const& p) const;
void update_desired_queue_size();
// the bandwidth channels, upload and download
// keeps track of the current quotas
bandwidth_channel m_bandwidth_channel[num_channels];
@ -665,6 +688,10 @@ namespace libtorrent
// web seeds also has a limit on the queue size.
int m_max_out_request_queue;
// the average rate of receiving complete piece messages
sliding_average<20> m_piece_rate;
sliding_average<20> m_send_rate;
void set_timeout(int s) { m_timeout = s; }
#ifndef TORRENT_DISABLE_EXTENSIONS
@ -711,6 +738,7 @@ namespace libtorrent
// the time when we last got a part of a
// piece packet from this peer
ptime m_last_piece;
// the time we sent a request to
// this peer the last time
ptime m_last_request;
@ -1076,6 +1104,9 @@ namespace libtorrent
// set to true when we've sent the first round of suggests
bool m_sent_suggests:1;
// set to true while we're trying to holepunch
bool m_holepunch_mode:1;
// when this is set, the transfer stats for this connection
// is not included in the torrent or session stats
bool m_ignore_stats:1;

View File

@ -59,7 +59,8 @@ namespace libtorrent
seed = 0x400,
optimistic_unchoke = 0x800,
snubbed = 0x1000,
upload_only = 0x2000
upload_only = 0x2000,
holepunched = 0x4000
#ifndef TORRENT_DISABLE_ENCRYPTION
, rc4_encrypted = 0x100000,
plaintext_encrypted = 0x200000
@ -186,7 +187,8 @@ namespace libtorrent
{
standard_bittorrent = 0,
web_seed = 1,
http_seed = 2
http_seed = 2,
bittorrent_utp = 3
};
int connection_type;

View File

@ -164,7 +164,8 @@ namespace libtorrent
// 43 1 1 failcount, connectable, optimistically_unchoked, seed
// 44 1 1 fast_reconnects, trust_points
// 45 1 1 source, pe_support, is_v6_addr
// 46 1 1 on_parole, banned, added_to_dht
// 46 1 1 on_parole, banned, added_to_dht, supports_utp,
// supports_holepunch
// 47 1 1 <padding>
// 48
struct TORRENT_EXPORT peer
@ -311,6 +312,11 @@ namespace libtorrent
// pinged by the DHT
bool added_to_dht:1;
#endif
// we think this peer supports uTP
bool supports_utp:1;
// we have been connected via uTP at least once
bool confirmed_supports_utp:1;
bool supports_holepunch:1;
#ifdef TORRENT_DEBUG
bool in_use:1;
#endif

View File

@ -222,6 +222,11 @@ namespace libtorrent
, default_peer_upload_rate(0)
, default_peer_download_rate(0)
, broadcast_lsd(false)
, enable_outgoing_utp(true)
, enable_incoming_utp(true)
, enable_outgoing_tcp(true)
, enable_incoming_tcp(true)
, max_pex_peers(200)
, ignore_resume_timestamps(false)
, anonymous_mode(false)
, tick_interval(100)
@ -234,6 +239,17 @@ namespace libtorrent
, unchoke_slots_limit(8)
, half_open_limit(0)
, connections_limit(200)
, utp_target_delay(75) // milliseconds
, utp_gain_factor(1500) // bytes per rtt
, utp_min_timeout(500) // milliseconds
, utp_syn_resends(2)
, utp_fin_resends(2)
, utp_num_resends(6)
, utp_connect_timeout(3000) // milliseconds
, utp_delayed_ack(0) // milliseconds
, utp_dynamic_sock_buf(true)
, mixed_mode_algorithm(peer_proportional)
, rate_limit_utp(false)
, listen_queue_size(5)
{}
@ -856,6 +872,24 @@ namespace libtorrent
// a network is known not to support multicast, this can be enabled
bool broadcast_lsd;
// when set to true, libtorrent will try to make outgoing utp connections
bool enable_outgoing_utp;
// if set to false, libtorrent will reject incoming utp connections
bool enable_incoming_utp;
// when set to false, no outgoing TCP connections will be made
bool enable_outgoing_tcp;
// if set to false, libtorrent will reject incoming tcp connections
bool enable_incoming_tcp;
// the max number of peers we accept from pex messages from a single peer.
// this limits the number of concurrent peers any of our peers claims to
// be connected to. If they clain to be connected to more than this, we'll
// ignore any peer that exceeds this limit
int max_pex_peers;
// when set to true, the file modification time is ignored when loading
// resume data. The resume data includes the expected timestamp of each
// file and is typically compared to make sure the files haven't changed
@ -903,6 +937,57 @@ namespace libtorrent
// the max number of connections in the session
int connections_limit;
// target delay, milliseconds
int utp_target_delay;
// max number of bytes to increase cwnd per rtt in uTP
// congestion controller
int utp_gain_factor;
// the shortest allowed uTP connection timeout in milliseconds
// defaults to 500 milliseconds. The shorter timeout, the
// faster the connection recovers from a loss of an entire window
int utp_min_timeout;
// the number of SYN packets that are sent before giving up
int utp_syn_resends;
// the number of resent packets sent on a closed socket before giving up
int utp_fin_resends;
// the number of times to send a packet before giving up
int utp_num_resends;
// initial timeout for uTP SYN packets
int utp_connect_timeout;
// number of milliseconds of delaying ACKing packets the most
int utp_delayed_ack;
// set to true if the uTP socket buffer size is allowed to increase
// dynamically based on the NIC MTU setting. This is true by default
// and improves uTP performance for networks with larger frame sizes
// including loopback
bool utp_dynamic_sock_buf;
enum bandwidth_mixed_algo_t
{
// disables the mixed mode bandwidth balancing
prefer_tcp = 0,
// does not throttle uTP, throttles TCP to the same proportion
// of throughput as there are TCP connections
peer_proportional = 1
};
// the algorithm to use to balance bandwidth between tcp
// connections and uTP connections
int mixed_mode_algorithm;
// set to true if uTP connections should be rate limited
// defaults to false
bool rate_limit_utp;
// this is the number passed in to listen(). i.e.
// the number of connections to accept while we're
// not waiting in an accept() call.
@ -915,7 +1000,9 @@ namespace libtorrent
dht_settings()
: max_peers_reply(100)
, search_branching(5)
#ifndef TORRENT_NO_DEPRECATE
, service_port(0)
#endif
, max_fail_count(20)
, max_torrent_search_reply(20)
{}
@ -928,9 +1015,11 @@ namespace libtorrent
// searching the DHT.
int search_branching;
#ifndef TORRENT_NO_DEPRECATE
// the listen port for the dht. This is a UDP port.
// zero means use the same as the tcp interface
int service_port;
#endif
// the maximum number of times a node can fail
// in a row before it is removed from the table.

View File

@ -35,6 +35,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include "libtorrent/config.hpp"
#include "libtorrent/size_type.hpp"
#include <vector>
namespace libtorrent
{
@ -53,6 +54,15 @@ namespace libtorrent
#endif
struct utp_status
{
int num_idle;
int num_syn_sent;
int num_connected;
int num_fin_sent;
int num_close_wait;
};
struct TORRENT_EXPORT session_status
{
bool has_incoming_connections;
@ -107,6 +117,8 @@ namespace libtorrent
int dht_total_allocations;
#endif
utp_status utp_stats;
int peerlist_size;
};

View File

@ -30,6 +30,9 @@ POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef TORRENT_SLIDING_AVERAGE_HPP_INCLUDED
#define TORRENT_SLIDING_AVERAGE_HPP_INCLUDED
namespace libtorrent
{
// a sliding average accumulator. Add samples to it and it
@ -70,3 +73,5 @@ private:
}
#endif

View File

@ -146,6 +146,40 @@ namespace libtorrent
size_t size(Protocol const&) const { return sizeof(m_value); }
char m_value;
};
#if defined IP_DONTFRAG || defined IP_MTU_DISCOVER || defined IP_DONTFRAGMENT
#define TORRENT_HAS_DONT_FRAGMENT
#endif
#ifdef TORRENT_HAS_DONT_FRAGMENT
struct dont_fragment
{
dont_fragment(bool val)
#ifdef IP_PMTUDISCOVER_DO
: m_value(val ? IP_PMTUDISC_DO : IP_PMTUDISC_DONT) {}
#else
: m_value(val) {}
#endif
template<class Protocol>
int level(Protocol const&) const { return IPPROTO_IP; }
template<class Protocol>
int name(Protocol const&) const
#if defined IP_DONTFRAG
{ return IP_DONTFRAG; }
#elif defined IP_MTU_DISCOVER
{ return IP_MTU_DISCOVER; }
#elif defined IP_DONTFRAGMENT
{ return IP_DONTFRAGMENT; }
#else
{}
#endif
template<class Protocol>
int const* data(Protocol const&) const { return &m_value; }
template<class Protocol>
size_t size(Protocol const&) const { return sizeof(m_value); }
int m_value;
};
#endif // TORRENT_HAS_DONT_FRAGMENT
}
#endif // TORRENT_SOCKET_HPP_INCLUDED

View File

@ -38,6 +38,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include "libtorrent/socks5_stream.hpp"
#include "libtorrent/http_stream.hpp"
#include "libtorrent/i2p_stream.hpp"
#include "libtorrent/utp_stream.hpp"
#include "libtorrent/io_service.hpp"
#include "libtorrent/max.hpp"
#include "libtorrent/assert.hpp"
@ -96,6 +97,8 @@ POSSIBILITY OF SUCH DAMAGE.
get<socks5_stream>()->x; break; \
case socket_type_int_impl<http_stream>::value: \
get<http_stream>()->x; break; \
case socket_type_int_impl<utp_stream>::value: \
get<utp_stream>()->x; break; \
TORRENT_SOCKTYPE_I2P_FORWARD(x) \
TORRENT_SOCKTYPE_SSL_FORWARD(x) \
default: TORRENT_ASSERT(false); \
@ -109,6 +112,8 @@ POSSIBILITY OF SUCH DAMAGE.
return get<socks5_stream>()->x; \
case socket_type_int_impl<http_stream>::value: \
return get<http_stream>()->x; \
case socket_type_int_impl<utp_stream>::value: \
return get<utp_stream>()->x; \
TORRENT_SOCKTYPE_I2P_FORWARD_RET(x, def) \
TORRENT_SOCKTYPE_SSL_FORWARD_RET(x, def) \
default: TORRENT_ASSERT(false); return def; \
@ -133,36 +138,38 @@ namespace libtorrent
struct socket_type_int_impl<http_stream>
{ enum { value = 3 }; };
template <>
struct socket_type_int_impl<utp_stream>
{ enum { value = 4 }; };
#if TORRENT_USE_I2P
template <>
struct socket_type_int_impl<i2p_stream>
{ enum { value = 4 }; };
{ enum { value = 5 }; };
#endif
#ifdef TORRENT_USE_OPENSSL
template <>
struct socket_type_int_impl<ssl_stream<stream_socket> >
{ enum { value = 5 }; };
template <>
struct socket_type_int_impl<ssl_stream<socks5_stream> >
{ enum { value = 6 }; };
template <>
struct socket_type_int_impl<ssl_stream<http_stream> >
struct socket_type_int_impl<ssl_stream<socks5_stream> >
{ enum { value = 7 }; };
template <>
struct socket_type_int_impl<ssl_stream<http_stream> >
{ enum { value = 8 }; };
#endif
struct TORRENT_EXPORT socket_type
{
typedef stream_socket::lowest_layer_type lowest_layer_type;
typedef stream_socket::endpoint_type endpoint_type;
typedef stream_socket::protocol_type protocol_type;
explicit socket_type(io_service& ios): m_io_service(ios), m_type(0) {}
~socket_type();
lowest_layer_type& lowest_layer();
io_service& get_io_service() const;
bool is_open() const;
@ -253,10 +260,11 @@ namespace libtorrent
io_service& m_io_service;
int m_type;
enum { storage_size = max7<
enum { storage_size = max8<
sizeof(stream_socket)
, sizeof(socks5_stream)
, sizeof(http_stream)
, sizeof(utp_stream)
#if TORRENT_USE_I2P
, sizeof(i2p_stream)
#else

View File

@ -0,0 +1,80 @@
/*
Copyright (c) 2009, Arvid Norberg
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
* Neither the name of the author nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef TIMESTAMP_HISTORY_HPP
#define TIMESTAMP_HISTORY_HPP
#include "boost/cstdint.hpp"
#include "libtorrent/assert.hpp"
namespace libtorrent {
// timestamp history keeps a history of the lowest timestamps we've
// seen in the last 20 minutes
struct timestamp_history
{
enum { history_size = 20 };
timestamp_history() : m_index(0), m_initialized(false), m_base(0), m_num_samples(0) {}
bool initialized() const { return m_initialized; }
// add a sample to the timestamp history. If step is true, it's been
// a minute since the last step
boost::uint32_t add_sample(boost::uint32_t sample, bool step);
boost::uint32_t base() const { TORRENT_ASSERT(m_initialized); return m_base; }
void adjust_base(int change);
private:
// this is a circular buffer
boost::uint32_t m_history[history_size];
// and this is the index we're currently at
// in the circular buffer
boost::uint16_t m_index;
bool m_initialized:1;
// this is the lowest sample seen in the
// last 'history_size' minutes
boost::uint32_t m_base;
// this is the number of samples since the
// last time we stepped one minute. If we
// don't have enough samples, we won't step
int m_num_samples;
};
}
#endif

View File

@ -91,6 +91,7 @@ namespace libtorrent
struct tracker_request;
struct add_torrent_params;
struct storage_interface;
struct bt_peer_connection;
namespace aux
{
@ -136,6 +137,15 @@ namespace libtorrent
// it will initialize the storage and the piece-picker
void init();
// find the peer that introduced us to the given endpoint. This is
// used when trying to holepunch. We need the introducer so that we
// can send a rendezvous connect message
bt_peer_connection* find_introducer(tcp::endpoint const& ep) const;
// if we're connected to a peer at ep, return its peer connection
// only count BitTorrent peers
bt_peer_connection* find_peer(tcp::endpoint const& ep) const;
void on_resume_data_checked(int ret, disk_io_job const& j);
void on_force_recheck(int ret, disk_io_job const& j);
void on_piece_checked(int ret, disk_io_job const& j);
@ -291,7 +301,7 @@ namespace libtorrent
tcp::endpoint get_interface() const;
void connect_to_url_seed(std::list<web_seed_entry>::iterator url);
bool connect_to_peer(policy::peer* peerinfo);
bool connect_to_peer(policy::peer* peerinfo, bool ignore_limit = false);
void set_ratio(float r)
{ TORRENT_ASSERT(r >= 0.0f); m_ratio = r; }

View File

@ -82,13 +82,31 @@ namespace libtorrent
proxy_settings const& get_proxy_settings() { return m_proxy_settings; }
bool is_closed() const { return m_abort; }
tcp::endpoint local_endpoint() const
tcp::endpoint local_endpoint(error_code& ec) const
{
error_code ec;
udp::endpoint ep = m_ipv4_sock.local_endpoint(ec);
return tcp::endpoint(ep.address(), ep.port());
}
void set_buf_size(int s);
template <class SocketOption>
void set_option(SocketOption const& opt, error_code& ec)
{
m_ipv4_sock.set_option(opt, ec);
#if TORRENT_USE_IPV6
m_ipv6_sock.set_option(opt, ec);
#endif
}
template <class SocketOption>
void get_option(SocketOption& opt, error_code& ec)
{
m_ipv4_sock.get_option(opt, ec);
}
udp::endpoint proxy_addr() const { return m_proxy_addr; }
protected:
struct queued_packet
@ -129,6 +147,8 @@ namespace libtorrent
void wrap(char const* hostname, int port, char const* p, int len, error_code& ec);
void unwrap(error_code const& e, char const* buf, int size);
void maybe_realloc_buffers();
#ifdef TORRENT_DEBUG
#if defined BOOST_HAS_PTHREADS
mutable pthread_t m_thread;
@ -146,12 +166,14 @@ namespace libtorrent
udp::socket m_ipv4_sock;
udp::endpoint m_v4_ep;
char m_v4_buf[1600];
int m_v4_buf_size;
char* m_v4_buf;
#if TORRENT_USE_IPV6
udp::socket m_ipv6_sock;
udp::endpoint m_v6_ep;
char m_v6_buf[1600];
int m_v6_buf_size;
char* m_v6_buf;
#endif
int m_bind_port;
@ -166,6 +188,11 @@ namespace libtorrent
bool m_queue_packets;
bool m_tunnel_packets;
bool m_abort;
// this is set to true to indicate that the m_v4_buf
// and m_v6_buf should be reallocated to the size
// of the buffer size members the next time their
// read handler gets triggered
bool m_reallocate_buffers;
udp::endpoint m_proxy_addr;
// while we're connecting to the proxy
// we have to queue the packets, we'll flush

View File

@ -0,0 +1,116 @@
/*
Copyright (c) 2009, Arvid Norberg
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
* Neither the name of the author nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef TORRENT_UTP_SOCKET_MANAGER_HPP_INCLUDED
#define TORRENT_UTP_SOCKET_MANAGER_HPP_INCLUDED
#include <map>
#include "libtorrent/socket_type.hpp"
#include "libtorrent/session_status.hpp"
#include "libtorrent/enum_net.hpp"
namespace libtorrent
{
class udp_socket;
class utp_stream;
struct utp_socket_impl;
typedef boost::function<void(boost::shared_ptr<socket_type> const&)> incoming_utp_callback_t;
struct utp_socket_manager
{
utp_socket_manager(session_settings const& sett, udp_socket& s, incoming_utp_callback_t cb);
~utp_socket_manager();
void get_status(utp_status& s) const;
// return false if this is not a uTP packet
bool incoming_packet(char const* p, int size, udp::endpoint const& ep);
void tick(ptime now);
tcp::endpoint local_endpoint(error_code& ec) const;
// flags for send_packet
enum { dont_fragment = 1 };
void send_packet(udp::endpoint const& ep, char const* p, int len
, error_code& ec, int flags = 0);
// internal, used by utp_stream
void remove_socket(boost::uint16_t id);
utp_socket_impl* new_utp_socket(utp_stream* str);
int gain_factor() const { return m_sett.utp_gain_factor; }
int target_delay() const { return m_sett.utp_target_delay * 1000; }
int syn_resends() const { return m_sett.utp_syn_resends; }
int fin_resends() const { return m_sett.utp_fin_resends; }
int num_resends() const { return m_sett.utp_num_resends; }
int connect_timeout() const { return m_sett.utp_connect_timeout; }
int delayed_ack() const { return m_sett.utp_delayed_ack; }
int min_timeout() const { return m_sett.utp_min_timeout; }
bool allow_dynamic_sock_buf() const { return m_sett.utp_dynamic_sock_buf; }
void mtu_for_dest(address const& addr, int& link_mtu, int& utp_mtu);
void set_sock_buf(int size);
private:
udp_socket& m_sock;
incoming_utp_callback_t m_cb;
// replace with a hash-map
typedef std::multimap<boost::uint16_t, utp_socket_impl*> socket_map_t;
socket_map_t m_utp_sockets;
// the last socket we received a packet on
utp_socket_impl* m_last_socket;
int m_new_connection;
session_settings const& m_sett;
// this is a copy of the routing table, used
// to initialize MTU sizes of uTP sockets
std::vector<ip_route> m_routes;
// the timestamp for the last time we updated
// the routing table
ptime m_last_route_update;
// the buffer size of the socket. This is used
// to now lower the buffer size
int m_sock_buf_size;
};
}
#endif

View File

@ -0,0 +1,386 @@
/*
Copyright (c) 2009, Arvid Norberg
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
* Neither the name of the author nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef TORRENT_UTP_STREAM_HPP_INCLUDED
#define TORRENT_UTP_STREAM_HPP_INCLUDED
#include "libtorrent/connection_queue.hpp"
#include "libtorrent/proxy_base.hpp"
#include "libtorrent/udp_socket.hpp"
#include "libtorrent/io.hpp"
#include "libtorrent/packet_buffer.hpp"
#include "libtorrent/error_code.hpp"
#include <boost/bind.hpp>
#include <boost/function/function1.hpp>
#include <boost/function/function2.hpp>
#define CCONTROL_TARGET 100
namespace libtorrent
{
struct utp_socket_manager;
// some MTU and protocol header sizes constants
enum
{
TORRENT_IPV4_HEADER = 20,
TORRENT_IPV6_HEADER = 40,
TORRENT_UDP_HEADER = 8,
TORRENT_SOCKS5_HEADER = 6, // plus the size of the destination address
TORRENT_ETHERNET_MTU = 1500,
TORRENT_TEREDO_MTU = 1280,
TORRENT_INET_MIN_MTU = 576,
TORRENT_INET_MAX_MTU = 0xffff
};
// the point of the bif_endian_int is two-fold
// one purpuse is to not have any alignment requirements
// so that any byffer received from the network can be cast
// to it and read as an integer of various sizes without
// triggering a bus error. The other purpose is to convert
// from network byte order to host byte order when read and
// written, to offer a convenient interface to both interpreting
// and writing network packets
template <class T> struct big_endian_int
{
big_endian_int& operator=(T v)
{
char* p = m_storage;
detail::write_impl(v, p);
return *this;
}
operator T() const
{
const char* p = m_storage;
return detail::read_impl(p, detail::type<T>());
}
private:
char m_storage[sizeof(T)];
};
typedef big_endian_int<boost::uint64_t> be_uint64;
typedef big_endian_int<boost::uint32_t> be_uint32;
typedef big_endian_int<boost::uint16_t> be_uint16;
typedef big_endian_int<boost::int64_t> be_int64;
typedef big_endian_int<boost::int32_t> be_int32;
typedef big_endian_int<boost::int16_t> be_int16;
/*
uTP header from BEP 29
0 4 8 16 24 32
+-------+-------+---------------+---------------+---------------+
| type | ver | extension | connection_id |
+-------+-------+---------------+---------------+---------------+
| timestamp_microseconds |
+---------------+---------------+---------------+---------------+
| timestamp_difference_microseconds |
+---------------+---------------+---------------+---------------+
| wnd_size |
+---------------+---------------+---------------+---------------+
| seq_nr | ack_nr |
+---------------+---------------+---------------+---------------+
*/
enum type { ST_DATA = 0, ST_FIN, ST_STATE, ST_RESET, ST_SYN, NUM_TYPES };
struct utp_header
{
unsigned char type_ver;
unsigned char extension;
be_uint16 connection_id;
be_uint32 timestamp_microseconds;
be_uint32 timestamp_difference_microseconds;
be_uint32 wnd_size;
be_uint16 seq_nr;
be_uint16 ack_nr;
int get_type() const { return type_ver >> 4; }
int get_version() const { return type_ver & 0xf; }
};
struct utp_socket_impl;
utp_socket_impl* construct_utp_impl(boost::uint16_t recv_id
, boost::uint16_t send_id, void* userdata
, utp_socket_manager* sm);
void detach_utp_impl(utp_socket_impl* s);
void delete_utp_impl(utp_socket_impl* s);
bool should_delete(utp_socket_impl* s);
void tick_utp_impl(utp_socket_impl* s, ptime const& now);
void utp_init_mtu(utp_socket_impl* s, int link_mtu, int utp_mtu);
bool utp_incoming_packet(utp_socket_impl* s, char const* p
, int size, udp::endpoint const& ep, ptime receive_time);
bool utp_match(utp_socket_impl* s, udp::endpoint const& ep, boost::uint16_t id);
udp::endpoint utp_remote_endpoint(utp_socket_impl* s);
boost::uint16_t utp_receive_id(utp_socket_impl* s);
int utp_socket_state(utp_socket_impl const* s);
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING
int socket_impl_size();
#endif
// this is the user-level stream interface to utp sockets.
// the reason why it's split up in a utp_stream class and
// an implementation class is because the socket state has
// to be able to out-live the user level socket. For instance
// when sending data on a stream and then closing it, the
// state holding the send buffer has to be kept around until
// it has been flushed, which may be longer than the client
// will keep the utp_stream object around for.
// for more details, see utp_socket_impl, which is analogous
// to the kernel state for a socket. It's defined in utp_stream.cpp
class utp_stream
{
public:
typedef stream_socket::endpoint_type endpoint_type;
typedef stream_socket::protocol_type protocol_type;
explicit utp_stream(asio::io_service& io_service);
~utp_stream();
// used for incoming connections
void set_impl(utp_socket_impl* s);
utp_socket_impl* get_impl();
#ifndef BOOST_NO_EXCEPTIONS
template <class IO_Control_Command>
void io_control(IO_Control_Command& ioc) {}
#endif
template <class IO_Control_Command>
void io_control(IO_Control_Command& ioc, error_code& ec) {}
#ifndef BOOST_NO_EXCEPTIONS
void bind(endpoint_type const& endpoint) {}
#endif
void bind(endpoint_type const& endpoint, error_code& ec);
#ifndef BOOST_NO_EXCEPTIONS
template <class SettableSocketOption>
void set_option(SettableSocketOption const& opt) {}
#endif
template <class SettableSocketOption>
error_code set_option(SettableSocketOption const& opt, error_code& ec) { return ec; }
void close();
void close(error_code const& ec) { close(); }
bool is_open() const { return m_open; }
int read_buffer_size() const;
static void on_read(void* self, size_t bytes_transferred, error_code const& ec, bool kill);
static void on_write(void* self, size_t bytes_transferred, error_code const& ec, bool kill);
static void on_connect(void* self, error_code const& ec, bool kill);
typedef void(*handler_t)(void*, size_t, error_code const&, bool);
typedef void(*connect_handler_t)(void*, error_code const&, bool);
void add_read_buffer(void* buf, size_t len);
void set_read_handler(handler_t h);
void add_write_buffer(void const* buf, size_t len);
void set_write_handler(handler_t h);
size_t read_some(bool clear_buffers);
void do_connect(tcp::endpoint const& ep, connect_handler_t h);
endpoint_type local_endpoint() const
{
error_code ec;
return local_endpoint(ec);
}
endpoint_type local_endpoint(error_code& ec) const;
endpoint_type remote_endpoint() const
{
error_code ec;
return remote_endpoint(ec);
}
endpoint_type remote_endpoint(error_code& ec) const;
std::size_t available() const;
std::size_t available(error_code& ec) const { return available(); }
asio::io_service& io_service()
{ return m_io_service; }
template <class Handler>
void async_connect(endpoint_type const& endpoint, Handler const& handler)
{
if (!endpoint.address().is_v4())
{
error_code ec = asio::error::operation_not_supported;
m_io_service.post(boost::bind<void>(handler, asio::error::operation_not_supported, 0));
return;
}
if (m_impl == 0)
{
m_io_service.post(boost::bind<void>(handler, asio::error::not_connected, 0));
return;
}
m_connect_handler = handler;
do_connect(endpoint, &utp_stream::on_connect);
}
template <class Mutable_Buffers, class Handler>
void async_read_some(Mutable_Buffers const& buffers, Handler const& handler)
{
if (m_impl == 0)
{
m_io_service.post(boost::bind<void>(handler, asio::error::not_connected, 0));
return;
}
TORRENT_ASSERT(!m_read_handler);
if (m_read_handler)
{
m_io_service.post(boost::bind<void>(handler, asio::error::operation_not_supported, 0));
return;
}
for (typename Mutable_Buffers::const_iterator i = buffers.begin()
, end(buffers.end()); i != end; ++i)
{
TORRENT_ASSERT(buffer_size(*i) > 0);
using asio::buffer_cast;
using asio::buffer_size;
add_read_buffer(buffer_cast<void*>(*i), buffer_size(*i));
}
m_read_handler = handler;
set_read_handler(&utp_stream::on_read);
}
void do_async_connect(endpoint_type const& ep
, boost::function<void(error_code const&)> const& handler);
template <class Protocol>
void open(Protocol const& p, error_code& ec)
{ m_open = true; }
template <class Protocol>
void open(Protocol const& p)
{ m_open = true; }
template <class Mutable_Buffers>
std::size_t read_some(Mutable_Buffers const& buffers, error_code& ec)
{
TORRENT_ASSERT(!m_read_handler);
if (m_impl == 0)
{
ec = asio::error::not_connected;
return 0;
}
if (read_buffer_size() == 0)
{
ec = asio::error::would_block;
return 0;
}
#ifdef TORRENT_DEBUG
int buf_size = 0;
#endif
for (typename Mutable_Buffers::const_iterator i = buffers.begin()
, end(buffers.end()); i != end; ++i)
{
using asio::buffer_cast;
using asio::buffer_size;
add_read_buffer(buffer_cast<void*>(*i), buffer_size(*i));
#ifdef TORRENT_DEBUG
buf_size += buffer_size(*i);
#endif
}
std::size_t ret = read_some(true);
TORRENT_ASSERT(int(ret) <= buf_size);
TORRENT_ASSERT(ret > 0);
return ret;
}
template <class Const_Buffers>
std::size_t write_some(Const_Buffers const& buffers, error_code& ec)
{
// TODO: implement
return 0;
}
template <class Const_Buffers, class Handler>
void async_write_some(Const_Buffers const& buffers, Handler const& handler)
{
if (m_impl == 0)
{
m_io_service.post(boost::bind<void>(handler, asio::error::not_connected, 0));
return;
}
TORRENT_ASSERT(!m_write_handler);
if (m_write_handler)
{
m_io_service.post(boost::bind<void>(handler, asio::error::operation_not_supported, 0));
return;
}
for (typename Const_Buffers::const_iterator i = buffers.begin()
, end(buffers.end()); i != end; ++i)
{
TORRENT_ASSERT(buffer_size(*i) > 0);
using asio::buffer_cast;
using asio::buffer_size;
add_write_buffer((void*)buffer_cast<void const*>(*i), buffer_size(*i));
}
m_write_handler = handler;
set_write_handler(&utp_stream::on_write);
}
//private:
void cancel_handlers(error_code const&);
boost::function1<void, error_code const&> m_connect_handler;
boost::function2<void, error_code const&, std::size_t> m_read_handler;
boost::function2<void, error_code const&, std::size_t> m_write_handler;
asio::io_service& m_io_service;
utp_socket_impl* m_impl;
bool m_open;
};
}
#endif

View File

@ -83,6 +83,12 @@ for l in f:
if 'std::string::append' in fun: fold = indentation
if 'getipnodebyname' == fun: fold = indentation
if '__gnu_debug::_Safe_iterator<std::' in fun: fold = indentation
if 'fflush' == fun: fold = indentation
if 'vfprintf' == fun: fold = indentation
if 'fprintf' == fun: fold = indentation
if 'BN_mod_exp' == fun: fold = indentation
if 'BN_CTX_free' == fun: fold = indentation
if 'cerror' == fun: fold = indentation
list = []
for k in fun_samples:

View File

@ -38,6 +38,8 @@ substitute_file('docs/manual.rst')
substitute_file('docs/building.rst')
substitute_file('docs/features.rst')
substitute_file('docs/contributing.rst')
substitute_file('docs/utp.rst')
substitute_file('docs/make_torrent.rst')
substitute_file('docs/tuning.rst')
substitute_file('Jamfile')

View File

@ -59,6 +59,7 @@ libtorrent_rasterbar_la_SOURCES = \
pe_crypto.cpp \
peer_connection.cpp \
piece_picker.cpp \
packet_buffer.cpp \
policy.cpp \
puff.cpp \
session.cpp \
@ -76,12 +77,15 @@ libtorrent_rasterbar_la_SOURCES = \
torrent_handle.cpp \
torrent_info.cpp \
time.cpp \
timestamp_history.cpp \
tracker_manager.cpp \
udp_socket.cpp \
udp_tracker_connection.cpp \
upnp.cpp \
ut_metadata.cpp \
ut_pex.cpp \
utp_socket_manager.cpp \
utp_stream.cpp \
web_peer_connection.cpp \
\
$(KADEMLIA_SOURCES) \

View File

@ -108,6 +108,18 @@ namespace libtorrent
#endif
}
TORRENT_EXPORT bool is_teredo(address const& addr)
{
#if TORRENT_USE_IPV6
if (!addr.is_v6()) return false;
boost::uint8_t teredo_prefix[] = {0x20, 0x01, 0, 0};
address_v6::bytes_type b = addr.to_v6().to_bytes();
return memcmp(&b[0], teredo_prefix, 4) == 0;
#else
return false;
#endif
}
bool supports_ipv6()
{
#if TORRENT_USE_IPV6

View File

@ -45,6 +45,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include "libtorrent/alert_types.hpp"
#include "libtorrent/invariant_check.hpp"
#include "libtorrent/io.hpp"
#include "libtorrent/socket_io.hpp"
#include "libtorrent/version.hpp"
#include "libtorrent/extensions.hpp"
#include "libtorrent/aux_/session_impl.hpp"
@ -258,7 +259,7 @@ namespace libtorrent
write_bitfield();
#ifndef TORRENT_DISABLE_DHT
if (m_supports_dht_port && m_ses.m_dht)
write_dht_port(m_ses.get_dht_settings().service_port);
write_dht_port(m_ses.m_external_udp_port);
#endif
}
@ -390,8 +391,9 @@ namespace libtorrent
if (is_queued()) p.flags |= peer_info::queued;
p.client = m_client_version;
p.connection_type = peer_info::standard_bittorrent;
p.connection_type = get_socket()->get<utp_stream>()
? peer_info::bittorrent_utp
: peer_info::standard_bittorrent;
}
bool bt_peer_connection::in_handshake() const
@ -657,6 +659,7 @@ namespace libtorrent
void bt_peer_connection::append_const_send_buffer(char const* buffer, int size)
{
TORRENT_ASSERT(!m_rc4_encrypted || send_buffer_size() == m_encrypted_bytes);
// if we're encrypting this buffer, we need to make a copy
// since we'll mutate it
#ifndef TORRENT_DISABLE_ENCRYPTION
@ -1349,7 +1352,7 @@ namespace libtorrent
m_supports_dht_port = true;
#ifndef TORRENT_DISABLE_DHT
if (m_supports_dht_port && m_ses.m_dht)
write_dht_port(m_ses.get_dht_settings().service_port);
write_dht_port(m_ses.m_external_udp_port);
#endif
}
}
@ -1443,6 +1446,203 @@ namespace libtorrent
incoming_allowed_fast(index);
}
// -----------------------------
// -------- RENDEZVOUS ---------
// -----------------------------
void bt_peer_connection::on_holepunch()
{
INVARIANT_CHECK;
if (!packet_finished()) return;
// we can't accept holepunch messages from peers
// that don't support the holepunch extension
// because we wouldn't be able to respond
if (m_holepunch_id == 0) return;
buffer::const_interval recv_buffer = receive_buffer();
TORRENT_ASSERT(*recv_buffer.begin == msg_extended);
++recv_buffer.begin;
TORRENT_ASSERT(*recv_buffer.begin == holepunch_msg);
++recv_buffer.begin;
const char* ptr = recv_buffer.begin;
// ignore invalid messages
if (recv_buffer.left() < 2) return;
int msg_type = detail::read_uint8(ptr);
int addr_type = detail::read_uint8(ptr);
tcp::endpoint ep;
if (addr_type == 0)
{
if (recv_buffer.left() < 2 + 4 + 2) return;
// IPv4 address
ep = detail::read_v4_endpoint<tcp::endpoint>(ptr);
}
#if TORRENT_USE_IPV6
else if (addr_type == 1)
{
// IPv6 address
if (recv_buffer.left() < 2 + 18 + 2) return;
ep = detail::read_v6_endpoint<tcp::endpoint>(ptr);
}
#endif
else
{
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING
error_code ec;
static const char* hp_msg_name[] = {"rendezvous", "connect", "failed"};
(*m_logger) << time_now_string() << " <== HOLEPUNCH [ msg:"
<< (msg_type >= 0 && msg_type < 3 ? hp_msg_name[msg_type] : "unknown message type")
<< " from:" << remote().address().to_string(ec)
<< " to: unknown address type ]\n";
#endif
return; // unknown address type
}
boost::shared_ptr<torrent> t = associated_torrent().lock();
if (!t) return;
switch (msg_type)
{
case hp_rendezvous: // rendezvous
{
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING
error_code ec;
(*m_logger) << time_now_string() << " <== HOLEPUNCH [ msg:rendezvous"
<< " to:" << ep.address().to_string(ec) << " ]\n";
#endif
// this peer is asking us to introduce it to
// the peer at 'ep'. We need to find which of
// our connections points to that endpoint
bt_peer_connection* p = t->find_peer(ep);
if (p == 0)
{
// we're not connected to this peer
write_holepunch_msg(hp_failed, ep, hp_not_connected);
break;
}
if (!p->supports_holepunch())
{
write_holepunch_msg(hp_failed, ep, hp_no_support);
break;
}
if (p == this)
{
write_holepunch_msg(hp_failed, ep, hp_no_self);
break;
}
write_holepunch_msg(hp_connect, ep, 0);
p->write_holepunch_msg(hp_connect, remote(), 0);
} break;
case hp_connect:
{
// add or find the peer with this endpoint
policy::peer* p = t->get_policy().add_peer(ep, peer_id(0), peer_info::pex, 0);
if (p == 0 || p->connection)
{
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING
error_code ec;
(*m_logger) << time_now_string() << " <== HOLEPUNCH [ msg:connect"
<< " to:" << ep.address().to_string(ec) << " error:failed to add peer ]\n";
#endif
// we either couldn't add this peer, or it's
// already connected. Just ignore the connect message
break;
}
if (p->banned)
{
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING
error_code ec;
(*m_logger) << time_now_string() << " <== HOLEPUNCH [ msg:connect"
<< " to:" << ep.address().to_string(ec) << " error:peer banned ]\n";
#endif
// this peer is banned, don't connect to it
break;
}
// to make sure we use the uTP protocol
p->supports_utp = true;
// #error make sure we make this a connection candidate
// in case it has too many failures for instance
t->connect_to_peer(p, true);
// mark this connection to be in holepunch mode
// so that it will retry faster and stick to uTP while it's
// retrying
if (p->connection)
p->connection->set_holepunch_mode();
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING
error_code ec;
(*m_logger) << time_now_string() << " <== HOLEPUNCH [ msg:connect"
<< " to:" << ep.address().to_string(ec) << " ]\n";
#endif
} break;
case hp_failed:
{
boost::uint32_t error = detail::read_uint32(ptr);
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING
error_code ec;
char const* err_msg[] = {"no such peer", "not connected", "no support", "no self"};
(*m_logger) << time_now_string() << " <== HOLEPUNCH [ msg:failed"
" error:" << error <<
" msg:" << ((error >= 0 && error < 4)?err_msg[error]:"unknown message id") <<
" ]\n";
#endif
// #error deal with holepunch errors
} break;
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING
default:
{
error_code ec;
(*m_logger) << time_now_string() << " <== HOLEPUNCH ["
" msg:unknown message type (" << msg_type << ")"
<< " to:" << ep.address().to_string(ec) << " ]\n";
}
#endif
}
}
void bt_peer_connection::write_holepunch_msg(int type, tcp::endpoint const& ep, int error)
{
char buf[35];
char* ptr = buf + 6;
detail::write_uint8(type, ptr);
if (ep.address().is_v4()) detail::write_uint8(0, ptr);
else detail::write_uint8(1, ptr);
detail::write_endpoint(ep, ptr);
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING
error_code ec;
static const char* hp_msg_name[] = {"rendezvous", "connect", "failed"};
static const char* hp_error_string[] = {"", "no such peer", "not connected", "no support", "no self"};
(*m_logger) << time_now_string() << " ==> HOLEPUNCH [ msg:"
<< (type >= 0 && type < 3 ? hp_msg_name[type] : "unknown message type")
<< " to:" << ep.address().to_string(ec)
<< " error:" << hp_error_string[error]
<< " ]\n";
#endif
if (type == hp_failed)
{
detail::write_uint32(error, ptr);
}
// write the packet length and type
char* hdr = buf;
detail::write_uint32(ptr - buf - 4, hdr);
detail::write_uint8(msg_extended, hdr);
detail::write_uint8(m_holepunch_id, hdr);
TORRENT_ASSERT(ptr <= buf + sizeof(buf));
send_buffer(buf, ptr - buf);
}
// -----------------------------
// --------- EXTENDED ----------
// -----------------------------
@ -1479,6 +1679,31 @@ namespace libtorrent
return;
}
if (extended_id == upload_only_msg)
{
if (!packet_finished()) return;
bool ul = detail::read_uint8(recv_buffer.begin);
#ifdef TORRENT_VERBOSE_LOGGING
(*m_logger) << time_now_string() << " <== UPLOAD_ONLY [ " << (ul?"true":"false") << " ]\n";
#endif
set_upload_only(ul);
return;
}
if (extended_id == holepunch_msg)
{
if (!packet_finished()) return;
on_holepunch();
return;
}
#ifdef TORRENT_VERBOSE_LOGGING
if (packet_finished())
(*m_logger) << time_now_string() << " <== EXTENSION MESSAGE ["
" msg:" << extended_id <<
" size:" << packet_size() << " ]\n";
#endif
#ifndef TORRENT_DISABLE_EXTENSIONS
for (extension_list_t::iterator i = m_extensions.begin()
, end(m_extensions.end()); i != end; ++i)
@ -1549,7 +1774,10 @@ namespace libtorrent
// upload_only
if (lazy_entry const* m = root.dict_find_dict("m"))
{
m_upload_only_id = m->dict_find_int_value("upload_only", 0);
m_holepunch_id = m->dict_find_int_value("ut_holepunch", 0);
}
// there is supposed to be a remote listen port
int listen_port = root.dict_find_int_value("p");
@ -1929,7 +2157,9 @@ namespace libtorrent
TORRENT_ASSERT(t);
m["upload_only"] = upload_only_msg;
m["ut_holepunch"] = holepunch_msg;
m["share_mode"] = share_mode_msg;
int complete_ago = -1;
if (t->last_seen_complete() > 0) complete_ago = t->time_since_complete();
handshake["complete_ago"] = complete_ago;
@ -1968,6 +2198,19 @@ namespace libtorrent
(*i)->add_handshake(handshake);
}
#ifndef NDEBUG
// make sure there are not conflicting extensions
std::set<int> ext;
for (entry::dictionary_type::const_iterator i = m.begin()
, end(m.end()); i != end; ++i)
{
if (i->second.type() != entry::int_t) continue;
int val = i->second.integer();
TORRENT_ASSERT(ext.find(val) == ext.end());
ext.insert(val);
}
#endif
std::vector<char> msg;
bencode(std::back_inserter(msg), handshake);
@ -1986,9 +2229,9 @@ namespace libtorrent
TORRENT_ASSERT(i.begin == i.end);
#if defined TORRENT_VERBOSE_LOGGING && TORRENT_USE_IOSTREAM
std::stringstream ext;
handshake.print(ext);
(*m_logger) << time_now_string() << " ==> EXTENDED HANDSHAKE: \n" << ext.str();
std::stringstream handshake_str;
handshake.print(handshake_str);
(*m_logger) << time_now_string() << " ==> EXTENDED HANDSHAKE: \n" << handshake_str.str();
#endif
setup_send();
@ -2198,6 +2441,8 @@ namespace libtorrent
(*m_logger) << time_now_string() << " received DH key\n";
#endif
TORRENT_ASSERT(!m_rc4_encrypted || send_buffer_size() == m_encrypted_bytes);
// PadA/B can be a max of 512 bytes, and 20 bytes more for
// the sync hash (if incoming), or 8 bytes more for the
// encrypted verification constant (if outgoing). Instead
@ -2981,7 +3226,7 @@ namespace libtorrent
write_bitfield();
#ifndef TORRENT_DISABLE_DHT
if (m_supports_dht_port && m_ses.m_dht)
write_dht_port(m_ses.get_dht_settings().service_port);
write_dht_port(m_ses.m_external_udp_port);
#endif
}

View File

@ -33,6 +33,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include "libtorrent/config.hpp"
#include <boost/bind.hpp>
#include <vector>
#include <stdlib.h> // for wcstombscstombs
#include "libtorrent/enum_net.hpp"
#include "libtorrent/broadcast_socket.hpp"
#include "libtorrent/error_code.hpp"
@ -84,31 +85,44 @@ POSSIBILITY OF SUCH DAMAGE.
namespace libtorrent { namespace
{
address inaddr_to_address(in_addr const* ina)
address inaddr_to_address(in_addr const* ina, int len = 4)
{
typedef asio::ip::address_v4::bytes_type bytes_t;
bytes_t b;
std::memcpy(&b[0], ina, b.size());
std::memset(&b[0], 0, b.size());
if (len > 0) std::memcpy(&b[0], ina, (std::min)(len, int(b.size())));
return address_v4(b);
}
#if TORRENT_USE_IPV6
address inaddr6_to_address(in6_addr const* ina6)
address inaddr6_to_address(in6_addr const* ina6, int len = 16)
{
typedef asio::ip::address_v6::bytes_type bytes_t;
bytes_t b;
std::memcpy(&b[0], ina6, b.size());
std::memset(&b[0], 0, b.size());
if (len > 0) std::memcpy(&b[0], ina6, (std::min)(len, int(b.size())));
return address_v6(b);
}
#endif
address sockaddr_to_address(sockaddr const* sin)
int sockaddr_len(sockaddr const* sin)
{
if (sin->sa_family == AF_INET)
return inaddr_to_address(&((sockaddr_in const*)sin)->sin_addr);
#if defined TORRENT_WINDOWS || TORRENT_MINGW || defined TORRENT_LINUX
return sin->sa_family == AF_INET ? sizeof(sockaddr_in) : sizeof(sockaddr_in6);
#else
return sin->sa_len;
#endif
}
address sockaddr_to_address(sockaddr const* sin, int assume_family = -1)
{
if (sin->sa_family == AF_INET || assume_family == AF_INET)
return inaddr_to_address(&((sockaddr_in const*)sin)->sin_addr
, sockaddr_len(sin) - offsetof(sockaddr, sa_data));
#if TORRENT_USE_IPV6
else if (sin->sa_family == AF_INET6)
return inaddr6_to_address(&((sockaddr_in6 const*)sin)->sin6_addr);
else if (sin->sa_family == AF_INET6 || assume_family == AF_INET6)
return inaddr6_to_address(&((sockaddr_in6 const*)sin)->sin6_addr
, sockaddr_len(sin) - offsetof(sockaddr, sa_data));
#endif
return address();
}
@ -142,13 +156,15 @@ namespace libtorrent { namespace
return msg_len;
}
bool parse_route(nlmsghdr* nl_hdr, ip_route* rt_info)
bool parse_route(int s, nlmsghdr* nl_hdr, ip_route* rt_info)
{
rtmsg* rt_msg = (rtmsg*)NLMSG_DATA(nl_hdr);
if((rt_msg->rtm_family != AF_INET) || (rt_msg->rtm_table != RT_TABLE_MAIN))
if((rt_msg->rtm_family != AF_INET && rt_msg->rtm_family != AF_INET6) || (rt_msg->rtm_table != RT_TABLE_MAIN
&& rt_msg->rtm_table != RT_TABLE_LOCAL))
return false;
int if_index = 0;
int rt_len = RTM_PAYLOAD(nl_hdr);
for (rtattr* rt_attr = (rtattr*)RTM_RTA(rt_msg);
RTA_OK(rt_attr,rt_len); rt_attr = RTA_NEXT(rt_attr,rt_len))
@ -156,23 +172,52 @@ namespace libtorrent { namespace
switch(rt_attr->rta_type)
{
case RTA_OIF:
if_indextoname(*(int*)RTA_DATA(rt_attr), rt_info->name);
if_index = *(int*)RTA_DATA(rt_attr);
break;
case RTA_GATEWAY:
rt_info->gateway = address_v4(ntohl(*(u_int*)RTA_DATA(rt_attr)));
#if TORRENT_USE_IPV6
if (rt_msg->rtm_family == AF_INET6)
{
rt_info->gateway = inaddr6_to_address((in6_addr*)RTA_DATA(rt_attr));
}
else
#endif
{
rt_info->gateway = inaddr_to_address((in_addr*)RTA_DATA(rt_attr));
}
break;
case RTA_DST:
rt_info->destination = address_v4(ntohl(*(u_int*)RTA_DATA(rt_attr)));
#if TORRENT_USE_IPV6
if (rt_msg->rtm_family == AF_INET6)
{
rt_info->destination = inaddr6_to_address((in6_addr*)RTA_DATA(rt_attr));
}
else
#endif
{
rt_info->destination = inaddr_to_address((in_addr*)RTA_DATA(rt_attr));
}
break;
}
}
if_indextoname(if_index, rt_info->name);
ifreq req;
memset(&req, 0, sizeof(req));
if_indextoname(if_index, req.ifr_name);
ioctl(s, SIOCGIFMTU, &req);
rt_info->mtu = req.ifr_mtu;
// obviously this doesn't work correctly. How do you get the netmask for a route?
// if (ioctl(s, SIOCGIFNETMASK, &req) == 0) {
// rt_info->netmask = sockaddr_to_address(&req.ifr_addr, req.ifr_addr.sa_family);
// }
return true;
}
#endif
#if defined TORRENT_BSD
bool parse_route(rt_msghdr* rtm, ip_route* rt_info)
bool parse_route(int s, rt_msghdr* rtm, ip_route* rt_info)
{
sockaddr* rti_info[RTAX_MAX];
sockaddr* sa = (sockaddr*)(rtm + 1);
@ -205,9 +250,17 @@ namespace libtorrent { namespace
return false;
rt_info->gateway = sockaddr_to_address(rti_info[RTAX_GATEWAY]);
rt_info->netmask = sockaddr_to_address(rti_info[RTAX_NETMASK]);
rt_info->destination = sockaddr_to_address(rti_info[RTAX_DST]);
rt_info->netmask = sockaddr_to_address(rti_info[RTAX_NETMASK]
, rt_info->destination.is_v4() ? AF_INET : AF_INET6);
if_indextoname(rtm->rtm_index, rt_info->name);
ifreq req;
memset(&req, 0, sizeof(req));
if_indextoname(rtm->rtm_index, req.ifr_name);
if (ioctl(s, SIOCGIFMTU, &req) < 0) return false;
rt_info->mtu = req.ifr_mtu;
return true;
}
#endif
@ -231,17 +284,29 @@ namespace libtorrent { namespace
namespace libtorrent
{
bool in_subnet(address const& addr, ip_interface const& iface)
// return (a1 & mask) == (a2 & mask)
bool match_addr_mask(address const& a1, address const& a2, address const& mask)
{
if (addr.is_v4() != iface.interface_address.is_v4()) return false;
// since netmasks seems unreliable for IPv6 interfaces
// (MacOS X returns AF_INET addresses as bitmasks) assume
// that any IPv6 address belongs to the subnet of any
// interface with an IPv6 address
if (addr.is_v6()) return true;
// all 3 addresses needs to belong to the same family
if (a1.is_v4() != a2.is_v4()) return false;
if (a1.is_v4() != mask.is_v4()) return false;
return (addr.to_v4().to_ulong() & iface.netmask.to_v4().to_ulong())
== (iface.interface_address.to_v4().to_ulong() & iface.netmask.to_v4().to_ulong());
#if TORRENT_USE_IPV6
if (a1.is_v6())
{
address_v6::bytes_type b1;
address_v6::bytes_type b2;
address_v6::bytes_type m;
b1 = a1.to_v6().to_bytes();
b2 = a2.to_v6().to_bytes();
m = mask.to_v6().to_bytes();
for (int i = 0; i < b1.size(); ++i)
b1[i] &= m[i];
return memcmp(&b1[0], &b2[0], b1.size());
}
#endif
return (a1.to_v4().to_ulong() & mask.to_v4().to_ulong())
== (a2.to_v4().to_ulong() & mask.to_v4().to_ulong());
}
bool in_local_network(io_service& ios, address const& addr, error_code& ec)
@ -251,10 +316,56 @@ namespace libtorrent
for (std::vector<ip_interface>::iterator i = net.begin()
, end(net.end()); i != end; ++i)
{
if (in_subnet(addr, *i)) return true;
if (match_addr_mask(addr, i->interface_address, i->netmask)) return true;
}
return false;
}
#if defined TORRENT_WINDOWS || defined TORRENT_MINGW
address build_netmask(int bits, int family)
{
if (family == AF_INET)
{
typedef asio::ip::address_v4::bytes_type bytes_t;
bytes_t b;
std::memset(&b[0], 0xff, b.size());
for (int i = sizeof(bytes_t)/8-1; i > 0; --i)
{
if (bits < 8)
{
b[i] <<= bits;
break;
}
b[i] = 0;
bits -= 8;
}
return address_v4(b);
}
#if TORRENT_USE_IPV6
else if (family == AF_INET6)
{
typedef asio::ip::address_v6::bytes_type bytes_t;
bytes_t b;
std::memset(&b[0], 0xff, b.size());
for (int i = sizeof(bytes_t)/8-1; i > 0; --i)
{
if (bits < 8)
{
b[i] <<= bits;
break;
}
b[i] = 0;
bits -= 8;
}
return address_v6(b);
}
#endif
else
{
return address();
}
}
#endif
std::vector<ip_interface> enum_net_interfaces(io_service& ios, error_code& ec)
{
@ -295,8 +406,20 @@ namespace libtorrent
iface.interface_address = sockaddr_to_address(&item.ifr_addr);
strcpy(iface.name, item.ifr_name);
ifreq netmask = item;
if (ioctl(s, SIOCGIFNETMASK, &netmask) < 0)
ifreq req;
memset(&req, 0, sizeof(req));
strncpy(req.ifr_name, item.ifr_name, IF_NAMESIZE);
if (ioctl(s, SIOCGIFMTU, &req) < 0)
{
ec = error_code(errno, asio::error::system_category);
close(s);
return ret;
}
iface.mtu = req.ifr_mtu;
memset(&req, 0, sizeof(req));
strncpy(req.ifr_name, item.ifr_name, IF_NAMESIZE);
if (ioctl(s, SIOCGIFNETMASK, &req) < 0)
{
#if TORRENT_USE_IPV6
if (iface.interface_address.is_v6())
@ -314,7 +437,7 @@ namespace libtorrent
}
else
{
iface.netmask = sockaddr_to_address(&netmask.ifr_addr);
iface.netmask = sockaddr_to_address(&req.ifr_addr, item.ifr_addr.sa_family);
}
ret.push_back(iface);
}
@ -331,6 +454,65 @@ namespace libtorrent
#elif defined TORRENT_WINDOWS || defined TORRENT_MINGW
// Load Iphlpapi library
HMODULE iphlp = LoadLibraryA("Iphlpapi.dll");
if (iphlp)
{
// Get GetAdaptersAddresses() pointer
typedef ULONG (WINAPI *GetAdaptersAddresses_t)(ULONG,ULONG,PVOID,PIP_ADAPTER_ADDRESSES,PULONG);
GetAdaptersAddresses_t GetAdaptersAddresses = (GetAdaptersAddresses_t)GetProcAddress(
iphlp, "GetAdaptersAddresses");
if (GetAdaptersAddresses)
{
PIP_ADAPTER_ADDRESSES adapter_addresses = 0;
ULONG out_buf_size = 0;
if (GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER
| GAA_FLAG_SKIP_ANYCAST, NULL, adapter_addresses, &out_buf_size) != ERROR_BUFFER_OVERFLOW)
{
FreeLibrary(iphlp);
ec = asio::error::operation_not_supported;
return std::vector<ip_interface>();
}
adapter_addresses = (IP_ADAPTER_ADDRESSES*)malloc(out_buf_size);
if (!adapter_addresses)
{
FreeLibrary(iphlp);
ec = asio::error::no_memory;
return std::vector<ip_interface>();
}
if (GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER
| GAA_FLAG_SKIP_ANYCAST, NULL, adapter_addresses, &out_buf_size) == NO_ERROR)
{
for (PIP_ADAPTER_ADDRESSES adapter = adapter_addresses;
adapter != 0; adapter = adapter->Next)
{
ip_interface r;
strncpy(r.name, adapter->AdapterName, sizeof(r.name));
r.name[sizeof(r.name)-1] = 0;
r.mtu = adapter->Mtu;
IP_ADAPTER_UNICAST_ADDRESS* unicast = adapter->FirstUnicastAddress;
while (unicast)
{
r.interface_address = sockaddr_to_address(unicast->Address.lpSockaddr);
ret.push_back(r);
unicast = unicast->Next;
}
}
}
// Free memory
free(adapter_addresses);
FreeLibrary(iphlp);
return ret;
}
FreeLibrary(iphlp);
}
SOCKET s = socket(AF_INET, SOCK_DGRAM, 0);
if (s == SOCKET_ERROR)
{
@ -356,9 +538,11 @@ namespace libtorrent
for (int i = 0; i < n; ++i)
{
iface.interface_address = sockaddr_to_address(&buffer[i].iiAddress.Address);
iface.netmask = sockaddr_to_address(&buffer[i].iiNetmask.Address);
iface.name[0] = 0;
if (iface.interface_address == address_v4::any()) continue;
iface.netmask = sockaddr_to_address(&buffer[i].iiNetmask.Address
, iface.interface_address.is_v4() ? AF_INET : AF_INET6);
iface.name[0] = 0;
iface.mtu = 1500; // how to get the MTU?
ret.push_back(iface);
}
@ -372,6 +556,7 @@ namespace libtorrent
for (;i != udp::resolver_iterator(); ++i)
{
iface.interface_address = i->endpoint().address();
iface.mtu = 1500;
if (iface.interface_address.is_v4())
iface.netmask = address_v4::netmask(iface.interface_address.to_v4());
ret.push_back(iface);
@ -528,6 +713,12 @@ namespace libtorrent
char* end = buf.get() + needed;
int s = socket(AF_INET, SOCK_DGRAM, 0);
if (s < 0)
{
ec = error_code(errno, asio::error::system_category);
return std::vector<ip_route>();
}
rt_msghdr* rtm;
for (char* next = buf.get(); next < end; next += rtm->rtm_msglen)
{
@ -536,11 +727,13 @@ namespace libtorrent
continue;
ip_route r;
if (parse_route(rtm, &r)) ret.push_back(r);
if (parse_route(s, rtm, &r)) ret.push_back(r);
}
close(s);
#elif defined TORRENT_WINDOWS || defined TORRENT_MINGW
/*
move this to enum_net_interfaces
// Load Iphlpapi library
HMODULE iphlp = LoadLibraryA("Iphlpapi.dll");
if (!iphlp)
@ -596,11 +789,109 @@ namespace libtorrent
ret.push_back(r);
}
}
// Free memory
free(adapter_info);
FreeLibrary(iphlp);
*/
// Load Iphlpapi library
HMODULE iphlp = LoadLibraryA("Iphlpapi.dll");
if (!iphlp)
{
ec = asio::error::operation_not_supported;
return std::vector<ip_route>();
}
typedef DWORD (WINAPI *GetIpForwardTable2_t)(
ADDRESS_FAMILY, PMIB_IPFORWARD_TABLE2*);
typedef void (WINAPI *FreeMibTable_t)(PVOID Memory);
GetIpForwardTable2_t GetIpForwardTable2 = (GetIpForwardTable2_t)GetProcAddress(
iphlp, "GetIpForwardTable2");
FreeMibTable_t FreeMibTable = (FreeMibTable_t)GetProcAddress(
iphlp, "FreeMibTable");
if (GetIpForwardTable2 && FreeMibTable)
{
MIB_IPFORWARD_TABLE2* routes = NULL;
int res = GetIpForwardTable2(AF_UNSPEC, &routes);
if (res == NO_ERROR)
{
for (int i = 0; i < routes->NumEntries; ++i)
{
ip_route r;
r.gateway = sockaddr_to_address((const sockaddr*)&routes->Table[i].NextHop);
r.destination = sockaddr_to_address(
(const sockaddr*)&routes->Table[i].DestinationPrefix.Prefix);
r.netmask = build_netmask(routes->Table[i].SitePrefixLength
, routes->Table[i].DestinationPrefix.Prefix.si_family);
MIB_IFROW ifentry;
ifentry.dwIndex = routes->Table[i].InterfaceIndex;
if (GetIfEntry(&ifentry) == NO_ERROR)
{
wcstombs(r.name, ifentry.wszName, sizeof(r.name));
r.mtu = ifentry.dwMtu;
ret.push_back(r);
}
}
}
if (routes) FreeMibTable(routes);
FreeLibrary(iphlp);
return ret;
}
// Get GetIpForwardTable() pointer
typedef DWORD (WINAPI *GetIpForwardTable_t)(PMIB_IPFORWARDTABLE pIpForwardTable,PULONG pdwSize,BOOL bOrder);
GetIpForwardTable_t GetIpForwardTable = (GetIpForwardTable_t)GetProcAddress(
iphlp, "GetIpForwardTable");
if (!GetIpForwardTable)
{
FreeLibrary(iphlp);
ec = asio::error::operation_not_supported;
return std::vector<ip_route>();
}
MIB_IPFORWARDTABLE* routes = NULL;
ULONG out_buf_size = 0;
if (GetIpForwardTable(routes, &out_buf_size, FALSE) != ERROR_INSUFFICIENT_BUFFER)
{
FreeLibrary(iphlp);
ec = asio::error::operation_not_supported;
return std::vector<ip_route>();
}
routes = (MIB_IPFORWARDTABLE*)malloc(out_buf_size);
if (!routes)
{
FreeLibrary(iphlp);
ec = asio::error::no_memory;
return std::vector<ip_route>();
}
if (GetIpForwardTable(routes, &out_buf_size, FALSE) == NO_ERROR)
{
for (int i = 0; i < routes->dwNumEntries; ++i)
{
ip_route r;
r.destination = inaddr_to_address((in_addr const*)&routes->table[i].dwForwardDest);
r.netmask = inaddr_to_address((in_addr const*)&routes->table[i].dwForwardMask);
r.gateway = inaddr_to_address((in_addr const*)&routes->table[i].dwForwardNextHop);
MIB_IFROW ifentry;
ifentry.dwIndex = routes->table[i].dwForwardIfIndex;
if (GetIfEntry(&ifentry) == NO_ERROR)
{
wcstombs(r.name, ifentry.wszName, sizeof(r.name));
r.name[sizeof(r.name)-1] = 0;
r.mtu = ifentry.dwMtu;
ret.push_back(r);
}
}
}
// Free memory
free(routes);
FreeLibrary(iphlp);
#elif defined TORRENT_LINUX
enum { BUFSIZE = 8192 };
@ -639,11 +930,18 @@ namespace libtorrent
return std::vector<ip_route>();
}
int s = socket(AF_INET, SOCK_DGRAM, 0);
if (s < 0)
{
ec = error_code(errno, asio::error::system_category);
return std::vector<ip_route>();
}
for (; NLMSG_OK(nl_msg, len); nl_msg = NLMSG_NEXT(nl_msg, len))
{
ip_route r;
if (parse_route(nl_msg, &r)) ret.push_back(r);
if (parse_route(s, nl_msg, &r)) ret.push_back(r);
}
close(s);
close(sock);
#endif

View File

@ -156,7 +156,7 @@ namespace libtorrent
"pex message too large",
"invalid pex message",
"invalid lt_tracker message",
"",
"pex messages sent too frequent (possible attack)",
"",
"",
"",

View File

@ -35,6 +35,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include "libtorrent/socket.hpp"
#include "libtorrent/session_settings.hpp"
#include "libtorrent/socket_type.hpp"
#include "libtorrent/utp_socket_manager.hpp"
#include <boost/shared_ptr.hpp>
#include <stdexcept>
@ -43,9 +44,15 @@ namespace libtorrent
bool instantiate_connection(io_service& ios
, proxy_settings const& ps, socket_type& s
, void* ssl_context)
, void* ssl_context
, utp_socket_manager* sm)
{
if (ps.type == proxy_settings::none)
if (sm)
{
s.instantiate<utp_stream>(ios);
s.get<utp_stream>()->set_impl(sm->new_utp_socket(s.get<utp_stream>()));
}
else if (ps.type == proxy_settings::none)
{
#ifdef TORRENT_USE_OPENSSL
if (ssl_context)

View File

@ -248,6 +248,8 @@ namespace libtorrent { namespace
, m_tp(tp)
{}
virtual char const* type() const { return "LT_metadata"; }
// can add entries to the extension handshake
virtual void add_handshake(entry& h)
{

189
src/packet_buffer.cpp Normal file
View File

@ -0,0 +1,189 @@
/*
Copyright (c) 2010, Arvid Norberg
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
* Neither the name of the author nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdlib.h> // free and calloc
#include "libtorrent/packet_buffer.hpp"
#include "libtorrent/assert.hpp"
namespace libtorrent {
bool compare_less_wrap(boost::uint32_t lhs, boost::uint32_t rhs
, boost::uint32_t mask);
packet_buffer::packet_buffer()
: m_storage(0)
, m_capacity(0)
, m_size(0)
, m_first(0)
, m_last(0)
{}
packet_buffer::~packet_buffer()
{
free(m_storage);
}
void* packet_buffer::insert(index_type idx, void* value)
{
TORRENT_ASSERT_VAL(idx <= 0xffff, idx);
// you're not allowed to insert NULLs!
TORRENT_ASSERT(value);
if (m_size != 0)
{
if (compare_less_wrap(idx, m_first, 0xffff))
{
// Index comes before m_first. If we have room, we can simply
// adjust m_first backward.
std::size_t free_space = 0;
for (index_type i = (m_first - 1) & (m_capacity - 1);
i != (m_first & (m_capacity - 1)); i = (i - 1) & (m_capacity - 1))
{
if (m_storage[i & (m_capacity - 1)])
break;
++free_space;
}
if (((m_first - idx) & 0xffff) > free_space)
reserve(((m_first - idx) & 0xffff) + m_capacity - free_space);
m_first = idx;
}
else if (idx >= m_first + m_capacity)
{
reserve(idx - m_first + 1);
}
else if (idx < m_first)
{
// We have wrapped.
if (idx > ((m_first + m_capacity) & 0xffff) && m_capacity < 0xffff)
{
reserve(m_capacity + (idx - ((m_first + m_capacity) & 0xffff)));
}
}
if (compare_less_wrap(m_last, (idx + 1) & 0xffff, 0xffff))
m_last = (idx + 1) & 0xffff;
}
else
{
m_first = idx;
m_last = (idx + 1) & 0xffff;
}
if (m_capacity == 0) reserve(16);
void* old_value = m_storage[idx & (m_capacity - 1)];
m_storage[idx & (m_capacity - 1)] = value;
if (m_size++ == 0)
{
m_first = idx;
}
TORRENT_ASSERT_VAL(m_first <= 0xffff, m_first);
return old_value;
}
void* packet_buffer::at(index_type idx) const
{
if (idx >= m_first + m_capacity)
return 0;
if (compare_less_wrap(idx, m_first, 0xffff))
{
return 0;
}
return m_storage[idx & (m_capacity - 1)];
}
void packet_buffer::reserve(std::size_t size)
{
TORRENT_ASSERT_VAL(size <= 0xffff, size);
std::size_t new_size = m_capacity == 0 ? 16 : m_capacity;
while (new_size < size)
new_size <<= 1;
void** new_storage = (void**)malloc(sizeof(void*) * new_size);
for (index_type i = 0; i < new_size; ++i)
new_storage[i] = 0;
for (index_type i = m_first; i < (m_first + m_capacity); ++i)
new_storage[i & (new_size - 1)] = m_storage[i & (m_capacity - 1)];
free(m_storage);
m_storage = new_storage;
m_capacity = new_size;
}
void* packet_buffer::remove(index_type idx)
{
// TODO: use compare_less_wrap for this comparison as well
if (idx >= m_first + m_capacity)
return 0;
if (compare_less_wrap(idx, m_first, 0xffff))
return 0;
void* old_value = m_storage[idx & (m_capacity - 1)];
m_storage[idx & (m_capacity - 1)] = 0;
if (old_value)
{
--m_size;
if (m_size == 0) m_last = m_first;
}
if (idx == m_first && m_size != 0)
{
while (!m_storage[++m_first & (m_capacity - 1)]);
m_first &= 0xffff;
}
if (((idx + 1) & 0xffff) == m_last && m_size != 0)
{
while (!m_storage[--m_last & (m_capacity - 1)]);
++m_last;
m_last &= 0xffff;
}
TORRENT_ASSERT_VAL(m_first <= 0xffff, m_first);
return old_value;
}
}

View File

@ -53,6 +53,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include "libtorrent/broadcast_socket.hpp"
#include "libtorrent/torrent.hpp"
#include "libtorrent/peer_info.hpp"
#include "libtorrent/bt_peer_connection.hpp"
#ifdef TORRENT_DEBUG
#include <set>
@ -150,6 +151,7 @@ namespace libtorrent
, m_bitfield_received(false)
, m_no_download(false)
, m_sent_suggests(false)
, m_holepunch_mode(false)
, m_ignore_stats(false)
#ifdef TORRENT_DEBUG
, m_in_constructor(true)
@ -195,6 +197,8 @@ namespace libtorrent
+ to_string(m_remote.port()).elems, m_ses.listen_port());
(*m_logger) << time_now_string() << " *** OUTGOING CONNECTION: "
<< print_endpoint(m_remote) << "\n";
if (m_socket->get<utp_stream>()) (*m_logger) << "uTP connection\n";
else (*m_logger) << "TCP connection\n";
#endif
#ifdef TORRENT_DEBUG
piece_failed = false;
@ -289,6 +293,7 @@ namespace libtorrent
, m_bitfield_received(false)
, m_no_download(false)
, m_sent_suggests(false)
, m_holepunch_mode(false)
, m_ignore_stats(false)
#ifdef TORRENT_DEBUG
, m_in_constructor(true)
@ -335,6 +340,8 @@ namespace libtorrent
+ to_string(remote().port()).elems, m_ses.listen_port());
(*m_logger) << time_now_string() << " *** INCOMING CONNECTION: "
<< print_endpoint(m_remote) << "\n";
if (m_socket->get<utp_stream>()) (*m_logger) << "uTP connection\n";
else (*m_logger) << "TCP connection\n";
#endif
#ifndef TORRENT_DISABLE_GEO_IP
@ -557,6 +564,7 @@ namespace libtorrent
return;
}
m_remote = m_socket->remote_endpoint(ec);
TORRENT_ASSERT(m_remote.address() != address_v4::any());
if (ec)
{
disconnect(ec);
@ -626,6 +634,16 @@ namespace libtorrent
{
m_extensions.push_back(ext);
}
peer_plugin const* peer_connection::find_plugin(char const* type)
{
for (extension_list_t::iterator i = m_extensions.begin()
, end(m_extensions.end()); i != end; ++i)
{
if (strcmp((*i)->type(), type) == 0) return (*i).get();
}
return 0;
}
#endif
void peer_connection::send_allowed_set()
@ -2215,6 +2233,8 @@ namespace libtorrent
if (!m_bitfield_received) incoming_have_none();
if (is_disconnecting()) return;
update_desired_queue_size();
#ifndef TORRENT_DISABLE_EXTENSIONS
for (extension_list_t::iterator i = m_extensions.begin()
, end(m_extensions.end()); i != end; ++i)
@ -3238,13 +3258,62 @@ namespace libtorrent
TORRENT_ASSERT(m_ses.is_network_thread());
TORRENT_ASSERT(m_connecting);
connect_failed(errors::timed_out);
}
void peer_connection::connect_failed(error_code const& e)
{
TORRENT_ASSERT(m_connecting);
TORRENT_ASSERT(e);
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING
peer_log("CONNECTION TIMED OUT: %s", print_endpoint(m_remote).c_str());
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 TIMED OUT: " << print_endpoint(m_remote) << "\n";
(*m_ses.m_logger) << "CONNECTION FAILED: " << print_endpoint(m_remote) << "\n";
#endif
disconnect(errors::timed_out, 1);
if (m_connection_ticket != -1)
{
m_ses.m_half_open.done(m_connection_ticket);
m_connecting = false;
}
// a connection attempt using uTP just failed
// mark this peer as not supporting uTP
// we'll never try it again (unless we're trying holepunch)
if (m_socket->get<utp_stream>()
&& m_peer_info
&& m_peer_info->supports_utp
&& !m_holepunch_mode)
{
m_peer_info->supports_utp = false;
// reconnect immediately using TCP
policy::peer* pi = peer_info_struct();
boost::shared_ptr<torrent> t = m_torrent.lock();
fast_reconnect(true);
disconnect(e, 0);
if (t && pi) t->connect_to_peer(pi, true);
return;
}
if (m_holepunch_mode)
fast_reconnect(true);
if ((!m_socket->get<utp_stream>() || !m_ses.m_settings.enable_outgoing_tcp)
&& m_peer_info
&& m_peer_info->supports_holepunch
&& !m_holepunch_mode)
{
boost::shared_ptr<torrent> t = m_torrent.lock();
// see if we can try a holepunch
bt_peer_connection* p = t->find_introducer(remote());
if (p)
p->write_holepunch_msg(bt_peer_connection::hp_rendezvous, remote(), 0);
}
disconnect(e, 1);
return;
}
// the error argument defaults to 0, which means deliberate disconnect
@ -3524,6 +3593,7 @@ namespace libtorrent
p.flags |= is_seed() ? peer_info::seed : 0;
p.flags |= m_snubbed ? peer_info::snubbed : 0;
p.flags |= m_upload_only ? peer_info::upload_only : 0;
p.flags |= m_holepunch_mode ? peer_info::holepunched : 0;
if (peer_info_struct())
{
policy::peer* pi = peer_info_struct();
@ -3680,6 +3750,37 @@ namespace libtorrent
m_superseed_piece = index;
}
void peer_connection::update_desired_queue_size()
{
if (m_snubbed)
{
m_desired_queue_size = 1;
return;
}
int download_rate = statistics().download_rate();
// calculate the desired download queue size
const int queue_time = m_ses.settings().request_queue_time;
// (if the latency is more than this, the download will stall)
// so, the queue size is queue_time * down_rate / 16 kiB
// (16 kB is the size of each request)
// the minimum number of requests is 2 and the maximum is 48
// the block size doesn't have to be 16. So we first query the
// torrent for it
boost::shared_ptr<torrent> t = m_torrent.lock();
const int block_size = t->block_size();
TORRENT_ASSERT(block_size > 0);
m_desired_queue_size = queue_time * download_rate / block_size;
if (m_desired_queue_size > m_max_out_request_queue)
m_desired_queue_size = m_max_out_request_queue;
if (m_desired_queue_size < min_request_queue)
m_desired_queue_size = min_request_queue;
}
void peer_connection::second_tick(int tick_interval_ms)
{
ptime now = time_now();
@ -3880,36 +3981,13 @@ namespace libtorrent
if (!t->ready_for_connections()) return;
// calculate the desired download queue size
const int queue_time = m_ses.settings().request_queue_time;
// (if the latency is more than this, the download will stall)
// so, the queue size is queue_time * down_rate / 16 kiB
// (16 kB is the size of each request)
// the minimum number of requests is 2 and the maximum is 48
// the block size doesn't have to be 16. So we first query the
// torrent for it
const int block_size = t->block_size();
TORRENT_ASSERT(block_size > 0);
if (m_snubbed)
{
m_desired_queue_size = 1;
}
else
{
m_desired_queue_size = queue_time
* statistics().download_rate() / block_size;
if (m_desired_queue_size > m_max_out_request_queue)
m_desired_queue_size = m_max_out_request_queue;
if (m_desired_queue_size < min_request_queue)
m_desired_queue_size = min_request_queue;
update_desired_queue_size();
if (m_desired_queue_size == m_max_out_request_queue
if (m_desired_queue_size == m_max_out_request_queue
&& t->alerts().should_post<performance_alert>())
{
t->alerts().post_alert(performance_alert(t->get_handle()
, performance_alert::outstanding_request_limit_reached));
}
{
t->alerts().post_alert(performance_alert(t->get_handle()
, performance_alert::outstanding_request_limit_reached));
}
int piece_timeout = m_ses.settings().piece_timeout;
@ -3926,6 +4004,7 @@ namespace libtorrent
// allowed to download. If it is impossible to beat the piece
// timeout at this rate, adjust it to be realistic
const int block_size = t->block_size();
int rate_limit_timeout = rate_limit / block_size;
if (piece_timeout < rate_limit_timeout) piece_timeout = rate_limit_timeout;
@ -4131,8 +4210,11 @@ namespace libtorrent
// only add new piece-chunks if the send buffer is small enough
// otherwise there will be no end to how large it will be!
int buffer_size_watermark = int(m_statistics.upload_rate())
int upload_rate = int(m_statistics.upload_rate());
int buffer_size_watermark = upload_rate
* m_ses.settings().send_buffer_watermark_factor;
if (buffer_size_watermark < 512) buffer_size_watermark = 512;
else if (buffer_size_watermark > m_ses.settings().send_buffer_watermark)
{
@ -4351,13 +4433,16 @@ namespace libtorrent
{
if (!m_ignore_bandwidth_limits)
{
bool utp = m_socket->get<utp_stream>();
// in this case, we have data to send, but no
// bandwidth. So, we simply request bandwidth
// from the bandwidth manager
request_upload_bandwidth(
&m_ses.m_upload_channel
(m_ses.m_settings.rate_limit_utp || !utp) ? &m_ses.m_upload_channel : 0
, &t->m_bandwidth_channel[upload_channel]
, &m_bandwidth_channel[upload_channel]);
, &m_bandwidth_channel[upload_channel]
, !utp ? &m_ses.m_tcp_upload_channel : 0);
}
else
{
@ -4461,13 +4546,16 @@ namespace libtorrent
{
if (!m_ignore_bandwidth_limits)
{
bool utp = m_socket->get<utp_stream>();
// in this case, we have outstanding data to
// receive, but no bandwidth quota. So, we simply
// request bandwidth from the bandwidth manager
request_download_bandwidth(
&m_ses.m_download_channel
(m_ses.m_settings.rate_limit_utp || !utp) ? &m_ses.m_download_channel : 0
, &t->m_bandwidth_channel[download_channel]
, &m_bandwidth_channel[download_channel]);
, &m_bandwidth_channel[download_channel]
, !utp ? &m_ses.m_tcp_download_channel : 0);
}
else
{
@ -4785,6 +4873,12 @@ namespace libtorrent
TORRENT_ASSERT(m_ses.is_network_thread());
INVARIANT_CHECK;
#ifdef TORRENT_VERBOSE_LOGGING
(*m_logger) << time_now_string() << " *** ON_RECEIVE_DATA ["
" bytes: " << bytes_transferred <<
" error: " << error.message() <<
" ]\n";
#endif
#if defined TORRENT_ASIO_DEBUGGING
complete_async("peer_connection::on_receive_data");
#endif
@ -4816,6 +4910,7 @@ namespace libtorrent
int num_loops = 0;
do
{
TORRENT_ASSERT(m_recv_pos + bytes_transferred <= m_packet_size);
#ifdef TORRENT_VERBOSE_LOGGING
peer_log("<<< read %d bytes", int(bytes_transferred));
#endif
@ -4869,6 +4964,7 @@ namespace libtorrent
error_code ec;
bytes_transferred = try_read(read_sync, ec);
TORRENT_ASSERT(bytes_transferred > 0 || ec);
if (ec && ec != asio::error::would_block)
{
m_statistics.trancieve_ip_packet(bytes_in_loop, m_remote.address().is_v6());
@ -5036,27 +5132,25 @@ namespace libtorrent
if (m_disconnecting) return;
m_connecting = false;
m_ses.m_half_open.done(m_connection_ticket);
error_code ec;
if (e)
{
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING
(*m_ses.m_logger) << time_now_string() << " CONNECTION FAILED: " << print_endpoint(m_remote)
<< ": " << e.message() << "\n";
#endif
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING
(*m_logger) << time_now_string() << " CONNECTION FAILED: " << print_endpoint(m_remote)
<< ": " << e.message() << "\n";
#endif
disconnect(e, 1);
connect_failed(e);
return;
}
m_connecting = false;
m_ses.m_half_open.done(m_connection_ticket);
if (m_disconnecting) return;
m_last_receive = time_now();
if (m_socket->get<utp_stream>() && m_peer_info)
{
m_peer_info->confirmed_supports_utp = true;
m_peer_info->supports_utp = false;
}
// this means the connection just succeeded
m_statistics.received_synack(m_remote.address().is_v6());
@ -5110,6 +5204,13 @@ namespace libtorrent
{
TORRENT_ASSERT(m_ses.is_network_thread());
#ifdef TORRENT_VERBOSE_LOGGING
(*m_logger) << time_now_string() << " *** ON_SEND_DATA ["
" bytes: " << bytes_transferred <<
" error: " << error.message() <<
" ]\n";
#endif
INVARIANT_CHECK;
#if defined TORRENT_ASIO_DEBUGGING

View File

@ -108,7 +108,7 @@ namespace libtorrent
{
// returns the rank of a peer's source. We have an affinity
// to connecting to peers with higher rank. This is to avoid
// problems when out peer list is diluted by stale peers from
// problems when our peer list is diluted by stale peers from
// the resume data for instance
int source_rank(int source_bitmask)
{
@ -1008,6 +1008,10 @@ namespace libtorrent
p->seed = true;
++m_num_seeds;
}
if (flags & 0x04)
p->supports_utp = true;
if (flags & 0x08)
p->supports_holepunch = true;
#ifndef TORRENT_DISABLE_GEO_IP
int as = m_torrent->session().as_for_ip(p->address());
@ -1048,6 +1052,10 @@ namespace libtorrent
if (!p->seed) ++m_num_seeds;
p->seed = true;
}
if (flags & 0x04)
p->supports_utp = true;
if (flags & 0x08)
p->supports_holepunch = true;
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING
if (p->connection)
@ -1362,7 +1370,13 @@ namespace libtorrent
TORRENT_ASSERT(c);
error_code ec;
TORRENT_ASSERT(c->remote() == c->get_socket()->remote_endpoint(ec) || ec);
if (c->remote() != c->get_socket()->remote_endpoint(ec) && !ec)
{
fprintf(stderr, "c->remote: %s\nc->get_socket()->remote_endpoint: %s\n"
, print_endpoint(c->remote()).c_str()
, print_endpoint(c->get_socket()->remote_endpoint(ec)).c_str());
TORRENT_ASSERT(false);
}
return std::find_if(
m_peers.begin()
@ -1525,6 +1539,9 @@ namespace libtorrent
#ifndef TORRENT_DISABLE_DHT
, added_to_dht(false)
#endif
, supports_utp(true) // assume peers support utp
, confirmed_supports_utp(false)
, supports_holepunch(false)
{
TORRENT_ASSERT((src & 0xff) == src);
}

View File

@ -154,6 +154,9 @@ namespace libtorrent
set.coalesce_reads = false;
set.coalesce_writes = false;
// disallow the buffer size to grow for the uTP socket
set.utp_dynamic_sock_buf = false;
return set;
}
@ -226,6 +229,9 @@ namespace libtorrent
// connect to us if they want to
set.max_failcount = 1;
// allow the buffer size to grow for the uTP socket
set.utp_dynamic_sock_buf = true;
return set;
}

View File

@ -319,6 +319,11 @@ namespace aux {
TORRENT_SETTING(integer, default_peer_upload_rate)
TORRENT_SETTING(integer, default_peer_download_rate)
TORRENT_SETTING(boolean, broadcast_lsd)
TORRENT_SETTING(boolean, enable_outgoing_utp)
TORRENT_SETTING(boolean, enable_incoming_utp)
TORRENT_SETTING(boolean, enable_outgoing_tcp)
TORRENT_SETTING(boolean, enable_incoming_tcp)
TORRENT_SETTING(integer, max_pex_peers)
TORRENT_SETTING(boolean, ignore_resume_timestamps)
TORRENT_SETTING(boolean, anonymous_mode)
TORRENT_SETTING(integer, tick_interval)
@ -329,6 +334,16 @@ namespace aux {
TORRENT_SETTING(integer, unchoke_slots_limit)
TORRENT_SETTING(integer, half_open_limit)
TORRENT_SETTING(integer, connections_limit)
TORRENT_SETTING(integer, utp_target_delay)
TORRENT_SETTING(integer, utp_gain_factor)
TORRENT_SETTING(integer, utp_syn_resends)
TORRENT_SETTING(integer, utp_fin_resends)
TORRENT_SETTING(integer, utp_num_resends)
TORRENT_SETTING(integer, utp_connect_timeout)
TORRENT_SETTING(integer, utp_delayed_ack)
TORRENT_SETTING(boolean, utp_dynamic_sock_buf)
TORRENT_SETTING(integer, mixed_mode_algorithm)
TORRENT_SETTING(boolean, rate_limit_utp)
TORRENT_SETTING(integer, listen_queue_size)
};
@ -482,6 +497,8 @@ namespace aux {
, boost::bind(&session_impl::on_receive_udp, this, _1, _2, _3, _4)
, boost::bind(&session_impl::on_receive_udp_hostname, this, _1, _2, _3, _4)
, m_half_open)
, m_utp_socket_manager(m_settings, m_udp_socket
, boost::bind(&session_impl::incoming_connection, this, _1))
, m_timer(m_io_service)
, m_lsd_announce_timer(m_io_service)
, m_host_resolver(m_io_service)
@ -645,6 +662,7 @@ namespace aux {
PRINT_SIZEOF(stat)
PRINT_SIZEOF(bandwidth_channel)
PRINT_SIZEOF(policy)
(*m_logger) << "sizeof(utp_socket_impl): " << socket_impl_size() << "\n";
PRINT_SIZEOF(file_entry)
@ -1792,12 +1810,15 @@ namespace aux {
{
// this is probably a dht message
m_dht->on_receive(ep, buf, len);
return;
}
if (m_utp_socket_manager.incoming_packet(buf, len, ep))
return;
// maybe it's a udp tracker response
else if (m_tracker_manager.incoming_udp(e, ep, buf, len))
{
if (m_tracker_manager.incoming_udp(e, ep, buf, len))
m_stat.received_tracker_bytes(len + 28);
}
}
void session_impl::on_receive_udp_hostname(error_code const& e
@ -1901,10 +1922,34 @@ namespace aux {
return;
}
TORRENT_ASSERT(endp.address() != address_v4::any());
#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING)
(*m_logger) << time_now_string() << " <== INCOMING CONNECTION " << endp << "\n";
#endif
if (!m_settings.enable_incoming_utp
&& s->get<utp_stream>())
{
#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING)
(*m_logger) << " rejected uTP connection\n";
#endif
if (m_alerts.should_post<peer_blocked_alert>())
m_alerts.post_alert(peer_blocked_alert(torrent_handle(), endp.address()));
return;
}
if (!m_settings.enable_incoming_tcp
&& s->get<stream_socket>())
{
#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING)
(*m_logger) << " rejected TCP connection\n";
#endif
if (m_alerts.should_post<peer_blocked_alert>())
m_alerts.post_alert(peer_blocked_alert(torrent_handle(), endp.address()));
return;
}
// local addresses do not count, since it's likely
// coming from our own client through local service discovery
// and it does not reflect whether or not a router is open
@ -2184,6 +2229,8 @@ namespace aux {
m_last_tick = now;
m_utp_socket_manager.tick(now);
// only tick the following once per second
if (now - m_last_second_tick < seconds(1)) return;
@ -2225,6 +2272,45 @@ namespace aux {
}
}
switch (m_settings.mixed_mode_algorithm)
{
case session_settings::prefer_tcp:
m_tcp_upload_channel.throttle(0);
m_tcp_download_channel.throttle(0);
break;
case session_settings::peer_proportional:
{
int num_tcp_peers = 0;
int num_peers = 0;
for (connection_map::iterator i = m_connections.begin()
, end(m_connections.end());i != end; ++i)
{
peer_connection& p = *(*i);
if (p.in_handshake()) continue;
if (!p.get_socket()->get<utp_stream>()) ++num_tcp_peers;
++num_peers;
}
if (num_peers == 0)
{
m_tcp_upload_channel.throttle(0);
m_tcp_download_channel.throttle(0);
}
else
{
if (num_tcp_peers == 0) num_tcp_peers = 1;
int upload_rate = (std::max)(m_stat.upload_rate(), 5000);
int download_rate = (std::max)(m_stat.download_rate(), 5000);
if (m_upload_channel.throttle()) upload_rate = m_upload_channel.throttle();
if (m_download_channel.throttle()) download_rate = m_download_channel.throttle();
m_tcp_upload_channel.throttle(upload_rate * num_tcp_peers / num_peers);
m_tcp_download_channel.throttle(download_rate * num_tcp_peers / num_peers);
}
}
break;
}
#ifdef TORRENT_STATS
++m_second_counter;
int downloading_torrents = 0;
@ -3682,6 +3768,8 @@ namespace aux {
}
#endif
m_utp_socket_manager.get_status(s.utp_stats);
int peerlist_size = 0;
for (torrent_map::const_iterator i = m_torrents.begin()
, end(m_torrents.end()); i != end; ++i)

View File

@ -53,6 +53,9 @@ namespace libtorrent
case socket_type_int_impl<http_stream>::value:
get<http_stream>()->~http_stream();
break;
case socket_type_int_impl<utp_stream>::value:
get<utp_stream>()->~utp_stream();
break;
#if TORRENT_USE_I2P
case socket_type_int_impl<i2p_stream>::value:
get<i2p_stream>()->~i2p_stream();
@ -69,6 +72,7 @@ namespace libtorrent
get<ssl_stream<http_stream> >()->~ssl_stream();
break;
#endif
default: TORRENT_ASSERT(false);
}
m_type = 0;
}
@ -88,6 +92,9 @@ namespace libtorrent
case socket_type_int_impl<http_stream>::value:
new ((http_stream*)m_data) http_stream(m_io_service);
break;
case socket_type_int_impl<utp_stream>::value:
new ((utp_stream*)m_data) utp_stream(m_io_service);
break;
#if TORRENT_USE_I2P
case socket_type_int_impl<i2p_stream>::value:
new ((i2p_stream*)m_data) i2p_stream(m_io_service);
@ -110,6 +117,7 @@ namespace libtorrent
, *((boost::asio::ssl::context*)userdata));
break;
#endif
default: TORRENT_ASSERT(false);
}
m_type = type;
@ -127,9 +135,6 @@ namespace libtorrent
TORRENT_SOCKTYPE_FORWARD_RET(is_open(), false)
}
socket_type::lowest_layer_type& socket_type::lowest_layer()
{ TORRENT_SOCKTYPE_FORWARD_RET(lowest_layer(), *((lowest_layer_type*)m_data)) }
void socket_type::open(protocol_type const& p, error_code& ec)
{ TORRENT_SOCKTYPE_FORWARD(open(p, ec)) }

View File

@ -347,10 +347,10 @@ namespace libtorrent
storage(file_storage const& fs, file_storage const* mapped, std::string const& path
, file_pool& fp, std::vector<boost::uint8_t> const& file_prio)
: m_files(fs)
, m_file_priority(file_prio)
, m_pool(fp)
, m_page_size(page_size())
, m_allocate_files(false)
, m_file_priority(file_prio)
{
if (mapped) m_mapped_files.reset(new file_storage(*mapped));

105
src/timestamp_history.cpp Normal file
View File

@ -0,0 +1,105 @@
/*
Copyright (c) 2009, Arvid Norberg
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
* Neither the name of the author nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#include "libtorrent/timestamp_history.hpp"
namespace libtorrent {
enum
{
TIME_MASK = 0xffffffff
};
// defined in utp_stream.cpp
bool compare_less_wrap(boost::uint32_t lhs, boost::uint32_t rhs
, boost::uint32_t mask);
boost::uint32_t timestamp_history::add_sample(boost::uint32_t sample, bool step)
{
if (!m_initialized)
{
for (int i = 0; i < history_size; ++i)
m_history[i] = sample;
m_base = sample;
m_initialized = true;
}
++m_num_samples;
// if sample is less than base, update the base
// and update the history entry (because it will
// be less than that too)
if (compare_less_wrap(sample, m_base, TIME_MASK))
{
m_base = sample;
m_history[m_index] = sample;
}
// if sample is less than our history entry, update it
else if (compare_less_wrap(sample, m_history[m_index], TIME_MASK))
{
m_history[m_index] = sample;
}
boost::uint32_t ret = sample - m_base;
// don't step base delay history unless we have at least 120
// samples. Anything less would suggest that the connection is
// essentially idle and the samples are probably not very reliable
if (step && m_num_samples > 120)
{
m_num_samples = 0;
m_index = (m_index + 1) % history_size;
m_history[m_index] = sample;
// update m_base
m_base = sample;
for (int i = 0; i < history_size; ++i)
{
if (compare_less_wrap(m_history[i], m_base, TIME_MASK))
m_base = m_history[i];
}
}
return ret;
}
void timestamp_history::adjust_base(int change)
{
m_base += change;
// make sure this adjustment sticks by updating all history slots
for (int i = 0; i < history_size; ++i)
{
if (compare_less_wrap(m_history[i], m_base, TIME_MASK))
m_history[i] = m_base;
}
}
}

View File

@ -313,6 +313,9 @@ namespace libtorrent
return ret;
}
// defined in ut_pex.cpp
bool was_introduced_by(peer_plugin const*, tcp::endpoint const&);
torrent::torrent(
session_impl& ses
, tcp::endpoint const& net_interface
@ -986,6 +989,33 @@ namespace libtorrent
, shared_from_this(), _1, _2));
}
bt_peer_connection* torrent::find_introducer(tcp::endpoint const& ep) const
{
#ifndef TORRENT_DISABLE_EXTENSIONS
for (const_peer_iterator i = m_connections.begin(); i != m_connections.end(); ++i)
{
if ((*i)->type() != peer_connection::bittorrent_connection) continue;
bt_peer_connection* p = (bt_peer_connection*)(*i);
if (!p->supports_holepunch()) continue;
peer_plugin const* pp = p->find_plugin("ut_pex");
if (!pp) continue;
if (was_introduced_by(pp, ep)) return (bt_peer_connection*)p;
}
#endif
return 0;
}
bt_peer_connection* torrent::find_peer(tcp::endpoint const& ep) const
{
for (const_peer_iterator i = m_connections.begin(); i != m_connections.end(); ++i)
{
peer_connection* p = *i;
if (p->type() != peer_connection::bittorrent_connection) continue;
if (p->remote() == ep) return (bt_peer_connection*)p;
}
return 0;
}
void torrent::on_resume_data_checked(int ret, disk_io_job const& j)
{
TORRENT_ASSERT(m_ses.is_network_thread());
@ -4366,7 +4396,7 @@ namespace libtorrent
}
bool torrent::connect_to_peer(policy::peer* peerinfo)
bool torrent::connect_to_peer(policy::peer* peerinfo, bool ignore_limit)
{
INVARIANT_CHECK;
@ -4390,8 +4420,8 @@ namespace libtorrent
#endif
#endif
TORRENT_ASSERT(want_more_peers());
TORRENT_ASSERT(m_ses.num_connections() < m_ses.settings().connections_limit);
TORRENT_ASSERT(want_more_peers() || ignore_limit);
TORRENT_ASSERT(m_ses.num_connections() < m_ses.settings().connections_limit || ignore_limit);
tcp::endpoint a(peerinfo->ip());
TORRENT_ASSERT((m_ses.m_ip_filter.access(peerinfo->address()) & ip_filter::blocked) == 0);
@ -4412,7 +4442,21 @@ namespace libtorrent
else
#endif
{
bool ret = instantiate_connection(m_ses.m_io_service, m_ses.proxy(), *s);
// this is where we determine if we open a regular TCP connection
// or a uTP connection. If the m_utp_socket_manager pointer is not passed in
// we'll instantiate a TCP connection
utp_socket_manager* sm = 0;
if (m_ses.m_settings.enable_outgoing_utp
&& (!m_ses.m_settings.enable_outgoing_tcp
|| peerinfo->supports_utp
|| peerinfo->confirmed_supports_utp))
sm = &m_ses.m_utp_socket_manager;
// 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);
(void)ret;
TORRENT_ASSERT(ret);
}

View File

@ -59,8 +59,12 @@ udp_socket::udp_socket(asio::io_service& ios
: m_callback(c)
, m_callback2(c2)
, m_ipv4_sock(ios)
, m_v4_buf_size(0)
, m_v4_buf(0)
#if TORRENT_USE_IPV6
, m_ipv6_sock(ios)
, m_v6_buf_size(0)
, m_v6_buf(0)
#endif
, m_bind_port(0)
, m_outstanding(0)
@ -71,6 +75,7 @@ udp_socket::udp_socket(asio::io_service& ios
, m_queue_packets(false)
, m_tunnel_packets(false)
, m_abort(false)
, m_reallocate_buffers(false)
{
#ifdef TORRENT_DEBUG
m_magic = 0x1337;
@ -79,11 +84,22 @@ udp_socket::udp_socket(asio::io_service& ios
#if defined BOOST_HAS_PTHREADS
m_thread = 0;
#endif
#endif
m_v4_buf_size = 1600;
m_v4_buf = (char*)malloc(m_v4_buf_size);
#if TORRENT_USE_IPV6
m_v6_buf_size = 1600;
m_v6_buf = (char*)malloc(m_v6_buf_size);
#endif
}
udp_socket::~udp_socket()
{
free(m_v4_buf);
#if TORRENT_USE_IPV6
free(m_v6_buf);
#endif
#ifdef TORRENT_DEBUG
TORRENT_ASSERT(m_magic == 0x1337);
TORRENT_ASSERT(!m_callback || !m_started);
@ -168,6 +184,18 @@ void udp_socket::send(udp::endpoint const& ep, char const* p, int len, error_cod
#endif
}
void udp_socket::maybe_realloc_buffers()
{
if (m_reallocate_buffers)
{
m_v4_buf = (char*)realloc(m_v4_buf, m_v4_buf_size);
#if TORRENT_USE_IPV6
m_v6_buf = (char*)realloc(m_v6_buf, m_v6_buf_size);
#endif
m_reallocate_buffers = false;
}
}
void udp_socket::on_read(udp::socket* s, error_code const& e, std::size_t bytes_transferred)
{
#if defined TORRENT_ASIO_DEBUGGING
@ -211,6 +239,7 @@ void udp_socket::on_read(udp::socket* s, error_code const& e, std::size_t bytes_
#ifndef BOOST_NO_EXCEPTIONS
} catch(std::exception&) {}
#endif
// don't stop listening on recoverable errors
if (e != asio::error::host_unreachable
&& e != asio::error::fault
@ -230,17 +259,19 @@ void udp_socket::on_read(udp::socket* s, error_code const& e, std::size_t bytes_
if (m_abort) return;
maybe_realloc_buffers();
#if defined TORRENT_ASIO_DEBUGGING
add_outstanding_async("udp_socket::on_read");
#endif
#if TORRENT_USE_IPV6
if (s == &m_ipv4_sock)
#endif
s->async_receive_from(asio::buffer(m_v4_buf, sizeof(m_v4_buf))
s->async_receive_from(asio::buffer(m_v4_buf, m_v4_buf_size)
, m_v4_ep, boost::bind(&udp_socket::on_read, this, s, _1, _2));
#if TORRENT_USE_IPV6
else
s->async_receive_from(asio::buffer(m_v6_buf, sizeof(m_v6_buf))
s->async_receive_from(asio::buffer(m_v6_buf, m_v6_buf_size)
, m_v6_ep, boost::bind(&udp_socket::on_read, this, s, _1, _2));
#endif
@ -277,10 +308,12 @@ void udp_socket::on_read(udp::socket* s, error_code const& e, std::size_t bytes_
if (m_abort) return;
maybe_realloc_buffers();
#if defined TORRENT_ASIO_DEBUGGING
add_outstanding_async("udp_socket::on_read");
#endif
s->async_receive_from(asio::buffer(m_v4_buf, sizeof(m_v4_buf))
s->async_receive_from(asio::buffer(m_v4_buf, m_v4_buf_size)
, m_v4_ep, boost::bind(&udp_socket::on_read, this, s, _1, _2));
}
#if TORRENT_USE_IPV6
@ -307,10 +340,12 @@ void udp_socket::on_read(udp::socket* s, error_code const& e, std::size_t bytes_
if (m_abort) return;
maybe_realloc_buffers();
#if defined TORRENT_ASIO_DEBUGGING
add_outstanding_async("udp_socket::on_read");
#endif
s->async_receive_from(asio::buffer(m_v6_buf, sizeof(m_v6_buf))
s->async_receive_from(asio::buffer(m_v6_buf, m_v6_buf_size)
, m_v6_ep, boost::bind(&udp_socket::on_read, this, s, _1, _2));
}
#endif
@ -421,6 +456,10 @@ void udp_socket::unwrap(error_code const& e, char const* buf, int size)
m_callback(e, sender, p, size - (p - buf));
}
#ifndef BOOST_ASIO_ENABLE_CANCELIO
#error BOOST_ASIO_ENABLE_CANCELIO needs to be defined when building libtorrent to enable cancel() in asio on windows
#endif
void udp_socket::close()
{
TORRENT_ASSERT(is_single_thread());
@ -461,6 +500,18 @@ void udp_socket::close()
}
}
void udp_socket::set_buf_size(int s)
{
if (s > m_v4_buf_size)
{
m_v4_buf_size = s;
#if TORRENT_USE_IPV6
m_v6_buf_size = s;
#endif
m_reallocate_buffers = true;
}
}
void udp_socket::bind(udp::endpoint const& ep, error_code& ec)
{
CHECK_MAGIC;
@ -474,6 +525,8 @@ void udp_socket::bind(udp::endpoint const& ep, error_code& ec)
if (m_ipv6_sock.is_open()) m_ipv6_sock.close(ec);
#endif
maybe_realloc_buffers();
if (ep.address().is_v4())
{
m_ipv4_sock.open(udp::v4(), ec);
@ -483,7 +536,7 @@ void udp_socket::bind(udp::endpoint const& ep, error_code& ec)
#if defined TORRENT_ASIO_DEBUGGING
add_outstanding_async("udp_socket::on_read");
#endif
m_ipv4_sock.async_receive_from(asio::buffer(m_v4_buf, sizeof(m_v4_buf))
m_ipv4_sock.async_receive_from(asio::buffer(m_v4_buf, m_v4_buf_size)
, m_v4_ep, boost::bind(&udp_socket::on_read, this, &m_ipv4_sock, _1, _2));
++m_outstanding;
}
@ -497,7 +550,7 @@ void udp_socket::bind(udp::endpoint const& ep, error_code& ec)
#if defined TORRENT_ASIO_DEBUGGING
add_outstanding_async("udp_socket::on_read");
#endif
m_ipv6_sock.async_receive_from(asio::buffer(m_v6_buf, sizeof(m_v6_buf))
m_ipv6_sock.async_receive_from(asio::buffer(m_v6_buf, m_v6_buf_size)
, m_v6_ep, boost::bind(&udp_socket::on_read, this, &m_ipv6_sock, _1, _2));
++m_outstanding;
}
@ -523,6 +576,8 @@ void udp_socket::bind(int port)
if (m_ipv6_sock.is_open()) m_ipv6_sock.close(ec);
#endif
maybe_realloc_buffers();
m_ipv4_sock.open(udp::v4(), ec);
if (!ec)
{
@ -530,7 +585,7 @@ void udp_socket::bind(int port)
add_outstanding_async("udp_socket::on_read");
#endif
m_ipv4_sock.bind(udp::endpoint(address_v4::any(), port), ec);
m_ipv4_sock.async_receive_from(asio::buffer(m_v4_buf, sizeof(m_v4_buf))
m_ipv4_sock.async_receive_from(asio::buffer(m_v4_buf, m_v4_buf_size)
, m_v4_ep, boost::bind(&udp_socket::on_read, this, &m_ipv4_sock, _1, _2));
++m_outstanding;
#ifdef TORRENT_DEBUG
@ -546,7 +601,7 @@ void udp_socket::bind(int port)
#endif
m_ipv6_sock.set_option(v6only(true), ec);
m_ipv6_sock.bind(udp::endpoint(address_v6::any(), port), ec);
m_ipv6_sock.async_receive_from(asio::buffer(m_v6_buf, sizeof(m_v6_buf))
m_ipv6_sock.async_receive_from(asio::buffer(m_v6_buf, m_v6_buf_size)
, m_v6_ep, boost::bind(&udp_socket::on_read, this, &m_ipv6_sock, _1, _2));
++m_outstanding;
#ifdef TORRENT_DEBUG

View File

@ -210,6 +210,8 @@ namespace libtorrent { namespace
, m_tp(tp)
{}
virtual char const* type() const { return "ut_metadata"; }
// can add entries to the extension handshake
virtual void add_handshake(entry& h)
{

View File

@ -145,10 +145,20 @@ namespace libtorrent { namespace
// no supported flags to set yet
// 0x01 - peer supports encryption
// 0x02 - peer is a seed
// 0x04 - supports uTP. This is only a positive flags
// passing 0 doesn't mean the peer doesn't
// support uTP
// 0x08 - supports holepunching protocol. If this
// flag is received from a peer, it can be
// used as a rendezvous point in case direct
// connections to the peer fail
int flags = p->is_seed() ? 2 : 0;
#ifndef TORRENT_DISABLE_ENCRYPTION
flags |= p->supports_encryption() ? 1 : 0;
#endif
flags |= p->get_socket()->get<utp_stream>() ? 4 : 0;
flags |= p->supports_holepunch() ? 8 : 0;
// i->first was added since the last time
if (remote.address().is_v4())
{
@ -205,11 +215,14 @@ namespace libtorrent { namespace
: m_torrent(t)
, m_pc(pc)
, m_tp(tp)
, m_last_pex(min_time())
, m_1_minute(55)
, m_message_index(0)
, m_first_time(true)
{}
virtual char const* type() const { return "ut_pex"; }
virtual void add_handshake(entry& h)
{
entry& messages = h["m"];
@ -239,9 +252,20 @@ namespace libtorrent { namespace
m_pc.disconnect(errors::pex_message_too_large, 2);
return true;
}
ptime now = time_now();
if (now - m_last_pex < seconds(10))
{
// this client appears to be trying to flood us
// with pex messages. Don't allow that.
m_pc.disconnect(errors::too_frequent_pex);
return true;
}
if (body.left() < length) return true;
m_last_pex = now;
lazy_entry pex_msg;
error_code ec;
int ret = lazy_bdecode(body.begin, body.end, pex_msg, ec);
@ -251,13 +275,34 @@ namespace libtorrent { namespace
return true;
}
lazy_entry const* p = pex_msg.dict_find("added");
lazy_entry const* pf = pex_msg.dict_find("added.f");
lazy_entry const* p = pex_msg.dict_find_string("dropped");
#ifdef TORRENT_VERBOSE_LOGGING
(*m_pc.m_logger) << time_now_string() << " <== PEX ["
" dropped:" << (p?p->string_length():0);
#endif
if (p)
{
int num_peers = p->string_length() / 6;
char const* in = p->string_ptr();
for (int i = 0; i < num_peers; ++i)
{
tcp::endpoint adr = detail::read_v4_endpoint<tcp::endpoint>(in);
peers4_t::value_type v(adr.address().to_v4().to_bytes(), adr.port());
peers4_t::iterator j = std::lower_bound(m_peers.begin(), m_peers.end(), v);
if (j != m_peers.end() && *j == v) m_peers.erase(j);
}
}
p = pex_msg.dict_find_string("added");
lazy_entry const* pf = pex_msg.dict_find_string("added.f");
#ifdef TORRENT_VERBOSE_LOGGING
(*m_pc.m_logger) << " added:" << (p?p->string_length():0) << " ]\n";
#endif
if (p != 0
&& pf != 0
&& p->type() == lazy_entry::string_t
&& pf->type() == lazy_entry::string_t
&& pf->string_length() == p->string_length() / 6)
{
int num_peers = pf->string_length();
@ -270,14 +315,39 @@ namespace libtorrent { namespace
{
tcp::endpoint adr = detail::read_v4_endpoint<tcp::endpoint>(in);
char flags = *fin++;
if (m_peers.size() >= m_torrent.settings().max_pex_peers) break;
// ignore local addresses unless the peer is local to us
if (is_local(adr.address()) && !is_local(m_pc.remote().address())) continue;
peers4_t::value_type v(adr.address().to_v4().to_bytes(), adr.port());
peers4_t::iterator j = std::lower_bound(m_peers.begin(), m_peers.end(), v);
// do we already know about this peer?
if (j != m_peers.end() && *j == v) continue;
m_peers.insert(j, v);
p.add_peer(adr, pid, peer_info::pex, flags);
}
}
#if TORRENT_USE_IPV6
lazy_entry const* p6 = pex_msg.dict_find("added6");
lazy_entry const* p6 = pex_msg.dict_find("dropped6");
if (p6 != 0 && p6->type() == lazy_entry::string_t)
{
int num_peers = p6->string_length() / 18;
char const* in = p6->string_ptr();
for (int i = 0; i < num_peers; ++i)
{
tcp::endpoint adr = detail::read_v6_endpoint<tcp::endpoint>(in);
peers6_t::value_type v(adr.address().to_v6().to_bytes(), adr.port());
peers6_t::iterator j = std::lower_bound(m_peers6.begin(), m_peers6.end(), v);
if (j != m_peers6.end() && *j == v) m_peers6.erase(j);
}
}
p6 = pex_msg.dict_find("added6");
lazy_entry const* p6f = pex_msg.dict_find("added6.f");
if (p6 != 0
&& p6f != 0
@ -297,6 +367,13 @@ namespace libtorrent { namespace
char flags = *fin++;
// ignore local addresses unless the peer is local to us
if (is_local(adr.address()) && !is_local(m_pc.remote().address())) continue;
if (m_peers6.size() >= m_torrent.settings().max_pex_peers) break;
peers6_t::value_type v(adr.address().to_v6().to_bytes(), adr.port());
peers6_t::iterator j = std::lower_bound(m_peers6.begin(), m_peers6.end(), v);
// do we already know about this peer?
if (j != m_peers6.end() && *j == v) continue;
m_peers6.insert(j, v);
p.add_peer(adr, pid, peer_info::pex, flags);
}
}
@ -323,8 +400,6 @@ namespace libtorrent { namespace
m_1_minute = 0;
}
private:
void send_ut_peer_diff()
{
// if there's no change in out peer set, don't send anything
@ -419,6 +494,23 @@ namespace libtorrent { namespace
torrent& m_torrent;
peer_connection& m_pc;
ut_pex_plugin& m_tp;
// stores all peers this this peer is connected to. These lists
// are updated with each pex message and are limited in size
// to protect against malicious clients. These lists are also
// used for looking up which peer a peer that supports holepunch
// came from.
// these are vectors to save memory and keep the items close
// together for performance. Inserting and removing is relatively
// cheap since the lists' size is limited
typedef std::vector<std::pair<address_v4::bytes_type, boost::uint16_t> > peers4_t;
peers4_t m_peers;
#if TORRENT_USE_IPV6
typedef std::vector<std::pair<address_v6::bytes_type, boost::uint16_t> > peers6_t;
peers6_t m_peers6;
#endif
// the last pex message we received
ptime m_last_pex;
int m_1_minute;
int m_message_index;
@ -438,11 +530,10 @@ namespace libtorrent { namespace
return boost::shared_ptr<peer_plugin>(new ut_pex_peer_plugin(m_torrent
, *pc, *this));
}
}}
} }
namespace libtorrent
{
boost::shared_ptr<torrent_plugin> create_ut_pex_plugin(torrent* t, void*)
{
if (t->torrent_file().priv() || (t->torrent_file().is_i2p()
@ -453,6 +544,28 @@ namespace libtorrent
return boost::shared_ptr<torrent_plugin>(new ut_pex_plugin(*t));
}
bool was_introduced_by(peer_plugin const* pp, tcp::endpoint const& ep)
{
ut_pex_peer_plugin* p = (ut_pex_peer_plugin*)pp;
#if TORRENT_USE_IPV6
if (ep.address().is_v4())
{
#endif
ut_pex_peer_plugin::peers4_t::value_type v(ep.address().to_v4().to_bytes(), ep.port());
ut_pex_peer_plugin::peers4_t::const_iterator i
= std::lower_bound(p->m_peers.begin(), p->m_peers.end(), v);
return i != p->m_peers.end() && *i == v;
#if TORRENT_USE_IPV6
}
else
{
ut_pex_peer_plugin::peers6_t::value_type v(ep.address().to_v6().to_bytes(), ep.port());
ut_pex_peer_plugin::peers6_t::iterator i
= std::lower_bound(p->m_peers6.begin(), p->m_peers6.end(), v);
return i != p->m_peers6.end() && *i == v;
}
#endif
}
}

324
src/utp_socket_manager.cpp Normal file
View File

@ -0,0 +1,324 @@
/*
Copyright (c) 2009, Arvid Norberg
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
* Neither the name of the author nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#include "libtorrent/utp_stream.hpp"
#include "libtorrent/udp_socket.hpp"
#include "libtorrent/utp_socket_manager.hpp"
#include "libtorrent/instantiate_connection.hpp"
#include "libtorrent/socket_io.hpp"
#include "libtorrent/broadcast_socket.hpp" // for is_teredo
// #define TORRENT_DEBUG_MTU 1135
namespace libtorrent
{
utp_socket_manager::utp_socket_manager(session_settings const& sett, udp_socket& s
, incoming_utp_callback_t cb)
: m_sock(s)
, m_cb(cb)
, m_last_socket(0)
, m_new_connection(-1)
, m_sett(sett)
, m_sock_buf_size(0)
{}
utp_socket_manager::~utp_socket_manager()
{
for (socket_map_t::iterator i = m_utp_sockets.begin()
, end(m_utp_sockets.end()); i != end; ++i)
{
delete_utp_impl(i->second);
}
}
void utp_socket_manager::get_status(utp_status& s) const
{
s.num_idle = 0;
s.num_syn_sent = 0;
s.num_connected = 0;
s.num_fin_sent = 0;
s.num_close_wait = 0;
for (socket_map_t::const_iterator i = m_utp_sockets.begin()
, end(m_utp_sockets.end()); i != end; ++i)
{
int state = utp_socket_state(i->second);
switch (state)
{
case 0: ++s.num_idle; break;
case 1: ++s.num_syn_sent; break;
case 2: ++s.num_connected; break;
case 3: ++s.num_fin_sent; break;
case 4: ++s.num_close_wait; break;
case 5: ++s.num_close_wait; break;
}
}
}
void utp_socket_manager::tick(ptime now)
{
for (socket_map_t::iterator i = m_utp_sockets.begin()
, end(m_utp_sockets.end()); i != end;)
{
if (should_delete(i->second))
{
delete_utp_impl(i->second);
if (m_last_socket == i->second) m_last_socket = 0;
m_utp_sockets.erase(i++);
continue;
}
tick_utp_impl(i->second, now);
++i;
}
}
void utp_socket_manager::mtu_for_dest(address const& addr, int& link_mtu, int& utp_mtu)
{
if (time_now() - m_last_route_update > seconds(60))
{
m_last_route_update = time_now();
error_code ec;
m_routes = enum_routes(m_sock.get_io_service(), ec);
}
int mtu = 0;
if (!m_routes.empty())
{
for (std::vector<ip_route>::iterator i = m_routes.begin()
, end(m_routes.end()); i != end; ++i)
{
if (!match_addr_mask(addr, i->destination, i->netmask)) continue;
// assume that we'll actually use the route with the largest
// MTU (seems like a reasonable assumption).
// this could however be improved by using the route metrics
// and the prefix length of the netmask to order the matches
if (mtu < i->mtu) mtu = i->mtu;
}
}
if (mtu == 0)
{
if (is_teredo(addr)) mtu = TORRENT_TEREDO_MTU;
else mtu = TORRENT_ETHERNET_MTU;
}
// clamp the MTU within reasonable bounds
if (mtu < TORRENT_INET_MIN_MTU) mtu = TORRENT_INET_MIN_MTU;
else if (mtu > TORRENT_INET_MAX_MTU) mtu = TORRENT_INET_MAX_MTU;
link_mtu = mtu;
mtu -= TORRENT_UDP_HEADER;
if (m_sock.get_proxy_settings().type == proxy_settings::socks5
|| m_sock.get_proxy_settings().type == proxy_settings::socks5_pw)
{
// this is for the IP layer
address proxy_addr = m_sock.proxy_addr().address();
if (proxy_addr.is_v4()) mtu -= TORRENT_IPV4_HEADER;
else mtu -= TORRENT_IPV6_HEADER;
// this is for the SOCKS layer
mtu -= TORRENT_SOCKS5_HEADER;
// the address field in the SOCKS header
if (addr.is_v4()) mtu -= 4;
else mtu -= 16;
}
else
{
if (addr.is_v4()) mtu -= TORRENT_IPV4_HEADER;
else mtu -= TORRENT_IPV6_HEADER;
}
utp_mtu = mtu;
}
void utp_socket_manager::send_packet(udp::endpoint const& ep, char const* p
, int len, error_code& ec, int flags)
{
if (!m_sock.is_open())
{
ec = asio::error::operation_aborted;
return;
}
#ifdef TORRENT_DEBUG_MTU
// drop packets that exceed the debug MTU
if ((flags & dont_fragment) && len > TORRENT_DEBUG_MTU) return;
#endif
#ifdef TORRENT_HAS_DONT_FRAGMENT
error_code tmp;
if (flags & utp_socket_manager::dont_fragment)
m_sock.set_option(libtorrent::dont_fragment(true), tmp);
#endif
m_sock.send(ep, p, len, ec);
#ifdef TORRENT_HAS_DONT_FRAGMENT
if (flags & utp_socket_manager::dont_fragment)
m_sock.set_option(libtorrent::dont_fragment(false), tmp);
#endif
}
tcp::endpoint utp_socket_manager::local_endpoint(error_code& ec) const
{
return m_sock.local_endpoint(ec);
}
bool utp_socket_manager::incoming_packet(char const* p, int size, udp::endpoint const& ep)
{
// UTP_LOGV("incoming packet size:%d\n", size);
if (size < sizeof(utp_header)) return false;
utp_header const* ph = (utp_header*)p;
// UTP_LOGV("incoming packet version:%d\n", int(ph->get_version()));
if (ph->get_version() != 1) return false;
const ptime receive_time = time_now_hires();
// parse out connection ID and look for existing
// connections. If found, forward to the utp_stream.
boost::uint16_t id = ph->connection_id;
// first test to see if it's the same socket as last time
// in most cases it is
if (m_last_socket
&& utp_match(m_last_socket, ep, id))
{
return utp_incoming_packet(m_last_socket, p, size, ep, receive_time);
}
socket_map_t::iterator i = m_utp_sockets.find(id);
std::pair<socket_map_t::iterator, socket_map_t::iterator> r =
m_utp_sockets.equal_range(id);
for (; r.first != r.second; ++r.first)
{
if (!utp_match(r.first->second, ep, id)) continue;
bool ret = utp_incoming_packet(r.first->second, p, size, ep, receive_time);
if (ret) m_last_socket = r.first->second;
return ret;
}
// UTP_LOGV("incoming packet id:%d source:%s\n", id, print_endpoint(ep).c_str());
if (!m_sett.enable_incoming_utp)
return false;
// if not found, see if it's a SYN packet, if it is,
// create a new utp_stream
if (ph->get_type() == ST_SYN)
{
// create the new socket with this ID
m_new_connection = id;
// UTP_LOGV("not found, new connection id:%d\n", m_new_connection);
boost::shared_ptr<socket_type> c(new (std::nothrow) socket_type(m_sock.get_io_service()));
if (!c) return false;
instantiate_connection(m_sock.get_io_service(), proxy_settings(), *c, 0, this);
utp_stream* str = c->get<utp_stream>();
TORRENT_ASSERT(str);
int link_mtu, utp_mtu;
mtu_for_dest(ep.address(), link_mtu, utp_mtu);
utp_init_mtu(str->get_impl(), link_mtu, utp_mtu);
bool ret = utp_incoming_packet(str->get_impl(), p, size, ep, receive_time);
if (!ret) return false;
m_cb(c);
// the connection most likely changed its connection ID here
// we need to move it to the correct ID
return true;
}
// #error send reset
return false;
}
void utp_socket_manager::remove_socket(boost::uint16_t id)
{
socket_map_t::iterator i = m_utp_sockets.find(id);
if (i == m_utp_sockets.end()) return;
delete_utp_impl(i->second);
if (m_last_socket == i->second) m_last_socket = 0;
m_utp_sockets.erase(i);
}
void utp_socket_manager::set_sock_buf(int size)
{
if (size < m_sock_buf_size) return;
m_sock.set_buf_size(size);
error_code ec;
// add more socket buffer storage on the lower level socket
// to avoid dropping packets because of a full receive buffer
// while processing a packet
// only update the buffer size if it's bigger than
// what we already have
datagram_socket::receive_buffer_size recv_buf_size_opt;
m_sock.get_option(recv_buf_size_opt, ec);
if (recv_buf_size_opt.value() < size * 10)
{
m_sock.set_option(datagram_socket::receive_buffer_size(size * 10), ec);
m_sock.set_option(datagram_socket::send_buffer_size(size * 3), ec);
}
m_sock_buf_size = size;
}
utp_socket_impl* utp_socket_manager::new_utp_socket(utp_stream* str)
{
boost::uint16_t send_id = 0;
boost::uint16_t recv_id = 0;
if (m_new_connection != -1)
{
send_id = m_new_connection;
recv_id = m_new_connection + 1;
m_new_connection = -1;
}
else
{
send_id = rand();
recv_id = send_id - 1;
}
utp_socket_impl* impl = construct_utp_impl(recv_id, send_id, str, this);
m_utp_sockets.insert(std::make_pair(recv_id, impl));
return impl;
}
}

2900
src/utp_stream.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -34,6 +34,7 @@ test-suite libtorrent :
[ run test_bdecode_performance.cpp ]
[ run test_pe_crypto.cpp ]
[ run test_utp.cpp ]
[ run test_auto_unchoke.cpp ]
[ run test_http_connection.cpp ]
[ run test_torrent.cpp ]

View File

@ -241,9 +241,9 @@ setup_transfer(session* ses1, session* ses2, session* ses3
ses1->set_settings(sess_set);
ses2->set_settings(sess_set);
if (ses3) ses3->set_settings(sess_set);
ses1->set_alert_mask(~alert::progress_notification);
ses2->set_alert_mask(~alert::progress_notification);
if (ses3) ses3->set_alert_mask(~alert::progress_notification);
ses1->set_alert_mask(~(alert::progress_notification | alert::stats_notification));
ses2->set_alert_mask(~(alert::progress_notification | alert::stats_notification));
if (ses3) ses3->set_alert_mask(~(alert::progress_notification | alert::stats_notification));
std::srand(time(0));
peer_id pid;
@ -289,9 +289,11 @@ setup_transfer(session* ses1, session* ses2, session* ses3
if (p) param = *p;
param.ti = clone_ptr(t);
param.save_path = "./tmp1" + suffix;
param.seed_mode = true;
error_code ec;
torrent_handle tor1 = ses1->add_torrent(param, ec);
tor1.super_seeding(super_seeding);
param.seed_mode = false;
TEST_CHECK(!ses1->get_torrents().empty());
torrent_handle tor2;
torrent_handle tor3;
@ -753,7 +755,7 @@ void web_server_thread(int* port, bool ssl, bool chunked)
while (!p.finished())
{
TORRENT_ASSERT(len < sizeof(buf));
TORRENT_ASSERT(len < int(sizeof(buf)));
size_t received = s.read_some(boost::asio::buffer(&buf[len]
, sizeof(buf) - len), ec);
// fprintf(stderr, "read: %d\n", int(received));

View File

@ -43,8 +43,11 @@ POSSIBILITY OF SUCH DAMAGE.
#include "libtorrent/broadcast_socket.hpp"
#include "libtorrent/identify_client.hpp"
#include "libtorrent/file.hpp"
#include "libtorrent/packet_buffer.hpp"
#include "libtorrent/session.hpp"
#include "libtorrent/bencode.hpp"
#include "libtorrent/timestamp_history.hpp"
#include "libtorrent/enum_net.hpp"
#ifndef TORRENT_DISABLE_DHT
#include "libtorrent/kademlia/node_id.hpp"
#include "libtorrent/kademlia/routing_table.hpp"
@ -382,6 +385,98 @@ int test_main()
error_code ec;
int ret = 0;
// test timestamp_history
{
timestamp_history h;
TEST_EQUAL(h.add_sample(0x32, false), 0);
TEST_EQUAL(h.base(), 0x32);
TEST_EQUAL(h.add_sample(0x33, false), 0x1);
TEST_EQUAL(h.base(), 0x32);
TEST_EQUAL(h.add_sample(0x3433, false), 0x3401);
TEST_EQUAL(h.base(), 0x32);
TEST_EQUAL(h.add_sample(0x30, false), 0);
TEST_EQUAL(h.base(), 0x30);
// test that wrapping of the timestamp is properly handled
h.add_sample(0xfffffff3, false);
TEST_EQUAL(h.base(), 0xfffffff3);
}
// test packet_buffer
{
packet_buffer pb;
TEST_EQUAL(pb.capacity(), 0);
TEST_EQUAL(pb.size(), 0);
TEST_EQUAL(pb.span(), 0);
pb.insert(123, (void*)123);
TEST_EQUAL(pb.at(123 + 16), 0);
TEST_CHECK(pb.at(123) == (void*)123);
TEST_CHECK(pb.capacity() > 0);
TEST_EQUAL(pb.size(), 1);
TEST_EQUAL(pb.span(), 1);
TEST_EQUAL(pb.cursor(), 123);
pb.insert(125, (void*)125);
TEST_CHECK(pb.at(125) == (void*)125);
TEST_EQUAL(pb.size(), 2);
TEST_EQUAL(pb.span(), 3);
TEST_EQUAL(pb.cursor(), 123);
pb.insert(500, (void*)500);
TEST_EQUAL(pb.size(), 3);
TEST_EQUAL(pb.span(), 501 - 123);
TEST_EQUAL(pb.capacity(), 512);
TEST_CHECK(pb.remove(123) == (void*)123);
TEST_EQUAL(pb.size(), 2);
TEST_EQUAL(pb.span(), 501 - 125);
TEST_EQUAL(pb.cursor(), 125);
TEST_CHECK(pb.remove(125) == (void*)125);
TEST_EQUAL(pb.size(), 1);
TEST_EQUAL(pb.span(), 1);
TEST_EQUAL(pb.cursor(), 500);
TEST_CHECK(pb.remove(500) == (void*)500);
TEST_EQUAL(pb.size(), 0);
TEST_EQUAL(pb.span(), 0);
for (int i = 0; i < 0xff; ++i)
{
int index = (i + 0xfff0) & 0xffff;
pb.insert(index, (void*)(index + 1));
fprintf(stderr, "insert: %u (mask: %x)\n", index, int(pb.capacity() - 1));
TEST_EQUAL(pb.capacity(), 512);
if (i >= 14)
{
index = (index - 14) & 0xffff;
fprintf(stderr, "remove: %u\n", index);
TEST_CHECK(pb.remove(index) == (void*)(index + 1));
TEST_EQUAL(pb.size(), 14);
}
}
}
{
// test wrapping the indices
packet_buffer pb;
TEST_EQUAL(pb.size(), 0);
pb.insert(0xfffe, (void*)1);
TEST_CHECK(pb.at(0xfffe) == (void*)1);
pb.insert(2, (void*)2);
TEST_CHECK(pb.at(2) == (void*)2);
pb.remove(0xfffe);
TEST_CHECK(pb.at(0xfffe) == (void*)0);
TEST_CHECK(pb.at(2) == (void*)2);
}
TEST_CHECK(error_code(errors::http_error).message() == "HTTP error");
TEST_CHECK(error_code(errors::missing_file_sizes).message() == "missing or invalid 'file sizes' entry");
TEST_CHECK(error_code(errors::unsupported_protocol_version).message() == "unsupported protocol version");
@ -391,7 +486,7 @@ int test_main()
TEST_CHECK(errors::reserved129 == 129);
TEST_CHECK(errors::reserved159 == 159);
TEST_CHECK(errors::reserved108 == 108);
TEST_CHECK(errors::reserved109 == 109);
{
// test session state load/restore
@ -1062,6 +1157,16 @@ int test_main()
#endif
TEST_CHECK(is_any(address_v4::any()));
TEST_CHECK(!is_any(address::from_string("31.53.21.64", ec)));
TEST_CHECK(match_addr_mask(
address::from_string("10.0.1.3", ec),
address::from_string("10.0.3.3", ec),
address::from_string("255.255.0.0", ec)));
TEST_CHECK(!match_addr_mask(
address::from_string("10.0.1.3", ec),
address::from_string("10.1.3.3", ec),
address::from_string("255.255.0.0", ec)));
// test torrent parsing

151
test/test_utp.cpp Normal file
View File

@ -0,0 +1,151 @@
/*
Copyright (c) 2008, Arvid Norberg
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
* Neither the name of the author nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#include "libtorrent/session.hpp"
#include "libtorrent/session_settings.hpp"
#include "libtorrent/hasher.hpp"
#include "libtorrent/alert_types.hpp"
#include "libtorrent/bencode.hpp"
#include "libtorrent/thread.hpp"
#include "libtorrent/time.hpp"
#include "libtorrent/file.hpp"
#include <boost/tuple/tuple.hpp>
#include <boost/bind.hpp>
#include "test.hpp"
#include "setup_transfer.hpp"
#include <fstream>
#include <iostream>
using namespace libtorrent;
using boost::tuples::ignore;
void test_transfer()
{
// in case the previous run was terminated
error_code ec;
remove_all("./tmp1_utp", ec);
remove_all("./tmp2_utp", ec);
session ses1(fingerprint("LT", 0, 1, 0, 0), std::make_pair(48885, 49930), "0.0.0.0", 0);
session ses2(fingerprint("LT", 0, 1, 0, 0), std::make_pair(49885, 50930), "0.0.0.0", 0);
session_settings sett;
sett.enable_outgoing_tcp = false;
sett.min_reconnect_time = 1;
sett.announce_to_all_trackers = true;
sett.announce_to_all_tiers = true;
// make sure we announce to both http and udp trackers
sett.prefer_udp_trackers = false;
// for performance testing
// sett.disable_hash_checks = true;
// sett.utp_delayed_ack = 0;
// disable this to use regular size packets over loopback
// sett.utp_dynamic_sock_buf = false;
ses1.set_settings(sett);
ses2.set_settings(sett);
#ifndef TORRENT_DISABLE_ENCRYPTION
pe_settings pes;
pes.out_enc_policy = pe_settings::disabled;
pes.in_enc_policy = pe_settings::disabled;
ses1.set_pe_settings(pes);
ses2.set_pe_settings(pes);
#endif
torrent_handle tor1;
torrent_handle tor2;
create_directory("./tmp1_utp", ec);
std::ofstream file("./tmp1_utp/temporary");
boost::intrusive_ptr<torrent_info> t = ::create_torrent(&file, 16 * 1024, 1000, false);
file.close();
// for performance testing
add_torrent_params atp;
// atp.storage = &disabled_storage_constructor;
// test using piece sizes smaller than 16kB
boost::tie(tor1, tor2, ignore) = setup_transfer(&ses1, &ses2, 0
, true, false, true, "_utp", 8 * 1024, &t, false, &atp);
for (int i = 0; i < 300; ++i)
{
print_alerts(ses1, "ses1", true, true, true);
print_alerts(ses2, "ses2", true, true, true);
torrent_status st1 = tor1.status();
torrent_status st2 = tor2.status();
std::cerr
<< "\033[32m" << int(st1.download_payload_rate / 1000.f) << "kB/s "
<< "\033[33m" << int(st1.upload_payload_rate / 1000.f) << "kB/s "
<< "\033[0m" << int(st1.progress * 100) << "% "
<< st1.num_peers
<< ": "
<< "\033[32m" << int(st2.download_payload_rate / 1000.f) << "kB/s "
<< "\033[31m" << int(st2.upload_payload_rate / 1000.f) << "kB/s "
<< "\033[0m" << int(st2.progress * 100) << "% "
<< st2.num_peers
<< " cc: " << st2.connect_candidates
<< std::endl;
if (st2.is_finished) break;
TEST_CHECK(st1.state == torrent_status::seeding
|| st1.state == torrent_status::checking_files);
TEST_CHECK(st2.state == torrent_status::downloading);
test_sleep(500);
}
TEST_CHECK(tor1.status().is_finished);
TEST_CHECK(tor2.status().is_finished);
}
int test_main()
{
using namespace libtorrent;
test_transfer();
error_code ec;
remove_all("./tmp1_utp", ec);
remove_all("./tmp2_utp", ec);
return 0;
}