2 use std
::collections
::{HashSet, HashMap, BTreeMap}
;
4 use anyhow
::{Error, format_err, bail}
;
5 use serde
::de
::{value, IntoDeserializer, Deserialize}
;
6 use lazy_static
::lazy_static
;
9 use proxmox
::tools
::{fs::replace_file, fs::CreateOptions}
;
20 use crate::api2
::types
::{Interface, NetworkConfigMethod, NetworkInterfaceType, LinuxBondMode}
;
23 static ref PHYSICAL_NIC_REGEX
: Regex
= Regex
::new(r
"^(?:eth\d+|en[^:.]+|ib\d+)$").unwrap();
26 pub fn is_physical_nic(iface
: &str) -> bool
{
27 PHYSICAL_NIC_REGEX
.is_match(iface
)
30 pub 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
))
35 pub fn bond_mode_to_str(mode
: LinuxBondMode
) -> &'
static str {
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",
49 pub fn new(name
: String
) -> Self {
52 interface_type
: NetworkInterfaceType
::Unknown
,
67 bridge_vlan_aware
: None
,
73 fn set_method_v4(&mut self, method
: NetworkConfigMethod
) -> Result
<(), Error
> {
74 if self.method
.is_none() {
75 self.method
= Some(method
);
77 bail
!("inet configuration method already set.");
82 fn set_method_v6(&mut self, method
: NetworkConfigMethod
) -> Result
<(), Error
> {
83 if self.method6
.is_none() {
84 self.method6
= Some(method
);
86 bail
!("inet6 configuration method already set.");
91 fn set_cidr_v4(&mut self, address
: String
) -> Result
<(), Error
> {
92 if self.cidr
.is_none() {
93 self.cidr
= Some(address
);
95 bail
!("duplicate IPv4 address.");
100 fn set_gateway_v4(&mut self, gateway
: String
) -> Result
<(), Error
> {
101 if self.gateway
.is_none() {
102 self.gateway
= Some(gateway
);
104 bail
!("duplicate IPv4 gateway.");
109 fn set_cidr_v6(&mut self, address
: String
) -> Result
<(), Error
> {
110 if self.cidr6
.is_none() {
111 self.cidr6
= Some(address
);
113 bail
!("duplicate IPv6 address.");
118 fn set_gateway_v6(&mut self, gateway
: String
) -> Result
<(), Error
> {
119 if self.gateway6
.is_none() {
120 self.gateway6
= Some(gateway
);
122 bail
!("duplicate IPv4 gateway.");
127 fn set_interface_type(&mut self, interface_type
: NetworkInterfaceType
) -> Result
<(), Error
> {
128 if self.interface_type
== NetworkInterfaceType
::Unknown
{
129 self.interface_type
= interface_type
;
130 } else if self.interface_type
!= interface_type
{
131 bail
!("interface type already defined - cannot change from {:?} to {:?}", self.interface_type
, interface_type
);
136 pub(crate) fn set_bridge_ports(&mut self, ports
: Vec
<String
>) -> Result
<(), Error
> {
137 if self.interface_type
!= NetworkInterfaceType
::Bridge
{
138 bail
!("interface '{}' is no bridge (type is {:?})", self.name
, self.interface_type
);
140 self.bridge_ports
= Some(ports
);
144 pub(crate) fn set_bond_slaves(&mut self, slaves
: Vec
<String
>) -> Result
<(), Error
> {
145 if self.interface_type
!= NetworkInterfaceType
::Bond
{
146 bail
!("interface '{}' is no bond (type is {:?})", self.name
, self.interface_type
);
148 self.slaves
= Some(slaves
);
152 /// Write attributes not dependening on address family
153 fn write_iface_attributes(&self, w
: &mut dyn Write
) -> Result
<(), Error
> {
155 static EMPTY_LIST
: Vec
<String
> = Vec
::new();
157 match self.interface_type
{
158 NetworkInterfaceType
::Bridge
=> {
159 if let Some(true) = self.bridge_vlan_aware
{
160 writeln
!(w
, "\tbridge-vlan-aware yes")?
;
162 let ports
= self.bridge_ports
.as_ref().unwrap_or(&EMPTY_LIST
);
163 if ports
.is_empty() {
164 writeln
!(w
, "\tbridge-ports none")?
;
166 writeln
!(w
, "\tbridge-ports {}", ports
.join(" "))?
;
169 NetworkInterfaceType
::Bond
=> {
170 let mode
= self.bond_mode
.unwrap_or(LinuxBondMode
::balance_rr
);
171 writeln
!(w
, "\tbond-mode {}", bond_mode_to_str(mode
))?
;
173 let slaves
= self.slaves
.as_ref().unwrap_or(&EMPTY_LIST
);
174 if slaves
.is_empty() {
175 writeln
!(w
, "\tbond-slaves none")?
;
177 writeln
!(w
, "\tbond-slaves {}", slaves
.join(" "))?
;
183 if let Some(mtu
) = self.mtu
{
184 writeln
!(w
, "\tmtu {}", mtu
)?
;
190 /// Write attributes dependening on address family inet (IPv4)
191 fn write_iface_attributes_v4(&self, w
: &mut dyn Write
, method
: NetworkConfigMethod
) -> Result
<(), Error
> {
192 if method
== NetworkConfigMethod
::Static
{
193 if let Some(address
) = &self.cidr
{
194 writeln
!(w
, "\taddress {}", address
)?
;
196 if let Some(gateway
) = &self.gateway
{
197 writeln
!(w
, "\tgateway {}", gateway
)?
;
201 for option
in &self.options
{
202 writeln
!(w
, "\t{}", option
)?
;
205 if let Some(ref comments
) = self.comments
{
206 for comment
in comments
.lines() {
207 writeln
!(w
, "#{}", comment
)?
;
214 /// Write attributes dependening on address family inet6 (IPv6)
215 fn write_iface_attributes_v6(&self, w
: &mut dyn Write
, method
: NetworkConfigMethod
) -> Result
<(), Error
> {
216 if method
== NetworkConfigMethod
::Static
{
217 if let Some(address
) = &self.cidr6
{
218 writeln
!(w
, "\taddress {}", address
)?
;
220 if let Some(gateway
) = &self.gateway6
{
221 writeln
!(w
, "\tgateway {}", gateway
)?
;
225 for option
in &self.options6
{
226 writeln
!(w
, "\t{}", option
)?
;
229 if let Some(ref comments
) = self.comments6
{
230 for comment
in comments
.lines() {
231 writeln
!(w
, "#{}", comment
)?
;
238 fn write_iface(&self, w
: &mut dyn Write
) -> Result
<(), Error
> {
240 fn method_to_str(method
: NetworkConfigMethod
) -> &'
static str {
242 NetworkConfigMethod
::Static
=> "static",
243 NetworkConfigMethod
::Loopback
=> "loopback",
244 NetworkConfigMethod
::Manual
=> "manual",
245 NetworkConfigMethod
::DHCP
=> "dhcp",
249 if self.method
.is_none() && self.method6
.is_none() { return Ok(()); }
252 writeln
!(w
, "auto {}", self.name
)?
;
255 if let Some(method
) = self.method
{
256 writeln
!(w
, "iface {} inet {}", self.name
, method_to_str(method
))?
;
257 self.write_iface_attributes_v4(w
, method
)?
;
258 self.write_iface_attributes(w
)?
;
262 if let Some(method6
) = self.method6
{
263 let mut skip_v6
= false; // avoid empty inet6 manual entry
264 if self.method
.is_some() && method6
== NetworkConfigMethod
::Manual
{
265 if self.comments6
.is_none() && self.options6
.is_empty() { skip_v6 = true; }
269 writeln
!(w
, "iface {} inet6 {}", self.name
, method_to_str(method6
))?
;
270 self.write_iface_attributes_v6(w
, method6
)?
;
271 if self.method
.is_none() { // only write common attributes once
272 self.write_iface_attributes(w
)?
;
283 enum NetworkOrderEntry
{
290 pub struct NetworkConfig
{
291 pub interfaces
: BTreeMap
<String
, Interface
>,
292 order
: Vec
<NetworkOrderEntry
>,
295 use std
::convert
::TryFrom
;
297 impl TryFrom
<NetworkConfig
> for String
{
301 fn try_from(config
: NetworkConfig
) -> Result
<Self, Self::Error
> {
302 let mut output
= Vec
::new();
303 config
.write_config(&mut output
)?
;
304 let res
= String
::from_utf8(output
)?
;
311 pub fn new() -> Self {
313 interfaces
: BTreeMap
::new(),
318 pub fn lookup(&self, name
: &str) -> Result
<&Interface
, Error
> {
319 let interface
= self.interfaces
.get(name
).ok_or_else(|| {
320 format_err
!("interface '{}' does not exist.", name
)
325 pub fn lookup_mut(&mut self, name
: &str) -> Result
<&mut Interface
, Error
> {
326 let interface
= self.interfaces
.get_mut(name
).ok_or_else(|| {
327 format_err
!("interface '{}' does not exist.", name
)
332 /// Check if ports are used only once
333 pub fn check_port_usage(&self) -> Result
<(), Error
> {
334 let mut used_ports
= HashMap
::new();
335 let mut check_port_usage
= |iface
, ports
: &Vec
<String
>| {
336 for port
in ports
.iter() {
337 if let Some(prev_iface
) = used_ports
.get(port
) {
338 bail
!("iface '{}' port '{}' is already used on interface '{}'",
339 iface
, port
, prev_iface
);
341 used_ports
.insert(port
.to_string(), iface
);
346 for (iface
, interface
) in self.interfaces
.iter() {
347 if let Some(ports
) = &interface
.bridge_ports { check_port_usage(iface, ports)?; }
348 if let Some(slaves
) = &interface
.slaves { check_port_usage(iface, slaves)?; }
353 /// Check if child mtu is less or equal than parent mtu
354 pub fn check_mtu(&self, parent_name
: &str, child_name
: &str) -> Result
<(), Error
> {
356 let parent
= self.interfaces
.get(parent_name
)
357 .ok_or(format_err
!("check_mtu - missing parent interface '{}'", parent_name
))?
;
358 let child
= self.interfaces
.get(child_name
)
359 .ok_or(format_err
!("check_mtu - missing child interface '{}'", child_name
))?
;
361 let child_mtu
= match child
.mtu
{
363 None
=> return Ok(()),
366 let parent_mtu
= match parent
.mtu
{
369 if parent
.interface_type
== NetworkInterfaceType
::Bond
{
377 if parent_mtu
< child_mtu
{
378 bail
!("interface '{}' - mtu {} is lower than '{}' - mtu {}\n",
379 parent_name
, parent_mtu
, child_name
, child_mtu
);
385 /// Check if bond slaves exists
386 pub fn check_bond_slaves(&self) -> Result
<(), Error
> {
387 for (iface
, interface
) in self.interfaces
.iter() {
388 if let Some(slaves
) = &interface
.slaves
{
389 for slave
in slaves
.iter() {
390 match self.interfaces
.get(slave
) {
392 if entry
.interface_type
!= NetworkInterfaceType
::Eth
{
393 bail
!("bond '{}' - wrong interface type on slave '{}' ({:?} != {:?})",
394 iface
, slave
, entry
.interface_type
, NetworkInterfaceType
::Eth
);
398 bail
!("bond '{}' - unable to find slave '{}'", iface
, slave
);
401 self.check_mtu(iface
, slave
)?
;
408 /// Check if bridge ports exists
409 pub fn check_bridge_ports(&self) -> Result
<(), Error
> {
411 static ref VLAN_INTERFACE_REGEX
: Regex
= Regex
::new(r
"^(\S+)\.(\d+)$").unwrap();
414 for (iface
, interface
) in self.interfaces
.iter() {
415 if let Some(ports
) = &interface
.bridge_ports
{
416 for port
in ports
.iter() {
417 let captures
= VLAN_INTERFACE_REGEX
.captures(port
);
418 let port
= if let Some(ref caps
) = captures { &caps[1] }
else { port.as_str() }
;
419 if !self.interfaces
.contains_key(port
) {
420 bail
!("bridge '{}' - unable to find port '{}'", iface
, port
);
422 self.check_mtu(iface
, port
)?
;
429 pub fn write_config(&self, w
: &mut dyn Write
) -> Result
<(), Error
> {
431 self.check_port_usage()?
;
432 self.check_bond_slaves()?
;
433 self.check_bridge_ports()?
;
435 let mut done
= HashSet
::new();
437 let mut last_entry_was_comment
= false;
439 for entry
in self.order
.iter() {
441 NetworkOrderEntry
::Comment(comment
) => {
442 writeln
!(w
, "#{}", comment
)?
;
443 last_entry_was_comment
= true;
445 NetworkOrderEntry
::Option(option
) => {
446 if last_entry_was_comment { writeln!(w)?; }
447 last_entry_was_comment
= false;
448 writeln
!(w
, "{}", option
)?
;
451 NetworkOrderEntry
::Iface(name
) => {
452 let interface
= match self.interfaces
.get(name
) {
453 Some(interface
) => interface
,
457 if last_entry_was_comment { writeln!(w)?; }
458 last_entry_was_comment
= false;
460 if done
.contains(name
) { continue; }
463 interface
.write_iface(w
)?
;
468 for (name
, interface
) in &self.interfaces
{
469 if done
.contains(name
) { continue; }
470 interface
.write_iface(w
)?
;
476 pub const NETWORK_INTERFACES_FILENAME
: &str = "/etc/network/interfaces";
477 pub const NETWORK_INTERFACES_NEW_FILENAME
: &str = "/etc/network/interfaces.new";
478 pub const NETWORK_LOCKFILE
: &str = "/var/lock/pve-network.lck";
480 pub fn config() -> Result
<(NetworkConfig
, [u8;32]), Error
> {
482 let content
= match proxmox
::tools
::fs
::file_get_optional_contents(NETWORK_INTERFACES_NEW_FILENAME
)?
{
483 Some(content
) => content
,
485 let content
= proxmox
::tools
::fs
::file_get_optional_contents(NETWORK_INTERFACES_FILENAME
)?
;
486 content
.unwrap_or(Vec
::new())
490 let digest
= openssl
::sha
::sha256(&content
);
492 let existing_interfaces
= get_network_interfaces()?
;
493 let mut parser
= NetworkParser
::new(&content
[..]);
494 let data
= parser
.parse_interfaces(Some(&existing_interfaces
))?
;
499 pub fn changes() -> Result
<String
, Error
> {
501 if !std
::path
::Path
::new(NETWORK_INTERFACES_NEW_FILENAME
).exists() {
502 return Ok(String
::new());
505 compute_file_diff(NETWORK_INTERFACES_FILENAME
, NETWORK_INTERFACES_NEW_FILENAME
)
508 pub fn save_config(config
: &NetworkConfig
) -> Result
<(), Error
> {
510 let mut raw
= Vec
::new();
511 config
.write_config(&mut raw
)?
;
513 let mode
= nix
::sys
::stat
::Mode
::from_bits_truncate(0o0644);
514 // set the correct owner/group/permissions while saving file
515 // owner(rw) = root, group(r)=root, others(r)
516 let options
= CreateOptions
::new()
518 .owner(nix
::unistd
::ROOT
)
519 .group(nix
::unistd
::Gid
::from_raw(0));
521 replace_file(NETWORK_INTERFACES_NEW_FILENAME
, &raw
, options
)?
;
526 // shell completion helper
527 pub fn complete_interface_name(_arg
: &str, _param
: &HashMap
<String
, String
>) -> Vec
<String
> {
529 Ok((data
, _digest
)) => data
.interfaces
.keys().map(|id
| id
.to_string()).collect(),
530 Err(_
) => return vec
![],
535 pub fn complete_port_list(arg
: &str, _param
: &HashMap
<String
, String
>) -> Vec
<String
> {
536 let mut ports
= Vec
::new();
538 Ok((data
, _digest
)) => {
539 for (iface
, interface
) in data
.interfaces
.iter() {
540 if interface
.interface_type
== NetworkInterfaceType
::Eth
{
541 ports
.push(iface
.to_string());
545 Err(_
) => return vec
![],
548 let arg
= arg
.clone().trim();
549 let prefix
= if let Some(idx
) = arg
.rfind(",") { &arg[..idx+1] }
else { "" }
;
550 ports
.iter().map(|port
| format
!("{}{}", prefix
, port
)).collect()
561 fn test_network_config_create_lo_1() -> Result
<(), Error
> {
565 let mut parser
= NetworkParser
::new(&input
.as_bytes()[..]);
567 let config
= parser
.parse_interfaces(None
)?
;
569 let output
= String
::try_from(config
)?
;
571 let expected
= "auto lo\niface lo inet loopback\n\n";
572 assert_eq
!(output
, expected
);
574 // run again using output as input
575 let mut parser
= NetworkParser
::new(&output
.as_bytes()[..]);
577 let config
= parser
.parse_interfaces(None
)?
;
579 let output
= String
::try_from(config
)?
;
581 assert_eq
!(output
, expected
);
587 fn test_network_config_create_lo_2() -> Result
<(), Error
> {
589 let input
= "#c1\n\n#c2\n\niface test inet manual\n";
591 let mut parser
= NetworkParser
::new(&input
.as_bytes()[..]);
593 let config
= parser
.parse_interfaces(None
)?
;
595 let output
= String
::try_from(config
)?
;
597 // Note: loopback should be added in front of other interfaces
598 let expected
= "#c1\n#c2\n\nauto lo\niface lo inet loopback\n\niface test inet manual\n\n";
599 assert_eq
!(output
, expected
);