use maidsafe_utilities::serialisation::{deserialise, SerialisationError, serialise};
use std::io;
use std::net::UdpSocket;
use std::time::{Instant, Duration};
use std::thread;
use socket_addr::SocketAddr;
use w_result::{WResult, WOk, WErr};
use rendezvous_info::{PrivRendezvousInfo, PubRendezvousInfo};
use rendezvous_info;
use socket_utils::RecvUntil;
use mapped_socket_addr::MappedSocketAddr;
#[derive(Debug, RustcEncodable, RustcDecodable)]
struct HolePunch {
pub secret: [u8; 4],
pub ack: bool,
}
#[derive(Debug)]
pub struct HolePunchPacketData {
data: HolePunch,
}
pub struct PunchedUdpSocket {
pub socket: UdpSocket,
pub peer_addr: SocketAddr,
}
quick_error! {
#[derive(Debug)]
#[allow(variant_size_differences)]
pub enum UdpPunchHoleWarning {
UnexpectedHolePunchPacket {
hole_punch: HolePunchPacketData,
} {
description("Received a hole punch packet that does correspond to the \
connection we are trying to make. Possibly, hole punch packets \
from an unrelated connection or arriving on this socket.")
display("Received a hole punch packet that does correspond to the \
connection we are trying to make. Possibly, hole punch packets \
from an unrelated connection or arriving on this socket. Debug \
info: {:#?}", hole_punch)
}
InvalidHolePunchPacket {
err: SerialisationError,
} {
description("Received invalid data on the udp socket while hole punching")
display("Received invalid data on the udp socket while hole punching. \
deserialisation produced the error: {}", err)
cause(err)
}
MsgEndpoint {
endpoint: MappedSocketAddr,
err: io::Error,
} {
description("IO error trying to send a message to one of the peer's potential endpoints.")
display("IO error trying to send a message to endpoint {:?}. {}", endpoint, err)
cause(err)
}
}
}
quick_error! {
#[derive(Debug)]
pub enum UdpPunchHoleError {
TimedOut {
description("Timed out waiting for a response from the peer.")
}
Io {
err: io::Error,
} {
description("IO error when using socket")
display("IO error when using socket: {}", err)
cause(err)
}
SendCompleteAck {
description("Error sending ACK to peer. Kept getting partial writes.")
display("Error sending ACK to peer. Kept getting partial writes.")
}
}
}
impl From<UdpPunchHoleError> for io::Error {
fn from(e: UdpPunchHoleError) -> io::Error {
let err_str = format!("{}", e);
let kind = match e {
UdpPunchHoleError::TimedOut => io::ErrorKind::TimedOut,
UdpPunchHoleError::Io { err } => err.kind(),
UdpPunchHoleError::SendCompleteAck => io::ErrorKind::Other,
};
io::Error::new(kind, err_str)
}
}
impl PunchedUdpSocket {
pub fn punch_hole(socket: UdpSocket,
our_priv_rendezvous_info: PrivRendezvousInfo,
their_pub_rendezvous_info: PubRendezvousInfo,
deadline: Instant)
-> WResult<PunchedUdpSocket, UdpPunchHoleWarning, UdpPunchHoleError>
{
let mut warnings = Vec::new();
let (mut endpoints, their_secret)
= rendezvous_info::decompose(their_pub_rendezvous_info);
let our_secret
= rendezvous_info::get_priv_secret(our_priv_rendezvous_info);
const MAX_DATAGRAM_SIZE: usize = 128;
let send_data = {
let hole_punch = HolePunch {
secret: our_secret,
ack: false,
};
serialise(&hole_punch).unwrap()
};
assert!(send_data.len() <= MAX_DATAGRAM_SIZE,
format!("Data exceed MAX_DATAGRAM_SIZE in blocking_udp_punch_hole: {} > {}",
send_data.len(),
MAX_DATAGRAM_SIZE));
let mut recv_data = [0u8; MAX_DATAGRAM_SIZE];
const DELAY_BETWEEN_RESENDS_MS: u64 = 600;
let mut recv_deadline = Instant::now();
while recv_deadline < deadline {
recv_deadline = recv_deadline + Duration::from_millis(DELAY_BETWEEN_RESENDS_MS);
let mut i = 0;
while i < endpoints.len() {
let _ = match socket.send_to(&send_data[..], &*endpoints[i].addr) {
Ok(n) => n,
Err(e) => {
warnings.push(UdpPunchHoleWarning::MsgEndpoint {
endpoint: endpoints.swap_remove(i),
err: e,
});
continue;
}
};
i += 1;
}
loop {
let (read_size, addr) = match socket.recv_until(&mut recv_data[..], recv_deadline) {
Ok(Some(x)) => x,
Ok(None) => break,
Err(e) => return WErr(UdpPunchHoleError::Io { err: e }),
};
match deserialise::<HolePunch>(&recv_data[..read_size]) {
Ok(hp) => {
if hp.secret == our_secret && hp.ack {
return WOk(PunchedUdpSocket {
socket: socket,
peer_addr: addr,
}, warnings);
}
if hp.secret == their_secret {
let send_data = {
let hole_punch = HolePunch {
secret: their_secret,
ack: true,
};
serialise(&hole_punch).unwrap()
};
assert!(send_data.len() <= MAX_DATAGRAM_SIZE,
format!("Data exceed MAX_DATAGRAM_SIZE in blocking_udp_punch_hole: {} > {}",
send_data.len(),
MAX_DATAGRAM_SIZE));
let mut attempts = 0;
let mut successful_attempts = 0;
let mut error = None;
while attempts < 2 || Instant::now() < deadline {
attempts += 1;
match socket.send_to(&send_data[..], &*addr) {
Ok(n) => {
if n == send_data.len() {
successful_attempts += 1;
if successful_attempts == 2 {
break;
}
}
}
Err(e) => {
if error.is_none() {
error = Some(e);
}
}
};
thread::sleep(Duration::from_millis(100));
}
if successful_attempts == 0 {
let ret = match error {
Some(e) => UdpPunchHoleError::Io { err: e },
None => UdpPunchHoleError::SendCompleteAck,
};
return WErr(ret);
}
else {
return WOk(PunchedUdpSocket {
socket: socket,
peer_addr: addr,
}, warnings);
}
}
if warnings.len() < 10 {
warnings.push(UdpPunchHoleWarning::UnexpectedHolePunchPacket {
hole_punch: HolePunchPacketData {
data: hp,
},
});
}
}
Err(e) => {
if warnings.len() < 10 {
warnings.push(UdpPunchHoleWarning::InvalidHolePunchPacket {
err: e,
});
}
}
};
}
}
WErr(UdpPunchHoleError::TimedOut)
}
}
pub fn filter_udp_hole_punch_packet(data: &[u8]) -> Option<&[u8]> {
match deserialise::<HolePunch>(data){
Ok(_) => None,
_ => Some(data),
}
}
#[cfg(test)]
mod tests {
use std::sync::mpsc;
use std::thread;
use std::time::{Instant, Duration};
use rand;
use mapping_context::MappingContext;
use mapped_udp_socket::MappedUdpSocket;
use punched_udp_socket::{PunchedUdpSocket, filter_udp_hole_punch_packet};
use rendezvous_info::gen_rendezvous_info;
#[test]
fn two_peers_udp_hole_punch_over_loopback() {
let deadline = Instant::now() + Duration::from_secs(3);
let mapping_context = unwrap_result!(MappingContext::new().result_discard());
let mapped_socket_0 = unwrap_result!(MappedUdpSocket::new(&mapping_context, deadline).result_discard());
let mapped_socket_1 = unwrap_result!(MappedUdpSocket::new(&mapping_context, deadline).result_discard());
let socket_0 = mapped_socket_0.socket;
let socket_1 = mapped_socket_1.socket;
let (priv_info_0, pub_info_0) = gen_rendezvous_info(mapped_socket_0.endpoints);
let (priv_info_1, pub_info_1) = gen_rendezvous_info(mapped_socket_1.endpoints);
let (tx_0, rx_0) = mpsc::channel();
let (tx_1, rx_1) = mpsc::channel();
let deadline = Instant::now() + Duration::from_secs(3);
let jh_0 = thread!("two_peers_hole_punch_over_loopback punch socket 0", move || {
let res = PunchedUdpSocket::punch_hole(socket_0,
priv_info_0,
pub_info_1,
deadline);
unwrap_result!(tx_0.send(res));
});
let jh_1 = thread!("two_peers_hole_punch_over_loopback punch socket 1", move || {
let res = PunchedUdpSocket::punch_hole(socket_1,
priv_info_1,
pub_info_0,
deadline);
unwrap_result!(tx_1.send(res));
});
thread::sleep(Duration::from_millis(500));
let punched_socket_0 = unwrap_result!(unwrap_result!(rx_0.try_recv()).result_discard());
let punched_socket_1 = unwrap_result!(unwrap_result!(rx_1.try_recv()).result_discard());
const DATA_LEN: usize = 8;
let data_send: [u8; DATA_LEN] = rand::random();
let mut data_recv;
data_recv = [0u8; 1024];
let n = unwrap_result!(punched_socket_0.socket.send_to(&data_send[..], &*punched_socket_0.peer_addr));
assert_eq!(n, DATA_LEN);
loop {
let (n, _) = unwrap_result!(punched_socket_1.socket.recv_from(&mut data_recv[..]));
match filter_udp_hole_punch_packet(&data_recv[..n]) {
Some(d) => {
assert_eq!(data_send, d);
break;
},
None => continue,
}
}
data_recv = [0u8; 1024];
let n = unwrap_result!(punched_socket_1.socket.send_to(&data_send[..], &*punched_socket_1.peer_addr));
assert_eq!(n, DATA_LEN);
loop {
let (n, _) = unwrap_result!(punched_socket_0.socket.recv_from(&mut data_recv[..]));
match filter_udp_hole_punch_packet(&data_recv[..n]) {
Some(d) => {
assert_eq!(data_send, d);
break;
},
None => continue,
}
}
unwrap_result!(jh_0.join());
unwrap_result!(jh_1.join());
}
}