]> git.proxmox.com Git - proxmox-backup.git/blob - src/config/network.rs
src/config/network.rs: make it compatible with pve
[proxmox-backup.git] / src / config / network.rs
1 use std::io::{Write};
2 use std::collections::{HashSet, HashMap};
3
4 use anyhow::{Error, format_err, bail};
5
6 use proxmox::tools::{fs::replace_file, fs::CreateOptions};
7
8 mod helper;
9 pub use helper::*;
10
11 mod lexer;
12 pub use lexer::*;
13
14 mod parser;
15 pub use parser::*;
16
17 use crate::api2::types::{Interface, NetworkConfigMethod, NetworkInterfaceType};
18
19 impl Interface {
20
21 pub fn new(name: String) -> Self {
22 Self {
23 name,
24 interface_type: NetworkInterfaceType::Unknown,
25 autostart: false,
26 active: false,
27 method: None,
28 method6: None,
29 cidr: None,
30 gateway: None,
31 cidr6: None,
32 gateway6: None,
33 options: Vec::new(),
34 options6: Vec::new(),
35 comments: None,
36 comments6: None,
37 mtu: None,
38 bridge_ports: None,
39 bridge_vlan_aware: None,
40 bond_slaves: None,
41 }
42 }
43
44 fn set_method_v4(&mut self, method: NetworkConfigMethod) -> Result<(), Error> {
45 if self.method.is_none() {
46 self.method = Some(method);
47 } else {
48 bail!("inet configuration method already set.");
49 }
50 Ok(())
51 }
52
53 fn set_method_v6(&mut self, method: NetworkConfigMethod) -> Result<(), Error> {
54 if self.method6.is_none() {
55 self.method6 = Some(method);
56 } else {
57 bail!("inet6 configuration method already set.");
58 }
59 Ok(())
60 }
61
62 fn set_cidr_v4(&mut self, address: String) -> Result<(), Error> {
63 if self.cidr.is_none() {
64 self.cidr = Some(address);
65 } else {
66 bail!("duplicate IPv4 address.");
67 }
68 Ok(())
69 }
70
71 fn set_gateway_v4(&mut self, gateway: String) -> Result<(), Error> {
72 if self.gateway.is_none() {
73 self.gateway = Some(gateway);
74 } else {
75 bail!("duplicate IPv4 gateway.");
76 }
77 Ok(())
78 }
79
80 fn set_cidr_v6(&mut self, address: String) -> Result<(), Error> {
81 if self.cidr6.is_none() {
82 self.cidr6 = Some(address);
83 } else {
84 bail!("duplicate IPv6 address.");
85 }
86 Ok(())
87 }
88
89 fn set_gateway_v6(&mut self, gateway: String) -> Result<(), Error> {
90 if self.gateway6.is_none() {
91 self.gateway6 = Some(gateway);
92 } else {
93 bail!("duplicate IPv4 gateway.");
94 }
95 Ok(())
96 }
97
98 fn set_interface_type(&mut self, interface_type: NetworkInterfaceType) -> Result<(), Error> {
99 if self.interface_type == NetworkInterfaceType::Unknown {
100 self.interface_type = interface_type;
101 } else if self.interface_type != interface_type {
102 bail!("interface type already defined - cannot change from {:?} to {:?}", self.interface_type, interface_type);
103 }
104 Ok(())
105 }
106
107 pub(crate) fn set_bridge_ports(&mut self, ports: Vec<String>) -> Result<(), Error> {
108 if self.interface_type != NetworkInterfaceType::Bridge {
109 bail!("interface '{}' is no bridge (type is {:?})", self.name, self.interface_type);
110 }
111 self.bridge_ports = Some(ports);
112 Ok(())
113 }
114
115 pub(crate) fn set_bond_slaves(&mut self, slaves: Vec<String>) -> Result<(), Error> {
116 if self.interface_type != NetworkInterfaceType::Bond {
117 bail!("interface '{}' is no bond (type is {:?})", self.name, self.interface_type);
118 }
119 self.bond_slaves = Some(slaves);
120 Ok(())
121 }
122
123 /// Write attributes not dependening on address family
124 fn write_iface_attributes(&self, w: &mut dyn Write) -> Result<(), Error> {
125
126 match self.interface_type {
127 NetworkInterfaceType::Bridge => {
128 if let Some(true) = self.bridge_vlan_aware {
129 writeln!(w, "\tbridge-vlan-aware yes")?;
130 }
131 if let Some(ref ports) = self.bridge_ports {
132 if ports.is_empty() {
133 writeln!(w, "\tbridge-ports none")?;
134 } else {
135 writeln!(w, "\tbridge-ports {}", ports.join(" "))?;
136 }
137 }
138 }
139 NetworkInterfaceType::Bond => {
140 if let Some(ref slaves) = self.bond_slaves {
141 if slaves.is_empty() {
142 writeln!(w, "\tbond-slaves none")?;
143 } else {
144 writeln!(w, "\tbond-slaves {}", slaves.join(" "))?;
145 }
146 }
147 }
148 _ => {}
149 }
150
151 if let Some(mtu) = self.mtu {
152 writeln!(w, "\tmtu {}", mtu)?;
153 }
154
155 Ok(())
156 }
157
158 /// Write attributes dependening on address family inet (IPv4)
159 fn write_iface_attributes_v4(&self, w: &mut dyn Write, method: NetworkConfigMethod) -> Result<(), Error> {
160 if method == NetworkConfigMethod::Static {
161 if let Some(address) = &self.cidr {
162 writeln!(w, "\taddress {}", address)?;
163 }
164 if let Some(gateway) = &self.gateway {
165 writeln!(w, "\tgateway {}", gateway)?;
166 }
167 }
168
169 for option in &self.options {
170 writeln!(w, "\t{}", option)?;
171 }
172
173 if let Some(ref comments) = self.comments {
174 for comment in comments.lines() {
175 writeln!(w, "#{}", comment)?;
176 }
177 }
178
179 Ok(())
180 }
181
182 /// Write attributes dependening on address family inet6 (IPv6)
183 fn write_iface_attributes_v6(&self, w: &mut dyn Write, method: NetworkConfigMethod) -> Result<(), Error> {
184 if method == NetworkConfigMethod::Static {
185 if let Some(address) = &self.cidr6 {
186 writeln!(w, "\taddress {}", address)?;
187 }
188 if let Some(gateway) = &self.gateway6 {
189 writeln!(w, "\tgateway {}", gateway)?;
190 }
191 }
192
193 for option in &self.options6 {
194 writeln!(w, "\t{}", option)?;
195 }
196
197 if let Some(ref comments) = self.comments6 {
198 for comment in comments.lines() {
199 writeln!(w, "#{}", comment)?;
200 }
201 }
202
203 Ok(())
204 }
205
206 /// Return whether we can write a single entry for inet and inet6
207 fn combine_entry(&self) -> bool {
208 // Note: use match to make sure we considered all values at compile time
209 match self {
210 Interface {
211 method,
212 method6,
213 options,
214 options6,
215 comments,
216 comments6,
217 // the rest does not matter
218 name: _name,
219 interface_type: _interface_type,
220 autostart: _autostart,
221 active: _active,
222 cidr: _cidr,
223 cidr6: _cidr6,
224 gateway: _gateway,
225 gateway6: _gateway6,
226 mtu: _mtu,
227 bridge_ports: _bridge_ports,
228 bridge_vlan_aware: _bridge_vlan_aware,
229 bond_slaves: _bond_slaves,
230 } => {
231 method == method6
232 && comments.is_none()
233 && comments6.is_none()
234 && options.is_empty()
235 && options6.is_empty()
236 }
237 }
238 }
239
240 fn write_iface(&self, w: &mut dyn Write) -> Result<(), Error> {
241
242 fn method_to_str(method: NetworkConfigMethod) -> &'static str {
243 match method {
244 NetworkConfigMethod::Static => "static",
245 NetworkConfigMethod::Loopback => "loopback",
246 NetworkConfigMethod::Manual => "manual",
247 NetworkConfigMethod::DHCP => "dhcp",
248 }
249 }
250
251 if self.method.is_none() && self.method6.is_none() { return Ok(()); }
252
253 if self.autostart {
254 writeln!(w, "auto {}", self.name)?;
255 }
256
257 if self.combine_entry() {
258 if let Some(method) = self.method {
259 writeln!(w, "iface {} {}", self.name, method_to_str(method))?;
260 self.write_iface_attributes_v4(w, method)?;
261 self.write_iface_attributes_v6(w, method)?;
262 self.write_iface_attributes(w)?;
263 writeln!(w)?;
264 }
265 return Ok(());
266 }
267
268 if let Some(method) = self.method {
269 writeln!(w, "iface {} inet {}", self.name, method_to_str(method))?;
270 self.write_iface_attributes_v4(w, method)?;
271 self.write_iface_attributes(w)?;
272 writeln!(w)?;
273 }
274
275 if let Some(method6) = self.method6 {
276 let mut skip_v6 = false; // avoid empty inet6 manual entry
277 if self.method.is_some() && method6 == NetworkConfigMethod::Manual {
278 if self.comments6.is_none() && self.options6.is_empty() { skip_v6 = true; }
279 }
280
281 if !skip_v6 {
282 writeln!(w, "iface {} inet6 {}", self.name, method_to_str(method6))?;
283 self.write_iface_attributes_v6(w, method6)?;
284 if self.method.is_none() { // only write common attributes once
285 self.write_iface_attributes(w)?;
286 }
287 writeln!(w)?;
288 }
289 }
290
291 Ok(())
292 }
293 }
294
295 #[derive(Debug)]
296 enum NetworkOrderEntry {
297 Iface(String),
298 Comment(String),
299 Option(String),
300 }
301
302 #[derive(Debug)]
303 pub struct NetworkConfig {
304 pub interfaces: HashMap<String, Interface>,
305 order: Vec<NetworkOrderEntry>,
306 }
307
308 use std::convert::TryFrom;
309
310 impl TryFrom<NetworkConfig> for String {
311
312 type Error = Error;
313
314 fn try_from(config: NetworkConfig) -> Result<Self, Self::Error> {
315 let mut output = Vec::new();
316 config.write_config(&mut output)?;
317 let res = String::from_utf8(output)?;
318 Ok(res)
319 }
320 }
321
322 impl NetworkConfig {
323
324 pub fn new() -> Self {
325 Self {
326 interfaces: HashMap::new(),
327 order: Vec::new(),
328 }
329 }
330
331 pub fn lookup(&self, name: &str) -> Result<&Interface, Error> {
332 let interface = self.interfaces.get(name).ok_or_else(|| {
333 format_err!("interface '{}' does not exist.", name)
334 })?;
335 Ok(interface)
336 }
337
338 pub fn lookup_mut(&mut self, name: &str) -> Result<&mut Interface, Error> {
339 let interface = self.interfaces.get_mut(name).ok_or_else(|| {
340 format_err!("interface '{}' does not exist.", name)
341 })?;
342 Ok(interface)
343 }
344
345 pub fn write_config(&self, w: &mut dyn Write) -> Result<(), Error> {
346
347 let mut done = HashSet::new();
348
349 let mut last_entry_was_comment = false;
350
351 for entry in self.order.iter() {
352 match entry {
353 NetworkOrderEntry::Comment(comment) => {
354 writeln!(w, "#{}", comment)?;
355 last_entry_was_comment = true;
356 }
357 NetworkOrderEntry::Option(option) => {
358 if last_entry_was_comment { writeln!(w)?; }
359 last_entry_was_comment = false;
360 writeln!(w, "{}", option)?;
361 writeln!(w)?;
362 }
363 NetworkOrderEntry::Iface(name) => {
364 let interface = match self.interfaces.get(name) {
365 Some(interface) => interface,
366 None => continue,
367 };
368
369 if last_entry_was_comment { writeln!(w)?; }
370 last_entry_was_comment = false;
371
372 if done.contains(name) { continue; }
373 done.insert(name);
374
375 interface.write_iface(w)?;
376 }
377 }
378 }
379
380 for (name, interface) in &self.interfaces {
381 if done.contains(name) { continue; }
382 interface.write_iface(w)?;
383 }
384 Ok(())
385 }
386 }
387
388 pub const NETWORK_INTERFACES_FILENAME: &str = "/etc/network/interfaces";
389 pub const NETWORK_INTERFACES_NEW_FILENAME: &str = "/etc/network/interfaces.new";
390 pub const NETWORK_LOCKFILE: &str = "/var/lock/pve-network.lck";
391
392
393 pub fn config() -> Result<(NetworkConfig, [u8;32]), Error> {
394 let content = std::fs::read(NETWORK_INTERFACES_NEW_FILENAME)
395 .or_else(|err| {
396 if err.kind() == std::io::ErrorKind::NotFound {
397 std::fs::read(NETWORK_INTERFACES_FILENAME)
398 .or_else(|err| {
399 if err.kind() == std::io::ErrorKind::NotFound {
400 Ok(Vec::new())
401 } else {
402 bail!("unable to read '{}' - {}", NETWORK_INTERFACES_FILENAME, err);
403 }
404 })
405 } else {
406 bail!("unable to read '{}' - {}", NETWORK_INTERFACES_NEW_FILENAME, err);
407 }
408 })?;
409
410
411 let digest = openssl::sha::sha256(&content);
412
413 let existing_interfaces = get_network_interfaces()?;
414 let mut parser = NetworkParser::new(&content[..]);
415 let data = parser.parse_interfaces(Some(&existing_interfaces))?;
416
417 Ok((data, digest))
418 }
419
420 pub fn changes() -> Result<String, Error> {
421
422 if !std::path::Path::new(NETWORK_INTERFACES_NEW_FILENAME).exists() {
423 return Ok(String::new());
424 }
425
426 compute_file_diff(NETWORK_INTERFACES_FILENAME, NETWORK_INTERFACES_NEW_FILENAME)
427 }
428
429 pub fn save_config(config: &NetworkConfig) -> Result<(), Error> {
430
431 let mut raw = Vec::new();
432 config.write_config(&mut raw)?;
433
434 let mode = nix::sys::stat::Mode::from_bits_truncate(0o0644);
435 // set the correct owner/group/permissions while saving file
436 // owner(rw) = root, group(r)=root, others(r)
437 let options = CreateOptions::new()
438 .perm(mode)
439 .owner(nix::unistd::ROOT)
440 .group(nix::unistd::Gid::from_raw(0));
441
442 replace_file(NETWORK_INTERFACES_NEW_FILENAME, &raw, options)?;
443
444 Ok(())
445 }
446
447 // shell completion helper
448 pub fn complete_interface_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
449 match config() {
450 Ok((data, _digest)) => data.interfaces.keys().map(|id| id.to_string()).collect(),
451 Err(_) => return vec![],
452 }
453 }
454
455 #[cfg(test)]
456 mod test {
457
458 use anyhow::{Error};
459
460 use super::*;
461
462 #[test]
463 fn test_network_config_create_lo_1() -> Result<(), Error> {
464
465 let input = "";
466
467 let mut parser = NetworkParser::new(&input.as_bytes()[..]);
468
469 let config = parser.parse_interfaces(None)?;
470
471 let output = String::try_from(config)?;
472
473 let expected = "auto lo\niface lo inet loopback\n\n";
474 assert_eq!(output, expected);
475
476 // run again using output as input
477 let mut parser = NetworkParser::new(&output.as_bytes()[..]);
478
479 let config = parser.parse_interfaces(None)?;
480
481 let output = String::try_from(config)?;
482
483 assert_eq!(output, expected);
484
485 Ok(())
486 }
487
488 #[test]
489 fn test_network_config_create_lo_2() -> Result<(), Error> {
490
491 let input = "#c1\n\n#c2\n\niface test inet manual\n";
492
493 let mut parser = NetworkParser::new(&input.as_bytes()[..]);
494
495 let config = parser.parse_interfaces(None)?;
496
497 let output = String::try_from(config)?;
498
499 // Note: loopback should be added in front of other interfaces
500 let expected = "#c1\n#c2\n\nauto lo\niface lo inet loopback\n\niface test inet manual\n\n";
501 assert_eq!(output, expected);
502
503 Ok(())
504 }
505 }