1#![deny(unsafe_op_in_unsafe_fn)]
4
5#[cfg(target_env = "musl")]
6type IoctlReq = libc::c_int;
7#[cfg(not(target_env = "musl"))]
8type IoctlReq = libc::c_ulong;
9
10use std::io;
11use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
12
13const TUN_PATH: &str = "/dev/net/tun";
14const IFF_TUN: libc::c_short = 0x0001;
15const IFF_TAP: libc::c_short = 0x0002;
16const IFF_MULTI_QUEUE: libc::c_short = 0x0100;
18const IFF_NO_PI: libc::c_short = 0x1000;
19const IFF_VNET_HDR: libc::c_short = 0x4000;
21const TUNSETIFF: libc::c_ulong = 0x4004_54ca;
23const TUNSETOFFLOAD: libc::c_ulong = 0x4004_54d0;
25pub const TUNSETVNETHDRSZ: libc::c_ulong = 0x4004_54d8;
27pub const TUNSETVNETHDR: libc::c_ulong = TUNSETVNETHDRSZ;
29const TUN_F_CSUM: libc::c_uint = 0x01;
30const TUN_F_TSO4: libc::c_uint = 0x02;
31const TUN_F_TSO6: libc::c_uint = 0x04;
32
33pub const VNET_HDR_LEN: usize = 10;
37
38#[derive(Debug, thiserror::Error)]
40pub enum DeviceError {
41 #[error("interface name too long")]
43 NameTooLong,
44 #[error("device io error: {0}")]
46 Io(#[from] io::Error),
47}
48
49fn encode_ifname(name: &str) -> Result<[u8; libc::IFNAMSIZ], DeviceError> {
51 let bytes = name.as_bytes();
52 if bytes.len() >= libc::IFNAMSIZ {
53 return Err(DeviceError::NameTooLong);
54 }
55 let mut buf = [0u8; libc::IFNAMSIZ];
56 buf[..bytes.len()].copy_from_slice(bytes);
57 Ok(buf)
58}
59
60fn decode_ifname(buf: &[u8; libc::IFNAMSIZ]) -> Result<String, DeviceError> {
62 let nul_pos = buf.iter().position(|&b| b == 0).unwrap_or(buf.len());
63 let s = std::str::from_utf8(&buf[..nul_pos])
64 .map_err(|e| DeviceError::Io(io::Error::new(io::ErrorKind::InvalidData, e)))?;
65 Ok(s.to_owned())
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69enum OffloadMode {
70 Full,
71 CsumOnly,
72}
73
74fn negotiate_offload(fd: std::os::fd::RawFd) -> Option<OffloadMode> {
75 let full = unsafe {
78 libc::ioctl(
79 fd,
80 TUNSETOFFLOAD as IoctlReq,
81 libc::c_ulong::from(TUN_F_CSUM | TUN_F_TSO4 | TUN_F_TSO6),
82 )
83 };
84 if full == 0 {
85 return Some(OffloadMode::Full);
86 }
87
88 let csum_only = unsafe {
90 libc::ioctl(
91 fd,
92 TUNSETOFFLOAD as IoctlReq,
93 libc::c_ulong::from(TUN_F_CSUM),
94 )
95 };
96 if csum_only == 0 {
97 return Some(OffloadMode::CsumOnly);
98 }
99
100 None
101}
102
103fn apply_offload(fd: std::os::fd::RawFd, mode: OffloadMode) -> Result<(), DeviceError> {
104 let flags = match mode {
105 OffloadMode::Full => libc::c_ulong::from(TUN_F_CSUM | TUN_F_TSO4 | TUN_F_TSO6),
106 OffloadMode::CsumOnly => libc::c_ulong::from(TUN_F_CSUM),
107 };
108 let rc = unsafe { libc::ioctl(fd, TUNSETOFFLOAD as IoctlReq, flags) };
110 if rc != 0 {
111 return Err(DeviceError::Io(io::Error::last_os_error()));
112 }
113 Ok(())
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
118pub enum DeviceKind {
119 Tun,
121 Tap,
123}
124
125pub trait Device {
127 fn kind(&self) -> DeviceKind;
129 fn read_frame(&mut self, buf: &mut [u8]) -> std::io::Result<usize>;
131 fn write_frame(&mut self, frame: &[u8]) -> std::io::Result<usize>;
133}
134
135fn bring_up(ifname: &[u8; libc::IFNAMSIZ]) -> Result<(), DeviceError> {
139 #[repr(C)]
141 struct IfReqFlags {
142 name: [u8; libc::IFNAMSIZ],
143 flags: libc::c_short,
144 _pad: [u8; 22],
145 }
146 let mut req = IfReqFlags {
147 name: *ifname,
148 flags: 0,
149 _pad: [0; 22],
150 };
151
152 let err = unsafe {
156 let sock_fd = libc::socket(libc::AF_INET, libc::SOCK_DGRAM, 0);
157 if sock_fd < 0 {
158 return Err(DeviceError::Io(io::Error::last_os_error()));
159 }
160 let rc_get = libc::ioctl(sock_fd, libc::SIOCGIFFLAGS as IoctlReq, &raw mut req);
161 if rc_get == 0 {
162 req.flags |= (libc::IFF_UP | libc::IFF_RUNNING) as libc::c_short;
165 let rc_set = libc::ioctl(sock_fd, libc::SIOCSIFFLAGS as IoctlReq, &raw mut req);
166 libc::close(sock_fd);
167 if rc_set != 0 {
168 Some(io::Error::last_os_error())
169 } else {
170 None
171 }
172 } else {
173 let e = io::Error::last_os_error();
174 libc::close(sock_fd);
175 Some(e)
176 }
177 };
178 match err {
179 Some(e) => Err(DeviceError::Io(e)),
180 None => Ok(()),
181 }
182}
183
184fn set_device_mtu(ifname: &[u8; libc::IFNAMSIZ], mtu: u32) -> Result<(), DeviceError> {
186 #[repr(C)]
187 struct IfReqMtu {
188 name: [u8; libc::IFNAMSIZ],
189 mtu: libc::c_int,
190 _pad: [u8; 20],
191 }
192 let req = IfReqMtu {
193 name: *ifname,
194 mtu: mtu as libc::c_int,
195 _pad: [0; 20],
196 };
197
198 let err = unsafe {
202 let sock_fd = libc::socket(libc::AF_INET, libc::SOCK_DGRAM, 0);
203 if sock_fd < 0 {
204 return Err(DeviceError::Io(io::Error::last_os_error()));
205 }
206 let rc = libc::ioctl(sock_fd, libc::SIOCSIFMTU as IoctlReq, &req);
207 let e = if rc != 0 {
208 Some(io::Error::last_os_error())
209 } else {
210 None
211 };
212 libc::close(sock_fd);
213 e
214 };
215 match err {
216 Some(e) => Err(DeviceError::Io(e)),
217 None => Ok(()),
218 }
219}
220
221#[repr(C)]
223struct IfReq {
224 name: [u8; libc::IFNAMSIZ],
225 flags: libc::c_short,
226 _pad: [u8; 22],
227}
228
229pub struct TunTap {
231 file: std::fs::File,
232 kind: DeviceKind,
233 name: String,
234 vnet_hdr_len: Option<usize>,
237}
238
239impl TunTap {
240 pub fn create(
249 name: &str,
250 kind: DeviceKind,
251 want_vnet_hdr: bool,
252 ) -> Result<TunTap, DeviceError> {
253 let ifname = encode_ifname(name)?;
254
255 if want_vnet_hdr {
256 if let Some(tun) = Self::open_with_offload(kind, &ifname)? {
257 return Ok(tun);
258 }
259 }
260
261 Self::open_plain(kind, &ifname)
262 }
263
264 pub fn create_multi_queue(
275 name: &str,
276 kind: DeviceKind,
277 queue_count: usize,
278 want_vnet_hdr: bool,
279 ) -> Result<Vec<TunTap>, DeviceError> {
280 if queue_count == 0 {
281 return Err(DeviceError::Io(io::Error::new(
282 io::ErrorKind::InvalidInput,
283 "queue_count must be at least 1",
284 )));
285 }
286
287 if queue_count == 1 {
288 let tun = Self::create(name, kind, want_vnet_hdr)?;
289 tun.set_nonblocking()?;
290 return Ok(vec![tun]);
291 }
292
293 let ifname = encode_ifname(name)?;
294
295 if want_vnet_hdr {
296 if let Some(queues) = Self::open_multi_queue_with_offload(kind, &ifname, queue_count)? {
297 return Ok(queues);
298 }
299 }
300
301 Self::open_multi_queue_plain(kind, &ifname, queue_count)
302 }
303
304 fn open_with_offload(
313 kind: DeviceKind,
314 ifname: &[u8; libc::IFNAMSIZ],
315 ) -> Result<Option<TunTap>, DeviceError> {
316 let file = std::fs::OpenOptions::new()
317 .read(true)
318 .write(true)
319 .open(TUN_PATH)?;
320
321 let type_flag = match kind {
322 DeviceKind::Tun => IFF_TUN,
323 DeviceKind::Tap => IFF_TAP,
324 };
325 let mut req = IfReq {
326 name: *ifname,
327 flags: type_flag | IFF_NO_PI | IFF_VNET_HDR,
328 _pad: [0; 22],
329 };
330
331 let rc = unsafe { libc::ioctl(file.as_raw_fd(), TUNSETIFF as IoctlReq, &raw mut req) };
336 if rc != 0 {
337 return Ok(None);
340 }
341
342 let mut hdr_sz: libc::c_int = VNET_HDR_LEN as libc::c_int;
344 let _ = unsafe {
347 libc::ioctl(
348 file.as_raw_fd(),
349 TUNSETVNETHDRSZ as IoctlReq,
350 &raw mut hdr_sz,
351 )
352 };
353
354 if let Some(_mode) = negotiate_offload(file.as_raw_fd()) {
355 bring_up(&req.name)?;
356 let actual_name = decode_ifname(&req.name)?;
357 return Ok(Some(TunTap {
358 file,
359 kind,
360 name: actual_name,
361 vnet_hdr_len: Some(VNET_HDR_LEN),
362 }));
363 }
364
365 Ok(None)
369 }
370
371 fn open_plain(kind: DeviceKind, ifname: &[u8; libc::IFNAMSIZ]) -> Result<TunTap, DeviceError> {
373 let file = std::fs::OpenOptions::new()
374 .read(true)
375 .write(true)
376 .open(TUN_PATH)?;
377
378 let type_flag = match kind {
379 DeviceKind::Tun => IFF_TUN,
380 DeviceKind::Tap => IFF_TAP,
381 };
382 let mut req = IfReq {
383 name: *ifname,
384 flags: type_flag | IFF_NO_PI,
385 _pad: [0; 22],
386 };
387
388 let rc = unsafe { libc::ioctl(file.as_raw_fd(), TUNSETIFF as IoctlReq, &raw mut req) };
392 if rc != 0 {
393 return Err(DeviceError::Io(io::Error::last_os_error()));
394 }
395
396 bring_up(&req.name)?;
398 let actual_name = decode_ifname(&req.name)?;
399
400 Ok(TunTap {
401 file,
402 kind,
403 name: actual_name,
404 vnet_hdr_len: None,
405 })
406 }
407
408 fn open_multi_queue_with_offload(
409 kind: DeviceKind,
410 ifname: &[u8; libc::IFNAMSIZ],
411 queue_count: usize,
412 ) -> Result<Option<Vec<TunTap>>, DeviceError> {
413 let file0 = std::fs::OpenOptions::new()
414 .read(true)
415 .write(true)
416 .open(TUN_PATH)?;
417
418 let type_flag = match kind {
419 DeviceKind::Tun => IFF_TUN,
420 DeviceKind::Tap => IFF_TAP,
421 };
422 let mut req0 = IfReq {
423 name: *ifname,
424 flags: type_flag | IFF_NO_PI | IFF_MULTI_QUEUE | IFF_VNET_HDR,
425 _pad: [0; 22],
426 };
427
428 let rc = unsafe { libc::ioctl(file0.as_raw_fd(), TUNSETIFF as IoctlReq, &raw mut req0) };
430 if rc != 0 {
431 return Ok(None);
432 }
433
434 let mut hdr_sz0: libc::c_int = VNET_HDR_LEN as libc::c_int;
435 let _ = unsafe {
437 libc::ioctl(
438 file0.as_raw_fd(),
439 TUNSETVNETHDRSZ as IoctlReq,
440 &raw mut hdr_sz0,
441 )
442 };
443
444 let offload_mode = match negotiate_offload(file0.as_raw_fd()) {
445 Some(mode) => mode,
446 None => return Ok(None),
447 };
448
449 bring_up(&req0.name)?;
450 let actual_name = decode_ifname(&req0.name)?;
451
452 let q0 = TunTap {
453 file: file0,
454 kind,
455 name: actual_name.clone(),
456 vnet_hdr_len: Some(VNET_HDR_LEN),
457 };
458 q0.set_nonblocking()?;
459
460 let mut queues = Vec::with_capacity(queue_count);
461 queues.push(q0);
462
463 for _ in 1..queue_count {
464 let file = std::fs::OpenOptions::new()
465 .read(true)
466 .write(true)
467 .open(TUN_PATH)?;
468
469 let mut req = IfReq {
470 name: req0.name,
471 flags: type_flag | IFF_NO_PI | IFF_MULTI_QUEUE | IFF_VNET_HDR,
472 _pad: [0; 22],
473 };
474
475 let rc = unsafe { libc::ioctl(file.as_raw_fd(), TUNSETIFF as IoctlReq, &raw mut req) };
477 if rc != 0 {
478 return Err(DeviceError::Io(io::Error::last_os_error()));
479 }
480
481 let mut hdr_sz: libc::c_int = VNET_HDR_LEN as libc::c_int;
482 let _ = unsafe {
484 libc::ioctl(
485 file.as_raw_fd(),
486 TUNSETVNETHDRSZ as IoctlReq,
487 &raw mut hdr_sz,
488 )
489 };
490
491 apply_offload(file.as_raw_fd(), offload_mode)?;
492
493 let q = TunTap {
494 file,
495 kind,
496 name: actual_name.clone(),
497 vnet_hdr_len: Some(VNET_HDR_LEN),
498 };
499 q.set_nonblocking()?;
500 queues.push(q);
501 }
502
503 Ok(Some(queues))
504 }
505
506 fn open_multi_queue_plain(
507 kind: DeviceKind,
508 ifname: &[u8; libc::IFNAMSIZ],
509 queue_count: usize,
510 ) -> Result<Vec<TunTap>, DeviceError> {
511 let file0 = std::fs::OpenOptions::new()
512 .read(true)
513 .write(true)
514 .open(TUN_PATH)?;
515
516 let type_flag = match kind {
517 DeviceKind::Tun => IFF_TUN,
518 DeviceKind::Tap => IFF_TAP,
519 };
520 let mut req0 = IfReq {
521 name: *ifname,
522 flags: type_flag | IFF_NO_PI | IFF_MULTI_QUEUE,
523 _pad: [0; 22],
524 };
525
526 let rc = unsafe { libc::ioctl(file0.as_raw_fd(), TUNSETIFF as IoctlReq, &raw mut req0) };
528 if rc != 0 {
529 return Err(DeviceError::Io(io::Error::last_os_error()));
530 }
531
532 bring_up(&req0.name)?;
533 let actual_name = decode_ifname(&req0.name)?;
534
535 let q0 = TunTap {
536 file: file0,
537 kind,
538 name: actual_name.clone(),
539 vnet_hdr_len: None,
540 };
541 q0.set_nonblocking()?;
542
543 let mut queues = Vec::with_capacity(queue_count);
544 queues.push(q0);
545
546 for _ in 1..queue_count {
547 let file = std::fs::OpenOptions::new()
548 .read(true)
549 .write(true)
550 .open(TUN_PATH)?;
551
552 let mut req = IfReq {
553 name: req0.name,
554 flags: type_flag | IFF_NO_PI | IFF_MULTI_QUEUE,
555 _pad: [0; 22],
556 };
557
558 let rc = unsafe { libc::ioctl(file.as_raw_fd(), TUNSETIFF as IoctlReq, &raw mut req) };
560 if rc != 0 {
561 return Err(DeviceError::Io(io::Error::last_os_error()));
562 }
563
564 let q = TunTap {
565 file,
566 kind,
567 name: actual_name.clone(),
568 vnet_hdr_len: None,
569 };
570 q.set_nonblocking()?;
571 queues.push(q);
572 }
573
574 Ok(queues)
575 }
576
577 pub fn name(&self) -> &str {
579 &self.name
580 }
581
582 pub fn vnet_hdr_len(&self) -> Option<usize> {
586 self.vnet_hdr_len
587 }
588
589 pub fn as_raw_fd(&self) -> std::os::fd::RawFd {
597 self.file.as_raw_fd()
598 }
599
600 pub fn set_nonblocking(&self) -> Result<(), DeviceError> {
605 let rc = unsafe { libc::fcntl(self.as_raw_fd(), libc::F_SETFL, libc::O_NONBLOCK) };
609 if rc < 0 {
610 Err(DeviceError::Io(io::Error::last_os_error()))
611 } else {
612 Ok(())
613 }
614 }
615
616 pub fn set_mtu(&self, mtu: u32) -> Result<(), DeviceError> {
618 let ifname = encode_ifname(&self.name)?;
619 set_device_mtu(&ifname, mtu)
620 }
621}
622
623pub struct TunReader {
625 file: std::fs::File,
626}
627
628pub struct TunWriter {
630 file: std::fs::File,
631}
632
633impl TunReader {
634 pub fn read_frame(&mut self, buf: &mut [u8]) -> io::Result<usize> {
636 use std::io::Read;
637 self.file.read(buf)
638 }
639
640 pub fn as_raw_fd(&self) -> std::os::fd::RawFd {
642 self.file.as_raw_fd()
643 }
644}
645
646impl TunWriter {
647 pub fn write_frame(&mut self, frame: &[u8]) -> io::Result<usize> {
649 use std::io::Write;
650 self.file.write(frame)
651 }
652
653 pub fn as_raw_fd(&self) -> std::os::fd::RawFd {
655 self.file.as_raw_fd()
656 }
657}
658
659impl TunTap {
660 pub fn split(self) -> Result<(TunReader, TunWriter), DeviceError> {
663 let raw = self.file.as_raw_fd();
664 let dup = unsafe { libc::dup(raw) };
667 if dup < 0 {
668 return Err(DeviceError::Io(io::Error::last_os_error()));
669 }
670 let dup_file = std::fs::File::from(unsafe { OwnedFd::from_raw_fd(dup) });
672 let reader = TunReader { file: dup_file };
673 let writer = TunWriter { file: self.file };
674 Ok((reader, writer))
675 }
676}
677
678impl Device for TunTap {
682 fn kind(&self) -> DeviceKind {
683 self.kind
684 }
685 fn read_frame(&mut self, buf: &mut [u8]) -> io::Result<usize> {
686 use std::io::Read;
687 self.file.read(buf)
688 }
689 fn write_frame(&mut self, frame: &[u8]) -> io::Result<usize> {
690 use std::io::Write;
691 self.file.write(frame)
692 }
693}
694
695impl AsRawFd for TunTap {
696 fn as_raw_fd(&self) -> std::os::fd::RawFd {
697 self.file.as_raw_fd()
698 }
699}
700
701impl AsRawFd for TunReader {
702 fn as_raw_fd(&self) -> std::os::fd::RawFd {
703 self.file.as_raw_fd()
704 }
705}
706
707impl AsRawFd for TunWriter {
708 fn as_raw_fd(&self) -> std::os::fd::RawFd {
709 self.file.as_raw_fd()
710 }
711}
712
713#[cfg(test)]
714mod tests {
715 use super::*;
716
717 #[test]
718 fn device_kinds_are_distinct() {
719 assert_ne!(DeviceKind::Tun, DeviceKind::Tap);
720 }
721
722 #[test]
723 fn ifname_encodes_and_rejects_too_long() {
724 let enc = encode_ifname("yip0").unwrap();
725 assert_eq!(&enc[..4], b"yip0");
726 assert_eq!(enc[4], 0, "NUL-padded");
727 let long = "x".repeat(libc::IFNAMSIZ); assert!(matches!(
729 encode_ifname(&long),
730 Err(DeviceError::NameTooLong)
731 ));
732 }
733
734 fn can_create_devices() -> bool {
736 TunTap::create("yipcap0", DeviceKind::Tun, false)
737 .map(drop)
738 .is_ok()
739 }
740
741 #[test]
742 fn tun_create_roundtrips_a_write() {
743 if !can_create_devices() {
744 eprintln!("SKIP tun_create_roundtrips_a_write: needs CAP_NET_ADMIN (run under sudo)");
745 return;
746 }
747 let mut dev = TunTap::create("yiptun0", DeviceKind::Tun, false).unwrap();
748 assert_eq!(dev.kind(), DeviceKind::Tun);
749 assert_eq!(dev.name(), "yiptun0");
750 let pkt = [
752 0x45u8, 0, 0, 20, 0, 0, 0, 0, 64, 17, 0, 0, 10, 9, 9, 1, 10, 9, 9, 2,
753 ];
754 let n = dev.write_frame(&pkt).unwrap();
755 assert_eq!(n, pkt.len());
756 }
757
758 #[test]
759 fn tap_create_reports_l2_kind() {
760 if !can_create_devices() {
761 eprintln!("SKIP tap_create_reports_l2_kind: needs CAP_NET_ADMIN (run under sudo)");
762 return;
763 }
764 let dev = TunTap::create("yiptap0", DeviceKind::Tap, false).unwrap();
765 assert_eq!(dev.kind(), DeviceKind::Tap);
766 assert_eq!(dev.name(), "yiptap0");
767 }
768
769 #[test]
770 fn split_yields_independent_reader_writer() {
771 if !can_create_devices() {
772 eprintln!("SKIP split_yields_independent_reader_writer: needs CAP_NET_ADMIN");
773 return;
774 }
775 let dev = TunTap::create("yipsplit0", DeviceKind::Tun, false).unwrap();
776 let (_reader, mut writer) = dev.split().unwrap();
777 let pkt = [
779 0x45u8, 0, 0, 20, 0, 0, 0, 0, 64, 17, 0, 0, 10, 9, 9, 1, 10, 9, 9, 2,
780 ];
781 assert_eq!(writer.write_frame(&pkt).unwrap(), pkt.len());
782 }
783
784 #[test]
785 fn plain_create_reports_no_vnet_hdr() {
786 if !can_create_devices() {
787 eprintln!(
788 "SKIP plain_create_reports_no_vnet_hdr: needs CAP_NET_ADMIN (run under sudo)"
789 );
790 return;
791 }
792 let dev = TunTap::create("yipvnh0", DeviceKind::Tun, false).unwrap();
793 assert_eq!(
794 dev.vnet_hdr_len(),
795 None,
796 "want_vnet_hdr=false must never activate vnet_hdr framing"
797 );
798 }
799
800 #[test]
801 fn set_mtu_fails_on_too_long_name() {
802 let dummy_file = std::fs::File::open("/dev/null").unwrap();
803 let dev = TunTap {
804 file: dummy_file,
805 kind: DeviceKind::Tun,
806 name: "x".repeat(libc::IFNAMSIZ),
807 vnet_hdr_len: None,
808 };
809 assert!(matches!(dev.set_mtu(1500), Err(DeviceError::NameTooLong)));
810 }
811
812 #[test]
813 fn set_device_mtu_nonexistent_interface() {
814 let enc = encode_ifname("nonexistent0").unwrap();
815 let res = set_device_mtu(&enc, 1500);
816 assert!(matches!(res, Err(DeviceError::Io(_))));
817 }
818
819 #[test]
820 fn set_mtu_nonexistent_interface() {
821 let dummy_file = std::fs::File::open("/dev/null").unwrap();
822 let dev = TunTap {
823 file: dummy_file,
824 kind: DeviceKind::Tun,
825 name: "nonexistent0".to_owned(),
826 vnet_hdr_len: None,
827 };
828 assert!(matches!(dev.set_mtu(1500), Err(DeviceError::Io(_))));
829 }
830
831 #[test]
832 fn tun_set_mtu_succeeds_when_privileged() {
833 if !can_create_devices() {
834 eprintln!("SKIP tun_set_mtu_succeeds_when_privileged: needs CAP_NET_ADMIN");
835 return;
836 }
837 let dev = TunTap::create("yipmtu0", DeviceKind::Tun, false).unwrap();
838 assert!(dev.set_mtu(1400).is_ok());
839 assert!(dev.set_mtu(1500).is_ok());
840 }
841}