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
,
33 options_v4
: Vec
::new(),
34 options_v6
: Vec
::new(),
43 fn set_method_v4(&mut self, method
: NetworkConfigMethod
) -> Result
<(), Error
> {
44 if self.method_v4
.is_none() {
45 self.method_v4
= Some(method
);
47 bail
!("inet configuration method already set.");
52 fn set_method_v6(&mut self, method
: NetworkConfigMethod
) -> Result
<(), Error
> {
53 if self.method_v6
.is_none() {
54 self.method_v6
= Some(method
);
56 bail
!("inet6 configuration method already set.");
61 fn set_cidr_v4(&mut self, address
: String
) -> Result
<(), Error
> {
62 if self.cidr_v4
.is_none() {
63 self.cidr_v4
= Some(address
);
65 bail
!("duplicate IPv4 address.");
70 fn set_gateway_v4(&mut self, gateway
: String
) -> Result
<(), Error
> {
71 if self.gateway_v4
.is_none() {
72 self.gateway_v4
= Some(gateway
);
74 bail
!("duplicate IPv4 gateway.");
79 fn set_cidr_v6(&mut self, address
: String
) -> Result
<(), Error
> {
80 if self.cidr_v6
.is_none() {
81 self.cidr_v6
= Some(address
);
83 bail
!("duplicate IPv6 address.");
88 fn set_gateway_v6(&mut self, gateway
: String
) -> Result
<(), Error
> {
89 if self.gateway_v6
.is_none() {
90 self.gateway_v6
= Some(gateway
);
92 bail
!("duplicate IPv4 gateway.");
97 fn set_interface_type(&mut self, interface_type
: NetworkInterfaceType
) -> Result
<(), Error
> {
98 if self.interface_type
== NetworkInterfaceType
::Unknown
{
99 self.interface_type
= interface_type
;
100 } else if self.interface_type
!= interface_type
{
101 bail
!("interface type already defined - cannot change from {:?} to {:?}", self.interface_type
, interface_type
);
106 pub(crate) fn set_bridge_ports(&mut self, ports
: Vec
<String
>) -> Result
<(), Error
> {
107 if self.interface_type
!= NetworkInterfaceType
::Bridge
{
108 bail
!("interface '{}' is no bridge (type is {:?})", self.name
, self.interface_type
);
110 self.bridge_ports
= Some(ports
);
114 pub(crate) fn set_bond_slaves(&mut self, slaves
: Vec
<String
>) -> Result
<(), Error
> {
115 if self.interface_type
!= NetworkInterfaceType
::Bond
{
116 bail
!("interface '{}' is no bond (type is {:?})", self.name
, self.interface_type
);
118 self.bond_slaves
= Some(slaves
);
122 /// Write attributes not dependening on address family
123 fn write_iface_attributes(&self, w
: &mut dyn Write
) -> Result
<(), Error
> {
125 match self.interface_type
{
126 NetworkInterfaceType
::Bridge
=> {
127 if let Some(ref ports
) = self.bridge_ports
{
128 if ports
.is_empty() {
129 writeln
!(w
, " bridge-ports none")?
;
131 writeln
!(w
, " bridge-ports {}", ports
.join(" "))?
;
135 NetworkInterfaceType
::Bond
=> {
136 if let Some(ref slaves
) = self.bond_slaves
{
137 if slaves
.is_empty() {
138 writeln
!(w
, " bond-slaves none")?
;
140 writeln
!(w
, " bond-slaves {}", slaves
.join(" "))?
;
147 if let Some(mtu
) = self.mtu
{
148 writeln
!(w
, " mtu {}", mtu
)?
;
154 /// Write attributes dependening on address family inet (IPv4)
155 fn write_iface_attributes_v4(&self, w
: &mut dyn Write
, method
: NetworkConfigMethod
) -> Result
<(), Error
> {
156 if method
== NetworkConfigMethod
::Static
{
157 if let Some(address
) = &self.cidr_v4
{
158 writeln
!(w
, " address {}", address
)?
;
160 if let Some(gateway
) = &self.gateway_v4
{
161 writeln
!(w
, " gateway {}", gateway
)?
;
165 for option
in &self.options_v4
{
166 writeln
!(w
, " {}", option
)?
;
169 if let Some(ref comments
) = self.comments_v4
{
170 for comment
in comments
.lines() {
171 writeln
!(w
, "#{}", comment
)?
;
178 /// Write attributes dependening on address family inet6 (IPv6)
179 fn write_iface_attributes_v6(&self, w
: &mut dyn Write
, method
: NetworkConfigMethod
) -> Result
<(), Error
> {
180 if method
== NetworkConfigMethod
::Static
{
181 if let Some(address
) = &self.cidr_v6
{
182 writeln
!(w
, " address {}", address
)?
;
184 if let Some(gateway
) = &self.gateway_v6
{
185 writeln
!(w
, " gateway {}", gateway
)?
;
189 for option
in &self.options_v6
{
190 writeln
!(w
, " {}", option
)?
;
193 if let Some(ref comments
) = self.comments_v6
{
194 for comment
in comments
.lines() {
195 writeln
!(w
, "#{}", comment
)?
;
202 /// Return whether we can write a single entry for inet and inet6
203 fn combine_entry(&self) -> bool
{
204 // Note: use match to make sure we considered all values at compile time
213 // the rest does not matter
215 interface_type
: _interface_type
,
220 gateway_v4
: _gateway_v4
,
221 gateway_v6
: _gateway_v6
,
223 bridge_ports
: _bridge_ports
,
224 bond_slaves
: _bond_slaves
,
226 method_v4
== method_v6
227 && comments_v4
.is_none()
228 && comments_v6
.is_none()
229 && options_v4
.is_empty()
230 && options_v6
.is_empty()
235 fn write_iface(&self, w
: &mut dyn Write
) -> Result
<(), Error
> {
237 fn method_to_str(method
: NetworkConfigMethod
) -> &'
static str {
239 NetworkConfigMethod
::Static
=> "static",
240 NetworkConfigMethod
::Loopback
=> "loopback",
241 NetworkConfigMethod
::Manual
=> "manual",
242 NetworkConfigMethod
::DHCP
=> "dhcp",
246 if self.method_v4
.is_none() && self.method_v6
.is_none() { return Ok(()); }
249 writeln
!(w
, "auto {}", self.name
)?
;
252 if self.combine_entry() {
253 if let Some(method
) = self.method_v4
{
254 writeln
!(w
, "iface {} {}", self.name
, method_to_str(method
))?
;
255 self.write_iface_attributes_v4(w
, method
)?
;
256 self.write_iface_attributes_v6(w
, method
)?
;
257 self.write_iface_attributes(w
)?
;
261 if let Some(method
) = self.method_v4
{
262 writeln
!(w
, "iface {} inet {}", self.name
, method_to_str(method
))?
;
263 self.write_iface_attributes_v4(w
, method
)?
;
264 self.write_iface_attributes(w
)?
;
267 if let Some(method
) = self.method_v6
{
268 writeln
!(w
, "iface {} inet6 {}", self.name
, method_to_str(method
))?
;
269 self.write_iface_attributes_v6(w
, method
)?
;
270 if self.method_v4
.is_none() { // only write common attributes once
271 self.write_iface_attributes(w
)?
;
281 enum NetworkOrderEntry
{
288 pub struct NetworkConfig
{
289 pub interfaces
: HashMap
<String
, Interface
>,
290 order
: Vec
<NetworkOrderEntry
>,
295 pub fn new() -> Self {
297 interfaces
: HashMap
::new(),
302 pub fn lookup(&self, name
: &str) -> Result
<&Interface
, Error
> {
303 let interface
= self.interfaces
.get(name
).ok_or_else(|| {
304 format_err
!("interface '{}' does not exist.", name
)
309 pub fn lookup_mut(&mut self, name
: &str) -> Result
<&mut Interface
, Error
> {
310 let interface
= self.interfaces
.get_mut(name
).ok_or_else(|| {
311 format_err
!("interface '{}' does not exist.", name
)
316 pub fn write_config(&self, w
: &mut dyn Write
) -> Result
<(), Error
> {
318 let mut done
= HashSet
::new();
320 let mut last_entry_was_comment
= false;
322 for entry
in self.order
.iter() {
324 NetworkOrderEntry
::Comment(comment
) => {
325 writeln
!(w
, "#{}", comment
)?
;
326 last_entry_was_comment
= true;
328 NetworkOrderEntry
::Option(option
) => {
329 if last_entry_was_comment { writeln!(w)?; }
330 last_entry_was_comment
= false;
331 writeln
!(w
, "{}", option
)?
;
334 NetworkOrderEntry
::Iface(name
) => {
335 let interface
= match self.interfaces
.get(name
) {
336 Some(interface
) => interface
,
340 if last_entry_was_comment { writeln!(w)?; }
341 last_entry_was_comment
= false;
343 if done
.contains(name
) { continue; }
346 interface
.write_iface(w
)?
;
351 for (name
, interface
) in &self.interfaces
{
352 if done
.contains(name
) { continue; }
353 interface
.write_iface(w
)?
;
359 pub const NETWORK_INTERFACES_FILENAME
: &str = "/etc/network/interfaces";
360 pub const NETWORK_INTERFACES_NEW_FILENAME
: &str = "/etc/network/interfaces.new";
361 pub const NETWORK_LOCKFILE
: &str = "/var/lock/pve-network.lck";
364 pub fn config() -> Result
<(NetworkConfig
, [u8;32]), Error
> {
365 let content
= std
::fs
::read(NETWORK_INTERFACES_NEW_FILENAME
)
367 if err
.kind() == std
::io
::ErrorKind
::NotFound
{
368 std
::fs
::read(NETWORK_INTERFACES_FILENAME
)
370 if err
.kind() == std
::io
::ErrorKind
::NotFound
{
373 bail
!("unable to read '{}' - {}", NETWORK_INTERFACES_FILENAME
, err
);
377 bail
!("unable to read '{}' - {}", NETWORK_INTERFACES_NEW_FILENAME
, err
);
382 let digest
= openssl
::sha
::sha256(&content
);
384 let mut parser
= NetworkParser
::new(&content
[..]);
385 let data
= parser
.parse_interfaces()?
;
390 pub fn save_config(config
: &NetworkConfig
) -> Result
<(), Error
> {
392 let mut raw
= Vec
::new();
393 config
.write_config(&mut raw
)?
;
395 let mode
= nix
::sys
::stat
::Mode
::from_bits_truncate(0o0644);
396 // set the correct owner/group/permissions while saving file
397 // owner(rw) = root, group(r)=root, others(r)
398 let options
= CreateOptions
::new()
400 .owner(nix
::unistd
::ROOT
)
401 .group(nix
::unistd
::Gid
::from_raw(0));
403 replace_file(NETWORK_INTERFACES_NEW_FILENAME
, &raw
, options
)?
;
408 // shell completion helper
409 pub fn complete_interface_name(_arg
: &str, _param
: &HashMap
<String
, String
>) -> Vec
<String
> {
411 Ok((data
, _digest
)) => data
.interfaces
.keys().map(|id
| id
.to_string()).collect(),
412 Err(_
) => return vec
![],