1#[cfg(not(target_os = "macos"))]
14#[cfg(any(target_os = "linux", target_os = "macos"))]
15pub(crate) fn run_tun_reader(runtime: TunReaderRuntime) {
16 #[cfg(target_os = "linux")]
17 if runtime.device.vnet_hdr() {
18 run_linux_vnet_tun_reader(runtime);
19 return;
20 }
21
22 let TunReaderRuntime {
23 mut device,
24 mtu,
25 our_addr,
26 tun_tx,
27 outbound_tx,
28 transport_mtu,
29 path_mtu_lookup,
30 } = runtime;
31 let read_buffer_len = device.read_buffer_len(mtu);
32 let (name, mut buf, max_mss) =
33 tun_reader_setup_with_buffer_len(device.name(), mtu, transport_mtu, read_buffer_len);
34
35 loop {
36 match device.read_packet(&mut buf) {
37 Ok(n) if n > 0 => {
38 crate::perf_profile::record_tun_read_packet(n);
39 if !handle_tun_packet(
40 &mut buf[..n],
41 max_mss,
42 &name,
43 our_addr,
44 &tun_tx,
45 &outbound_tx,
46 &path_mtu_lookup,
47 ) {
48 break;
49 }
50 }
51 Ok(_) => {}
52 Err(e) => {
53 if e.raw_os_error() != Some(libc::EFAULT) {
55 error!(name = %name, error = %e, "TUN read error");
56 }
57 break;
58 }
59 }
60 }
61}
62
63#[cfg(target_os = "linux")]
64fn run_linux_vnet_tun_reader(runtime: TunReaderRuntime) {
65 let TunReaderRuntime {
66 mut device,
67 mtu,
68 our_addr,
69 tun_tx,
70 outbound_tx,
71 transport_mtu,
72 path_mtu_lookup,
73 } = runtime;
74 let read_buffer_len = device.read_buffer_len(mtu);
75 let (name, mut buf, max_mss) =
76 tun_reader_setup_with_buffer_len(device.name(), mtu, transport_mtu, read_buffer_len);
77 let mut packets = Vec::with_capacity(64);
78
79 loop {
80 packets.clear();
81 match device.read_vnet_packets_into(&mut buf, &mut packets) {
82 Ok(n) if n > 0 => {
83 debug_assert_eq!(n, packets.len());
84 for packet in packets.drain(..) {
85 crate::perf_profile::record_tun_read_packet(packet.len());
86 if !handle_tun_packet_owned(
87 packet,
88 max_mss,
89 &name,
90 our_addr,
91 &tun_tx,
92 &outbound_tx,
93 &path_mtu_lookup,
94 ) {
95 return;
96 }
97 }
98 }
99 Ok(_) => {}
100 Err(e) => {
101 if e.raw_os_error() != Some(libc::EFAULT) {
102 error!(name = %name, error = %e, "Linux vnet TUN read error");
103 }
104 break;
105 }
106 }
107 }
108}
109
110#[cfg(target_os = "macos")]
115struct ShutdownFd(std::os::unix::io::RawFd);
116
117#[cfg(target_os = "macos")]
118impl Drop for ShutdownFd {
119 fn drop(&mut self) {
120 unsafe {
121 libc::close(self.0);
122 }
123 }
124}
125
126#[cfg(target_os = "macos")]
132pub(crate) fn run_tun_reader(runtime: TunReaderRuntime, shutdown_fd: std::os::unix::io::RawFd) {
133 let TunReaderRuntime {
134 mut device,
135 mtu,
136 our_addr,
137 tun_tx,
138 outbound_tx,
139 transport_mtu,
140 path_mtu_lookup,
141 } = runtime;
142 let _shutdown_fd = ShutdownFd(shutdown_fd);
143 let tun_fd = device.device().as_raw_fd();
144 let (name, mut buf, max_mss) = tun_reader_setup(device.name(), mtu, transport_mtu);
145
146 unsafe {
149 let flags = libc::fcntl(tun_fd, libc::F_GETFL);
150 if flags >= 0 {
151 libc::fcntl(tun_fd, libc::F_SETFL, flags | libc::O_NONBLOCK);
152 }
153 }
154
155 let nfds = tun_fd.max(shutdown_fd) + 1;
156
157 loop {
158 unsafe {
160 let mut read_fds: libc::fd_set = std::mem::zeroed();
161 libc::FD_ZERO(&mut read_fds);
162 libc::FD_SET(tun_fd, &mut read_fds);
163 libc::FD_SET(shutdown_fd, &mut read_fds);
164
165 let ret = libc::select(
166 nfds,
167 &mut read_fds,
168 std::ptr::null_mut(),
169 std::ptr::null_mut(),
170 std::ptr::null_mut(),
171 );
172 if ret < 0 {
173 let err = std::io::Error::last_os_error();
174 if err.kind() == std::io::ErrorKind::Interrupted {
175 continue;
176 }
177 error!(name = %name, error = %err, "TUN select error");
178 break;
179 }
180
181 if libc::FD_ISSET(shutdown_fd, &read_fds) {
183 debug!(name = %name, "TUN reader received shutdown signal");
184 break;
185 }
186 }
187
188 loop {
190 match device.read_packet(&mut buf) {
191 Ok(n) if n > 0 => {
192 crate::perf_profile::record_tun_read_packet(n);
193 if !handle_tun_packet(
194 &mut buf[..n],
195 max_mss,
196 &name,
197 our_addr,
198 &tun_tx,
199 &outbound_tx,
200 &path_mtu_lookup,
201 ) {
202 return; }
204 }
205 Ok(_) => break, Err(e) => {
207 if e.kind() == std::io::ErrorKind::WouldBlock {
208 break; }
210 if e.raw_os_error() != Some(libc::EBADF) {
212 error!(name = %name, error = %e, "TUN read error");
213 }
214 return; }
216 }
217 }
218 }
219 }
221
222#[cfg(any(target_os = "macos", windows))]
224fn tun_reader_setup(device_name: &str, mtu: u16, transport_mtu: u16) -> (String, Vec<u8>, u16) {
225 tun_reader_setup_with_buffer_len(
226 device_name,
227 mtu,
228 transport_mtu,
229 default_tun_read_buffer_len(mtu),
230 )
231}
232
233#[cfg(any(target_os = "linux", target_os = "macos", windows))]
234fn tun_reader_setup_with_buffer_len(
235 device_name: &str,
236 mtu: u16,
237 transport_mtu: u16,
238 read_buffer_len: usize,
239) -> (String, Vec<u8>, u16) {
240 use super::icmp::effective_ipv6_mtu;
241
242 let name = device_name.to_string();
243 let buf = vec![0u8; read_buffer_len];
244
245 const IPV6_HEADER: u16 = 40;
246 const TCP_HEADER: u16 = 20;
247 let effective_mtu = effective_ipv6_mtu(transport_mtu);
248 let max_mss = effective_mtu
249 .saturating_sub(IPV6_HEADER)
250 .saturating_sub(TCP_HEADER);
251
252 debug!(
253 name = %name,
254 tun_mtu = mtu,
255 transport_mtu = transport_mtu,
256 effective_mtu = effective_mtu,
257 max_mss = max_mss,
258 "TUN reader starting"
259 );
260
261 (name, buf, max_mss)
262}
263
264#[cfg(any(target_os = "linux", target_os = "macos", windows))]
265fn default_tun_read_buffer_len(mtu: u16) -> usize {
266 mtu as usize + 100
267}
268
269#[cfg(any(target_os = "linux", target_os = "macos", windows))]
271fn handle_tun_packet(
272 packet: &mut [u8],
273 max_mss: u16,
274 name: &str,
275 our_addr: FipsAddress,
276 tun_tx: &TunTx,
277 outbound_tx: &TunOutboundTx,
278 path_mtu_lookup: &PathMtuLookup,
279) -> bool {
280 match prepare_tun_packet(packet, max_mss, name, our_addr, path_mtu_lookup) {
281 TunPacketAction::Forward => {
282 if outbound_tx
283 .admit_from_tun_reader(tun_outbound_packet(packet))
284 .is_err()
285 {
286 return false; }
288 }
289 TunPacketAction::Icmp(response) => {
290 if tun_tx.send(response).is_err() {
291 return false;
292 }
293 }
294 TunPacketAction::Ignore => {}
295 }
296 true
297}
298
299#[cfg(target_os = "linux")]
300fn handle_tun_packet_owned(
301 mut packet: Vec<u8>,
302 max_mss: u16,
303 name: &str,
304 our_addr: FipsAddress,
305 tun_tx: &TunTx,
306 outbound_tx: &TunOutboundTx,
307 path_mtu_lookup: &PathMtuLookup,
308) -> bool {
309 match prepare_tun_packet(&mut packet, max_mss, name, our_addr, path_mtu_lookup) {
310 TunPacketAction::Forward => {
311 if outbound_tx
312 .admit_from_tun_reader(tun_outbound_packet_owned(packet))
313 .is_err()
314 {
315 return false;
316 }
317 }
318 TunPacketAction::Icmp(response) => {
319 if tun_tx.send(response).is_err() {
320 return false;
321 }
322 }
323 TunPacketAction::Ignore => {}
324 }
325 true
326}
327
328#[cfg(any(target_os = "linux", target_os = "macos", windows))]
329enum TunPacketAction {
330 Forward,
331 Icmp(Vec<u8>),
332 Ignore,
333}
334
335#[cfg(any(target_os = "linux", target_os = "macos", windows))]
336fn prepare_tun_packet(
337 packet: &mut [u8],
338 max_mss: u16,
339 name: &str,
340 our_addr: FipsAddress,
341 path_mtu_lookup: &PathMtuLookup,
342) -> TunPacketAction {
343 use super::icmp::{DestUnreachableCode, build_dest_unreachable, should_send_icmp_error};
344 use super::tcp_mss::clamp_tcp_mss;
345
346 log_ipv6_packet(packet);
347
348 if packet.len() < 40 || packet[0] >> 4 != 6 {
349 return TunPacketAction::Ignore;
350 }
351
352 if packet[24] == crate::identity::FIPS_ADDRESS_PREFIX {
353 let effective_max_mss = per_flow_max_mss(path_mtu_lookup, &packet[24..40], max_mss);
354 if clamp_tcp_mss(packet, effective_max_mss) {
355 trace!(name = %name, max_mss = effective_max_mss, "Clamped TCP MSS in SYN packet");
356 }
357 return TunPacketAction::Forward;
358 }
359
360 if should_send_icmp_error(packet)
361 && let Some(response) =
362 build_dest_unreachable(packet, DestUnreachableCode::NoRoute, our_addr.to_ipv6())
363 {
364 trace!(name = %name, len = response.len(), "Sending ICMPv6 Destination Unreachable (non-FIPS destination)");
365 return TunPacketAction::Icmp(response);
366 }
367
368 TunPacketAction::Ignore
369}
370
371#[cfg(any(test, target_os = "linux", target_os = "macos", windows))]
372fn tun_outbound_packet(packet: &[u8]) -> Vec<u8> {
373 let mut outbound = Vec::with_capacity(
374 packet
375 .len()
376 .saturating_add(TUN_OUTBOUND_PACKET_TAIL_RESERVE),
377 );
378 outbound.extend_from_slice(packet);
379 outbound
380}
381
382#[cfg(any(test, target_os = "linux"))]
383fn tun_outbound_packet_owned(mut packet: Vec<u8>) -> Vec<u8> {
384 let needed = packet
385 .len()
386 .saturating_add(TUN_OUTBOUND_PACKET_TAIL_RESERVE);
387 if packet.capacity() < needed {
388 packet.reserve(needed - packet.capacity());
389 }
390 packet
391}
392
393#[cfg(any(target_os = "linux", target_os = "macos"))]
394impl std::fmt::Debug for TunDevice {
395 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
396 f.debug_struct("TunDevice")
397 .field("name", &self.name)
398 .field("mtu", &self.mtu)
399 .field("address", &self.address)
400 .finish()
401 }
402}
403
404pub fn log_ipv6_packet(packet: &[u8]) {
406 if packet.len() < 40 {
407 debug!(len = packet.len(), "Received undersized packet");
408 return;
409 }
410
411 let version = packet[0] >> 4;
412 if version != 6 {
413 debug!(version, len = packet.len(), "Received non-IPv6 packet");
414 return;
415 }
416
417 let payload_len = u16::from_be_bytes([packet[4], packet[5]]);
418 let next_header = packet[6];
419 let hop_limit = packet[7];
420
421 let src = Ipv6Addr::from(<[u8; 16]>::try_from(&packet[8..24]).unwrap());
422 let dst = Ipv6Addr::from(<[u8; 16]>::try_from(&packet[24..40]).unwrap());
423
424 let protocol = match next_header {
425 6 => "TCP",
426 17 => "UDP",
427 58 => "ICMPv6",
428 _ => "other",
429 };
430
431 trace!("TUN packet received:");
432 trace!(" src: {}", src);
433 trace!(" dst: {}", dst);
434 trace!(" protocol: {} ({})", protocol, next_header);
435 trace!(" payload: {} bytes, hop_limit: {}", payload_len, hop_limit);
436}
437
438#[cfg(any(target_os = "linux", target_os = "macos"))]
444pub async fn shutdown_tun_interface(name: &str) -> Result<(), TunError> {
445 debug!("Shutting down TUN interface {}", name);
446 platform::delete_interface(name).await?;
447 debug!("TUN interface {} stopped", name);
448 Ok(())
449}
450
451#[cfg(windows)]
456pub(crate) use windows::run_tun_reader;
457#[cfg(windows)]
458pub use windows::{TunDevice, TunWriter, shutdown_tun_interface};
459
460#[cfg(not(any(target_os = "linux", target_os = "macos", windows)))]
461pub(crate) use unsupported::run_tun_reader;
462#[cfg(not(any(target_os = "linux", target_os = "macos", windows)))]
463pub use unsupported::{TunDevice, TunWriter, shutdown_tun_interface};
464
465pub(crate) struct TunReaderRuntime {
466 pub(crate) device: TunDevice,
467 pub(crate) mtu: u16,
468 pub(crate) our_addr: FipsAddress,
469 pub(crate) tun_tx: TunTx,
470 pub(crate) outbound_tx: TunOutboundTx,
471 pub(crate) transport_mtu: u16,
472 pub(crate) path_mtu_lookup: PathMtuLookup,
473}