1use std::collections::HashMap;
4use std::collections::HashSet;
5use std::net::Ipv6Addr;
6
7use serde::{
8 de::IntoDeserializer, Deserialize, Deserializer, Serialize, Serializer,
9};
10
11use crate::{
12 deserializer::NumberAsString, BaseInterface, ErrorKind, Interface,
13 InterfaceState, InterfaceType, MergedInterface, NmstateError,
14};
15
16#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
17#[serde(rename_all = "kebab-case")]
18#[non_exhaustive]
19pub struct BondInterface {
60 #[serde(flatten)]
61 pub base: BaseInterface,
63 #[serde(
64 skip_serializing_if = "Option::is_none",
65 rename = "link-aggregation",
66 alias = "bond"
67 )]
68 pub bond: Option<BondConfig>,
70}
71
72impl Default for BondInterface {
73 fn default() -> Self {
74 let mut base = BaseInterface::new();
75 base.iface_type = InterfaceType::Bond;
76 Self { base, bond: None }
77 }
78}
79
80impl BondInterface {
81 pub(crate) fn special_merge(&mut self, desired: &Self, current: &Self) {
83 if let Some(bond_conf) = self.bond.as_mut() {
84 if let (Some(des_bond_conf), Some(cur_bond_conf)) =
85 (desired.bond.as_ref(), current.bond.as_ref())
86 {
87 if des_bond_conf.mode != cur_bond_conf.mode {
88 bond_conf.options.clone_from(&des_bond_conf.options);
89 }
90 }
91 }
92 }
93
94 fn drop_empty_arp_ip_target(&mut self) {
95 if let Some(ref mut bond_conf) = self.bond {
96 if let Some(ref mut bond_opts) = &mut bond_conf.options {
97 if let Some(ref mut arp_ip_target) = bond_opts.arp_ip_target {
98 if arp_ip_target.is_empty() {
99 bond_opts.arp_ip_target = None;
100 }
101 }
102 if let Some(ref mut v) = bond_opts.ns_ip6_target {
103 if v.is_empty() {
104 bond_opts.ns_ip6_target = None;
105 }
106 }
107 }
108 }
109 }
110
111 fn sort_ports(&mut self) {
112 if let Some(ports) = self.bond.as_mut().and_then(|b| b.port.as_mut()) {
113 ports.sort_unstable();
114 }
115 }
116
117 fn sort_ports_config(&mut self) {
118 if let Some(port_confs) =
119 self.bond.as_mut().and_then(|b| b.ports_config.as_mut())
120 {
121 port_confs.sort_unstable_by_key(|p| p.name.clone());
122 }
123 }
124
125 pub(crate) fn sanitize(&mut self) -> Result<(), NmstateError> {
126 self.sort_ports();
127 self.sort_ports_config();
128 self.drop_empty_arp_ip_target();
129 self.make_ad_actor_system_mac_upper_case();
130 self.check_overlap_queue_id()?;
131 Ok(())
132 }
133
134 fn check_overlap_queue_id(&self) -> Result<(), NmstateError> {
138 let mut existing_qids: HashMap<u16, &str> = HashMap::new();
139 if let Some(ports_conf) =
140 self.bond.as_ref().and_then(|b| b.ports_config.as_deref())
141 {
142 for port_conf in ports_conf
143 .iter()
144 .filter(|p| p.queue_id.is_some() && p.queue_id != Some(0))
145 {
146 if let Some(queue_id) = port_conf.queue_id {
147 if let Some(exist_port_name) = existing_qids.get(&queue_id)
148 {
149 return Err(NmstateError::new(
150 ErrorKind::InvalidArgument,
151 format!(
152 "Port {} and {} of Bond {} are sharing the \
153 same queue-id which is not supported by \
154 linux kernel yet",
155 exist_port_name,
156 port_conf.name.as_str(),
157 self.base.name.as_str()
158 ),
159 ));
160 } else {
161 existing_qids.insert(queue_id, port_conf.name.as_str());
162 }
163 }
164 }
165 }
166 Ok(())
167 }
168
169 pub(crate) fn ports(&self) -> Option<Vec<&str>> {
170 self.bond.as_ref().and_then(|bond_conf| {
171 bond_conf
172 .port
173 .as_ref()
174 .map(|ports| {
175 ports.as_slice().iter().map(|p| p.as_str()).collect()
176 })
177 .or_else(|| {
178 bond_conf.ports_config.as_ref().map(|ports| {
179 ports
180 .as_slice()
181 .iter()
182 .map(|p| p.name.as_str())
183 .collect()
184 })
185 })
186 })
187 }
188
189 pub(crate) fn get_port_conf(
190 &self,
191 port_name: &str,
192 ) -> Option<&BondPortConfig> {
193 self.bond
194 .as_ref()
195 .and_then(|bond_conf| bond_conf.ports_config.as_ref())
196 .and_then(|port_confs| {
197 port_confs
198 .iter()
199 .find(|port_conf| port_conf.name == port_name)
200 })
201 }
202
203 pub(crate) fn mode(&self) -> Option<BondMode> {
204 self.bond.as_ref().and_then(|bond_conf| bond_conf.mode)
205 }
206
207 pub fn new() -> Self {
208 Self::default()
209 }
210
211 fn is_mac_restricted_mode(&self) -> bool {
212 self.bond
213 .as_ref()
214 .and_then(|bond_conf| {
215 if self.mode() == Some(BondMode::ActiveBackup) {
216 bond_conf.options.as_ref()
217 } else {
218 None
219 }
220 })
221 .and_then(|bond_opts| bond_opts.fail_over_mac)
222 == Some(BondFailOverMac::Active)
223 }
224
225 fn is_not_mac_restricted_mode_explicitly(&self) -> bool {
226 (self.mode().is_some() && self.mode() != Some(BondMode::ActiveBackup))
227 || ![None, Some(BondFailOverMac::Active)].contains(
228 &self
229 .bond
230 .as_ref()
231 .and_then(|bond_conf| bond_conf.options.as_ref())
232 .and_then(|bond_opts| bond_opts.fail_over_mac),
233 )
234 }
235
236 fn validate_new_iface_with_no_mode(
237 &self,
238 current: Option<&Interface>,
239 ) -> Result<(), NmstateError> {
240 if self.base.state == InterfaceState::Up
241 && current.is_none()
242 && self.mode().is_none()
243 {
244 let e = NmstateError::new(
245 ErrorKind::InvalidArgument,
246 format!(
247 "Bond mode is mandatory for new bond interface: {}",
248 &self.base.name
249 ),
250 );
251 log::error!("{e}");
252 return Err(e);
253 }
254 Ok(())
255 }
256
257 fn sanitize_mac_restricted_mode(&mut self, current: Option<&Interface>) {
264 let warn_msg = format!(
265 "The bond interface {} with fail_over_mac:active and \
266 mode:active-backup is determined by its ports, hence ignoring \
267 desired MAC address",
268 self.base.name.as_str()
269 );
270
271 if let Some(Interface::Bond(current)) = current {
272 if current.is_mac_restricted_mode()
273 && self.base.mac_address.is_some()
274 && !self.is_not_mac_restricted_mode_explicitly()
275 {
276 self.base.mac_address = None;
277 log::warn!("{warn_msg}");
278 }
279 } else if self.is_mac_restricted_mode()
280 && self.base.mac_address.is_some()
281 {
282 self.base.mac_address = None;
283 log::warn!("{warn_msg}");
284 }
285 }
286
287 fn validate_conflict_in_port_and_port_configs(
288 &self,
289 ) -> Result<(), NmstateError> {
290 if let Some(bond_conf) = self.bond.as_ref() {
291 if let (Some(ports), Some(ports_config)) =
292 (&bond_conf.port, &bond_conf.ports_config)
293 {
294 let ports: HashSet<&str> =
295 ports.iter().map(String::as_str).collect();
296 let port_confs: HashSet<&str> =
297 ports_config.iter().map(|p| p.name.as_str()).collect();
298
299 if ports != port_confs {
300 let missing_in_config: Vec<&str> =
301 ports.difference(&port_confs).copied().collect();
302 let missing_in_ports: Vec<&str> =
303 port_confs.difference(&ports).copied().collect();
304
305 let mut error_msg_detail = String::new();
306
307 if !missing_in_config.is_empty() {
308 error_msg_detail.push_str(&format!(
309 "Ports in `port` but not in `ports_config`: {}. ",
310 missing_in_config.join(", ")
311 ));
312 }
313 if !missing_in_ports.is_empty() {
314 error_msg_detail.push_str(&format!(
315 "Ports in `ports_config` but not in `port`: {}. ",
316 missing_in_ports.join(", ")
317 ));
318 }
319
320 let e = NmstateError::new(
321 ErrorKind::InvalidArgument,
322 format!(
323 "The port names specified in `port` conflict with \
324 the port names specified in `ports-config` for \
325 bond interface: {}. {}",
326 &self.base.name, error_msg_detail
327 ),
328 );
329 log::error!("{e}");
330 return Err(e);
331 }
332 }
333 }
334
335 Ok(())
336 }
337
338 pub(crate) fn is_options_reset(&self) -> bool {
339 if let Some(bond_opts) = self
340 .bond
341 .as_ref()
342 .and_then(|bond_conf| bond_conf.options.as_ref())
343 {
344 bond_opts == &BondOptions::default()
345 } else {
346 false
347 }
348 }
349
350 fn make_ad_actor_system_mac_upper_case(&mut self) {
351 if let Some(mac) = self
352 .bond
353 .as_mut()
354 .and_then(|c| c.options.as_mut())
355 .and_then(|o| o.ad_actor_system.as_mut())
356 {
357 mac.make_ascii_uppercase();
358 }
359 }
360
361 pub(crate) fn remove_port(&mut self, port_to_remove: &str) {
362 if let Some(index) = self.bond.as_ref().and_then(|bond_conf| {
363 bond_conf.port.as_ref().and_then(|ports| {
364 ports
365 .iter()
366 .position(|port_name| port_name == port_to_remove)
367 })
368 }) {
369 self.bond
370 .as_mut()
371 .and_then(|bond_conf| bond_conf.port.as_mut())
372 .map(|ports| ports.remove(index));
373 }
374 }
375
376 pub(crate) fn change_port_name(
377 &mut self,
378 origin_name: &str,
379 new_name: String,
380 ) {
381 if let Some(port_name) = self
382 .bond
383 .as_mut()
384 .and_then(|bond_conf| bond_conf.port.as_mut())
385 .and_then(|ports| {
386 ports
387 .iter_mut()
388 .find(|port_name| port_name.as_str() == origin_name)
389 })
390 {
391 *port_name = new_name.clone();
392 }
393
394 if let Some(port_conf) = self
395 .bond
396 .as_mut()
397 .and_then(|bond_conf| bond_conf.ports_config.as_mut())
398 .and_then(|port_confs| {
399 port_confs
400 .iter_mut()
401 .find(|port_conf| port_conf.name == origin_name)
402 })
403 {
404 port_conf.name = new_name;
405 }
406 }
407
408 pub(crate) fn get_config_changed_ports(&self, current: &Self) -> Vec<&str> {
409 let mut ret: Vec<&str> = Vec::new();
410 let mut des_ports_index: HashMap<&str, &BondPortConfig> =
411 HashMap::new();
412 let mut cur_ports_index: HashMap<&str, &BondPortConfig> =
413 HashMap::new();
414 if let Some(port_confs) = self
415 .bond
416 .as_ref()
417 .and_then(|bond_conf| bond_conf.ports_config.as_ref())
418 {
419 for port_conf in port_confs {
420 des_ports_index.insert(port_conf.name.as_str(), port_conf);
421 }
422 }
423
424 if let Some(port_confs) = current
425 .bond
426 .as_ref()
427 .and_then(|bond_conf| bond_conf.ports_config.as_ref())
428 {
429 for port_conf in port_confs {
430 cur_ports_index.insert(port_conf.name.as_str(), port_conf);
431 }
432 }
433
434 for (port_name, port_conf) in des_ports_index.iter() {
435 if let Some(cur_port_conf) = cur_ports_index.get(port_name) {
436 if port_conf.is_changed(cur_port_conf) {
437 ret.push(port_name);
438 }
439 }
440 }
441 ret
442 }
443}
444
445#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
446#[non_exhaustive]
447#[serde(remote = "BondMode")]
448#[derive(Default)]
450pub enum BondMode {
451 #[serde(rename = "balance-rr", alias = "0")]
452 #[default]
455 RoundRobin,
456 #[serde(rename = "active-backup", alias = "1")]
457 ActiveBackup,
460 #[serde(rename = "balance-xor", alias = "2")]
461 XOR,
464 #[serde(rename = "broadcast", alias = "3")]
465 Broadcast,
468 #[serde(rename = "802.3ad", alias = "lacp", alias = "4")]
469 LACP,
473 #[serde(rename = "balance-tlb", alias = "5")]
474 TLB,
477 #[serde(rename = "balance-alb", alias = "6")]
480 ALB,
481 #[serde(rename = "unknown")]
482 Unknown,
483}
484
485impl<'de> Deserialize<'de> for BondMode {
486 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
487 where
488 D: Deserializer<'de>,
489 {
490 BondMode::deserialize(
491 NumberAsString::deserialize(deserializer)?
492 .as_str()
493 .into_deserializer(),
494 )
495 }
496}
497
498impl Serialize for BondMode {
499 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
500 where
501 S: Serializer,
502 {
503 BondMode::serialize(self, serializer)
504 }
505}
506
507impl std::fmt::Display for BondMode {
508 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
509 write!(
510 f,
511 "{}",
512 match self {
513 BondMode::RoundRobin => "balance-rr",
514 BondMode::ActiveBackup => "active-backup",
515 BondMode::XOR => "balance-xor",
516 BondMode::Broadcast => "broadcast",
517 BondMode::LACP => "802.3ad",
518 BondMode::TLB => "balance-tlb",
519 BondMode::ALB => "balance-alb",
520 BondMode::Unknown => "unknown",
521 }
522 )
523 }
524}
525
526#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
527#[serde(rename_all = "kebab-case", deny_unknown_fields)]
528#[non_exhaustive]
529pub struct BondConfig {
530 #[serde(skip_serializing_if = "Option::is_none")]
531 pub mode: Option<BondMode>,
533 #[serde(skip_serializing_if = "Option::is_none")]
534 pub options: Option<BondOptions>,
539 #[serde(skip_serializing_if = "Option::is_none", alias = "ports")]
540 pub port: Option<Vec<String>>,
544 #[serde(skip_serializing_if = "Option::is_none")]
545 pub ports_config: Option<Vec<BondPortConfig>>,
552}
553
554impl BondConfig {
555 pub fn new() -> Self {
556 Self::default()
557 }
558}
559
560#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
561#[non_exhaustive]
562#[serde(remote = "BondAdSelect", rename_all = "kebab-case")]
563pub enum BondAdSelect {
565 #[serde(alias = "0")]
567 Stable,
568 #[serde(alias = "1")]
570 Bandwidth,
571 #[serde(alias = "2")]
573 Count,
574}
575
576impl<'de> Deserialize<'de> for BondAdSelect {
577 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
578 where
579 D: Deserializer<'de>,
580 {
581 BondAdSelect::deserialize(
582 NumberAsString::deserialize(deserializer)?
583 .as_str()
584 .into_deserializer(),
585 )
586 }
587}
588
589impl Serialize for BondAdSelect {
590 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
591 where
592 S: Serializer,
593 {
594 BondAdSelect::serialize(self, serializer)
595 }
596}
597
598impl std::fmt::Display for BondAdSelect {
599 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
600 write!(
601 f,
602 "{}",
603 match self {
604 Self::Stable => "stable",
605 Self::Bandwidth => "bandwidth",
606 Self::Count => "count",
607 }
608 )
609 }
610}
611
612#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
613#[serde(rename_all = "kebab-case", remote = "BondLacpRate")]
614#[non_exhaustive]
615pub enum BondLacpRate {
618 #[serde(alias = "0")]
622 Slow,
623 #[serde(alias = "1")]
627 Fast,
628}
629
630impl<'de> Deserialize<'de> for BondLacpRate {
631 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
632 where
633 D: Deserializer<'de>,
634 {
635 BondLacpRate::deserialize(
636 NumberAsString::deserialize(deserializer)?
637 .as_str()
638 .into_deserializer(),
639 )
640 }
641}
642
643impl Serialize for BondLacpRate {
644 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
645 where
646 S: Serializer,
647 {
648 BondLacpRate::serialize(self, serializer)
649 }
650}
651
652impl std::fmt::Display for BondLacpRate {
653 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
654 write!(
655 f,
656 "{}",
657 match self {
658 Self::Slow => "slow",
659 Self::Fast => "fast",
660 }
661 )
662 }
663}
664
665#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
666#[serde(rename_all = "kebab-case", remote = "BondAllPortsActive")]
667#[non_exhaustive]
668pub enum BondAllPortsActive {
672 #[serde(alias = "0")]
676 Dropped,
677 #[serde(alias = "1")]
681 Delivered,
682}
683
684impl<'de> Deserialize<'de> for BondAllPortsActive {
685 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
686 where
687 D: Deserializer<'de>,
688 {
689 BondAllPortsActive::deserialize(
690 NumberAsString::deserialize(deserializer)?
691 .as_str()
692 .into_deserializer(),
693 )
694 }
695}
696
697impl Serialize for BondAllPortsActive {
698 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
699 where
700 S: Serializer,
701 {
702 BondAllPortsActive::serialize(self, serializer)
703 }
704}
705
706impl std::fmt::Display for BondAllPortsActive {
707 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
708 write!(
709 f,
710 "{}",
711 match self {
712 Self::Dropped => "dropped",
713 Self::Delivered => "delivered",
714 }
715 )
716 }
717}
718
719impl From<BondAllPortsActive> for u8 {
720 fn from(v: BondAllPortsActive) -> u8 {
721 match v {
722 BondAllPortsActive::Dropped => 0,
723 BondAllPortsActive::Delivered => 1,
724 }
725 }
726}
727
728#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
734#[serde(rename_all = "kebab-case", remote = "BondArpAllTargets")]
735#[non_exhaustive]
736pub enum BondArpAllTargets {
737 #[serde(alias = "0")]
739 Any,
740 #[serde(alias = "1")]
743 All,
744}
745
746impl<'de> Deserialize<'de> for BondArpAllTargets {
747 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
748 where
749 D: Deserializer<'de>,
750 {
751 BondArpAllTargets::deserialize(
752 NumberAsString::deserialize(deserializer)?
753 .as_str()
754 .into_deserializer(),
755 )
756 }
757}
758
759impl Serialize for BondArpAllTargets {
760 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
761 where
762 S: Serializer,
763 {
764 BondArpAllTargets::serialize(self, serializer)
765 }
766}
767
768impl std::fmt::Display for BondArpAllTargets {
769 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
770 write!(
771 f,
772 "{}",
773 match self {
774 Self::Any => "any",
775 Self::All => "all",
776 }
777 )
778 }
779}
780
781#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
787#[serde(rename_all = "snake_case", remote = "BondArpValidate")]
788#[non_exhaustive]
789pub enum BondArpValidate {
790 #[serde(alias = "0")]
794 None,
795 #[serde(alias = "1")]
799 Active,
800 #[serde(alias = "2")]
804 Backup,
805 #[serde(alias = "3")]
809 All,
810 #[serde(alias = "4")]
814 Filter,
815 #[serde(alias = "5")]
820 FilterActive,
821 #[serde(alias = "6")]
826 FilterBackup,
827}
828
829impl<'de> Deserialize<'de> for BondArpValidate {
830 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
831 where
832 D: Deserializer<'de>,
833 {
834 BondArpValidate::deserialize(
835 NumberAsString::deserialize(deserializer)?
836 .as_str()
837 .into_deserializer(),
838 )
839 }
840}
841
842impl Serialize for BondArpValidate {
843 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
844 where
845 S: Serializer,
846 {
847 BondArpValidate::serialize(self, serializer)
848 }
849}
850
851impl std::fmt::Display for BondArpValidate {
852 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
853 write!(
854 f,
855 "{}",
856 match self {
857 Self::None => "none",
858 Self::Active => "active",
859 Self::Backup => "backup",
860 Self::All => "all",
861 Self::Filter => "filter",
862 Self::FilterActive => "filter_active",
863 Self::FilterBackup => "filter_backup",
864 }
865 )
866 }
867}
868
869#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
876#[serde(rename_all = "kebab-case", remote = "BondFailOverMac")]
877#[non_exhaustive]
878pub enum BondFailOverMac {
879 #[serde(alias = "0")]
885 None,
886 #[serde(alias = "1")]
893 Active,
894 #[serde(alias = "2")]
903 Follow,
904}
905
906impl<'de> Deserialize<'de> for BondFailOverMac {
907 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
908 where
909 D: Deserializer<'de>,
910 {
911 BondFailOverMac::deserialize(
912 NumberAsString::deserialize(deserializer)?
913 .as_str()
914 .into_deserializer(),
915 )
916 }
917}
918
919impl Serialize for BondFailOverMac {
920 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
921 where
922 S: Serializer,
923 {
924 BondFailOverMac::serialize(self, serializer)
925 }
926}
927
928impl std::fmt::Display for BondFailOverMac {
929 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
930 write!(
931 f,
932 "{}",
933 match self {
934 Self::None => "none",
935 Self::Active => "active",
936 Self::Follow => "follow",
937 }
938 )
939 }
940}
941
942#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
949#[serde(rename_all = "kebab-case", remote = "BondPrimaryReselect")]
950#[non_exhaustive]
951pub enum BondPrimaryReselect {
952 #[serde(alias = "0")]
956 Always,
957 #[serde(alias = "1")]
963 Better,
964 #[serde(alias = "2")]
969 Failure,
970}
971
972impl<'de> Deserialize<'de> for BondPrimaryReselect {
973 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
974 where
975 D: Deserializer<'de>,
976 {
977 BondPrimaryReselect::deserialize(
978 NumberAsString::deserialize(deserializer)?
979 .as_str()
980 .into_deserializer(),
981 )
982 }
983}
984
985impl Serialize for BondPrimaryReselect {
986 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
987 where
988 S: Serializer,
989 {
990 BondPrimaryReselect::serialize(self, serializer)
991 }
992}
993impl std::fmt::Display for BondPrimaryReselect {
994 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
995 write!(
996 f,
997 "{}",
998 match self {
999 Self::Always => "always",
1000 Self::Better => "better",
1001 Self::Failure => "failure",
1002 }
1003 )
1004 }
1005}
1006
1007#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone, Copy)]
1012#[non_exhaustive]
1013#[serde(remote = "BondXmitHashPolicy")]
1014pub enum BondXmitHashPolicy {
1015 #[serde(rename = "layer2", alias = "0")]
1016 Layer2,
1019 #[serde(rename = "layer3+4", alias = "1")]
1020 Layer34,
1023 #[serde(rename = "layer2+3", alias = "2")]
1024 Layer23,
1027 #[serde(rename = "encap2+3", alias = "3")]
1028 Encap23,
1031 #[serde(rename = "encap3+4", alias = "4")]
1032 Encap34,
1035 #[serde(rename = "vlan+srcmac", alias = "5")]
1036 VlanSrcMac,
1039}
1040
1041impl<'de> Deserialize<'de> for BondXmitHashPolicy {
1042 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1043 where
1044 D: Deserializer<'de>,
1045 {
1046 BondXmitHashPolicy::deserialize(
1047 NumberAsString::deserialize(deserializer)?
1048 .as_str()
1049 .into_deserializer(),
1050 )
1051 }
1052}
1053
1054impl Serialize for BondXmitHashPolicy {
1055 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1056 where
1057 S: Serializer,
1058 {
1059 BondXmitHashPolicy::serialize(self, serializer)
1060 }
1061}
1062
1063impl std::fmt::Display for BondXmitHashPolicy {
1064 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1065 write!(
1066 f,
1067 "{}",
1068 match self {
1069 Self::Layer2 => "layer2",
1070 Self::Layer34 => "layer3+4",
1071 Self::Layer23 => "layer2+3",
1072 Self::Encap23 => "encap2+3",
1073 Self::Encap34 => "encap3+4",
1074 Self::VlanSrcMac => "vlan+srcmac",
1075 }
1076 )
1077 }
1078}
1079
1080#[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq, Eq)]
1081#[non_exhaustive]
1082#[serde(deny_unknown_fields)]
1083pub struct BondOptions {
1084 #[serde(
1085 skip_serializing_if = "Option::is_none",
1086 default,
1087 deserialize_with = "crate::deserializer::option_u16_or_string"
1088 )]
1089 pub ad_actor_sys_prio: Option<u16>,
1092 #[serde(skip_serializing_if = "Option::is_none")]
1093 pub ad_actor_system: Option<String>,
1100 #[serde(skip_serializing_if = "Option::is_none")]
1101 pub ad_select: Option<BondAdSelect>,
1104 #[serde(
1105 skip_serializing_if = "Option::is_none",
1106 default,
1107 deserialize_with = "crate::deserializer::option_u16_or_string"
1108 )]
1109 pub ad_user_port_key: Option<u16>,
1124 #[serde(skip_serializing_if = "Option::is_none")]
1125 pub all_slaves_active: Option<BondAllPortsActive>,
1132 #[serde(skip_serializing_if = "Option::is_none")]
1133 pub arp_all_targets: Option<BondArpAllTargets>,
1138 #[serde(
1139 skip_serializing_if = "Option::is_none",
1140 default,
1141 deserialize_with = "crate::deserializer::option_u32_or_string"
1142 )]
1143 pub arp_interval: Option<u32>,
1167 #[serde(skip_serializing_if = "Option::is_none")]
1168 pub arp_ip_target: Option<String>,
1176 #[serde(skip_serializing_if = "Option::is_none")]
1177 pub arp_validate: Option<BondArpValidate>,
1181 #[serde(
1182 skip_serializing_if = "Option::is_none",
1183 default,
1184 deserialize_with = "crate::deserializer::option_u32_or_string"
1185 )]
1186 pub downdelay: Option<u32>,
1192 #[serde(skip_serializing_if = "Option::is_none")]
1193 pub fail_over_mac: Option<BondFailOverMac>,
1198 #[serde(skip_serializing_if = "Option::is_none")]
1199 pub lacp_rate: Option<BondLacpRate>,
1202 #[serde(
1203 skip_serializing_if = "Option::is_none",
1204 default,
1205 deserialize_with = "crate::deserializer::option_u32_or_string"
1206 )]
1207 pub lp_interval: Option<u32>,
1213 #[serde(
1214 skip_serializing_if = "Option::is_none",
1215 default,
1216 deserialize_with = "crate::deserializer::option_u32_or_string"
1217 )]
1218 pub miimon: Option<u32>,
1226 #[serde(
1227 skip_serializing_if = "Option::is_none",
1228 default,
1229 deserialize_with = "crate::deserializer::option_u32_or_string"
1230 )]
1231 pub min_links: Option<u32>,
1246 #[serde(
1247 skip_serializing_if = "Option::is_none",
1248 default,
1249 deserialize_with = "crate::deserializer::option_u8_or_string"
1250 )]
1251 pub num_grat_arp: Option<u8>,
1267 #[serde(
1268 skip_serializing_if = "Option::is_none",
1269 default,
1270 deserialize_with = "crate::deserializer::option_u8_or_string"
1271 )]
1272 pub num_unsol_na: Option<u8>,
1274 #[serde(
1275 skip_serializing_if = "Option::is_none",
1276 default,
1277 deserialize_with = "crate::deserializer::option_u32_or_string"
1278 )]
1279 pub packets_per_slave: Option<u32>,
1285 #[serde(skip_serializing_if = "Option::is_none")]
1286 pub primary: Option<String>,
1295 #[serde(skip_serializing_if = "Option::is_none")]
1296 pub primary_reselect: Option<BondPrimaryReselect>,
1302 #[serde(
1303 skip_serializing_if = "Option::is_none",
1304 default,
1305 deserialize_with = "crate::deserializer::option_u32_or_string"
1306 )]
1307 pub resend_igmp: Option<u32>,
1321 #[serde(
1322 skip_serializing_if = "Option::is_none",
1323 default,
1324 deserialize_with = "crate::deserializer::option_bool_or_string"
1325 )]
1326 pub tlb_dynamic_lb: Option<bool>,
1343 #[serde(
1344 skip_serializing_if = "Option::is_none",
1345 default,
1346 deserialize_with = "crate::deserializer::option_u32_or_string"
1347 )]
1348 pub updelay: Option<u32>,
1354 #[serde(
1355 skip_serializing_if = "Option::is_none",
1356 default,
1357 deserialize_with = "crate::deserializer::option_bool_or_string"
1358 )]
1359 pub use_carrier: Option<bool>,
1379 #[serde(skip_serializing_if = "Option::is_none")]
1380 pub xmit_hash_policy: Option<BondXmitHashPolicy>,
1383 #[serde(
1384 skip_serializing_if = "Option::is_none",
1385 default,
1386 deserialize_with = "crate::deserializer::option_bool_or_string",
1387 alias = "balance-slb"
1388 )]
1389 pub balance_slb: Option<bool>,
1390 #[serde(
1391 skip_serializing_if = "Option::is_none",
1392 default,
1393 deserialize_with = "crate::deserializer::option_u8_or_string"
1394 )]
1395 pub arp_missed_max: Option<u8>,
1396 #[serde(
1399 skip_serializing_if = "Option::is_none",
1400 default,
1401 deserialize_with = "crate::deserializer::option_bool_or_string"
1402 )]
1403 pub lacp_active: Option<bool>,
1404 #[serde(skip_serializing_if = "Option::is_none")]
1407 pub ns_ip6_target: Option<Vec<Ipv6Addr>>,
1408}
1409
1410impl BondOptions {
1411 pub fn new() -> Self {
1412 Self::default()
1413 }
1414
1415 fn validate_ad_actor_system_mac_address(&self) -> Result<(), NmstateError> {
1416 if let Some(ad_actor_system) = &self.ad_actor_system {
1417 if ad_actor_system.to_uppercase().starts_with("01:00:5E") {
1418 let e = NmstateError::new(
1419 ErrorKind::InvalidArgument,
1420 "The ad_actor_system bond option cannot be an IANA \
1421 multicast address(prefix with 01:00:5E)"
1422 .to_string(),
1423 );
1424 log::error!("{e}");
1425 return Err(e);
1426 }
1427 }
1428 Ok(())
1429 }
1430
1431 fn validate_miimon_and_arp_interval(&self) -> Result<(), NmstateError> {
1432 if let (Some(miimon), Some(arp_interval)) =
1433 (self.miimon, self.arp_interval)
1434 {
1435 if miimon > 0 && arp_interval > 0 {
1436 let e = NmstateError::new(
1437 ErrorKind::InvalidArgument,
1438 "Bond miimon and arp interval are not compatible options."
1439 .to_string(),
1440 );
1441 log::error!("{e}");
1442 return Err(e);
1443 }
1444 }
1445 Ok(())
1446 }
1447
1448 fn validate_balance_slb(
1449 &self,
1450 current: Option<&Self>,
1451 mode: BondMode,
1452 ) -> Result<(), NmstateError> {
1453 if self
1454 .balance_slb
1455 .or_else(|| current.and_then(|c| c.balance_slb))
1456 == Some(true)
1457 {
1458 let xmit_hash_policy = self
1459 .xmit_hash_policy
1460 .or_else(|| current.and_then(|c| c.xmit_hash_policy));
1461 if mode != BondMode::XOR
1462 || xmit_hash_policy != Some(BondXmitHashPolicy::VlanSrcMac)
1463 {
1464 return Err(NmstateError::new(
1465 ErrorKind::InvalidArgument,
1466 "To enable balance-slb, bond mode should be balance-xor \
1467 and xmit_hash_policy: 'vlan+srcmac'"
1468 .to_string(),
1469 ));
1470 }
1471 }
1472 Ok(())
1473 }
1474
1475 fn validate_lacp_opts(&self, mode: BondMode) -> Result<(), NmstateError> {
1476 if mode != BondMode::LACP {
1477 if self.lacp_rate.is_some() {
1478 return Err(NmstateError::new(
1479 ErrorKind::InvalidArgument,
1480 "The `lacp_rate` option is only valid for bond in \
1481 '802.3ad' mode"
1482 .to_string(),
1483 ));
1484 }
1485 if self.lacp_active.is_some() {
1486 return Err(NmstateError::new(
1487 ErrorKind::InvalidArgument,
1488 "The `lacp_active` option is only valid for bond in \
1489 '802.3ad' mode"
1490 .to_string(),
1491 ));
1492 }
1493 }
1494 Ok(())
1495 }
1496
1497 fn validate_arp_interval(
1502 &self,
1503 current: Option<&Self>,
1504 mode: BondMode,
1505 ) -> Result<(), NmstateError> {
1506 if let Some(arp_interval) = self
1507 .arp_interval
1508 .or_else(|| current.and_then(|c| c.arp_interval))
1509 {
1510 if arp_interval > 0 {
1511 if mode == BondMode::LACP
1512 || mode == BondMode::TLB
1513 || mode == BondMode::ALB
1514 {
1515 return Err(NmstateError::new(
1516 ErrorKind::InvalidArgument,
1517 "The `arp_interval` option is invalid for bond in \
1518 '802.3ad', 'balance-tlb' or 'balance-alb' mode"
1519 .to_string(),
1520 ));
1521 }
1522 } else {
1523 if let Some(arp_ip_target) = self.arp_ip_target.as_ref() {
1524 if !arp_ip_target.is_empty() {
1525 return Err(NmstateError::new(
1526 ErrorKind::InvalidArgument,
1527 "The `arp_ip_target` option is only valid when \
1528 'arp_interval' is enabled(>0)."
1529 .to_string(),
1530 ));
1531 }
1532 }
1533 if let Some(ns_ip6_target) = self.ns_ip6_target.as_ref() {
1534 if !ns_ip6_target.is_empty() {
1535 return Err(NmstateError::new(
1536 ErrorKind::InvalidArgument,
1537 "The `ns_ip6_target` option is only valid when \
1538 'arp_interval' is enabled(>0)."
1539 .to_string(),
1540 ));
1541 }
1542 }
1543 }
1544 }
1545 Ok(())
1546 }
1547}
1548
1549impl MergedInterface {
1550 pub(crate) fn post_inter_ifaces_process_bond(
1551 &mut self,
1552 ) -> Result<(), NmstateError> {
1553 if self.merged.is_absent() {
1554 return Ok(());
1555 }
1556 if let Some(Interface::Bond(apply_iface)) = self.for_apply.as_mut() {
1557 apply_iface
1558 .validate_new_iface_with_no_mode(self.current.as_ref())?;
1559 apply_iface.sanitize_mac_restricted_mode(self.current.as_ref());
1560 apply_iface.validate_conflict_in_port_and_port_configs()?;
1561
1562 if let Some(bond_opts) =
1563 apply_iface.bond.as_ref().and_then(|b| b.options.as_ref())
1564 {
1565 bond_opts.validate_ad_actor_system_mac_address()?;
1566 bond_opts.validate_miimon_and_arp_interval()?;
1567
1568 if let Interface::Bond(merged_iface) = &self.merged {
1569 if let Some(mode) =
1570 merged_iface.bond.as_ref().and_then(|b| b.mode)
1571 {
1572 let cur_bond_opts =
1573 if let Some(Interface::Bond(cur_iface)) =
1574 self.current.as_ref()
1575 {
1576 cur_iface
1577 .bond
1578 .as_ref()
1579 .and_then(|b| b.options.as_ref())
1580 } else {
1581 None
1582 };
1583 bond_opts.validate_balance_slb(cur_bond_opts, mode)?;
1584 bond_opts.validate_lacp_opts(mode)?;
1585 bond_opts.validate_arp_interval(cur_bond_opts, mode)?;
1586 }
1587 }
1588 }
1589 }
1590 Ok(())
1591 }
1592}
1593
1594#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
1595#[serde(rename_all = "kebab-case")]
1596#[non_exhaustive]
1597pub struct BondPortConfig {
1598 pub name: String,
1600 #[serde(
1601 skip_serializing_if = "Option::is_none",
1602 default,
1603 deserialize_with = "crate::deserializer::option_i32_or_string"
1604 )]
1605 pub priority: Option<i32>,
1610 #[serde(
1611 skip_serializing_if = "Option::is_none",
1612 default,
1613 deserialize_with = "crate::deserializer::option_u16_or_string"
1614 )]
1615 pub queue_id: Option<u16>,
1617}
1618
1619impl std::fmt::Display for BondPortConfig {
1620 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1621 write!(
1622 f,
1623 "BondPortConfig {{ name: {}, priority: {}, queue_id: {} }}",
1624 self.name,
1625 self.priority.unwrap_or_default(),
1626 self.queue_id.unwrap_or_default()
1627 )
1628 }
1629}
1630
1631impl BondPortConfig {
1632 pub fn new() -> Self {
1633 Self::default()
1634 }
1635
1636 fn is_changed(&self, current: &Self) -> bool {
1637 (self.priority.is_some() && self.priority != current.priority)
1638 || (self.queue_id.is_some() && self.queue_id != current.queue_id)
1639 }
1640}