Files
libtorrent/simulation/test_outgoing_interfaces.cpp
2026-07-26 11:35:31 -07:00

167 lines
5.5 KiB
C++

/*
Copyright (c) 2026, Arvid Norberg
All rights reserved.
You may use, distribute and modify this code under the terms of the BSD license,
see LICENSE file.
*/
#include "libtorrent/session.hpp"
#include "libtorrent/torrent_handle.hpp"
#include "libtorrent/settings_pack.hpp"
#include "libtorrent/alert_types.hpp"
#include "libtorrent/aux_/deadline_timer.hpp"
#include "settings.hpp"
#include "create_torrent.hpp"
#include "simulator/simulator.hpp"
#include "simulator/socks_server.hpp"
#include "simulator/utils.hpp"
#include "fake_peer.hpp"
#include "utils.hpp"
using namespace sim;
namespace {
// under the simulator, enum_net_interfaces() always reports the interface
// name "eth0" for every address on a node (see enum_net_interfaces() in
// src/enum_net.cpp).
constexpr char const* device_name = "eth0";
template <typename Settings, typename Alert, typename Test>
void run_test(Settings const& make_settings, Alert const& on_alert, Test const& test)
{
// setup the simulation
sim::default_config network_cfg;
sim::simulation sim{network_cfg};
sim::asio::io_context ios(sim, asio::ip::make_address_v4("50.0.0.1"));
lt::session_proxy zombie;
// a real, working SOCKS5 relay, used by the proxy test below to make sure
// outgoing connections actually go all the way through and reach
// verify_bound_address(), instead of failing earlier for unrelated
// reasons (e.g. a proxy that doesn't exist).
sim::asio::io_context proxy_ios{sim, asio::ip::make_address_v4("50.50.50.50")};
sim::socks_server socks5(proxy_ios, 5555, 5);
lt::settings_pack pack = settings();
make_settings(pack);
std::shared_ptr<lt::session> ses = std::make_shared<lt::session>(pack, ios);
fake_peer p1(sim, "60.0.0.0");
lt::add_torrent_params params = ::create_torrent(0, false);
params.flags &= ~lt::torrent_flags::auto_managed;
params.flags &= ~lt::torrent_flags::paused;
ses->async_add_torrent(params);
print_alerts(*ses, [&](lt::session&, lt::alert const* a) {
if (auto at = lt::alert_cast<lt::add_torrent_alert>(a))
{
lt::torrent_handle h = at->handle;
add_fake_peer(h, 0);
}
on_alert(a);
});
sim::timer t(sim, lt::seconds(60), [&](boost::system::error_code const&) {
test(p1);
// shut down
zombie = ses->abort();
p1.close();
ses.reset();
});
sim.run();
}
template <typename Settings, typename Test>
void run_test(Settings const& make_settings, Test const& test)
{
run_test(make_settings, [](lt::alert const*) {}, test);
}
} // anonymous namespace
// settings_pack::outgoing_interfaces restricts which local device outgoing
// peer connections may bind to. session_impl caches the result of
// enumerating the local network interfaces (m_cached_interfaces) instead of
// asking the OS every time verify_bound_address() is called. This makes sure
// that cache is actually populated and used correctly: when the configured
// device matches the one we're bound to, the connection is allowed through.
TORRENT_TEST(outgoing_interfaces_matching_device_allowed)
{
run_test(
[](lt::settings_pack& pack) {
pack.set_str(lt::settings_pack::outgoing_interfaces, device_name);
},
[](fake_peer& p) { TEST_EQUAL(p.connected(), true); });
}
// the opposite of outgoing_interfaces_matching_device_allowed: when the
// configured device does not match, verify_bound_address() must reject the
// connection and it must never complete a BT handshake.
TORRENT_TEST(outgoing_interfaces_wrong_device_blocked)
{
run_test(
[](lt::settings_pack& pack) {
pack.set_str(lt::settings_pack::outgoing_interfaces, "nonexistent0");
},
[](fake_peer& p) { TEST_EQUAL(p.connected(), false); });
}
// same as outgoing_interfaces_matching_device_allowed, but with peer
// connections proxied. reopen_listen_sockets() only refreshes
// m_cached_interfaces unconditionally when it also needs the interface list
// to build listen endpoints; when proxying peer connections it must still
// populate the cache whenever outgoing_interfaces is set, since
// verify_bound_address() depends on it for every connection attempt.
TORRENT_TEST(outgoing_interfaces_matching_device_allowed_with_proxy)
{
run_test(
[](lt::settings_pack& pack) {
pack.set_str(lt::settings_pack::outgoing_interfaces, device_name);
pack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::socks5);
pack.set_str(lt::settings_pack::proxy_hostname, "50.50.50.50");
pack.set_int(lt::settings_pack::proxy_port, 5555);
pack.set_bool(lt::settings_pack::proxy_peer_connections, true);
},
[](fake_peer& p) { TEST_EQUAL(p.connected(), true); });
}
#ifndef TORRENT_DISABLE_LOGGING
// peer_dscp must be applied after opening the socket but before initiating
// the outgoing TCP connection. Otherwise set_traffic_class() fails with
// error::bad_descriptor and the SYN is sent without the configured DSCP.
TORRENT_TEST(outgoing_peer_dscp)
{
bool socket_opened = false;
bool dscp_error = false;
run_test(
[](lt::settings_pack& pack) {
pack.set_int(lt::settings_pack::peer_dscp, 0x04);
pack.set_bool(lt::settings_pack::enable_outgoing_utp, false);
},
[&](lt::alert const* a) {
auto const* pl = lt::alert_cast<lt::peer_log_alert>(a);
if (pl == nullptr)
return;
if (pl->event_type == lt::peer_log_alert::open)
socket_opened = true;
else if (pl->event_type == lt::peer_log_alert::set_dscp)
{
TEST_EQUAL(socket_opened, true);
dscp_error = true;
}
},
[&](fake_peer& p) {
TEST_EQUAL(p.connected(), true);
TEST_EQUAL(socket_opened, true);
TEST_EQUAL(dscp_error, false);
});
}
#endif