1use serde::{Deserialize, Serialize};
4
5use crate::{
6 BaseInterface, ErrorKind, Interface, InterfaceType, MergedInterfaces,
7 NmstateError,
8};
9
10#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
11#[serde(rename_all = "kebab-case")]
12#[non_exhaustive]
13pub struct HsrInterface {
35 #[serde(flatten)]
36 pub base: BaseInterface,
37 #[serde(skip_serializing_if = "Option::is_none")]
38 pub hsr: Option<HsrConfig>,
40}
41
42impl Default for HsrInterface {
43 fn default() -> Self {
44 Self {
45 base: BaseInterface {
46 iface_type: InterfaceType::Hsr,
47 ..Default::default()
48 },
49 hsr: None,
50 }
51 }
52}
53
54impl HsrInterface {
55 pub fn new() -> Self {
56 Self::default()
57 }
58
59 pub(crate) fn sanitize(
60 &mut self,
61 is_desired: bool,
62 ) -> Result<(), NmstateError> {
63 if is_desired {
64 if let Some(conf) = &mut self.hsr {
65 if let Some(address) = &mut conf.supervision_address {
66 address.as_mut().make_ascii_uppercase();
67 log::warn!(
68 "The supervision-address is read-only, ignoring it on \
69 desired state."
70 );
71 }
72 }
73 }
74 Ok(())
75 }
76}
77
78impl MergedInterfaces {
79 pub(crate) fn get_hsr_mac(&self, iface: &HsrInterface) -> Option<String> {
80 if iface.base.mac_address.is_some() {
81 return iface.base.mac_address.clone();
82 }
83
84 let hsr_conf = iface.hsr.as_ref()?;
85
86 for use_permanent in [false, true] {
87 for port in [&hsr_conf.port1, &hsr_conf.port2] {
88 let mac = self
89 .get_iface(port, InterfaceType::Unknown)
90 .and_then(|iface| {
91 let base = iface.merged.base_iface();
92 if use_permanent {
93 base.permanent_mac_address.clone()
94 } else {
95 base.mac_address.clone()
96 }
97 });
98 if mac.is_some() {
99 return mac;
100 }
101 }
102 }
103
104 None
105 }
106
107 pub(crate) fn validate_hsr_mac(&self) -> Result<(), NmstateError> {
108 for hsr_iface in self.kernel_ifaces.iter().filter_map(|(_, iface)| {
109 if let Interface::Hsr(hsr_iface) = iface.desired.as_ref()? {
110 let hsr_conf = hsr_iface.hsr.as_ref()?;
111
112 if hsr_conf.protocol == HsrProtocol::Prp {
113 return Some(hsr_iface);
114 }
115 }
116
117 None
118 }) {
119 let macs = [
120 hsr_iface.base.mac_address.as_deref(),
121 hsr_iface
122 .hsr
123 .as_ref()
124 .and_then(|hsr_conf| {
125 self.get_iface(&hsr_conf.port1, InterfaceType::Unknown)
126 })
127 .and_then(|iface| iface.desired.as_ref())
128 .and_then(|iface| {
129 iface.base_iface().mac_address.as_deref()
130 }),
131 hsr_iface
132 .hsr
133 .as_ref()
134 .and_then(|hsr_conf| {
135 self.get_iface(&hsr_conf.port2, InterfaceType::Unknown)
136 })
137 .and_then(|iface| iface.desired.as_ref())
138 .and_then(|iface| {
139 iface.base_iface().mac_address.as_deref()
140 }),
141 ];
142
143 let mut acc = None;
144
145 for &mac in macs.iter().flatten() {
146 if acc.is_some() && Some(mac) != acc {
147 return Err(NmstateError::new(
148 ErrorKind::InvalidArgument,
149 format!(
150 "HSR ports on interface {} cannot have different \
151 MAC addresses",
152 hsr_iface.base.name
153 ),
154 ));
155 }
156
157 acc = Some(mac);
158 }
159 }
160
161 Ok(())
162 }
163
164 pub(crate) fn copy_hsr_mac(&mut self) -> Result<(), NmstateError> {
165 let mut pending_changes = Vec::new();
166
167 for (hsr_iface, hsr_conf, mac) in
168 self.kernel_ifaces.iter().filter_map(|(_, iface)| {
169 if !iface.is_desired() {
170 return None;
171 }
172
173 if let Interface::Hsr(hsr_iface) = &iface.merged {
174 let mac = self.get_hsr_mac(hsr_iface)?;
175 let hsr_conf = hsr_iface.hsr.as_ref()?;
176
177 if hsr_conf.protocol != HsrProtocol::Prp {
178 return None;
179 }
180
181 if iface.current.is_some() {
182 if Some(mac.as_str())
183 != self.kernel_ifaces.get(&hsr_conf.port2).and_then(
184 |iface| {
185 iface
186 .merged
187 .base_iface()
188 .mac_address
189 .as_deref()
190 },
191 )
192 {
193 log::warn!(
194 "Existing HSR PRP interface {} has \
195 mismatching MAC addresses on port1 and \
196 port2, which may break functionality",
197 iface.merged.name()
198 );
199 }
200
201 return None;
202 }
203
204 return Some((hsr_iface, hsr_conf, mac));
205 }
206
207 None
208 })
209 {
210 for ifname in
211 [&hsr_iface.base.name, &hsr_conf.port1, &hsr_conf.port2]
212 {
213 pending_changes.push((ifname.clone(), mac.clone()));
214 }
215 }
216
217 for (ifname, mac) in pending_changes {
218 if let Some(iface) = self.kernel_ifaces.get_mut(&ifname) {
219 iface.mark_as_changed();
220 iface.set_copy_from_mac(mac.clone());
221 }
222 }
223
224 Ok(())
225 }
226}
227
228#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
229#[serde(rename_all = "kebab-case")]
230#[non_exhaustive]
231#[derive(Default)]
232pub struct HsrConfig {
233 pub port1: String,
235 pub port2: String,
237 #[serde(skip_serializing_if = "Option::is_none")]
238 pub supervision_address: Option<String>,
241 pub multicast_spec: u8,
243 pub protocol: HsrProtocol,
245}
246
247impl HsrConfig {
248 pub fn new() -> Self {
249 Self::default()
250 }
251}
252
253#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
254#[serde(rename_all = "kebab-case")]
255#[non_exhaustive]
256#[derive(Default)]
257pub enum HsrProtocol {
258 #[default]
259 Hsr,
260 Prp,
261}