Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enabling listening on tcp port when proxy is used for peer connections #7726

Open
wants to merge 5 commits into
base: RC_2_0
Choose a base branch
from

Conversation

Masong19hippows
Copy link

qbittorrent disables the listening tcp port when the option "Use proxy for peer connections" is enabled. This is mentioned in #21134 and #8718.

I don't believe this should be the case as its a fundamental misunderstanding of what the proxy should do. Proxies are only for outbound connections and should not change behavior of incoming connections. This is outlined in the http proxy spec as defining a proxy as a service for making requests.

I think the current implementation is an artificial limitation set by libtorrent rather than the protocol itself. There are plenty of reasons to still accept incoming connections while using the proxy for outbound connections. A very reason is for networks that require a proxy for internet.

This pull request is heavily based on this article.

@arvidn
Copy link
Owner

arvidn commented Oct 1, 2024

@Masong19hippows that link appears to be broken. Is that text posted anywhere else?

@arvidn
Copy link
Owner

arvidn commented Oct 1, 2024

it's not surprising the HTTP proxy specification from 1997 defines proxy as an intermediary for making requests. That was essentially the only thing you could do with HTTP. The Socks5 protocol (which is also called "proxy") does in fact support incoming connections, both over TCP and UDP.

I wouldn't consider that text a compelling argument to change libtorrents behavior. I think enumerating the legitimate use cases might be.

Either way, if there is a compelling argument, I think it would be best to guard it under a new setting, rather than changing behavior for everyone.

@Masong19hippows
Copy link
Author

@Masong19hippows that link appears to be broken. Is that text posted anywhere else?

Unfortunately, it doesn't look like it. However, it still cached on my machine via google amp. I just coped the text article and here it is.

Patch qBittorrent: Listening on port while using proxy
Brandon - 05 11月 2022

First things first, to achieve that, we need a proxy where we can set up port forwarding on it, then we modify and compile qbittorrent ourselves. A VPN can work, but that requires UPnP/PMP support of the VPN, which many lacks in. And also, use with caution, if you do not want to expose your IP address accidentally, always use a VPN.

Recently, I came across a situation where I need to do some seeding, but the network I was on requires a proxy to gain access to the Internet. Naively, I set up the proxy settings in qbittorrent, quickly found out that it did not work at all.

After some long nights searching and digging, it turned out that the library qbittorrent (along with several other FOSS torrenting software) depends on, libtorrent, on some recent versions, completely disables port listening while Use proxy for peer connections is enabled, to avoid network traffic leakage, rendering it impossible to do seeding at all while using a proxy. But the authors never make this clear, and I suspect that qbittorrent authors haven't even realized this behavior. The setting page on qbittorrent doesn't reflect the change in port listening section at all when you enable proxy for peers.

So here come the easier parts. We find the corresponding conditional code, modify, patch, then recompile libtorrent. No changes in qbittorrent are needed, only libtorrent. Check out my patch file listen-when-proxy-on.patch based on v2.0.7 of libtorrent at end of this article.

Since I'm on archlinux (and yes, BTW, I'm using arch :D), patching software is relatively convenient. Go grab the PKGBUILD script from arch offical package page https://archlinux.org/packages/extra/x86_64/libtorrent-rasterbar/ (source files link), add our patch into prepare() section like this:

prepare() {
makedir -p build
cd "$pkgname-$pkgver"
patch --forward --strip=1 --input="${srcdir}/listen-when-proxy-on.patch"
}
and do makepkg -Acs to compile the package. After it's done, sudo pacman -U libtorrent... to do a force install. Then we need to do some config stuff.

Fire up qbittorrent and set a fixed listening port, say 7890;
Then set up port forwarding on the proxy server we're gonna use. I'm using FRP for port forwarding on my proxy server, so I set up a tcp reverse tunnel with local_port as 7890 on my computer;
Go to qbittorrent advanced settings page, enable Allow multiple connections from the same IP address;
Finally, set proxy server for qbittorrent, and enable proxy for peer connections. Do not turn on UPnP/NAT-PMP. But if you know what you're doing, the patch ensures that they can be turned on along with proxy.
Now we can add a torrent and see, it should start listening for incoming connections from the reverse tunnel we just set on the proxy server. And we can also see all the incoming peers shown as localhost or 127.0.0.1. No worries, the bittorrent protocol doesn't care about peers' ip addresses, from everyone else's perspective, you're just running qbittorrent on 7890 on the proxy server, everything works fine.

Disclaimer: qBittorrent and libtorrent are FOSS, the patch shared here is also free from restrictions. Please comply with your local regulations, use with caution and at your own risks.

listen-when-proxy-on.patch:

diff --git a/src/session_impl.cpp b/src/session_impl.cpp
index e25434926..8f38a3344 100644
--- a/src/session_impl.cpp
+++ b/src/session_impl.cpp
@@ -2016,7 +2016,7 @@ namespace {

// if we don't proxy peer connections, don't apply the special logic for
// proxies

  • if (m_settings.get_int(settings_pack::proxy_type) != settings_pack::none
  • if (false && m_settings.get_int(settings_pack::proxy_type) != settings_pack::none
    && m_settings.get_bool(settings_pack::proxy_peer_connections))
    {
    // we will be able to accept incoming connections over UDP. so use
    @@ -2774,7 +2774,7 @@ namespace {
    // directly.
    // This path is only for accepting incoming TCP sockets. The udp_socket
    // class also restricts incoming packets based on proxy settings.
  • if (m_settings.get_int(settings_pack::proxy_type) != settings_pack::none
  • if (false && m_settings.get_int(settings_pack::proxy_type) != settings_pack::none
    && m_settings.get_bool(settings_pack::proxy_peer_connections))
    return;

@@ -5473,7 +5473,7 @@ namespace {
// connections. Not even uTP connections via the port we know about.
// The DHT may use the implied port to make it work, but the port we
// announce here has no relevance for that.

  • if (sock->flags & listen_socket_t::proxy)
  • if (false && sock->flags & listen_socket_t::proxy)
    return 0;

    if (!(sock->flags & listen_socket_t::accept_incoming))
    @@ -5513,7 +5513,7 @@ namespace {
    return std::uint16_t(sock->tcp_external_port());
    }

  • if (m_settings.get_int(settings_pack::proxy_type) != settings_pack::none
  • if (false && m_settings.get_int(settings_pack::proxy_type) != settings_pack::none
    && m_settings.get_bool(settings_pack::proxy_peer_connections))
    return 0;

@@ -5611,7 +5611,7 @@ namespace {

if (!s->natpmp_mapper
&& !(s->flags & listen_socket_t::local_network)

  • && !(s->flags & listen_socket_t::proxy))
  • && !(s->flags & listen_socket_t::proxy && false))
    {
    // the natpmp constructor may fail and call the callbacks
    // into the session_impl.
    @@ -6833,8 +6833,9 @@ namespace {
    // there's no point in starting the UPnP mapper for a network that isn't
    // connected to the internet. The whole point is to forward ports through
    // the gateway
  • // Enable UPnP anyway, even when on proxy, we can deal with this ourselves.
    if ((s->flags & listen_socket_t::local_network)
  • || (s->flags & listen_socket_t::proxy))
  • || (s->flags & listen_socket_t::proxy && false))
    return;

    if (!s->upnp_mapper)
    小本本
    ✏️📖🖥️

Copy link
Owner

@arvidn arvidn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one possible way to implement this would be to add a new proxy type, like http_with_local_listen, or something like that. It would behave like http but also accept incoming connections

Comment on lines 5669 to 5677
// the natpmp constructor may fail and call the callbacks
// into the session_impl.
s->natpmp_mapper = std::make_shared<natpmp>(m_io_context, *this, listen_socket_handle(s));
ip_interface ip;
ip.interface_address = s->local_endpoint.address();
ip.netmask = s->netmask;
std::strncpy(ip.name, s->device.c_str(), sizeof(ip.name) - 1);
ip.name[sizeof(ip.name) - 1] = '\0';
s->natpmp_mapper->start(ip);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it doesn't make any sense to disable NAT-PMP entirely. this seems unrelated to the intention of this patch

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this was just a mistake on my end. It was not my intention to disable nat-pmp entirely. Rather, it was to enable the ability to use it regardless of proxy.

@Masong19hippows
Copy link
Author

Masong19hippows commented Oct 1, 2024

The Socks5 protocol (which is also called "proxy") does in fact support incoming connections, both over TCP and UDP.

It does not. The socks4 protocol specifies bind and connect commands while the socks5 protocol specifies auth and the udp communication commands. Bind in the socks4 protocol is defined as:

"The client connects to the SOCKS server and sends a BIND request when
it wants to prepare for an inbound connection from an application server.
This should only happen after a primary connection to the application".

and in the socks5 spec:

"It is expected that the client side of an application protocol will
use the BIND request only to establish secondary connections after a
primary connection is established using CONNECT"

This is not facilitating inbound connections in the traditional sense, rather it facilitates inbound connections on an already existing outbound connection. In the spec, it used ftp as an example, and I think this is perfect because ftp needs at least 2 connections to the client, one inbound and one outbound. I think this explains it better than I could. If we were to hack the bind command in order to listen and forward ports, the qbittorrent host would first need to send an outbound connect command to all potential leachers. This would require massive changes and its more hacky than it should be.

There are applications that extend/break the socks protocol (like ssh) to include the support of inbound connections. However, this is not something outlined in the socks protocol itself, and so it shouldn't be treated as such in my opinion.

I wouldn't consider that text a compelling argument to change libtorrents behavior. I think enumerating the legitimate use cases might be.

I was more just trying to point out that the proxy server itself should not facilitate inbound connections, because this was used in the past issues to justify libtorrent behavior.

I have been using this PR with qbittorrent for awhile now and I honestly haven't noticed any issues. Before, the seeding wasn't working well. As for the use cases, I see this as removing an artificial limitation rather than adding a feature. The use case would be that the user would get full seeding/leaching potential. It honestly works so well that I had to limit the number of inbound connections because it overloaded my socks5 server and used all of the ports on my system.

Either way, if there is a compelling argument, I think it would be best to guard it under a new setting, rather than changing behavior for everyone.

That makes sense. I just did a quick and dirty thing to try and get the conversation started, but I am willing to learn and expand this.

@anonbaby
Copy link

anonbaby commented Oct 8, 2024

We can setup reserve proxy/port forwarding on the same server with http/socks proxy.
That would be equivalent to wireguard+port forwarding setup, if port listening is not disabled.

@Masong19hippows
Copy link
Author

Masong19hippows commented Oct 8, 2024

We can setup reserve proxy/port forwarding on the same server with http/socks proxy.
That would be equivalent to wireguard+port forwarding setup, if port listening is not disabled.

I'm unsure what you mean. Can you please elaborate?

I get the setup up a reverse proxy type thing and forwarding the port to the socks server, but what would the socks server do with it once it gets that traffic? Socks servers do not accept inbound traffic to forward to a host unless otherwise defined by the socks bind protocol. There's no way to do this with just socks.

If I'm getting what you are putting down, I am actually already doing this right now. I am using the patch above to listen to the port while using a proxy, and then using socat to forward the port from the socks server to the qbittorrent host.

Qbittorrent should not have anything to do with a reverse proxy though, because the socks host and the qbittorrent host might be on separate machines. Any way you look at it, you can't inheritly forward ports from the socks host to the qbittorrent host with just the socks protocol.

@anonbaby
Copy link

anonbaby commented Oct 8, 2024 via email

@Masong19hippows
Copy link
Author

@arvidn can you please review again? I changed this so that there is now a new setting to enable listening while using a proxy. The default value for this is false to maintain compatibility between versions. When this value is false, it follows the original logic before the PR.

I also branched and made changes to qbittorrent in order to test these changes. This can be found in the listenOnProxy branch at https://github.com/Masong19hippows/qBittorrent. There are some bugs that I need to fix and I need to figure out the translations, but it should provide a good enough platform for testing the change. The most notable issue with the qbittorrent branch is that if you change the new setting, you will need to restart qbittorrent in order for it to be applied.

@arvidn
Copy link
Owner

arvidn commented Oct 27, 2024

This test is ensuring some aspects of ABI stability and is failing. https://ci.appveyor.com/project/arvidn/libtorrent/builds/50811938/job/l22u63al4sr0ykyn#L1156

the listen_on_proxy enum value has to be added at the end of the enums, to ensure that all existing ones are unchanged.

Copy link
Owner

@arvidn arvidn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this could be simplified by using the existing listen socket flag listen_socket_t::accept_incoming instead of tacking on additional conditions

include/libtorrent/settings_pack.hpp Outdated Show resolved Hide resolved
include/libtorrent/aux_/proxy_settings.hpp Outdated Show resolved Hide resolved
@@ -6891,7 +6896,7 @@ namespace {
// connected to the internet. The whole point is to forward ports through
// the gateway
if ((s->flags & listen_socket_t::local_network)
|| (s->flags & listen_socket_t::proxy))
|| (s->flags & !m_settings.get_bool(settings_pack::listen_on_proxy)))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not right. the & is a bit-wise AND operation. you can mask the flags field against flags/bits set in this field. get_bool() returns a bool, not a flag value. I think you meant to preserve the existing check and add this additional one.

@@ -5664,7 +5669,7 @@ namespace {

if (!s->natpmp_mapper
&& !(s->flags & listen_socket_t::local_network)
&& !(s->flags & listen_socket_t::proxy))
&& m_settings.get_bool(settings_pack::listen_on_proxy))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you don't configure a proxy and leave listen_on_proxy as false; it looks like this change would disable NAT-PMP in that case. I think what you meant to do was to add another condition to the flags & proxy check.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there might be a simpler way of doing this. Rather than checking the settings here, I think you should be able to check the listen_socket_t::accept_incoming flag. You'd have to make sure this flag is set correctly depending on the listen_on_proxy setting when the sockets are created though

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@arvidn would it be acceptable to just remove the proxy being a factor in whether the user can enable natpmp? Or is this something that is best left in another PR/Issue/Discussion?

@@ -5566,9 +5574,6 @@ namespace {
return std::uint16_t(sock->tcp_external_port());
}

if (m_settings.get_int(settings_pack::proxy_type) != settings_pack::none
&& m_settings.get_bool(settings_pack::proxy_peer_connections))
return 0;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would have expected this also depend on the listen_on_proxy setting, no?

@@ -5522,11 +5525,11 @@ namespace {
if (m_listen_sockets.empty()) return 0;
if (sock)
{
// if we're using a proxy, we won't be able to accept any TCP
// connections. Not even uTP connections via the port we know about.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better to update or extend this comment, rather than removing it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants