1use crate::FipsAddress;
13#[cfg(any(
14 target_os = "linux",
15 target_os = "macos",
16 not(any(target_os = "linux", target_os = "macos", windows))
17))]
18use crate::TunConfig;
19use std::collections::HashMap;
20#[cfg(any(target_os = "linux", target_os = "macos"))]
21use std::fs::File;
22#[cfg(any(target_os = "linux", target_os = "macos"))]
23use std::io::Read;
24#[cfg(not(target_os = "macos"))]
25#[cfg(any(target_os = "linux", target_os = "macos"))]
26use std::io::Write;
27use std::net::Ipv6Addr;
28#[cfg(any(target_os = "linux", target_os = "macos"))]
29use std::os::unix::io::{AsRawFd, FromRawFd};
30use std::sync::{Arc, RwLock};
31use thiserror::Error;
32#[cfg(any(target_os = "linux", target_os = "macos"))]
33use tracing::error;
34use tracing::{debug, trace};
35#[cfg(windows)]
36use tracing::{error, warn};
37#[cfg(any(target_os = "linux", target_os = "macos"))]
38use tun::Layer;
39
40#[cfg(target_os = "linux")]
41use self::linux_vnet::{LinuxVnetTun, linux_vnet_tun_enabled};
42
43pub(crate) use super::tun_outbound::tun_outbound_channel;
44pub use super::tun_outbound::{TunOutboundRx, TunOutboundTx};
45#[cfg(any(test, target_os = "linux", target_os = "macos", windows))]
46pub(crate) use super::tun_write::TunRx;
47pub use super::tun_write::TunTx;
48pub(crate) use super::tun_write::write_channel;
49
50pub type PathMtuLookup = Arc<RwLock<HashMap<FipsAddress, u16>>>;
56
57#[cfg(any(test, target_os = "linux", target_os = "macos", windows))]
58const TUN_OUTBOUND_PACKET_TAIL_RESERVE: usize = 128;
59
60#[cfg(any(test, target_os = "linux", target_os = "macos", windows))]
86pub(crate) fn per_flow_max_mss(
87 lookup: &PathMtuLookup,
88 addr_bytes: &[u8],
89 global_max_mss: u16,
90) -> u16 {
91 use super::icmp::effective_ipv6_mtu;
92
93 const IPV6_MIN_MTU: u16 = 1280;
97 let conservative_max_mss = effective_ipv6_mtu(IPV6_MIN_MTU)
98 .saturating_sub(40)
99 .saturating_sub(20);
100 let empty_lookup_ceiling = std::cmp::min(global_max_mss, conservative_max_mss);
101
102 if addr_bytes.len() != 16 {
103 trace!(
104 len = addr_bytes.len(),
105 global_max_mss,
106 empty_lookup_ceiling,
107 "per_flow_max_mss: addr_bytes wrong length, fall back to conservative ceiling"
108 );
109 return empty_lookup_ceiling;
110 }
111 let Ok(fips_addr) = FipsAddress::from_slice(addr_bytes) else {
112 trace!(
113 global_max_mss,
114 empty_lookup_ceiling,
115 "per_flow_max_mss: FipsAddress::from_slice rejected (non-fd::/8 prefix), fall back to conservative ceiling"
116 );
117 return empty_lookup_ceiling;
118 };
119 let Ok(map) = lookup.read() else {
120 trace!(
121 fips_addr = %fips_addr,
122 global_max_mss,
123 empty_lookup_ceiling,
124 "per_flow_max_mss: lookup read lock poisoned, fall back to conservative ceiling"
125 );
126 return empty_lookup_ceiling;
127 };
128 let Some(&path_mtu) = map.get(&fips_addr) else {
129 trace!(
130 fips_addr = %fips_addr,
131 global_max_mss,
132 empty_lookup_ceiling,
133 map_len = map.len(),
134 "per_flow_max_mss: no path_mtu_lookup entry for destination, fall back to conservative ceiling"
135 );
136 return empty_lookup_ceiling;
137 };
138 let path_max_mss = effective_ipv6_mtu(path_mtu)
139 .saturating_sub(40)
140 .saturating_sub(20);
141 let result = std::cmp::min(global_max_mss, path_max_mss);
142 trace!(
143 fips_addr = %fips_addr,
144 path_mtu,
145 path_max_mss,
146 global_max_mss,
147 result,
148 "per_flow_max_mss: per-destination clamp applied"
149 );
150 result
151}
152
153#[derive(Debug, Error)]
155pub enum TunError {
156 #[error("failed to create TUN device: {0}")]
157 Create(#[source] Box<dyn std::error::Error + Send + Sync>),
158
159 #[error("failed to configure TUN device: {0}")]
160 Configure(String),
161
162 #[cfg(target_os = "linux")]
163 #[error("netlink error: {0}")]
164 Netlink(#[from] rtnetlink::Error),
165
166 #[error("interface not found: {0}")]
167 InterfaceNotFound(String),
168
169 #[error("permission denied: {0}")]
170 PermissionDenied(String),
171
172 #[cfg(any(target_os = "linux", target_os = "macos"))]
173 #[error("IPv6 is disabled (set net.ipv6.conf.all.disable_ipv6=0)")]
174 Ipv6Disabled,
175
176 #[error("system TUN is not supported on this platform")]
177 UnsupportedPlatform,
178}
179
180#[cfg(any(target_os = "linux", target_os = "macos"))]
181impl From<tun::Error> for TunError {
182 fn from(e: tun::Error) -> Self {
183 TunError::Create(Box::new(e))
184 }
185}
186
187#[derive(Debug, Clone, Copy, PartialEq, Eq)]
189pub enum TunState {
190 Disabled,
192 Configured,
194 Active,
196 Failed,
198}
199
200impl std::fmt::Display for TunState {
201 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202 match self {
203 TunState::Disabled => write!(f, "disabled"),
204 TunState::Configured => write!(f, "configured"),
205 TunState::Active => write!(f, "active"),
206 TunState::Failed => write!(f, "failed"),
207 }
208 }
209}
210
211#[cfg(any(target_os = "linux", target_os = "macos"))]
217pub struct TunDevice {
218 #[cfg(target_os = "linux")]
219 device: LinuxTunDevice,
220 #[cfg(target_os = "macos")]
221 device: tun::Device,
222 name: String,
223 mtu: u16,
224 address: FipsAddress,
225}
226
227#[cfg(target_os = "linux")]
228enum LinuxTunDevice {
229 Plain(tun::Device),
230 Vnet(LinuxVnetTun),
231}
232
233#[cfg(target_os = "linux")]
234impl LinuxTunDevice {
235 fn as_raw_fd(&self) -> std::os::unix::io::RawFd {
236 match self {
237 Self::Plain(device) => device.as_raw_fd(),
238 Self::Vnet(device) => device.as_raw_fd(),
239 }
240 }
241
242 fn read_packet(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error> {
243 match self {
244 Self::Plain(device) => {
245 let n = device.read(buf)?;
246 if n > 0 {
247 crate::perf_profile::record_tun_read_frame(n);
248 }
249 Ok(n)
250 }
251 Self::Vnet(device) => device.read_packet(buf),
252 }
253 }
254
255 fn read_vnet_packets_into(
256 &mut self,
257 buf: &mut [u8],
258 packets: &mut Vec<Vec<u8>>,
259 ) -> Result<usize, std::io::Error> {
260 match self {
261 Self::Plain(_) => unreachable!("Linux vnet packet batching requires a vnet TUN"),
262 Self::Vnet(device) => device.read_packets_into(buf, packets),
263 }
264 }
265
266 fn read_buffer_len(&self, mtu: u16) -> usize {
267 match self {
268 Self::Plain(_) => default_tun_read_buffer_len(mtu),
269 Self::Vnet(device) => device.read_buffer_len(),
270 }
271 }
272
273 fn vnet_hdr(&self) -> bool {
274 matches!(self, Self::Vnet(_))
275 }
276}
277
278#[cfg(any(target_os = "linux", target_os = "macos"))]
279impl TunDevice {
280 pub async fn create(config: &TunConfig, address: FipsAddress) -> Result<Self, TunError> {
287 if platform::is_ipv6_disabled() {
289 return Err(TunError::Ipv6Disabled);
290 }
291
292 let name = config.name();
293 let mtu = config.mtu();
294
295 if platform::interface_exists(name).await {
297 debug!(name, "Deleting existing TUN interface");
298 if let Err(e) = platform::delete_interface(name).await {
299 debug!(name, error = %e, "Failed to delete existing interface");
300 }
301 }
302
303 #[cfg(target_os = "linux")]
304 let (device, actual_name) = {
305 if linux_vnet_tun_enabled() {
306 let device =
307 LinuxVnetTun::create(name).map_err(|e| TunError::Create(Box::new(e)))?;
308 let actual_name = device.name().to_string();
309 (LinuxTunDevice::Vnet(device), actual_name)
310 } else {
311 let mut tun_config = tun::Configuration::default();
312 tun_config.tun_name(name).layer(Layer::L3).mtu(mtu);
313 let device = tun::create(&tun_config)?;
314 let actual_name = {
315 use tun::AbstractDevice;
316 device.tun_name().map_err(|e| {
317 TunError::Configure(format!("failed to get device name: {}", e))
318 })?
319 };
320 (LinuxTunDevice::Plain(device), actual_name)
321 }
322 };
323
324 #[cfg(target_os = "macos")]
325 let (device, actual_name) = {
326 let mut tun_config = tun::Configuration::default();
329 tun_config.layer(Layer::L3).mtu(mtu);
330 let device = tun::create(&tun_config)?;
331 let actual_name = {
332 use tun::AbstractDevice;
333 device
334 .tun_name()
335 .map_err(|e| TunError::Configure(format!("failed to get device name: {}", e)))?
336 };
337 (device, actual_name)
338 };
339
340 platform::configure_interface(&actual_name, address.to_ipv6(), mtu).await?;
342
343 Ok(Self {
344 device,
345 name: actual_name,
346 mtu,
347 address,
348 })
349 }
350
351 pub fn name(&self) -> &str {
353 &self.name
354 }
355
356 pub fn mtu(&self) -> u16 {
358 self.mtu
359 }
360
361 pub fn address(&self) -> &FipsAddress {
363 &self.address
364 }
365
366 #[cfg(target_os = "macos")]
368 pub fn device(&self) -> &tun::Device {
369 &self.device
370 }
371
372 #[cfg(target_os = "macos")]
374 pub fn device_mut(&mut self) -> &mut tun::Device {
375 &mut self.device
376 }
377
378 pub fn read_packet(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error> {
390 #[cfg(target_os = "linux")]
391 {
392 return self.device.read_packet(buf);
393 }
394
395 #[cfg(target_os = "macos")]
396 self.device.read(buf)
397 }
398
399 #[cfg(target_os = "linux")]
400 fn read_vnet_packets_into(
401 &mut self,
402 buf: &mut [u8],
403 packets: &mut Vec<Vec<u8>>,
404 ) -> Result<usize, std::io::Error> {
405 self.device.read_vnet_packets_into(buf, packets)
406 }
407
408 #[cfg(target_os = "linux")]
409 fn read_buffer_len(&self, mtu: u16) -> usize {
410 self.device.read_buffer_len(mtu)
411 }
412
413 #[cfg(target_os = "linux")]
414 fn vnet_hdr(&self) -> bool {
415 self.device.vnet_hdr()
416 }
417
418 #[cfg(any(target_os = "linux", target_os = "macos"))]
419 fn as_raw_fd(&self) -> std::os::unix::io::RawFd {
420 #[cfg(target_os = "linux")]
421 {
422 return self.device.as_raw_fd();
423 }
424
425 #[cfg(target_os = "macos")]
426 self.device.as_raw_fd()
427 }
428
429 pub async fn shutdown(&self) -> Result<(), TunError> {
433 debug!(name = %self.name, "Deleting TUN device");
434 platform::delete_interface(&self.name).await
435 }
436
437 pub fn create_writer(
449 &self,
450 max_mss: u16,
451 path_mtu_lookup: PathMtuLookup,
452 ) -> Result<(TunWriter, TunTx), TunError> {
453 let fd = self.as_raw_fd();
454
455 let write_fd = unsafe { libc::dup(fd) };
457 if write_fd < 0 {
458 return Err(TunError::Configure(format!(
459 "failed to dup fd: {}",
460 std::io::Error::last_os_error()
461 )));
462 }
463
464 let write_file = unsafe { File::from_raw_fd(write_fd) };
465 let (tx, rx) = write_channel();
466
467 Ok((
468 TunWriter {
469 file: write_file,
470 rx,
471 name: self.name.clone(),
472 max_mss,
473 path_mtu_lookup,
474 #[cfg(target_os = "linux")]
475 vnet_hdr: self.vnet_hdr(),
476 },
477 tx,
478 ))
479 }
480}
481
482#[cfg(target_os = "macos")]
486const UTUN_AF_INET6: u32 = 30;
487
488#[cfg(target_os = "macos")]
495#[inline]
496fn utun_af_inet6_header() -> [u8; 4] {
497 UTUN_AF_INET6.to_be_bytes()
498}
499
500#[cfg(all(test, target_os = "macos"))]
509#[inline]
510fn parse_utun_af_prefix(buf: &[u8]) -> Option<u32> {
511 if buf.len() < 4 {
512 return None;
513 }
514 Some(u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]))
515}
516
517#[cfg(any(target_os = "linux", target_os = "macos"))]
524pub struct TunWriter {
525 file: File,
526 rx: TunRx,
527 name: String,
528 max_mss: u16,
529 path_mtu_lookup: PathMtuLookup,
530 #[cfg(target_os = "linux")]
531 vnet_hdr: bool,
532}
533
534#[cfg(any(target_os = "linux", target_os = "macos"))]
535impl TunWriter {
536 fn clamp_inbound_packet(&self, packet: &mut super::tun_write::TunWritePacket) {
537 use super::tcp_mss::clamp_tcp_mss;
538
539 let effective_max_mss = if packet.len() >= 24 {
543 per_flow_max_mss(
544 &self.path_mtu_lookup,
545 &packet.as_slice()[8..24],
546 self.max_mss,
547 )
548 } else {
549 self.max_mss
550 };
551 if clamp_tcp_mss(packet.as_mut_slice(), effective_max_mss) {
553 trace!(
554 name = %self.name,
555 max_mss = effective_max_mss,
556 "Clamped TCP MSS in inbound SYN-ACK packet"
557 );
558 }
559 }
560
561 #[cfg(target_os = "linux")]
562 fn run_linux_vnet(mut self) {
563 use std::sync::mpsc::TryRecvError;
564
565 const LINUX_VNET_TUN_WRITE_BATCH_CAP: usize = 256;
566
567 debug!(name = %self.name, max_mss = self.max_mss, "Linux vnet TUN writer starting");
568
569 let mut batch = Vec::with_capacity(LINUX_VNET_TUN_WRITE_BATCH_CAP);
570 let mut write_preparer = linux_vnet::LinuxVnetWritePreparer::new();
571
572 while let Some(mut packet) = self.rx.recv() {
573 self.clamp_inbound_packet(&mut packet);
574 batch.push(packet);
575
576 while batch.len() < LINUX_VNET_TUN_WRITE_BATCH_CAP {
577 match self.rx.try_recv_packet() {
578 Ok(mut packet) => {
579 self.clamp_inbound_packet(&mut packet);
580 batch.push(packet);
581 }
582 Err(TryRecvError::Empty | TryRecvError::Disconnected) => break,
583 }
584 }
585
586 let write_result = {
587 linux_vnet::write_packet_slices_to_tun(
588 &mut self.file,
589 batch.iter().map(|packet| packet.as_slice()),
590 &mut write_preparer,
591 )
592 };
593
594 if let Err(e) = write_result {
595 let err_str = e.to_string();
596 if err_str.contains("Bad address") {
597 break;
598 }
599 error!(name = %self.name, error = %e, "Linux vnet TUN write error");
600 } else {
601 for packet in &batch {
602 crate::perf_profile::record_tun_write_packet(packet.len());
603 debug_ipv4_icmp_packet("Linux vnet TUN packet written", packet.as_slice());
604 trace!(name = %self.name, len = packet.len(), "TUN packet written");
605 }
606 }
607
608 batch.clear();
609 }
610 }
611
612 #[cfg(target_os = "macos")]
613 fn write_packet(&self, packet: &super::tun_write::TunWritePacket) -> std::io::Result<()> {
614 use std::os::unix::io::AsRawFd;
615
616 let af_header = utun_af_inet6_header();
617 let iov = [
618 libc::iovec {
619 iov_base: af_header.as_ptr() as *mut libc::c_void,
620 iov_len: 4,
621 },
622 libc::iovec {
623 iov_base: packet.as_slice().as_ptr() as *mut libc::c_void,
624 iov_len: packet.len(),
625 },
626 ];
627 let ret = unsafe { libc::writev(self.file.as_raw_fd(), iov.as_ptr(), 2) };
628 if ret < 0 {
629 return Err(std::io::Error::last_os_error());
630 }
631 let expected = 4 + packet.len();
632 if (ret as usize) < expected {
633 return Err(std::io::Error::new(
634 std::io::ErrorKind::WriteZero,
635 format!("short writev: {} of {} bytes", ret, expected),
636 ));
637 }
638 Ok(())
639 }
640
641 #[cfg(not(target_os = "macos"))]
642 fn write_packet(&mut self, packet: &super::tun_write::TunWritePacket) -> std::io::Result<()> {
643 self.file.write_all(packet.as_slice())
644 }
645
646 pub fn run(self) {
651 #[cfg(target_os = "linux")]
652 let mut writer = self;
653 #[cfg(target_os = "macos")]
654 let writer = self;
655
656 debug!(name = %writer.name, max_mss = writer.max_mss, "TUN writer starting");
657
658 #[cfg(target_os = "linux")]
659 if writer.vnet_hdr {
660 writer.run_linux_vnet();
661 return;
662 }
663
664 while let Some(mut packet) = writer.rx.recv() {
665 writer.clamp_inbound_packet(&mut packet);
666 let write_result = writer.write_packet(&packet);
667
668 if let Err(e) = write_result {
669 let err_str = e.to_string();
671 if err_str.contains("Bad address") {
672 break;
673 }
674 error!(name = %writer.name, error = %e, "TUN write error");
675 } else {
676 crate::perf_profile::record_tun_write_packet(packet.len());
677 debug_ipv4_icmp_packet("TUN packet written", packet.as_slice());
678 trace!(name = %writer.name, len = packet.len(), "TUN packet written");
679 }
680 }
681 }
682}
683
684#[cfg(any(target_os = "linux", target_os = "macos"))]
685fn debug_ipv4_icmp_packet(message: &'static str, packet: &[u8]) {
686 let Some((src, dst, icmp_type, icmp_id, icmp_seq)) = ipv4_icmp_echo(packet) else {
687 return;
688 };
689 debug!(
690 src = %src,
691 dst = %dst,
692 icmp_type,
693 icmp_id,
694 icmp_seq,
695 message
696 );
697}
698
699#[cfg(any(target_os = "linux", target_os = "macos"))]
700fn ipv4_icmp_echo(packet: &[u8]) -> Option<(std::net::Ipv4Addr, std::net::Ipv4Addr, u8, u16, u16)> {
701 if packet.len() < 28 || packet[0] >> 4 != 4 || packet[9] != 1 {
702 return None;
703 }
704 let header_len = usize::from(packet[0] & 0x0f).checked_mul(4)?;
705 if header_len < 20 || packet.len() < header_len.saturating_add(8) {
706 return None;
707 }
708 let icmp_type = packet[header_len];
709 if !matches!(icmp_type, 0 | 8) {
710 return None;
711 }
712 let src = std::net::Ipv4Addr::new(packet[12], packet[13], packet[14], packet[15]);
713 let dst = std::net::Ipv4Addr::new(packet[16], packet[17], packet[18], packet[19]);
714 let icmp_id = u16::from_be_bytes([packet[header_len + 4], packet[header_len + 5]]);
715 let icmp_seq = u16::from_be_bytes([packet[header_len + 6], packet[header_len + 7]]);
716 Some((src, dst, icmp_type, icmp_id, icmp_seq))
717}
718
719#[cfg(not(target_os = "macos"))]
732#[cfg(any(target_os = "linux", target_os = "macos"))]
733pub(crate) fn run_tun_reader(runtime: TunReaderRuntime) {
734 #[cfg(target_os = "linux")]
735 if runtime.device.vnet_hdr() {
736 run_linux_vnet_tun_reader(runtime);
737 return;
738 }
739
740 let TunReaderRuntime {
741 mut device,
742 mtu,
743 our_addr,
744 tun_tx,
745 outbound_tx,
746 transport_mtu,
747 path_mtu_lookup,
748 } = runtime;
749 let read_buffer_len = device.read_buffer_len(mtu);
750 let (name, mut buf, max_mss) =
751 tun_reader_setup_with_buffer_len(device.name(), mtu, transport_mtu, read_buffer_len);
752
753 loop {
754 match device.read_packet(&mut buf) {
755 Ok(n) if n > 0 => {
756 crate::perf_profile::record_tun_read_packet(n);
757 if !handle_tun_packet(
758 &mut buf[..n],
759 max_mss,
760 &name,
761 our_addr,
762 &tun_tx,
763 &outbound_tx,
764 &path_mtu_lookup,
765 ) {
766 break;
767 }
768 }
769 Ok(_) => {}
770 Err(e) => {
771 if e.raw_os_error() != Some(libc::EFAULT) {
773 error!(name = %name, error = %e, "TUN read error");
774 }
775 break;
776 }
777 }
778 }
779}
780
781#[cfg(target_os = "linux")]
782fn run_linux_vnet_tun_reader(runtime: TunReaderRuntime) {
783 let TunReaderRuntime {
784 mut device,
785 mtu,
786 our_addr,
787 tun_tx,
788 outbound_tx,
789 transport_mtu,
790 path_mtu_lookup,
791 } = runtime;
792 let read_buffer_len = device.read_buffer_len(mtu);
793 let (name, mut buf, max_mss) =
794 tun_reader_setup_with_buffer_len(device.name(), mtu, transport_mtu, read_buffer_len);
795 let mut packets = Vec::with_capacity(64);
796
797 loop {
798 packets.clear();
799 match device.read_vnet_packets_into(&mut buf, &mut packets) {
800 Ok(n) if n > 0 => {
801 debug_assert_eq!(n, packets.len());
802 for packet in packets.drain(..) {
803 crate::perf_profile::record_tun_read_packet(packet.len());
804 if !handle_tun_packet_owned(
805 packet,
806 max_mss,
807 &name,
808 our_addr,
809 &tun_tx,
810 &outbound_tx,
811 &path_mtu_lookup,
812 ) {
813 return;
814 }
815 }
816 }
817 Ok(_) => {}
818 Err(e) => {
819 if e.raw_os_error() != Some(libc::EFAULT) {
820 error!(name = %name, error = %e, "Linux vnet TUN read error");
821 }
822 break;
823 }
824 }
825 }
826}
827
828#[cfg(target_os = "macos")]
833struct ShutdownFd(std::os::unix::io::RawFd);
834
835#[cfg(target_os = "macos")]
836impl Drop for ShutdownFd {
837 fn drop(&mut self) {
838 unsafe {
839 libc::close(self.0);
840 }
841 }
842}
843
844#[cfg(target_os = "macos")]
850pub(crate) fn run_tun_reader(runtime: TunReaderRuntime, shutdown_fd: std::os::unix::io::RawFd) {
851 let TunReaderRuntime {
852 mut device,
853 mtu,
854 our_addr,
855 tun_tx,
856 outbound_tx,
857 transport_mtu,
858 path_mtu_lookup,
859 } = runtime;
860 let _shutdown_fd = ShutdownFd(shutdown_fd);
861 let tun_fd = device.device().as_raw_fd();
862 let (name, mut buf, max_mss) = tun_reader_setup(device.name(), mtu, transport_mtu);
863
864 unsafe {
867 let flags = libc::fcntl(tun_fd, libc::F_GETFL);
868 if flags >= 0 {
869 libc::fcntl(tun_fd, libc::F_SETFL, flags | libc::O_NONBLOCK);
870 }
871 }
872
873 let nfds = tun_fd.max(shutdown_fd) + 1;
874
875 loop {
876 unsafe {
878 let mut read_fds: libc::fd_set = std::mem::zeroed();
879 libc::FD_ZERO(&mut read_fds);
880 libc::FD_SET(tun_fd, &mut read_fds);
881 libc::FD_SET(shutdown_fd, &mut read_fds);
882
883 let ret = libc::select(
884 nfds,
885 &mut read_fds,
886 std::ptr::null_mut(),
887 std::ptr::null_mut(),
888 std::ptr::null_mut(),
889 );
890 if ret < 0 {
891 let err = std::io::Error::last_os_error();
892 if err.kind() == std::io::ErrorKind::Interrupted {
893 continue;
894 }
895 error!(name = %name, error = %err, "TUN select error");
896 break;
897 }
898
899 if libc::FD_ISSET(shutdown_fd, &read_fds) {
901 debug!(name = %name, "TUN reader received shutdown signal");
902 break;
903 }
904 }
905
906 loop {
908 match device.read_packet(&mut buf) {
909 Ok(n) if n > 0 => {
910 crate::perf_profile::record_tun_read_packet(n);
911 if !handle_tun_packet(
912 &mut buf[..n],
913 max_mss,
914 &name,
915 our_addr,
916 &tun_tx,
917 &outbound_tx,
918 &path_mtu_lookup,
919 ) {
920 return; }
922 }
923 Ok(_) => break, Err(e) => {
925 if e.kind() == std::io::ErrorKind::WouldBlock {
926 break; }
928 if e.raw_os_error() != Some(libc::EBADF) {
930 error!(name = %name, error = %e, "TUN read error");
931 }
932 return; }
934 }
935 }
936 }
937 }
939
940#[cfg(any(target_os = "macos", windows))]
942fn tun_reader_setup(device_name: &str, mtu: u16, transport_mtu: u16) -> (String, Vec<u8>, u16) {
943 tun_reader_setup_with_buffer_len(
944 device_name,
945 mtu,
946 transport_mtu,
947 default_tun_read_buffer_len(mtu),
948 )
949}
950
951#[cfg(any(target_os = "linux", target_os = "macos", windows))]
952fn tun_reader_setup_with_buffer_len(
953 device_name: &str,
954 mtu: u16,
955 transport_mtu: u16,
956 read_buffer_len: usize,
957) -> (String, Vec<u8>, u16) {
958 use super::icmp::effective_ipv6_mtu;
959
960 let name = device_name.to_string();
961 let buf = vec![0u8; read_buffer_len];
962
963 const IPV6_HEADER: u16 = 40;
964 const TCP_HEADER: u16 = 20;
965 let effective_mtu = effective_ipv6_mtu(transport_mtu);
966 let max_mss = effective_mtu
967 .saturating_sub(IPV6_HEADER)
968 .saturating_sub(TCP_HEADER);
969
970 debug!(
971 name = %name,
972 tun_mtu = mtu,
973 transport_mtu = transport_mtu,
974 effective_mtu = effective_mtu,
975 max_mss = max_mss,
976 "TUN reader starting"
977 );
978
979 (name, buf, max_mss)
980}
981
982#[cfg(any(target_os = "linux", target_os = "macos", windows))]
983fn default_tun_read_buffer_len(mtu: u16) -> usize {
984 mtu as usize + 100
985}
986
987#[cfg(any(target_os = "linux", target_os = "macos", windows))]
989fn handle_tun_packet(
990 packet: &mut [u8],
991 max_mss: u16,
992 name: &str,
993 our_addr: FipsAddress,
994 tun_tx: &TunTx,
995 outbound_tx: &TunOutboundTx,
996 path_mtu_lookup: &PathMtuLookup,
997) -> bool {
998 match prepare_tun_packet(packet, max_mss, name, our_addr, path_mtu_lookup) {
999 TunPacketAction::Forward => {
1000 if outbound_tx
1001 .admit_from_tun_reader(tun_outbound_packet(packet))
1002 .is_err()
1003 {
1004 return false; }
1006 }
1007 TunPacketAction::Icmp(response) => {
1008 if tun_tx.send(response).is_err() {
1009 return false;
1010 }
1011 }
1012 TunPacketAction::Ignore => {}
1013 }
1014 true
1015}
1016
1017#[cfg(target_os = "linux")]
1018fn handle_tun_packet_owned(
1019 mut packet: Vec<u8>,
1020 max_mss: u16,
1021 name: &str,
1022 our_addr: FipsAddress,
1023 tun_tx: &TunTx,
1024 outbound_tx: &TunOutboundTx,
1025 path_mtu_lookup: &PathMtuLookup,
1026) -> bool {
1027 match prepare_tun_packet(&mut packet, max_mss, name, our_addr, path_mtu_lookup) {
1028 TunPacketAction::Forward => {
1029 if outbound_tx
1030 .admit_from_tun_reader(tun_outbound_packet_owned(packet))
1031 .is_err()
1032 {
1033 return false;
1034 }
1035 }
1036 TunPacketAction::Icmp(response) => {
1037 if tun_tx.send(response).is_err() {
1038 return false;
1039 }
1040 }
1041 TunPacketAction::Ignore => {}
1042 }
1043 true
1044}
1045
1046#[cfg(any(target_os = "linux", target_os = "macos", windows))]
1047enum TunPacketAction {
1048 Forward,
1049 Icmp(Vec<u8>),
1050 Ignore,
1051}
1052
1053#[cfg(any(target_os = "linux", target_os = "macos", windows))]
1054fn prepare_tun_packet(
1055 packet: &mut [u8],
1056 max_mss: u16,
1057 name: &str,
1058 our_addr: FipsAddress,
1059 path_mtu_lookup: &PathMtuLookup,
1060) -> TunPacketAction {
1061 use super::icmp::{DestUnreachableCode, build_dest_unreachable, should_send_icmp_error};
1062 use super::tcp_mss::clamp_tcp_mss;
1063
1064 log_ipv6_packet(packet);
1065
1066 if packet.len() < 40 || packet[0] >> 4 != 6 {
1067 return TunPacketAction::Ignore;
1068 }
1069
1070 if packet[24] == crate::identity::FIPS_ADDRESS_PREFIX {
1071 let effective_max_mss = per_flow_max_mss(path_mtu_lookup, &packet[24..40], max_mss);
1072 if clamp_tcp_mss(packet, effective_max_mss) {
1073 trace!(name = %name, max_mss = effective_max_mss, "Clamped TCP MSS in SYN packet");
1074 }
1075 return TunPacketAction::Forward;
1076 }
1077
1078 if should_send_icmp_error(packet)
1079 && let Some(response) =
1080 build_dest_unreachable(packet, DestUnreachableCode::NoRoute, our_addr.to_ipv6())
1081 {
1082 trace!(name = %name, len = response.len(), "Sending ICMPv6 Destination Unreachable (non-FIPS destination)");
1083 return TunPacketAction::Icmp(response);
1084 }
1085
1086 TunPacketAction::Ignore
1087}
1088
1089#[cfg(any(test, target_os = "linux", target_os = "macos", windows))]
1090fn tun_outbound_packet(packet: &[u8]) -> Vec<u8> {
1091 let mut outbound = Vec::with_capacity(
1092 packet
1093 .len()
1094 .saturating_add(TUN_OUTBOUND_PACKET_TAIL_RESERVE),
1095 );
1096 outbound.extend_from_slice(packet);
1097 outbound
1098}
1099
1100#[cfg(any(test, target_os = "linux"))]
1101fn tun_outbound_packet_owned(mut packet: Vec<u8>) -> Vec<u8> {
1102 let needed = packet
1103 .len()
1104 .saturating_add(TUN_OUTBOUND_PACKET_TAIL_RESERVE);
1105 if packet.capacity() < needed {
1106 packet.reserve(needed - packet.capacity());
1107 }
1108 packet
1109}
1110
1111#[cfg(any(target_os = "linux", target_os = "macos"))]
1112impl std::fmt::Debug for TunDevice {
1113 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1114 f.debug_struct("TunDevice")
1115 .field("name", &self.name)
1116 .field("mtu", &self.mtu)
1117 .field("address", &self.address)
1118 .finish()
1119 }
1120}
1121
1122pub fn log_ipv6_packet(packet: &[u8]) {
1124 if packet.len() < 40 {
1125 debug!(len = packet.len(), "Received undersized packet");
1126 return;
1127 }
1128
1129 let version = packet[0] >> 4;
1130 if version != 6 {
1131 debug!(version, len = packet.len(), "Received non-IPv6 packet");
1132 return;
1133 }
1134
1135 let payload_len = u16::from_be_bytes([packet[4], packet[5]]);
1136 let next_header = packet[6];
1137 let hop_limit = packet[7];
1138
1139 let src = Ipv6Addr::from(<[u8; 16]>::try_from(&packet[8..24]).unwrap());
1140 let dst = Ipv6Addr::from(<[u8; 16]>::try_from(&packet[24..40]).unwrap());
1141
1142 let protocol = match next_header {
1143 6 => "TCP",
1144 17 => "UDP",
1145 58 => "ICMPv6",
1146 _ => "other",
1147 };
1148
1149 trace!("TUN packet received:");
1150 trace!(" src: {}", src);
1151 trace!(" dst: {}", dst);
1152 trace!(" protocol: {} ({})", protocol, next_header);
1153 trace!(" payload: {} bytes, hop_limit: {}", payload_len, hop_limit);
1154}
1155
1156#[cfg(any(target_os = "linux", target_os = "macos"))]
1162pub async fn shutdown_tun_interface(name: &str) -> Result<(), TunError> {
1163 debug!("Shutting down TUN interface {}", name);
1164 platform::delete_interface(name).await?;
1165 debug!("TUN interface {} stopped", name);
1166 Ok(())
1167}
1168
1169#[cfg(windows)]
1174mod windows;
1175#[cfg(windows)]
1176pub(crate) use windows::run_tun_reader;
1177#[cfg(windows)]
1178pub use windows::{TunDevice, TunWriter, shutdown_tun_interface};
1179
1180#[cfg(not(any(target_os = "linux", target_os = "macos", windows)))]
1181mod unsupported;
1182#[cfg(not(any(target_os = "linux", target_os = "macos", windows)))]
1183pub(crate) use unsupported::run_tun_reader;
1184#[cfg(not(any(target_os = "linux", target_os = "macos", windows)))]
1185pub use unsupported::{TunDevice, TunWriter, shutdown_tun_interface};
1186
1187pub(crate) struct TunReaderRuntime {
1188 pub(crate) device: TunDevice,
1189 pub(crate) mtu: u16,
1190 pub(crate) our_addr: FipsAddress,
1191 pub(crate) tun_tx: TunTx,
1192 pub(crate) outbound_tx: TunOutboundTx,
1193 pub(crate) transport_mtu: u16,
1194 pub(crate) path_mtu_lookup: PathMtuLookup,
1195}
1196
1197#[cfg(target_os = "linux")]
1198mod linux_vnet;
1199#[cfg(any(target_os = "linux", target_os = "macos"))]
1200mod platform;
1201
1202#[cfg(test)]
1203mod tests;