mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-09 00:04:54 +00:00
Add Windows platform support (#45)
Gate platform-specific code behind cfg attributes and add full Windows
support: TUN device via wintun, TCP control socket on localhost:21210,
Windows Service lifecycle (--install-service/--uninstall-service/--service),
CI build and test matrix, and packaging with ZIP builder and PowerShell
service management scripts.
Key changes:
- Cargo.toml: move tun/libc/rtnetlink behind cfg(unix); add wintun and
windows-service dependencies for Windows
- upper/tun.rs: wintun-based TUN implementation with netsh configuration
for IPv6 address, MTU, and fd00::/8 routing
- control/mod.rs: split into unix_impl/windows_impl; Windows uses TCP on
localhost:21210 with shared connection handler
- bin/fips.rs: refactor main() into run_daemon() accepting a shutdown
signal; add Windows Service support via windows-service crate
- transport/udp/socket.rs: platform-gated modules; Windows uses
tokio::net::UdpSocket (kernel drop count unavailable, returns 0)
- transport/ethernet: gate to cfg(unix); add Windows stub types
- config: platform-conditional default paths (socket, hosts) for Windows
- CI: add windows-latest to build matrix and test-windows job with
cargo-nextest
- packaging/windows: build-zip.ps1, install-service.ps1,
uninstall-service.ps1, and package-windows.yml workflow
- README/docs: Windows build instructions, service management, and
control socket platform differences
Linux and macOS behavior is unchanged.
This commit is contained in:
@@ -17,7 +17,10 @@ use std::time::SystemTime;
|
||||
use tracing::{debug, info, warn};
|
||||
|
||||
/// Default path for the FIPS hosts file.
|
||||
#[cfg(unix)]
|
||||
pub const DEFAULT_HOSTS_PATH: &str = "/etc/fips/hosts";
|
||||
#[cfg(windows)]
|
||||
pub const DEFAULT_HOSTS_PATH: &str = r"C:\ProgramData\fips\hosts";
|
||||
|
||||
/// Bidirectional hostname ↔ npub mapping table.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
|
||||
+446
-17
@@ -3,17 +3,34 @@
|
||||
//! Manages the TUN device for sending and receiving IPv6 packets.
|
||||
//! The TUN interface presents FIPS addresses to the local system,
|
||||
//! allowing standard socket applications to communicate over the mesh.
|
||||
//!
|
||||
//! Platform-specific implementations:
|
||||
//! - Linux: Uses the `tun` crate with `rtnetlink` for interface configuration
|
||||
//! - macOS: Uses the `tun` crate with `ifconfig`/`route` for interface configuration
|
||||
//! - Windows: Uses the `wintun` crate for TUN device support
|
||||
|
||||
#[cfg(windows)]
|
||||
use crate::FipsAddress;
|
||||
#[cfg(unix)]
|
||||
use crate::{FipsAddress, TunConfig};
|
||||
#[cfg(unix)]
|
||||
use std::fs::File;
|
||||
#[cfg(unix)]
|
||||
use std::io::Read;
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
#[cfg(unix)]
|
||||
use std::io::Write;
|
||||
use std::net::Ipv6Addr;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd};
|
||||
use std::sync::mpsc;
|
||||
use thiserror::Error;
|
||||
use tracing::{debug, error, trace};
|
||||
#[cfg(unix)]
|
||||
use tracing::error;
|
||||
use tracing::{debug, trace};
|
||||
#[cfg(windows)]
|
||||
use tracing::{error, warn};
|
||||
#[cfg(unix)]
|
||||
use tun::Layer;
|
||||
|
||||
/// Channel sender for packets to be written to TUN.
|
||||
@@ -28,7 +45,7 @@ pub type TunOutboundRx = tokio::sync::mpsc::Receiver<Vec<u8>>;
|
||||
#[derive(Debug, Error)]
|
||||
pub enum TunError {
|
||||
#[error("failed to create TUN device: {0}")]
|
||||
Create(#[from] tun::Error),
|
||||
Create(#[source] Box<dyn std::error::Error + Send + Sync>),
|
||||
|
||||
#[error("failed to configure TUN device: {0}")]
|
||||
Configure(String),
|
||||
@@ -43,10 +60,18 @@ pub enum TunError {
|
||||
#[error("permission denied: {0}")]
|
||||
PermissionDenied(String),
|
||||
|
||||
#[cfg(unix)]
|
||||
#[error("IPv6 is disabled (set net.ipv6.conf.all.disable_ipv6=0)")]
|
||||
Ipv6Disabled,
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl From<tun::Error> for TunError {
|
||||
fn from(e: tun::Error) -> Self {
|
||||
TunError::Create(Box::new(e))
|
||||
}
|
||||
}
|
||||
|
||||
/// TUN device state.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TunState {
|
||||
@@ -71,7 +96,12 @@ impl std::fmt::Display for TunState {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Unix (Linux + macOS) TUN implementation
|
||||
// ============================================================================
|
||||
|
||||
/// FIPS TUN device wrapper.
|
||||
#[cfg(unix)]
|
||||
pub struct TunDevice {
|
||||
device: tun::Device,
|
||||
name: String,
|
||||
@@ -79,6 +109,7 @@ pub struct TunDevice {
|
||||
address: FipsAddress,
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl TunDevice {
|
||||
/// Create or open a TUN device.
|
||||
///
|
||||
@@ -227,6 +258,7 @@ impl TunDevice {
|
||||
/// Multiple producers can send packets via the TunTx channel.
|
||||
///
|
||||
/// Also performs TCP MSS clamping on inbound SYN-ACK packets.
|
||||
#[cfg(unix)]
|
||||
pub struct TunWriter {
|
||||
file: File,
|
||||
rx: mpsc::Receiver<Vec<u8>>,
|
||||
@@ -234,6 +266,7 @@ pub struct TunWriter {
|
||||
max_mss: u16,
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl TunWriter {
|
||||
/// Run the writer loop.
|
||||
///
|
||||
@@ -318,6 +351,7 @@ impl TunWriter {
|
||||
/// The loop exits when the TUN interface is deleted (EFAULT) or an unrecoverable
|
||||
/// error occurs.
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
#[cfg(unix)]
|
||||
pub fn run_tun_reader(
|
||||
mut device: TunDevice,
|
||||
mtu: u16,
|
||||
@@ -326,7 +360,7 @@ pub fn run_tun_reader(
|
||||
outbound_tx: TunOutboundTx,
|
||||
transport_mtu: u16,
|
||||
) {
|
||||
let (name, mut buf, max_mss) = tun_reader_setup(&device, mtu, transport_mtu);
|
||||
let (name, mut buf, max_mss) = tun_reader_setup(device.name(), mtu, transport_mtu);
|
||||
|
||||
loop {
|
||||
match device.read_packet(&mut buf) {
|
||||
@@ -387,7 +421,7 @@ pub fn run_tun_reader(
|
||||
) {
|
||||
let _shutdown_fd = ShutdownFd(shutdown_fd);
|
||||
let tun_fd = device.device().as_raw_fd();
|
||||
let (name, mut buf, max_mss) = tun_reader_setup(&device, mtu, transport_mtu);
|
||||
let (name, mut buf, max_mss) = tun_reader_setup(device.name(), mtu, transport_mtu);
|
||||
|
||||
// Set TUN fd to non-blocking so we can use select + read without blocking
|
||||
// past the point where select returns readable.
|
||||
@@ -463,11 +497,11 @@ pub fn run_tun_reader(
|
||||
// _shutdown_fd closes on drop
|
||||
}
|
||||
|
||||
/// Common setup for TUN reader: extracts name, allocates buffer, computes max MSS.
|
||||
fn tun_reader_setup(device: &TunDevice, mtu: u16, transport_mtu: u16) -> (String, Vec<u8>, u16) {
|
||||
/// Common setup for TUN reader: allocates buffer, computes max MSS.
|
||||
fn tun_reader_setup(device_name: &str, mtu: u16, transport_mtu: u16) -> (String, Vec<u8>, u16) {
|
||||
use super::icmp::effective_ipv6_mtu;
|
||||
|
||||
let name = device.name().to_string();
|
||||
let name = device_name.to_string();
|
||||
let buf = vec![0u8; mtu as usize + 100];
|
||||
|
||||
const IPV6_HEADER: u16 = 40;
|
||||
@@ -531,6 +565,17 @@ fn handle_tun_packet(
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl std::fmt::Debug for TunDevice {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("TunDevice")
|
||||
.field("name", &self.name)
|
||||
.field("mtu", &self.mtu)
|
||||
.field("address", &self.address)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Log basic information about an IPv6 packet at TRACE level.
|
||||
pub fn log_ipv6_packet(packet: &[u8]) {
|
||||
if packet.len() < 40 {
|
||||
@@ -570,6 +615,7 @@ pub fn log_ipv6_packet(packet: &[u8]) {
|
||||
/// This deletes the interface, which will cause any blocking reads
|
||||
/// to return an error. Use this for graceful shutdown when the TUN device
|
||||
/// has been moved to another thread.
|
||||
#[cfg(unix)]
|
||||
pub async fn shutdown_tun_interface(name: &str) -> Result<(), TunError> {
|
||||
debug!("Shutting down TUN interface {}", name);
|
||||
platform::delete_interface(name).await?;
|
||||
@@ -577,19 +623,402 @@ pub async fn shutdown_tun_interface(name: &str) -> Result<(), TunError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for TunDevice {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("TunDevice")
|
||||
.field("name", &self.name)
|
||||
.field("mtu", &self.mtu)
|
||||
.field("address", &self.address)
|
||||
.finish()
|
||||
// ============================================================================
|
||||
// Windows TUN implementation (wintun)
|
||||
// ============================================================================
|
||||
|
||||
#[cfg(windows)]
|
||||
mod windows_tun {
|
||||
use super::*;
|
||||
use crate::TunConfig;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// The Windows adapter name visible in network settings and used in netsh commands.
|
||||
pub(crate) const ADAPTER_NAME: &str = "FIPS";
|
||||
|
||||
/// Wintun ring buffer capacity in bytes. Must be a power of 2 between
|
||||
/// 0x20000 (128 KiB) and 0x4000000 (64 MiB). 2 MiB balances memory
|
||||
/// usage against burst tolerance.
|
||||
const WINTUN_RING_CAPACITY: u32 = 0x200000; // 2 MiB
|
||||
|
||||
/// FIPS TUN device wrapper (Windows/wintun).
|
||||
///
|
||||
/// Uses the wintun driver for userspace packet I/O on Windows. The wintun
|
||||
/// DLL must be present in the executable's directory or system PATH.
|
||||
/// Adapter creation requires Administrator privileges.
|
||||
///
|
||||
/// Unlike the Linux TUN which uses a file descriptor, wintun uses a
|
||||
/// session-based API with ring buffers for packet exchange.
|
||||
pub struct TunDevice {
|
||||
session: Arc<wintun::Session>,
|
||||
_adapter: Arc<wintun::Adapter>,
|
||||
name: String,
|
||||
mtu: u16,
|
||||
address: FipsAddress,
|
||||
}
|
||||
|
||||
impl TunDevice {
|
||||
/// Create a wintun TUN adapter and configure it with an IPv6 address.
|
||||
///
|
||||
/// Loads the wintun DLL, creates (or reopens) a named adapter, starts
|
||||
/// a session with a 2 MiB ring buffer, and configures the interface
|
||||
/// via netsh. Requires Administrator privileges.
|
||||
pub async fn create(config: &TunConfig, address: FipsAddress) -> Result<Self, TunError> {
|
||||
let name = config.name();
|
||||
let mtu = config.mtu();
|
||||
|
||||
// Load the wintun DLL
|
||||
let wintun = unsafe { wintun::load() }.map_err(|e| {
|
||||
TunError::Create(
|
||||
format!(
|
||||
"Failed to load wintun.dll: {}. Download from https://www.wintun.net/",
|
||||
e
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
})?;
|
||||
|
||||
// Create or reopen the adapter.
|
||||
// First arg: adapter name visible in Windows network settings.
|
||||
// Second arg: tunnel type (internal identifier for wintun).
|
||||
let adapter = match wintun::Adapter::create(&wintun, ADAPTER_NAME, name, None) {
|
||||
Ok(a) => a,
|
||||
Err(e) => {
|
||||
return Err(TunError::Create(
|
||||
format!(
|
||||
"Failed to create wintun adapter '{}': {}. Run as Administrator.",
|
||||
name, e
|
||||
)
|
||||
.into(),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
// Start a session with the configured ring buffer capacity
|
||||
let session = adapter.start_session(WINTUN_RING_CAPACITY).map_err(|e| {
|
||||
TunError::Create(format!("Failed to start wintun session: {}", e).into())
|
||||
})?;
|
||||
|
||||
let session = Arc::new(session);
|
||||
|
||||
// Configure the IPv6 address and route via netsh.
|
||||
// Use the adapter name (ADAPTER_NAME) not the tunnel type name.
|
||||
let ipv6_addr = address.to_ipv6();
|
||||
configure_windows_interface(ADAPTER_NAME, ipv6_addr, mtu).await?;
|
||||
|
||||
Ok(Self {
|
||||
session,
|
||||
_adapter: adapter,
|
||||
name: name.to_string(),
|
||||
mtu,
|
||||
address,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the device name.
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
/// Get the configured MTU.
|
||||
pub fn mtu(&self) -> u16 {
|
||||
self.mtu
|
||||
}
|
||||
|
||||
/// Get the FIPS address assigned to this device.
|
||||
pub fn address(&self) -> &FipsAddress {
|
||||
&self.address
|
||||
}
|
||||
|
||||
/// Read a packet from the TUN device.
|
||||
///
|
||||
/// Blocks until a packet is available from the wintun session.
|
||||
/// Returns the number of bytes copied into `buf`.
|
||||
pub fn read_packet(&mut self, buf: &mut [u8]) -> Result<usize, TunError> {
|
||||
match self.session.receive_blocking() {
|
||||
Ok(packet) => {
|
||||
let bytes = packet.bytes();
|
||||
let len = bytes.len().min(buf.len());
|
||||
buf[..len].copy_from_slice(&bytes[..len]);
|
||||
Ok(len)
|
||||
}
|
||||
Err(e) => Err(TunError::Configure(format!("read failed: {}", e))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Shutdown the TUN device by removing the fd00::/8 route.
|
||||
///
|
||||
/// The wintun adapter and session are cleaned up when dropped.
|
||||
pub async fn shutdown(&self) -> Result<(), TunError> {
|
||||
debug!(name = %self.name, "Shutting down TUN device");
|
||||
let _ = tokio::process::Command::new("netsh")
|
||||
.args([
|
||||
"interface",
|
||||
"ipv6",
|
||||
"delete",
|
||||
"route",
|
||||
"fd00::/8",
|
||||
&format!("interface={}", ADAPTER_NAME),
|
||||
])
|
||||
.output()
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create a TunWriter for this device.
|
||||
///
|
||||
/// Clones the wintun session `Arc` so the writer can allocate and send
|
||||
/// packets independently. Returns the writer and a channel sender for
|
||||
/// submitting packets to be written.
|
||||
///
|
||||
/// The `max_mss` parameter is used for TCP MSS clamping on inbound packets.
|
||||
pub fn create_writer(&self, max_mss: u16) -> Result<(TunWriter, TunTx), TunError> {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
Ok((
|
||||
TunWriter {
|
||||
session: self.session.clone(),
|
||||
rx,
|
||||
name: self.name.clone(),
|
||||
max_mss,
|
||||
},
|
||||
tx,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for TunDevice {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("TunDevice")
|
||||
.field("name", &self.name)
|
||||
.field("mtu", &self.mtu)
|
||||
.field("address", &self.address)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Writer thread for TUN device (Windows).
|
||||
///
|
||||
/// Services a queue of outbound packets and writes them to the wintun
|
||||
/// session. Uses `allocate_send_packet()` / `send_packet()` instead of
|
||||
/// file I/O.
|
||||
///
|
||||
/// Also performs TCP MSS clamping on inbound SYN-ACK packets.
|
||||
pub struct TunWriter {
|
||||
session: Arc<wintun::Session>,
|
||||
rx: mpsc::Receiver<Vec<u8>>,
|
||||
name: String,
|
||||
max_mss: u16,
|
||||
}
|
||||
|
||||
impl TunWriter {
|
||||
/// Run the writer loop.
|
||||
///
|
||||
/// Blocks forever, reading packets from the channel and writing them
|
||||
/// to the wintun session. Returns when the channel is closed.
|
||||
pub fn run(self) {
|
||||
use crate::upper::tcp_mss::clamp_tcp_mss;
|
||||
|
||||
debug!(name = %self.name, max_mss = self.max_mss, "TUN writer starting");
|
||||
|
||||
for mut packet in self.rx {
|
||||
// Clamp TCP MSS on inbound SYN-ACK packets
|
||||
if clamp_tcp_mss(&mut packet, self.max_mss) {
|
||||
trace!(
|
||||
name = %self.name,
|
||||
max_mss = self.max_mss,
|
||||
"Clamped TCP MSS in inbound SYN-ACK packet"
|
||||
);
|
||||
}
|
||||
|
||||
let pkt_len = match u16::try_from(packet.len()) {
|
||||
Ok(len) => len,
|
||||
Err(_) => {
|
||||
warn!(name = %self.name, len = packet.len(), "Dropping oversized packet for TUN");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
match self.session.allocate_send_packet(pkt_len) {
|
||||
Ok(mut send_packet) => {
|
||||
send_packet.bytes_mut().copy_from_slice(&packet);
|
||||
self.session.send_packet(send_packet);
|
||||
trace!(name = %self.name, len = packet.len(), "TUN packet written");
|
||||
}
|
||||
Err(e) => {
|
||||
error!(name = %self.name, error = %e, "TUN write error (allocate)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// TUN packet reader loop (Windows).
|
||||
///
|
||||
/// Reads IPv6 packets from the wintun session. Packets destined for FIPS
|
||||
/// addresses (fd::/8) are forwarded to the Node via the outbound channel
|
||||
/// for session encapsulation and routing. Non-FIPS packets receive ICMPv6
|
||||
/// Destination Unreachable responses.
|
||||
///
|
||||
/// Also performs TCP MSS clamping on SYN packets to prevent oversized segments.
|
||||
///
|
||||
/// This is designed to run in a dedicated thread since wintun reads are blocking.
|
||||
/// The loop exits when the session is closed or an unrecoverable error occurs.
|
||||
pub fn run_tun_reader(
|
||||
mut device: TunDevice,
|
||||
mtu: u16,
|
||||
our_addr: FipsAddress,
|
||||
tun_tx: TunTx,
|
||||
outbound_tx: TunOutboundTx,
|
||||
transport_mtu: u16,
|
||||
) {
|
||||
let (name, mut buf, max_mss) = super::tun_reader_setup(device.name(), mtu, transport_mtu);
|
||||
|
||||
loop {
|
||||
match device.read_packet(&mut buf) {
|
||||
Ok(n) if n > 0 => {
|
||||
if !super::handle_tun_packet(
|
||||
&mut buf[..n],
|
||||
max_mss,
|
||||
&name,
|
||||
our_addr,
|
||||
&tun_tx,
|
||||
&outbound_tx,
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
let err_str = format!("{}", e);
|
||||
if !err_str.contains("Bad address") {
|
||||
error!(name = %name, error = %e, "TUN read error");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Shutdown and delete a TUN interface by name (Windows).
|
||||
///
|
||||
/// Removes the fd00::/8 route via netsh. The wintun adapter itself
|
||||
/// is cleaned up when the `Adapter` handle is dropped.
|
||||
pub async fn shutdown_tun_interface(name: &str) -> Result<(), TunError> {
|
||||
debug!("Shutting down TUN interface {}", name);
|
||||
let _ = tokio::process::Command::new("netsh")
|
||||
.args([
|
||||
"interface",
|
||||
"ipv6",
|
||||
"delete",
|
||||
"route",
|
||||
"fd00::/8",
|
||||
&format!("interface={}", ADAPTER_NAME),
|
||||
])
|
||||
.output()
|
||||
.await;
|
||||
let _ = name; // name is the tunnel type, not the adapter name
|
||||
debug!("TUN interface {} stopped", name);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Configure the Windows network interface with IPv6 address, MTU, and route.
|
||||
///
|
||||
/// Uses `netsh` commands to configure the wintun adapter. A brief delay
|
||||
/// is inserted before configuration to allow Windows to fully register
|
||||
/// the adapter in its network stack.
|
||||
///
|
||||
/// `adapter_name` must be the Windows adapter name (e.g. "FIPS"), not the
|
||||
/// wintun tunnel type name.
|
||||
async fn configure_windows_interface(
|
||||
adapter_name: &str,
|
||||
addr: Ipv6Addr,
|
||||
mtu: u16,
|
||||
) -> Result<(), TunError> {
|
||||
// Brief delay to let Windows fully register the adapter
|
||||
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
|
||||
|
||||
// Set IPv6 address
|
||||
let output = tokio::process::Command::new("netsh")
|
||||
.args([
|
||||
"interface",
|
||||
"ipv6",
|
||||
"add",
|
||||
"address",
|
||||
adapter_name,
|
||||
&format!("{}/128", addr),
|
||||
])
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| TunError::Configure(format!("netsh add address failed: {}", e)))?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
if !stderr.contains("already") && !stdout.contains("already") {
|
||||
warn!(
|
||||
"netsh add address failed: stdout={} stderr={}",
|
||||
stdout.trim(),
|
||||
stderr.trim()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Set MTU
|
||||
let output = tokio::process::Command::new("netsh")
|
||||
.args([
|
||||
"interface",
|
||||
"ipv6",
|
||||
"set",
|
||||
"subinterface",
|
||||
adapter_name,
|
||||
&format!("mtu={}", mtu),
|
||||
])
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| TunError::Configure(format!("netsh set mtu failed: {}", e)))?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
warn!(
|
||||
"netsh set mtu failed: stdout={} stderr={}",
|
||||
stdout.trim(),
|
||||
stderr.trim()
|
||||
);
|
||||
}
|
||||
|
||||
// Add route for fd00::/8 (FIPS address space) via this adapter
|
||||
let output = tokio::process::Command::new("netsh")
|
||||
.args([
|
||||
"interface",
|
||||
"ipv6",
|
||||
"add",
|
||||
"route",
|
||||
"fd00::/8",
|
||||
adapter_name,
|
||||
])
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| TunError::Configure(format!("netsh add route failed: {}", e)))?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
if !stderr.contains("already") && !stdout.contains("already") {
|
||||
warn!(
|
||||
"netsh add route failed: stdout={} stderr={}",
|
||||
stdout.trim(),
|
||||
stderr.trim()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Platform-specific TUN configuration
|
||||
// =============================================================================
|
||||
// Re-export Windows TUN types at module level
|
||||
#[cfg(windows)]
|
||||
pub use windows_tun::{TunDevice, TunWriter, run_tun_reader, shutdown_tun_interface};
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
mod platform {
|
||||
|
||||
Reference in New Issue
Block a user