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

Request: Allow binding specific interface. #304

Open
Quackdoc opened this issue Jan 15, 2025 · 6 comments
Open

Request: Allow binding specific interface. #304

Quackdoc opened this issue Jan 15, 2025 · 6 comments

Comments

@Quackdoc
Copy link

Currently it seems rqbit will use whatever interface is the "default" on the PC. sadly this means that if I start a download while my VPN (for work) is enabled it will use that. This is less then ideal.

The other benefit is... obvious for folk who live in countries with strong laws. I did do a simple patch below which seems to work for this. for my use case at least. I'm not sure about any security concerns such as ip leaks, I presume DHT would need to be set for it's interface as well. It may also be nice to set for upnp as some people may just want to share on a single network like zerotier or tailscale, likewise I can also see this being useful for the http api.

It's worth noting that just binding to an ip address can often be insufficient as if you have multiple VPNs like I do for work and play, it is possible for VPNs to have conflicting IP addresses which can cause the user to need some really janky routing which can be fairly complex to set up for some folk

diff --git a/crates/librqbit/src/session.rs b/crates/librqbit/src/session.rs
index 6606f7a..26deefd 100644
--- a/crates/librqbit/src/session.rs
+++ b/crates/librqbit/src/session.rs
@@ -600,6 +600,7 @@ impl Session {
                     reqwest::Client::builder().proxy(proxy)
                 } else {
                     reqwest::Client::builder()
+                    .interface("MY-INTERFACE-HERE")
                 };

                 builder.build().context("error building HTTP(S) client")?
@Quackdoc
Copy link
Author

Quackdoc commented Jan 16, 2025

hmm, I have tried using this, and i noticed uploading doesn't work when set for the VPN. Sadly it seems like tcplistener cannot bind to a specific interface, It may be possible to use socket2 in the future however on linux it is locked bind rust-lang/socket2#500 and on windows I don't think this will work either.

EDIT: I didn't realize tokio actually supported it now, I used old docs. Turns out something like this will work on android fuscia and linux, sadly, it doesn't seem to work on apple devices like socket2 does.

async fn create_tcp_listener(
    port_range: std::ops::Range<u16>,
) -> anyhow::Result<(TcpListener, u16)> {
    let addr = "0.0.0.0:48207".parse().unwrap();
    let socket = TcpSocket::new_v4()?;
    socket.bind_device(Some(b"MY-INTERFACE-HERE"));
    socket.bind(addr)?;

    match socket.listen(1024) {
        Ok(l) => return Ok((l, port)),
        Err(e) => {
            error!("error listening on port {port}: {e:#}")
        }
    }
    bail!("no free TCP ports in range 48207");
}

@ikatson
Copy link
Owner

ikatson commented Jan 16, 2025

that if I start a download while my VPN (for work) is enabled it will use that

binding the TCP listener to a different interface won't help here. The TCP listener is only used for incoming connections. Most of the downloads are done through outgoing connections.

The outgoing connections to peers are just TCP connect(addr). Also DHT sends its own UDP packets, HTTP (e.g. trackers) sends its own etc. It just uses your default gateway which happens to be your VPN. I'm not aware of socket setsockopt magic, esp. cross-platform that will give you full control of the routing.

Instead, the solution to "rqbit uses one interface, everything else uses another" should be outside of rqbit. E.g. on Linux, you can put rqbit in its own network namespace, and then configure it as you wish, including routing, tunneling through VPNs etc. You could also use policy based routing.

On other platforms smth similar might exist, although I haven't tried.

Allow binding specific network interface

Binding the TCP listener to a different interface might be useful only if you really don't want to listen for incoming connections from those interfaces, which seems very niche unless someone gives a specific example within their network setup that makes this reasonable.

@Quackdoc
Copy link
Author

Quackdoc commented Jan 16, 2025

Binding a TCP listener to an interface is necessary when you don't have routing setup. In my case, I use multiple VPNs all at the same time. And anytime I need an app to use a specific VPN I just bind to the VPN itself, which saves me from having to do a bunch of complicated routing setup.

In my case, it's necessary to bind the TCP listener to the VPN to see incoming connections, If I don't do it because I don't have the routing set up on my PC, then I cannot see the incoming connections and every external app just sees the port as closed.

It is true you can use namespaces but on some configurations that can really be quite a nightmare to set up. Setting TCP listener seems to more in line work with other popular VPN solutions, such as qbittorrent.

EDIT: Typically this socket stuff should work across unix platforms, but it seems the various implementations don't all seem to work with one another.

@Quackdoc
Copy link
Author

Quackdoc commented Jan 20, 2025

The things I found regarding upload/download are

  • tracker_comms tracker_comms - tracker_comms_udp.rs
  • dht dht - dht.src
  • upnp upnp - lib.rs
  • setting port for upload librqbit - session.rs
  • reqwest for download stuff/misc librqbit - session.rs

are there any that I am missing?

@ikatson
Copy link
Owner

ikatson commented Jan 20, 2025

Fair enough, I read about those socket options. If your prototype works as intended I'll be fine merging this in one it's ready. Thanks

@ikatson
Copy link
Owner

ikatson commented Jan 20, 2025

are there any that I am missing?

Nothing else comes to mind but if they exist we'll fix them later

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

No branches or pull requests

2 participants