2 use std
::collections
::{HashSet, HashMap}
;
4 use anyhow
::{Error, format_err, bail}
;
6 use proxmox
::tools
::{fs::replace_file, fs::CreateOptions}
;
17 use crate::api2
::types
::{Interface, NetworkConfigMethod, NetworkInterfaceType}
;
21 pub fn new(name
: String
) -> Self {
24 interface_type
: NetworkInterfaceType
::Unknown
,
39 bridge_vlan_aware
: None
,
44 fn set_method_v4(&mut self, method
: NetworkConfigMethod
) -> Result
<(), Error
> {
45 if self.method
.is_none() {
46 self.method
= Some(method
);
48 bail
!("inet configuration method already set.");
53 fn set_method_v6(&mut self, method
: NetworkConfigMethod
) -> Result
<(), Error
> {
54 if self.method6
.is_none() {
55 self.method6
= Some(method
);
57 bail
!("inet6 configuration method already set.");
62 fn set_cidr_v4(&mut self, address
: String
) -> Result
<(), Error
> {
63 if self.cidr
.is_none() {
64 self.cidr
= Some(address
);
66 bail
!("duplicate IPv4 address.");
71 fn set_gateway_v4(&mut self, gateway
: String
) -> Result
<(), Error
> {
72 if self.gateway
.is_none() {
73 self.gateway
= Some(gateway
);
75 bail
!("duplicate IPv4 gateway.");
80 fn set_cidr_v6(&mut self, address
: String
) -> Result
<(), Error
> {
81 if self.cidr6
.is_none() {
82 self.cidr6
= Some(address
);
84 bail
!("duplicate IPv6 address.");
89 fn set_gateway_v6(&mut self, gateway
: String
) -> Result
<(), Error
> {
90 if self.gateway6
.is_none() {
91 self.gateway6
= Some(gateway
);
93 bail
!("duplicate IPv4 gateway.");
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
);
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
);
111 self.bridge_ports
= Some(ports
);
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
);
119 self.bond_slaves
= Some(slaves
);
123 /// Write attributes not dependening on address family
124 fn write_iface_attributes(&self, w
: &mut dyn Write
) -> Result
<(), Error
> {
126 match self.interface_type
{
127 NetworkInterfaceType
::Bridge
=> {
128 if let Some(true) = self.bridge_vlan_aware
{
129 writeln
!(w
, "\tbridge-vlan-aware yes")?
;
131 if let Some(ref ports
) = self.bridge_ports
{
132 if ports
.is_empty() {
133 writeln
!(w
, "\tbridge-ports none")?
;
135 writeln
!(w
, "\tbridge-ports {}", ports
.join(" "))?
;
139 NetworkInterfaceType
::Bond
=> {
140 if let Some(ref slaves
) = self.bond_slaves
{
141 if slaves
.is_empty() {
142 writeln
!(w
, "\tbond-slaves none")?
;
144 writeln
!(w
, "\tbond-slaves {}", slaves
.join(" "))?
;
151 if let Some(mtu
) = self.mtu
{
152 writeln
!(w
, "\tmtu {}", mtu
)?
;
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
)?
;
164 if let Some(gateway
) = &self.gateway
{
165 writeln
!(w
, "\tgateway {}", gateway
)?
;
169 for option
in &self.options
{
170 writeln
!(w
, "\t{}", option
)?
;
173 if let Some(ref comments
) = self.comments
{
174 for comment
in comments
.lines() {
175 writeln
!(w
, "#{}", comment
)?
;
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
)?
;
188 if let Some(gateway
) = &self.gateway6
{
189 writeln
!(w
, "\tgateway {}", gateway
)?
;
193 for option
in &self.options6
{
194 writeln
!(w
, "\t{}", option
)?
;
197 if let Some(ref comments
) = self.comments6
{
198 for comment
in comments
.lines() {
199 writeln
!(w
, "#{}", comment
)?
;
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
217 // the rest does not matter
219 interface_type
: _interface_type
,
220 autostart
: _autostart
,
227 bridge_ports
: _bridge_ports
,
228 bridge_vlan_aware
: _bridge_vlan_aware
,
229 bond_slaves
: _bond_slaves
,
232 && comments
.is_none()
233 && comments6
.is_none()
234 && options
.is_empty()
235 && options6
.is_empty()
240 fn write_iface(&self, w
: &mut dyn Write
) -> Result
<(), Error
> {
242 fn method_to_str(method
: NetworkConfigMethod
) -> &'
static str {
244 NetworkConfigMethod
::Static
=> "static",
245 NetworkConfigMethod
::Loopback
=> "loopback",
246 NetworkConfigMethod
::Manual
=> "manual",
247 NetworkConfigMethod
::DHCP
=> "dhcp",
251 if self.method
.is_none() && self.method6
.is_none() { return Ok(()); }
254 writeln
!(w
, "auto {}", self.name
)?
;
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
)?
;
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
)?
;
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; }
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
)?
;
296 enum NetworkOrderEntry
{
303 pub struct NetworkConfig
{
304 pub interfaces
: HashMap
<String
, Interface
>,
305 order
: Vec
<NetworkOrderEntry
>,
308 use std
::convert
::TryFrom
;
310 impl TryFrom
<NetworkConfig
> for String
{
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
)?
;
324 pub fn new() -> Self {
326 interfaces
: HashMap
::new(),
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
)
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
)
345 pub fn write_config(&self, w
: &mut dyn Write
) -> Result
<(), Error
> {
347 let mut done
= HashSet
::new();
349 let mut last_entry_was_comment
= false;
351 for entry
in self.order
.iter() {
353 NetworkOrderEntry
::Comment(comment
) => {
354 writeln
!(w
, "#{}", comment
)?
;
355 last_entry_was_comment
= true;
357 NetworkOrderEntry
::Option(option
) => {
358 if last_entry_was_comment { writeln!(w)?; }
359 last_entry_was_comment
= false;
360 writeln
!(w
, "{}", option
)?
;
363 NetworkOrderEntry
::Iface(name
) => {
364 let interface
= match self.interfaces
.get(name
) {
365 Some(interface
) => interface
,
369 if last_entry_was_comment { writeln!(w)?; }
370 last_entry_was_comment
= false;
372 if done
.contains(name
) { continue; }
375 interface
.write_iface(w
)?
;
380 for (name
, interface
) in &self.interfaces
{
381 if done
.contains(name
) { continue; }
382 interface
.write_iface(w
)?
;
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";
393 pub fn config() -> Result
<(NetworkConfig
, [u8;32]), Error
> {
394 let content
= std
::fs
::read(NETWORK_INTERFACES_NEW_FILENAME
)
396 if err
.kind() == std
::io
::ErrorKind
::NotFound
{
397 std
::fs
::read(NETWORK_INTERFACES_FILENAME
)
399 if err
.kind() == std
::io
::ErrorKind
::NotFound
{
402 bail
!("unable to read '{}' - {}", NETWORK_INTERFACES_FILENAME
, err
);
406 bail
!("unable to read '{}' - {}", NETWORK_INTERFACES_NEW_FILENAME
, err
);
411 let digest
= openssl
::sha
::sha256(&content
);
413 let existing_interfaces
= get_network_interfaces()?
;
414 let mut parser
= NetworkParser
::new(&content
[..]);
415 let data
= parser
.parse_interfaces(Some(&existing_interfaces
))?
;
420 pub fn changes() -> Result
<String
, Error
> {
422 if !std
::path
::Path
::new(NETWORK_INTERFACES_NEW_FILENAME
).exists() {
423 return Ok(String
::new());
426 compute_file_diff(NETWORK_INTERFACES_FILENAME
, NETWORK_INTERFACES_NEW_FILENAME
)
429 pub fn save_config(config
: &NetworkConfig
) -> Result
<(), Error
> {
431 let mut raw
= Vec
::new();
432 config
.write_config(&mut raw
)?
;
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()
439 .owner(nix
::unistd
::ROOT
)
440 .group(nix
::unistd
::Gid
::from_raw(0));
442 replace_file(NETWORK_INTERFACES_NEW_FILENAME
, &raw
, options
)?
;
447 // shell completion helper
448 pub fn complete_interface_name(_arg
: &str, _param
: &HashMap
<String
, String
>) -> Vec
<String
> {
450 Ok((data
, _digest
)) => data
.interfaces
.keys().map(|id
| id
.to_string()).collect(),
451 Err(_
) => return vec
![],
463 fn test_network_config_create_lo_1() -> Result
<(), Error
> {
467 let mut parser
= NetworkParser
::new(&input
.as_bytes()[..]);
469 let config
= parser
.parse_interfaces(None
)?
;
471 let output
= String
::try_from(config
)?
;
473 let expected
= "auto lo\niface lo inet loopback\n\n";
474 assert_eq
!(output
, expected
);
476 // run again using output as input
477 let mut parser
= NetworkParser
::new(&output
.as_bytes()[..]);
479 let config
= parser
.parse_interfaces(None
)?
;
481 let output
= String
::try_from(config
)?
;
483 assert_eq
!(output
, expected
);
489 fn test_network_config_create_lo_2() -> Result
<(), Error
> {
491 let input
= "#c1\n\n#c2\n\niface test inet manual\n";
493 let mut parser
= NetworkParser
::new(&input
.as_bytes()[..]);
495 let config
= parser
.parse_interfaces(None
)?
;
497 let output
= String
::try_from(config
)?
;
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
);