xref: /cloud-hypervisor/net_util/src/open_tap.rs (revision 9af2968a7dc47b89bf07ea9dc5e735084efcfa3a)
1 // Copyright (c) 2020 Intel Corporation. All rights reserved.
2 //
3 // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
4 
5 use super::{vnet_hdr_len, MacAddr, Tap, TapError};
6 use std::net::Ipv4Addr;
7 use std::path::Path;
8 use std::{fs, io};
9 
10 #[derive(Debug)]
11 pub enum Error {
12     /// Failed to convert an hexadecimal string into an integer.
13     ConvertHexStringToInt(std::num::ParseIntError),
14     /// Error related to the multiqueue support (no support TAP side).
15     MultiQueueNoTapSupport,
16     /// Error related to the multiqueue support (no support device side).
17     MultiQueueNoDeviceSupport,
18     /// Failed to read the TAP flags from sysfs.
19     ReadSysfsTunFlags(io::Error),
20     /// Open tap device failed.
21     TapOpen(TapError),
22     /// Setting tap IP failed.
23     TapSetIp(TapError),
24     /// Setting tap netmask failed.
25     TapSetNetmask(TapError),
26     /// Setting MAC address failed
27     TapSetMac(TapError),
28     /// Getting MAC address failed
29     TapGetMac(TapError),
30     /// Setting vnet header size failed.
31     TapSetVnetHdrSize(TapError),
32     /// Enabling tap interface failed.
33     TapEnable(TapError),
34 }
35 
36 type Result<T> = std::result::Result<T, Error>;
37 
38 fn check_mq_support(if_name: &Option<&str>, queue_pairs: usize) -> Result<()> {
39     if let Some(tap_name) = if_name {
40         let mq = queue_pairs > 1;
41         let path = format!("/sys/class/net/{}/tun_flags", tap_name);
42         // interface does not exist, check is not required
43         if !Path::new(&path).exists() {
44             return Ok(());
45         }
46         let tun_flags_str = fs::read_to_string(path).map_err(Error::ReadSysfsTunFlags)?;
47         let tun_flags = u32::from_str_radix(tun_flags_str.trim().trim_start_matches("0x"), 16)
48             .map_err(Error::ConvertHexStringToInt)?;
49         if (tun_flags & net_gen::IFF_MULTI_QUEUE != 0) && !mq {
50             return Err(Error::MultiQueueNoDeviceSupport);
51         } else if (tun_flags & net_gen::IFF_MULTI_QUEUE == 0) && mq {
52             return Err(Error::MultiQueueNoTapSupport);
53         }
54     }
55     Ok(())
56 }
57 
58 /// Create a new virtio network device with the given IP address and
59 /// netmask.
60 pub fn open_tap(
61     if_name: Option<&str>,
62     ip_addr: Option<Ipv4Addr>,
63     netmask: Option<Ipv4Addr>,
64     host_mac: &mut Option<MacAddr>,
65     num_rx_q: usize,
66     flags: Option<i32>,
67 ) -> Result<Vec<Tap>> {
68     let mut taps: Vec<Tap> = Vec::new();
69     let mut ifname: String = String::new();
70     let vnet_hdr_size = vnet_hdr_len() as i32;
71 
72     // In case the tap interface already exists, check if the number of
73     // queues is appropriate. The tap might not support multiqueue while
74     // the number of queues indicates the user expects multiple queues, or
75     // on the contrary, the tap might support multiqueue while the number
76     // of queues indicates the user doesn't expect multiple queues.
77     check_mq_support(&if_name, num_rx_q)?;
78 
79     for i in 0..num_rx_q {
80         let tap: Tap;
81         if i == 0 {
82             tap = match if_name {
83                 Some(name) => Tap::open_named(name, num_rx_q, flags).map_err(Error::TapOpen)?,
84                 None => Tap::new(num_rx_q).map_err(Error::TapOpen)?,
85             };
86             if let Some(ip) = ip_addr {
87                 tap.set_ip_addr(ip).map_err(Error::TapSetIp)?;
88             }
89             if let Some(mask) = netmask {
90                 tap.set_netmask(mask).map_err(Error::TapSetNetmask)?;
91             }
92             if let Some(mac) = host_mac {
93                 tap.set_mac_addr(*mac).map_err(Error::TapSetMac)?
94             } else {
95                 *host_mac = Some(tap.get_mac_addr().map_err(Error::TapGetMac)?)
96             }
97             tap.enable().map_err(Error::TapEnable)?;
98 
99             tap.set_vnet_hdr_size(vnet_hdr_size)
100                 .map_err(Error::TapSetVnetHdrSize)?;
101 
102             ifname = String::from_utf8(tap.get_if_name()).unwrap();
103         } else {
104             tap = Tap::open_named(ifname.as_str(), num_rx_q, flags).map_err(Error::TapOpen)?;
105 
106             tap.set_vnet_hdr_size(vnet_hdr_size)
107                 .map_err(Error::TapSetVnetHdrSize)?;
108         }
109         taps.push(tap);
110     }
111     Ok(taps)
112 }
113