]>
Commit | Line | Data |
---|---|---|
904e9886 | 1 | use std::io::{BufRead}; |
f34d4401 | 2 | use std::iter::{Peekable, Iterator}; |
1ca540a6 | 3 | use std::collections::{HashMap, HashSet}; |
f34d4401 DM |
4 | |
5 | use anyhow::{Error, bail, format_err}; | |
6 | use lazy_static::lazy_static; | |
7 | use regex::Regex; | |
8 | ||
f34d4401 DM |
9 | use super::helper::*; |
10 | use super::lexer::*; | |
11 | ||
8f2f3dd7 | 12 | use super::{NetworkConfig, NetworkOrderEntry, Interface, NetworkConfigMethod, NetworkInterfaceType, bond_mode_from_str, bond_xmit_hash_policy_from_str}; |
f34d4401 | 13 | |
6f422880 DM |
14 | fn set_method_v4(iface: &mut Interface, method: NetworkConfigMethod) -> Result<(), Error> { |
15 | if iface.method.is_none() { | |
16 | iface.method = Some(method); | |
17 | } else { | |
18 | bail!("inet configuration method already set."); | |
19 | } | |
20 | Ok(()) | |
21 | } | |
22 | ||
23 | fn set_method_v6(iface: &mut Interface, method: NetworkConfigMethod) -> Result<(), Error> { | |
24 | if iface.method6.is_none() { | |
25 | iface.method6 = Some(method); | |
26 | } else { | |
27 | bail!("inet6 configuration method already set."); | |
28 | } | |
29 | Ok(()) | |
30 | } | |
31 | ||
32 | fn set_cidr_v4(iface: &mut Interface, address: String) -> Result<(), Error> { | |
33 | if iface.cidr.is_none() { | |
34 | iface.cidr = Some(address); | |
35 | } else { | |
36 | bail!("duplicate IPv4 address."); | |
37 | } | |
38 | Ok(()) | |
39 | } | |
40 | ||
41 | fn set_gateway_v4(iface: &mut Interface, gateway: String) -> Result<(), Error> { | |
42 | if iface.gateway.is_none() { | |
43 | iface.gateway = Some(gateway); | |
44 | } else { | |
45 | bail!("duplicate IPv4 gateway."); | |
46 | } | |
47 | Ok(()) | |
48 | } | |
49 | ||
50 | fn set_cidr_v6(iface: &mut Interface, address: String) -> Result<(), Error> { | |
51 | if iface.cidr6.is_none() { | |
52 | iface.cidr6 = Some(address); | |
53 | } else { | |
54 | bail!("duplicate IPv6 address."); | |
55 | } | |
56 | Ok(()) | |
57 | } | |
58 | ||
59 | fn set_gateway_v6(iface: &mut Interface, gateway: String) -> Result<(), Error> { | |
60 | if iface.gateway6.is_none() { | |
61 | iface.gateway6 = Some(gateway); | |
62 | } else { | |
63 | bail!("duplicate IPv4 gateway."); | |
64 | } | |
65 | Ok(()) | |
66 | } | |
67 | ||
68 | fn set_interface_type(iface: &mut Interface, interface_type: NetworkInterfaceType) -> Result<(), Error> { | |
69 | if iface.interface_type == NetworkInterfaceType::Unknown { | |
70 | iface.interface_type = interface_type; | |
71 | } else if iface.interface_type != interface_type { | |
72 | bail!("interface type already defined - cannot change from {:?} to {:?}", iface.interface_type, interface_type); | |
73 | } | |
74 | Ok(()) | |
75 | } | |
76 | ||
904e9886 DM |
77 | pub struct NetworkParser<R: BufRead> { |
78 | input: Peekable<Lexer<R>>, | |
f34d4401 DM |
79 | line_nr: usize, |
80 | } | |
81 | ||
904e9886 | 82 | impl <R: BufRead> NetworkParser<R> { |
f34d4401 | 83 | |
904e9886 | 84 | pub fn new(reader: R) -> Self { |
f34d4401 DM |
85 | let input = Lexer::new(reader).peekable(); |
86 | Self { input, line_nr: 1 } | |
87 | } | |
88 | ||
89 | fn peek(&mut self) -> Result<Token, Error> { | |
90 | match self.input.peek() { | |
91 | Some(Err(err)) => { | |
92 | bail!("input error - {}", err); | |
93 | } | |
94 | Some(Ok((token, _))) => { | |
38556bf6 | 95 | Ok(*token) |
f34d4401 DM |
96 | } |
97 | None => { | |
98 | bail!("got unexpected end of stream (inside peek)"); | |
99 | } | |
100 | } | |
101 | } | |
102 | ||
103 | fn next(&mut self) -> Result<(Token, String), Error> { | |
104 | match self.input.next() { | |
105 | Some(Err(err)) => { | |
106 | bail!("input error - {}", err); | |
107 | } | |
108 | Some(Ok((token, text))) => { | |
109 | if token == Token::Newline { self.line_nr += 1; } | |
38556bf6 | 110 | Ok((token, text)) |
f34d4401 DM |
111 | } |
112 | None => { | |
113 | bail!("got unexpected end of stream (inside peek)"); | |
114 | } | |
115 | } | |
116 | } | |
117 | ||
118 | fn next_text(&mut self) -> Result<String, Error> { | |
119 | match self.next()? { | |
120 | (Token::Text, text) => Ok(text), | |
121 | (unexpected, _) => bail!("got unexpected token {:?} (expecting Text)", unexpected), | |
122 | } | |
123 | } | |
124 | ||
5f60a58f DM |
125 | fn eat(&mut self, expected: Token) -> Result<String, Error> { |
126 | let (next, text) = self.next()?; | |
f34d4401 DM |
127 | if next != expected { |
128 | bail!("expected {:?}, got {:?}", expected, next); | |
129 | } | |
5f60a58f | 130 | Ok(text) |
f34d4401 DM |
131 | } |
132 | ||
a9bb491e | 133 | fn parse_auto(&mut self, auto_flag: &mut HashSet<String>) -> Result<(), Error> { |
f34d4401 DM |
134 | self.eat(Token::Auto)?; |
135 | ||
a9bb491e DM |
136 | loop { |
137 | match self.next()? { | |
138 | (Token::Text, iface) => { | |
e2d940b9 | 139 | auto_flag.insert(iface.to_string()); |
a9bb491e DM |
140 | } |
141 | (Token::Newline, _) => break, | |
142 | unexpected => { | |
143 | bail!("expected {:?}, got {:?}", Token::Text, unexpected); | |
144 | } | |
145 | } | |
f34d4401 | 146 | |
a9bb491e | 147 | } |
f34d4401 DM |
148 | |
149 | Ok(()) | |
150 | } | |
151 | ||
645a47ff DM |
152 | fn parse_netmask(&mut self) -> Result<u8, Error> { |
153 | self.eat(Token::Netmask)?; | |
154 | let netmask = self.next_text()?; | |
155 | ||
156 | let mask = if let Some(mask) = IPV4_MASK_HASH_LOCALNET.get(netmask.as_str()) { | |
157 | *mask | |
158 | } else { | |
159 | match u8::from_str_radix(netmask.as_str(), 10) { | |
160 | Ok(mask) => mask, | |
161 | Err(err) => { | |
056ee785 | 162 | bail!("unable to parse netmask '{}' - {}", netmask, err); |
645a47ff DM |
163 | } |
164 | } | |
165 | }; | |
166 | ||
167 | self.eat(Token::Newline)?; | |
168 | ||
169 | Ok(mask) | |
170 | } | |
171 | ||
172 | fn parse_iface_address(&mut self) -> Result<(String, Option<u8>, bool), Error> { | |
f34d4401 | 173 | self.eat(Token::Address)?; |
8b57cd44 | 174 | let cidr = self.next_text()?; |
f34d4401 | 175 | |
645a47ff | 176 | let (_address, mask, ipv6) = parse_address_or_cidr(&cidr)?; |
f34d4401 DM |
177 | |
178 | self.eat(Token::Newline)?; | |
179 | ||
645a47ff | 180 | Ok((cidr, mask, ipv6)) |
f34d4401 DM |
181 | } |
182 | ||
183 | fn parse_iface_gateway(&mut self, interface: &mut Interface) -> Result<(), Error> { | |
184 | self.eat(Token::Gateway)?; | |
185 | let gateway = self.next_text()?; | |
186 | ||
25877d05 | 187 | if pbs_api_types::common_regex::IP_REGEX.is_match(&gateway) { |
f34d4401 | 188 | if gateway.contains(':') { |
6f422880 | 189 | set_gateway_v6(interface, gateway)?; |
f34d4401 | 190 | } else { |
6f422880 | 191 | set_gateway_v4(interface, gateway)?; |
f34d4401 DM |
192 | } |
193 | } else { | |
194 | bail!("unable to parse gateway address"); | |
195 | } | |
196 | ||
197 | self.eat(Token::Newline)?; | |
198 | ||
199 | Ok(()) | |
200 | } | |
201 | ||
3fce3bc3 DM |
202 | fn parse_iface_mtu(&mut self) -> Result<u64, Error> { |
203 | self.eat(Token::MTU)?; | |
204 | ||
205 | let mtu = self.next_text()?; | |
206 | let mtu = match u64::from_str_radix(&mtu, 10) { | |
207 | Ok(mtu) => mtu, | |
208 | Err(err) => { | |
209 | bail!("unable to parse mtu value '{}' - {}", mtu, err); | |
210 | } | |
211 | }; | |
212 | ||
213 | self.eat(Token::Newline)?; | |
214 | ||
215 | Ok(mtu) | |
216 | } | |
217 | ||
7b22acd0 DM |
218 | fn parse_yes_no(&mut self) -> Result<bool, Error> { |
219 | let text = self.next_text()?; | |
220 | let value = match text.to_lowercase().as_str() { | |
221 | "yes" => true, | |
222 | "no" => false, | |
223 | _ => { | |
224 | bail!("unable to bool value '{}' - (expected yes/no)", text); | |
225 | } | |
226 | }; | |
227 | ||
228 | self.eat(Token::Newline)?; | |
229 | ||
230 | Ok(value) | |
231 | } | |
232 | ||
f34d4401 DM |
233 | fn parse_to_eol(&mut self) -> Result<String, Error> { |
234 | let mut line = String::new(); | |
235 | loop { | |
236 | match self.next()? { | |
237 | (Token::Newline, _) => return Ok(line), | |
238 | (_, text) => { | |
239 | if !line.is_empty() { line.push(' '); } | |
240 | line.push_str(&text); | |
241 | } | |
242 | } | |
243 | } | |
244 | } | |
245 | ||
1d9a68c2 DM |
246 | fn parse_iface_list(&mut self) -> Result<Vec<String>, Error> { |
247 | let mut list = Vec::new(); | |
248 | ||
249 | loop { | |
250 | let (token, text) = self.next()?; | |
251 | match token { | |
252 | Token::Newline => break, | |
253 | Token::Text => { | |
254 | if &text != "none" { | |
255 | list.push(text); | |
256 | } | |
257 | } | |
258 | _ => bail!("unable to parse interface list - unexpected token '{:?}'", token), | |
259 | } | |
260 | } | |
261 | ||
262 | Ok(list) | |
263 | } | |
c38b4bb8 | 264 | |
5f60a58f DM |
265 | fn parse_iface_attributes( |
266 | &mut self, | |
267 | interface: &mut Interface, | |
268 | address_family_v4: bool, | |
269 | address_family_v6: bool, | |
270 | ) -> Result<(), Error> { | |
f34d4401 | 271 | |
645a47ff DM |
272 | let mut netmask = None; |
273 | let mut address_list = Vec::new(); | |
274 | ||
f34d4401 DM |
275 | loop { |
276 | match self.peek()? { | |
5f60a58f DM |
277 | Token::Attribute => { self.eat(Token::Attribute)?; }, |
278 | Token::Comment => { | |
279 | let comment = self.eat(Token::Comment)?; | |
280 | if !address_family_v4 && address_family_v6 { | |
17c7b46a | 281 | let mut comments = interface.comments6.take().unwrap_or_default(); |
8a6b86b8 DM |
282 | if !comments.is_empty() { comments.push('\n'); } |
283 | comments.push_str(&comment); | |
7b22acd0 | 284 | interface.comments6 = Some(comments); |
5f60a58f | 285 | } else { |
17c7b46a | 286 | let mut comments = interface.comments.take().unwrap_or_default(); |
8a6b86b8 DM |
287 | if !comments.is_empty() { comments.push('\n'); } |
288 | comments.push_str(&comment); | |
7b22acd0 | 289 | interface.comments = Some(comments); |
5f60a58f DM |
290 | } |
291 | self.eat(Token::Newline)?; | |
292 | continue; | |
293 | } | |
87c4cb74 | 294 | _ => break, |
f34d4401 DM |
295 | } |
296 | ||
297 | match self.peek()? { | |
645a47ff DM |
298 | Token::Address => { |
299 | let (cidr, mask, is_v6) = self.parse_iface_address()?; | |
300 | address_list.push((cidr, mask, is_v6)); | |
301 | } | |
f34d4401 | 302 | Token::Gateway => self.parse_iface_gateway(interface)?, |
645a47ff DM |
303 | Token::Netmask => { |
304 | //Note: netmask is deprecated, but we try to do our best | |
305 | netmask = Some(self.parse_netmask()?); | |
306 | } | |
3fce3bc3 DM |
307 | Token::MTU => { |
308 | let mtu = self.parse_iface_mtu()?; | |
2c18efd9 | 309 | interface.mtu = Some(mtu); |
3fce3bc3 | 310 | } |
7b22acd0 DM |
311 | Token::BridgeVlanAware => { |
312 | self.eat(Token::BridgeVlanAware)?; | |
313 | let bridge_vlan_aware = self.parse_yes_no()?; | |
314 | interface.bridge_vlan_aware = Some(bridge_vlan_aware); | |
315 | } | |
1d9a68c2 DM |
316 | Token::BridgePorts => { |
317 | self.eat(Token::BridgePorts)?; | |
318 | let ports = self.parse_iface_list()?; | |
319 | interface.bridge_ports = Some(ports); | |
6f422880 | 320 | set_interface_type(interface, NetworkInterfaceType::Bridge)?; |
1d9a68c2 | 321 | } |
42fbe91a DM |
322 | Token::BondSlaves => { |
323 | self.eat(Token::BondSlaves)?; | |
324 | let slaves = self.parse_iface_list()?; | |
bab5d18c | 325 | interface.slaves = Some(slaves); |
6f422880 | 326 | set_interface_type(interface, NetworkInterfaceType::Bond)?; |
42fbe91a | 327 | } |
bab5d18c DM |
328 | Token::BondMode => { |
329 | self.eat(Token::BondMode)?; | |
330 | let mode = self.next_text()?; | |
331 | interface.bond_mode = Some(bond_mode_from_str(&mode)?); | |
332 | self.eat(Token::Newline)?; | |
333 | } | |
85959a99 DC |
334 | Token::BondPrimary => { |
335 | self.eat(Token::BondPrimary)?; | |
336 | let primary = self.next_text()?; | |
337 | interface.bond_primary = Some(primary); | |
338 | self.eat(Token::Newline)?; | |
339 | } | |
8f2f3dd7 DC |
340 | Token::BondXmitHashPolicy => { |
341 | self.eat(Token::BondXmitHashPolicy)?; | |
342 | let policy = bond_xmit_hash_policy_from_str(&self.next_text()?)?; | |
343 | interface.bond_xmit_hash_policy = Some(policy); | |
344 | self.eat(Token::Newline)?; | |
345 | } | |
5f60a58f DM |
346 | _ => { // parse addon attributes |
347 | let option = self.parse_to_eol()?; | |
348 | if !option.is_empty() { | |
349 | if !address_family_v4 && address_family_v6 { | |
7b22acd0 | 350 | interface.options6.push(option); |
5f60a58f | 351 | } else { |
7b22acd0 | 352 | interface.options.push(option); |
5f60a58f DM |
353 | } |
354 | }; | |
355 | }, | |
f34d4401 DM |
356 | } |
357 | } | |
358 | ||
43313c2e | 359 | #[allow(clippy::comparison_chain)] |
645a47ff DM |
360 | if let Some(netmask) = netmask { |
361 | if address_list.len() > 1 { | |
362 | bail!("unable to apply netmask to multiple addresses (please use cidr notation)"); | |
363 | } else if address_list.len() == 1 { | |
364 | let (mut cidr, mask, is_v6) = address_list.pop().unwrap(); | |
365 | if mask.is_some() { | |
366 | // address already has a mask - ignore netmask | |
367 | } else { | |
368 | check_netmask(netmask, is_v6)?; | |
369 | cidr.push_str(&format!("/{}", netmask)); | |
370 | } | |
371 | if is_v6 { | |
6f422880 | 372 | set_cidr_v6(interface, cidr)?; |
645a47ff | 373 | } else { |
6f422880 | 374 | set_cidr_v4(interface, cidr)?; |
645a47ff DM |
375 | } |
376 | } else { | |
377 | // no address - simply ignore useless netmask | |
378 | } | |
379 | } else { | |
380 | for (cidr, mask, is_v6) in address_list { | |
381 | if mask.is_none() { | |
382 | bail!("missing netmask in '{}'", cidr); | |
383 | } | |
384 | if is_v6 { | |
6f422880 | 385 | set_cidr_v6(interface, cidr)?; |
645a47ff | 386 | } else { |
6f422880 | 387 | set_cidr_v4(interface, cidr)?; |
645a47ff DM |
388 | } |
389 | } | |
390 | } | |
391 | ||
f34d4401 DM |
392 | Ok(()) |
393 | } | |
394 | ||
395 | fn parse_iface(&mut self, config: &mut NetworkConfig) -> Result<(), Error> { | |
396 | self.eat(Token::Iface)?; | |
397 | let iface = self.next_text()?; | |
398 | ||
92310d58 DM |
399 | let mut address_family_v4 = false; |
400 | let mut address_family_v6 = false; | |
f34d4401 DM |
401 | let mut config_method = None; |
402 | ||
403 | loop { | |
404 | let (token, text) = self.next()?; | |
405 | match token { | |
406 | Token::Newline => break, | |
92310d58 DM |
407 | Token::Inet => address_family_v4 = true, |
408 | Token::Inet6 => address_family_v6 = true, | |
7e02d08c DM |
409 | Token::Loopback => config_method = Some(NetworkConfigMethod::Loopback), |
410 | Token::Static => config_method = Some(NetworkConfigMethod::Static), | |
411 | Token::Manual => config_method = Some(NetworkConfigMethod::Manual), | |
412 | Token::DHCP => config_method = Some(NetworkConfigMethod::DHCP), | |
f34d4401 DM |
413 | _ => bail!("unknown iface option {}", text), |
414 | } | |
415 | } | |
416 | ||
7e02d08c | 417 | let config_method = config_method.unwrap_or(NetworkConfigMethod::Static); |
92310d58 DM |
418 | |
419 | if !(address_family_v4 || address_family_v6) { | |
420 | address_family_v4 = true; | |
421 | address_family_v6 = true; | |
422 | } | |
f34d4401 DM |
423 | |
424 | if let Some(mut interface) = config.interfaces.get_mut(&iface) { | |
92310d58 | 425 | if address_family_v4 { |
6f422880 | 426 | set_method_v4(interface, config_method)?; |
92310d58 DM |
427 | } |
428 | if address_family_v6 { | |
6f422880 | 429 | set_method_v6(interface, config_method)?; |
f34d4401 | 430 | } |
92310d58 | 431 | |
5f60a58f | 432 | self.parse_iface_attributes(&mut interface, address_family_v4, address_family_v6)?; |
f34d4401 | 433 | } else { |
92310d58 DM |
434 | let mut interface = Interface::new(iface.clone()); |
435 | if address_family_v4 { | |
6f422880 | 436 | set_method_v4(&mut interface, config_method)?; |
92310d58 DM |
437 | } |
438 | if address_family_v6 { | |
6f422880 | 439 | set_method_v6(&mut interface, config_method)?; |
92310d58 | 440 | } |
f34d4401 | 441 | |
5f60a58f | 442 | self.parse_iface_attributes(&mut interface, address_family_v4, address_family_v6)?; |
f34d4401 DM |
443 | |
444 | config.interfaces.insert(interface.name.clone(), interface); | |
92310d58 | 445 | |
f34d4401 DM |
446 | config.order.push(NetworkOrderEntry::Iface(iface)); |
447 | } | |
448 | ||
449 | Ok(()) | |
450 | } | |
451 | ||
1ca540a6 DM |
452 | pub fn parse_interfaces(&mut self, existing_interfaces: Option<&HashMap<String, bool>>) -> Result<NetworkConfig, Error> { |
453 | self._parse_interfaces(existing_interfaces) | |
f34d4401 DM |
454 | .map_err(|err| format_err!("line {}: {}", self.line_nr, err)) |
455 | } | |
456 | ||
1ca540a6 | 457 | pub fn _parse_interfaces(&mut self, existing_interfaces: Option<&HashMap<String, bool>>) -> Result<NetworkConfig, Error> { |
f34d4401 DM |
458 | let mut config = NetworkConfig::new(); |
459 | ||
a9bb491e DM |
460 | let mut auto_flag: HashSet<String> = HashSet::new(); |
461 | ||
f34d4401 | 462 | loop { |
e2d940b9 | 463 | match self.peek()? { |
f34d4401 | 464 | Token::EOF => { |
a9bb491e | 465 | break; |
f34d4401 DM |
466 | } |
467 | Token::Newline => { | |
e2d940b9 | 468 | // skip empty lines |
f34d4401 | 469 | self.eat(Token::Newline)?; |
f34d4401 DM |
470 | } |
471 | Token::Comment => { | |
472 | let (_, text) = self.next()?; | |
f34d4401 DM |
473 | config.order.push(NetworkOrderEntry::Comment(text)); |
474 | self.eat(Token::Newline)?; | |
475 | } | |
476 | Token::Auto => { | |
a9bb491e | 477 | self.parse_auto(&mut auto_flag)?; |
f34d4401 DM |
478 | } |
479 | Token::Iface => { | |
480 | self.parse_iface(&mut config)?; | |
481 | } | |
482 | _ => { | |
483 | let option = self.parse_to_eol()?; | |
484 | if !option.is_empty() { | |
485 | config.order.push(NetworkOrderEntry::Option(option)); | |
486 | } | |
487 | } | |
488 | } | |
489 | } | |
a9bb491e DM |
490 | |
491 | for iface in auto_flag.iter() { | |
492 | if let Some(interface) = config.interfaces.get_mut(iface) { | |
7b22acd0 | 493 | interface.autostart = true; |
a9bb491e DM |
494 | } |
495 | } | |
496 | ||
3f129233 | 497 | lazy_static!{ |
02269f3d DM |
498 | static ref INTERFACE_ALIAS_REGEX: Regex = Regex::new(r"^\S+:\d+$").unwrap(); |
499 | static ref VLAN_INTERFACE_REGEX: Regex = Regex::new(r"^\S+\.\d+$").unwrap(); | |
3f129233 DM |
500 | } |
501 | ||
1ca540a6 DM |
502 | if let Some(existing_interfaces) = existing_interfaces { |
503 | for (iface, active) in existing_interfaces.iter() { | |
504 | if let Some(interface) = config.interfaces.get_mut(iface) { | |
505 | interface.active = *active; | |
0ed9a2b3 | 506 | if interface.interface_type == NetworkInterfaceType::Unknown && super::is_physical_nic(iface) { |
7b22acd0 | 507 | interface.interface_type = NetworkInterfaceType::Eth; |
1ca540a6 | 508 | } |
0ed9a2b3 | 509 | } else if super::is_physical_nic(iface) { // also add all physical NICs |
1ca540a6 | 510 | let mut interface = Interface::new(iface.clone()); |
6f422880 | 511 | set_method_v4(&mut interface, NetworkConfigMethod::Manual)?; |
7b22acd0 | 512 | interface.interface_type = NetworkInterfaceType::Eth; |
1ca540a6 DM |
513 | interface.active = *active; |
514 | config.interfaces.insert(interface.name.clone(), interface); | |
515 | config.order.push(NetworkOrderEntry::Iface(iface.to_string())); | |
96d94786 | 516 | } |
3f129233 DM |
517 | } |
518 | } | |
519 | ||
02269f3d DM |
520 | for (name, interface) in config.interfaces.iter_mut() { |
521 | if interface.interface_type != NetworkInterfaceType::Unknown { continue; } | |
522 | if name == "lo" { | |
523 | interface.interface_type = NetworkInterfaceType::Loopback; | |
524 | continue; | |
525 | } | |
526 | if INTERFACE_ALIAS_REGEX.is_match(name) { | |
527 | interface.interface_type = NetworkInterfaceType::Alias; | |
528 | continue; | |
529 | } | |
530 | if VLAN_INTERFACE_REGEX.is_match(name) { | |
531 | interface.interface_type = NetworkInterfaceType::Vlan; | |
532 | continue; | |
533 | } | |
0ed9a2b3 | 534 | if super::is_physical_nic(name) { |
7b22acd0 | 535 | interface.interface_type = NetworkInterfaceType::Eth; |
02269f3d DM |
536 | continue; |
537 | } | |
538 | } | |
539 | ||
1ca540a6 DM |
540 | if config.interfaces.get("lo").is_none() { |
541 | let mut interface = Interface::new(String::from("lo")); | |
6f422880 | 542 | set_method_v4(&mut interface, NetworkConfigMethod::Loopback)?; |
1ca540a6 | 543 | interface.interface_type = NetworkInterfaceType::Loopback; |
7b22acd0 | 544 | interface.autostart = true; |
1ca540a6 DM |
545 | config.interfaces.insert(interface.name.clone(), interface); |
546 | ||
547 | // Note: insert 'lo' as first interface after initial comments | |
548 | let mut new_order = Vec::new(); | |
549 | let mut added_lo = false; | |
550 | for entry in config.order { | |
551 | if added_lo { new_order.push(entry); continue; } // copy the rest | |
552 | match entry { | |
553 | NetworkOrderEntry::Comment(_) => { | |
554 | new_order.push(entry); | |
555 | } | |
556 | _ => { | |
557 | new_order.push(NetworkOrderEntry::Iface(String::from("lo"))); | |
558 | added_lo = true; | |
559 | new_order.push(entry); | |
560 | } | |
561 | } | |
562 | } | |
563 | config.order = new_order; | |
564 | } | |
565 | ||
a9bb491e | 566 | Ok(config) |
f34d4401 DM |
567 | } |
568 | } |