1use 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]
17pub struct BaseInterface {
19 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 pub description: Option<String>,
28 #[serde(rename = "type", default = "default_iface_type")]
29 pub iface_type: InterfaceType,
31 #[serde(skip_serializing_if = "crate::serializer::is_option_string_empty")]
32 pub driver: Option<String>,
34 #[serde(default = "default_state")]
35 pub state: InterfaceState,
37 #[serde(default, skip_serializing_if = "Option::is_none")]
38 pub identifier: Option<InterfaceIdentifier>,
41 #[serde(skip_serializing_if = "Option::is_none")]
45 pub mac_address: Option<String>,
53 #[serde(skip_serializing_if = "Option::is_none")]
54 pub permanent_mac_address: Option<String>,
58 #[serde(skip_serializing_if = "Option::is_none")]
59 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 pub mtu: Option<u64>,
71 #[serde(skip_serializing_if = "Option::is_none")]
72 pub min_mtu: Option<u64>,
75 #[serde(skip_serializing_if = "Option::is_none")]
76 pub max_mtu: Option<u64>,
79 #[serde(skip_serializing_if = "Option::is_none")]
80 pub wait_ip: Option<WaitIp>,
84 #[serde(skip_serializing_if = "Option::is_none")]
85 pub ipv4: Option<InterfaceIpv4>,
89 #[serde(skip_serializing_if = "Option::is_none")]
90 pub ipv6: Option<InterfaceIpv6>,
94 #[serde(skip_serializing_if = "Option::is_none")]
95 pub mptcp: Option<MptcpConfig>,
99 #[serde(skip_serializing_if = "Option::is_none")]
100 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 pub accept_all_mac_addresses: Option<bool>,
115 #[serde(skip_serializing_if = "Option::is_none")]
116 pub copy_mac_from: Option<String>,
120 #[serde(skip_serializing_if = "Option::is_none", rename = "ovs-db")]
121 pub ovsdb: Option<OvsDbIfaceConfig>,
123 #[serde(skip_serializing_if = "Option::is_none", rename = "802.1x")]
124 pub ieee8021x: Option<Ieee8021XConfig>,
127 #[serde(skip_serializing_if = "Option::is_none")]
128 pub lldp: Option<LldpConfig>,
130 #[serde(skip_serializing_if = "Option::is_none")]
131 pub ethtool: Option<EthtoolConfig>,
133 #[serde(skip_serializing_if = "Option::is_none")]
135 pub dispatch: Option<DispatchConfig>,
136 #[serde(skip_serializing_if = "Option::is_none")]
138 pub alt_names: Option<Vec<AltNameEntry>>,
139 #[serde(skip)]
140 pub controller_type: Option<InterfaceType>,
141 #[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 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(¤t.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 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 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 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 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 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 Some(InterfaceIdentifier::Name) | None => (),
447 }
448 }
449 }
450}