1use std::{net::Ipv4Addr, num::NonZeroU16, time::Duration};
4
5use rand::RngCore;
6use tracing::{debug, trace};
7
8mod protocol;
9
10const RECV_TIMEOUT: Duration = Duration::from_millis(500);
12
13const MAPPING_REQUESTED_LIFETIME_SECONDS: u32 = 60 * 60;
16
17#[derive(Debug)]
19pub struct Mapping {
20 pub local_ip: Ipv4Addr,
22 pub local_port: NonZeroU16,
24 pub gateway: Ipv4Addr,
26 pub external_port: NonZeroU16,
28 pub external_address: Ipv4Addr,
30 pub lifetime_seconds: u32,
32 pub nonce: [u8; 12],
35}
36
37impl Mapping {
38 pub async fn new(
40 local_ip: Ipv4Addr,
41 local_port: NonZeroU16,
42 gateway: Ipv4Addr,
43 preferred_external_address: Option<(Ipv4Addr, NonZeroU16)>,
44 ) -> anyhow::Result<Self> {
45 let socket = tokio::net::UdpSocket::bind((local_ip, 0)).await?;
47 socket.connect((gateway, protocol::SERVER_PORT)).await?;
48
49 let mut nonce = [0u8; 12];
50 rand::thread_rng().fill_bytes(&mut nonce);
51
52 let (requested_address, requested_port) = match preferred_external_address {
53 Some((ip, port)) => (Some(ip), Some(port.into())),
54 None => (None, None),
55 };
56
57 let req = protocol::Request::mapping(
58 nonce,
59 local_port.into(),
60 local_ip,
61 requested_port,
62 requested_address,
63 MAPPING_REQUESTED_LIFETIME_SECONDS,
64 );
65
66 socket.send(&req.encode()).await?;
67
68 let mut buffer = vec![0; protocol::Response::MAX_SIZE];
70 let read = tokio::time::timeout(RECV_TIMEOUT, socket.recv(&mut buffer)).await??;
71 let response = protocol::Response::decode(&buffer[..read])?;
72
73 let protocol::Response {
75 lifetime_seconds,
76 epoch_time: _,
77 data,
78 } = response;
79
80 match data {
81 protocol::OpcodeData::MapData(map_data) => {
82 let protocol::MapData {
83 nonce: received_nonce,
84 protocol,
85 local_port: received_local_port,
86 external_port,
87 external_address,
88 } = map_data;
89
90 if nonce != received_nonce {
91 anyhow::bail!("received nonce does not match sent request");
92 }
93
94 if protocol != protocol::MapProtocol::Udp {
95 anyhow::bail!("received mapping is not for UDP");
96 }
97
98 let sent_port: u16 = local_port.into();
99 if received_local_port != sent_port {
100 anyhow::bail!("received mapping is for a local port that does not match the requested one");
101 }
102 let external_port = external_port
103 .try_into()
104 .map_err(|_| anyhow::anyhow!("received 0 external port for mapping"))?;
105
106 let external_address = external_address
107 .to_ipv4_mapped()
108 .ok_or(anyhow::anyhow!("received external address is not ipv4"))?;
109
110 Ok(Mapping {
111 external_port,
112 external_address,
113 lifetime_seconds,
114 nonce,
115 local_ip,
116 local_port,
117 gateway,
118 })
119 }
120 protocol::OpcodeData::Announce => {
121 anyhow::bail!("received an announce response for a map request")
122 }
123 }
124 }
125
126 pub async fn release(self) -> anyhow::Result<()> {
127 let Mapping {
128 nonce,
129 local_ip,
130 local_port,
131 gateway,
132 ..
133 } = self;
134
135 let socket = tokio::net::UdpSocket::bind((local_ip, 0)).await?;
137 socket.connect((gateway, protocol::SERVER_PORT)).await?;
138
139 let local_port = local_port.into();
140 let req = protocol::Request::mapping(nonce, local_port, local_ip, None, None, 0);
141
142 socket.send(&req.encode()).await?;
143
144 Ok(())
146 }
147}
148
149pub async fn probe_available(local_ip: Ipv4Addr, gateway: Ipv4Addr) -> bool {
151 match probe_available_fallible(local_ip, gateway).await {
152 Ok(response) => {
153 trace!("probe response: {response:?}");
154 let protocol::Response {
155 lifetime_seconds: _,
156 epoch_time: _,
157 data,
158 } = response;
159 match data {
160 protocol::OpcodeData::Announce => true,
161 _ => {
162 debug!("server returned an unexpected response type for probe");
163 false
165 }
166 }
167 }
168 Err(e) => {
169 debug!("probe failed: {e}");
170 false
171 }
172 }
173}
174
175async fn probe_available_fallible(
176 local_ip: Ipv4Addr,
177 gateway: Ipv4Addr,
178) -> anyhow::Result<protocol::Response> {
179 let socket = tokio::net::UdpSocket::bind((local_ip, 0)).await?;
181 socket.connect((gateway, protocol::SERVER_PORT)).await?;
182 let req = protocol::Request::annouce(local_ip.to_ipv6_mapped());
183 socket.send(&req.encode()).await?;
184
185 let mut buffer = vec![0; protocol::Response::MAX_SIZE];
187 let read = tokio::time::timeout(RECV_TIMEOUT, socket.recv(&mut buffer)).await??;
188 let response = protocol::Response::decode(&buffer[..read])?;
189
190 Ok(response)
191}