Reuse some libraries that we already use in upnp crate

This commit is contained in:
Igor Katson 2024-08-25 14:59:39 +01:00
parent 291b440f81
commit 01499231ac
No known key found for this signature in database
GPG key ID: B4EC22B66D61A3F5
3 changed files with 29 additions and 17 deletions

2
Cargo.lock generated
View file

@ -1695,7 +1695,9 @@ version = "0.1.1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-recursion", "async-recursion",
"bstr",
"futures", "futures",
"httparse",
"network-interface", "network-interface",
"reqwest", "reqwest",
"serde", "serde",

View file

@ -22,6 +22,8 @@ futures = "0.3"
url = "2" url = "2"
async-recursion = "1" async-recursion = "1"
network-interface = { git = 'https://github.com/ikatson/network-interface', branch = "compile-on-freebsd" } network-interface = { git = 'https://github.com/ikatson/network-interface', branch = "compile-on-freebsd" }
httparse = "1.9.4"
bstr = "1.10.0"
[dev-dependencies] [dev-dependencies]
tokio = { version = "1", features = ["macros", "rt-multi-thread"] } tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

View file

@ -1,11 +1,12 @@
use anyhow::{bail, Context}; use anyhow::{bail, Context};
use bstr::BStr;
use futures::{stream::FuturesUnordered, StreamExt, TryFutureExt}; use futures::{stream::FuturesUnordered, StreamExt, TryFutureExt};
use network_interface::NetworkInterfaceConfig; use network_interface::NetworkInterfaceConfig;
use reqwest::Client; use reqwest::Client;
use serde::Deserialize; use serde::Deserialize;
use serde_xml_rs::from_str; use serde_xml_rs::from_str;
use std::{ use std::{
collections::{HashMap, HashSet}, collections::HashSet,
net::{Ipv4Addr, SocketAddr, SocketAddrV4}, net::{Ipv4Addr, SocketAddr, SocketAddrV4},
time::Duration, time::Duration,
}; };
@ -20,7 +21,7 @@ const SSDP_SEARCH_REQUEST: &str = "M-SEARCH * HTTP/1.1\r\n\
Host: 239.255.255.250:1900\r\n\ Host: 239.255.255.250:1900\r\n\
Man: \"ssdp:discover\"\r\n\ Man: \"ssdp:discover\"\r\n\
MX: 3\r\n\ MX: 3\r\n\
ST: upnp:rootdevice\r\n\ ST: urn:schemas-upnp-org:service:WANIPConnection:1\r\n\
\r\n"; \r\n";
fn get_local_ip_relative_to(local_dest: Ipv4Addr) -> anyhow::Result<Ipv4Addr> { fn get_local_ip_relative_to(local_dest: Ipv4Addr) -> anyhow::Result<Ipv4Addr> {
@ -265,16 +266,30 @@ async fn discover_services(location: Url) -> anyhow::Result<RootDesc> {
} }
fn parse_upnp_discover_response( fn parse_upnp_discover_response(
response: &str, buf: &[u8],
received_from: SocketAddr, received_from: SocketAddr,
) -> anyhow::Result<UpnpDiscoverResponse> { ) -> anyhow::Result<UpnpDiscoverResponse> {
let mut headers = HashMap::new(); let mut headers = [httparse::EMPTY_HEADER; 16];
for line in response.lines() { let mut resp = httparse::Response::new(&mut headers);
if let Some((key, value)) = line.split_once(": ") { resp.parse(buf).context("error parsing response")?;
headers.insert(key.to_lowercase(), value.trim_end().to_string());
trace!(?resp, "parsed SSDP response");
match resp.code {
Some(200) => {}
other => anyhow::bail!("bad response code {other:?}, expected 200"),
}
let mut location = None;
for header in resp.headers {
match header.name {
"location" | "LOCATION" | "Location" => {
location = Some(
std::str::from_utf8(header.value).context("bad utf-8 in location header")?,
)
}
_ => continue,
} }
} }
let location = headers.get("location").context("missing location header")?; let location = location.context("missing location header")?;
let location = let location =
Url::parse(location).with_context(|| format!("failed parsing location {location}"))?; Url::parse(location).with_context(|| format!("failed parsing location {location}"))?;
Ok(UpnpDiscoverResponse { Ok(UpnpDiscoverResponse {
@ -352,20 +367,13 @@ impl UpnpPortForwarder {
timed_out = true; timed_out = true;
} }
Ok((len, addr)) = socket.recv_from(&mut buffer), if !timed_out => { Ok((len, addr)) = socket.recv_from(&mut buffer), if !timed_out => {
let response = match std::str::from_utf8(&buffer[..len]) { let response = &buffer[..len];
Ok(response) => response,
Err(_) => {
warn!(%addr, "received invalid utf-8");
continue;
},
};
trace!(%addr, response, "response");
match parse_upnp_discover_response(response, addr) { match parse_upnp_discover_response(response, addr) {
Ok(r) => { Ok(r) => {
tx.send(r)?; tx.send(r)?;
discovered += 1; discovered += 1;
}, },
Err(e) => warn!("failed to parse response: {e:#}"), Err(e) => warn!(error=?e, response=?BStr::new(response), "failed to parse SSDP response"),
}; };
}, },
} }