1 // Copyright (c) 2020 Intel Corporation. All rights reserved.
2 //
3 // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
4
5 use std::net::IpAddr;
6 use std::path::Path;
7 use std::{fs, io};
8
9 use thiserror::Error;
10
11 use super::{vnet_hdr_len, MacAddr, Tap, TapError};
12
13 #[derive(Error, Debug)]
14 pub enum Error {
15 #[error("Failed to convert an hexadecimal string into an integer")]
16 ConvertHexStringToInt(#[source] std::num::ParseIntError),
17 #[error("Error related to the multiqueue support (no support TAP side)")]
18 MultiQueueNoTapSupport,
19 #[error("Error related to the multiqueue support (no support device side)")]
20 MultiQueueNoDeviceSupport,
21 #[error("Failed to read the TAP flags from sysfs")]
22 ReadSysfsTunFlags(#[source] io::Error),
23 #[error("Open tap device failed")]
24 TapOpen(#[source] TapError),
25 #[error("Setting tap IP and/or netmask failed")]
26 TapSetIpNetmask(#[source] TapError),
27 #[error("Setting MAC address failed")]
28 TapSetMac(#[source] TapError),
29 #[error("Getting MAC address failed")]
30 TapGetMac(#[source] TapError),
31 #[error("Setting vnet header size failed")]
32 TapSetVnetHdrSize(#[source] TapError),
33 #[error("Setting MTU failed")]
34 TapSetMtu(#[source] TapError),
35 #[error("Enabling tap interface failed")]
36 TapEnable(#[source] TapError),
37 }
38
39 type Result<T> = std::result::Result<T, Error>;
40
check_mq_support(if_name: &Option<&str>, queue_pairs: usize) -> Result<()>41 fn check_mq_support(if_name: &Option<&str>, queue_pairs: usize) -> Result<()> {
42 if let Some(tap_name) = if_name {
43 let mq = queue_pairs > 1;
44 let path = format!("/sys/class/net/{tap_name}/tun_flags");
45 // interface does not exist, check is not required
46 if !Path::new(&path).exists() {
47 return Ok(());
48 }
49 let tun_flags_str = fs::read_to_string(path).map_err(Error::ReadSysfsTunFlags)?;
50 let tun_flags = u32::from_str_radix(tun_flags_str.trim().trim_start_matches("0x"), 16)
51 .map_err(Error::ConvertHexStringToInt)?;
52 if (tun_flags & net_gen::IFF_MULTI_QUEUE != 0) && !mq {
53 return Err(Error::MultiQueueNoDeviceSupport);
54 } else if (tun_flags & net_gen::IFF_MULTI_QUEUE == 0) && mq {
55 return Err(Error::MultiQueueNoTapSupport);
56 }
57 }
58 Ok(())
59 }
60
61 /// Opens a Tap device and configures it.
62 ///
63 /// Afterward, further RX queues can be opened with a common config.
open_tap_rx_q_0( if_name: Option<&str>, ip_addr: Option<IpAddr>, netmask: Option<IpAddr>, host_mac: &mut Option<MacAddr>, mtu: Option<u16>, num_rx_q: usize, flags: Option<i32>, ) -> Result<Tap>64 fn open_tap_rx_q_0(
65 if_name: Option<&str>,
66 ip_addr: Option<IpAddr>,
67 netmask: Option<IpAddr>,
68 host_mac: &mut Option<MacAddr>,
69 mtu: Option<u16>,
70 num_rx_q: usize,
71 flags: Option<i32>,
72 ) -> Result<Tap> {
73 // Check if the given interface exists before we create it.
74 let tap_exists = if_name.is_some_and(|n| Path::new(&format!("/sys/class/net/{n}")).exists());
75
76 let tap = match if_name {
77 Some(name) => Tap::open_named(name, num_rx_q, flags).map_err(Error::TapOpen)?,
78 // Create a new Tap device in Linux, if none was specified.
79 None => Tap::new(num_rx_q).map_err(Error::TapOpen)?,
80 };
81 // Don't overwrite ip configuration of existing interfaces:
82 if !tap_exists {
83 if let Some(ip) = ip_addr {
84 tap.set_ip_addr(ip, netmask)
85 .map_err(Error::TapSetIpNetmask)?;
86 }
87 } else {
88 warn!(
89 "Tap {} already exists. IP configuration will not be overwritten.",
90 if_name.unwrap_or_default()
91 );
92 }
93 if let Some(mac) = host_mac {
94 tap.set_mac_addr(*mac).map_err(Error::TapSetMac)?
95 } else {
96 *host_mac = Some(tap.get_mac_addr().map_err(Error::TapGetMac)?)
97 }
98 if let Some(mtu) = mtu {
99 tap.set_mtu(mtu as i32).map_err(Error::TapSetMtu)?;
100 }
101 tap.enable().map_err(Error::TapEnable)?;
102
103 tap.set_vnet_hdr_size(vnet_hdr_len() as i32)
104 .map_err(Error::TapSetVnetHdrSize)?;
105
106 Ok(tap)
107 }
108
109 /// Create a new virtio network device with the given IP address and
110 /// netmask.
open_tap( if_name: Option<&str>, ip_addr: Option<IpAddr>, netmask: Option<IpAddr>, host_mac: &mut Option<MacAddr>, mtu: Option<u16>, num_rx_q: usize, flags: Option<i32>, ) -> Result<Vec<Tap>>111 pub fn open_tap(
112 if_name: Option<&str>,
113 ip_addr: Option<IpAddr>,
114 netmask: Option<IpAddr>,
115 host_mac: &mut Option<MacAddr>,
116 mtu: Option<u16>,
117 num_rx_q: usize,
118 flags: Option<i32>,
119 ) -> Result<Vec<Tap>> {
120 let mut taps: Vec<Tap> = Vec::new();
121 let mut ifname: String = String::new();
122
123 // In case the tap interface already exists, check if the number of
124 // queues is appropriate. The tap might not support multiqueue while
125 // the number of queues indicates the user expects multiple queues, or
126 // on the contrary, the tap might support multiqueue while the number
127 // of queues indicates the user doesn't expect multiple queues.
128 check_mq_support(&if_name, num_rx_q)?;
129
130 for i in 0..num_rx_q {
131 let tap: Tap;
132 if i == 0 {
133 // Special handling is required for the first RX queue, such as
134 // configuring the device. Subsequent iterations will then use the
135 // same device.
136 tap = open_tap_rx_q_0(if_name, ip_addr, netmask, host_mac, mtu, num_rx_q, flags)?;
137 // Set the name of the tap device we open in subsequent iterations.
138 ifname = String::from_utf8(tap.get_if_name()).unwrap();
139 } else {
140 tap = Tap::open_named(ifname.as_str(), num_rx_q, flags).map_err(Error::TapOpen)?;
141
142 tap.set_vnet_hdr_size(vnet_hdr_len() as i32)
143 .map_err(Error::TapSetVnetHdrSize)?;
144 }
145 taps.push(tap);
146 }
147 Ok(taps)
148 }
149