Skip to main content

nmstate/ifaces/
base.rs

1// SPDX-License-Identifier: Apache-2.0
2
3use serde::{Deserialize, Serialize};
4
5use crate::{
6    AltNameEntry, DispatchConfig, ErrorKind, EthtoolConfig, Ieee8021XConfig,
7    InterfaceIdentifier, InterfaceIpv4, InterfaceIpv6, InterfaceState,
8    InterfaceType, LldpConfig, MergedInterface, MptcpConfig, NmstateError,
9    OvsDbIfaceConfig, PciAddress, RouteEntry, WaitIp,
10};
11
12const MINIMUM_IPV6_MTU: u64 = 1280;
13
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
15#[serde(rename_all = "kebab-case", deny_unknown_fields)]
16#[non_exhaustive]
17/// Information shared among all interface types
18pub struct BaseInterface {
19    /// Interface name, when applying with `InterfaceIdentifier::MacAddress`,
20    /// if `profile_name` not defined, this will be used as profile name.
21    pub name: String,
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub profile_name: Option<String>,
24    #[serde(skip_serializing_if = "crate::serializer::is_option_string_empty")]
25    /// Interface description stored in network backend. Not available for
26    /// kernel only mode.
27    pub description: Option<String>,
28    #[serde(rename = "type", default = "default_iface_type")]
29    /// Interface type. Serialize and deserialize to/from `type`
30    pub iface_type: InterfaceType,
31    #[serde(skip_serializing_if = "crate::serializer::is_option_string_empty")]
32    /// The driver of the specified network device.
33    pub driver: Option<String>,
34    #[serde(default = "default_state")]
35    /// Interface state. Default to [InterfaceState::Up] when applying.
36    pub state: InterfaceState,
37    #[serde(default, skip_serializing_if = "Option::is_none")]
38    /// Define network backend matching method on choosing network interface.
39    /// Default to [InterfaceIdentifier::Name].
40    pub identifier: Option<InterfaceIdentifier>,
41    /// When applying with `[InterfaceIdentifier::MacAddress]`,
42    /// nmstate will store original desired interface name as `profile_name`
43    /// here and store the real interface name as `name` property.
44    #[serde(skip_serializing_if = "Option::is_none")]
45    /// For [InterfaceIdentifier::Name] (default), this property will change
46    /// the interface MAC address to desired one when applying.
47    /// For [InterfaceIdentifier::MacAddress], this property will be used
48    /// for searching interface on desired MAC address when applying.
49    /// MAC address in the format: upper case hex string separated by `:` on
50    /// every two characters. Case insensitive when applying.
51    /// Serialize and deserialize to/from `mac-address`.
52    pub mac_address: Option<String>,
53    #[serde(skip_serializing_if = "Option::is_none")]
54    /// MAC address never change after reboots(normally stored in firmware of
55    /// network interface). Using the same format as `mac_address` property.
56    /// Ignored during apply.
57    pub permanent_mac_address: Option<String>,
58    #[serde(skip_serializing_if = "Option::is_none")]
59    /// The PCI address of interface. For [InterfaceIdentifier::PciAddress],
60    /// this PCI address will be used for searching interface.
61    /// The accepted formats are: `00:14.3` and `0000:00:1f.4`.
62    /// Serialize and deserialize to/from `pci-address`.
63    pub pci_address: Option<PciAddress>,
64    #[serde(
65        skip_serializing_if = "Option::is_none",
66        default,
67        deserialize_with = "crate::deserializer::option_u64_or_string"
68    )]
69    /// Maximum transmission unit.
70    pub mtu: Option<u64>,
71    #[serde(skip_serializing_if = "Option::is_none")]
72    /// Minimum MTU allowed. Ignored during apply.
73    /// Serialize and deserialize to/from `min-mtu`.
74    pub min_mtu: Option<u64>,
75    #[serde(skip_serializing_if = "Option::is_none")]
76    /// Maximum MTU allowed. Ignored during apply.
77    /// Serialize and deserialize to/from `max-mtu`.
78    pub max_mtu: Option<u64>,
79    #[serde(skip_serializing_if = "Option::is_none")]
80    /// Whether system should wait certain IP stack before considering
81    /// network interface activated.
82    /// Serialize and deserialize to/from `wait-ip`.
83    pub wait_ip: Option<WaitIp>,
84    #[serde(skip_serializing_if = "Option::is_none")]
85    /// IPv4 information.
86    /// Hided if interface is not allowed to hold IP information(e.g. port of
87    /// bond is not allowed to hold IP information).
88    pub ipv4: Option<InterfaceIpv4>,
89    #[serde(skip_serializing_if = "Option::is_none")]
90    /// IPv4 information.
91    /// Hided if interface is not allowed to hold IP information(e.g. port of
92    /// bond is not allowed to hold IP information).
93    pub ipv6: Option<InterfaceIpv6>,
94    #[serde(skip_serializing_if = "Option::is_none")]
95    /// Interface wide MPTCP flags.
96    /// Nmstate will apply these flags to all valid IP addresses(both static
97    /// and dynamic).
98    pub mptcp: Option<MptcpConfig>,
99    #[serde(skip_serializing_if = "Option::is_none")]
100    /// Controller of the specified interface.
101    /// Only valid for applying, `None` means no change, empty string means
102    /// detach from current controller, please be advise, an error will trigger
103    /// if this property conflict with ports list of bridge/bond/etc.
104    /// Been always set to `None` by [crate::NetworkState::retrieve()].
105    pub controller: Option<String>,
106    #[serde(
107        skip_serializing_if = "Option::is_none",
108        default,
109        deserialize_with = "crate::deserializer::option_bool_or_string"
110    )]
111    /// Whether kernel should skip check on package targeting MAC address and
112    /// accept all packages, also known as promiscuous mode.
113    /// Serialize and deserialize to/from `accpet-all-mac-addresses`.
114    pub accept_all_mac_addresses: Option<bool>,
115    #[serde(skip_serializing_if = "Option::is_none")]
116    /// Copy the MAC address from specified interface.
117    /// Ignored during serializing.
118    /// Deserialize from `copy-mac-from`.
119    pub copy_mac_from: Option<String>,
120    #[serde(skip_serializing_if = "Option::is_none", rename = "ovs-db")]
121    /// Interface specific OpenvSwitch database configurations.
122    pub ovsdb: Option<OvsDbIfaceConfig>,
123    #[serde(skip_serializing_if = "Option::is_none", rename = "802.1x")]
124    /// IEEE 802.1X authentication configurations.
125    /// Serialize and deserialize to/from `802.1x`.
126    pub ieee8021x: Option<Ieee8021XConfig>,
127    #[serde(skip_serializing_if = "Option::is_none")]
128    /// Link Layer Discovery Protocol configurations.
129    pub lldp: Option<LldpConfig>,
130    #[serde(skip_serializing_if = "Option::is_none")]
131    /// Ethtool configurations
132    pub ethtool: Option<EthtoolConfig>,
133    /// Dispatch script configurations
134    #[serde(skip_serializing_if = "Option::is_none")]
135    pub dispatch: Option<DispatchConfig>,
136    /// Alternative names
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub alt_names: Option<Vec<AltNameEntry>>,
139    #[serde(skip)]
140    pub controller_type: Option<InterfaceType>,
141    // The interface lowest up_priority will be activated first.
142    // The up_priority should be its controller's up_priority
143    // plus one.
144    // The 0 means top controller or no controller.
145    #[serde(skip)]
146    pub(crate) up_priority: u32,
147    #[serde(skip)]
148    pub(crate) routes: Option<Vec<RouteEntry>>,
149    #[serde(flatten)]
150    pub _other: serde_json::Map<String, serde_json::Value>,
151}
152
153impl BaseInterface {
154    // Besides normal HashMap merging:
155    //  * the IP stacks need extra care
156    //  * `copy_mac_from` is skip_serializing
157    //  * `permanent_mac_address` is skip_serializing
158    pub(crate) fn special_merge(&mut self, desired: &Self, current: &Self) {
159        if let Some(ipv4) = self.ipv4.as_mut()
160            && let (Some(d), Some(c)) =
161                (desired.ipv4.as_ref(), current.ipv4.as_ref())
162        {
163            ipv4.special_merge(d, c);
164        }
165        if let Some(ipv6) = self.ipv6.as_mut()
166            && let (Some(d), Some(c)) =
167                (desired.ipv6.as_ref(), current.ipv6.as_ref())
168        {
169            ipv6.special_merge(d, c);
170        }
171        if self.permanent_mac_address.is_none() {
172            self.permanent_mac_address
173                .clone_from(&current.permanent_mac_address);
174        }
175        self.copy_mac_from.clone_from(&desired.copy_mac_from);
176    }
177
178    fn has_controller(&self) -> bool {
179        if let Some(ctrl) = self.controller.as_deref() {
180            !ctrl.is_empty()
181        } else {
182            false
183        }
184    }
185
186    /// Whether this interface can hold IP information or not.
187    pub fn can_have_ip(&self) -> bool {
188        (!self.has_controller())
189            || self.iface_type == InterfaceType::OvsInterface
190            || self.controller_type == Some(InterfaceType::Vrf)
191    }
192
193    pub(crate) fn is_up_priority_valid(&self) -> bool {
194        if self.has_controller() {
195            self.up_priority != 0
196        } else {
197            true
198        }
199    }
200
201    /// Create empty [BaseInterface] with state set to [InterfaceState::Up]
202    pub fn new() -> Self {
203        Self {
204            state: InterfaceState::Up,
205            ..Default::default()
206        }
207    }
208
209    pub(crate) fn clone_name_type_only(&self) -> Self {
210        Self {
211            name: self.name.clone(),
212            iface_type: self.iface_type.clone(),
213            state: InterfaceState::Up,
214            ..Default::default()
215        }
216    }
217
218    pub(crate) fn hide_secrets(&mut self) {
219        if let Some(conf) = self.ieee8021x.as_mut() {
220            conf.hide_secrets();
221        }
222    }
223
224    pub(crate) fn is_ipv4_enabled(&self) -> bool {
225        self.ipv4.as_ref().map(|i| i.enabled) == Some(true)
226    }
227
228    pub(crate) fn is_ipv6_enabled(&self) -> bool {
229        self.ipv6.as_ref().map(|i| i.enabled) == Some(true)
230    }
231
232    pub(crate) fn sanitize(
233        &mut self,
234        is_desired: bool,
235    ) -> Result<(), NmstateError> {
236        if let Some(mac) = self.mac_address.as_mut() {
237            mac.make_ascii_uppercase();
238        }
239        // These are not for apply or verify
240        self.permanent_mac_address = None;
241        self.max_mtu = None;
242        self.min_mtu = None;
243        self.copy_mac_from = None;
244        self.driver = None;
245
246        if let Some(ipv4_conf) = self.ipv4.as_mut() {
247            ipv4_conf.sanitize(is_desired)?;
248        }
249        if let Some(ipv6_conf) = self.ipv6.as_mut() {
250            ipv6_conf.sanitize(is_desired)?;
251            if ipv6_conf.enabled
252                && let Some(mtu) = self.mtu
253                && mtu < MINIMUM_IPV6_MTU
254            {
255                return Err(NmstateError::new(
256                    ErrorKind::InvalidArgument,
257                    format!(
258                        "MTU should be >= {MINIMUM_IPV6_MTU} when IPv6 is \
259                         enabled on interface {}, but got mtu: {mtu}",
260                        self.name.as_str()
261                    ),
262                ));
263            }
264        }
265        if let Some(lldp_conf) = self.lldp.as_mut() {
266            lldp_conf.sanitize();
267        }
268
269        if !self.can_have_ip() {
270            self.wait_ip = None;
271        }
272
273        if is_desired
274            && self.iface_type.is_userspace()
275            && self.dispatch.is_some()
276        {
277            return Err(NmstateError::new(
278                ErrorKind::InvalidArgument,
279                format!(
280                    "User space interface {}/{} is not allow to hold dispatch \
281                     configurations",
282                    self.name.as_str(),
283                    self.iface_type,
284                ),
285            ));
286        }
287
288        // Remove permanent_mac_address in desired state as it is query only
289        if is_desired {
290            self.permanent_mac_address = None;
291        }
292
293        if let Some(ethtool) = self.ethtool.as_mut() {
294            ethtool.sanitize(is_desired);
295        }
296        self.sanitize_alt_names()?;
297        Ok(())
298    }
299}
300
301fn default_state() -> InterfaceState {
302    InterfaceState::Up
303}
304
305fn default_iface_type() -> InterfaceType {
306    InterfaceType::Unknown
307}
308
309impl MergedInterface {
310    pub(crate) fn post_inter_ifaces_process_base_iface(
311        &mut self,
312    ) -> Result<(), NmstateError> {
313        self.post_inter_ifaces_process_ip()?;
314        self.post_inter_ifaces_process_mptcp()?;
315        self.post_inter_ifaces_process_ethtool();
316        self.validate_mtu()?;
317        self.validate_can_have_ip()?;
318        Ok(())
319    }
320
321    fn validate_mtu(&self) -> Result<(), NmstateError> {
322        if let (Some(desired), Some(current)) = (
323            self.desired.as_ref().map(|i| i.base_iface()),
324            self.current.as_ref().map(|i| i.base_iface()),
325        ) && let (Some(desire_mtu), Some(min_mtu), Some(max_mtu)) =
326            (desired.mtu, current.min_mtu, current.max_mtu)
327        {
328            if desire_mtu > max_mtu {
329                return Err(NmstateError::new(
330                    ErrorKind::InvalidArgument,
331                    format!(
332                        "Desired MTU {} for interface {} is bigger than \
333                         maximum allowed MTU {}",
334                        desire_mtu, desired.name, max_mtu
335                    ),
336                ));
337            } else if desire_mtu < min_mtu {
338                return Err(NmstateError::new(
339                    ErrorKind::InvalidArgument,
340                    format!(
341                        "Desired MTU {} for interface {} is smaller than \
342                         minimum allowed MTU {}",
343                        desire_mtu, desired.name, min_mtu
344                    ),
345                ));
346            }
347        }
348        Ok(())
349    }
350
351    fn validate_can_have_ip(&mut self) -> Result<(), NmstateError> {
352        if self.is_desired()
353            && self.merged.is_up()
354            && let Some(apply_iface) = self.for_apply.as_ref()
355        {
356            let base_iface = apply_iface.base_iface();
357            if !base_iface.can_have_ip()
358                && (base_iface.ipv4.as_ref().map(|ipv4| ipv4.enabled)
359                    == Some(true)
360                    || base_iface.ipv6.as_ref().map(|ipv6| ipv6.enabled)
361                        == Some(true))
362            {
363                if let Some(ctrl) = apply_iface.base_iface().controller.as_ref()
364                {
365                    return Err(NmstateError::new(
366                        ErrorKind::InvalidArgument,
367                        format!(
368                            "Interface {} cannot have IP enabled as it is \
369                             attached to controller {ctrl} where IP is not \
370                             allowed on its port",
371                            base_iface.name.as_str()
372                        ),
373                    ));
374                } else {
375                    return Err(NmstateError::new(
376                        ErrorKind::InvalidArgument,
377                        format!(
378                            "Interface {} cannot have IP enabled",
379                            base_iface.name.as_str()
380                        ),
381                    ));
382                }
383            }
384        }
385        Ok(())
386    }
387
388    /// Copy MAC address when identifier is [InterfaceIdentifier::MacAddress]
389    /// Copy PCI address when identifier is [InterfaceIdentifier::PciAddress]
390    pub(crate) fn preserve_current_identifer_info(&mut self) {
391        if self.for_apply.as_ref().map(|i| i.is_up()) == Some(true)
392            && let (Some(apply_iface), Some(cur_iface)) =
393                (self.for_apply.as_mut(), self.current.as_ref())
394        {
395            match cur_iface.base_iface().identifier {
396                Some(InterfaceIdentifier::MacAddress) => {
397                    if apply_iface.base_iface().identifier.is_none() {
398                        apply_iface
399                            .base_iface_mut()
400                            .identifier
401                            .clone_from(&cur_iface.base_iface().identifier);
402                        if apply_iface.base_iface().mac_address.is_none() {
403                            apply_iface
404                                .base_iface_mut()
405                                .mac_address
406                                .clone_from(
407                                    &cur_iface.base_iface().mac_address,
408                                );
409                        }
410                        if apply_iface.base_iface().profile_name.is_none() {
411                            apply_iface
412                                .base_iface_mut()
413                                .profile_name
414                                .clone_from(
415                                    &cur_iface.base_iface().profile_name,
416                                );
417                        }
418                    }
419                }
420                Some(InterfaceIdentifier::PciAddress) => {
421                    if apply_iface.base_iface().identifier.is_none() {
422                        apply_iface
423                            .base_iface_mut()
424                            .identifier
425                            .clone_from(&cur_iface.base_iface().identifier);
426                        if apply_iface.base_iface().pci_address.is_none() {
427                            apply_iface
428                                .base_iface_mut()
429                                .pci_address
430                                .clone_from(
431                                    &cur_iface.base_iface().pci_address,
432                                );
433                        }
434                        if apply_iface.base_iface().profile_name.is_none() {
435                            apply_iface
436                                .base_iface_mut()
437                                .profile_name
438                                .clone_from(
439                                    &cur_iface.base_iface().profile_name,
440                                );
441                        }
442                    }
443                }
444                // Do not use _ match here, we want developer to be
445                // notified by compiler when new identifier been added.
446                Some(InterfaceIdentifier::Name) | None => (),
447            }
448        }
449    }
450}