use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4};
use std::time::Duration;
use async_trait::async_trait;
use tokio::net::UdpSocket;
use crate::error::MethodError;
use crate::method::natpmp::NATPMP_PORT;
use crate::method::{MethodOutcome, TraversalKind, TraversalMethod};
use crate::peer::PeerTarget;
pub const PCP_VERSION: u8 = 2;
pub const OP_MAP: u8 = 1;
pub const RESPONSE_BIT: u8 = 0x80;
pub const RESULT_SUCCESS: u8 = 0;
pub const PROTO_UDP: u8 = 17;
pub const PROTO_TCP: u8 = 6;
pub type MapNonce = [u8; 12];
pub fn encode_map_request(
nonce: &MapNonce,
tcp: bool,
internal_port: u16,
suggested_external_port: u16,
lifetime_secs: u32,
client_ip: IpAddr,
) -> Vec<u8> {
let mut buf = Vec::with_capacity(60);
buf.push(PCP_VERSION);
buf.push(OP_MAP); buf.extend_from_slice(&[0, 0]); buf.extend_from_slice(&lifetime_secs.to_be_bytes());
buf.extend_from_slice(&ip_to_16(client_ip));
buf.extend_from_slice(nonce);
buf.push(if tcp { PROTO_TCP } else { PROTO_UDP });
buf.extend_from_slice(&[0, 0, 0]); buf.extend_from_slice(&internal_port.to_be_bytes());
buf.extend_from_slice(&suggested_external_port.to_be_bytes());
buf.extend_from_slice(&ip_to_16(IpAddr::V4(Ipv4Addr::UNSPECIFIED))); debug_assert_eq!(buf.len(), 60);
buf
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MapResponse {
pub lifetime_secs: u32,
pub nonce: MapNonce,
pub external_port: u16,
pub external_ip: IpAddr,
}
pub fn parse_map_response(msg: &[u8], expected_nonce: &MapNonce) -> Result<MapResponse, PcpError> {
if msg.len() < 60 {
return Err(PcpError::Truncated);
}
if msg[0] != PCP_VERSION {
return Err(PcpError::BadVersion(msg[0]));
}
if msg[1] != (OP_MAP | RESPONSE_BIT) {
return Err(PcpError::UnexpectedOpcode(msg[1]));
}
let result = msg[3];
if result != RESULT_SUCCESS {
return Err(PcpError::ResultCode(result));
}
let lifetime = u32::from_be_bytes([msg[4], msg[5], msg[6], msg[7]]);
let nonce: MapNonce = msg[24..36].try_into().map_err(|_| PcpError::Truncated)?;
if &nonce != expected_nonce {
return Err(PcpError::NonceMismatch);
}
let external_port = u16::from_be_bytes([msg[42], msg[43]]);
let ext_ip_bytes: [u8; 16] = msg[44..60].try_into().map_err(|_| PcpError::Truncated)?;
Ok(MapResponse {
lifetime_secs: lifetime,
nonce,
external_port,
external_ip: ip_from_16(ext_ip_bytes),
})
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum PcpError {
#[error("PCP response truncated")]
Truncated,
#[error("unexpected PCP version: {0}")]
BadVersion(u8),
#[error("unexpected PCP opcode: {0}")]
UnexpectedOpcode(u8),
#[error("PCP result code {0}")]
ResultCode(u8),
#[error("PCP MAP nonce mismatch")]
NonceMismatch,
#[error("PCP io: {0}")]
Io(String),
#[error("PCP request timed out")]
Timeout,
}
#[derive(Debug, Clone)]
pub struct PcpMethod {
pub gateway: SocketAddrV4,
pub local_port: u16,
pub client_ip: IpAddr,
pub lifetime_secs: u32,
pub timeout: Duration,
}
impl PcpMethod {
pub fn new(gateway: Ipv4Addr, local_port: u16, client_ip: IpAddr) -> Self {
PcpMethod {
gateway: SocketAddrV4::new(gateway, NATPMP_PORT),
local_port,
client_ip,
lifetime_secs: 7200,
timeout: Duration::from_secs(1),
}
}
async fn transact(&self, payload: &[u8]) -> Result<Vec<u8>, PcpError> {
let socket = UdpSocket::bind((Ipv4Addr::UNSPECIFIED, 0))
.await
.map_err(|e| PcpError::Io(e.to_string()))?;
socket
.send_to(payload, self.gateway)
.await
.map_err(|e| PcpError::Io(e.to_string()))?;
let mut buf = [0u8; 128];
let deadline = tokio::time::Instant::now() + self.timeout;
loop {
let remaining = deadline.saturating_duration_since(tokio::time::Instant::now());
if remaining.is_zero() {
return Err(PcpError::Timeout);
}
let (n, from) = match tokio::time::timeout(remaining, socket.recv_from(&mut buf)).await
{
Ok(Ok(x)) => x,
Ok(Err(e)) => return Err(PcpError::Io(e.to_string())),
Err(_) => return Err(PcpError::Timeout),
};
if from != SocketAddr::V4(self.gateway) {
continue;
}
return Ok(buf[..n].to_vec());
}
}
}
#[async_trait]
impl TraversalMethod for PcpMethod {
fn kind(&self) -> TraversalKind {
TraversalKind::Pcp
}
async fn attempt(&self, peer: &PeerTarget) -> Result<MethodOutcome, MethodError> {
let dial_addrs = peer.direct_addrs();
if dial_addrs.is_empty() {
return Err(MethodError::failed(
TraversalKind::Pcp,
"peer has no address to dial after mapping",
));
}
let nonce = new_nonce();
let req = encode_map_request(
&nonce,
false,
self.local_port,
self.local_port,
self.lifetime_secs,
self.client_ip,
);
let resp = self.transact(&req).await.map_err(|e| to_method_error(&e))?;
parse_map_response(&resp, &nonce).map_err(|e| to_method_error(&e))?;
Ok(MethodOutcome::candidates(
TraversalKind::Pcp,
dial_addrs.to_vec(),
))
}
}
fn to_method_error(e: &PcpError) -> MethodError {
match e {
PcpError::Timeout => MethodError::timeout(TraversalKind::Pcp),
other => MethodError::failed(TraversalKind::Pcp, other.to_string()),
}
}
fn ip_to_16(ip: IpAddr) -> [u8; 16] {
match ip {
IpAddr::V4(v4) => v4.to_ipv6_mapped().octets(),
IpAddr::V6(v6) => v6.octets(),
}
}
fn ip_from_16(bytes: [u8; 16]) -> IpAddr {
let v6 = Ipv6Addr::from(bytes);
match v6.to_ipv4_mapped() {
Some(v4) => IpAddr::V4(v4),
None => IpAddr::V6(v6),
}
}
pub fn new_nonce() -> MapNonce {
use ring::rand::{SecureRandom, SystemRandom};
let mut n = [0u8; 12];
SystemRandom::new()
.fill(&mut n)
.expect("OS CSPRNG must be available to generate a PCP MAP nonce");
n
}
pub fn ipv4_gateway(addr: SocketAddr) -> Option<Ipv4Addr> {
match addr {
SocketAddr::V4(v4) => Some(*v4.ip()),
SocketAddr::V6(_) => None,
}
}