libp2p_upnp/
tokio.rs

1// Copyright 2023 Protocol Labs.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a
4// copy of this software and associated documentation files (the "Software"),
5// to deal in the Software without restriction, including without limitation
6// the rights to use, copy, modify, merge, publish, distribute, sublicense,
7// and/or sell copies of the Software, and to permit persons to whom the
8// Software is furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in
11// all copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19// DEALINGS IN THE SOFTWARE.
20
21use std::{error::Error, net::IpAddr};
22
23use futures::{
24    channel::{mpsc, oneshot},
25    SinkExt, StreamExt,
26};
27use igd_next::SearchOptions;
28
29pub use crate::behaviour::Behaviour;
30use crate::behaviour::{GatewayEvent, GatewayRequest};
31
32// TODO: remove when `IpAddr::is_global` stabilizes.
33pub(crate) fn is_addr_global(addr: IpAddr) -> bool {
34    match addr {
35        IpAddr::V4(ip) => {
36            !(ip.octets()[0] == 0 // "This network"
37                                    || ip.is_private()
38                                    // code for Ipv4::is_shared()
39                                    || (ip.octets()[0] == 100 && (ip.octets()[1] & 0b1100_0000 == 0b0100_0000))
40                                    || ip.is_loopback()
41                                    || ip.is_link_local()
42                                    // addresses reserved for future protocols (`192.0.0.0/24`)
43                                    ||(ip.octets()[0] == 192 && ip.octets()[1] == 0 && ip.octets()[2] == 0)
44                                    || ip.is_documentation()
45                                    // code for Ipv4::is_benchmarking()
46                                    || (ip.octets()[0] == 198 && (ip.octets()[1] & 0xfe) == 18)
47                                    // code for Ipv4::is_reserved()
48                                    || (ip.octets()[0] & 240 == 240 && !ip.is_broadcast())
49                                    || ip.is_broadcast())
50        }
51        IpAddr::V6(ip) => {
52            !(ip.is_unspecified()
53                                    || ip.is_loopback()
54                                    // IPv4-mapped Address (`::ffff:0:0/96`)
55                                    || matches!(ip.segments(), [0, 0, 0, 0, 0, 0xffff, _, _])
56                                    // IPv4-IPv6 Translat. (`64:ff9b:1::/48`)
57                                    || matches!(ip.segments(), [0x64, 0xff9b, 1, _, _, _, _, _])
58                                    // Discard-Only Address Block (`100::/64`)
59                                    || matches!(ip.segments(), [0x100, 0, 0, 0, _, _, _, _])
60                                    // IETF Protocol Assignments (`2001::/23`)
61                                    || (matches!(ip.segments(), [0x2001, b, _, _, _, _, _, _] if b < 0x200)
62                                    && !(
63                                    // Port Control Protocol Anycast (`2001:1::1`)
64                                    u128::from_be_bytes(ip.octets()) == 0x2001_0001_0000_0000_0000_0000_0000_0001
65                                    // Traversal Using Relays around NAT Anycast (`2001:1::2`)
66                                    || u128::from_be_bytes(ip.octets()) == 0x2001_0001_0000_0000_0000_0000_0000_0002
67                                    // AMT (`2001:3::/32`)
68                                    || matches!(ip.segments(), [0x2001, 3, _, _, _, _, _, _])
69                                    // AS112-v6 (`2001:4:112::/48`)
70                                    || matches!(ip.segments(), [0x2001, 4, 0x112, _, _, _, _, _])
71                                    // ORCHIDv2 (`2001:20::/28`)
72                                    || matches!(ip.segments(), [0x2001, b, _, _, _, _, _, _] if (0x20..=0x2F).contains(&b))
73                                ))
74                                    // code for Ipv4::is_documentation()
75                                    || (ip.segments()[0] == 0x2001) && (ip.segments()[1] == 0xdb8)
76                                    // code for Ipv4::is_unique_local()
77                                    || (ip.segments()[0] & 0xfe00) == 0xfc00
78                                    // code for Ipv4::is_unicast_link_local()
79                                    || (ip.segments()[0] & 0xffc0) == 0xfe80)
80        }
81    }
82}
83
84/// Interface that interacts with the inner gateway by messages,
85/// `GatewayRequest`s and `GatewayEvent`s.
86#[derive(Debug)]
87pub(crate) struct Gateway {
88    pub(crate) sender: mpsc::Sender<GatewayRequest>,
89    pub(crate) receiver: mpsc::Receiver<GatewayEvent>,
90    pub(crate) external_addr: IpAddr,
91}
92
93pub(crate) fn search_gateway() -> oneshot::Receiver<Result<Gateway, Box<dyn Error + Send + Sync>>> {
94    let (search_result_sender, search_result_receiver) = oneshot::channel();
95
96    let (events_sender, mut task_receiver) = mpsc::channel(10);
97    let (mut task_sender, events_queue) = mpsc::channel(0);
98
99    tokio::spawn(async move {
100        let gateway = match igd_next::aio::tokio::search_gateway(SearchOptions::default()).await {
101            Ok(gateway) => gateway,
102            Err(err) => {
103                let _ = search_result_sender.send(Err(err.into()));
104                return;
105            }
106        };
107
108        let external_addr = match gateway.get_external_ip().await {
109            Ok(addr) => addr,
110            Err(err) => {
111                let _ = search_result_sender.send(Err(err.into()));
112                return;
113            }
114        };
115
116        // Check if receiver dropped.
117        if search_result_sender
118            .send(Ok(Gateway {
119                sender: events_sender,
120                receiver: events_queue,
121                external_addr,
122            }))
123            .is_err()
124        {
125            return;
126        }
127
128        loop {
129            // The task sender has dropped so we can return.
130            let Some(req) = task_receiver.next().await else {
131                return;
132            };
133            let event = match req {
134                GatewayRequest::AddMapping { mapping, duration } => {
135                    let gateway = gateway.clone();
136                    match gateway
137                        .add_port(
138                            mapping.protocol,
139                            mapping.internal_addr.port(),
140                            mapping.internal_addr,
141                            duration,
142                            "rust-libp2p mapping",
143                        )
144                        .await
145                    {
146                        Ok(()) => GatewayEvent::Mapped(mapping),
147                        Err(err) => GatewayEvent::MapFailure(mapping, err.into()),
148                    }
149                }
150                GatewayRequest::RemoveMapping(mapping) => {
151                    let gateway = gateway.clone();
152                    match gateway
153                        .remove_port(mapping.protocol, mapping.internal_addr.port())
154                        .await
155                    {
156                        Ok(()) => GatewayEvent::Removed(mapping),
157                        Err(err) => GatewayEvent::RemovalFailure(mapping, err.into()),
158                    }
159                }
160            };
161            // Gateway was dropped.
162            if task_sender.send(event).await.is_err() {
163                return;
164            }
165        }
166    });
167
168    search_result_receiver
169}