mirror of https://github.com/ikatson/rqbit
BREAKING: add uTP support to desktop
This commit is contained in:
parent
789cc26a36
commit
3d31175a0c
4
Makefile
4
Makefile
|
|
@ -14,11 +14,11 @@ webui-dev: webui-deps
|
|||
export RQBIT_UPNP_SERVER_ENABLE ?= true
|
||||
export RQBIT_UPNP_SERVER_FRIENDLY_NAME ?= rqbit-dev
|
||||
export RQBIT_HTTP_API_LISTEN_ADDR ?= [::]:3030
|
||||
export RQBIT_EXPERIMENTAL_UTP_LISTEN_ENABLE ?= true
|
||||
export RQBIT_FASTRESUME = true
|
||||
|
||||
# Don't expose devserver
|
||||
export RQBIT_UPNP_PORT_FORWARD_DISABLE = true
|
||||
export RQBIT_TCP_LISTEN_DISABLE = true
|
||||
export RQBIT_LISTEN_IP = 127.0.0.1
|
||||
|
||||
CARGO_RUN_FLAGS ?=
|
||||
RQBIT_OUTPUT_FOLDER ?= /tmp/scratch
|
||||
|
|
|
|||
|
|
@ -96,11 +96,14 @@ impl ListenerOptions {
|
|||
if !self.mode.utp_enabled() {
|
||||
return Ok::<_, anyhow::Error>(None);
|
||||
}
|
||||
Ok(Some(
|
||||
UtpSocketUdp::new_udp_with_opts(self.listen_addr, utp_opts)
|
||||
let socket = UtpSocketUdp::new_udp_with_opts(self.listen_addr, utp_opts)
|
||||
.await
|
||||
.context("error starting uTP listener")?,
|
||||
))
|
||||
.context("error starting uTP listener")?;
|
||||
info!(
|
||||
"Listening on UDP {:?} for incoming uTP peer connections",
|
||||
self.listen_addr
|
||||
);
|
||||
Ok(Some(socket))
|
||||
};
|
||||
|
||||
let announce_port = if self.listen_addr.ip().is_loopback() {
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ pub enum WriterRequest {
|
|||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Default, Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
#[derive(Default, Debug, Copy, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
||||
pub struct PeerConnectionOptions {
|
||||
#[serde_as(as = "Option<serde_with::DurationSeconds>")]
|
||||
pub connect_timeout: Option<Duration>,
|
||||
|
|
|
|||
|
|
@ -700,6 +700,7 @@ impl Session {
|
|||
}
|
||||
if let Some(announce_port) = listen.announce_port {
|
||||
if listen.enable_upnp_port_forwarding {
|
||||
info!(port = announce_port, "starting UPnP port forwarder");
|
||||
session.spawn(
|
||||
error_span!(parent: session.rs(), "upnp_forward", port = announce_port),
|
||||
Self::task_upnp_port_forwarder(announce_port),
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use std::{
|
||||
collections::HashSet,
|
||||
io,
|
||||
net::{Ipv4Addr, SocketAddr},
|
||||
net::{IpAddr, SocketAddr},
|
||||
num::NonZeroU32,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
|
|
@ -133,6 +133,10 @@ struct Opts {
|
|||
#[arg(long = "disable-tcp-listen", env = "RQBIT_TCP_LISTEN_DISABLE")]
|
||||
disable_tcp_listen: bool,
|
||||
|
||||
// Disable connecting over TCP. Only uTP will be used (if enabled).
|
||||
#[arg(long = "disable-tcp-connect", env = "RQBIT_TCP_CONNECT_DISABLE")]
|
||||
disable_tcp_connect: bool,
|
||||
|
||||
// Enable to listen and connect over uTP
|
||||
#[arg(
|
||||
long = "experimental-enable-utp-listen",
|
||||
|
|
@ -148,15 +152,19 @@ struct Opts {
|
|||
)]
|
||||
listen_port: u16,
|
||||
|
||||
/// What's the IP to listen on. Default is to listen on all interfaces.
|
||||
#[arg(long = "listen-ip", default_value = "0.0.0.0", env = "RQBIT_LISTEN_IP")]
|
||||
listen_ip: IpAddr,
|
||||
|
||||
/// If set, will try to publish the chosen port through upnp on your router.
|
||||
/// If the listen-ip is localhost, this will not be used.
|
||||
#[arg(
|
||||
long = "disable-upnp-port-forward",
|
||||
env = "RQBIT_UPNP_PORT_FORWARD_DISABLE"
|
||||
)]
|
||||
disable_upnp_port_forward: bool,
|
||||
|
||||
/// If set, will run a UPNP Media server and stream all the torrents through it.
|
||||
/// Should be set to your hostname/IP as seen by your LAN neighbors.
|
||||
/// If set, will run a UPNP Media server on RQBIT_HTTP_API_LISTEN_ADDR.
|
||||
#[arg(long = "enable-upnp-server", env = "RQBIT_UPNP_SERVER_ENABLE")]
|
||||
enable_upnp_server: bool,
|
||||
|
||||
|
|
@ -490,7 +498,7 @@ async fn async_main(opts: Opts, cancel: CancellationToken) -> anyhow::Result<()>
|
|||
};
|
||||
let listen = listen_mode.map(|mode| ListenerOptions {
|
||||
mode,
|
||||
listen_addr: (Ipv4Addr::UNSPECIFIED, opts.listen_port).into(),
|
||||
listen_addr: (opts.listen_ip, opts.listen_port).into(),
|
||||
enable_upnp_port_forwarding: !opts.disable_upnp_port_forward,
|
||||
..Default::default()
|
||||
});
|
||||
|
|
@ -505,7 +513,7 @@ async fn async_main(opts: Opts, cancel: CancellationToken) -> anyhow::Result<()>
|
|||
listen,
|
||||
connect: Some(ConnectionOptions {
|
||||
proxy_url: opts.socks_url,
|
||||
enable_tcp: true,
|
||||
enable_tcp: !opts.disable_tcp_connect,
|
||||
peer_opts: Some(PeerConnectionOptions {
|
||||
connect_timeout: Some(opts.peer_connect_timeout),
|
||||
read_write_timeout: Some(opts.peer_read_write_timeout),
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
[package]
|
||||
name = "rqbit-desktop"
|
||||
edition = "2024"
|
||||
version = "8.1.0"
|
||||
description = "rqbit torrent client"
|
||||
authors = ["you"]
|
||||
authors = ["Igor Katson igor.katson@gmail.com"]
|
||||
license = ""
|
||||
repository = ""
|
||||
edition = "2024"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[build-dependencies]
|
||||
|
|
|
|||
|
|
@ -4,7 +4,10 @@ use std::{
|
|||
time::Duration,
|
||||
};
|
||||
|
||||
use librqbit::{dht::PersistentDht, limits::LimitsConfig, ListenerMode, ListenerOptions};
|
||||
use librqbit::{
|
||||
dht::PersistentDht, limits::LimitsConfig, ConnectionOptions, ListenerMode, ListenerOptions,
|
||||
PeerConnectionOptions,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::serde_as;
|
||||
|
||||
|
|
@ -26,39 +29,65 @@ impl Default for RqbitDesktopConfigDht {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde_as]
|
||||
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(default)]
|
||||
pub struct RqbitDesktopConfigListen {
|
||||
pub enable_tcp: bool,
|
||||
pub struct RqbitDesktopConfigConnections {
|
||||
pub enable_tcp_listen: bool,
|
||||
pub enable_tcp_outgoing: bool,
|
||||
pub enable_utp: bool,
|
||||
pub enable_upnp_port_forward: bool,
|
||||
pub port: u16,
|
||||
pub socks_proxy: String,
|
||||
pub listen_port: u16,
|
||||
|
||||
#[serde_as(as = "serde_with::DurationSeconds")]
|
||||
pub peer_connect_timeout: Duration,
|
||||
#[serde_as(as = "serde_with::DurationSeconds")]
|
||||
pub peer_read_write_timeout: Duration,
|
||||
}
|
||||
|
||||
impl RqbitDesktopConfigListen {
|
||||
pub fn as_listener_opts(&self) -> Option<ListenerOptions> {
|
||||
let mode = match (self.enable_tcp, self.enable_utp) {
|
||||
(true, true) => ListenerMode::TcpAndUtp,
|
||||
(true, false) => ListenerMode::TcpOnly,
|
||||
(false, true) => ListenerMode::UtpOnly,
|
||||
(false, false) => return None,
|
||||
impl RqbitDesktopConfigConnections {
|
||||
pub fn as_listener_and_connect_opts(&self) -> (Option<ListenerOptions>, ConnectionOptions) {
|
||||
let mode = match (self.enable_tcp_listen, self.enable_utp) {
|
||||
(true, true) => Some(ListenerMode::TcpAndUtp),
|
||||
(true, false) => Some(ListenerMode::TcpOnly),
|
||||
(false, true) => Some(ListenerMode::UtpOnly),
|
||||
(false, false) => None,
|
||||
};
|
||||
Some(ListenerOptions {
|
||||
let listener_opts = mode.map(|mode| ListenerOptions {
|
||||
mode,
|
||||
listen_addr: (Ipv4Addr::UNSPECIFIED, self.port).into(),
|
||||
listen_addr: (Ipv4Addr::UNSPECIFIED, self.listen_port).into(),
|
||||
enable_upnp_port_forwarding: self.enable_upnp_port_forward,
|
||||
..Default::default()
|
||||
})
|
||||
});
|
||||
let connect_opts = ConnectionOptions {
|
||||
proxy_url: if self.socks_proxy.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(self.socks_proxy.clone())
|
||||
},
|
||||
enable_tcp: self.enable_tcp_outgoing,
|
||||
peer_opts: Some(PeerConnectionOptions {
|
||||
connect_timeout: Some(self.peer_connect_timeout),
|
||||
read_write_timeout: Some(self.peer_read_write_timeout),
|
||||
..Default::default()
|
||||
}),
|
||||
};
|
||||
(listener_opts, connect_opts)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RqbitDesktopConfigListen {
|
||||
impl Default for RqbitDesktopConfigConnections {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enable_tcp: true,
|
||||
enable_tcp_listen: true,
|
||||
enable_tcp_outgoing: true,
|
||||
enable_utp: false,
|
||||
enable_upnp_port_forward: true,
|
||||
port: 4240,
|
||||
listen_port: 4240,
|
||||
socks_proxy: String::new(),
|
||||
peer_connect_timeout: Duration::from_secs(2),
|
||||
peer_read_write_timeout: Duration::from_secs(10),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -104,26 +133,6 @@ impl Default for RqbitDesktopConfigPersistence {
|
|||
}
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(default)]
|
||||
pub struct RqbitDesktopConfigPeerOpts {
|
||||
#[serde_as(as = "serde_with::DurationSeconds")]
|
||||
pub connect_timeout: Duration,
|
||||
|
||||
#[serde_as(as = "serde_with::DurationSeconds")]
|
||||
pub read_write_timeout: Duration,
|
||||
}
|
||||
|
||||
impl Default for RqbitDesktopConfigPeerOpts {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
connect_timeout: Duration::from_secs(2),
|
||||
read_write_timeout: Duration::from_secs(10),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(default)]
|
||||
|
|
@ -163,11 +172,9 @@ pub struct RqbitDesktopConfig {
|
|||
pub disable_upload: bool,
|
||||
|
||||
pub dht: RqbitDesktopConfigDht,
|
||||
#[serde(default)]
|
||||
pub listen: RqbitDesktopConfigListen,
|
||||
pub connections: RqbitDesktopConfigConnections,
|
||||
pub upnp: RqbitDesktopConfigUpnp,
|
||||
pub persistence: RqbitDesktopConfigPersistence,
|
||||
pub peer_opts: RqbitDesktopConfigPeerOpts,
|
||||
pub http_api: RqbitDesktopConfigHttpApi,
|
||||
|
||||
#[serde(default)]
|
||||
|
|
@ -185,10 +192,9 @@ impl Default for RqbitDesktopConfig {
|
|||
Self {
|
||||
default_download_location: download_folder,
|
||||
dht: Default::default(),
|
||||
listen: Default::default(),
|
||||
connections: Default::default(),
|
||||
upnp: Default::default(),
|
||||
persistence: Default::default(),
|
||||
peer_opts: Default::default(),
|
||||
http_api: Default::default(),
|
||||
ratelimits: Default::default(),
|
||||
#[cfg(feature = "disable-upload")]
|
||||
|
|
|
|||
|
|
@ -84,6 +84,8 @@ async fn api_from_config(
|
|||
})
|
||||
};
|
||||
|
||||
let (listen, connect) = config.connections.as_listener_and_connect_opts();
|
||||
|
||||
let session = Session::new_with_opts(
|
||||
config.default_download_location.clone(),
|
||||
SessionOptions {
|
||||
|
|
@ -94,16 +96,8 @@ async fn api_from_config(
|
|||
..Default::default()
|
||||
}),
|
||||
persistence,
|
||||
connect: Some(librqbit::ConnectionOptions {
|
||||
enable_tcp: true,
|
||||
peer_opts: Some(PeerConnectionOptions {
|
||||
connect_timeout: Some(config.peer_opts.connect_timeout),
|
||||
read_write_timeout: Some(config.peer_opts.read_write_timeout),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
}),
|
||||
listen: config.listen.as_listener_opts(),
|
||||
connect: Some(connect),
|
||||
listen,
|
||||
fastresume: config.persistence.fastresume,
|
||||
ratelimits: config.ratelimits,
|
||||
#[cfg(feature = "disable-upload")]
|
||||
|
|
|
|||
|
|
@ -8,10 +8,15 @@ interface RqbitDesktopConfigDht {
|
|||
persistence_filename: PathLike;
|
||||
}
|
||||
|
||||
interface RqbitDesktopConfigTcpListen {
|
||||
disable: boolean;
|
||||
min_port: number;
|
||||
max_port: number;
|
||||
interface RqbitDesktopConfigConnections {
|
||||
enable_tcp_listen: boolean;
|
||||
enable_tcp_outgoing: boolean;
|
||||
enable_utp: boolean;
|
||||
enable_upnp_port_forward: boolean;
|
||||
socks_proxy: string;
|
||||
listen_port: number;
|
||||
peer_connect_timeout: Duration;
|
||||
peer_read_write_timeout: Duration;
|
||||
}
|
||||
|
||||
interface RqbitDesktopConfigPersistence {
|
||||
|
|
@ -20,11 +25,6 @@ interface RqbitDesktopConfigPersistence {
|
|||
fastresume: boolean;
|
||||
}
|
||||
|
||||
interface RqbitDesktopConfigPeerOpts {
|
||||
connect_timeout: Duration;
|
||||
read_write_timeout: Duration;
|
||||
}
|
||||
|
||||
interface RqbitDesktopConfigHttpApi {
|
||||
disable: boolean;
|
||||
listen_addr: SocketAddr;
|
||||
|
|
@ -48,10 +48,9 @@ export interface RqbitDesktopConfig {
|
|||
default_download_location: PathLike;
|
||||
disable_upload?: boolean;
|
||||
dht: RqbitDesktopConfigDht;
|
||||
tcp_listen: RqbitDesktopConfigTcpListen;
|
||||
connections: RqbitDesktopConfigConnections;
|
||||
upnp: RqbitDesktopConfigUpnp;
|
||||
persistence: RqbitDesktopConfigPersistence;
|
||||
peer_opts: RqbitDesktopConfigPeerOpts;
|
||||
http_api: RqbitDesktopConfigHttpApi;
|
||||
ratelimits: LimitsConfig;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,17 +58,15 @@ type TAB =
|
|||
| "Home"
|
||||
| "DHT"
|
||||
| "Session"
|
||||
| "Peer options"
|
||||
| "HTTP API"
|
||||
| "TCP Listen"
|
||||
| "Connection"
|
||||
| "UPnP Server";
|
||||
|
||||
const TABS: readonly TAB[] = [
|
||||
"Home",
|
||||
"DHT",
|
||||
"Session",
|
||||
"TCP Listen",
|
||||
"Peer options",
|
||||
"Connection",
|
||||
"HTTP API",
|
||||
"UPnP Server",
|
||||
] as const;
|
||||
|
|
@ -133,7 +131,7 @@ export const ConfigModal: React.FC<{
|
|||
};
|
||||
|
||||
const handleToggleChange: React.ChangeEventHandler<HTMLInputElement> = (
|
||||
e
|
||||
e,
|
||||
) => {
|
||||
const name: string = e.target.name;
|
||||
const [mainField, subField] = name.split(".", 2);
|
||||
|
|
@ -169,7 +167,7 @@ export const ConfigModal: React.FC<{
|
|||
text: "Error saving configuration",
|
||||
details: e,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
|
|
@ -257,42 +255,78 @@ Might be useful e.g. if rqbit upload consumes all your upload bandwidth and inte
|
|||
</Fieldset>
|
||||
</Tab>
|
||||
|
||||
<Tab name="TCP Listen" currentTab={tab}>
|
||||
<Tab name="Connection" currentTab={tab}>
|
||||
<Fieldset>
|
||||
<FormCheck
|
||||
label="Listen on TCP"
|
||||
name="tcp_listen.disable"
|
||||
checked={!config.tcp_listen.disable}
|
||||
name="connections.enable_tcp_listen"
|
||||
checked={config.connections.enable_tcp_listen}
|
||||
onChange={handleToggleChange}
|
||||
help="Listen for torrent requests on TCP. Required for peers to be able to connect to you, mainly for uploading."
|
||||
/>
|
||||
|
||||
<FormCheck
|
||||
label="Advertise TCP port over UPnP"
|
||||
name="tcp_listen.disable"
|
||||
checked={!config.tcp_listen.disable}
|
||||
label="Listen on uTP (over UDP)"
|
||||
name="connections.enable_utp"
|
||||
checked={config.connections.enable_utp}
|
||||
onChange={handleToggleChange}
|
||||
help="Listen for torrent requests on uTP over UDP. Required for uTP support in general, both outgoing and incoming."
|
||||
/>
|
||||
|
||||
<FormCheck
|
||||
label="Advertise port over UPnP"
|
||||
name="connections.enable_upnp_port_forward"
|
||||
checked={config.connections.enable_upnp_port_forward}
|
||||
onChange={handleToggleChange}
|
||||
help="Advertise your port over UPnP to your router(s). This is required for peers to be able to connect to you from the internet. Will only work if your router has a static IP."
|
||||
/>
|
||||
|
||||
<FormCheck
|
||||
label="[ADVANCED] Disable outgoing connections over TCP"
|
||||
name="connections.enable_tcp_outgoing"
|
||||
checked={!config.connections.enable_tcp_outgoing}
|
||||
onChange={handleToggleChange}
|
||||
help="WARNING: leave this unchecked unless you know what you are doing."
|
||||
/>
|
||||
|
||||
<FormInput
|
||||
inputType="number"
|
||||
label="Min port"
|
||||
name="tcp_listen.min_port"
|
||||
value={config.tcp_listen.min_port}
|
||||
disabled={config.tcp_listen.disable}
|
||||
inputType="text"
|
||||
label="Socks proxy"
|
||||
name="connections.socks_proxy"
|
||||
value={config.connections.socks_proxy}
|
||||
onChange={handleInputChange}
|
||||
help="The min port to try to listen on. First successful is taken."
|
||||
help="Socks5 proxy for outgoing connections. Format: socks5://[username:password@]host:port"
|
||||
/>
|
||||
|
||||
<FormInput
|
||||
inputType="number"
|
||||
label="Max port"
|
||||
name="tcp_listen.max_port"
|
||||
value={config.tcp_listen.max_port}
|
||||
disabled={config.tcp_listen.disable}
|
||||
label="Port"
|
||||
name="connections.listen_port"
|
||||
value={config.connections.listen_port}
|
||||
disabled={
|
||||
!config.connections.enable_tcp_listen &&
|
||||
!config.connections.enable_utp
|
||||
}
|
||||
onChange={handleInputChange}
|
||||
help="The max port to try to listen on."
|
||||
help="The port to listen on for both TCP and UDP (if enabled)."
|
||||
/>
|
||||
|
||||
<FormInput
|
||||
label="Peer connect timeout (seconds)"
|
||||
inputType="number"
|
||||
name="connections.peer_connect_timeout"
|
||||
value={config.connections.peer_connect_timeout}
|
||||
onChange={handleInputChange}
|
||||
help="How much to wait for outgoing connections to connect. Default is low to prefer faster peers."
|
||||
/>
|
||||
|
||||
<FormInput
|
||||
label="Peer read/write timeout (seconds)"
|
||||
inputType="number"
|
||||
name="connections.peer_read_write_timeout"
|
||||
value={config.connections.peer_read_write_timeout}
|
||||
onChange={handleInputChange}
|
||||
help="Peer socket read/write timeout."
|
||||
/>
|
||||
</Fieldset>
|
||||
</Tab>
|
||||
|
|
@ -378,28 +412,6 @@ Might be useful e.g. if rqbit upload consumes all your upload bandwidth and inte
|
|||
</Fieldset>
|
||||
</Tab>
|
||||
|
||||
<Tab name="Peer options" currentTab={tab}>
|
||||
<Fieldset>
|
||||
<FormInput
|
||||
label="Connect timeout (seconds)"
|
||||
inputType="number"
|
||||
name="peer_opts.connect_timeout"
|
||||
value={config.peer_opts.connect_timeout}
|
||||
onChange={handleInputChange}
|
||||
help="How much to wait for outgoing connections to connect. Default is low to prefer faster peers."
|
||||
/>
|
||||
|
||||
<FormInput
|
||||
label="Read/write timeout (seconds)"
|
||||
inputType="number"
|
||||
name="peer_opts.read_write_timeout"
|
||||
value={config.peer_opts.read_write_timeout}
|
||||
onChange={handleInputChange}
|
||||
help="Peer socket read/write timeout."
|
||||
/>
|
||||
</Fieldset>
|
||||
</Tab>
|
||||
|
||||
<Tab name="HTTP API" currentTab={tab}>
|
||||
<Fieldset>
|
||||
<FormCheck
|
||||
|
|
|
|||
Loading…
Reference in New Issue