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