1#![cfg(target_os = "linux")]
4
5use std::os::unix::io::RawFd;
6
7use crate::backend::{NetBackend, NetOffloadFlags};
8use crate::header::NetPacket;
9
10const TUNSETOFFLOAD: libc::c_ulong = 0x400454d0;
14const TUNSETVNETHDRSZ: libc::c_ulong = 0x400454d8;
15
16const TUN_F_CSUM: u32 = 0x01;
17const TUN_F_TSO4: u32 = 0x02;
18const TUN_F_TSO6: u32 = 0x04;
19const TUN_F_TSO_ECN: u32 = 0x08;
20const TUN_F_UFO: u32 = 0x10;
21
22pub struct TapBackend {
24 fd: RawFd,
26 name: String,
28 nonblocking: bool,
30}
31
32impl TapBackend {
33 pub fn new(name: Option<&str>) -> std::io::Result<Self> {
39 let fd: RawFd = unsafe {
42 libc::open(
43 b"/dev/net/tun\0".as_ptr() as *const libc::c_char,
44 libc::O_RDWR | libc::O_CLOEXEC,
45 )
46 };
47
48 if fd < 0 {
49 return Err(std::io::Error::last_os_error());
50 }
51
52 #[repr(C)]
53 struct Ifreq {
54 ifr_name: [libc::c_char; libc::IFNAMSIZ],
55 ifr_flags: libc::c_short,
56 _padding: [u8; 22], }
58
59 let mut ifr = Ifreq {
60 ifr_name: [0; libc::IFNAMSIZ],
61 ifr_flags: (libc::IFF_TAP | libc::IFF_NO_PI) as libc::c_short,
62 _padding: [0; 22],
63 };
64
65 if let Some(dev_name) = name {
66 let name_bytes = dev_name.as_bytes();
67 let len = name_bytes.len().min(libc::IFNAMSIZ - 1);
68 for (i, &b) in name_bytes[..len].iter().enumerate() {
69 ifr.ifr_name[i] = b as libc::c_char;
70 }
71 }
72
73 const TUNSETIFF: libc::c_ulong = 0x400454ca;
75 let ret = unsafe { libc::ioctl(fd, TUNSETIFF, &ifr) };
78 if ret < 0 {
79 unsafe { libc::close(fd) };
80 return Err(std::io::Error::last_os_error());
81 }
82
83 let name = {
84 let len = ifr
85 .ifr_name
86 .iter()
87 .position(|&c| c == 0)
88 .unwrap_or(libc::IFNAMSIZ);
89 let bytes: Vec<u8> = ifr.ifr_name[..len].iter().map(|&c| c as u8).collect();
90 String::from_utf8_lossy(&bytes).into_owned()
91 };
92
93 tracing::info!("Created TAP device: {}", name);
94
95 Ok(Self {
96 fd,
97 name,
98 nonblocking: false,
99 })
100 }
101
102 pub fn set_nonblocking(&mut self, nonblocking: bool) -> std::io::Result<()> {
104 let flags = unsafe { libc::fcntl(self.fd, libc::F_GETFL) };
106 if flags < 0 {
107 return Err(std::io::Error::last_os_error());
108 }
109
110 let new_flags = if nonblocking {
111 flags | libc::O_NONBLOCK
112 } else {
113 flags & !libc::O_NONBLOCK
114 };
115
116 let ret = unsafe { libc::fcntl(self.fd, libc::F_SETFL, new_flags) };
118 if ret < 0 {
119 return Err(std::io::Error::last_os_error());
120 }
121
122 self.nonblocking = nonblocking;
123 Ok(())
124 }
125
126 #[must_use]
128 pub fn name(&self) -> &str {
129 &self.name
130 }
131
132 pub fn bring_up(&self) -> std::io::Result<()> {
134 use std::process::Command;
135
136 let status = Command::new("ip")
137 .args(["link", "set", &self.name, "up"])
138 .status()?;
139
140 if status.success() {
141 Ok(())
142 } else {
143 Err(std::io::Error::new(
144 std::io::ErrorKind::Other,
145 "Failed to bring interface up",
146 ))
147 }
148 }
149
150 pub fn set_ip(&self, ip: &str, prefix_len: u8) -> std::io::Result<()> {
152 use std::process::Command;
153
154 let addr = format!("{}/{}", ip, prefix_len);
155 let status = Command::new("ip")
156 .args(["addr", "add", &addr, "dev", &self.name])
157 .status()?;
158
159 if status.success() {
160 Ok(())
161 } else {
162 Err(std::io::Error::new(
163 std::io::ErrorKind::Other,
164 "Failed to set IP address",
165 ))
166 }
167 }
168}
169
170impl Drop for TapBackend {
171 fn drop(&mut self) {
172 if self.fd >= 0 {
173 unsafe { libc::close(self.fd) };
175 }
176 }
177}
178
179impl NetBackend for TapBackend {
180 fn send(&mut self, packet: &NetPacket) -> std::io::Result<usize> {
181 let ret = unsafe {
183 libc::write(
184 self.fd,
185 packet.data.as_ptr() as *const libc::c_void,
186 packet.data.len(),
187 )
188 };
189
190 if ret < 0 {
191 Err(std::io::Error::last_os_error())
192 } else {
193 Ok(ret as usize)
194 }
195 }
196
197 fn recv(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
198 let ret = unsafe { libc::read(self.fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len()) };
200
201 if ret < 0 {
202 let err = std::io::Error::last_os_error();
203 if err.kind() == std::io::ErrorKind::WouldBlock {
204 Ok(0)
205 } else {
206 Err(err)
207 }
208 } else {
209 Ok(ret as usize)
210 }
211 }
212
213 fn has_data(&self) -> bool {
214 let mut pollfd = libc::pollfd {
215 fd: self.fd,
216 events: libc::POLLIN,
217 revents: 0,
218 };
219
220 let ret = unsafe { libc::poll(&mut pollfd, 1, 0) };
222 ret > 0 && (pollfd.revents & libc::POLLIN) != 0
223 }
224
225 fn configure_offload(&mut self, flags: NetOffloadFlags) -> std::io::Result<()> {
226 let mut tun_flags: u32 = 0;
227 if flags.csum {
228 tun_flags |= TUN_F_CSUM;
229 }
230 if flags.tso4 {
231 tun_flags |= TUN_F_TSO4;
232 }
233 if flags.tso6 {
234 tun_flags |= TUN_F_TSO6;
235 }
236 if flags.tso_ecn {
237 tun_flags |= TUN_F_TSO_ECN;
238 }
239 if flags.ufo {
240 tun_flags |= TUN_F_UFO;
241 }
242
243 let ret = unsafe { libc::ioctl(self.fd, TUNSETOFFLOAD, tun_flags) };
246 if ret < 0 {
247 return Err(std::io::Error::last_os_error());
248 }
249 tracing::debug!(
250 "TAP {}: configured offload flags=0x{:x} (csum={} tso4={} tso6={} tso_ecn={} ufo={})",
251 self.name,
252 tun_flags,
253 flags.csum,
254 flags.tso4,
255 flags.tso6,
256 flags.tso_ecn,
257 flags.ufo,
258 );
259 Ok(())
260 }
261
262 fn set_vnet_hdr_sz(&mut self, size: u32) -> std::io::Result<()> {
263 let sz: libc::c_int = size as libc::c_int;
264 let ret = unsafe { libc::ioctl(self.fd, TUNSETVNETHDRSZ, &sz) };
266 if ret < 0 {
267 return Err(std::io::Error::last_os_error());
268 }
269 tracing::debug!("TAP {}: set vnet_hdr_sz = {}", self.name, size);
270 Ok(())
271 }
272}