dbs_utils/net/
tap.rs

1// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3//
4// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
5// Use of this source code is governed by a BSD-style license that can be
6// found in the THIRD-PARTY file.
7
8use std::fs::File;
9use std::io::{Error as IoError, Read, Result as IoResult, Write};
10use std::net::UdpSocket;
11use std::os::raw::*;
12use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
13
14use vmm_sys_util::ioctl::{ioctl_with_mut_ref, ioctl_with_ref, ioctl_with_val};
15use vmm_sys_util::{ioctl_ioc_nr, ioctl_iow_nr};
16
17use crate::net::net_gen;
18
19// As defined in the Linux UAPI:
20// https://elixir.bootlin.com/linux/v4.17/source/include/uapi/linux/if.h#L33
21pub(crate) const IFACE_NAME_MAX_LEN: usize = 16;
22
23/// List of errors the tap implementation can throw.
24#[derive(Debug, thiserror::Error)]
25pub enum Error {
26    /// Failed to create a socket.
27    #[error("cannot create socket. {0}")]
28    CreateSocket(#[source] IoError),
29
30    /// Unable to create tap interface.
31    #[error("cannot create tap devic. {0}")]
32    CreateTap(IoError),
33
34    /// Invalid interface name.
35    #[error("invalid network interface name")]
36    InvalidIfname,
37
38    /// ioctl failed.
39    #[error("failure while issue Tap ioctl command. {0}")]
40    IoctlError(#[source] IoError),
41
42    /// Couldn't open /dev/net/tun.
43    #[error("cannot open tap device. {0}")]
44    OpenTun(#[source] IoError),
45}
46
47pub type Result<T> = ::std::result::Result<T, Error>;
48
49const TUNTAP: ::std::os::raw::c_uint = 84;
50ioctl_iow_nr!(TUNSETIFF, TUNTAP, 202, ::std::os::raw::c_int);
51ioctl_iow_nr!(TUNSETOFFLOAD, TUNTAP, 208, ::std::os::raw::c_uint);
52ioctl_iow_nr!(TUNSETVNETHDRSZ, TUNTAP, 216, ::std::os::raw::c_int);
53
54/// Handle for a network tap interface.
55///
56/// For now, this simply wraps the file descriptor for the tap device so methods
57/// can run ioctls on the interface. The tap interface fd will be closed when
58/// Tap goes out of scope, and the kernel will clean up the interface automatically.
59#[derive(Debug)]
60pub struct Tap {
61    /// tap device file handle
62    pub tap_file: File,
63    pub(crate) if_name: [u8; IFACE_NAME_MAX_LEN],
64    pub(crate) if_flags: std::os::raw::c_short,
65}
66
67impl PartialEq for Tap {
68    fn eq(&self, other: &Tap) -> bool {
69        self.if_name == other.if_name
70    }
71}
72
73fn create_socket() -> Result<UdpSocket> {
74    // This is safe since we check the return value.
75    let sock = unsafe { libc::socket(libc::AF_INET, libc::SOCK_DGRAM, 0) };
76    if sock < 0 {
77        return Err(Error::CreateSocket(IoError::last_os_error()));
78    }
79
80    // This is safe; nothing else will use or hold onto the raw sock fd.
81    Ok(unsafe { UdpSocket::from_raw_fd(sock) })
82}
83
84// Returns a byte vector representing the contents of a null terminated C string which
85// contains if_name.
86fn build_terminated_if_name(if_name: &str) -> Result<[u8; IFACE_NAME_MAX_LEN]> {
87    // Convert the string slice to bytes, and shadow the variable,
88    // since we no longer need the &str version.
89    let if_name = if_name.as_bytes();
90
91    if if_name.len() >= IFACE_NAME_MAX_LEN {
92        return Err(Error::InvalidIfname);
93    }
94
95    let mut terminated_if_name = [b'\0'; IFACE_NAME_MAX_LEN];
96    terminated_if_name[..if_name.len()].copy_from_slice(if_name);
97
98    Ok(terminated_if_name)
99}
100
101impl Tap {
102    /// Create a TUN/TAP device given the interface name.
103    /// # Arguments
104    ///
105    /// * `if_name` - the name of the interface.
106    /// # Example
107    ///
108    /// ```no_run
109    /// use dbs_utils::net::Tap;
110    /// Tap::open_named("doc-test-tap", false).unwrap();
111    /// ```
112    pub fn open_named(if_name: &str, multi_vq: bool) -> Result<Tap> {
113        let terminated_if_name = build_terminated_if_name(if_name)?;
114
115        // This is pretty messy because of the unions used by ifreq. Since we
116        // don't call as_mut on the same union field more than once, this block
117        // is safe.
118        let mut ifreq: net_gen::ifreq = Default::default();
119        unsafe {
120            let ifrn_name = ifreq.ifr_ifrn.ifrn_name.as_mut();
121            ifrn_name.copy_from_slice(terminated_if_name.as_ref());
122            let ifru_flags = ifreq.ifr_ifru.ifru_flags.as_mut();
123            *ifru_flags = (net_gen::IFF_TAP
124                | net_gen::IFF_NO_PI
125                | net_gen::IFF_VNET_HDR
126                | if multi_vq {
127                    net_gen::IFF_MULTI_QUEUE
128                } else {
129                    0
130                }) as c_short;
131        }
132
133        Tap::create_tap_with_ifreq(&mut ifreq)
134    }
135
136    fn create_tap_with_ifreq(ifreq: &mut net_gen::ifreq) -> Result<Tap> {
137        let fd = unsafe {
138            // Open calls are safe because we give a constant null-terminated
139            // string and verify the result.
140            libc::open(
141                b"/dev/net/tun\0".as_ptr() as *const c_char,
142                libc::O_RDWR | libc::O_NONBLOCK | libc::O_CLOEXEC,
143            )
144        };
145        if fd < 0 {
146            return Err(Error::OpenTun(IoError::last_os_error()));
147        }
148
149        // We just checked that the fd is valid.
150        let tuntap = unsafe { File::from_raw_fd(fd) };
151
152        // ioctl is safe since we call it with a valid tap fd and check the return
153        // value.
154        let ret = unsafe { ioctl_with_mut_ref(&tuntap, TUNSETIFF(), ifreq) };
155
156        if ret < 0 {
157            return Err(Error::CreateTap(IoError::last_os_error()));
158        }
159
160        // Safe since only the name is accessed, and it's cloned out.
161        Ok(Tap {
162            tap_file: tuntap,
163            if_name: unsafe { *ifreq.ifr_ifrn.ifrn_name.as_ref() },
164            if_flags: unsafe { *ifreq.ifr_ifru.ifru_flags.as_ref() },
165        })
166    }
167
168    /// Change the origin tap into multiqueue taps.
169    pub fn into_mq_taps(self, vq_pairs: usize) -> Result<Vec<Tap>> {
170        let mut taps = Vec::new();
171
172        if vq_pairs <= 1 {
173            taps.push(self);
174            return Ok(taps);
175        }
176
177        // Add other socket into the origin tap interface
178        for _ in 0..vq_pairs - 1 {
179            let mut ifreq = self.get_ifreq();
180            let tap = Tap::create_tap_with_ifreq(&mut ifreq)?;
181
182            tap.enable()?;
183
184            taps.push(tap);
185        }
186
187        taps.insert(0, self);
188        Ok(taps)
189    }
190
191    /// Set the offload flags for the tap interface.
192    pub fn set_offload(&self, flags: c_uint) -> Result<()> {
193        // ioctl is safe. Called with a valid tap fd, and we check the return.
194        let ret = unsafe { ioctl_with_val(&self.tap_file, TUNSETOFFLOAD(), c_ulong::from(flags)) };
195        if ret < 0 {
196            return Err(Error::IoctlError(IoError::last_os_error()));
197        }
198
199        Ok(())
200    }
201
202    /// Enable the tap interface.
203    pub fn enable(&self) -> Result<()> {
204        let sock = create_socket()?;
205
206        let mut ifreq = self.get_ifreq();
207
208        // We only access one field of the ifru union, hence this is safe.
209        unsafe {
210            let ifru_flags = ifreq.ifr_ifru.ifru_flags.as_mut();
211            *ifru_flags =
212                (net_gen::net_device_flags_IFF_UP | net_gen::net_device_flags_IFF_RUNNING) as i16;
213        }
214
215        // ioctl is safe. Called with a valid sock fd, and we check the return.
216        let ret =
217            unsafe { ioctl_with_ref(&sock, c_ulong::from(net_gen::sockios::SIOCSIFFLAGS), &ifreq) };
218        if ret < 0 {
219            return Err(Error::IoctlError(IoError::last_os_error()));
220        }
221
222        Ok(())
223    }
224
225    /// Set the size of the vnet hdr.
226    pub fn set_vnet_hdr_size(&self, size: c_int) -> Result<()> {
227        // ioctl is safe. Called with a valid tap fd, and we check the return.
228        let ret = unsafe { ioctl_with_ref(&self.tap_file, TUNSETVNETHDRSZ(), &size) };
229        if ret < 0 {
230            return Err(Error::IoctlError(IoError::last_os_error()));
231        }
232
233        Ok(())
234    }
235
236    fn get_ifreq(&self) -> net_gen::ifreq {
237        let mut ifreq: net_gen::ifreq = Default::default();
238
239        // This sets the name of the interface, which is the only entry
240        // in a single-field union.
241        unsafe {
242            let ifrn_name = ifreq.ifr_ifrn.ifrn_name.as_mut();
243            ifrn_name.clone_from_slice(&self.if_name);
244
245            let flags = ifreq.ifr_ifru.ifru_flags.as_mut();
246            *flags = self.if_flags;
247        }
248
249        ifreq
250    }
251
252    /// Get the origin flags when interface was created.
253    pub fn if_flags(&self) -> u32 {
254        self.if_flags as u32
255    }
256}
257
258impl Read for Tap {
259    fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> {
260        self.tap_file.read(buf)
261    }
262}
263
264impl Write for Tap {
265    fn write(&mut self, buf: &[u8]) -> IoResult<usize> {
266        self.tap_file.write(buf)
267    }
268
269    fn flush(&mut self) -> IoResult<()> {
270        Ok(())
271    }
272}
273
274impl AsRawFd for Tap {
275    fn as_raw_fd(&self) -> RawFd {
276        self.tap_file.as_raw_fd()
277    }
278}
279
280mod tests {
281    #![allow(dead_code)]
282
283    use std::mem;
284    use std::net::Ipv4Addr;
285    use std::str;
286    use std::sync::atomic::{AtomicUsize, Ordering};
287
288    use super::*;
289
290    const SUBNET_MASK: &str = "255.255.255.0";
291    const TAP_IP_PREFIX: &str = "192.168.241.";
292    const FAKE_MAC: &str = "12:34:56:78:9a:bc";
293
294    // We skip the first 10 bytes because the IFF_VNET_HDR flag is set when the interface
295    // is created, and the legacy header is 10 bytes long without a certain flag which
296    // is not set in Tap::new().
297    const VETH_OFFSET: usize = 10;
298    static NEXT_IP: AtomicUsize = AtomicUsize::new(1);
299
300    // Create a sockaddr_in from an IPv4 address, and expose it as
301    // an opaque sockaddr suitable for usage by socket ioctls.
302    fn create_sockaddr(ip_addr: Ipv4Addr) -> net_gen::sockaddr {
303        // IPv4 addresses big-endian (network order), but Ipv4Addr will give us
304        // a view of those bytes directly so we can avoid any endian trickiness.
305        let addr_in = net_gen::sockaddr_in {
306            sin_family: net_gen::AF_INET as u16,
307            sin_port: 0,
308            sin_addr: unsafe { mem::transmute(ip_addr.octets()) },
309            __pad: [0; 8usize],
310        };
311
312        unsafe { mem::transmute(addr_in) }
313    }
314    impl Tap {
315        // We do not run unit tests in parallel so we should have no problem
316        // assigning the same IP.
317
318        /// Create a new tap interface.
319        pub fn new() -> Result<Tap> {
320            // The name of the tap should be {module_name}{index} so that
321            // we make sure it stays different when tests are run concurrently.
322            let next_ip = NEXT_IP.fetch_add(1, Ordering::SeqCst);
323            Self::open_named(&format!("dbs_tap{}", next_ip), false)
324        }
325
326        /// Set the host-side IP address for the tap interface.
327        pub fn set_ip_addr(&self, ip_addr: Ipv4Addr) -> Result<()> {
328            let sock = create_socket()?;
329            let addr = create_sockaddr(ip_addr);
330
331            let mut ifreq = self.get_ifreq();
332
333            // We only access one field of the ifru union, hence this is safe.
334            unsafe {
335                let ifru_addr = ifreq.ifr_ifru.ifru_addr.as_mut();
336                *ifru_addr = addr;
337            }
338
339            // ioctl is safe. Called with a valid sock fd, and we check the return.
340            let ret = unsafe {
341                ioctl_with_ref(&sock, c_ulong::from(net_gen::sockios::SIOCSIFADDR), &ifreq)
342            };
343            if ret < 0 {
344                return Err(Error::IoctlError(IoError::last_os_error()));
345            }
346
347            Ok(())
348        }
349
350        /// Set the netmask for the subnet that the tap interface will exist on.
351        pub fn set_netmask(&self, netmask: Ipv4Addr) -> Result<()> {
352            let sock = create_socket()?;
353            let addr = create_sockaddr(netmask);
354
355            let mut ifreq = self.get_ifreq();
356
357            // We only access one field of the ifru union, hence this is safe.
358            unsafe {
359                let ifru_addr = ifreq.ifr_ifru.ifru_addr.as_mut();
360                *ifru_addr = addr;
361            }
362
363            // ioctl is safe. Called with a valid sock fd, and we check the return.
364            let ret = unsafe {
365                ioctl_with_ref(
366                    &sock,
367                    c_ulong::from(net_gen::sockios::SIOCSIFNETMASK),
368                    &ifreq,
369                )
370            };
371            if ret < 0 {
372                return Err(Error::IoctlError(IoError::last_os_error()));
373            }
374
375            Ok(())
376        }
377    }
378
379    fn tap_name_to_string(tap: &Tap) -> String {
380        let null_pos = tap.if_name.iter().position(|x| *x == 0).unwrap();
381        str::from_utf8(&tap.if_name[..null_pos])
382            .unwrap()
383            .to_string()
384    }
385
386    #[test]
387    fn test_tap_name() {
388        // Sanity check that the assumed max iface name length is correct.
389        assert_eq!(
390            IFACE_NAME_MAX_LEN,
391            net_gen::ifreq__bindgen_ty_1::default()
392                .bindgen_union_field
393                .len()
394        );
395
396        // 16 characters - too long.
397        let name = "a123456789abcdef";
398        match Tap::open_named(name, false) {
399            Err(Error::InvalidIfname) => (),
400            _ => panic!("Expected Error::InvalidIfname"),
401        };
402
403        // 15 characters - OK.
404        let name = "a123456789abcde";
405        let tap = Tap::open_named(name, false).unwrap();
406        assert_eq!(
407            name,
408            std::str::from_utf8(&tap.if_name[0..(IFACE_NAME_MAX_LEN - 1)]).unwrap()
409        );
410    }
411
412    #[test]
413    fn test_tap_partial_eq() {
414        assert_ne!(Tap::new().unwrap(), Tap::new().unwrap());
415    }
416
417    #[test]
418    fn test_tap_configure() {
419        // `fetch_add` adds to the current value, returning the previous value.
420        let next_ip = NEXT_IP.fetch_add(1, Ordering::SeqCst);
421
422        let tap = Tap::new().unwrap();
423        let ip_addr: Ipv4Addr = format!("{}{}", TAP_IP_PREFIX, next_ip).parse().unwrap();
424        let netmask: Ipv4Addr = SUBNET_MASK.parse().unwrap();
425
426        let ret = tap.set_ip_addr(ip_addr);
427        assert!(ret.is_ok());
428        let ret = tap.set_netmask(netmask);
429        assert!(ret.is_ok());
430    }
431
432    #[test]
433    fn test_set_options() {
434        // This line will fail to provide an initialized FD if the test is not run as root.
435        let tap = Tap::new().unwrap();
436        tap.set_vnet_hdr_size(16).unwrap();
437        tap.set_offload(0).unwrap();
438
439        let faulty_tap = Tap {
440            tap_file: unsafe { File::from_raw_fd(i32::MAX) },
441            if_name: [0x01; 16],
442            if_flags: 0,
443        };
444        assert!(faulty_tap.set_vnet_hdr_size(16).is_err());
445        assert!(faulty_tap.set_offload(0).is_err());
446    }
447
448    #[test]
449    fn test_tap_enable() {
450        let tap = Tap::new().unwrap();
451        let ret = tap.enable();
452        assert!(ret.is_ok());
453    }
454
455    #[test]
456    fn test_tap_get_ifreq() {
457        let tap = Tap::new().unwrap();
458        let ret = tap.get_ifreq();
459        assert_eq!(
460            "__BindgenUnionField",
461            format!("{:?}", ret.ifr_ifrn.ifrn_name)
462        );
463    }
464
465    #[test]
466    fn test_raw_fd() {
467        let tap = Tap::new().unwrap();
468        assert_eq!(tap.as_raw_fd(), tap.tap_file.as_raw_fd());
469    }
470}