init
Some checks failed
CI / Rust (push) Successful in 20s
CI / Android (push) Failing after 8m35s

This commit is contained in:
2026-05-31 15:36:07 +03:30
commit 4ffbc3bffe
61 changed files with 2760 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
use std::net::IpAddr;
use std::time::Duration;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DnsPolicy {
pub gateway_listen: IpAddr,
pub min_ttl: Duration,
pub max_ttl: Duration,
pub enable_tcp_fallback: bool,
}
impl DnsPolicy {
pub fn clamp_ttl(&self, upstream_ttl: Duration) -> Duration {
upstream_ttl.max(self.min_ttl).min(self.max_ttl)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DnsRequest {
pub transaction_id: u16,
pub question_name: String,
pub question_type: u16,
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::{IpAddr, Ipv4Addr};
#[test]
fn ttl_is_clamped() {
let policy = DnsPolicy {
gateway_listen: IpAddr::V4(Ipv4Addr::new(10, 241, 0, 1)),
min_ttl: Duration::from_secs(10),
max_ttl: Duration::from_secs(300),
enable_tcp_fallback: true,
};
assert_eq!(policy.clamp_ttl(Duration::from_secs(1)), Duration::from_secs(10));
assert_eq!(policy.clamp_ttl(Duration::from_secs(600)), Duration::from_secs(300));
}
}

View File

@@ -0,0 +1,99 @@
use std::collections::HashMap;
use std::net::{Ipv4Addr, Ipv6Addr};
use std::time::{Duration, Instant};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct PeerId([u8; 16]);
impl PeerId {
pub fn from_u128(value: u128) -> Self {
Self(value.to_be_bytes())
}
pub fn as_bytes(&self) -> [u8; 16] {
self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Lease {
pub peer_id: PeerId,
pub ipv4: Ipv4Addr,
pub ipv6: Option<Ipv6Addr>,
pub dns_gateway: Ipv4Addr,
pub mtu: u16,
pub expires_at: Instant,
}
#[derive(Debug)]
pub struct LeaseAllocator {
next_host: u8,
default_mtu: u16,
leases: HashMap<PeerId, Lease>,
}
impl LeaseAllocator {
pub fn new(default_mtu: u16) -> Self {
Self {
next_host: 2,
default_mtu,
leases: HashMap::new(),
}
}
pub fn allocate(&mut self, peer_id: PeerId) -> Result<Lease, LeaseError> {
if let Some(existing) = self.leases.get(&peer_id) {
return Ok(existing.clone());
}
if self.next_host == u8::MAX {
return Err(LeaseError::PoolExhausted);
}
let lease = Lease {
peer_id,
ipv4: Ipv4Addr::new(10, 241, 0, self.next_host),
ipv6: None,
dns_gateway: Ipv4Addr::new(10, 241, 0, 1),
mtu: self.default_mtu,
expires_at: Instant::now() + Duration::from_secs(12 * 60 * 60),
};
self.next_host += 1;
self.leases.insert(peer_id, lease.clone());
Ok(lease)
}
pub fn revoke(&mut self, peer_id: PeerId) -> Option<Lease> {
self.leases.remove(&peer_id)
}
pub fn clear(&mut self) {
self.next_host = 2;
self.leases.clear();
}
pub fn active_count(&self) -> usize {
self.leases.len()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum LeaseError {
PoolExhausted,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn allocates_stable_peer_lease() {
let mut allocator = LeaseAllocator::new(1280);
let peer = PeerId::from_u128(42);
let first = allocator.allocate(peer).unwrap();
let second = allocator.allocate(peer).unwrap();
assert_eq!(first.ipv4, Ipv4Addr::new(10, 241, 0, 2));
assert_eq!(first, second);
}
}

View File

@@ -0,0 +1,117 @@
//! Core VPN Share engine primitives.
pub mod dns;
pub mod lease;
pub mod mtu;
pub mod nat;
pub mod packet;
use std::time::Instant;
pub use dns::{DnsPolicy, DnsRequest};
pub use lease::{Lease, LeaseAllocator, PeerId};
pub use mtu::MtuPolicy;
pub use nat::{FlowKey, FlowTable, TransportProtocol};
pub use packet::{IpPacket, PacketError};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GatewayState {
Stopped,
Starting,
Running,
Failed,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GatewayConfig {
pub max_peers: usize,
pub default_mtu: u16,
pub ipv6_enabled: bool,
}
impl Default for GatewayConfig {
fn default() -> Self {
Self {
max_peers: 4,
default_mtu: 1280,
ipv6_enabled: false,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VpnStatus {
pub active: bool,
pub interface_name: Option<String>,
pub supports_ipv4: bool,
pub supports_ipv6: bool,
}
#[derive(Debug)]
pub struct GatewayEngine {
state: GatewayState,
config: GatewayConfig,
leases: LeaseAllocator,
flows: FlowTable,
started_at: Option<Instant>,
}
impl GatewayEngine {
pub fn new(config: GatewayConfig) -> Self {
Self {
state: GatewayState::Stopped,
leases: LeaseAllocator::new(config.default_mtu),
flows: FlowTable::default(),
config,
started_at: None,
}
}
pub fn start(&mut self, now: Instant) {
self.state = GatewayState::Running;
self.started_at = Some(now);
}
pub fn stop(&mut self) {
self.state = GatewayState::Stopped;
self.started_at = None;
self.flows.clear();
self.leases.clear();
}
pub fn state(&self) -> GatewayState {
self.state
}
pub fn config(&self) -> &GatewayConfig {
&self.config
}
pub fn leases(&mut self) -> &mut LeaseAllocator {
&mut self.leases
}
pub fn flows(&mut self) -> &mut FlowTable {
&mut self.flows
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn gateway_start_stop_clears_runtime_state() {
let mut engine = GatewayEngine::new(GatewayConfig::default());
engine.start(Instant::now());
assert_eq!(engine.state(), GatewayState::Running);
let peer = PeerId::from_u128(1);
engine.leases().allocate(peer).unwrap();
assert_eq!(engine.leases().active_count(), 1);
engine.stop();
assert_eq!(engine.state(), GatewayState::Stopped);
assert_eq!(engine.leases().active_count(), 0);
}
}

View File

@@ -0,0 +1,50 @@
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MtuPolicy {
pub link_mtu: u16,
pub protocol_overhead: u16,
pub min_ipv6_mtu: u16,
}
impl MtuPolicy {
pub fn effective_tunnel_mtu(&self) -> u16 {
self.link_mtu
.saturating_sub(self.protocol_overhead)
.max(self.min_ipv6_mtu)
}
pub fn tcp_mss_ipv4(&self) -> u16 {
self.effective_tunnel_mtu().saturating_sub(40)
}
pub fn tcp_mss_ipv6(&self) -> u16 {
self.effective_tunnel_mtu().saturating_sub(60)
}
}
impl Default for MtuPolicy {
fn default() -> Self {
Self {
link_mtu: 1420,
protocol_overhead: 96,
min_ipv6_mtu: 1280,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mtu_never_below_ipv6_minimum() {
let policy = MtuPolicy {
link_mtu: 1000,
protocol_overhead: 100,
min_ipv6_mtu: 1280,
};
assert_eq!(policy.effective_tunnel_mtu(), 1280);
assert_eq!(policy.tcp_mss_ipv4(), 1240);
assert_eq!(policy.tcp_mss_ipv6(), 1220);
}
}

View File

@@ -0,0 +1,104 @@
use crate::lease::PeerId;
use std::collections::HashMap;
use std::net::IpAddr;
use std::time::{Duration, Instant};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TransportProtocol {
Tcp,
Udp,
Icmp,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct FlowKey {
pub protocol: TransportProtocol,
pub source: IpAddr,
pub source_port: u16,
pub destination: IpAddr,
pub destination_port: u16,
}
#[derive(Debug, Clone)]
pub struct FlowEntry {
pub key: FlowKey,
pub peer_id: PeerId,
pub created_at: Instant,
pub last_seen_at: Instant,
pub bytes_from_peer: u64,
pub bytes_to_peer: u64,
}
#[derive(Debug, Default)]
pub struct FlowTable {
entries: HashMap<FlowKey, FlowEntry>,
}
impl FlowTable {
pub fn upsert(&mut self, key: FlowKey, peer_id: PeerId, now: Instant, bytes_from_peer: u64) {
self.entries
.entry(key.clone())
.and_modify(|entry| {
entry.last_seen_at = now;
entry.bytes_from_peer = entry.bytes_from_peer.saturating_add(bytes_from_peer);
})
.or_insert(FlowEntry {
key,
peer_id,
created_at: now,
last_seen_at: now,
bytes_from_peer,
bytes_to_peer: 0,
});
}
pub fn record_return_bytes(&mut self, key: &FlowKey, bytes_to_peer: u64) {
if let Some(entry) = self.entries.get_mut(key) {
entry.bytes_to_peer = entry.bytes_to_peer.saturating_add(bytes_to_peer);
}
}
pub fn expire_idle(&mut self, now: Instant, idle_timeout: Duration) -> usize {
let before = self.entries.len();
self.entries
.retain(|_, entry| now.duration_since(entry.last_seen_at) <= idle_timeout);
before - self.entries.len()
}
pub fn clear(&mut self) {
self.entries.clear();
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::{IpAddr, Ipv4Addr};
#[test]
fn expires_idle_flows() {
let now = Instant::now();
let mut table = FlowTable::default();
let key = FlowKey {
protocol: TransportProtocol::Tcp,
source: IpAddr::V4(Ipv4Addr::new(10, 241, 0, 2)),
source_port: 1234,
destination: IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)),
destination_port: 443,
};
table.upsert(key, PeerId::from_u128(1), now, 10);
assert_eq!(table.len(), 1);
let expired = table.expire_idle(now + Duration::from_secs(61), Duration::from_secs(60));
assert_eq!(expired, 1);
assert!(table.is_empty());
}
}

View File

@@ -0,0 +1,166 @@
use crate::nat::TransportProtocol;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum IpPacket<'a> {
V4(Ipv4Packet<'a>),
V6(Ipv6Packet<'a>),
}
impl<'a> IpPacket<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, PacketError> {
let first = *bytes.first().ok_or(PacketError::TooShort)?;
match first >> 4 {
4 => Ok(Self::V4(Ipv4Packet::parse(bytes)?)),
6 => Ok(Self::V6(Ipv6Packet::parse(bytes)?)),
version => Err(PacketError::UnsupportedVersion(version)),
}
}
pub fn source(&self) -> IpAddr {
match self {
Self::V4(packet) => IpAddr::V4(packet.source),
Self::V6(packet) => IpAddr::V6(packet.source),
}
}
pub fn destination(&self) -> IpAddr {
match self {
Self::V4(packet) => IpAddr::V4(packet.destination),
Self::V6(packet) => IpAddr::V6(packet.destination),
}
}
pub fn transport_protocol(&self) -> Option<TransportProtocol> {
match self {
Self::V4(packet) => packet.transport_protocol(),
Self::V6(packet) => packet.transport_protocol(),
}
}
pub fn l4_ports(&self) -> Option<(u16, u16)> {
let payload = match self {
Self::V4(packet) => packet.payload,
Self::V6(packet) => packet.payload,
};
if payload.len() < 4 {
return None;
}
Some((
u16::from_be_bytes([payload[0], payload[1]]),
u16::from_be_bytes([payload[2], payload[3]]),
))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Ipv4Packet<'a> {
pub source: Ipv4Addr,
pub destination: Ipv4Addr,
pub protocol: u8,
pub header_len: usize,
pub total_len: usize,
pub payload: &'a [u8],
}
impl<'a> Ipv4Packet<'a> {
fn parse(bytes: &'a [u8]) -> Result<Self, PacketError> {
if bytes.len() < 20 {
return Err(PacketError::TooShort);
}
let ihl = (bytes[0] & 0x0f) as usize * 4;
if ihl < 20 || bytes.len() < ihl {
return Err(PacketError::InvalidHeaderLength(ihl));
}
let total_len = u16::from_be_bytes([bytes[2], bytes[3]]) as usize;
if total_len < ihl || total_len > bytes.len() {
return Err(PacketError::InvalidTotalLength(total_len));
}
Ok(Self {
source: Ipv4Addr::new(bytes[12], bytes[13], bytes[14], bytes[15]),
destination: Ipv4Addr::new(bytes[16], bytes[17], bytes[18], bytes[19]),
protocol: bytes[9],
header_len: ihl,
total_len,
payload: &bytes[ihl..total_len],
})
}
fn transport_protocol(&self) -> Option<TransportProtocol> {
protocol_number_to_transport(self.protocol)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Ipv6Packet<'a> {
pub source: Ipv6Addr,
pub destination: Ipv6Addr,
pub next_header: u8,
pub payload: &'a [u8],
}
impl<'a> Ipv6Packet<'a> {
fn parse(bytes: &'a [u8]) -> Result<Self, PacketError> {
if bytes.len() < 40 {
return Err(PacketError::TooShort);
}
let payload_len = u16::from_be_bytes([bytes[4], bytes[5]]) as usize;
let total_len = 40 + payload_len;
if total_len > bytes.len() {
return Err(PacketError::InvalidTotalLength(total_len));
}
let source = Ipv6Addr::from(<[u8; 16]>::try_from(&bytes[8..24]).expect("slice length checked"));
let destination = Ipv6Addr::from(<[u8; 16]>::try_from(&bytes[24..40]).expect("slice length checked"));
Ok(Self {
source,
destination,
next_header: bytes[6],
payload: &bytes[40..total_len],
})
}
fn transport_protocol(&self) -> Option<TransportProtocol> {
protocol_number_to_transport(self.next_header)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PacketError {
TooShort,
UnsupportedVersion(u8),
InvalidHeaderLength(usize),
InvalidTotalLength(usize),
}
fn protocol_number_to_transport(protocol: u8) -> Option<TransportProtocol> {
match protocol {
1 | 58 => Some(TransportProtocol::Icmp),
6 => Some(TransportProtocol::Tcp),
17 => Some(TransportProtocol::Udp),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_ipv4_udp_packet() {
let packet = [
0x45, 0, 0, 28, 0, 0, 0, 0, 64, 17, 0, 0, 10, 241, 0, 2, 1, 1, 1, 1, 0x30, 0x39, 0, 53, 0, 8, 0, 0,
];
let parsed = IpPacket::parse(&packet).unwrap();
assert_eq!(parsed.source(), IpAddr::V4(Ipv4Addr::new(10, 241, 0, 2)));
assert_eq!(parsed.destination(), IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)));
assert_eq!(parsed.transport_protocol(), Some(TransportProtocol::Udp));
assert_eq!(parsed.l4_ports(), Some((12345, 53)));
}
#[test]
fn rejects_unknown_ip_version() {
assert_eq!(IpPacket::parse(&[0xf0]), Err(PacketError::UnsupportedVersion(15)));
}
}