Skip to content

Commit b9a4a53

Browse files
authored
Allow multiple connections from the same Peer ID (#8215)
* if the value of settings_pack::allow_multiple_connections_per_ip is true, then skip the check for the same pid * add new setting * No change to ABI * Update test file * Remove invalid tests in test_peer_list.cpp as allow_multiple_connections_per_pid has no effect on peer_list * Restore the test file * add new test * add new test * fix peer_list.hpp * Delete the currently unnecessary fields * Merge test code
1 parent 8b8242f commit b9a4a53

7 files changed

Lines changed: 222 additions & 20 deletions

File tree

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,7 @@ SIM_SOURCES = \
753753
setup_dht.hpp \
754754
setup_swarm.cpp \
755755
setup_swarm.hpp \
756+
test_allow_multiple_connections_per_pid.cpp \
756757
test_auto_manage.cpp \
757758
test_checking.cpp \
758759
test_dht.cpp \

include/libtorrent/settings_pack.hpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,6 +1046,14 @@ namespace aux {
10461046
// NOCOW flag on the download directory, which will cause all files
10471047
// created within it to have the NOCOW flag set.
10481048
disk_disable_copy_on_write,
1049+
1050+
// determines if connections from the same Peer ID as existing
1051+
// connections should be rejected or not. Typically, we
1052+
// only establish a single connection with each peer. If
1053+
// a peer has multiple IP addresses, enabling this feature
1054+
// may improve transfer efficiency, but it may also
1055+
// increase network load.
1056+
allow_multiple_connections_per_pid,
10491057

10501058
max_bool_setting_internal
10511059
};

simulation/Jamfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,5 @@ run test_save_resume.cpp ;
6565
run test_error_handling.cpp ;
6666
run test_timeout.cpp ;
6767
run test_peer_connection.cpp ;
68+
run test_allow_multiple_connections_per_pid.cpp ;
6869

simulation/fake_peer.hpp

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,13 @@ struct fake_peer
172172
});
173173
}
174174

175+
// Set a fixed peer-id to use instead of random bytes
176+
void set_peer_id(lt::peer_id const& pid)
177+
{
178+
m_fixed_pid = pid;
179+
m_use_fixed_pid = true;
180+
}
181+
175182
private:
176183

177184
void send_simple_msg(std::uint8_t const msg_code)
@@ -201,7 +208,14 @@ struct fake_peer
201208
int const len = sizeof(handshake) - 1;
202209
memcpy(m_out_buffer.data(), handshake, len);
203210
memcpy(&m_out_buffer[28], ih.data(), 20);
204-
lt::aux::random_bytes({&m_out_buffer[48], 20});
211+
if (m_use_fixed_pid)
212+
{
213+
memcpy(&m_out_buffer[48], m_fixed_pid.data(), 20);
214+
}
215+
else
216+
{
217+
lt::aux::random_bytes({&m_out_buffer[48], 20});
218+
}
205219

206220
TORRENT_ASSERT(!m_writing);
207221
m_writing = true;
@@ -320,6 +334,10 @@ struct fake_peer
320334
// socket
321335
bool m_writing = false;
322336

337+
// fixed peer-id to use instead of random bytes
338+
lt::peer_id m_fixed_pid;
339+
bool m_use_fixed_pid = false;
340+
323341
std::vector<char> m_send_buffer;
324342
};
325343

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
/*
2+
3+
4+
5+
6+
Redistribution and use in source and binary forms, with or without
7+
modification, are permitted provided that the following conditions
8+
are met:
9+
10+
* Redistributions of source code must retain the above copyright
11+
notice, this list of conditions and the following disclaimer.
12+
* Redistributions in binary form must reproduce the above copyright
13+
notice, this list of conditions and the following disclaimer in
14+
the documentation and/or other materials provided with the distribution.
15+
* Neither the name of the author nor the names of its
16+
contributors may be used to endorse or promote products derived
17+
from this software without specific prior written permission.
18+
19+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29+
POSSIBILITY OF SUCH DAMAGE.
30+
31+
*/
32+
33+
#include "libtorrent/session.hpp"
34+
#include "libtorrent/torrent_handle.hpp"
35+
#include "libtorrent/settings_pack.hpp"
36+
#include "libtorrent/alert_types.hpp"
37+
#include "libtorrent/disabled_disk_io.hpp"
38+
#include "libtorrent/torrent_flags.hpp"
39+
#include "settings.hpp"
40+
#include "fake_peer.hpp"
41+
#include "utils.hpp"
42+
#include "test_utils.hpp"
43+
#include "setup_transfer.hpp"
44+
#include "create_torrent.hpp"
45+
#include "simulator/simulator.hpp"
46+
#include "simulator/utils.hpp"
47+
48+
namespace {
49+
50+
struct test_result
51+
{
52+
std::vector<lt::error_code> disconnects;
53+
std::vector<lt::tcp::endpoint> connects;
54+
};
55+
56+
test_result test_allow_multiple_connections_per_pid(bool allow
57+
, lt::peer_id const& pid
58+
, char const* peer1_ip
59+
, char const* peer2_ip)
60+
{
61+
// setup the simulation
62+
sim::default_config cfg;
63+
sim::simulation sim{cfg};
64+
auto ios = std::make_unique<sim::asio::io_context>(sim, lt::make_address_v4("50.0.0.1"));
65+
lt::session_proxy zombie;
66+
67+
// setup settings pack
68+
lt::session_params sp;
69+
sp.settings = settings();
70+
sp.settings.set_int(lt::settings_pack::alert_mask
71+
, lt::alert_category::all & ~lt::alert_category::stats);
72+
sp.settings.set_bool(lt::settings_pack::allow_multiple_connections_per_pid, allow);
73+
sp.disk_io_constructor = lt::disabled_disk_io_constructor;
74+
75+
// create session
76+
std::shared_ptr<lt::session> ses = std::make_shared<lt::session>(sp, *ios);
77+
78+
// add torrent
79+
lt::add_torrent_params params = ::create_torrent(0, false);
80+
lt::sha1_hash const info_hash = params.ti->info_hash();
81+
params.flags &= ~lt::torrent_flags::auto_managed;
82+
params.flags &= ~lt::torrent_flags::paused;
83+
ses->async_add_torrent(std::move(params));
84+
85+
// create two fake peers with the same peer-id but different IPs
86+
auto peer1 = std::make_unique<fake_peer>(sim, peer1_ip);
87+
auto peer2 = std::make_unique<fake_peer>(sim, peer2_ip);
88+
peer1->set_peer_id(pid);
89+
peer2->set_peer_id(pid);
90+
91+
test_result result;
92+
93+
// set up a timer to fire later, to shut down
94+
sim::timer t2(sim, lt::seconds(5)
95+
, [&](boost::system::error_code const&)
96+
{
97+
zombie = ses->abort();
98+
ses.reset();
99+
});
100+
101+
print_alerts(*ses, [&](lt::session&, lt::alert const* a)
102+
{
103+
auto* pd = lt::alert_cast<lt::peer_disconnected_alert>(a);
104+
if (pd) result.disconnects.push_back(pd->error);
105+
106+
auto* pa = lt::alert_cast<lt::peer_connect_alert>(a);
107+
if (pa) result.connects.push_back(pa->endpoint);
108+
109+
if (lt::alert_cast<lt::add_torrent_alert>(a))
110+
{
111+
// both peers connect immediately after the torrent is added
112+
peer1->connect_to(ep("50.0.0.1", 6881), info_hash);
113+
peer2->connect_to(ep("50.0.0.1", 6881), info_hash);
114+
}
115+
});
116+
117+
sim.run();
118+
119+
return result;
120+
}
121+
122+
bool has_duplicate_peer_id_error(std::vector<lt::error_code> const& errors)
123+
{
124+
for (auto const& err : errors)
125+
if (err == lt::errors::duplicate_peer_id)
126+
return true;
127+
return false;
128+
}
129+
130+
bool has_connected(std::vector<lt::tcp::endpoint> const& endpoints
131+
, lt::address_v4 const& addr)
132+
{
133+
for (auto const& ep : endpoints)
134+
if (ep.address() == addr)
135+
return true;
136+
return false;
137+
}
138+
139+
} // anonymous namespace
140+
141+
// allow_multiple_connections_per_pid = false:
142+
// a second connection from a different IP but the same peer-id should be rejected
143+
TORRENT_TEST(allow_multiple_connections_per_pid_false)
144+
{
145+
lt::peer_id pid;
146+
std::fill(pid.data(), pid.data() + 20, char(0xAA));
147+
148+
auto result = test_allow_multiple_connections_per_pid(false, pid
149+
, "60.0.0.1", "60.0.0.2");
150+
151+
// verify that a duplicate_peer_id disconnect occurred
152+
TEST_CHECK(has_duplicate_peer_id_error(result.disconnects));
153+
}
154+
155+
// allow_multiple_connections_per_pid = true:
156+
// a second connection from a different IP but the same peer-id should be allowed
157+
TORRENT_TEST(allow_multiple_connections_per_pid_true)
158+
{
159+
lt::peer_id pid;
160+
std::fill(pid.data(), pid.data() + 20, char(0xBB));
161+
162+
auto result = test_allow_multiple_connections_per_pid(true, pid
163+
, "60.0.0.3", "60.0.0.4");
164+
165+
// verify no duplicate_peer_id error occurred
166+
TEST_CHECK(!has_duplicate_peer_id_error(result.disconnects));
167+
168+
// verify that the second peer connected successfully
169+
TEST_CHECK(has_connected(result.connects, lt::make_address_v4("60.0.0.4")));
170+
}

src/bt_peer_connection.cpp

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3518,28 +3518,31 @@ namespace {
35183518
std::copy(recv_buffer.begin(), recv_buffer.begin() + 20, pid.data());
35193519

35203520
// now, let's see if this connection should be closed
3521-
peer_connection* p = t->find_peer(pid);
3522-
if (p)
3523-
{
3524-
TORRENT_ASSERT(p->pid() == pid);
3525-
// we found another connection with the same peer-id
3526-
// which connection should be closed in order to be
3527-
// sure that the other end closes the same connection?
3528-
// the peer with greatest peer-id is the one allowed to
3529-
// initiate connections. So, if our peer-id is greater than
3530-
// the others, we should close the incoming connection,
3531-
// if not, we should close the outgoing one.
3532-
if ((pid < m_our_peer_id) == is_outgoing())
3533-
{
3534-
p->disconnect(errors::duplicate_peer_id, operation_t::bittorrent);
3535-
}
3536-
else
3521+
if (!t->settings().get_bool(settings_pack::allow_multiple_connections_per_pid))
3522+
{
3523+
peer_connection* p = t->find_peer(pid);
3524+
if (p)
35373525
{
3538-
disconnect(errors::duplicate_peer_id, operation_t::bittorrent);
3539-
return;
3526+
TORRENT_ASSERT(p->pid() == pid);
3527+
// we found another connection with the same peer-id
3528+
// which connection should be closed in order to be
3529+
// sure that the other end closes the same connection?
3530+
// the peer with greatest peer-id is the one allowed to
3531+
// initiate connections. So, if our peer-id is greater than
3532+
// the others, we should close the incoming connection,
3533+
// if not, we should close the outgoing one.
3534+
if ((pid < m_our_peer_id) == is_outgoing())
3535+
{
3536+
p->disconnect(errors::duplicate_peer_id, operation_t::bittorrent);
3537+
}
3538+
else
3539+
{
3540+
disconnect(errors::duplicate_peer_id, operation_t::bittorrent);
3541+
return;
3542+
}
35403543
}
35413544
}
3542-
3545+
35433546
set_pid(pid);
35443547
m_client_version = identify_client(pid);
35453548
if (pid[0] == '-' && pid[1] == 'B' && pid[2] == 'C' && pid[7] == '-')

src/settings_pack.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ constexpr int DISK_WRITE_MODE = settings_pack::enable_os_cache;
242242
SET(socks5_udp_send_local_ep, false, nullptr),
243243
SET(proxy_send_host_in_connect, false, nullptr),
244244
SET(disk_disable_copy_on_write, false, nullptr),
245+
SET(allow_multiple_connections_per_pid, false, nullptr),
245246
}});
246247

247248
CONSTEXPR_SETTINGS

0 commit comments

Comments
 (0)