xref: /cloud-hypervisor/net_util/src/open_tap.rs (revision d580ed55c6b0785aecae9de400f3dab484bb4bd6)
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 
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.
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.
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