UPNP: send notifies on all interfaces, no need to specify hostname anymore

This commit is contained in:
Igor Katson 2024-08-28 13:38:11 +01:00
parent 0214817122
commit b174afaa12
No known key found for this signature in database
GPG key ID: B4EC22B66D61A3F5
12 changed files with 101 additions and 65 deletions

View file

@ -52,6 +52,15 @@ async fn generate_content_directory_control_response(
};
match action.as_ref() {
SOAP_ACTION_CONTENT_DIRECTORY_BROWSE => {
let http_hostname = headers
.get("host")
.and_then(|h| std::str::from_utf8(h.as_bytes()).ok())
.and_then(|h| h.split(':').next());
let http_hostname = match http_hostname {
Some(h) => h,
None => return StatusCode::BAD_REQUEST.into_response(),
};
let body = match std::str::from_utf8(body) {
Ok(body) => body,
Err(_) => return (StatusCode::BAD_REQUEST, "cannot parse request").into_response(),
@ -71,7 +80,9 @@ async fn generate_content_directory_control_response(
BrowseFlag::BrowseDirectChildren => (
[(CONTENT_TYPE, CONTENT_TYPE_XML_UTF8)],
render_content_directory_browse(
state.provider.browse_direct_children(request.object_id),
state
.provider
.browse_direct_children(request.object_id, http_hostname),
),
)
.into_response(),

View file

@ -20,7 +20,6 @@ pub mod upnp_types;
pub struct UpnpServerOptions {
pub friendly_name: String,
pub http_hostname: String,
pub http_listen_port: u16,
pub http_prefix: String,
pub browse_provider: Box<dyn ContentDirectoryBrowseProvider>,
@ -57,14 +56,16 @@ impl UpnpServer {
let usn = create_usn(&opts).context("error generating USN")?;
let description_http_location = {
let hostname = &opts.http_hostname;
let port = opts.http_listen_port;
let http_prefix = &opts.http_prefix;
format!("http://{hostname}:{port}{http_prefix}/description.xml")
let surl = format!("http://0.0.0.0:{port}{http_prefix}/description.xml");
url::Url::parse(&surl)
.context(surl)
.context("error parsing url")?
};
info!(
location = description_http_location,
location = %description_http_location,
"starting UPnP/SSDP announcer for MediaServer"
);
let ssdp_runner = crate::ssdp::SsdpRunner::new(ssdp::SsdpRunnerOptions {

View file

@ -94,7 +94,7 @@ pub fn try_parse_ssdp<'a, 'h>(
pub struct SsdpRunnerOptions {
pub usn: String,
pub description_http_location: String,
pub description_http_location: url::Url,
pub server_string: String,
pub notify_interval: Duration,
pub shutdown: CancellationToken,
@ -151,11 +151,20 @@ USN: {usn}::{kind}\r
)
}
fn generate_ssdp_discover_response(&self, st: &str) -> String {
let location = &self.opts.description_http_location;
fn generate_ssdp_discover_response(
&self,
st: &str,
addr: SocketAddr,
) -> anyhow::Result<String> {
let local_ip = ::librqbit_upnp::get_local_ip_relative_to(addr.ip())?;
let location = {
let mut loc = self.opts.description_http_location.clone();
loc.set_host(Some(&format!("{local_ip}")))?;
loc
};
let usn = &self.opts.usn;
let server = &self.opts.server_string;
format!(
Ok(format!(
"HTTP/1.1 200 OK\r
Cache-Control: max-age=75\r
Ext: \r
@ -164,7 +173,7 @@ Server: {server}\r
St: {st}\r
Usn: {usn}::{st}\r
Content-Length: 0\r\n\r\n"
)
))
}
async fn try_send_notifies(&self, nts: &str) {
@ -178,18 +187,12 @@ Content-Length: 0\r\n\r\n"
}
};
let location = match url::Url::parse(&self.opts.description_http_location) {
Ok(u) => u,
// TODO: rewrite this
Err(e) => {
warn!(error=?e, "error parsing description_http_location");
return;
}
};
for ni in interfaces {
for niaddr in ni.addr {
let ip = niaddr.ip();
if ip.is_ipv6() || ip.is_loopback() {
continue;
}
let addr = SocketAddr::new(ip, 0);
let sock = match tokio::net::UdpSocket::bind(addr).await {
Ok(sock) => sock,
@ -199,16 +202,16 @@ Content-Length: 0\r\n\r\n"
}
};
let mut location = location.clone();
let mut location = self.opts.description_http_location.clone();
location.set_host(Some(&format!("{ip}"))).unwrap();
for kind in [UPNP_KIND_ROOT_DEVICE, UPNP_KIND_MEDIASERVER] {
let msg = self.generate_notify_message(kind, nts, &format!("{location}"));
trace!(content=?msg, addr=?UPNP_BROADCAST_ADDR, "sending SSDP NOTIFY");
if let Err(e) = sock.send_to(msg.as_bytes(), UPNP_BROADCAST_ADDR).await {
warn!(error=?e, "error sending SSDP NOTIFY")
warn!(sock_addr=%addr, error=%e, "error sending SSDP NOTIFY")
} else {
debug!(kind, nts, "sent SSDP NOTIFY")
debug!(kind, nts, %location, "sent SSDP NOTIFY")
}
}
}
@ -244,7 +247,7 @@ Content-Length: 0\r\n\r\n"
}
if let Ok(st) = std::str::from_utf8(msg.st) {
let response = self.generate_ssdp_discover_response(st);
let response = self.generate_ssdp_discover_response(st, addr)?;
trace!(content = response, ?addr, "sending SSDP discover response");
self.socket
.send_to(response.as_bytes(), addr)

View file

@ -70,11 +70,19 @@ pub mod content_directory {
}
pub trait ContentDirectoryBrowseProvider: Send + Sync {
fn browse_direct_children(&self, parent_id: usize) -> Vec<ItemOrContainer>;
fn browse_direct_children(
&self,
parent_id: usize,
http_hostname: &str,
) -> Vec<ItemOrContainer>;
}
impl ContentDirectoryBrowseProvider for Vec<ItemOrContainer> {
fn browse_direct_children(&self, _parent_id: usize) -> Vec<ItemOrContainer> {
fn browse_direct_children(
&self,
_parent_id: usize,
_http_host: &str,
) -> Vec<ItemOrContainer> {
self.clone()
}
}