refactor(core): separate portable core from native runtime (#2451)

Create easytier-core as the portable owner of configuration,
connectivity, tunnels, peer and routing state, gateways, management,
the data plane, and instance lifecycle. Keep operating-system
integration, native protocol engines, process startup, and presentation
in easytier behind explicit Host capability adapters.

Create easytier-proto to own schemas, generated RPC types, descriptors,
and feature-scoped protocol slices. Remove runtime protobuf reflection
from core while preserving unknown route-peer fields across forwarding.

Normalize instance construction through CoreInstance, CoreHostAdapters,
CoreProcessRuntime, and InstanceManager. Make the runtime config store
the only authoritative mutable configuration after startup.

Move the portable TCP/UDP data plane into core and extract a generic
OperationBroker for completion, cancellation, disposal, and capacity
accounting. Expose the session-based FFI v2 completion API and keep the
WASI guest ABI, wire schemas, and adapters with core.

Migrate CLI, GUI, web, FFI, Android JNI, OHOS, uptime, and mobile
consumers to the shared manager and core state. Add explicit user/web
config ownership and revision-aware web reconciliation.

Preserve configuration, wire, and management behavior while fixing
regressions discovered by the full platform and integration matrix:

- inherit advertised relay capabilities in foreign networks;
- refresh OSPF peer state immediately after runtime config changes;
- restore CLI GlobalCtx event output without forcing GUI logging;
- retain legacy encryption names and standalone RPC tunnel metadata;
- restore ICMP host composition and fragmented UDP handling;
- use portable 64-bit atomics on 32-bit MIPS targets; and
- retain discarded operations until late cancellation completes.

Validate the refactor across 45 GitHub checks, including Linux, macOS,
Windows, FreeBSD, web, GUI, Android, OHOS, feature profiles, and
three-node and subnet-proxy integration tests.

BREAKING CHANGE: internal Rust module paths are not preserved. Legacy
native data-plane APIs are replaced by the session-based FFI v2 API.
The dedicated Android data-plane wrapper is removed.
This commit is contained in:
KKRainbow
2026-07-26 15:41:55 +08:00
committed by GitHub
parent 346f32d3d0
commit 021f523431
523 changed files with 102785 additions and 67067 deletions
+213
View File
@@ -0,0 +1,213 @@
use zerocopy::{AsBytes as _, FromBytes as _};
use super::{COMPRESSOR_TAIL_SIZE, CompressorAlgo, CompressorTail, ZCPacket};
mod zstd;
type Error = anyhow::Error;
#[async_trait::async_trait]
pub trait Compressor {
async fn compress(
&self,
packet: &mut ZCPacket,
compress_algo: CompressorAlgo,
) -> Result<(), Error>;
async fn decompress(&self, packet: &mut ZCPacket) -> Result<(), Error>;
}
pub struct DefaultCompressor {}
impl Default for DefaultCompressor {
fn default() -> Self {
Self::new()
}
}
impl DefaultCompressor {
pub fn new() -> Self {
DefaultCompressor {}
}
pub async fn compress_raw(
&self,
data: &[u8],
compress_algo: CompressorAlgo,
) -> Result<Vec<u8>, Error> {
match compress_algo {
CompressorAlgo::ZstdDefault => zstd::compress(data, compress_algo),
CompressorAlgo::None => Ok(data.to_vec()),
}
}
pub async fn decompress_raw(
&self,
data: &[u8],
compress_algo: CompressorAlgo,
) -> Result<Vec<u8>, Error> {
match compress_algo {
CompressorAlgo::ZstdDefault => zstd::decompress(data, compress_algo),
CompressorAlgo::None => Ok(data.to_vec()),
}
}
}
#[async_trait::async_trait]
impl Compressor for DefaultCompressor {
async fn compress(
&self,
zc_packet: &mut ZCPacket,
compress_algo: CompressorAlgo,
) -> Result<(), Error> {
if matches!(compress_algo, CompressorAlgo::None) {
return Ok(());
}
let pm_header = zc_packet.peer_manager_header().unwrap();
if pm_header.is_compressed() {
return Ok(());
}
let tail = CompressorTail::new(compress_algo);
let buf = self
.compress_raw(zc_packet.payload(), compress_algo)
.await?;
if buf.len() + COMPRESSOR_TAIL_SIZE > pm_header.len.get() as usize {
// Compressed data is larger than original data, don't compress
return Ok(());
}
zc_packet
.mut_peer_manager_header()
.unwrap()
.set_compressed(true);
let payload_offset = zc_packet.payload_offset();
zc_packet.mut_inner().truncate(payload_offset);
zc_packet.mut_inner().extend_from_slice(&buf);
zc_packet.mut_inner().extend_from_slice(tail.as_bytes());
Ok(())
}
async fn decompress(&self, zc_packet: &mut ZCPacket) -> Result<(), Error> {
let pm_header = zc_packet.peer_manager_header().unwrap();
if !pm_header.is_compressed() {
return Ok(());
}
let payload_len = zc_packet.payload().len();
if payload_len < COMPRESSOR_TAIL_SIZE {
return Err(anyhow::anyhow!("Packet too short: {}", payload_len));
}
let text_len = payload_len - COMPRESSOR_TAIL_SIZE;
let tail = CompressorTail::ref_from_suffix(zc_packet.payload())
.unwrap()
.clone();
let algo = tail
.get_algo()
.ok_or(anyhow::anyhow!("Unknown algo: {:?}", tail))?;
let buf = self
.decompress_raw(&zc_packet.payload()[..text_len], algo)
.await?;
if buf.len() != pm_header.len.get() as usize {
anyhow::bail!(
"Decompressed length mismatch: decompressed len {} != pm header len {}",
buf.len(),
pm_header.len.get()
);
}
zc_packet
.mut_peer_manager_header()
.unwrap()
.set_compressed(false);
let payload_offset = zc_packet.payload_offset();
zc_packet.mut_inner().truncate(payload_offset);
zc_packet.mut_inner().extend_from_slice(&buf);
Ok(())
}
}
pub(super) fn zstd_available() -> bool {
zstd::AVAILABLE
}
#[cfg(test)]
pub mod tests {
use super::*;
#[cfg(feature = "zstd")]
#[tokio::test]
async fn test_compress() {
let text = b"12345670000000000000000000";
let mut packet = ZCPacket::new_with_payload(text);
packet.fill_peer_manager_hdr(0, 0, 0);
let compressor = DefaultCompressor {};
println!(
"Uncompressed packet: {:?}, len: {}",
packet,
packet.payload_len()
);
compressor
.compress(&mut packet, CompressorAlgo::ZstdDefault)
.await
.unwrap();
println!(
"Compressed packet: {:?}, len: {}",
packet,
packet.payload_len()
);
assert!(packet.peer_manager_header().unwrap().is_compressed());
compressor.decompress(&mut packet).await.unwrap();
assert_eq!(packet.payload(), text);
assert!(!packet.peer_manager_header().unwrap().is_compressed());
}
#[cfg(feature = "zstd")]
#[tokio::test]
async fn test_short_text_compress() {
let text = b"1234";
let mut packet = ZCPacket::new_with_payload(text);
packet.fill_peer_manager_hdr(0, 0, 0);
let compressor = DefaultCompressor {};
// short text can't be compressed
compressor
.compress(&mut packet, CompressorAlgo::ZstdDefault)
.await
.unwrap();
assert!(!packet.peer_manager_header().unwrap().is_compressed());
compressor.decompress(&mut packet).await.unwrap();
assert_eq!(packet.payload(), text);
assert!(!packet.peer_manager_header().unwrap().is_compressed());
}
#[cfg(not(feature = "zstd"))]
#[tokio::test]
async fn unavailable_zstd_returns_an_explicit_error() {
let error = DefaultCompressor::new()
.compress_raw(b"payload", CompressorAlgo::ZstdDefault)
.await
.unwrap_err();
assert_eq!(
error.to_string(),
"compression algorithm is unavailable in this build: ZstdDefault"
);
}
}
@@ -0,0 +1,76 @@
#[cfg(feature = "zstd")]
use std::cell::RefCell;
#[cfg(feature = "zstd")]
use anyhow::Context as _;
#[cfg(feature = "zstd")]
use dashmap::DashMap;
#[cfg(feature = "zstd")]
use zstd::bulk;
use super::CompressorAlgo;
#[cfg(feature = "zstd")]
pub(super) const AVAILABLE: bool = true;
#[cfg(not(feature = "zstd"))]
pub(super) const AVAILABLE: bool = false;
#[cfg(feature = "zstd")]
thread_local! {
static CTX_MAP: RefCell<DashMap<CompressorAlgo, bulk::Compressor<'static>>> =
RefCell::new(DashMap::new());
static DCTX_MAP: RefCell<DashMap<CompressorAlgo, bulk::Decompressor<'static>>> =
RefCell::new(DashMap::new());
}
#[cfg(feature = "zstd")]
pub(super) fn compress(data: &[u8], compress_algo: CompressorAlgo) -> anyhow::Result<Vec<u8>> {
CTX_MAP.with(|map_cell| {
let map = map_cell.borrow();
let mut ctx_entry = map.entry(compress_algo).or_default();
ctx_entry.compress(data).with_context(|| {
format!(
"Failed to compress data with algorithm: {:?}",
compress_algo
)
})
})
}
#[cfg(not(feature = "zstd"))]
pub(super) fn compress(_data: &[u8], compress_algo: CompressorAlgo) -> anyhow::Result<Vec<u8>> {
unavailable(compress_algo)
}
#[cfg(feature = "zstd")]
pub(super) fn decompress(data: &[u8], compress_algo: CompressorAlgo) -> anyhow::Result<Vec<u8>> {
DCTX_MAP.with(|map_cell| {
let map = map_cell.borrow();
let mut ctx_entry = map.entry(compress_algo).or_default();
for i in 1..=5 {
let mut len = data.len() * 2usize.pow(i);
if i == 5 && len < 64 * 1024 {
len = 64 * 1024;
}
match ctx_entry.decompress(data, len) {
Ok(buf) => return Ok(buf),
Err(error) if error.to_string().contains("buffer is too small") => continue,
Err(error) => return Err(error.into()),
}
}
Err(anyhow::anyhow!(
"Failed to decompress data after multiple attempts with algorithm: {:?}",
compress_algo
))
})
}
#[cfg(not(feature = "zstd"))]
pub(super) fn decompress(_data: &[u8], compress_algo: CompressorAlgo) -> anyhow::Result<Vec<u8>> {
unavailable(compress_algo)
}
#[cfg(not(feature = "zstd"))]
fn unavailable(compress_algo: CompressorAlgo) -> anyhow::Result<Vec<u8>> {
Err(super::super::CompressionUnavailableError(compress_algo).into())
}
+79
View File
@@ -0,0 +1,79 @@
use bytes::BytesMut;
use rand::{Rng, SeedableRng};
use zerocopy::FromBytes as _;
use super::{UDP_TUNNEL_HEADER_SIZE, UDPTunnelHeader, UdpPacketType, ZCPacket, ZCPacketType};
pub(crate) const HOLE_PUNCH_PACKET_BODY_LEN: u16 = 16;
fn new_udp_packet<F>(f: F, udp_body: &[u8]) -> ZCPacket
where
F: FnOnce(&mut UDPTunnelHeader),
{
let mut buf = BytesMut::new();
buf.resize(UDP_TUNNEL_HEADER_SIZE + udp_body.len(), 0);
buf[UDP_TUNNEL_HEADER_SIZE..].copy_from_slice(udp_body);
let mut ret = ZCPacket::new_from_buf(buf, ZCPacketType::UDP);
let header = ret.mut_udp_tunnel_header().unwrap();
f(header);
ret
}
pub(crate) fn new_hole_punch_packet(tid: u32, buf_len: u16) -> ZCPacket {
let mut rng = rand::rngs::StdRng::from_entropy();
let mut buf = vec![0u8; buf_len as usize];
rng.fill(&mut buf[..]);
new_udp_packet(
|header| {
header.msg_type = UdpPacketType::HolePunch as u8;
header.conn_id.set(tid);
header.len.set(buf_len);
},
&buf,
)
}
pub(crate) fn hole_punch_packet_tid(data: &[u8], body_len: u16) -> Option<u32> {
if data.len() != UDP_TUNNEL_HEADER_SIZE + body_len as usize {
return None;
}
let header = UDPTunnelHeader::ref_from_prefix(data)?;
let valid = header.msg_type == UdpPacketType::HolePunch as u8 && header.len.get() == body_len;
valid.then(|| header.conn_id.get())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builds_and_parses_hole_punch_packet() {
let tid = 0x1234_5678;
let packet = new_hole_punch_packet(tid, HOLE_PUNCH_PACKET_BODY_LEN);
let bytes = packet.into_bytes();
assert_eq!(
bytes.len(),
UDP_TUNNEL_HEADER_SIZE + HOLE_PUNCH_PACKET_BODY_LEN as usize
);
assert_eq!(
hole_punch_packet_tid(&bytes, HOLE_PUNCH_PACKET_BODY_LEN),
Some(tid)
);
}
#[test]
fn rejects_non_matching_hole_punch_packet_length() {
let packet = new_hole_punch_packet(1, HOLE_PUNCH_PACKET_BODY_LEN);
let mut bytes = packet.into_bytes().to_vec();
bytes.pop();
assert_eq!(
hole_punch_packet_tid(&bytes, HOLE_PUNCH_PACKET_BODY_LEN),
None
);
}
}
+922
View File
@@ -0,0 +1,922 @@
pub(crate) mod compressor;
mod hole_punch;
pub mod stun;
pub(crate) use hole_punch::{
HOLE_PUNCH_PACKET_BODY_LEN, hole_punch_packet_tid, new_hole_punch_packet,
};
use bytes::Buf;
use bytes::Bytes;
use bytes::BytesMut;
use easytier_proto::common::CompressionAlgoPb;
use zerocopy::AsBytes;
use zerocopy::FromBytes;
use zerocopy::FromZeroes;
use zerocopy::byteorder::*;
type DefaultEndian = LittleEndian;
const fn max(a: usize, b: usize) -> usize {
[a, b][(a < b) as usize]
}
// TCP TunnelHeader
#[repr(C, packed)]
#[derive(AsBytes, FromBytes, FromZeroes, Clone, Debug, Default)]
pub struct TCPTunnelHeader {
pub len: U32<DefaultEndian>,
}
pub const TCP_TUNNEL_HEADER_SIZE: usize = std::mem::size_of::<TCPTunnelHeader>();
#[derive(AsBytes, FromZeroes, Clone, Debug)]
#[repr(u8)]
pub enum UdpPacketType {
Invalid = 0,
Syn = 1,
Sack = 2,
Data = 3,
Fin = 4,
HolePunch = 5,
V4HolePunch = 6, // when receiving v4 hole punch packet, the packet contains a socket addr of other peer, we
// will send a hole punch packet to that peer. we only accept this packet from loopback interface.
V6HolePunch = 7, // when receiving v6 hole punch packet, the packet contains a socket addr of other peer, we
// will send a hole punch packet to that peer. we only accept this packet from lookback interface.
}
#[repr(C, packed)]
#[derive(AsBytes, FromBytes, FromZeroes, Clone, Debug, Default)]
pub struct V4HolePunchPacket {
pub dst_ipv4: [u8; 4],
pub dst_port: U16<DefaultEndian>,
}
#[repr(C, packed)]
#[derive(AsBytes, FromBytes, FromZeroes, Clone, Debug, Default)]
pub struct V6HolePunchPacket {
pub dst_ipv6: [u8; 16],
pub dst_port: U16<DefaultEndian>,
pub preferred_src_ipv6: [u8; 16],
pub preferred_src_ifindex: U32<DefaultEndian>,
}
#[repr(C, packed)]
#[derive(AsBytes, FromBytes, FromZeroes, Clone, Debug, Default)]
pub struct UDPTunnelHeader {
pub conn_id: U32<DefaultEndian>,
pub msg_type: u8,
pub padding: u8,
pub len: U16<DefaultEndian>,
}
pub const UDP_TUNNEL_HEADER_SIZE: usize = std::mem::size_of::<UDPTunnelHeader>();
#[repr(C, packed)]
#[derive(AsBytes, FromBytes, FromZeroes, Clone, Debug, Default)]
pub struct WGTunnelHeader {
pub ipv4_header: [u8; 20],
}
pub const WG_TUNNEL_HEADER_SIZE: usize = std::mem::size_of::<WGTunnelHeader>();
#[derive(AsBytes, FromZeroes, Copy, Clone, Debug)]
#[repr(u8)]
pub enum PacketType {
Invalid = 0,
Data = 1,
HandShake = 2,
RoutePacket = 3, // deprecated
Ping = 4,
Pong = 5,
TaRpc = 6, // deprecated
Route = 7, // deprecated
RpcReq = 8,
RpcResp = 9,
ForeignNetworkPacket = 10,
KcpSrc = 11,
KcpDst = 12,
QuicSrc = 16,
QuicDst = 17,
NoiseHandshakeMsg1 = 13,
NoiseHandshakeMsg2 = 14,
NoiseHandshakeMsg3 = 15,
RelayHandshake = 20,
RelayHandshakeAck = 21,
// used internally,
DataWithKcpSrcModified = 18,
DataWithQuicSrcModified = 19,
}
bitflags::bitflags! {
struct PeerManagerHeaderFlags: u8 {
const ENCRYPTED = 0b0000_0001;
const LATENCY_FIRST = 0b0000_0010;
const EXIT_NODE = 0b0000_0100;
const NO_PROXY = 0b0000_1000;
const COMPRESSED = 0b0001_0000;
// deprecated flags, can be reused.
// const KCP_SRC_MODIFIED = 0b0010_0000;
// const QUIC_SRC_MODIFIED = 0b1000_0000;
const NOT_SEND_TO_TUN = 0b0100_0000;
const _ = !0;
}
}
#[repr(C, packed)]
#[derive(AsBytes, FromBytes, FromZeroes, Clone, Debug, Default)]
pub struct PeerManagerHeader {
pub from_peer_id: U32<DefaultEndian>,
pub to_peer_id: U32<DefaultEndian>,
pub packet_type: u8,
pub flags: u8,
pub forward_counter: u8,
reserved: u8,
pub len: U32<DefaultEndian>,
}
pub const PEER_MANAGER_HEADER_SIZE: usize = std::mem::size_of::<PeerManagerHeader>();
impl PeerManagerHeader {
pub fn is_encrypted(&self) -> bool {
PeerManagerHeaderFlags::from_bits(self.flags)
.unwrap()
.contains(PeerManagerHeaderFlags::ENCRYPTED)
}
pub fn set_encrypted(&mut self, encrypted: bool) {
let mut flags = PeerManagerHeaderFlags::from_bits(self.flags).unwrap();
if encrypted {
flags.insert(PeerManagerHeaderFlags::ENCRYPTED);
} else {
flags.remove(PeerManagerHeaderFlags::ENCRYPTED);
}
self.flags = flags.bits();
}
pub fn is_latency_first(&self) -> bool {
PeerManagerHeaderFlags::from_bits(self.flags)
.unwrap()
.contains(PeerManagerHeaderFlags::LATENCY_FIRST)
}
pub fn is_exit_node(&self) -> bool {
PeerManagerHeaderFlags::from_bits(self.flags)
.unwrap()
.contains(PeerManagerHeaderFlags::EXIT_NODE)
}
pub fn is_no_proxy(&self) -> bool {
PeerManagerHeaderFlags::from_bits(self.flags)
.unwrap()
.contains(PeerManagerHeaderFlags::NO_PROXY)
}
pub fn is_compressed(&self) -> bool {
PeerManagerHeaderFlags::from_bits(self.flags)
.unwrap()
.contains(PeerManagerHeaderFlags::COMPRESSED)
}
pub fn set_latency_first(&mut self, latency_first: bool) -> &mut Self {
let mut flags = PeerManagerHeaderFlags::from_bits(self.flags).unwrap();
if latency_first {
flags.insert(PeerManagerHeaderFlags::LATENCY_FIRST);
} else {
flags.remove(PeerManagerHeaderFlags::LATENCY_FIRST);
}
self.flags = flags.bits();
self
}
pub fn set_exit_node(&mut self, exit_node: bool) -> &mut Self {
let mut flags = PeerManagerHeaderFlags::from_bits(self.flags).unwrap();
if exit_node {
flags.insert(PeerManagerHeaderFlags::EXIT_NODE);
} else {
flags.remove(PeerManagerHeaderFlags::EXIT_NODE);
}
self.flags = flags.bits();
self
}
pub fn set_no_proxy(&mut self, no_proxy: bool) -> &mut Self {
let mut flags = PeerManagerHeaderFlags::from_bits(self.flags).unwrap();
if no_proxy {
flags.insert(PeerManagerHeaderFlags::NO_PROXY);
} else {
flags.remove(PeerManagerHeaderFlags::NO_PROXY);
}
self.flags = flags.bits();
self
}
pub fn set_compressed(&mut self, compressed: bool) -> &mut Self {
let mut flags = PeerManagerHeaderFlags::from_bits(self.flags).unwrap();
if compressed {
flags.insert(PeerManagerHeaderFlags::COMPRESSED);
} else {
flags.remove(PeerManagerHeaderFlags::COMPRESSED);
}
self.flags = flags.bits();
self
}
pub fn mark_kcp_src_modified(&mut self) -> &mut Self {
assert_eq!(self.packet_type, PacketType::Data as u8);
self.packet_type = PacketType::DataWithKcpSrcModified as u8;
self
}
pub fn mark_quic_src_modified(&mut self) -> &mut Self {
assert_eq!(self.packet_type, PacketType::Data as u8);
self.packet_type = PacketType::DataWithQuicSrcModified as u8;
self
}
pub fn set_not_send_to_tun(&mut self, not_send_to_tun: bool) -> &mut Self {
let mut flags = PeerManagerHeaderFlags::from_bits(self.flags).unwrap();
if not_send_to_tun {
flags.insert(PeerManagerHeaderFlags::NOT_SEND_TO_TUN);
} else {
flags.remove(PeerManagerHeaderFlags::NOT_SEND_TO_TUN);
}
self.flags = flags.bits();
self
}
pub fn is_not_send_to_tun(&self) -> bool {
PeerManagerHeaderFlags::from_bits(self.flags)
.unwrap()
.contains(PeerManagerHeaderFlags::NOT_SEND_TO_TUN)
}
}
#[repr(C, packed)]
#[derive(AsBytes, FromBytes, FromZeroes, Clone, Debug, Default)]
pub struct ForeignNetworkPacketHeader {
pub header_len: U16<DefaultEndian>,
pub dst_peer_id: U32<DefaultEndian>,
pub network_name_offset: U16<DefaultEndian>,
pub network_name_len: U16<DefaultEndian>,
/* variable length network_name string */
}
impl ForeignNetworkPacketHeader {
pub fn new(dst_peer_id: u32, network_name: &str) -> Self {
let network_name_offset = std::mem::size_of::<ForeignNetworkPacketHeader>() as u16;
let network_name_len = network_name.len() as u16;
let header_len = network_name_offset + network_name_len;
Self {
header_len: U16::new(header_len),
dst_peer_id: U32::new(dst_peer_id),
network_name_offset: U16::new(network_name_offset),
network_name_len: U16::new(network_name_len),
}
}
pub fn get_network_name(&self, zc_packet_payload: &[u8]) -> String {
let offset = self.network_name_offset.get() as usize;
let len = self.network_name_len.get() as usize;
std::str::from_utf8(&zc_packet_payload[offset..offset + len])
.unwrap()
.to_string()
}
pub fn get_dst_peer_id(&self) -> u32 {
self.dst_peer_id.get()
}
pub fn get_header_len(&self) -> usize {
self.header_len.get() as usize
}
}
// reserve space for AEAD authentication tag and nonce
#[repr(C, packed)]
#[derive(AsBytes, FromBytes, FromZeroes, Clone, Debug)]
pub struct AeadTail<const TAG_SIZE: usize, const NONCE_SIZE: usize> {
pub tag: [u8; TAG_SIZE],
pub nonce: [u8; NONCE_SIZE],
}
impl<const TAG_SIZE: usize, const NONCE_SIZE: usize> AeadTail<TAG_SIZE, NONCE_SIZE> {
pub const TAG_SIZE: usize = TAG_SIZE;
pub const NONCE_SIZE: usize = NONCE_SIZE;
pub const SIZE: usize = std::mem::size_of::<Self>();
}
pub type StandardAeadTail = AeadTail<16, 12>;
#[derive(AsBytes, FromZeroes, Clone, Debug, Copy, PartialEq, Hash, Eq)]
#[repr(u8)]
pub enum CompressorAlgo {
None = 0,
ZstdDefault = 1,
}
impl CompressorAlgo {
pub fn is_available(self) -> bool {
match self {
Self::None => true,
Self::ZstdDefault => compressor::zstd_available(),
}
}
pub fn ensure_available(self) -> Result<(), CompressionUnavailableError> {
self.is_available()
.then_some(())
.ok_or(CompressionUnavailableError(self))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[error("invalid compression algorithm: {0:?}")]
pub struct CompressionAlgoError(pub CompressionAlgoPb);
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[error("compression algorithm is unavailable in this build: {0:?}")]
pub struct CompressionUnavailableError(pub CompressorAlgo);
impl TryFrom<CompressionAlgoPb> for CompressorAlgo {
type Error = CompressionAlgoError;
fn try_from(value: CompressionAlgoPb) -> Result<Self, Self::Error> {
match value {
CompressionAlgoPb::Zstd => Ok(CompressorAlgo::ZstdDefault),
CompressionAlgoPb::None => Ok(CompressorAlgo::None),
_ => Err(CompressionAlgoError(value)),
}
}
}
impl TryFrom<CompressorAlgo> for CompressionAlgoPb {
type Error = CompressionAlgoError;
fn try_from(value: CompressorAlgo) -> Result<Self, Self::Error> {
match value {
CompressorAlgo::ZstdDefault => Ok(CompressionAlgoPb::Zstd),
CompressorAlgo::None => Ok(CompressionAlgoPb::None),
}
}
}
#[repr(C, packed)]
#[derive(AsBytes, FromBytes, FromZeroes, Clone, Debug, Default)]
pub struct CompressorTail {
pub algo: u8,
}
pub const COMPRESSOR_TAIL_SIZE: usize = std::mem::size_of::<CompressorTail>();
impl CompressorTail {
pub fn get_algo(&self) -> Option<CompressorAlgo> {
match self.algo {
1 => Some(CompressorAlgo::ZstdDefault),
_ => None,
}
}
pub fn new(algo: CompressorAlgo) -> Self {
Self { algo: algo as u8 }
}
}
pub const TAIL_RESERVED_SIZE: usize = max(StandardAeadTail::SIZE, COMPRESSOR_TAIL_SIZE);
#[derive(Default, Debug)]
pub struct ZCPacketOffsets {
pub payload_offset: usize,
pub peer_manager_header_offset: usize,
pub tcp_tunnel_header_offset: usize,
pub udp_tunnel_header_offset: usize,
pub wg_tunnel_header_offset: usize,
pub dummy_tunnel_header_offset: usize,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ZCPacketType {
// received from peer tcp connection
TCP,
// received from peer udp connection
UDP,
// received from peer wireguard connection
WG,
// received from local tun device, should reserve header space for tcp or udp tunnel
NIC,
// tunnel without header
DummyTunnel,
}
const PAYLOAD_OFFSET_FOR_NIC_PACKET: usize = max(
max(TCP_TUNNEL_HEADER_SIZE, UDP_TUNNEL_HEADER_SIZE),
WG_TUNNEL_HEADER_SIZE,
) + PEER_MANAGER_HEADER_SIZE;
// UDP Tunnel: TUN MTU + 24 (Easy) + 20 (Encrypted) + 8(UDP) + 20(IP) = TUN MTU + 72
// TCP Tunnel: TUN MTU + 20 (Easy) + 20 (Encrypted) + 20(TCP) + 20(IP) = TUN MTU + 80
const INVALID_OFFSET: usize = usize::MAX;
const fn get_converted_offset(old_hdr_size: usize, new_hdr_size: usize) -> usize {
if old_hdr_size < new_hdr_size {
INVALID_OFFSET
} else {
old_hdr_size - new_hdr_size
}
}
impl ZCPacketType {
pub fn get_packet_offsets(&self) -> ZCPacketOffsets {
match self {
ZCPacketType::TCP => ZCPacketOffsets {
payload_offset: TCP_TUNNEL_HEADER_SIZE + PEER_MANAGER_HEADER_SIZE,
peer_manager_header_offset: TCP_TUNNEL_HEADER_SIZE,
tcp_tunnel_header_offset: 0,
udp_tunnel_header_offset: get_converted_offset(
TCP_TUNNEL_HEADER_SIZE,
UDP_TUNNEL_HEADER_SIZE,
),
wg_tunnel_header_offset: get_converted_offset(
TCP_TUNNEL_HEADER_SIZE,
WG_TUNNEL_HEADER_SIZE,
),
dummy_tunnel_header_offset: get_converted_offset(TCP_TUNNEL_HEADER_SIZE, 0),
},
ZCPacketType::UDP => ZCPacketOffsets {
payload_offset: UDP_TUNNEL_HEADER_SIZE + PEER_MANAGER_HEADER_SIZE,
peer_manager_header_offset: UDP_TUNNEL_HEADER_SIZE,
tcp_tunnel_header_offset: get_converted_offset(
UDP_TUNNEL_HEADER_SIZE,
TCP_TUNNEL_HEADER_SIZE,
),
udp_tunnel_header_offset: 0,
wg_tunnel_header_offset: get_converted_offset(
UDP_TUNNEL_HEADER_SIZE,
WG_TUNNEL_HEADER_SIZE,
),
dummy_tunnel_header_offset: get_converted_offset(UDP_TUNNEL_HEADER_SIZE, 0),
},
ZCPacketType::WG => ZCPacketOffsets {
payload_offset: WG_TUNNEL_HEADER_SIZE + PEER_MANAGER_HEADER_SIZE,
peer_manager_header_offset: WG_TUNNEL_HEADER_SIZE,
tcp_tunnel_header_offset: get_converted_offset(
WG_TUNNEL_HEADER_SIZE,
TCP_TUNNEL_HEADER_SIZE,
),
udp_tunnel_header_offset: get_converted_offset(
WG_TUNNEL_HEADER_SIZE,
UDP_TUNNEL_HEADER_SIZE,
),
wg_tunnel_header_offset: 0,
dummy_tunnel_header_offset: get_converted_offset(WG_TUNNEL_HEADER_SIZE, 0),
},
ZCPacketType::NIC => ZCPacketOffsets {
payload_offset: PAYLOAD_OFFSET_FOR_NIC_PACKET,
peer_manager_header_offset: PAYLOAD_OFFSET_FOR_NIC_PACKET
- PEER_MANAGER_HEADER_SIZE,
tcp_tunnel_header_offset: PAYLOAD_OFFSET_FOR_NIC_PACKET
- PEER_MANAGER_HEADER_SIZE
- TCP_TUNNEL_HEADER_SIZE,
udp_tunnel_header_offset: PAYLOAD_OFFSET_FOR_NIC_PACKET
- PEER_MANAGER_HEADER_SIZE
- UDP_TUNNEL_HEADER_SIZE,
wg_tunnel_header_offset: PAYLOAD_OFFSET_FOR_NIC_PACKET
- PEER_MANAGER_HEADER_SIZE
- WG_TUNNEL_HEADER_SIZE,
dummy_tunnel_header_offset: PAYLOAD_OFFSET_FOR_NIC_PACKET
- PEER_MANAGER_HEADER_SIZE,
},
ZCPacketType::DummyTunnel => ZCPacketOffsets {
payload_offset: PEER_MANAGER_HEADER_SIZE,
peer_manager_header_offset: 0,
tcp_tunnel_header_offset: get_converted_offset(0, TCP_TUNNEL_HEADER_SIZE),
udp_tunnel_header_offset: get_converted_offset(0, UDP_TUNNEL_HEADER_SIZE),
wg_tunnel_header_offset: get_converted_offset(0, WG_TUNNEL_HEADER_SIZE),
dummy_tunnel_header_offset: 0,
},
}
}
}
#[derive(Debug, Clone)]
pub struct ZCPacket {
inner: BytesMut,
packet_type: ZCPacketType,
}
impl ZCPacket {
fn bytes_from_offset(&self, offset: usize) -> Option<&[u8]> {
self.inner.get(offset..)
}
fn mut_bytes_from_offset(&mut self, offset: usize) -> Option<&mut [u8]> {
self.inner.get_mut(offset..)
}
pub fn new_nic_packet() -> Self {
Self {
inner: BytesMut::new(),
packet_type: ZCPacketType::NIC,
}
}
pub fn new_from_buf(buf: BytesMut, packet_type: ZCPacketType) -> Self {
Self {
inner: buf,
packet_type,
}
}
pub fn new_with_payload(payload: &[u8]) -> Self {
let mut ret = Self::new_nic_packet();
let payload_off = ret.packet_type.get_packet_offsets().payload_offset;
let total_len = payload_off + payload.len();
ret.inner.reserve(total_len);
unsafe { ret.inner.set_len(total_len) };
ret.mut_payload().copy_from_slice(payload);
ret
}
pub fn new_for_tun(cap: usize, packet_info_len: usize) -> Self {
let mut ret = Self::new_nic_packet();
ret.inner.reserve(cap);
let total_len = ret.packet_type.get_packet_offsets().payload_offset - packet_info_len;
unsafe { ret.inner.set_len(total_len) };
ret
}
pub fn new_for_foreign_network(
network_name: &String,
dst_peer_id: u32,
foreign_zc_packet: &ZCPacket,
) -> Self {
let foreign_network_hdr = ForeignNetworkPacketHeader::new(dst_peer_id, network_name);
let total_payload_len =
foreign_network_hdr.get_header_len() + foreign_zc_packet.tunnel_payload().len();
let mut ret = Self::new_nic_packet();
let payload_off = ret.packet_type.get_packet_offsets().payload_offset;
ret.inner.reserve(payload_off + total_payload_len);
unsafe { ret.inner.set_len(payload_off + total_payload_len) };
let fixed_hdr_len = std::mem::size_of::<ForeignNetworkPacketHeader>();
ret.mut_payload()[..fixed_hdr_len].copy_from_slice(foreign_network_hdr.as_bytes());
let name_offset = foreign_network_hdr.network_name_offset.get() as usize;
let name_len = foreign_network_hdr.network_name_len.get() as usize;
ret.mut_payload()[name_offset..name_offset + name_len]
.copy_from_slice(network_name.as_bytes());
ret.mut_payload()[foreign_network_hdr.get_header_len()..]
.copy_from_slice(foreign_zc_packet.tunnel_payload());
let hdr = ret.mut_peer_manager_header().unwrap();
hdr.from_peer_id = 0.into();
hdr.to_peer_id = 0.into();
hdr.packet_type = PacketType::ForeignNetworkPacket as u8;
hdr.len.set(total_payload_len as u32);
ret
}
pub fn packet_type(&self) -> ZCPacketType {
self.packet_type
}
pub fn payload_offset(&self) -> usize {
self.packet_type.get_packet_offsets().payload_offset
}
pub fn mut_payload(&mut self) -> &mut [u8] {
let offset = self.payload_offset();
&mut self.inner[offset..]
}
pub fn mut_peer_manager_header(&mut self) -> Option<&mut PeerManagerHeader> {
let offset = self
.packet_type
.get_packet_offsets()
.peer_manager_header_offset;
let bytes = self.mut_bytes_from_offset(offset)?;
PeerManagerHeader::mut_from_prefix(bytes)
}
pub fn mut_tcp_tunnel_header(&mut self) -> Option<&mut TCPTunnelHeader> {
let offset = self
.packet_type
.get_packet_offsets()
.tcp_tunnel_header_offset;
let bytes = self.mut_bytes_from_offset(offset)?;
TCPTunnelHeader::mut_from_prefix(bytes)
}
pub fn mut_udp_tunnel_header(&mut self) -> Option<&mut UDPTunnelHeader> {
let offset = self
.packet_type
.get_packet_offsets()
.udp_tunnel_header_offset;
let bytes = self.mut_bytes_from_offset(offset)?;
UDPTunnelHeader::mut_from_prefix(bytes)
}
pub fn mut_wg_tunnel_header(&mut self) -> Option<&mut WGTunnelHeader> {
let offset = self
.packet_type
.get_packet_offsets()
.wg_tunnel_header_offset;
let bytes = self.mut_bytes_from_offset(offset)?;
WGTunnelHeader::mut_from_prefix(bytes)
}
// ref versions
pub fn payload(&self) -> &[u8] {
&self.inner[self.payload_offset()..]
}
pub fn payload_bytes(mut self) -> BytesMut {
self.inner.advance(self.payload_offset());
self.inner
}
pub fn peer_manager_header(&self) -> Option<&PeerManagerHeader> {
let offset = self
.packet_type
.get_packet_offsets()
.peer_manager_header_offset;
let bytes = self.bytes_from_offset(offset)?;
PeerManagerHeader::ref_from_prefix(bytes)
}
pub fn udp_tunnel_header(&self) -> Option<&UDPTunnelHeader> {
let offset = self
.packet_type
.get_packet_offsets()
.udp_tunnel_header_offset;
let bytes = self.bytes_from_offset(offset)?;
UDPTunnelHeader::ref_from_prefix(bytes)
}
pub fn udp_payload(&self) -> &[u8] {
&self.inner[self
.packet_type
.get_packet_offsets()
.udp_tunnel_header_offset
+ UDP_TUNNEL_HEADER_SIZE..]
}
pub fn payload_len(&self) -> usize {
self.inner.len() - self.payload_offset()
}
pub fn buf_len(&self) -> usize {
self.inner.len()
}
pub fn fill_peer_manager_hdr(&mut self, from_peer_id: u32, to_peer_id: u32, packet_type: u8) {
let payload_len = self.payload_len();
let hdr = self.mut_peer_manager_header().unwrap();
hdr.from_peer_id.set(from_peer_id);
hdr.to_peer_id.set(to_peer_id);
hdr.packet_type = packet_type;
hdr.flags = 0;
hdr.forward_counter = 1;
hdr.len.set(payload_len as u32);
}
pub fn tunnel_payload(&self) -> &[u8] {
&self.inner[self
.packet_type
.get_packet_offsets()
.peer_manager_header_offset..]
}
pub fn tunnel_payload_bytes(mut self) -> BytesMut {
self.inner.advance(
self.packet_type
.get_packet_offsets()
.peer_manager_header_offset,
);
self.inner
}
pub fn convert_type(mut self, target_packet_type: ZCPacketType) -> Self {
if target_packet_type == self.packet_type {
return self;
}
let new_offset = match target_packet_type {
ZCPacketType::TCP => {
self.packet_type
.get_packet_offsets()
.tcp_tunnel_header_offset
}
ZCPacketType::UDP => {
self.packet_type
.get_packet_offsets()
.udp_tunnel_header_offset
}
ZCPacketType::WG => {
self.packet_type
.get_packet_offsets()
.wg_tunnel_header_offset
}
ZCPacketType::DummyTunnel => {
self.packet_type
.get_packet_offsets()
.dummy_tunnel_header_offset
}
ZCPacketType::NIC => unreachable!(),
};
tracing::trace!(?self.packet_type, ?target_packet_type, ?new_offset, "convert zc packet type");
if new_offset == INVALID_OFFSET {
// copy peer manager header and payload to new buffer
let tunnel_payload = self.tunnel_payload();
let new_pm_offset = target_packet_type
.get_packet_offsets()
.peer_manager_header_offset;
let mut buf = BytesMut::with_capacity(new_pm_offset + tunnel_payload.len());
unsafe { buf.set_len(new_pm_offset) };
buf.extend_from_slice(tunnel_payload);
return Self::new_from_buf(buf, target_packet_type);
}
self.inner.advance(new_offset);
Self::new_from_buf(self.inner, target_packet_type)
}
pub fn into_bytes(self) -> Bytes {
self.inner.freeze()
}
pub fn inner(self) -> BytesMut {
self.inner
}
pub fn mut_inner(&mut self) -> &mut BytesMut {
&mut self.inner
}
pub fn is_lossy(&self) -> bool {
self.peer_manager_header()
.map(|hdr| hdr.packet_type == PacketType::Data as u8)
.unwrap_or(false)
}
pub fn foreign_network_hdr(&self) -> Option<&ForeignNetworkPacketHeader> {
if self.peer_manager_header().unwrap().packet_type == PacketType::ForeignNetworkPacket as u8
{
ForeignNetworkPacketHeader::ref_from_prefix(self.payload())
} else {
None
}
}
pub fn foreign_network_inner_packet_type(&self) -> Option<u8> {
if self.peer_manager_header()?.packet_type != PacketType::ForeignNetworkPacket as u8 {
return None;
}
let payload = self.payload();
let hdr = ForeignNetworkPacketHeader::ref_from_prefix(payload)?;
let inner_packet = payload.get(hdr.get_header_len()..)?;
PeerManagerHeader::ref_from_prefix(inner_packet).map(|hdr| hdr.packet_type)
}
pub fn foreign_network_packet(mut self) -> Self {
let hdr = self.foreign_network_hdr().unwrap();
let foreign_hdr_len = hdr.get_header_len();
Self::new_from_buf(
{
self.inner.advance(foreign_hdr_len + self.payload_offset());
self.inner
},
ZCPacketType::DummyTunnel,
)
}
pub fn get_src_peer_id(&self) -> Option<u32> {
self.peer_manager_header().map(|hdr| hdr.from_peer_id.get())
}
pub fn get_dst_peer_id(&self) -> Option<u32> {
self.peer_manager_header().map(|hdr| hdr.to_peer_id.get())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "proxy-packet")]
impl PeerManagerHeader {
pub(crate) fn is_kcp_src_modified(&self) -> bool {
self.packet_type == PacketType::DataWithKcpSrcModified as u8
}
pub(crate) fn is_quic_src_modified(&self) -> bool {
self.packet_type == PacketType::DataWithQuicSrcModified as u8
}
}
impl ZCPacket {
fn tcp_tunnel_header(&self) -> Option<&TCPTunnelHeader> {
let offset = self
.packet_type
.get_packet_offsets()
.tcp_tunnel_header_offset;
let bytes = self.bytes_from_offset(offset)?;
TCPTunnelHeader::ref_from_prefix(bytes)
}
}
#[test]
fn test_zc_packet() {
let payload = b"hello world";
let mut packet = ZCPacket::new_with_payload(payload);
let peer_manager_header = packet.mut_peer_manager_header().unwrap();
peer_manager_header.packet_type = PacketType::Data as u8;
peer_manager_header.len.set(payload.len() as u32);
let tcp_tunnel_header = packet.mut_tcp_tunnel_header().unwrap();
tcp_tunnel_header.len.set(payload.len() as u32);
// let udp_tunnel_header = packet.mut_udp_tunnel_header().unwrap();
// udp_tunnel_header.conn_id = 1;
// udp_tunnel_header.msg_type = 2;
// udp_tunnel_header.len = payload.len() as u32;
assert_eq!(packet.payload(), b"hello world");
assert_eq!(packet.payload_len(), 11);
println!("{:?}", packet.inner);
let tcp_packet = packet.convert_type(ZCPacketType::TCP).into_bytes();
assert_eq!(&tcp_packet[..1], b"\x0b");
println!("{:?}", tcp_packet);
}
#[test]
fn test_short_tcp_packet_header_access_is_safe() {
let mut packet = ZCPacket::new_from_buf(BytesMut::from(&b"\x01"[..]), ZCPacketType::TCP);
assert!(packet.peer_manager_header().is_none());
assert!(packet.tcp_tunnel_header().is_none());
assert!(packet.udp_tunnel_header().is_none());
assert!(packet.mut_peer_manager_header().is_none());
assert!(packet.mut_tcp_tunnel_header().is_none());
assert!(packet.mut_udp_tunnel_header().is_none());
assert!(packet.mut_wg_tunnel_header().is_none());
}
#[test]
fn test_invalid_converted_header_offset_is_safe() {
let mut packet = ZCPacket::new_from_buf(BytesMut::from(&b"\x01"[..]), ZCPacketType::UDP);
assert!(packet.mut_wg_tunnel_header().is_none());
}
#[test]
fn converts_compression_algo_none() {
assert_eq!(
CompressorAlgo::None,
CompressorAlgo::try_from(CompressionAlgoPb::None).unwrap()
);
assert_eq!(
CompressionAlgoPb::None,
CompressionAlgoPb::try_from(CompressorAlgo::None).unwrap()
);
}
#[test]
fn converts_zstd_compression_algo_in_every_profile() {
assert_eq!(
CompressorAlgo::ZstdDefault,
CompressorAlgo::try_from(CompressionAlgoPb::Zstd).unwrap()
);
assert_eq!(
CompressionAlgoPb::Zstd,
CompressionAlgoPb::try_from(CompressorAlgo::ZstdDefault).unwrap()
);
assert_eq!(
Some(CompressorAlgo::ZstdDefault),
CompressorTail { algo: 1 }.get_algo()
);
}
#[cfg(not(feature = "zstd"))]
#[test]
fn reports_zstd_as_unavailable_without_changing_vocabulary() {
assert!(!CompressorAlgo::ZstdDefault.is_available());
assert_eq!(
CompressorAlgo::ZstdDefault.ensure_available().unwrap_err(),
CompressionUnavailableError(CompressorAlgo::ZstdDefault)
);
}
#[cfg(feature = "zstd")]
#[test]
fn reports_zstd_as_available_when_compiled() {
assert!(CompressorAlgo::ZstdDefault.is_available());
}
}
+319
View File
@@ -0,0 +1,319 @@
//! STUN wire codec for the attributes EasyTier NAT traversal uses.
//!
//! EasyTier speaks STUN (RFC 5389/5780) for NAT mapping detection and port
//! mapping. This module holds the wire-level attribute types, their codecs,
//! and the EasyTier transaction-id convention (a `0xdeadbeef` prefix); it
//! performs no I/O. The connectivity layer drives probing and responding on
//! top of it.
use std::net::SocketAddr;
use bytecodec::fixnum::{U32beDecoder, U32beEncoder};
use bytecodec::{ByteCount, Decode, Encode, Eos, Result};
use bytecodec::{SizedEncode, TryTaggedDecode};
use stun_codec::macros::track;
use stun_codec::net::{SocketAddrDecoder, SocketAddrEncoder, socket_addr_xor};
use stun_codec::rfc5389::attributes::{
MappedAddress, Software, XorMappedAddress, XorMappedAddress2,
};
use stun_codec::rfc5780::attributes::{OtherAddress, ResponseOrigin};
use stun_codec::{AttributeType, Message, TransactionId, define_attribute_enums};
macro_rules! impl_decode {
($decoder:ty, $item:ident, $and_then:expr) => {
impl Decode for $decoder {
type Item = $item;
fn decode(&mut self, buf: &[u8], eos: Eos) -> Result<usize> {
track!(self.0.decode(buf, eos))
}
fn finish_decoding(&mut self) -> Result<Self::Item> {
track!(self.0.finish_decoding()).and_then($and_then)
}
fn requiring_bytes(&self) -> ByteCount {
self.0.requiring_bytes()
}
fn is_idle(&self) -> bool {
self.0.is_idle()
}
}
impl TryTaggedDecode for $decoder {
type Tag = AttributeType;
fn try_start_decoding(&mut self, attr_type: Self::Tag) -> Result<bool> {
Ok(attr_type.as_u16() == $item::CODEPOINT)
}
}
};
}
macro_rules! impl_encode {
($encoder:ty, $item:ty, $map_from:expr) => {
impl Encode for $encoder {
type Item = $item;
fn encode(&mut self, buf: &mut [u8], eos: Eos) -> Result<usize> {
track!(self.0.encode(buf, eos))
}
#[allow(clippy::redundant_closure_call)]
fn start_encoding(&mut self, item: Self::Item) -> Result<()> {
track!(self.0.start_encoding($map_from(item)))
}
fn requiring_bytes(&self) -> ByteCount {
self.0.requiring_bytes()
}
fn is_idle(&self) -> bool {
self.0.is_idle()
}
}
impl SizedEncode for $encoder {
fn exact_requiring_bytes(&self) -> u64 {
self.0.exact_requiring_bytes()
}
}
};
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ChangedAddress(SocketAddr);
impl ChangedAddress {
/// The codepoint of the type of the attribute.
pub const CODEPOINT: u16 = 0x0005;
pub fn new(addr: SocketAddr) -> Self {
ChangedAddress(addr)
}
/// Returns the address of this instance.
pub fn address(&self) -> SocketAddr {
self.0
}
}
impl stun_codec::Attribute for ChangedAddress {
type Decoder = ChangedAddressDecoder;
type Encoder = ChangedAddressEncoder;
fn get_type(&self) -> AttributeType {
AttributeType::new(Self::CODEPOINT)
}
fn before_encode<A: stun_codec::Attribute>(
&mut self,
message: &Message<A>,
) -> bytecodec::Result<()> {
self.0 = socket_addr_xor(self.0, message.transaction_id());
Ok(())
}
fn after_decode<A: stun_codec::Attribute>(
&mut self,
message: &Message<A>,
) -> bytecodec::Result<()> {
self.0 = socket_addr_xor(self.0, message.transaction_id());
Ok(())
}
}
#[derive(Debug, Default)]
pub struct ChangedAddressDecoder(SocketAddrDecoder);
impl ChangedAddressDecoder {
pub fn new() -> Self {
Self::default()
}
}
impl_decode!(ChangedAddressDecoder, ChangedAddress, |item| Ok(
ChangedAddress(item)
));
#[derive(Debug, Default)]
pub struct ChangedAddressEncoder(SocketAddrEncoder);
impl ChangedAddressEncoder {
pub fn new() -> Self {
Self::default()
}
}
impl_encode!(ChangedAddressEncoder, ChangedAddress, |item: Self::Item| {
item.0
});
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SourceAddress(SocketAddr);
impl SourceAddress {
/// The codepoint of the type of the attribute.
pub const CODEPOINT: u16 = 0x0004;
pub fn new(addr: SocketAddr) -> Self {
SourceAddress(addr)
}
/// Returns the address of this instance.
pub fn address(&self) -> SocketAddr {
self.0
}
}
impl stun_codec::Attribute for SourceAddress {
type Decoder = SourceAddressDecoder;
type Encoder = SourceAddressEncoder;
fn get_type(&self) -> AttributeType {
AttributeType::new(Self::CODEPOINT)
}
fn before_encode<A: stun_codec::Attribute>(
&mut self,
message: &Message<A>,
) -> bytecodec::Result<()> {
self.0 = socket_addr_xor(self.0, message.transaction_id());
Ok(())
}
fn after_decode<A: stun_codec::Attribute>(
&mut self,
message: &Message<A>,
) -> bytecodec::Result<()> {
self.0 = socket_addr_xor(self.0, message.transaction_id());
Ok(())
}
}
#[derive(Debug, Default)]
pub struct SourceAddressDecoder(SocketAddrDecoder);
impl SourceAddressDecoder {
pub fn new() -> Self {
Self::default()
}
}
impl_decode!(SourceAddressDecoder, SourceAddress, |item| Ok(
SourceAddress(item)
));
#[derive(Debug, Default)]
pub struct SourceAddressEncoder(SocketAddrEncoder);
impl SourceAddressEncoder {
pub fn new() -> Self {
Self::default()
}
}
impl_encode!(SourceAddressEncoder, SourceAddress, |item: Self::Item| {
item.0
});
/// `CHANGE-REQUEST` attribute.
///
/// See [RFC 5780 -- 7.2. CHANGE-REQUEST] about this attribute.
///
/// [RFC 5780 -- 7.2. CHANGE-REQUEST]: https://tools.ietf.org/html/rfc5780#section-7.2
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ChangeRequest(bool, bool);
impl ChangeRequest {
/// The codepoint of the type of the attribute.
pub const CODEPOINT: u16 = 0x0003;
/// Makes a new `ChangeRequest` instance.
pub fn new(ip: bool, port: bool) -> Self {
ChangeRequest(ip, port)
}
/// Returns whether the client requested the server to send the Binding Response with a
/// different IP address than the one the Binding Request was received on
pub fn ip(&self) -> bool {
self.0
}
/// Returns whether the client requested the server to send the Binding Response with a
/// different port than the one the Binding Request was received on
pub fn port(&self) -> bool {
self.1
}
}
impl stun_codec::Attribute for ChangeRequest {
type Decoder = ChangeRequestDecoder;
type Encoder = ChangeRequestEncoder;
fn get_type(&self) -> AttributeType {
AttributeType::new(Self::CODEPOINT)
}
}
/// [`ChangeRequest`] decoder.
#[derive(Debug, Default)]
pub struct ChangeRequestDecoder(U32beDecoder);
impl ChangeRequestDecoder {
/// Makes a new `ChangeRequestDecoder` instance.
pub fn new() -> Self {
Self::default()
}
}
impl_decode!(ChangeRequestDecoder, ChangeRequest, |item| {
Ok(ChangeRequest((item & 0x4) != 0, (item & 0x2) != 0))
});
/// [`ChangeRequest`] encoder.
#[derive(Debug, Default)]
pub struct ChangeRequestEncoder(U32beEncoder);
impl ChangeRequestEncoder {
/// Makes a new `ChangeRequestEncoder` instance.
pub fn new() -> Self {
Self::default()
}
}
impl_encode!(ChangeRequestEncoder, ChangeRequest, |item: Self::Item| {
let ip = item.0 as u8;
let port = item.1 as u8;
((ip << 1 | port) << 1) as u32
});
pub fn tid_to_u32(tid: &TransactionId) -> u32 {
let mut tid_buf = [0u8; 4];
// copy bytes from msg_tid to tid_buf
tid_buf[..].copy_from_slice(&tid.as_bytes()[8..12]);
u32::from_le_bytes(tid_buf)
}
pub fn u32_to_tid(tid: u32) -> TransactionId {
let tid_buf = tid.to_le_bytes();
let mut tid_arr = [0u8; 12];
tid_arr[..4].copy_from_slice(&0xdeadbeefu32.to_be_bytes());
tid_arr[8..12].copy_from_slice(&tid_buf);
TransactionId::new(tid_arr)
}
define_attribute_enums!(
Attribute,
AttributeDecoder,
AttributeEncoder,
[
Software,
MappedAddress,
XorMappedAddress,
XorMappedAddress2,
OtherAddress,
ChangeRequest,
ChangedAddress,
SourceAddress,
ResponseOrigin
]
);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn easytier_transaction_id_roundtrips_u32() {
let tid = u32_to_tid(0x1122_3344);
assert_eq!(&tid.as_bytes()[..4], &[0xde, 0xad, 0xbe, 0xef]);
assert_eq!(tid_to_u32(&tid), 0x1122_3344);
}
}