UPnP server configurable from UI

This commit is contained in:
Igor Katson 2024-08-24 00:34:57 +01:00
parent 3110f68f36
commit 9f340d92e5
No known key found for this signature in database
GPG key ID: B4EC22B66D61A3F5
6 changed files with 166 additions and 8 deletions

View file

@ -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(())
}
}

View file

@ -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
};