UPnP server configurable from UI
This commit is contained in:
parent
3110f68f36
commit
9f340d92e5
6 changed files with 166 additions and 8 deletions
58
desktop/src-tauri/Cargo.lock
generated
58
desktop/src-tauri/Cargo.lock
generated
|
|
@ -317,6 +317,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"regex-automata 0.4.7",
|
||||
"serde",
|
||||
]
|
||||
|
||||
|
|
@ -1182,6 +1183,16 @@ dependencies = [
|
|||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gethostname"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc3655aa6818d65bc620d6911f05aa7b6aeb596291e1e9f79e52df85583d1e30"
|
||||
dependencies = [
|
||||
"rustix",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.1.16"
|
||||
|
|
@ -1855,6 +1866,7 @@ dependencies = [
|
|||
"tower-http",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"upnp-serve",
|
||||
"url",
|
||||
"urlencoding",
|
||||
"uuid",
|
||||
|
|
@ -2670,7 +2682,7 @@ checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016"
|
|||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"indexmap 2.4.0",
|
||||
"quick-xml",
|
||||
"quick-xml 0.32.0",
|
||||
"serde",
|
||||
"time",
|
||||
]
|
||||
|
|
@ -2767,6 +2779,15 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96a05e2e8efddfa51a84ca47cec303fac86c8541b686d37cac5efc0e094417bc"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.36"
|
||||
|
|
@ -3315,6 +3336,15 @@ version = "1.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.7"
|
||||
|
|
@ -3886,7 +3916,9 @@ dependencies = [
|
|||
"bytes",
|
||||
"libc",
|
||||
"mio",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.52.0",
|
||||
|
|
@ -4186,6 +4218,30 @@ version = "1.11.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
|
||||
|
||||
[[package]]
|
||||
name = "upnp-serve"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
"bstr",
|
||||
"gethostname",
|
||||
"http 1.1.0",
|
||||
"httparse",
|
||||
"librqbit-core",
|
||||
"librqbit-sha1-wrapper",
|
||||
"librqbit-upnp",
|
||||
"mime_guess",
|
||||
"parking_lot",
|
||||
"quick-xml 0.36.1",
|
||||
"reqwest",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.2"
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ librqbit = { path = "../../crates/librqbit", features = [
|
|||
"tracing-subscriber-utils",
|
||||
"http-api",
|
||||
"webui",
|
||||
"upnp-serve-adapter",
|
||||
] }
|
||||
tokio = { version = "1.34.0", features = ["rt-multi-thread"] }
|
||||
anyhow = "1.0.75"
|
||||
|
|
|
|||
|
|
@ -125,10 +125,21 @@ impl Default for RqbitDesktopConfigHttpApi {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq, Debug)]
|
||||
#[serde(default)]
|
||||
pub struct RqbitDesktopConfigUpnp {
|
||||
pub disable: bool,
|
||||
// rename for backwards compat
|
||||
#[serde(rename = "disable")]
|
||||
pub disable_tcp_port_forward: bool,
|
||||
|
||||
#[serde(default)]
|
||||
pub enable_server: bool,
|
||||
|
||||
#[serde(default)]
|
||||
pub server_hostname: Option<String>,
|
||||
|
||||
#[serde(default)]
|
||||
pub server_friendly_name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
|
|
@ -162,3 +173,23 @@ impl Default for RqbitDesktopConfig {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RqbitDesktopConfig {
|
||||
pub fn validate(&self) -> anyhow::Result<()> {
|
||||
if self.upnp.enable_server {
|
||||
if self.http_api.disable {
|
||||
anyhow::bail!("if UPnP server is enabled, you need to enable the HTTP API also.")
|
||||
}
|
||||
if self.http_api.listen_addr.ip().is_loopback() {
|
||||
anyhow::bail!("if UPnP server is enabled, you need to set HTTP API IP to 0.0.0.0 or at least non-localhost address.")
|
||||
}
|
||||
match self.upnp.server_hostname.as_ref().map(|s| s.trim()) {
|
||||
Some("") | None => {
|
||||
anyhow::bail!("UPnP hostname must be set to non-empty string")
|
||||
}
|
||||
Some(_) => {}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,6 +69,9 @@ async fn api_from_config(
|
|||
init_logging: &InitLoggingResult,
|
||||
config: &RqbitDesktopConfig,
|
||||
) -> anyhow::Result<Api> {
|
||||
config
|
||||
.validate()
|
||||
.context("error validating configuration")?;
|
||||
let persistence = if config.persistence.disable {
|
||||
None
|
||||
} else {
|
||||
|
|
@ -100,7 +103,7 @@ async fn api_from_config(
|
|||
} else {
|
||||
None
|
||||
},
|
||||
enable_upnp_port_forwarding: !config.upnp.disable,
|
||||
enable_upnp_port_forwarding: !config.upnp.disable_tcp_port_forward,
|
||||
fastresume: config.persistence.fastresume,
|
||||
..Default::default()
|
||||
},
|
||||
|
|
@ -118,6 +121,35 @@ async fn api_from_config(
|
|||
let listen_addr = config.http_api.listen_addr;
|
||||
let api = api.clone();
|
||||
let read_only = config.http_api.read_only;
|
||||
let upnp_router = if config.upnp.enable_server {
|
||||
let hostname = config
|
||||
.upnp
|
||||
.server_hostname
|
||||
.as_ref()
|
||||
.map(|h| h.trim())
|
||||
.context("empty UPNP hostname")?
|
||||
.to_owned();
|
||||
let friendly_name = config
|
||||
.upnp
|
||||
.server_friendly_name
|
||||
.as_ref()
|
||||
.map(|f| f.trim())
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|s| s.to_owned())
|
||||
.unwrap_or_else(|| format!("rqbit@{hostname}"));
|
||||
|
||||
let mut upnp_adapter = session
|
||||
.make_upnp_adapter(friendly_name, hostname, config.http_api.listen_addr.port())
|
||||
.await
|
||||
.context("error starting UPnP server")?;
|
||||
let router = upnp_adapter.take_router()?;
|
||||
session.spawn(error_span!("ssdp"), async move {
|
||||
upnp_adapter.run_ssdp_forever().await
|
||||
});
|
||||
Some(router)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let http_api_task = async move {
|
||||
let listener = tokio::net::TcpListener::bind(listen_addr)
|
||||
.await
|
||||
|
|
@ -126,7 +158,7 @@ async fn api_from_config(
|
|||
api.clone(),
|
||||
Some(librqbit::http_api::HttpApiOptions { read_only }),
|
||||
)
|
||||
.make_http_api_and_run(listener, None)
|
||||
.make_http_api_and_run(listener, upnp_router)
|
||||
.await
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,10 @@ interface RqbitDesktopConfigHttpApi {
|
|||
|
||||
interface RqbitDesktopConfigUpnp {
|
||||
disable: boolean;
|
||||
|
||||
enable_server: boolean;
|
||||
server_hostname: string;
|
||||
server_friendly_name: string;
|
||||
}
|
||||
|
||||
export interface RqbitDesktopConfig {
|
||||
|
|
|
|||
|
|
@ -59,7 +59,8 @@ type TAB =
|
|||
| "Session"
|
||||
| "Peer options"
|
||||
| "HTTP API"
|
||||
| "TCP Listen";
|
||||
| "TCP Listen"
|
||||
| "UPnP Server";
|
||||
|
||||
const TABS: readonly TAB[] = [
|
||||
"Home",
|
||||
|
|
@ -68,6 +69,7 @@ const TABS: readonly TAB[] = [
|
|||
"TCP Listen",
|
||||
"Peer options",
|
||||
"HTTP API",
|
||||
"UPnP Server",
|
||||
] as const;
|
||||
|
||||
const Tab: React.FC<{
|
||||
|
|
@ -252,11 +254,11 @@ export const ConfigModal: React.FC<{
|
|||
/>
|
||||
|
||||
<FormCheck
|
||||
label="Advertise over UPnP"
|
||||
label="Advertise TCP port over UPnP"
|
||||
name="tcp_listen.disable"
|
||||
checked={!config.tcp_listen.disable}
|
||||
onChange={handleToggleChange}
|
||||
help="Advertise your port over UPnP. 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."
|
||||
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."
|
||||
/>
|
||||
|
||||
<FormInput
|
||||
|
|
@ -281,6 +283,38 @@ export const ConfigModal: React.FC<{
|
|||
</Fieldset>
|
||||
</Tab>
|
||||
|
||||
<Tab name="UPnP Server" currentTab={tab}>
|
||||
<Fieldset>
|
||||
<FormCheck
|
||||
label="Enable UPnP media server"
|
||||
name="upnp.enable_server"
|
||||
checked={config.upnp.enable_server}
|
||||
onChange={handleToggleChange}
|
||||
help="If enabled, rqbit will advertise the media to supported LAN devices, e.g. TVs."
|
||||
/>
|
||||
|
||||
<FormInput
|
||||
inputType="text"
|
||||
label="[Required] Hostname"
|
||||
name="upnp.server_hostname"
|
||||
value={config.upnp.server_hostname}
|
||||
disabled={!config.upnp.enable_server}
|
||||
onChange={handleInputChange}
|
||||
help="Set this to your LAN IP or hostname resolvable from LAN."
|
||||
/>
|
||||
|
||||
<FormInput
|
||||
inputType="text"
|
||||
label="Friendly name"
|
||||
name="upnp.server_friendly_name"
|
||||
value={config.upnp.server_friendly_name}
|
||||
disabled={!config.upnp.enable_server}
|
||||
onChange={handleInputChange}
|
||||
help="The name displayed on supported devices. If not set will be generated, will look smth like <rqbit at HOSTNAME>."
|
||||
/>
|
||||
</Fieldset>
|
||||
</Tab>
|
||||
|
||||
<Tab name="Session" currentTab={tab}>
|
||||
<Fieldset>
|
||||
<FormCheck
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue