UPNP: send notifies on all interfaces, no need to specify hostname anymore
This commit is contained in:
parent
0214817122
commit
b174afaa12
12 changed files with 101 additions and 65 deletions
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue