/* 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 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 ses = std::make_shared(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(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 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(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