]> git.proxmox.com Git - proxmox-backup.git/blame - src/config/network.rs
tfa: remember recovery indices
[proxmox-backup.git] / src / config / network.rs
CommitLineData
f34d4401 1use std::io::{Write};
a4ccb461 2use std::collections::{HashSet, HashMap, BTreeMap};
f34d4401 3
df6bb03d 4use anyhow::{Error, format_err, bail};
bab5d18c 5use serde::de::{value, IntoDeserializer, Deserialize};
5bef0f43
DM
6use lazy_static::lazy_static;
7use regex::Regex;
f34d4401 8
904e9886
DM
9use proxmox::tools::{fs::replace_file, fs::CreateOptions};
10
f34d4401 11mod helper;
92310d58 12pub use helper::*;
f34d4401
DM
13
14mod lexer;
15pub use lexer::*;
16
17mod parser;
18pub use parser::*;
19
8f2f3dd7 20use crate::api2::types::{Interface, NetworkConfigMethod, NetworkInterfaceType, LinuxBondMode, BondXmitHashPolicy};
bab5d18c 21
0ed9a2b3
DM
22lazy_static!{
23 static ref PHYSICAL_NIC_REGEX: Regex = Regex::new(r"^(?:eth\d+|en[^:.]+|ib\d+)$").unwrap();
24}
25
26pub fn is_physical_nic(iface: &str) -> bool {
27 PHYSICAL_NIC_REGEX.is_match(iface)
28}
29
bab5d18c
DM
30pub fn bond_mode_from_str(s: &str) -> Result<LinuxBondMode, Error> {
31 LinuxBondMode::deserialize(s.into_deserializer())
32 .map_err(|_: value::Error| format_err!("invalid bond_mode '{}'", s))
33}
34
35pub fn bond_mode_to_str(mode: LinuxBondMode) -> &'static str {
36 match mode {
37 LinuxBondMode::balance_rr => "balance-rr",
38 LinuxBondMode::active_backup => "active-backup",
39 LinuxBondMode::balance_xor => "balance-xor",
40 LinuxBondMode::broadcast => "broadcast",
41 LinuxBondMode::ieee802_3ad => "802.3ad",
42 LinuxBondMode::balance_tlb => "balance-tlb",
43 LinuxBondMode::balance_alb => "balance-alb",
44 }
45}
f34d4401 46
8f2f3dd7
DC
47pub fn bond_xmit_hash_policy_from_str(s: &str) -> Result<BondXmitHashPolicy, Error> {
48 BondXmitHashPolicy::deserialize(s.into_deserializer())
49 .map_err(|_: value::Error| format_err!("invalid bond_xmit_hash_policy '{}'", s))
50}
51
52pub fn bond_xmit_hash_policy_to_str(policy: &BondXmitHashPolicy) -> &'static str {
53 match policy {
54 BondXmitHashPolicy::layer2 => "layer2",
55 BondXmitHashPolicy::layer2_3 => "layer2+3",
56 BondXmitHashPolicy::layer3_4 => "layer3+4",
57 }
58}
59
f34d4401
DM
60impl Interface {
61
92310d58 62 pub fn new(name: String) -> Self {
a9bb491e 63 Self {
f34d4401 64 name,
02269f3d 65 interface_type: NetworkInterfaceType::Unknown,
7b22acd0 66 autostart: false,
3f129233 67 active: false,
7b22acd0
DM
68 method: None,
69 method6: None,
70 cidr: None,
71 gateway: None,
72 cidr6: None,
73 gateway6: None,
74 options: Vec::new(),
75 options6: Vec::new(),
76 comments: None,
77 comments6: None,
2c18efd9 78 mtu: None,
1d9a68c2 79 bridge_ports: None,
7b22acd0 80 bridge_vlan_aware: None,
bab5d18c
DM
81 slaves: None,
82 bond_mode: None,
85959a99 83 bond_primary: None,
8f2f3dd7 84 bond_xmit_hash_policy: None,
f34d4401
DM
85 }
86 }
87
7e02d08c 88 fn set_method_v4(&mut self, method: NetworkConfigMethod) -> Result<(), Error> {
7b22acd0
DM
89 if self.method.is_none() {
90 self.method = Some(method);
92310d58
DM
91 } else {
92 bail!("inet configuration method already set.");
93 }
94 Ok(())
95 }
96
7e02d08c 97 fn set_method_v6(&mut self, method: NetworkConfigMethod) -> Result<(), Error> {
7b22acd0
DM
98 if self.method6.is_none() {
99 self.method6 = Some(method);
92310d58
DM
100 } else {
101 bail!("inet6 configuration method already set.");
102 }
103 Ok(())
104 }
105
8b57cd44 106 fn set_cidr_v4(&mut self, address: String) -> Result<(), Error> {
7b22acd0
DM
107 if self.cidr.is_none() {
108 self.cidr = Some(address);
f34d4401
DM
109 } else {
110 bail!("duplicate IPv4 address.");
111 }
112 Ok(())
113 }
114
115 fn set_gateway_v4(&mut self, gateway: String) -> Result<(), Error> {
7b22acd0
DM
116 if self.gateway.is_none() {
117 self.gateway = Some(gateway);
f34d4401
DM
118 } else {
119 bail!("duplicate IPv4 gateway.");
120 }
121 Ok(())
122 }
123
8b57cd44 124 fn set_cidr_v6(&mut self, address: String) -> Result<(), Error> {
7b22acd0
DM
125 if self.cidr6.is_none() {
126 self.cidr6 = Some(address);
f34d4401
DM
127 } else {
128 bail!("duplicate IPv6 address.");
129 }
130 Ok(())
131 }
132
133 fn set_gateway_v6(&mut self, gateway: String) -> Result<(), Error> {
7b22acd0
DM
134 if self.gateway6.is_none() {
135 self.gateway6 = Some(gateway);
f34d4401
DM
136 } else {
137 bail!("duplicate IPv4 gateway.");
138 }
139 Ok(())
140 }
141
c38b4bb8
DM
142 fn set_interface_type(&mut self, interface_type: NetworkInterfaceType) -> Result<(), Error> {
143 if self.interface_type == NetworkInterfaceType::Unknown {
144 self.interface_type = interface_type;
145 } else if self.interface_type != interface_type {
146 bail!("interface type already defined - cannot change from {:?} to {:?}", self.interface_type, interface_type);
147 }
148 Ok(())
149 }
150
5e4e88e8
DM
151 pub(crate) fn set_bridge_ports(&mut self, ports: Vec<String>) -> Result<(), Error> {
152 if self.interface_type != NetworkInterfaceType::Bridge {
153 bail!("interface '{}' is no bridge (type is {:?})", self.name, self.interface_type);
154 }
155 self.bridge_ports = Some(ports);
156 Ok(())
157 }
158
159 pub(crate) fn set_bond_slaves(&mut self, slaves: Vec<String>) -> Result<(), Error> {
160 if self.interface_type != NetworkInterfaceType::Bond {
161 bail!("interface '{}' is no bond (type is {:?})", self.name, self.interface_type);
162 }
bab5d18c 163 self.slaves = Some(slaves);
5e4e88e8
DM
164 Ok(())
165 }
166
add5861e 167 /// Write attributes not depending on address family
2c18efd9 168 fn write_iface_attributes(&self, w: &mut dyn Write) -> Result<(), Error> {
1d9a68c2 169
db5672e8
DM
170 static EMPTY_LIST: Vec<String> = Vec::new();
171
1d9a68c2
DM
172 match self.interface_type {
173 NetworkInterfaceType::Bridge => {
7b22acd0
DM
174 if let Some(true) = self.bridge_vlan_aware {
175 writeln!(w, "\tbridge-vlan-aware yes")?;
176 }
db5672e8
DM
177 let ports = self.bridge_ports.as_ref().unwrap_or(&EMPTY_LIST);
178 if ports.is_empty() {
179 writeln!(w, "\tbridge-ports none")?;
180 } else {
181 writeln!(w, "\tbridge-ports {}", ports.join(" "))?;
1d9a68c2
DM
182 }
183 }
42fbe91a 184 NetworkInterfaceType::Bond => {
bab5d18c
DM
185 let mode = self.bond_mode.unwrap_or(LinuxBondMode::balance_rr);
186 writeln!(w, "\tbond-mode {}", bond_mode_to_str(mode))?;
85959a99
DC
187 if let Some(primary) = &self.bond_primary {
188 if mode == LinuxBondMode::active_backup {
189 writeln!(w, "\tbond-primary {}", primary)?;
190 }
191 }
bab5d18c 192
8f2f3dd7
DC
193 if let Some(xmit_policy) = &self.bond_xmit_hash_policy {
194 if mode == LinuxBondMode::ieee802_3ad ||
195 mode == LinuxBondMode::balance_xor
196 {
197 writeln!(w, "\tbond_xmit_hash_policy {}", bond_xmit_hash_policy_to_str(xmit_policy))?;
198 }
199 }
200
db5672e8
DM
201 let slaves = self.slaves.as_ref().unwrap_or(&EMPTY_LIST);
202 if slaves.is_empty() {
203 writeln!(w, "\tbond-slaves none")?;
204 } else {
205 writeln!(w, "\tbond-slaves {}", slaves.join(" "))?;
42fbe91a
DM
206 }
207 }
1d9a68c2
DM
208 _ => {}
209 }
210
2c18efd9 211 if let Some(mtu) = self.mtu {
7b22acd0 212 writeln!(w, "\tmtu {}", mtu)?;
2c18efd9 213 }
1d9a68c2 214
2c18efd9
DM
215 Ok(())
216 }
217
add5861e 218 /// Write attributes depending on address family inet (IPv4)
f8e7ac68
DM
219 fn write_iface_attributes_v4(&self, w: &mut dyn Write, method: NetworkConfigMethod) -> Result<(), Error> {
220 if method == NetworkConfigMethod::Static {
7b22acd0
DM
221 if let Some(address) = &self.cidr {
222 writeln!(w, "\taddress {}", address)?;
f8e7ac68 223 }
7b22acd0
DM
224 if let Some(gateway) = &self.gateway {
225 writeln!(w, "\tgateway {}", gateway)?;
f8e7ac68 226 }
a9bb491e 227 }
f8e7ac68 228
7b22acd0
DM
229 for option in &self.options {
230 writeln!(w, "\t{}", option)?;
a9bb491e
DM
231 }
232
7b22acd0 233 if let Some(ref comments) = self.comments {
8a6b86b8
DM
234 for comment in comments.lines() {
235 writeln!(w, "#{}", comment)?;
236 }
5f60a58f
DM
237 }
238
a9bb491e
DM
239 Ok(())
240 }
241
add5861e 242 /// Write attributes depending on address family inet6 (IPv6)
f8e7ac68
DM
243 fn write_iface_attributes_v6(&self, w: &mut dyn Write, method: NetworkConfigMethod) -> Result<(), Error> {
244 if method == NetworkConfigMethod::Static {
7b22acd0
DM
245 if let Some(address) = &self.cidr6 {
246 writeln!(w, "\taddress {}", address)?;
f8e7ac68 247 }
7b22acd0
DM
248 if let Some(gateway) = &self.gateway6 {
249 writeln!(w, "\tgateway {}", gateway)?;
f8e7ac68 250 }
a9bb491e 251 }
f8e7ac68 252
7b22acd0
DM
253 for option in &self.options6 {
254 writeln!(w, "\t{}", option)?;
a9bb491e
DM
255 }
256
7b22acd0 257 if let Some(ref comments) = self.comments6 {
8a6b86b8
DM
258 for comment in comments.lines() {
259 writeln!(w, "#{}", comment)?;
260 }
5f60a58f
DM
261 }
262
a9bb491e
DM
263 Ok(())
264 }
265
266 fn write_iface(&self, w: &mut dyn Write) -> Result<(), Error> {
267
7e02d08c 268 fn method_to_str(method: NetworkConfigMethod) -> &'static str {
a9bb491e 269 match method {
7e02d08c
DM
270 NetworkConfigMethod::Static => "static",
271 NetworkConfigMethod::Loopback => "loopback",
272 NetworkConfigMethod::Manual => "manual",
273 NetworkConfigMethod::DHCP => "dhcp",
a9bb491e
DM
274 }
275 }
276
7b22acd0 277 if self.method.is_none() && self.method6.is_none() { return Ok(()); }
f8e7ac68 278
7b22acd0 279 if self.autostart {
a9bb491e
DM
280 writeln!(w, "auto {}", self.name)?;
281 }
282
7b22acd0
DM
283 if let Some(method) = self.method {
284 writeln!(w, "iface {} inet {}", self.name, method_to_str(method))?;
285 self.write_iface_attributes_v4(w, method)?;
286 self.write_iface_attributes(w)?;
287 writeln!(w)?;
288 }
bab5d18c 289
7b22acd0
DM
290 if let Some(method6) = self.method6 {
291 let mut skip_v6 = false; // avoid empty inet6 manual entry
20813274
WB
292 if self.method.is_some()
293 && method6 == NetworkConfigMethod::Manual
294 && self.comments6.is_none()
295 && self.options6.is_empty()
296 {
297 skip_v6 = true;
a9bb491e 298 }
bab5d18c 299
7b22acd0
DM
300 if !skip_v6 {
301 writeln!(w, "iface {} inet6 {}", self.name, method_to_str(method6))?;
302 self.write_iface_attributes_v6(w, method6)?;
303 if self.method.is_none() { // only write common attributes once
5f60a58f
DM
304 self.write_iface_attributes(w)?;
305 }
a9bb491e
DM
306 writeln!(w)?;
307 }
308 }
7b22acd0 309
a9bb491e
DM
310 Ok(())
311 }
f34d4401
DM
312}
313
314#[derive(Debug)]
315enum NetworkOrderEntry {
316 Iface(String),
317 Comment(String),
318 Option(String),
319}
320
321#[derive(Debug)]
322pub struct NetworkConfig {
a4ccb461 323 pub interfaces: BTreeMap<String, Interface>,
f34d4401
DM
324 order: Vec<NetworkOrderEntry>,
325}
326
1ca540a6
DM
327use std::convert::TryFrom;
328
329impl TryFrom<NetworkConfig> for String {
330
331 type Error = Error;
332
333 fn try_from(config: NetworkConfig) -> Result<Self, Self::Error> {
334 let mut output = Vec::new();
335 config.write_config(&mut output)?;
336 let res = String::from_utf8(output)?;
337 Ok(res)
338 }
339}
340
f34d4401
DM
341impl NetworkConfig {
342
343 pub fn new() -> Self {
344 Self {
a4ccb461 345 interfaces: BTreeMap::new(),
f34d4401
DM
346 order: Vec::new(),
347 }
348 }
349
df6bb03d
DM
350 pub fn lookup(&self, name: &str) -> Result<&Interface, Error> {
351 let interface = self.interfaces.get(name).ok_or_else(|| {
352 format_err!("interface '{}' does not exist.", name)
353 })?;
354 Ok(interface)
355 }
356
357 pub fn lookup_mut(&mut self, name: &str) -> Result<&mut Interface, Error> {
358 let interface = self.interfaces.get_mut(name).ok_or_else(|| {
359 format_err!("interface '{}' does not exist.", name)
360 })?;
361 Ok(interface)
362 }
363
a4ccb461
DM
364 /// Check if ports are used only once
365 pub fn check_port_usage(&self) -> Result<(), Error> {
366 let mut used_ports = HashMap::new();
367 let mut check_port_usage = |iface, ports: &Vec<String>| {
368 for port in ports.iter() {
369 if let Some(prev_iface) = used_ports.get(port) {
370 bail!("iface '{}' port '{}' is already used on interface '{}'",
371 iface, port, prev_iface);
372 }
373 used_ports.insert(port.to_string(), iface);
374 }
375 Ok(())
376 };
377
378 for (iface, interface) in self.interfaces.iter() {
379 if let Some(ports) = &interface.bridge_ports { check_port_usage(iface, ports)?; }
380 if let Some(slaves) = &interface.slaves { check_port_usage(iface, slaves)?; }
381 }
382 Ok(())
383 }
384
0f6bdbb0
DM
385 /// Check if child mtu is less or equal than parent mtu
386 pub fn check_mtu(&self, parent_name: &str, child_name: &str) -> Result<(), Error> {
387
388 let parent = self.interfaces.get(parent_name)
389 .ok_or(format_err!("check_mtu - missing parent interface '{}'", parent_name))?;
390 let child = self.interfaces.get(child_name)
391 .ok_or(format_err!("check_mtu - missing child interface '{}'", child_name))?;
392
393 let child_mtu = match child.mtu {
394 Some(mtu) => mtu,
395 None => return Ok(()),
396 };
397
398 let parent_mtu = match parent.mtu {
399 Some(mtu) => mtu,
400 None => {
401 if parent.interface_type == NetworkInterfaceType::Bond {
402 child_mtu
403 } else {
404 1500
405 }
406 }
407 };
408
409 if parent_mtu < child_mtu {
410 bail!("interface '{}' - mtu {} is lower than '{}' - mtu {}\n",
411 parent_name, parent_mtu, child_name, child_mtu);
412 }
413
414 Ok(())
415 }
416
417 /// Check if bond slaves exists
418 pub fn check_bond_slaves(&self) -> Result<(), Error> {
419 for (iface, interface) in self.interfaces.iter() {
420 if let Some(slaves) = &interface.slaves {
421 for slave in slaves.iter() {
422 match self.interfaces.get(slave) {
423 Some(entry) => {
424 if entry.interface_type != NetworkInterfaceType::Eth {
425 bail!("bond '{}' - wrong interface type on slave '{}' ({:?} != {:?})",
426 iface, slave, entry.interface_type, NetworkInterfaceType::Eth);
427 }
428 }
429 None => {
430 bail!("bond '{}' - unable to find slave '{}'", iface, slave);
431 }
432 }
433 self.check_mtu(iface, slave)?;
434 }
435 }
436 }
437 Ok(())
438 }
439
440 /// Check if bridge ports exists
441 pub fn check_bridge_ports(&self) -> Result<(), Error> {
5bef0f43
DM
442 lazy_static!{
443 static ref VLAN_INTERFACE_REGEX: Regex = Regex::new(r"^(\S+)\.(\d+)$").unwrap();
444 }
445
0f6bdbb0
DM
446 for (iface, interface) in self.interfaces.iter() {
447 if let Some(ports) = &interface.bridge_ports {
448 for port in ports.iter() {
5bef0f43
DM
449 let captures = VLAN_INTERFACE_REGEX.captures(port);
450 let port = if let Some(ref caps) = captures { &caps[1] } else { port.as_str() };
0f6bdbb0
DM
451 if !self.interfaces.contains_key(port) {
452 bail!("bridge '{}' - unable to find port '{}'", iface, port);
453 }
454 self.check_mtu(iface, port)?;
455 }
456 }
457 }
458 Ok(())
459 }
460
f34d4401
DM
461 pub fn write_config(&self, w: &mut dyn Write) -> Result<(), Error> {
462
a4ccb461 463 self.check_port_usage()?;
0f6bdbb0
DM
464 self.check_bond_slaves()?;
465 self.check_bridge_ports()?;
a4ccb461 466
f34d4401
DM
467 let mut done = HashSet::new();
468
469 let mut last_entry_was_comment = false;
470
471 for entry in self.order.iter() {
a9bb491e 472 match entry {
f34d4401
DM
473 NetworkOrderEntry::Comment(comment) => {
474 writeln!(w, "#{}", comment)?;
475 last_entry_was_comment = true;
476 }
477 NetworkOrderEntry::Option(option) => {
478 if last_entry_was_comment { writeln!(w)?; }
479 last_entry_was_comment = false;
480 writeln!(w, "{}", option)?;
481 writeln!(w)?;
482 }
483 NetworkOrderEntry::Iface(name) => {
484 let interface = match self.interfaces.get(name) {
485 Some(interface) => interface,
486 None => continue,
487 };
488
489 if last_entry_was_comment { writeln!(w)?; }
490 last_entry_was_comment = false;
491
492 if done.contains(name) { continue; }
493 done.insert(name);
494
a9bb491e 495 interface.write_iface(w)?;
f34d4401
DM
496 }
497 }
498 }
499
500 for (name, interface) in &self.interfaces {
501 if done.contains(name) { continue; }
a9bb491e 502 interface.write_iface(w)?;
f34d4401
DM
503 }
504 Ok(())
505 }
506}
904e9886
DM
507
508pub const NETWORK_INTERFACES_FILENAME: &str = "/etc/network/interfaces";
02e36d96 509pub const NETWORK_INTERFACES_NEW_FILENAME: &str = "/etc/network/interfaces.new";
904e9886
DM
510pub const NETWORK_LOCKFILE: &str = "/var/lock/pve-network.lck";
511
512pub fn config() -> Result<(NetworkConfig, [u8;32]), Error> {
02e36d96 513
3eeba687
DM
514 let content = match proxmox::tools::fs::file_get_optional_contents(NETWORK_INTERFACES_NEW_FILENAME)? {
515 Some(content) => content,
516 None => {
517 let content = proxmox::tools::fs::file_get_optional_contents(NETWORK_INTERFACES_FILENAME)?;
518 content.unwrap_or(Vec::new())
519 }
520 };
904e9886
DM
521
522 let digest = openssl::sha::sha256(&content);
523
1ca540a6 524 let existing_interfaces = get_network_interfaces()?;
904e9886 525 let mut parser = NetworkParser::new(&content[..]);
1ca540a6 526 let data = parser.parse_interfaces(Some(&existing_interfaces))?;
904e9886
DM
527
528 Ok((data, digest))
529}
530
2eefd9ae
DM
531pub fn changes() -> Result<String, Error> {
532
533 if !std::path::Path::new(NETWORK_INTERFACES_NEW_FILENAME).exists() {
534 return Ok(String::new());
535 }
536
537 compute_file_diff(NETWORK_INTERFACES_FILENAME, NETWORK_INTERFACES_NEW_FILENAME)
538}
539
904e9886
DM
540pub fn save_config(config: &NetworkConfig) -> Result<(), Error> {
541
542 let mut raw = Vec::new();
543 config.write_config(&mut raw)?;
544
545 let mode = nix::sys::stat::Mode::from_bits_truncate(0o0644);
546 // set the correct owner/group/permissions while saving file
547 // owner(rw) = root, group(r)=root, others(r)
548 let options = CreateOptions::new()
549 .perm(mode)
550 .owner(nix::unistd::ROOT)
551 .group(nix::unistd::Gid::from_raw(0));
552
02e36d96 553 replace_file(NETWORK_INTERFACES_NEW_FILENAME, &raw, options)?;
904e9886
DM
554
555 Ok(())
556}
df6bb03d
DM
557
558// shell completion helper
559pub fn complete_interface_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
560 match config() {
561 Ok((data, _digest)) => data.interfaces.keys().map(|id| id.to_string()).collect(),
562 Err(_) => return vec![],
563 }
564}
1ca540a6 565
65dab026
DM
566
567pub fn complete_port_list(arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
568 let mut ports = Vec::new();
569 match config() {
570 Ok((data, _digest)) => {
571 for (iface, interface) in data.interfaces.iter() {
572 if interface.interface_type == NetworkInterfaceType::Eth {
573 ports.push(iface.to_string());
574 }
575 }
576 }
577 Err(_) => return vec![],
578 };
579
580 let arg = arg.clone().trim();
581 let prefix = if let Some(idx) = arg.rfind(",") { &arg[..idx+1] } else { "" };
582 ports.iter().map(|port| format!("{}{}", prefix, port)).collect()
583}
584
1ca540a6
DM
585#[cfg(test)]
586mod test {
587
588 use anyhow::{Error};
589
590 use super::*;
591
592 #[test]
593 fn test_network_config_create_lo_1() -> Result<(), Error> {
594
595 let input = "";
596
597 let mut parser = NetworkParser::new(&input.as_bytes()[..]);
598
599 let config = parser.parse_interfaces(None)?;
600
601 let output = String::try_from(config)?;
602
603 let expected = "auto lo\niface lo inet loopback\n\n";
604 assert_eq!(output, expected);
605
606 // run again using output as input
607 let mut parser = NetworkParser::new(&output.as_bytes()[..]);
608
609 let config = parser.parse_interfaces(None)?;
610
611 let output = String::try_from(config)?;
612
613 assert_eq!(output, expected);
614
615 Ok(())
616 }
617
618 #[test]
619 fn test_network_config_create_lo_2() -> Result<(), Error> {
620
621 let input = "#c1\n\n#c2\n\niface test inet manual\n";
622
623 let mut parser = NetworkParser::new(&input.as_bytes()[..]);
624
625 let config = parser.parse_interfaces(None)?;
626
627 let output = String::try_from(config)?;
628
629 // Note: loopback should be added in front of other interfaces
630 let expected = "#c1\n#c2\n\nauto lo\niface lo inet loopback\n\niface test inet manual\n\n";
631 assert_eq!(output, expected);
632
633 Ok(())
634 }
87c4cb74
FE
635
636 #[test]
637 fn test_network_config_parser_no_blank_1() -> Result<(), Error> {
638 let input = "auto lo\n\
639 iface lo inet loopback\n\
640 iface lo inet6 loopback\n\
641 auto ens18\n\
642 iface ens18 inet static\n\
643 \taddress 192.168.20.144/20\n\
644 \tgateway 192.168.16.1\n\
645 # comment\n\
646 iface ens20 inet static\n\
647 \taddress 192.168.20.145/20\n\
648 iface ens21 inet manual\n\
649 iface ens22 inet manual\n";
650
651 let mut parser = NetworkParser::new(&input.as_bytes()[..]);
652
653 let config = parser.parse_interfaces(None)?;
654
655 let output = String::try_from(config)?;
656
657 let expected = "auto lo\n\
658 iface lo inet loopback\n\
659 \n\
660 iface lo inet6 loopback\n\
661 \n\
662 auto ens18\n\
663 iface ens18 inet static\n\
664 \taddress 192.168.20.144/20\n\
665 \tgateway 192.168.16.1\n\
666 #comment\n\
667 \n\
668 iface ens20 inet static\n\
669 \taddress 192.168.20.145/20\n\
670 \n\
671 iface ens21 inet manual\n\
672 \n\
673 iface ens22 inet manual\n\
674 \n";
675 assert_eq!(output, expected);
676
677 Ok(())
678 }
679
680 #[test]
681 fn test_network_config_parser_no_blank_2() -> Result<(), Error> {
682 // Adapted from bug 2926
683 let input = "### Hetzner Online GmbH installimage\n\
684 \n\
685 source /etc/network/interfaces.d/*\n\
686 \n\
687 auto lo\n\
688 iface lo inet loopback\n\
689 iface lo inet6 loopback\n\
690 \n\
691 auto enp4s0\n\
692 iface enp4s0 inet static\n\
693 \taddress 10.10.10.10/24\n\
694 \tgateway 10.10.10.1\n\
695 \t# route 10.10.20.10/24 via 10.10.20.1\n\
696 \tup route add -net 10.10.20.10 netmask 255.255.255.0 gw 10.10.20.1 dev enp4s0\n\
697 \n\
698 iface enp4s0 inet6 static\n\
699 \taddress fe80::5496:35ff:fe99:5a6a/64\n\
700 \tgateway fe80::1\n";
701
702 let mut parser = NetworkParser::new(&input.as_bytes()[..]);
703
704 let config = parser.parse_interfaces(None)?;
705
706 let output = String::try_from(config)?;
707
708 let expected = "### Hetzner Online GmbH installimage\n\
709 \n\
710 source /etc/network/interfaces.d/*\n\
711 \n\
712 auto lo\n\
713 iface lo inet loopback\n\
714 \n\
715 iface lo inet6 loopback\n\
716 \n\
717 auto enp4s0\n\
718 iface enp4s0 inet static\n\
719 \taddress 10.10.10.10/24\n\
720 \tgateway 10.10.10.1\n\
721 \t# route 10.10.20.10/24 via 10.10.20.1\n\
722 \tup route add -net 10.10.20.10 netmask 255.255.255.0 gw 10.10.20.1 dev enp4s0\n\
723 \n\
724 iface enp4s0 inet6 static\n\
725 \taddress fe80::5496:35ff:fe99:5a6a/64\n\
726 \tgateway fe80::1\n\
727 \n";
728 assert_eq!(output, expected);
729
730 Ok(())
731 }
1ca540a6 732}