nmstate/ifaces/
sriov.rs

1// SPDX-License-Identifier: Apache-2.0
2
3use serde::{Deserialize, Serialize};
4
5use crate::{
6    ErrorKind, Interface, InterfaceType, Interfaces, MergedInterface,
7    NmstateError, VlanProtocol,
8};
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
11#[serde(rename_all = "kebab-case", deny_unknown_fields)]
12#[non_exhaustive]
13/// Single Root I/O Virtualization(SRIOV) configuration. The example yaml output
14/// of [crate::NetworkState] with SR-IOV enabled ethernet interface would be:
15/// ```yml
16/// interfaces:
17/// - name: ens1f1
18///   type: ethernet
19///   state: up
20///   mac-address: 00:11:22:33:44:55
21///   mtu: 1500
22///   min-mtu: 68
23///   max-mtu: 9702
24///   ethernet:
25///     sr-iov:
26///       drivers-autoprobe: true
27///       total-vfs: 2
28///       vfs:
29///       - id: 0
30///         mac-address: 00:11:22:33:00:ff
31///         spoof-check: true
32///         trust: false
33///         min-tx-rate: 0
34///         max-tx-rate: 0
35///         vlan-id: 0
36///         qos: 0
37///       - id: 1
38///         mac-address: 00:11:22:33:00:ef
39///         spoof-check: true
40///         trust: false
41///         min-tx-rate: 0
42///         max-tx-rate: 0
43///         vlan-id: 0
44///         qos: 0
45/// ```
46pub struct SrIovConfig {
47    #[serde(
48        skip_serializing_if = "Option::is_none",
49        default,
50        deserialize_with = "crate::deserializer::option_bool_or_string"
51    )]
52    /// Bind created VFs to their default kernel driver.
53    /// This relates to sriov_drivers_autoprobe.
54    /// More info here https://docs.kernel.org/PCI/pci-iov-howto.html#sr-iov-api
55    /// Deserialize and serialize from/to `drivers-autoprobe`.
56    pub drivers_autoprobe: Option<bool>,
57    #[serde(
58        skip_serializing_if = "Option::is_none",
59        default,
60        deserialize_with = "crate::deserializer::option_u32_or_string"
61    )]
62    /// The number of VFs enabled on PF.
63    /// Deserialize and serialize from/to `total-vfs`.
64    pub total_vfs: Option<u32>,
65    #[serde(skip_serializing_if = "Option::is_none")]
66    /// VF specific configurations.
67    /// * Setting to `Some(Vec::new())` will revert all VF configurations back
68    ///   to defaults.
69    /// * If not empty, missing [SrIovVfConfig] will use current configuration.
70    pub vfs: Option<Vec<SrIovVfConfig>>,
71}
72
73impl SrIovConfig {
74    pub(crate) const VF_NAMING_PREFIX: &'static str = "sriov:";
75    pub(crate) const VF_NAMING_SEPERATOR: char = ':';
76
77    pub fn new() -> Self {
78        Self::default()
79    }
80
81    // * Convert VF MAC address to upper case
82    // * Sort by VF ID
83    pub(crate) fn sanitize(&mut self) -> Result<(), NmstateError> {
84        if let Some(vfs) = self.vfs.as_mut() {
85            for vf in vfs.iter_mut() {
86                if let Some(address) = vf.mac_address.as_mut() {
87                    address.make_ascii_uppercase()
88                }
89
90                if let Some(VlanProtocol::Ieee8021Ad) = vf.vlan_proto {
91                    if vf.vlan_id.unwrap_or_default() == 0
92                        && vf.qos.unwrap_or_default() == 0
93                    {
94                        let e = NmstateError::new(
95                            ErrorKind::InvalidArgument,
96                            "VLAN protocol 802.1ad is not allowed when both \
97                             VLAN ID and VLAN QoS are zero or unset"
98                                .to_string(),
99                        );
100                        log::error!("VF ID {}: {}", vf.id, e);
101                        return Err(e);
102                    }
103                }
104            }
105            vfs.sort_unstable_by(|a, b| a.id.cmp(&b.id));
106        }
107
108        Ok(())
109    }
110
111    // * Auto fill unmentioned VF ID
112    pub(crate) fn auto_fill_unmentioned_vf_id(
113        &mut self,
114        current: Option<&Self>,
115    ) {
116        if let Some(vfs) = self.vfs.as_mut() {
117            for vf in vfs.iter_mut() {
118                if let Some(address) = vf.mac_address.as_mut() {
119                    address.make_ascii_uppercase()
120                }
121            }
122            vfs.sort_unstable_by(|a, b| a.id.cmp(&b.id));
123
124            if !vfs.is_empty() {
125                let total_vfs = self.total_vfs.unwrap_or_else(|| {
126                    current.and_then(|c| c.total_vfs).unwrap_or(
127                        vfs.iter().map(|v| v.id).max().unwrap_or_default() + 1,
128                    )
129                });
130                self.total_vfs = Some(total_vfs);
131                // Auto fill the missing
132                if total_vfs as usize != vfs.len() {
133                    let mut new_vf_confs: Vec<SrIovVfConfig> = (0..total_vfs)
134                        .map(|i| {
135                            let mut vf_conf = SrIovVfConfig::new();
136                            vf_conf.id = i;
137                            vf_conf
138                        })
139                        .collect();
140                    for vf in vfs {
141                        if new_vf_confs.len() > vf.id as usize {
142                            new_vf_confs[vf.id as usize] = vf.clone();
143                        }
144                    }
145                    self.vfs = Some(new_vf_confs);
146                }
147            }
148        }
149    }
150}
151
152#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
153#[serde(rename_all = "kebab-case", deny_unknown_fields)]
154#[non_exhaustive]
155pub struct SrIovVfConfig {
156    #[serde(deserialize_with = "crate::deserializer::u32_or_string")]
157    pub id: u32,
158    /// Interface name for this VF, only for querying, will be ignored
159    /// when applying network state.
160    #[serde(default, skip_serializing_if = "String::is_empty")]
161    pub iface_name: String,
162    #[serde(skip_serializing_if = "Option::is_none")]
163    /// Deserialize and serialize from/to `mac-address`.
164    pub mac_address: Option<String>,
165    #[serde(
166        skip_serializing_if = "Option::is_none",
167        default,
168        deserialize_with = "crate::deserializer::option_bool_or_string"
169    )]
170    /// Deserialize and serialize from/to `spoof-check`.
171    pub spoof_check: Option<bool>,
172    #[serde(
173        skip_serializing_if = "Option::is_none",
174        default,
175        deserialize_with = "crate::deserializer::option_bool_or_string"
176    )]
177    pub trust: Option<bool>,
178    #[serde(
179        skip_serializing_if = "Option::is_none",
180        default,
181        deserialize_with = "crate::deserializer::option_u32_or_string"
182    )]
183    /// Deserialize and serialize from/to `min_tx_rate`.
184    pub min_tx_rate: Option<u32>,
185    #[serde(
186        skip_serializing_if = "Option::is_none",
187        default,
188        deserialize_with = "crate::deserializer::option_u32_or_string"
189    )]
190    /// Deserialize and serialize from/to `max-tx-rate`.
191    pub max_tx_rate: Option<u32>,
192    #[serde(
193        skip_serializing_if = "Option::is_none",
194        default,
195        deserialize_with = "crate::deserializer::option_u32_or_string"
196    )]
197    /// Deserialize and serialize from/to `vlan-id`.
198    pub vlan_id: Option<u32>,
199    #[serde(
200        skip_serializing_if = "Option::is_none",
201        default,
202        deserialize_with = "crate::deserializer::option_u32_or_string"
203    )]
204    pub qos: Option<u32>,
205
206    #[serde(skip_serializing_if = "Option::is_none")]
207    pub vlan_proto: Option<VlanProtocol>,
208}
209
210impl SrIovVfConfig {
211    pub fn new() -> Self {
212        Self::default()
213    }
214}
215
216impl Interfaces {
217    pub(crate) fn resolve_sriov_reference(
218        &mut self,
219        current: &Self,
220    ) -> Result<(), NmstateError> {
221        self.resolve_sriov_reference_iface_name(current)?;
222        self.resolve_sriov_reference_port_name(current)?;
223        Ok(())
224    }
225
226    fn resolve_sriov_reference_iface_name(
227        &mut self,
228        current: &Self,
229    ) -> Result<(), NmstateError> {
230        let mut changed_iface_names: Vec<String> = Vec::new();
231        for iface in self.kernel_ifaces.values_mut() {
232            if let Some((pf_name, vf_id)) = parse_sriov_vf_naming(iface.name())?
233            {
234                if let Some(vf_iface_name) =
235                    get_sriov_vf_iface_name(current, pf_name, vf_id)
236                {
237                    changed_iface_names.push(iface.name().to_string());
238                    log::info!(
239                        "SR-IOV VF {} resolved to interface name {}",
240                        iface.name(),
241                        vf_iface_name
242                    );
243                    iface.base_iface_mut().name = vf_iface_name;
244                } else {
245                    let e = NmstateError::new(
246                        ErrorKind::InvalidArgument,
247                        format!(
248                            "Failed to find SR-IOV VF interface name for {}",
249                            iface.name()
250                        ),
251                    );
252                    log::error!("{e}");
253                    return Err(e);
254                }
255            }
256        }
257        for changed_iface_name in changed_iface_names {
258            if let Some(iface) = self.kernel_ifaces.remove(&changed_iface_name)
259            {
260                if self.kernel_ifaces.contains_key(iface.name()) {
261                    let e = NmstateError::new(
262                        ErrorKind::InvalidArgument,
263                        format!(
264                            "SR-IOV VF name {} has been resolved as interface \
265                             {}, but it is already defined in desire state",
266                            changed_iface_name,
267                            iface.name()
268                        ),
269                    );
270                    log::error!("{e}");
271                    return Err(e);
272                }
273                self.kernel_ifaces.insert(iface.name().to_string(), iface);
274            }
275        }
276        Ok(())
277    }
278
279    fn resolve_sriov_reference_port_name(
280        &mut self,
281        current: &Self,
282    ) -> Result<(), NmstateError> {
283        //  pending_changes:
284        //      Vec<(ctrl_name, ctrl_iface_type, origin_name, new_name)>
285        let mut pending_changes = Vec::new();
286        for iface in self
287            .kernel_ifaces
288            .values()
289            .chain(self.user_ifaces.values())
290            .filter(|i| i.is_controller())
291        {
292            let ports = match iface.ports() {
293                Some(p) => p,
294                None => continue,
295            };
296            for port in ports {
297                if let Some((pf_name, vf_id)) = parse_sriov_vf_naming(port)? {
298                    if let Some(vf_iface_name) =
299                        get_sriov_vf_iface_name(current, pf_name, vf_id)
300                    {
301                        log::info!(
302                            "SR-IOV VF {port} resolved to interface name \
303                             {vf_iface_name}"
304                        );
305                        pending_changes.push((
306                            iface.name().to_string(),
307                            iface.iface_type(),
308                            port.to_string(),
309                            vf_iface_name.to_string(),
310                        ));
311                    } else {
312                        return Err(NmstateError::new(
313                            ErrorKind::InvalidArgument,
314                            format!(
315                                "Failed to find SR-IOV VF interface name for \
316                                 {}",
317                                iface.name()
318                            ),
319                        ));
320                    }
321                }
322            }
323        }
324        for (ctrl, ctrl_iface_type, origin_name, new_name) in pending_changes {
325            if let Some(iface) = self.get_iface_mut(&ctrl, ctrl_iface_type) {
326                iface.change_port_name(origin_name.as_str(), new_name);
327            }
328        }
329        Ok(())
330    }
331}
332
333fn parse_sriov_vf_naming(
334    iface_name: &str,
335) -> Result<Option<(&str, u32)>, NmstateError> {
336    if iface_name.starts_with(SrIovConfig::VF_NAMING_PREFIX) {
337        let names: Vec<&str> =
338            iface_name.split(SrIovConfig::VF_NAMING_SEPERATOR).collect();
339        if names.len() == 3 {
340            match names[2].parse::<u32>() {
341                Ok(vf_id) => Ok(Some((names[1], vf_id))),
342                Err(e) => {
343                    let e = NmstateError::new(
344                        ErrorKind::InvalidArgument,
345                        format!(
346                            "Invalid SR-IOV VF ID in {iface_name}, correct \
347                             format is 'sriov:<pf_name>:<vf_id>', error: {e}"
348                        ),
349                    );
350                    log::error!("{e}");
351                    Err(e)
352                }
353            }
354        } else {
355            let e = NmstateError::new(
356                ErrorKind::InvalidArgument,
357                format!(
358                    "Invalid SR-IOV VF name {iface_name}, correct format is \
359                     'sriov:<pf_name>:<vf_id>'",
360                ),
361            );
362            log::error!("{e}");
363            Err(e)
364        }
365    } else {
366        Ok(None)
367    }
368}
369
370fn get_sriov_vf_iface_name(
371    current: &Interfaces,
372    pf_name: &str,
373    vf_id: u32,
374) -> Option<String> {
375    if let Some(Interface::Ethernet(pf_iface)) =
376        current.get_iface(pf_name, InterfaceType::Ethernet)
377    {
378        if let Some(vfs) = pf_iface
379            .ethernet
380            .as_ref()
381            .and_then(|e| e.sr_iov.as_ref())
382            .and_then(|s| s.vfs.as_ref())
383        {
384            for vf in vfs {
385                if vf.id == vf_id {
386                    if !vf.iface_name.is_empty() {
387                        return Some(vf.iface_name.clone());
388                    }
389                    break;
390                }
391            }
392        }
393    }
394    None
395}
396
397impl MergedInterface {
398    pub(crate) fn post_inter_ifaces_process_sriov(
399        &mut self,
400    ) -> Result<(), NmstateError> {
401        if let (
402            Some(Interface::Ethernet(apply_iface)),
403            Some(Interface::Ethernet(verify_iface)),
404            Some(Interface::Ethernet(cur_iface)),
405        ) = (
406            self.for_apply.as_mut(),
407            self.for_verify.as_mut(),
408            self.current.as_ref(),
409        ) {
410            let cur_conf =
411                cur_iface.ethernet.as_ref().and_then(|e| e.sr_iov.as_ref());
412            if let (Some(apply_conf), Some(verify_conf)) = (
413                apply_iface
414                    .ethernet
415                    .as_mut()
416                    .and_then(|e| e.sr_iov.as_mut()),
417                verify_iface
418                    .ethernet
419                    .as_mut()
420                    .and_then(|e| e.sr_iov.as_mut()),
421            ) {
422                apply_conf.auto_fill_unmentioned_vf_id(cur_conf);
423                verify_conf.auto_fill_unmentioned_vf_id(cur_conf);
424            }
425        }
426        Ok(())
427    }
428}