().ok())
+ .ok_or_else(make_err("Field NewLeaseDuration is invalid".into()))?;
+ Ok(PortMappingEntry {
+ remote_host,
+ external_port,
+ protocol,
+ internal_port,
+ internal_client,
+ enabled,
+ port_mapping_description,
+ lease_duration,
+ })
+}
+
+#[test]
+fn test_parse_search_result_case_insensitivity() {
+ assert!(parse_search_result("location:http://0.0.0.0:0/control_url").is_ok());
+ assert!(parse_search_result("LOCATION:http://0.0.0.0:0/control_url").is_ok());
+}
+
+#[test]
+fn test_parse_search_result_ok() {
+ let result = parse_search_result("location:http://0.0.0.0:0/control_url").unwrap();
+ assert_eq!(
+ result.0.ip(),
+ IpAddr::V4(std::net::Ipv4Addr::new(0, 0, 0, 0))
+ );
+ assert_eq!(result.0.port(), 0);
+ assert_eq!(&result.1[..], "/control_url");
+}
+
+#[test]
+fn test_parse_search_result_fail() {
+ assert!(parse_search_result("content-type:http://0.0.0.0:0/control_url").is_err());
+}
+
+#[test]
+fn test_parse_device1() {
+ let text = r#"
+
+
+ 1
+ 0
+
+
+ urn:schemas-upnp-org:device:InternetGatewayDevice:1
+
+
+
+
+
+ 1
+ 00000000
+
+
+
+ urn:schemas-upnp-org:service:Layer3Forwarding:1
+ urn:upnp-org:serviceId:Layer3Forwarding1
+ /ctl/L3F
+ /evt/L3F
+ /L3F.xml
+
+
+
+
+ urn:schemas-upnp-org:device:WANDevice:1
+ WANDevice
+ MiniUPnP
+ http://miniupnp.free.fr/
+ WAN Device
+ WAN Device
+ 20180615
+ http://miniupnp.free.fr/
+ 00000000
+ uuid:804e2e56-7bfe-4733-bae0-04bf6d569692
+ MINIUPNPD
+
+
+ urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1
+ urn:upnp-org:serviceId:WANCommonIFC1
+ /ctl/CmnIfCfg
+ /evt/CmnIfCfg
+ /WANCfg.xml
+
+
+
+
+ urn:schemas-upnp-org:device:WANConnectionDevice:1
+ WANConnectionDevice
+ MiniUPnP
+ http://miniupnp.free.fr/
+ MiniUPnP daemon
+ MiniUPnPd
+ 20180615
+ http://miniupnp.free.fr/
+ 00000000
+ uuid:804e2e56-7bfe-4733-bae0-04bf6d569692
+ MINIUPNPD
+
+
+ urn:schemas-upnp-org:service:WANIPConnection:1
+ urn:upnp-org:serviceId:WANIPConn1
+ /ctl/IPConn
+ /evt/IPConn
+ /WANIPCn.xml
+
+
+
+
+
+
+ http://192.168.0.1/
+
+"#;
+
+ let (control_schema_url, control_url) = parse_control_urls(text.as_bytes()).unwrap();
+ assert_eq!(control_url, "/ctl/IPConn");
+ assert_eq!(control_schema_url, "/WANIPCn.xml");
+}
+
+#[test]
+fn test_parse_device2() {
+ let text = r#"
+
+
+ 1
+ 0
+
+
+ urn:schemas-upnp-org:device:InternetGatewayDevice:1
+ FRITZ!Box 7430
+ AVM Berlin
+ http://www.avm.de
+ FRITZ!Box 7430
+ FRITZ!Box 7430
+ avm
+ http://www.avm.de
+ uuid:00000000-0000-0000-0000-000000000000
+
+
+ image/gif
+ 118
+ 119
+ 8
+ /ligd.gif
+
+
+
+
+ urn:schemas-any-com:service:Any:1
+ urn:any-com:serviceId:any1
+ /igdupnp/control/any
+ /igdupnp/control/any
+ /any.xml
+
+
+
+
+ urn:schemas-upnp-org:device:WANDevice:1
+ WANDevice - FRITZ!Box 7430
+ AVM Berlin
+ www.avm.de
+ WANDevice - FRITZ!Box 7430
+ WANDevice - FRITZ!Box 7430
+ avm
+ www.avm.de
+ uuid:00000000-0000-0000-0000-000000000000
+ AVM IGD
+
+
+ urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1
+ urn:upnp-org:serviceId:WANCommonIFC1
+ /igdupnp/control/WANCommonIFC1
+ /igdupnp/control/WANCommonIFC1
+ /igdicfgSCPD.xml
+
+
+
+
+ urn:schemas-upnp-org:device:WANConnectionDevice:1
+ WANConnectionDevice - FRITZ!Box 7430
+ AVM Berlin
+ www.avm.de
+ WANConnectionDevice - FRITZ!Box 7430
+ WANConnectionDevice - FRITZ!Box 7430
+ avm
+ www.avm.de
+ uuid:00000000-0000-0000-0000-000000000000
+ AVM IGD
+
+
+ urn:schemas-upnp-org:service:WANDSLLinkConfig:1
+ urn:upnp-org:serviceId:WANDSLLinkC1
+ /igdupnp/control/WANDSLLinkC1
+ /igdupnp/control/WANDSLLinkC1
+ /igddslSCPD.xml
+
+
+ urn:schemas-upnp-org:service:WANIPConnection:1
+ urn:upnp-org:serviceId:WANIPConn1
+ /igdupnp/control/WANIPConn1
+ /igdupnp/control/WANIPConn1
+ /igdconnSCPD.xml
+
+
+ urn:schemas-upnp-org:service:WANIPv6FirewallControl:1
+ urn:upnp-org:serviceId:WANIPv6Firewall1
+ /igd2upnp/control/WANIPv6Firewall1
+ /igd2upnp/control/WANIPv6Firewall1
+ /igd2ipv6fwcSCPD.xml
+
+
+
+
+
+
+ http://fritz.box
+
+
+ "#;
+ let result = parse_control_urls(text.as_bytes());
+ assert!(result.is_ok());
+ let (control_schema_url, control_url) = result.unwrap();
+ assert_eq!(control_url, "/igdupnp/control/WANIPConn1");
+ assert_eq!(control_schema_url, "/igdconnSCPD.xml");
+}
+
+#[test]
+fn test_parse_device3() {
+ let text = r#"
+
+
+ 1
+ 0
+
+
+ urn:schemas-upnp-org:device:InternetGatewayDevice:1
+
+
+
+
+
+
+
+ http://192.168.1.1
+ uuid:00000000-0000-0000-0000-000000000000
+ 999999999001
+
+
+ image/png
+ 16
+ 16
+ 8
+ /ligd.png
+
+
+
+
+ urn:schemas-upnp-org:device:WANDevice:1
+
+
+
+
+
+
+
+
+ http://192.168.1.254
+ uuid:00000000-0000-0000-0000-000000000000
+ 999999999001
+
+
+ urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1
+ urn:upnp-org:serviceId:WANCommonIFC1
+ /upnp/control/WANCommonIFC1
+ /upnp/control/WANCommonIFC1
+ /332b484d/wancomicfgSCPD.xml
+
+
+
+
+ urn:schemas-upnp-org:device:WANConnectionDevice:1
+
+
+
+
+
+
+
+
+ http://192.168.1.254
+ uuid:00000000-0000-0000-0000-000000000000
+ 999999999001
+
+
+ urn:schemas-upnp-org:service:WANIPConnection:1
+ urn:upnp-org:serviceId:WANIPConn1
+ /upnp/control/WANIPConn1
+ /upnp/control/WANIPConn1
+ /332b484d/wanipconnSCPD.xml
+
+
+
+
+
+
+
+"#;
+
+ let (control_schema_url, control_url) = parse_control_urls(text.as_bytes()).unwrap();
+ assert_eq!(control_url, "/upnp/control/WANIPConn1");
+ assert_eq!(control_schema_url, "/332b484d/wanipconnSCPD.xml");
+}
diff --git a/easytier/src/instance/upnp_igd/gateway.rs b/easytier/src/instance/upnp_igd/gateway.rs
new file mode 100644
index 00000000..e6f68be2
--- /dev/null
+++ b/easytier/src/instance/upnp_igd/gateway.rs
@@ -0,0 +1,323 @@
+use std::collections::HashMap;
+use std::fmt;
+use std::hash::{Hash, Hasher};
+use std::net::{IpAddr, SocketAddr};
+
+use super::Provider;
+
+use super::common::{self, messages, parsing, parsing::RequestReponse};
+use super::PortMappingProtocol;
+
+/// This structure represents a gateway found by the search functions.
+#[derive(Clone, Debug)]
+pub struct Gateway {
+ /// Socket address of the gateway
+ pub addr: SocketAddr,
+ /// Root url of the device
+ pub root_url: String,
+ /// Control url of the device
+ pub control_url: String,
+ /// Url to get schema data from
+ pub control_schema_url: String,
+ /// Control schema for all actions
+ pub control_schema: HashMap>,
+ /// Executor provider
+ pub provider: P,
+}
+
+impl Gateway {
+ async fn perform_request(
+ &self,
+ header: &str,
+ body: &str,
+ ok: &str,
+ ) -> anyhow::Result {
+ let url = format!("{self}");
+ let text = P::send_async(&url, header, body).await?;
+ parsing::parse_response(text, ok)
+ }
+
+ /// Get the external IP address of the gateway in a tokio compatible way
+ pub async fn get_external_ip(&self) -> anyhow::Result