From 9acb255cfa85ddd54300cd94fb40e5f5ba1ab892 Mon Sep 17 00:00:00 2001 From: arvidn Date: Sat, 13 Jul 2024 11:56:24 -0500 Subject: [PATCH] add option to send host header in HTTP proxy CONNECT command --- ChangeLog | 1 + include/libtorrent/aux_/proxy_settings.hpp | 4 +++ include/libtorrent/http_stream.hpp | 14 ++++++---- include/libtorrent/settings_pack.hpp | 8 ++++++ simulation/test_http_connection.cpp | 30 ++++++++++++++++++---- src/http_connection.cpp | 6 +++++ src/proxy_settings.cpp | 2 ++ src/settings_pack.cpp | 1 + src/torrent.cpp | 19 ++++++++++++-- 9 files changed, 73 insertions(+), 12 deletions(-) diff --git a/ChangeLog b/ChangeLog index de183faa2ae..aaefbd9761d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,7 @@ 2.0.11 not released + * add option to send host header in HTTP proxy CONNECT command * don't hint FADV_RANDOM on posix systems. May improve seeding performance * allow boost connect while checking resume data if no_verify_files flag is set * fix BEP-40 peer priority for IPv6 diff --git a/include/libtorrent/aux_/proxy_settings.hpp b/include/libtorrent/aux_/proxy_settings.hpp index 57cf0b64f92..283c7e706c3 100644 --- a/include/libtorrent/aux_/proxy_settings.hpp +++ b/include/libtorrent/aux_/proxy_settings.hpp @@ -86,6 +86,10 @@ namespace aux { // if true, tracker connections are subject to the proxy settings bool proxy_tracker_connections = true; + + // when proxying non plain HTTP over an HTTP proxy (SSL or peer + // connections), also tell the proxy which hostname we're connecting to + bool send_host_in_connect = false; }; }} diff --git a/include/libtorrent/http_stream.hpp b/include/libtorrent/http_stream.hpp index 9151dcee5dd..56249d1bb13 100644 --- a/include/libtorrent/http_stream.hpp +++ b/include/libtorrent/http_stream.hpp @@ -60,21 +60,21 @@ class http_stream : public proxy_base m_password = password; } - void set_dst_name(std::string const& host) + void set_host(std::string const& host) { - m_dst_name = host; + m_host = host; } void close(error_code& ec) { - m_dst_name.clear(); + m_host.clear(); proxy_base::close(ec); } #ifndef BOOST_NO_EXCEPTIONS void close() { - m_dst_name.clear(); + m_host.clear(); proxy_base::close(); } #endif @@ -129,6 +129,10 @@ class http_stream : public proxy_base std::back_insert_iterator> p(m_buffer); std::string const endpoint = print_endpoint(m_remote_endpoint); write_string("CONNECT " + endpoint + " HTTP/1.0\r\n", p); + if (!m_host.empty()) + { + write_string("Host: " + m_host + "\r\n", p); + } if (!m_user.empty()) { write_string("Proxy-Authorization: Basic " + base64encode( @@ -218,7 +222,7 @@ class http_stream : public proxy_base // proxy authentication std::string m_user; std::string m_password; - std::string m_dst_name; + std::string m_host; // this is true if the connection is HTTP based and // want to talk directly to the proxy diff --git a/include/libtorrent/settings_pack.hpp b/include/libtorrent/settings_pack.hpp index 0446f2b60d6..71f10e296ab 100644 --- a/include/libtorrent/settings_pack.hpp +++ b/include/libtorrent/settings_pack.hpp @@ -1014,6 +1014,14 @@ namespace aux { // protocol may not be valid from the proxy's point of view. socks5_udp_send_local_ep, + // When using HTTP proxy (in proxy_type), libtorrent will connect + // to peers and trackers using the `CONNECT` proxy command. In this + // command it's possible to reveal the hostname of the server we're + // connecting to. When this option is true, the hostname will be + // sent. This feature can be useful if the proxy is used to + // man-in-the-middle connections. + proxy_send_host_in_connect, + max_bool_setting_internal }; diff --git a/simulation/test_http_connection.cpp b/simulation/test_http_connection.cpp index 69833e27db2..569a533b187 100644 --- a/simulation/test_http_connection.cpp +++ b/simulation/test_http_connection.cpp @@ -270,7 +270,7 @@ void run_suite(lt::aux::proxy_settings ps) // the try-next test in his case would test the socks proxy itself, whether // it has robust retry behavior (which the simple test proxy that comes with // libsimulator doesn't). - if (ps.proxy_hostnames == false) + if (ps.type != settings_pack::socks5 && ps.proxy_hostnames == false) { // this hostname will resolve to multiple IPs, all but one that we cannot // connect to and the second one where we'll get the test file response. Make @@ -600,9 +600,7 @@ TORRENT_TEST(http_connection_http_error) test_proxy_failure(settings_pack::http); } -// Requests a proxied SSL connection. This test just ensures that the correct CONNECT request -// is sent to the proxy server. -TORRENT_TEST(http_connection_ssl_proxy) +void test_connection_ssl_proxy(bool const with_hostname) { using sim::asio::ip::address_v4; sim_config network_cfg; @@ -615,15 +613,24 @@ TORRENT_TEST(http_connection_ssl_proxy) sim::http_server http_proxy(proxy_ios, 4445); lt::aux::proxy_settings ps = make_proxy_settings(settings_pack::http); + ps.send_host_in_connect = with_hostname; int client_counter = 0; int proxy_counter = 0; http_proxy.register_handler("10.0.0.2:8080" - , [&proxy_counter](std::string method, std::string req, std::map&) + , [&proxy_counter, with_hostname](std::string method, std::string req, std::map& headers) { proxy_counter++; TEST_EQUAL(method, "CONNECT"); + if (with_hostname) + { + TEST_EQUAL(headers["host"], "10.0.0.2"); + } + else + { + TEST_CHECK(headers.empty()); + } return sim::send_response(403, "Not supported", 1337); }); @@ -656,6 +663,19 @@ TORRENT_TEST(http_connection_ssl_proxy) TEST_EQUAL(proxy_counter, 1); } +// Requests a proxied SSL connection. This test just ensures that the correct CONNECT request +// is sent to the proxy server. +TORRENT_TEST(http_connection_ssl_proxy_no_hostname) +{ + test_connection_ssl_proxy(false); +} + +TORRENT_TEST(http_connection_ssl_proxy_hostname) +{ + test_connection_ssl_proxy(true); +} + + // TODO: test http proxy with password // TODO: test socks5 with password // TODO: test SSL diff --git a/src/http_connection.cpp b/src/http_connection.cpp index b47f76b877e..57e48f919ca 100644 --- a/src/http_connection.cpp +++ b/src/http_connection.cpp @@ -588,6 +588,12 @@ void http_connection::connect() } } + if (m_proxy.send_host_in_connect + && boost::get(&*m_sock)) + { + boost::get(*m_sock).set_host(m_hostname); + } + TORRENT_ASSERT(m_next_ep < int(m_endpoints.size())); if (m_next_ep >= int(m_endpoints.size())) return; diff --git a/src/proxy_settings.cpp b/src/proxy_settings.cpp index 01fa002555f..14e607d7ea6 100644 --- a/src/proxy_settings.cpp +++ b/src/proxy_settings.cpp @@ -51,6 +51,8 @@ void init(proxy_settings& p, Settings const& sett) settings_pack::proxy_peer_connections); p.proxy_tracker_connections = sett.get_bool( settings_pack::proxy_tracker_connections); + p.send_host_in_connect = sett.get_bool( + settings_pack::proxy_send_host_in_connect); } } diff --git a/src/settings_pack.cpp b/src/settings_pack.cpp index 5a2c20f7405..df32a2695cf 100644 --- a/src/settings_pack.cpp +++ b/src/settings_pack.cpp @@ -239,6 +239,7 @@ constexpr int DISK_WRITE_MODE = settings_pack::enable_os_cache; SET(allow_idna, false, nullptr), SET(enable_set_file_valid_data, false, nullptr), SET(socks5_udp_send_local_ep, false, nullptr), + SET(proxy_send_host_in_connect, false, nullptr), }}); CONSTEXPR_SETTINGS diff --git a/src/torrent.cpp b/src/torrent.cpp index 538e8963450..6d68e057ca3 100644 --- a/src/torrent.cpp +++ b/src/torrent.cpp @@ -6658,11 +6658,11 @@ namespace { aux::socket_type s = instantiate_connection(m_ses.get_context() , m_ses.proxy(), userdata, nullptr, true, false); - if (boost::get(&s)) + if (auto* inner = boost::get(&s)) { // the web seed connection will talk immediately to // the proxy, without requiring CONNECT support - boost::get(s).set_no_connect(true); + inner->set_no_connect(true); } std::string hostname; @@ -6723,6 +6723,21 @@ namespace { bool const proxy_hostnames = settings().get_bool(settings_pack::proxy_hostnames) && !is_ip; + if (!is_ip + && settings().get_bool(settings_pack::proxy_send_host_in_connect)) + { + if (auto* inner1 = boost::get(&s)) + { + inner1->set_host(hostname); + } +#if TORRENT_USE_SSL + else if (auto* inner2 = boost::get>(&s)) + { + inner2->next_layer().set_host(hostname); + } +#endif + } + if (proxy_hostnames && (boost::get(&s) #if TORRENT_USE_SSL