1use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
12use std::time::Duration;
13
14use async_trait::async_trait;
15use tokio::net::UdpSocket;
16
17use crate::error::MethodError;
18use crate::method::{MethodOutcome, TraversalKind, TraversalMethod};
19use crate::peer::PeerTarget;
20
21pub const NATPMP_PORT: u16 = 5351;
23pub const NATPMP_VERSION: u8 = 0;
25pub const OP_EXTERNAL_ADDRESS: u8 = 0;
27pub const OP_MAP_UDP: u8 = 1;
29pub const OP_MAP_TCP: u8 = 2;
31pub const RESPONSE_FLAG: u8 = 0x80;
33
34pub const RESULT_SUCCESS: u16 = 0;
36
37pub fn encode_external_address_request() -> [u8; 2] {
39 [NATPMP_VERSION, OP_EXTERNAL_ADDRESS]
40}
41
42pub fn encode_map_request(
45 tcp: bool,
46 internal_port: u16,
47 suggested_external_port: u16,
48 lifetime_secs: u32,
49) -> [u8; 12] {
50 let opcode = if tcp { OP_MAP_TCP } else { OP_MAP_UDP };
51 let mut buf = [0u8; 12];
52 buf[0] = NATPMP_VERSION;
53 buf[1] = opcode;
54 buf[4..6].copy_from_slice(&internal_port.to_be_bytes());
56 buf[6..8].copy_from_slice(&suggested_external_port.to_be_bytes());
57 buf[8..12].copy_from_slice(&lifetime_secs.to_be_bytes());
58 buf
59}
60
61#[derive(Debug, Clone, PartialEq, Eq)]
63pub struct ExternalAddressResponse {
64 pub seconds_since_epoch: u32,
66 pub external_ip: Ipv4Addr,
68}
69
70pub fn parse_external_address_response(msg: &[u8]) -> Result<ExternalAddressResponse, NatPmpError> {
73 if msg.len() < 12 {
74 return Err(NatPmpError::Truncated);
75 }
76 if msg[1] != OP_EXTERNAL_ADDRESS + RESPONSE_FLAG {
77 return Err(NatPmpError::UnexpectedOpcode(msg[1]));
78 }
79 let result = u16::from_be_bytes([msg[2], msg[3]]);
80 if result != RESULT_SUCCESS {
81 return Err(NatPmpError::ResultCode(result));
82 }
83 let seconds = u32::from_be_bytes([msg[4], msg[5], msg[6], msg[7]]);
84 let ip = Ipv4Addr::new(msg[8], msg[9], msg[10], msg[11]);
85 Ok(ExternalAddressResponse {
86 seconds_since_epoch: seconds,
87 external_ip: ip,
88 })
89}
90
91#[derive(Debug, Clone, PartialEq, Eq)]
93pub struct MapResponse {
94 pub internal_port: u16,
96 pub external_port: u16,
98 pub lifetime_secs: u32,
100}
101
102pub fn parse_map_response(msg: &[u8], tcp: bool) -> Result<MapResponse, NatPmpError> {
105 if msg.len() < 16 {
106 return Err(NatPmpError::Truncated);
107 }
108 let expected_op = if tcp { OP_MAP_TCP } else { OP_MAP_UDP } + RESPONSE_FLAG;
109 if msg[1] != expected_op {
110 return Err(NatPmpError::UnexpectedOpcode(msg[1]));
111 }
112 let result = u16::from_be_bytes([msg[2], msg[3]]);
113 if result != RESULT_SUCCESS {
114 return Err(NatPmpError::ResultCode(result));
115 }
116 let internal_port = u16::from_be_bytes([msg[8], msg[9]]);
117 let external_port = u16::from_be_bytes([msg[10], msg[11]]);
118 let lifetime = u32::from_be_bytes([msg[12], msg[13], msg[14], msg[15]]);
119 Ok(MapResponse {
120 internal_port,
121 external_port,
122 lifetime_secs: lifetime,
123 })
124}
125
126#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
128pub enum NatPmpError {
129 #[error("NAT-PMP response truncated")]
131 Truncated,
132 #[error("unexpected NAT-PMP opcode: {0}")]
134 UnexpectedOpcode(u8),
135 #[error("NAT-PMP result code {0}")]
137 ResultCode(u16),
138 #[error("NAT-PMP io: {0}")]
140 Io(String),
141 #[error("NAT-PMP request timed out")]
143 Timeout,
144}
145
146#[derive(Debug, Clone)]
152pub struct NatPmpMethod {
153 pub gateway: SocketAddrV4,
155 pub local_port: u16,
157 pub lifetime_secs: u32,
159 pub timeout: Duration,
161}
162
163impl NatPmpMethod {
164 pub fn new(gateway: Ipv4Addr, local_port: u16) -> Self {
167 NatPmpMethod {
168 gateway: SocketAddrV4::new(gateway, NATPMP_PORT),
169 local_port,
170 lifetime_secs: 7200,
171 timeout: Duration::from_secs(1),
172 }
173 }
174
175 async fn transact(&self, payload: &[u8]) -> Result<Vec<u8>, NatPmpError> {
177 let socket = UdpSocket::bind((Ipv4Addr::UNSPECIFIED, 0))
178 .await
179 .map_err(|e| NatPmpError::Io(e.to_string()))?;
180 socket
181 .send_to(payload, self.gateway)
182 .await
183 .map_err(|e| NatPmpError::Io(e.to_string()))?;
184 let mut buf = [0u8; 32];
185 match tokio::time::timeout(self.timeout, socket.recv_from(&mut buf)).await {
186 Ok(Ok((n, _))) => Ok(buf[..n].to_vec()),
187 Ok(Err(e)) => Err(NatPmpError::Io(e.to_string())),
188 Err(_) => Err(NatPmpError::Timeout),
189 }
190 }
191}
192
193#[async_trait]
194impl TraversalMethod for NatPmpMethod {
195 fn kind(&self) -> TraversalKind {
196 TraversalKind::NatPmp
197 }
198
199 async fn attempt(&self, peer: &PeerTarget) -> Result<MethodOutcome, MethodError> {
200 let dial_addrs = peer.direct_addrs();
203 if dial_addrs.is_empty() {
204 return Err(MethodError::failed(
205 TraversalKind::NatPmp,
206 "peer has no address to dial after mapping",
207 ));
208 }
209
210 let resp = self
212 .transact(&encode_external_address_request())
213 .await
214 .map_err(|e| to_method_error(&e))?;
215 parse_external_address_response(&resp).map_err(|e| to_method_error(&e))?;
216
217 let map = encode_map_request(false, self.local_port, self.local_port, self.lifetime_secs);
219 let resp = self.transact(&map).await.map_err(|e| to_method_error(&e))?;
220 parse_map_response(&resp, false).map_err(|e| to_method_error(&e))?;
221
222 Ok(MethodOutcome::candidates(
223 TraversalKind::NatPmp,
224 dial_addrs.to_vec(),
225 ))
226 }
227}
228
229fn to_method_error(e: &NatPmpError) -> MethodError {
231 match e {
232 NatPmpError::Timeout => MethodError::timeout(TraversalKind::NatPmp),
233 other => MethodError::failed(TraversalKind::NatPmp, other.to_string()),
234 }
235}
236
237pub fn ipv4_gateway(addr: SocketAddr) -> Option<Ipv4Addr> {
239 match addr {
240 SocketAddr::V4(v4) => Some(*v4.ip()),
241 SocketAddr::V6(_) => None,
242 }
243}