Skip to main content

igd_next/aio/
gateway.rs

1use std::collections::HashMap;
2use std::fmt;
3use std::hash::{Hash, Hasher};
4use std::net::{IpAddr, SocketAddr};
5
6use super::Provider;
7use crate::errors::{self, AddAnyPortError, AddPortError, GetExternalIpError, RemovePortError, RequestError};
8
9use crate::common::{self, messages, parsing, parsing::RequestReponse};
10use crate::PortMappingProtocol;
11
12/// This structure represents a gateway found by the search functions.
13#[derive(Clone, Debug)]
14pub struct Gateway<P> {
15    /// Socket address of the gateway
16    pub addr: SocketAddr,
17    /// Root url of the device
18    pub root_url: String,
19    /// Control url of the device
20    pub control_url: String,
21    /// Url to get schema data from
22    pub control_schema_url: String,
23    /// Control schema for all actions
24    pub control_schema: HashMap<String, Vec<String>>,
25    /// Executor provider
26    pub provider: P,
27}
28
29impl<P: Provider> Gateway<P> {
30    async fn perform_request(&self, header: &str, body: &str, ok: &str) -> Result<RequestReponse, RequestError> {
31        let url = format!("{self}");
32        let text = P::send_async(&url, header, body).await?;
33        parsing::parse_response(text, ok)
34    }
35
36    /// Get the external IP address of the gateway in a tokio compatible way
37    pub async fn get_external_ip(&self) -> Result<IpAddr, GetExternalIpError> {
38        let result = self
39            .perform_request(
40                messages::GET_EXTERNAL_IP_HEADER,
41                &messages::format_get_external_ip_message(),
42                "GetExternalIPAddressResponse",
43            )
44            .await;
45        parsing::parse_get_external_ip_response(result)
46    }
47
48    /// Get an external socket address with our external ip and any port. This is a convenience
49    /// function that calls `get_external_ip` followed by `add_any_port`
50    ///
51    /// The local_addr is the address where the traffic is sent to.
52    /// The lease_duration parameter is in seconds. A value of 0 is infinite.
53    ///
54    /// # Returns
55    ///
56    /// The external address that was mapped on success. Otherwise an error.
57    pub async fn get_any_address(
58        &self,
59        protocol: PortMappingProtocol,
60        local_addr: SocketAddr,
61        lease_duration: u32,
62        description: &str,
63    ) -> Result<SocketAddr, AddAnyPortError> {
64        let description = description.to_owned();
65        let ip = self.get_external_ip().await?;
66        let port = self
67            .add_any_port(protocol, local_addr, lease_duration, &description)
68            .await?;
69        Ok(SocketAddr::new(ip, port))
70    }
71
72    /// Add a port mapping.with any external port.
73    ///
74    /// The local_addr is the address where the traffic is sent to.
75    /// The lease_duration parameter is in seconds. A value of 0 is infinite.
76    ///
77    /// # Returns
78    ///
79    /// The external port that was mapped on success. Otherwise an error.
80    pub async fn add_any_port(
81        &self,
82        protocol: PortMappingProtocol,
83        local_addr: SocketAddr,
84        lease_duration: u32,
85        description: &str,
86    ) -> Result<u16, AddAnyPortError> {
87        // This function first attempts to call AddAnyPortMapping on the IGD with a random port
88        // number. If that fails due to the method being unknown it attempts to call AddPortMapping
89        // instead with a random port number. If that fails due to ConflictInMappingEntry it retrys
90        // with another port up to a maximum of 20 times. If it fails due to SamePortValuesRequired
91        // it retrys once with the same port values.
92
93        if local_addr.port() == 0 {
94            return Err(AddAnyPortError::InternalPortZeroInvalid);
95        }
96
97        let schema = self.control_schema.get("AddAnyPortMapping");
98        if let Some(schema) = schema {
99            let external_port = common::random_port();
100
101            let description = description.to_owned();
102
103            let resp = self
104                .perform_request(
105                    messages::ADD_ANY_PORT_MAPPING_HEADER,
106                    &messages::format_add_any_port_mapping_message(
107                        schema,
108                        protocol,
109                        external_port,
110                        local_addr,
111                        lease_duration,
112                        &description,
113                    ),
114                    "AddAnyPortMappingResponse",
115                )
116                .await;
117            parsing::parse_add_any_port_mapping_response(resp)
118        } else {
119            // The router does not have the AddAnyPortMapping method.
120            // Fall back to using AddPortMapping with a random port.
121            self.retry_add_random_port_mapping(protocol, local_addr, lease_duration, description)
122                .await
123        }
124    }
125
126    async fn retry_add_random_port_mapping(
127        &self,
128        protocol: PortMappingProtocol,
129        local_addr: SocketAddr,
130        lease_duration: u32,
131        description: &str,
132    ) -> Result<u16, AddAnyPortError> {
133        for _ in 0u8..20u8 {
134            match self
135                .add_random_port_mapping(protocol, local_addr, lease_duration, description)
136                .await
137            {
138                Ok(port) => return Ok(port),
139                Err(AddAnyPortError::NoPortsAvailable) => continue,
140                e => return e,
141            }
142        }
143        Err(AddAnyPortError::NoPortsAvailable)
144    }
145
146    async fn add_random_port_mapping(
147        &self,
148        protocol: PortMappingProtocol,
149        local_addr: SocketAddr,
150        lease_duration: u32,
151        description: &str,
152    ) -> Result<u16, AddAnyPortError> {
153        let description = description.to_owned();
154        let external_port = common::random_port();
155        let res = self
156            .add_port_mapping(protocol, external_port, local_addr, lease_duration, &description)
157            .await;
158
159        match res {
160            Ok(_) => Ok(external_port),
161            Err(err) => match parsing::convert_add_random_port_mapping_error(err) {
162                Some(err) => Err(err),
163                None => {
164                    self.add_same_port_mapping(protocol, local_addr, lease_duration, &description)
165                        .await
166                }
167            },
168        }
169    }
170
171    async fn add_same_port_mapping(
172        &self,
173        protocol: PortMappingProtocol,
174        local_addr: SocketAddr,
175        lease_duration: u32,
176        description: &str,
177    ) -> Result<u16, AddAnyPortError> {
178        let res = self
179            .add_port_mapping(protocol, local_addr.port(), local_addr, lease_duration, description)
180            .await;
181        match res {
182            Ok(_) => Ok(local_addr.port()),
183            Err(err) => Err(parsing::convert_add_same_port_mapping_error(err)),
184        }
185    }
186
187    async fn add_port_mapping(
188        &self,
189        protocol: PortMappingProtocol,
190        external_port: u16,
191        local_addr: SocketAddr,
192        lease_duration: u32,
193        description: &str,
194    ) -> Result<(), RequestError> {
195        self.perform_request(
196            messages::ADD_PORT_MAPPING_HEADER,
197            &messages::format_add_port_mapping_message(
198                self.control_schema
199                    .get("AddPortMapping")
200                    .ok_or_else(|| RequestError::UnsupportedAction("AddPortMapping".to_string()))?,
201                protocol,
202                external_port,
203                local_addr,
204                lease_duration,
205                description,
206            ),
207            "AddPortMappingResponse",
208        )
209        .await?;
210        Ok(())
211    }
212
213    /// Add a port mapping.
214    ///
215    /// The local_addr is the address where the traffic is sent to.
216    /// The lease_duration parameter is in seconds. A value of 0 is infinite.
217    pub async fn add_port(
218        &self,
219        protocol: PortMappingProtocol,
220        external_port: u16,
221        local_addr: SocketAddr,
222        lease_duration: u32,
223        description: &str,
224    ) -> Result<(), AddPortError> {
225        if external_port == 0 {
226            return Err(AddPortError::ExternalPortZeroInvalid);
227        }
228        if local_addr.port() == 0 {
229            return Err(AddPortError::InternalPortZeroInvalid);
230        }
231
232        let res = self
233            .add_port_mapping(protocol, external_port, local_addr, lease_duration, description)
234            .await;
235        if let Err(err) = res {
236            return Err(parsing::convert_add_port_error(err));
237        };
238        Ok(())
239    }
240
241    /// Remove a port mapping.
242    pub async fn remove_port(&self, protocol: PortMappingProtocol, external_port: u16) -> Result<(), RemovePortError> {
243        let res = self
244            .perform_request(
245                messages::DELETE_PORT_MAPPING_HEADER,
246                &messages::format_delete_port_message(
247                    self.control_schema.get("DeletePortMapping").ok_or_else(|| {
248                        RemovePortError::RequestError(RequestError::UnsupportedAction("DeletePortMapping".to_string()))
249                    })?,
250                    protocol,
251                    external_port,
252                ),
253                "DeletePortMappingResponse",
254            )
255            .await;
256        parsing::parse_delete_port_mapping_response(res)
257    }
258
259    /// Get one port mapping entry
260    ///
261    /// Gets one port mapping entry by its index.
262    /// Not all existing port mappings might be visible to this client.
263    /// If the index is out of bound, GetGenericPortMappingEntryError::SpecifiedArrayIndexInvalid will be returned
264    pub async fn get_generic_port_mapping_entry(
265        &self,
266        index: u32,
267    ) -> Result<parsing::PortMappingEntry, errors::GetGenericPortMappingEntryError> {
268        let result = self
269            .perform_request(
270                messages::GET_GENERIC_PORT_MAPPING_ENTRY,
271                &messages::formate_get_generic_port_mapping_entry_message(index),
272                "GetGenericPortMappingEntryResponse",
273            )
274            .await;
275        parsing::parse_get_generic_port_mapping_entry(result)
276    }
277}
278
279impl<P> fmt::Display for Gateway<P> {
280    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
281        write!(f, "http://{}{}", self.addr, self.control_url)
282    }
283}
284
285impl<P> PartialEq for Gateway<P> {
286    fn eq(&self, other: &Gateway<P>) -> bool {
287        self.addr == other.addr && self.control_url == other.control_url
288    }
289}
290
291impl<P> Eq for Gateway<P> {}
292
293impl<P> Hash for Gateway<P> {
294    fn hash<H: Hasher>(&self, state: &mut H) {
295        self.addr.hash(state);
296        self.control_url.hash(state);
297    }
298}