pub mod errors;
pub mod handshake;
pub mod rate_limiter;
mod session;
mod timers;
use crate::noise::errors::WireGuardError;
use crate::noise::handshake::Handshake;
use crate::noise::rate_limiter::RateLimiter;
use crate::noise::timers::{TimerName, Timers};
use crate::x25519;
use std::collections::VecDeque;
use std::convert::{TryFrom, TryInto};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::sync::Arc;
use std::time::Duration;
const PEER_HANDSHAKE_RATE_LIMIT: u64 = 10;
const IPV4_MIN_HEADER_SIZE: usize = 20;
const IPV4_LEN_OFF: usize = 2;
const IPV4_SRC_IP_OFF: usize = 12;
const IPV4_DST_IP_OFF: usize = 16;
const IPV4_IP_SZ: usize = 4;
const IPV6_MIN_HEADER_SIZE: usize = 40;
const IPV6_LEN_OFF: usize = 4;
const IPV6_SRC_IP_OFF: usize = 8;
const IPV6_DST_IP_OFF: usize = 24;
const IPV6_IP_SZ: usize = 16;
const IP_LEN_SZ: usize = 2;
const MAX_QUEUE_DEPTH: usize = 256;
const N_SESSIONS: usize = 8;
#[derive(Debug)]
pub enum TunnResult<'a> {
Done,
Err(WireGuardError),
WriteToNetwork(&'a mut [u8]),
WriteToTunnelV4(&'a mut [u8], Ipv4Addr),
WriteToTunnelV6(&'a mut [u8], Ipv6Addr),
}
impl<'a> From<WireGuardError> for TunnResult<'a> {
fn from(err: WireGuardError) -> TunnResult<'a> {
TunnResult::Err(err)
}
}
pub struct Tunn {
handshake: handshake::Handshake,
sessions: [Option<session::Session>; N_SESSIONS],
current: usize,
packet_queue: VecDeque<Vec<u8>>,
timers: timers::Timers,
tx_bytes: usize,
rx_bytes: usize,
rate_limiter: Arc<RateLimiter>,
}
type MessageType = u32;
const HANDSHAKE_INIT: MessageType = 1;
const HANDSHAKE_RESP: MessageType = 2;
const COOKIE_REPLY: MessageType = 3;
const DATA: MessageType = 4;
const HANDSHAKE_INIT_SZ: usize = 148;
const HANDSHAKE_RESP_SZ: usize = 92;
const COOKIE_REPLY_SZ: usize = 64;
const DATA_OVERHEAD_SZ: usize = 32;
#[derive(Debug)]
pub struct HandshakeInit<'a> {
sender_idx: u32,
unencrypted_ephemeral: &'a [u8; 32],
encrypted_static: &'a [u8],
encrypted_timestamp: &'a [u8],
}
#[derive(Debug)]
pub struct HandshakeResponse<'a> {
sender_idx: u32,
pub receiver_idx: u32,
unencrypted_ephemeral: &'a [u8; 32],
encrypted_nothing: &'a [u8],
}
#[derive(Debug)]
pub struct PacketCookieReply<'a> {
pub receiver_idx: u32,
nonce: &'a [u8],
encrypted_cookie: &'a [u8],
}
#[derive(Debug)]
pub struct PacketData<'a> {
pub receiver_idx: u32,
counter: u64,
encrypted_encapsulated_packet: &'a [u8],
}
#[derive(Debug)]
pub enum Packet<'a> {
HandshakeInit(HandshakeInit<'a>),
HandshakeResponse(HandshakeResponse<'a>),
PacketCookieReply(PacketCookieReply<'a>),
PacketData(PacketData<'a>),
}
impl Tunn {
#[inline(always)]
pub fn parse_incoming_packet(src: &[u8]) -> Result<Packet, WireGuardError> {
if src.len() < 4 {
return Err(WireGuardError::InvalidPacket);
}
let packet_type = u32::from_le_bytes(src[0..4].try_into().unwrap());
Ok(match (packet_type, src.len()) {
(HANDSHAKE_INIT, HANDSHAKE_INIT_SZ) => Packet::HandshakeInit(HandshakeInit {
sender_idx: u32::from_le_bytes(src[4..8].try_into().unwrap()),
unencrypted_ephemeral: <&[u8; 32] as TryFrom<&[u8]>>::try_from(&src[8..40])
.expect("length already checked above"),
encrypted_static: &src[40..88],
encrypted_timestamp: &src[88..116],
}),
(HANDSHAKE_RESP, HANDSHAKE_RESP_SZ) => Packet::HandshakeResponse(HandshakeResponse {
sender_idx: u32::from_le_bytes(src[4..8].try_into().unwrap()),
receiver_idx: u32::from_le_bytes(src[8..12].try_into().unwrap()),
unencrypted_ephemeral: <&[u8; 32] as TryFrom<&[u8]>>::try_from(&src[12..44])
.expect("length already checked above"),
encrypted_nothing: &src[44..60],
}),
(COOKIE_REPLY, COOKIE_REPLY_SZ) => Packet::PacketCookieReply(PacketCookieReply {
receiver_idx: u32::from_le_bytes(src[4..8].try_into().unwrap()),
nonce: &src[8..32],
encrypted_cookie: &src[32..64],
}),
(DATA, DATA_OVERHEAD_SZ..=std::usize::MAX) => Packet::PacketData(PacketData {
receiver_idx: u32::from_le_bytes(src[4..8].try_into().unwrap()),
counter: u64::from_le_bytes(src[8..16].try_into().unwrap()),
encrypted_encapsulated_packet: &src[16..],
}),
_ => return Err(WireGuardError::InvalidPacket),
})
}
pub fn is_expired(&self) -> bool {
self.handshake.is_expired()
}
pub fn dst_address(packet: &[u8]) -> Option<IpAddr> {
if packet.is_empty() {
return None;
}
match packet[0] >> 4 {
4 if packet.len() >= IPV4_MIN_HEADER_SIZE => {
let addr_bytes: [u8; IPV4_IP_SZ] = packet
[IPV4_DST_IP_OFF..IPV4_DST_IP_OFF + IPV4_IP_SZ]
.try_into()
.unwrap();
Some(IpAddr::from(addr_bytes))
}
6 if packet.len() >= IPV6_MIN_HEADER_SIZE => {
let addr_bytes: [u8; IPV6_IP_SZ] = packet
[IPV6_DST_IP_OFF..IPV6_DST_IP_OFF + IPV6_IP_SZ]
.try_into()
.unwrap();
Some(IpAddr::from(addr_bytes))
}
_ => None,
}
}
pub fn new(
static_private: x25519::StaticSecret,
peer_static_public: x25519::PublicKey,
preshared_key: Option<[u8; 32]>,
persistent_keepalive: Option<u16>,
index: u32,
rate_limiter: Option<Arc<RateLimiter>>,
) -> Self {
let static_public = x25519::PublicKey::from(&static_private);
Tunn {
handshake: Handshake::new(
static_private,
static_public,
peer_static_public,
index << 8,
preshared_key,
),
sessions: Default::default(),
current: Default::default(),
tx_bytes: Default::default(),
rx_bytes: Default::default(),
packet_queue: VecDeque::new(),
timers: Timers::new(persistent_keepalive, rate_limiter.is_none()),
rate_limiter: rate_limiter.unwrap_or_else(|| {
Arc::new(RateLimiter::new(&static_public, PEER_HANDSHAKE_RATE_LIMIT))
}),
}
}
pub fn set_static_private(
&mut self,
static_private: x25519::StaticSecret,
static_public: x25519::PublicKey,
rate_limiter: Option<Arc<RateLimiter>>,
) {
self.timers.should_reset_rr = rate_limiter.is_none();
self.rate_limiter = rate_limiter.unwrap_or_else(|| {
Arc::new(RateLimiter::new(&static_public, PEER_HANDSHAKE_RATE_LIMIT))
});
self.handshake
.set_static_private(static_private, static_public);
for s in &mut self.sessions {
*s = None;
}
}
pub fn encapsulate<'a>(&mut self, src: &[u8], dst: &'a mut [u8]) -> TunnResult<'a> {
let current = self.current;
if let Some(ref session) = self.sessions[current % N_SESSIONS] {
let packet = session.format_packet_data(src, dst);
self.timer_tick(TimerName::TimeLastPacketSent);
if !src.is_empty() {
self.timer_tick(TimerName::TimeLastDataPacketSent);
}
self.tx_bytes += src.len();
return TunnResult::WriteToNetwork(packet);
}
self.queue_packet(src);
self.format_handshake_initiation(dst, false)
}
pub fn decapsulate<'a>(
&mut self,
src_addr: Option<IpAddr>,
datagram: &[u8],
dst: &'a mut [u8],
) -> TunnResult<'a> {
if datagram.is_empty() {
return self.send_queued_packet(dst);
}
let mut cookie = [0u8; COOKIE_REPLY_SZ];
let packet = match self
.rate_limiter
.verify_packet(src_addr, datagram, &mut cookie)
{
Ok(packet) => packet,
Err(TunnResult::WriteToNetwork(cookie)) => {
dst[..cookie.len()].copy_from_slice(cookie);
return TunnResult::WriteToNetwork(&mut dst[..cookie.len()]);
}
Err(TunnResult::Err(e)) => return TunnResult::Err(e),
_ => unreachable!(),
};
self.handle_verified_packet(packet, dst)
}
pub(crate) fn handle_verified_packet<'a>(
&mut self,
packet: Packet,
dst: &'a mut [u8],
) -> TunnResult<'a> {
match packet {
Packet::HandshakeInit(p) => self.handle_handshake_init(p, dst),
Packet::HandshakeResponse(p) => self.handle_handshake_response(p, dst),
Packet::PacketCookieReply(p) => self.handle_cookie_reply(p),
Packet::PacketData(p) => self.handle_data(p, dst),
}
.unwrap_or_else(TunnResult::from)
}
fn handle_handshake_init<'a>(
&mut self,
p: HandshakeInit,
dst: &'a mut [u8],
) -> Result<TunnResult<'a>, WireGuardError> {
tracing::debug!(
message = "Received handshake_initiation",
remote_idx = p.sender_idx
);
let (packet, session) = self.handshake.receive_handshake_initialization(p, dst)?;
let index = session.local_index();
self.sessions[index % N_SESSIONS] = Some(session);
self.timer_tick(TimerName::TimeLastPacketReceived);
self.timer_tick(TimerName::TimeLastPacketSent);
self.timer_tick_session_established(false, index);
tracing::debug!(message = "Sending handshake_response", local_idx = index);
Ok(TunnResult::WriteToNetwork(packet))
}
fn handle_handshake_response<'a>(
&mut self,
p: HandshakeResponse,
dst: &'a mut [u8],
) -> Result<TunnResult<'a>, WireGuardError> {
tracing::debug!(
message = "Received handshake_response",
local_idx = p.receiver_idx,
remote_idx = p.sender_idx
);
let session = self.handshake.receive_handshake_response(p)?;
let keepalive_packet = session.format_packet_data(&[], dst);
let l_idx = session.local_index();
let index = l_idx % N_SESSIONS;
self.sessions[index] = Some(session);
self.timer_tick(TimerName::TimeLastPacketReceived);
self.timer_tick_session_established(true, index); self.set_current_session(l_idx);
tracing::debug!("Sending keepalive");
Ok(TunnResult::WriteToNetwork(keepalive_packet)) }
fn handle_cookie_reply<'a>(
&mut self,
p: PacketCookieReply,
) -> Result<TunnResult<'a>, WireGuardError> {
tracing::debug!(
message = "Received cookie_reply",
local_idx = p.receiver_idx
);
self.handshake.receive_cookie_reply(p)?;
self.timer_tick(TimerName::TimeLastPacketReceived);
self.timer_tick(TimerName::TimeCookieReceived);
tracing::debug!("Did set cookie");
Ok(TunnResult::Done)
}
fn set_current_session(&mut self, new_idx: usize) {
let cur_idx = self.current;
if cur_idx == new_idx {
return;
}
if self.sessions[cur_idx % N_SESSIONS].is_none()
|| self.timers.session_timers[new_idx % N_SESSIONS]
>= self.timers.session_timers[cur_idx % N_SESSIONS]
{
self.current = new_idx;
tracing::debug!(message = "New session", session = new_idx);
}
}
fn handle_data<'a>(
&mut self,
packet: PacketData,
dst: &'a mut [u8],
) -> Result<TunnResult<'a>, WireGuardError> {
let r_idx = packet.receiver_idx as usize;
let idx = r_idx % N_SESSIONS;
let decapsulated_packet = {
let session = self.sessions[idx].as_ref();
let session = session.ok_or_else(|| {
tracing::trace!(message = "No current session available", remote_idx = r_idx);
WireGuardError::NoCurrentSession
})?;
session.receive_packet_data(packet, dst)?
};
self.set_current_session(r_idx);
self.timer_tick(TimerName::TimeLastPacketReceived);
Ok(self.validate_decapsulated_packet(decapsulated_packet))
}
pub fn format_handshake_initiation<'a>(
&mut self,
dst: &'a mut [u8],
force_resend: bool,
) -> TunnResult<'a> {
if self.handshake.is_in_progress() && !force_resend {
return TunnResult::Done;
}
if self.handshake.is_expired() {
self.timers.clear();
}
let starting_new_handshake = !self.handshake.is_in_progress();
match self.handshake.format_handshake_initiation(dst) {
Ok(packet) => {
tracing::debug!("Sending handshake_initiation");
if starting_new_handshake {
self.timer_tick(TimerName::TimeLastHandshakeStarted);
}
self.timer_tick(TimerName::TimeLastPacketSent);
TunnResult::WriteToNetwork(packet)
}
Err(e) => TunnResult::Err(e),
}
}
fn validate_decapsulated_packet<'a>(&mut self, packet: &'a mut [u8]) -> TunnResult<'a> {
let (computed_len, src_ip_address) = match packet.len() {
0 => return TunnResult::Done, _ if packet[0] >> 4 == 4 && packet.len() >= IPV4_MIN_HEADER_SIZE => {
let len_bytes: [u8; IP_LEN_SZ] = packet[IPV4_LEN_OFF..IPV4_LEN_OFF + IP_LEN_SZ]
.try_into()
.unwrap();
let addr_bytes: [u8; IPV4_IP_SZ] = packet
[IPV4_SRC_IP_OFF..IPV4_SRC_IP_OFF + IPV4_IP_SZ]
.try_into()
.unwrap();
(
u16::from_be_bytes(len_bytes) as usize,
IpAddr::from(addr_bytes),
)
}
_ if packet[0] >> 4 == 6 && packet.len() >= IPV6_MIN_HEADER_SIZE => {
let len_bytes: [u8; IP_LEN_SZ] = packet[IPV6_LEN_OFF..IPV6_LEN_OFF + IP_LEN_SZ]
.try_into()
.unwrap();
let addr_bytes: [u8; IPV6_IP_SZ] = packet
[IPV6_SRC_IP_OFF..IPV6_SRC_IP_OFF + IPV6_IP_SZ]
.try_into()
.unwrap();
(
u16::from_be_bytes(len_bytes) as usize + IPV6_MIN_HEADER_SIZE,
IpAddr::from(addr_bytes),
)
}
_ => return TunnResult::Err(WireGuardError::InvalidPacket),
};
if computed_len > packet.len() {
return TunnResult::Err(WireGuardError::InvalidPacket);
}
self.timer_tick(TimerName::TimeLastDataPacketReceived);
self.rx_bytes += computed_len;
match src_ip_address {
IpAddr::V4(addr) => TunnResult::WriteToTunnelV4(&mut packet[..computed_len], addr),
IpAddr::V6(addr) => TunnResult::WriteToTunnelV6(&mut packet[..computed_len], addr),
}
}
fn send_queued_packet<'a>(&mut self, dst: &'a mut [u8]) -> TunnResult<'a> {
if let Some(packet) = self.dequeue_packet() {
match self.encapsulate(&packet, dst) {
TunnResult::Err(_) => {
self.requeue_packet(packet);
}
r => return r,
}
}
TunnResult::Done
}
fn queue_packet(&mut self, packet: &[u8]) {
if self.packet_queue.len() < MAX_QUEUE_DEPTH {
self.packet_queue.push_back(packet.to_vec());
}
}
fn requeue_packet(&mut self, packet: Vec<u8>) {
if self.packet_queue.len() < MAX_QUEUE_DEPTH {
self.packet_queue.push_front(packet);
}
}
fn dequeue_packet(&mut self) -> Option<Vec<u8>> {
self.packet_queue.pop_front()
}
fn estimate_loss(&self) -> f32 {
let session_idx = self.current;
let mut weight = 9.0;
let mut cur_avg = 0.0;
let mut total_weight = 0.0;
for i in 0..N_SESSIONS {
if let Some(ref session) = self.sessions[(session_idx.wrapping_sub(i)) % N_SESSIONS] {
let (expected, received) = session.current_packet_cnt();
let loss = if expected == 0 {
0.0
} else {
1.0 - received as f32 / expected as f32
};
cur_avg += loss * weight;
total_weight += weight;
weight /= 3.0;
}
}
if total_weight == 0.0 {
0.0
} else {
cur_avg / total_weight
}
}
pub fn stats(&self) -> (Option<Duration>, usize, usize, f32, Option<u32>) {
let time = self.time_since_last_handshake();
let tx_bytes = self.tx_bytes;
let rx_bytes = self.rx_bytes;
let loss = self.estimate_loss();
let rtt = self.handshake.last_rtt;
(time, tx_bytes, rx_bytes, loss, rtt)
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "mock-instant")]
use crate::noise::timers::{REKEY_AFTER_TIME, REKEY_TIMEOUT};
use super::*;
use rand_core::{OsRng, RngCore};
fn create_two_tuns() -> (Tunn, Tunn) {
let my_secret_key = x25519_dalek::StaticSecret::random_from_rng(OsRng);
let my_public_key = x25519_dalek::PublicKey::from(&my_secret_key);
let my_idx = OsRng.next_u32();
let their_secret_key = x25519_dalek::StaticSecret::random_from_rng(OsRng);
let their_public_key = x25519_dalek::PublicKey::from(&their_secret_key);
let their_idx = OsRng.next_u32();
let my_tun = Tunn::new(my_secret_key, their_public_key, None, None, my_idx, None);
let their_tun = Tunn::new(their_secret_key, my_public_key, None, None, their_idx, None);
(my_tun, their_tun)
}
fn create_handshake_init(tun: &mut Tunn) -> Vec<u8> {
let mut dst = vec![0u8; 2048];
let handshake_init = tun.format_handshake_initiation(&mut dst, false);
assert!(matches!(handshake_init, TunnResult::WriteToNetwork(_)));
let handshake_init = if let TunnResult::WriteToNetwork(sent) = handshake_init {
sent
} else {
unreachable!();
};
handshake_init.into()
}
fn create_handshake_response(tun: &mut Tunn, handshake_init: &[u8]) -> Vec<u8> {
let mut dst = vec![0u8; 2048];
let handshake_resp = tun.decapsulate(None, handshake_init, &mut dst);
assert!(matches!(handshake_resp, TunnResult::WriteToNetwork(_)));
let handshake_resp = if let TunnResult::WriteToNetwork(sent) = handshake_resp {
sent
} else {
unreachable!();
};
handshake_resp.into()
}
fn parse_handshake_resp(tun: &mut Tunn, handshake_resp: &[u8]) -> Vec<u8> {
let mut dst = vec![0u8; 2048];
let keepalive = tun.decapsulate(None, handshake_resp, &mut dst);
assert!(matches!(keepalive, TunnResult::WriteToNetwork(_)));
let keepalive = if let TunnResult::WriteToNetwork(sent) = keepalive {
sent
} else {
unreachable!();
};
keepalive.into()
}
fn parse_keepalive(tun: &mut Tunn, keepalive: &[u8]) {
let mut dst = vec![0u8; 2048];
let keepalive = tun.decapsulate(None, keepalive, &mut dst);
assert!(matches!(keepalive, TunnResult::Done));
}
fn create_two_tuns_and_handshake() -> (Tunn, Tunn) {
let (mut my_tun, mut their_tun) = create_two_tuns();
let init = create_handshake_init(&mut my_tun);
let resp = create_handshake_response(&mut their_tun, &init);
let keepalive = parse_handshake_resp(&mut my_tun, &resp);
parse_keepalive(&mut their_tun, &keepalive);
(my_tun, their_tun)
}
fn create_ipv4_udp_packet() -> Vec<u8> {
let header =
etherparse::PacketBuilder::ipv4([192, 168, 1, 2], [192, 168, 1, 3], 5).udp(5678, 23);
let payload = [0, 1, 2, 3];
let mut packet = Vec::<u8>::with_capacity(header.size(payload.len()));
header.write(&mut packet, &payload).unwrap();
packet
}
#[cfg(feature = "mock-instant")]
fn update_timer_results_in_handshake(tun: &mut Tunn) {
let mut dst = vec![0u8; 2048];
let result = tun.update_timers(&mut dst);
assert!(matches!(result, TunnResult::WriteToNetwork(_)));
let packet_data = if let TunnResult::WriteToNetwork(data) = result {
data
} else {
unreachable!();
};
let packet = Tunn::parse_incoming_packet(packet_data).unwrap();
assert!(matches!(packet, Packet::HandshakeInit(_)));
}
#[test]
fn create_two_tunnels_linked_to_eachother() {
let (_my_tun, _their_tun) = create_two_tuns();
}
#[test]
fn handshake_init() {
let (mut my_tun, _their_tun) = create_two_tuns();
let init = create_handshake_init(&mut my_tun);
let packet = Tunn::parse_incoming_packet(&init).unwrap();
assert!(matches!(packet, Packet::HandshakeInit(_)));
}
#[test]
fn handshake_init_and_response() {
let (mut my_tun, mut their_tun) = create_two_tuns();
let init = create_handshake_init(&mut my_tun);
let resp = create_handshake_response(&mut their_tun, &init);
let packet = Tunn::parse_incoming_packet(&resp).unwrap();
assert!(matches!(packet, Packet::HandshakeResponse(_)));
}
#[test]
fn full_handshake() {
let (mut my_tun, mut their_tun) = create_two_tuns();
let init = create_handshake_init(&mut my_tun);
let resp = create_handshake_response(&mut their_tun, &init);
let keepalive = parse_handshake_resp(&mut my_tun, &resp);
let packet = Tunn::parse_incoming_packet(&keepalive).unwrap();
assert!(matches!(packet, Packet::PacketData(_)));
}
#[test]
fn full_handshake_plus_timers() {
let (mut my_tun, mut their_tun) = create_two_tuns_and_handshake();
assert!(matches!(my_tun.update_timers(&mut []), TunnResult::Done));
assert!(matches!(their_tun.update_timers(&mut []), TunnResult::Done));
}
#[test]
#[cfg(feature = "mock-instant")]
fn new_handshake_after_two_mins() {
let (mut my_tun, mut their_tun) = create_two_tuns_and_handshake();
let mut my_dst = [0u8; 1024];
mock_instant::MockClock::advance(Duration::from_secs(1));
assert!(matches!(their_tun.update_timers(&mut []), TunnResult::Done));
assert!(matches!(
my_tun.update_timers(&mut my_dst),
TunnResult::Done
));
let sent_packet_buf = create_ipv4_udp_packet();
let data = my_tun.encapsulate(&sent_packet_buf, &mut my_dst);
assert!(matches!(data, TunnResult::WriteToNetwork(_)));
mock_instant::MockClock::advance(REKEY_AFTER_TIME);
assert!(matches!(their_tun.update_timers(&mut []), TunnResult::Done));
update_timer_results_in_handshake(&mut my_tun);
}
#[test]
#[cfg(feature = "mock-instant")]
fn handshake_no_resp_rekey_timeout() {
let (mut my_tun, _their_tun) = create_two_tuns();
let init = create_handshake_init(&mut my_tun);
let packet = Tunn::parse_incoming_packet(&init).unwrap();
assert!(matches!(packet, Packet::HandshakeInit(_)));
mock_instant::MockClock::advance(REKEY_TIMEOUT);
update_timer_results_in_handshake(&mut my_tun)
}
#[test]
fn one_ip_packet() {
let (mut my_tun, mut their_tun) = create_two_tuns_and_handshake();
let mut my_dst = [0u8; 1024];
let mut their_dst = [0u8; 1024];
let sent_packet_buf = create_ipv4_udp_packet();
let data = my_tun.encapsulate(&sent_packet_buf, &mut my_dst);
assert!(matches!(data, TunnResult::WriteToNetwork(_)));
let data = if let TunnResult::WriteToNetwork(sent) = data {
sent
} else {
unreachable!();
};
let data = their_tun.decapsulate(None, data, &mut their_dst);
assert!(matches!(data, TunnResult::WriteToTunnelV4(..)));
let recv_packet_buf = if let TunnResult::WriteToTunnelV4(recv, _addr) = data {
recv
} else {
unreachable!();
};
assert_eq!(sent_packet_buf, recv_packet_buf);
}
}