forked from sbstp/rust-igd
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Test both sync and async gateway search
Gateway searching tests extracted out of async only code into a common test module.
- Loading branch information
1 parent
53280c3
commit 8fb446f
Showing
4 changed files
with
272 additions
and
224 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -100,6 +100,8 @@ fn handle_broadcast_resp(from: &SocketAddr, data: &[u8]) -> Result<(SocketAddr, | |
async fn get_control_urls(addr: &SocketAddr, path: &str) -> Result<(String, String), SearchError> { | ||
let url: reqwest::Url = format!("http://{}{}", addr, path).parse()?; | ||
|
||
validate_url(addr.ip(), &url)?; | ||
|
||
debug!("requesting control url from: {:?}", url); | ||
let client = reqwest::Client::new(); | ||
let resp = client.get(url).send().await?; | ||
|
@@ -126,186 +128,3 @@ async fn get_control_schemas( | |
let body = resp.bytes().await?; | ||
parsing::parse_schemas(body.as_ref()) | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
use http_body_util::Full; | ||
use hyper::{body::Bytes, service::service_fn, Request, Response}; | ||
use hyper_util::rt::TokioIo; | ||
use std::convert::Infallible; | ||
use std::{ | ||
net::{Ipv4Addr, SocketAddrV4}, | ||
time::Duration, | ||
}; | ||
use test_log::test; | ||
use tokio::net::TcpListener; | ||
|
||
async fn start_broadcast_reply_sender(location: String) -> u16 { | ||
let port = { | ||
// Not 100% reliable way to find a free port number, but should be good enough | ||
let sock = UdpSocket::bind((Ipv4Addr::UNSPECIFIED, 0)).await.unwrap(); | ||
sock.local_addr().unwrap().port() | ||
}; | ||
|
||
tokio::spawn(async move { | ||
tokio::time::sleep(Duration::from_secs(1)).await; | ||
|
||
let sock = UdpSocket::bind((Ipv4Addr::LOCALHOST, 0)).await.unwrap(); | ||
|
||
sock.send_to(format!("location: {location}").as_bytes(), (Ipv4Addr::LOCALHOST, port)) | ||
.await | ||
.unwrap(); | ||
}); | ||
port | ||
} | ||
|
||
fn default_options_with_using_free_port(port: u16) -> SearchOptions { | ||
SearchOptions { | ||
bind_addr: SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, port)), | ||
timeout: Some(Duration::from_secs(5)), | ||
http_timeout: Some(Duration::from_secs(1)), | ||
..Default::default() | ||
} | ||
} | ||
|
||
#[test(tokio::test)] | ||
async fn ip_spoofing_in_broadcast_response() { | ||
let port = start_broadcast_reply_sender("http://1.2.3.4:5".to_owned()).await; | ||
|
||
let options = default_options_with_using_free_port(port); | ||
|
||
let result = search_gateway(options).await; | ||
if let Err(SearchError::SpoofedIp { src_ip, url_ip }) = result { | ||
assert_eq!(src_ip, Ipv4Addr::LOCALHOST); | ||
assert_eq!(url_ip, Ipv4Addr::new(1, 2, 3, 4)); | ||
} else { | ||
panic!("Unexpected result: {result:?}"); | ||
} | ||
} | ||
|
||
const RESP: &'static str = r#"<?xml version="1.0" ?> | ||
<root xmlns="urn:schemas-upnp-org:device-1-0"> | ||
<device> | ||
<deviceList> | ||
<device> | ||
<deviceList> | ||
<device> | ||
<serviceList> | ||
<service> | ||
<serviceType>urn:schemas-upnp-org:service:WANIPConnection:1</serviceType> | ||
<controlURL>/igdupnp/control/WANIPConn1</controlURL> | ||
<SCPDURL>:[email protected]/exec_cmd?cmd=touch%20%2ftmp%2frce</SCPDURL> | ||
</service> | ||
</serviceList> | ||
</device> | ||
</deviceList> | ||
</device> | ||
</deviceList> | ||
</device> | ||
</root> | ||
"#; | ||
const RESP2: &'static str = r#"<?xml version="1.0" ?> | ||
<root xmlns="urn:schemas-upnp-org:device-1-0"> | ||
<device> | ||
<deviceList> | ||
<device> | ||
<deviceList> | ||
<device> | ||
<serviceList> | ||
<service> | ||
<serviceType>urn:schemas-upnp-org:service:WANIPConnection:1</serviceType> | ||
<controlURL>:[email protected]/exec_cmd?cmd=touch%20%2ftmp%2frce</controlURL> | ||
<SCPDURL>/igdupnp/control/WANIPConn1</SCPDURL> | ||
</service> | ||
</serviceList> | ||
</device> | ||
</deviceList> | ||
</device> | ||
</deviceList> | ||
</device> | ||
</root> | ||
"#; | ||
const CONTROL_SCHEMA: &'static str = r#"<?xml version="1.0" ?> | ||
<root xmlns="urn:schemas-upnp-org:device-1-0"> | ||
<actionList> | ||
<action> | ||
</action> | ||
</actionList> | ||
</root> | ||
"#; | ||
|
||
async fn start_http_server(responses: Vec<String>) -> u16 { | ||
let addr = SocketAddr::from(([0, 0, 0, 0], 0)); | ||
|
||
// We create a TcpListener and bind it to 127.0.0.1:3000 | ||
let listener = TcpListener::bind(addr).await.unwrap(); | ||
|
||
let ret = listener.local_addr().unwrap().port(); | ||
|
||
tokio::task::spawn(async move { | ||
for resp in responses { | ||
let (stream, _) = listener.accept().await.unwrap(); | ||
|
||
// Use an adapter to access something implementing `tokio::io` traits as if they implement | ||
// `hyper::rt` IO traits. | ||
let io = TokioIo::new(stream); | ||
|
||
let hello_fn = move |r: Request<hyper::body::Incoming>| -> Result<Response<Full<Bytes>>, Infallible> { | ||
println!("Request: {r:?}"); | ||
Ok(Response::new(Full::new(Bytes::from(resp.clone())))) | ||
}; | ||
|
||
// Finally, we bind the incoming connection to our `hello` service | ||
if let Err(err) = hyper::server::conn::http1::Builder::new() | ||
// `service_fn` converts our function in a `Service` | ||
.serve_connection(io, service_fn(|r| async { hello_fn(r) })) | ||
.await | ||
{ | ||
eprintln!("Error serving connection: {:?}", err); | ||
} | ||
} | ||
}); | ||
|
||
ret | ||
} | ||
|
||
#[test(tokio::test)] | ||
async fn ip_spoofing_in_getxml_body() { | ||
let http_port = start_http_server(vec![RESP.to_owned()]).await; | ||
|
||
let port = start_broadcast_reply_sender(format!("http://127.0.0.1:{http_port}")).await; | ||
|
||
println!("http server port: {http_port}, udp port: {port}"); | ||
|
||
let options = default_options_with_using_free_port(port); | ||
|
||
let result = search_gateway(options).await; | ||
if let Err(SearchError::SpoofedUrl { src_ip, url_host }) = result { | ||
assert_eq!(src_ip, Ipv4Addr::LOCALHOST); | ||
assert_eq!(url_host, "example.com"); | ||
} else { | ||
panic!("Unexpected result: {result:?}"); | ||
} | ||
} | ||
|
||
#[test(tokio::test)] | ||
async fn ip_spoofing_in_getxml_body_control_url() { | ||
let http_port = start_http_server(vec![RESP2.to_owned(), CONTROL_SCHEMA.to_owned()]).await; | ||
|
||
let port = start_broadcast_reply_sender(format!("http://127.0.0.1:{http_port}")).await; | ||
|
||
println!("http server port: {http_port}, udp port: {port}"); | ||
|
||
let options = default_options_with_using_free_port(port); | ||
|
||
let result = search_gateway(options).await; | ||
|
||
if let Err(SearchError::SpoofedUrl { src_ip, url_host }) = result { | ||
assert_eq!(src_ip, Ipv4Addr::LOCALHOST); | ||
assert_eq!(url_host, "example.com"); | ||
} else { | ||
panic!("Unexpected result: {result:?}"); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.