use crate::tunneling::{Tunnel, TunnelConfig, TunnelState, TunnelMetrics, TunnelProtocol};
use crate::{Result, P2PError};
use async_trait::async_trait;
use std::net::{Ipv4Addr, Ipv6Addr, IpAddr, SocketAddr};
use std::time::{Duration, Instant, SystemTime};
use tracing::{info, warn, debug};
use tokio::net::UdpSocket;
use serde::{Serialize, Deserialize};
const ISATAP_IID_PREFIX: u64 = 0x00005EFE;
const DEFAULT_ROUTER_DISCOVERY_INTERVAL: Duration = Duration::from_secs(300);
const MAX_PRL_SIZE: usize = 10;
pub struct IsatapTunnel {
config: TunnelConfig,
state: TunnelState,
metrics: TunnelMetrics,
local_ipv4: Option<Ipv4Addr>,
isatap_ipv6: Option<Ipv6Addr>,
socket: Option<UdpSocket>,
potential_router_list: Vec<IsatapRouter>,
last_router_discovery: Option<Instant>,
active_router: Option<IsatapRouter>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IsatapRouter {
pub ipv4_addr: Ipv4Addr,
pub ipv6_prefix: Option<Ipv6Addr>,
pub priority: u8,
pub last_seen: Option<SystemTime>,
pub reachable: bool,
pub rtt: Option<Duration>,
}
#[derive(Debug, Clone)]
pub enum RouterDiscoveryMethod {
DnsWellKnown(String),
ConfiguredList(Vec<Ipv4Addr>),
Dhcp,
Manual(Vec<IsatapRouter>),
}
impl IsatapTunnel {
pub fn new(config: TunnelConfig) -> Result<Self> {
if config.protocol != TunnelProtocol::Isatap {
return Err(P2PError::Config(
"Invalid protocol for ISATAP tunnel".to_string()
));
}
info!("Creating ISATAP tunnel for enterprise IPv6 connectivity");
Ok(Self {
config,
state: TunnelState::Disconnected,
metrics: TunnelMetrics::default(),
local_ipv4: None,
isatap_ipv6: None,
socket: None,
potential_router_list: Vec::new(),
last_router_discovery: None,
active_router: None,
})
}
pub fn generate_isatap_address(ipv4_addr: Ipv4Addr, prefix: Option<Ipv6Addr>) -> Ipv6Addr {
let prefix = prefix.unwrap_or_else(|| "fe80::".parse().unwrap());
let ipv4_bytes = ipv4_addr.octets();
let ipv4_u32 = u32::from_be_bytes(ipv4_bytes);
let iid_high = ISATAP_IID_PREFIX;
let prefix_bytes = prefix.octets();
let mut addr_bytes = [0u8; 16];
addr_bytes[..8].copy_from_slice(&prefix_bytes[..8]);
addr_bytes[8..12].copy_from_slice(&iid_high.to_be_bytes()[4..]);
addr_bytes[12..16].copy_from_slice(&ipv4_u32.to_be_bytes());
Ipv6Addr::from(addr_bytes)
}
pub fn is_isatap_address(addr: Ipv6Addr) -> bool {
let segments = addr.segments();
segments[4] == 0x0000 && segments[5] == 0x5EFE
}
pub fn extract_ipv4_from_isatap(addr: Ipv6Addr) -> Option<Ipv4Addr> {
if !Self::is_isatap_address(addr) {
return None;
}
let segments = addr.segments();
let ipv4_bytes = [
((segments[6] >> 8) & 0xFF) as u8,
(segments[6] & 0xFF) as u8,
((segments[7] >> 8) & 0xFF) as u8,
(segments[7] & 0xFF) as u8,
];
Some(Ipv4Addr::from(ipv4_bytes))
}
pub async fn discover_routers(&mut self, method: RouterDiscoveryMethod) -> Result<Vec<IsatapRouter>> {
info!("Discovering ISATAP routers using: {:?}", method);
let discovered_routers = match method {
RouterDiscoveryMethod::DnsWellKnown(domain) => {
self.discover_routers_dns(&domain).await?
}
RouterDiscoveryMethod::ConfiguredList(addresses) => {
self.create_routers_from_addresses(addresses)
}
RouterDiscoveryMethod::Manual(routers) => routers,
RouterDiscoveryMethod::Dhcp => {
warn!("DHCP router discovery not yet implemented");
Vec::new()
}
};
self.update_potential_router_list(discovered_routers.clone());
self.last_router_discovery = Some(Instant::now());
info!("Discovered {} ISATAP routers", discovered_routers.len());
Ok(discovered_routers)
}
async fn discover_routers_dns(&self, domain: &str) -> Result<Vec<IsatapRouter>> {
let isatap_hostname = format!("isatap.{}", domain);
debug!("Looking up ISATAP router at: {}", isatap_hostname);
match tokio::net::lookup_host(format!("{}:80", isatap_hostname)).await {
Ok(addrs) => {
let mut routers = Vec::new();
for addr in addrs {
if let IpAddr::V4(ipv4) = addr.ip() {
routers.push(IsatapRouter {
ipv4_addr: ipv4,
ipv6_prefix: None,
priority: 10, last_seen: None,
reachable: false,
rtt: None,
});
}
}
Ok(routers)
}
Err(e) => {
warn!("Failed to resolve ISATAP router '{}': {}", isatap_hostname, e);
Ok(Vec::new())
}
}
}
fn create_routers_from_addresses(&self, addresses: Vec<Ipv4Addr>) -> Vec<IsatapRouter> {
addresses.into_iter().enumerate().map(|(i, addr)| {
IsatapRouter {
ipv4_addr: addr,
ipv6_prefix: None,
priority: i as u8,
last_seen: None,
reachable: false,
rtt: None,
}
}).collect()
}
fn update_potential_router_list(&mut self, new_routers: Vec<IsatapRouter>) {
for new_router in new_routers {
if !self.potential_router_list.iter().any(|r| r.ipv4_addr == new_router.ipv4_addr) {
self.potential_router_list.push(new_router);
}
}
self.potential_router_list.sort_by_key(|r| r.priority);
self.potential_router_list.truncate(MAX_PRL_SIZE);
debug!("Updated PRL with {} routers", self.potential_router_list.len());
}
pub async fn test_router_reachability(&self, router: &IsatapRouter) -> Result<Duration> {
let target = format!("{}:80", router.ipv4_addr);
let start = Instant::now();
match tokio::time::timeout(Duration::from_secs(5), tokio::net::TcpStream::connect(&target)).await {
Ok(Ok(_)) => {
let rtt = start.elapsed();
debug!("ISATAP router {} reachable, RTT: {:?}", router.ipv4_addr, rtt);
Ok(rtt)
}
Ok(Err(e)) => {
debug!("ISATAP router {} unreachable: {}", router.ipv4_addr, e);
Err(P2PError::Network(format!("Router unreachable: {}", e)))
}
Err(_) => {
debug!("ISATAP router {} timeout", router.ipv4_addr);
Err(P2PError::Network("Router reachability timeout".to_string()))
}
}
}
pub async fn select_router(&mut self) -> Result<IsatapRouter> {
if self.potential_router_list.is_empty() {
return Err(P2PError::Config("No ISATAP routers available".to_string()));
}
info!("Selecting best ISATAP router from {} candidates", self.potential_router_list.len());
let mut best_router = None;
let mut best_rtt = Duration::from_secs(u64::MAX);
let mut test_results = Vec::new();
for (i, router) in self.potential_router_list.iter().enumerate() {
let rtt_result = self.test_router_reachability(router).await;
test_results.push((i, rtt_result));
}
for (i, rtt_result) in test_results {
let router = &mut self.potential_router_list[i];
match rtt_result {
Ok(rtt) => {
router.reachable = true;
router.rtt = Some(rtt);
router.last_seen = Some(SystemTime::now());
if rtt < best_rtt {
best_rtt = rtt;
best_router = Some(router.clone());
}
}
Err(_) => {
router.reachable = false;
router.rtt = None;
}
}
}
match best_router {
Some(router) => {
info!("Selected ISATAP router: {} (RTT: {:?})", router.ipv4_addr, router.rtt);
self.active_router = Some(router.clone());
Ok(router)
}
None => Err(P2PError::Network("No reachable ISATAP routers found".to_string()))
}
}
pub async fn initialize_addresses(&mut self) -> Result<()> {
let local_ipv4 = self.get_local_ipv4().await?;
self.local_ipv4 = Some(local_ipv4);
let prefix = self.get_ipv6_prefix();
let isatap_addr = Self::generate_isatap_address(local_ipv4, prefix);
self.isatap_ipv6 = Some(isatap_addr);
info!("Initialized ISATAP addresses: IPv4={}, IPv6={}", local_ipv4, isatap_addr);
Ok(())
}
async fn get_local_ipv4(&self) -> Result<Ipv4Addr> {
if let Some(addr) = self.config.local_ipv4 {
return Ok(addr);
}
match tokio::net::UdpSocket::bind("0.0.0.0:0").await {
Ok(socket) => {
if socket.connect("8.8.8.8:53").await.is_ok() {
if let Ok(local_addr) = socket.local_addr() {
if let IpAddr::V4(ipv4) = local_addr.ip() {
return Ok(ipv4);
}
}
}
}
Err(e) => {
warn!("Failed to auto-detect local IPv4: {}", e);
}
}
Err(P2PError::Network("Could not determine local IPv4 address".to_string()))
}
fn get_ipv6_prefix(&self) -> Option<Ipv6Addr> {
self.config.ipv6_prefix.or_else(|| {
Some("fe80::".parse().unwrap())
})
}
async fn create_socket(&mut self) -> Result<()> {
let local_ipv4 = self.local_ipv4.ok_or_else(|| {
P2PError::Config("Local IPv4 address not initialized".to_string())
})?;
let bind_addr = SocketAddr::new(IpAddr::V4(local_ipv4), 0);
let socket = UdpSocket::bind(bind_addr).await
.map_err(|e| P2PError::Network(format!("Failed to create ISATAP socket: {}", e)))?;
info!("Created ISATAP socket on: {}", socket.local_addr().unwrap());
self.socket = Some(socket);
Ok(())
}
pub fn encapsulate_ipv6_in_ipv4(&self, ipv6_packet: &[u8], dest_ipv4: Ipv4Addr) -> Result<Vec<u8>> {
if ipv6_packet.len() < 40 {
return Err(P2PError::Transport("IPv6 packet too short".to_string()));
}
if (ipv6_packet[0] & 0xF0) != 0x60 {
return Err(P2PError::Transport("Not an IPv6 packet".to_string()));
}
let local_ipv4 = self.local_ipv4.ok_or_else(|| {
P2PError::Network("Local IPv4 address not available".to_string())
})?;
let mut ipv4_packet = Vec::with_capacity(20 + ipv6_packet.len());
ipv4_packet.push(0x45); ipv4_packet.push(0x00); ipv4_packet.extend_from_slice(&((20 + ipv6_packet.len()) as u16).to_be_bytes()); ipv4_packet.extend_from_slice(&[0x00, 0x00]); ipv4_packet.extend_from_slice(&[0x40, 0x00]); ipv4_packet.push(64); ipv4_packet.push(41); ipv4_packet.extend_from_slice(&[0x00, 0x00]); ipv4_packet.extend_from_slice(&local_ipv4.octets()); ipv4_packet.extend_from_slice(&dest_ipv4.octets());
let checksum = Self::calculate_ipv4_checksum(&ipv4_packet);
ipv4_packet[10..12].copy_from_slice(&checksum.to_be_bytes());
ipv4_packet.extend_from_slice(ipv6_packet);
Ok(ipv4_packet)
}
pub fn decapsulate_ipv4_to_ipv6(&self, ipv4_packet: &[u8]) -> Result<Vec<u8>> {
if ipv4_packet.len() < 20 {
return Err(P2PError::Transport("IPv4 packet too short".to_string()));
}
if (ipv4_packet[0] & 0xF0) != 0x40 {
return Err(P2PError::Transport("Not an IPv4 packet".to_string()));
}
if ipv4_packet[9] != 41 {
return Err(P2PError::Transport("IPv4 packet does not contain IPv6".to_string()));
}
let ihl = (ipv4_packet[0] & 0x0F) as usize * 4;
if ipv4_packet.len() <= ihl {
return Err(P2PError::Transport("IPv4 header length invalid".to_string()));
}
let ipv6_packet = ipv4_packet[ihl..].to_vec();
if ipv6_packet.is_empty() || (ipv6_packet[0] & 0xF0) != 0x60 {
return Err(P2PError::Transport("Invalid IPv6 payload".to_string()));
}
Ok(ipv6_packet)
}
fn calculate_ipv4_checksum(header: &[u8]) -> u16 {
let mut sum: u32 = 0;
for i in (0..20).step_by(2) {
if i == 10 { continue; }
let word = if i + 1 < header.len() {
u16::from_be_bytes([header[i], header[i + 1]])
} else {
u16::from_be_bytes([header[i], 0])
};
sum += word as u32;
}
while (sum >> 16) != 0 {
sum = (sum & 0xFFFF) + (sum >> 16);
}
!sum as u16
}
}
#[async_trait]
impl Tunnel for IsatapTunnel {
fn protocol(&self) -> TunnelProtocol {
TunnelProtocol::Isatap
}
fn config(&self) -> &TunnelConfig {
&self.config
}
async fn state(&self) -> TunnelState {
self.state.clone()
}
async fn metrics(&self) -> TunnelMetrics {
self.metrics.clone()
}
async fn connect(&mut self) -> Result<()> {
info!("Connecting ISATAP tunnel for enterprise IPv6 connectivity");
self.state = TunnelState::Connecting;
if let Err(e) = self.initialize_addresses().await {
self.state = TunnelState::Failed(format!("Address initialization failed: {}", e));
return Err(e);
}
let discovery_method = if let Some(domain) = std::env::var("ISATAP_DOMAIN").ok() {
RouterDiscoveryMethod::DnsWellKnown(domain)
} else {
RouterDiscoveryMethod::ConfiguredList(vec![
"192.168.1.1".parse().unwrap(),
"10.0.0.1".parse().unwrap(),
])
};
if let Err(e) = self.discover_routers(discovery_method).await {
warn!("Router discovery failed: {}", e);
}
if self.select_router().await.is_err() {
warn!("No ISATAP router available, operating in host-to-host mode");
}
if let Err(e) = self.create_socket().await {
self.state = TunnelState::Failed(format!("Socket creation failed: {}", e));
return Err(e);
}
self.state = TunnelState::Connected;
info!("ISATAP tunnel connected successfully");
Ok(())
}
async fn is_active(&self) -> bool {
matches!(self.state, TunnelState::Connected)
}
async fn disconnect(&mut self) -> Result<()> {
info!("Disconnecting ISATAP tunnel");
if let Some(socket) = self.socket.take() {
drop(socket);
}
self.state = TunnelState::Disconnected;
self.active_router = None;
info!("ISATAP tunnel disconnected");
Ok(())
}
async fn encapsulate(&self, ipv6_packet: &[u8]) -> Result<Vec<u8>> {
if !self.is_active().await {
return Err(P2PError::Network("ISATAP tunnel not connected".to_string()));
}
let dest_ipv4 = if let Some(router) = &self.active_router {
router.ipv4_addr
} else {
if ipv6_packet.len() >= 40 {
let dest_ipv6_bytes = &ipv6_packet[24..40];
let dest_ipv6 = Ipv6Addr::from(<[u8; 16]>::try_from(dest_ipv6_bytes).unwrap());
if let Some(ipv4) = Self::extract_ipv4_from_isatap(dest_ipv6) {
ipv4
} else {
return Err(P2PError::Network("No route to IPv6 destination".to_string()));
}
} else {
return Err(P2PError::Transport("IPv6 packet too short".to_string()));
}
};
self.encapsulate_ipv6_in_ipv4(ipv6_packet, dest_ipv4)
}
async fn decapsulate(&self, ipv4_packet: &[u8]) -> Result<Vec<u8>> {
if !self.is_active().await {
return Err(P2PError::Network("ISATAP tunnel not connected".to_string()));
}
self.decapsulate_ipv4_to_ipv6(ipv4_packet)
}
async fn send(&mut self, packet: &[u8]) -> Result<()> {
let socket = self.socket.as_ref().ok_or_else(|| {
P2PError::Network("ISATAP socket not available".to_string())
})?;
if packet.len() < 20 {
return Err(P2PError::Transport("Packet too short".to_string()));
}
let dest_bytes = &packet[16..20];
let dest_addr = Ipv4Addr::from(<[u8; 4]>::try_from(dest_bytes).unwrap());
let dest_port = 41;
socket.send_to(packet, (dest_addr, dest_port)).await
.map_err(|e| P2PError::Network(format!("Failed to send packet: {}", e)))?;
self.metrics.packets_sent += 1;
self.metrics.bytes_sent += packet.len() as u64;
Ok(())
}
async fn receive(&mut self) -> Result<Vec<u8>> {
let socket = self.socket.as_ref().ok_or_else(|| {
P2PError::Network("ISATAP socket not available".to_string())
})?;
let mut buffer = vec![0u8; 1500]; let (size, _) = socket.recv_from(&mut buffer).await
.map_err(|e| P2PError::Network(format!("Failed to receive packet: {}", e)))?;
buffer.truncate(size);
self.metrics.packets_received += 1;
self.metrics.bytes_received += size as u64;
Ok(buffer)
}
async fn maintain(&mut self) -> Result<()> {
if let Some(last_discovery) = self.last_router_discovery {
if last_discovery.elapsed() > DEFAULT_ROUTER_DISCOVERY_INTERVAL {
debug!("Performing periodic ISATAP router discovery");
let discovery_method = RouterDiscoveryMethod::DnsWellKnown(
std::env::var("ISATAP_DOMAIN").unwrap_or_else(|_| "corp.local".to_string())
);
if let Err(e) = self.discover_routers(discovery_method).await {
warn!("Periodic router discovery failed: {}", e);
}
}
}
if let Some(router) = &self.active_router {
if let Err(_) = self.test_router_reachability(router).await {
warn!("Active ISATAP router {} became unreachable", router.ipv4_addr);
self.active_router = None;
if let Err(e) = self.select_router().await {
warn!("Failed to select new ISATAP router: {}", e);
}
}
}
Ok(())
}
async fn local_ipv6_addr(&self) -> Result<Ipv6Addr> {
self.isatap_ipv6.ok_or_else(|| {
P2PError::Network("ISATAP IPv6 address not available".to_string())
})
}
async fn local_ipv4_addr(&self) -> Result<Ipv4Addr> {
self.local_ipv4.ok_or_else(|| {
P2PError::Network("Local IPv4 address not available".to_string())
})
}
async fn ping(&mut self, _timeout: Duration) -> Result<Duration> {
let router = self.active_router.as_ref().ok_or_else(|| {
P2PError::Network("No active ISATAP router for ping".to_string())
})?;
self.test_router_reachability(router).await
}
}