1use std::io;
26use std::os::fd::{AsRawFd, OwnedFd, RawFd};
27use std::os::unix::net::UnixStream;
28use std::sync::atomic::{AtomicBool, Ordering};
29use std::sync::Arc;
30use std::thread::JoinHandle;
31
32use crate::frame_stream::{read_frame, write_frame};
33
34const TAP_READ_BUF: usize = 65_536;
36
37pub struct NetnsTapBridge {
40 stop: Arc<AtomicBool>,
41 threads: Vec<JoinHandle<()>>,
42}
43
44impl NetnsTapBridge {
45 pub fn shutdown(&mut self) {
47 self.stop.store(true, Ordering::SeqCst);
48 for t in self.threads.drain(..) {
49 let _ = t.join();
50 }
51 }
52}
53
54impl Drop for NetnsTapBridge {
55 fn drop(&mut self) {
56 self.shutdown();
57 }
58}
59
60pub fn start_netns_tap_bridge(stream: UnixStream, tap: OwnedFd) -> io::Result<NetnsTapBridge> {
71 let stream_rx = stream.try_clone()?;
74 let stream_tx = stream;
75 let tap_rx = tap.try_clone()?;
76 let tap_tx = tap;
77
78 let stop = Arc::new(AtomicBool::new(false));
79
80 let stop_a = stop.clone();
81 let t_up = std::thread::Builder::new()
82 .name("netns-tap-tx".into())
83 .spawn(move || pump_tap_to_stream(tap_rx, stream_tx, &stop_a))?;
84
85 let stop_b = stop.clone();
86 let t_down = std::thread::Builder::new()
87 .name("netns-tap-rx".into())
88 .spawn(move || pump_stream_to_tap(stream_rx, tap_tx, &stop_b))?;
89
90 Ok(NetnsTapBridge {
91 stop,
92 threads: vec![t_up, t_down],
93 })
94}
95
96fn pump_tap_to_stream(tap: OwnedFd, mut stream: UnixStream, stop: &AtomicBool) {
97 let mut buf = vec![0u8; TAP_READ_BUF];
98 let fd = tap.as_raw_fd();
99 while !stop.load(Ordering::SeqCst) {
100 match read_fd(fd, &mut buf) {
101 Ok(0) => break, Ok(n) => {
103 if let Err(e) = write_frame(&mut stream, &buf[..n]) {
104 if !stop.load(Ordering::SeqCst) {
105 tracing::debug!("netns-tap: stream write ended: {e}");
106 }
107 break;
108 }
109 }
110 Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
111 Err(e) => {
112 if !stop.load(Ordering::SeqCst) {
113 tracing::debug!("netns-tap: tap read ended: {e}");
114 }
115 break;
116 }
117 }
118 }
119}
120
121fn pump_stream_to_tap(mut stream: UnixStream, tap: OwnedFd, stop: &AtomicBool) {
122 let fd = tap.as_raw_fd();
123 while !stop.load(Ordering::SeqCst) {
124 match read_frame(&mut stream) {
125 Ok(frame) => {
126 if let Err(e) = write_fd_all(fd, &frame) {
129 if !stop.load(Ordering::SeqCst) {
130 tracing::debug!("netns-tap: tap write ended: {e}");
131 }
132 break;
133 }
134 }
135 Err(e) => {
136 if !stop.load(Ordering::SeqCst) {
137 tracing::debug!("netns-tap: stream read ended: {e}");
138 }
139 break;
140 }
141 }
142 }
143}
144
145fn read_fd(fd: RawFd, buf: &mut [u8]) -> io::Result<usize> {
150 let n = unsafe { libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len()) };
151 if n < 0 {
152 Err(io::Error::last_os_error())
153 } else {
154 Ok(n as usize)
155 }
156}
157
158fn write_fd_all(fd: RawFd, buf: &[u8]) -> io::Result<()> {
159 let n = unsafe { libc::write(fd, buf.as_ptr() as *const libc::c_void, buf.len()) };
161 if n < 0 {
162 return Err(io::Error::last_os_error());
163 }
164 if (n as usize) != buf.len() {
165 return Err(io::Error::new(
166 io::ErrorKind::WriteZero,
167 "short tap write truncated an ethernet frame",
168 ));
169 }
170 Ok(())
171}
172
173#[cfg(target_os = "linux")]
183pub fn open_tap_in_netns(netns_path: &str, ifname: &str) -> io::Result<OwnedFd> {
184 use std::fs::File;
185 use std::os::fd::AsFd;
186
187 if ifname.len() >= libc::IFNAMSIZ {
188 return Err(io::Error::new(
189 io::ErrorKind::InvalidInput,
190 "interface name too long",
191 ));
192 }
193
194 let self_ns = File::open("/proc/self/ns/net")?;
196 let target = File::open(netns_path)?;
197
198 let ifname = ifname.to_string();
202 std::thread::scope(|s| {
203 s.spawn(|| -> io::Result<OwnedFd> {
204 enter_netns(target.as_fd().as_raw_fd())?;
205 let res = create_tap(&ifname);
206 let _ = enter_netns(self_ns.as_fd().as_raw_fd());
210 res
211 })
212 .join()
213 .map_err(|_| io::Error::other("tap-open thread panicked"))?
214 })
215}
216
217#[cfg(target_os = "linux")]
229pub fn setup_tc_redirect(netns_path: &str, cni_if: &str, tap_if: &str) -> io::Result<()> {
230 tc(netns_path, &["qdisc", "add", "dev", cni_if, "ingress"])?;
233 tc(netns_path, &["qdisc", "add", "dev", tap_if, "ingress"])?;
234 tc(
235 netns_path,
236 &[
237 "filter", "add", "dev", cni_if, "ingress", "protocol", "all", "matchall", "action",
238 "mirred", "egress", "redirect", "dev", tap_if,
239 ],
240 )?;
241 tc(
242 netns_path,
243 &[
244 "filter", "add", "dev", tap_if, "ingress", "protocol", "all", "matchall", "action",
245 "mirred", "egress", "redirect", "dev", cni_if,
246 ],
247 )?;
248 Ok(())
249}
250
251#[cfg(target_os = "linux")]
252fn tc(netns_path: &str, args: &[&str]) -> io::Result<()> {
253 let out = std::process::Command::new("nsenter")
254 .arg(format!("--net={netns_path}"))
255 .arg("tc")
256 .args(args)
257 .output()?;
258 if !out.status.success() {
259 return Err(io::Error::other(format!(
260 "nsenter --net={netns_path} tc {}: {}",
261 args.join(" "),
262 String::from_utf8_lossy(&out.stderr).trim()
263 )));
264 }
265 Ok(())
266}
267
268#[cfg(target_os = "linux")]
269fn enter_netns(ns_fd: RawFd) -> io::Result<()> {
270 let rc = unsafe { libc::setns(ns_fd, libc::CLONE_NEWNET) };
271 if rc != 0 {
272 return Err(io::Error::last_os_error());
273 }
274 Ok(())
275}
276
277#[cfg(target_os = "linux")]
278fn create_tap(ifname: &str) -> io::Result<OwnedFd> {
279 use std::os::fd::FromRawFd;
280
281 let tun = unsafe { libc::open(c"/dev/net/tun".as_ptr(), libc::O_RDWR) };
282 if tun < 0 {
283 return Err(io::Error::last_os_error());
284 }
285 let owned = unsafe { OwnedFd::from_raw_fd(tun) };
287
288 #[repr(C)]
289 struct Ifreq {
290 name: [libc::c_char; libc::IFNAMSIZ],
291 flags: libc::c_short,
292 _pad: [u8; 22],
293 }
294 let mut req = Ifreq {
295 name: [0; libc::IFNAMSIZ],
296 flags: (libc::IFF_TAP | libc::IFF_NO_PI) as libc::c_short,
297 _pad: [0; 22],
298 };
299 for (i, b) in ifname.bytes().enumerate() {
300 req.name[i] = b as libc::c_char;
301 }
302 const TUNSETIFF: libc::c_ulong = 0x4004_54ca;
304 let rc = unsafe { libc::ioctl(owned.as_raw_fd(), TUNSETIFF as _, &mut req) };
305 if rc < 0 {
306 return Err(io::Error::last_os_error());
307 }
308
309 bring_up(ifname)?;
310 Ok(owned)
311}
312
313#[cfg(target_os = "linux")]
316fn bring_up(ifname: &str) -> io::Result<()> {
317 let sock = unsafe { libc::socket(libc::AF_INET, libc::SOCK_DGRAM, 0) };
318 if sock < 0 {
319 return Err(io::Error::last_os_error());
320 }
321 let _guard = scopeguard_close(sock);
322
323 #[repr(C)]
324 struct IfreqFlags {
325 name: [libc::c_char; libc::IFNAMSIZ],
326 flags: libc::c_short,
327 _pad: [u8; 22],
328 }
329 let mut req = IfreqFlags {
330 name: [0; libc::IFNAMSIZ],
331 flags: 0,
332 _pad: [0; 22],
333 };
334 for (i, b) in ifname.bytes().enumerate() {
335 req.name[i] = b as libc::c_char;
336 }
337 const SIOCGIFFLAGS: libc::c_ulong = 0x8913;
338 const SIOCSIFFLAGS: libc::c_ulong = 0x8914;
339 if unsafe { libc::ioctl(sock, SIOCGIFFLAGS as _, &mut req) } < 0 {
340 return Err(io::Error::last_os_error());
341 }
342 req.flags |= (libc::IFF_UP | libc::IFF_RUNNING) as libc::c_short;
343 if unsafe { libc::ioctl(sock, SIOCSIFFLAGS as _, &mut req) } < 0 {
344 return Err(io::Error::last_os_error());
345 }
346 Ok(())
347}
348
349#[cfg(target_os = "linux")]
350fn scopeguard_close(fd: RawFd) -> impl Drop {
351 struct Closer(RawFd);
352 impl Drop for Closer {
353 fn drop(&mut self) {
354 unsafe { libc::close(self.0) };
355 }
356 }
357 Closer(fd)
358}
359
360#[cfg(test)]
361mod tests {
362 use super::*;
363 use std::io::{Read, Write};
364 use std::os::fd::OwnedFd;
365
366 fn socketpair() -> (OwnedFd, OwnedFd) {
369 let mut fds = [0i32; 2];
370 let rc = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) };
371 assert_eq!(rc, 0);
372 use std::os::fd::FromRawFd;
373 unsafe { (OwnedFd::from_raw_fd(fds[0]), OwnedFd::from_raw_fd(fds[1])) }
374 }
375
376 #[test]
377 fn frames_relay_both_directions() {
378 let (tap_near, tap_far) = socketpair();
381 let (stream_near_raw, stream_far_raw) = socketpair();
382 let stream_near = UnixStream::from(stream_near_raw);
383 let mut stream_far = UnixStream::from(stream_far_raw);
384
385 let _bridge = start_netns_tap_bridge(stream_near, tap_near).unwrap();
386
387 let frame = b"\xde\xad\xbe\xef hello ethernet";
390 let mut tap_far_w = UnixStream::from(tap_far);
391 tap_far_w.write_all(frame).unwrap();
392 let got = read_frame(&mut stream_far).unwrap();
393 assert_eq!(&got, frame);
394
395 let frame2 = b"reply frame \x00\x01\x02";
398 write_frame(&mut stream_far, frame2).unwrap();
399 let mut buf = vec![0u8; frame2.len()];
400 tap_far_w.read_exact(&mut buf).unwrap();
401 assert_eq!(&buf, frame2);
402 }
403}