1 use anyhow
::{Error, bail}
;
2 use serde_json
::{Value, to_value}
;
3 use ::serde
::{Deserialize, Serialize}
;
5 use proxmox
::api
::{api, ApiMethod, Router, RpcEnvironment, Permission}
;
6 use proxmox
::api
::schema
::parse_property_string
;
8 use crate::config
::network
::{self, NetworkConfig}
;
9 use crate::config
::acl
::{PRIV_SYS_AUDIT, PRIV_SYS_MODIFY}
;
10 use crate::api2
::types
::*;
11 use crate::server
::{WorkerTask}
;
12 use crate::backup
::open_backup_lockfile
;
14 fn split_interface_list(list
: &str) -> Result
<Vec
<String
>, Error
> {
15 let value
= parse_property_string(&list
, &NETWORK_INTERFACE_ARRAY_SCHEMA
)?
;
16 Ok(value
.as_array().unwrap().iter().map(|v
| v
.as_str().unwrap().to_string()).collect())
19 fn check_duplicate_gateway_v4(config
: &NetworkConfig
, iface
: &str) -> Result
<(), Error
> {
21 let current_gateway_v4
= config
.interfaces
.iter()
22 .find(|(_
, interface
)| interface
.gateway
.is_some())
23 .map(|(name
, _
)| name
.to_string());
25 if let Some(current_gateway_v4
) = current_gateway_v4
{
26 if current_gateway_v4
!= iface
{
27 bail
!("Default IPv4 gateway already exists on interface '{}'", current_gateway_v4
);
33 fn check_duplicate_gateway_v6(config
: &NetworkConfig
, iface
: &str) -> Result
<(), Error
> {
35 let current_gateway_v6
= config
.interfaces
.iter()
36 .find(|(_
, interface
)| interface
.gateway6
.is_some())
37 .map(|(name
, _
)| name
.to_string());
39 if let Some(current_gateway_v6
) = current_gateway_v6
{
40 if current_gateway_v6
!= iface
{
41 bail
!("Default IPv6 gateway already exists on interface '{}'", current_gateway_v6
);
56 description
: "List network devices (with config digest).",
63 permission
: &Permission
::Privilege(&["system", "network", "interfaces"], PRIV_SYS_AUDIT
, false),
66 /// List all datastores
67 pub fn list_network_devices(
70 mut rpcenv
: &mut dyn RpcEnvironment
,
71 ) -> Result
<Value
, Error
> {
73 let (config
, digest
) = network
::config()?
;
74 let digest
= proxmox
::tools
::digest_to_hex(&digest
);
76 let mut list
= Vec
::new();
78 for (iface
, interface
) in config
.interfaces
.iter() {
79 if iface
== "lo" { continue; }
// do not list lo
80 let mut item
: Value
= to_value(interface
)?
;
81 item
["digest"] = digest
.clone().into();
82 item
["iface"] = iface
.to_string().into();
86 let diff
= network
::changes()?
;
88 rpcenv
["changes"] = diff
.into();
101 schema
: NETWORK_INTERFACE_NAME_SCHEMA
,
105 returns
: { type: Interface }
,
107 permission
: &Permission
::Privilege(&["system", "network", "interfaces", "{name}"], PRIV_SYS_AUDIT
, false),
110 /// Read a network interface configuration.
111 pub fn read_interface(iface
: String
) -> Result
<Value
, Error
> {
113 let (config
, digest
) = network
::config()?
;
115 let interface
= config
.lookup(&iface
)?
;
117 let mut data
: Value
= to_value(interface
)?
;
118 data
["digest"] = proxmox
::tools
::digest_to_hex(&digest
).into();
132 schema
: NETWORK_INTERFACE_NAME_SCHEMA
,
135 type: NetworkInterfaceType
,
139 description
: "Autostart interface.",
144 type: NetworkConfigMethod
,
148 type: NetworkConfigMethod
,
152 description
: "Comments (inet, may span multiple lines)",
157 description
: "Comments (inet5, may span multiple lines)",
162 schema
: CIDR_V4_SCHEMA
,
166 schema
: CIDR_V6_SCHEMA
,
170 schema
: IP_V4_SCHEMA
,
174 schema
: IP_V6_SCHEMA
,
178 description
: "Maximum Transmission Unit.",
185 schema
: NETWORK_INTERFACE_LIST_SCHEMA
,
189 description
: "Enable bridge vlan support.",
198 schema
: NETWORK_INTERFACE_NAME_SCHEMA
,
201 bond_xmit_hash_policy
: {
202 type: BondXmitHashPolicy
,
206 schema
: NETWORK_INTERFACE_LIST_SCHEMA
,
212 permission
: &Permission
::Privilege(&["system", "network", "interfaces", "{iface}"], PRIV_SYS_MODIFY
, false),
215 /// Create network interface configuration.
216 #[allow(clippy::too_many_arguments)]
217 pub fn create_interface(
219 autostart
: Option
<bool
>,
220 method
: Option
<NetworkConfigMethod
>,
221 method6
: Option
<NetworkConfigMethod
>,
222 comments
: Option
<String
>,
223 comments6
: Option
<String
>,
224 cidr
: Option
<String
>,
225 gateway
: Option
<String
>,
226 cidr6
: Option
<String
>,
227 gateway6
: Option
<String
>,
229 bridge_ports
: Option
<String
>,
230 bridge_vlan_aware
: Option
<bool
>,
231 bond_mode
: Option
<LinuxBondMode
>,
232 bond_primary
: Option
<String
>,
233 bond_xmit_hash_policy
: Option
<BondXmitHashPolicy
>,
234 slaves
: Option
<String
>,
236 ) -> Result
<(), Error
> {
238 let interface_type
= pbs_tools
::json
::required_string_param(¶m
, "type")?
;
239 let interface_type
: NetworkInterfaceType
= serde_json
::from_value(interface_type
.into())?
;
241 let _lock
= open_backup_lockfile(network
::NETWORK_LOCKFILE
, None
, true)?
;
243 let (mut config
, _digest
) = network
::config()?
;
245 if config
.interfaces
.contains_key(&iface
) {
246 bail
!("interface '{}' already exists", iface
);
249 let mut interface
= Interface
::new(iface
.clone());
250 interface
.interface_type
= interface_type
;
252 if let Some(autostart
) = autostart { interface.autostart = autostart; }
253 if method
.is_some() { interface.method = method; }
254 if method6
.is_some() { interface.method6 = method6; }
255 if mtu
.is_some() { interface.mtu = mtu; }
256 if comments
.is_some() { interface.comments = comments; }
257 if comments6
.is_some() { interface.comments6 = comments6; }
259 if let Some(cidr
) = cidr
{
260 let (_
, _
, is_v6
) = network
::parse_cidr(&cidr
)?
;
261 if is_v6 { bail!("invalid address type (expected IPv4, got IPv6)"); }
262 interface
.cidr
= Some(cidr
);
265 if let Some(cidr6
) = cidr6
{
266 let (_
, _
, is_v6
) = network
::parse_cidr(&cidr6
)?
;
267 if !is_v6 { bail!("invalid address type (expected IPv6, got IPv4)"); }
268 interface
.cidr6
= Some(cidr6
);
271 if let Some(gateway
) = gateway
{
272 let is_v6
= gateway
.contains('
:'
);
273 if is_v6 { bail!("invalid address type (expected IPv4, got IPv6)"); }
274 check_duplicate_gateway_v4(&config
, &iface
)?
;
275 interface
.gateway
= Some(gateway
);
278 if let Some(gateway6
) = gateway6
{
279 let is_v6
= gateway6
.contains('
:'
);
280 if !is_v6 { bail!("invalid address type (expected IPv6, got IPv4)"); }
281 check_duplicate_gateway_v6(&config
, &iface
)?
;
282 interface
.gateway6
= Some(gateway6
);
285 match interface_type
{
286 NetworkInterfaceType
::Bridge
=> {
287 if let Some(ports
) = bridge_ports
{
288 let ports
= split_interface_list(&ports
)?
;
289 interface
.set_bridge_ports(ports
)?
;
291 if bridge_vlan_aware
.is_some() { interface.bridge_vlan_aware = bridge_vlan_aware; }
293 NetworkInterfaceType
::Bond
=> {
294 if let Some(mode
) = bond_mode
{
295 interface
.bond_mode
= bond_mode
;
296 if bond_primary
.is_some() {
297 if mode
!= LinuxBondMode
::active_backup
{
298 bail
!("bond-primary is only valid with Active/Backup mode");
300 interface
.bond_primary
= bond_primary
;
302 if bond_xmit_hash_policy
.is_some() {
303 if mode
!= LinuxBondMode
::ieee802_3ad
&&
304 mode
!= LinuxBondMode
::balance_xor
306 bail
!("bond_xmit_hash_policy is only valid with LACP(802.3ad) or balance-xor mode");
308 interface
.bond_xmit_hash_policy
= bond_xmit_hash_policy
;
311 if let Some(slaves
) = slaves
{
312 let slaves
= split_interface_list(&slaves
)?
;
313 interface
.set_bond_slaves(slaves
)?
;
316 _
=> bail
!("creating network interface type '{:?}' is not supported", interface_type
),
319 if interface
.cidr
.is_some() || interface
.gateway
.is_some() {
320 interface
.method
= Some(NetworkConfigMethod
::Static
);
321 } else if interface
.method
.is_none() {
322 interface
.method
= Some(NetworkConfigMethod
::Manual
);
325 if interface
.cidr6
.is_some() || interface
.gateway6
.is_some() {
326 interface
.method6
= Some(NetworkConfigMethod
::Static
);
327 } else if interface
.method6
.is_none() {
328 interface
.method6
= Some(NetworkConfigMethod
::Manual
);
331 config
.interfaces
.insert(iface
, interface
);
333 network
::save_config(&config
)?
;
339 #[derive(Serialize, Deserialize)]
340 #[allow(non_camel_case_types)]
341 /// Deletable property name
342 pub enum DeletableProperty
{
343 /// Delete the IPv4 address property.
345 /// Delete the IPv6 address property.
347 /// Delete the IPv4 gateway property.
349 /// Delete the IPv6 gateway property.
351 /// Delete the whole IPv4 configuration entry.
353 /// Delete the whole IPv6 configuration entry.
355 /// Delete IPv4 comments
357 /// Delete IPv6 comments
361 /// Delete autostart flag
363 /// Delete bridge ports (set to 'none')
365 /// Delete bridge-vlan-aware flag
367 /// Delete bond-slaves (set to 'none')
369 /// Delete bond-primary
370 #[serde(rename = "bond-primary")]
372 /// Delete bond transmit hash policy
373 bond_xmit_hash_policy
,
385 schema
: NETWORK_INTERFACE_NAME_SCHEMA
,
388 type: NetworkInterfaceType
,
392 description
: "Autostart interface.",
397 type: NetworkConfigMethod
,
401 type: NetworkConfigMethod
,
405 description
: "Comments (inet, may span multiple lines)",
410 description
: "Comments (inet5, may span multiple lines)",
415 schema
: CIDR_V4_SCHEMA
,
419 schema
: CIDR_V6_SCHEMA
,
423 schema
: IP_V4_SCHEMA
,
427 schema
: IP_V6_SCHEMA
,
431 description
: "Maximum Transmission Unit.",
438 schema
: NETWORK_INTERFACE_LIST_SCHEMA
,
442 description
: "Enable bridge vlan support.",
451 schema
: NETWORK_INTERFACE_NAME_SCHEMA
,
454 bond_xmit_hash_policy
: {
455 type: BondXmitHashPolicy
,
459 schema
: NETWORK_INTERFACE_LIST_SCHEMA
,
463 description
: "List of properties to delete.",
467 type: DeletableProperty
,
472 schema
: PROXMOX_CONFIG_DIGEST_SCHEMA
,
477 permission
: &Permission
::Privilege(&["system", "network", "interfaces", "{iface}"], PRIV_SYS_MODIFY
, false),
480 /// Update network interface config.
481 #[allow(clippy::too_many_arguments)]
482 pub fn update_interface(
484 autostart
: Option
<bool
>,
485 method
: Option
<NetworkConfigMethod
>,
486 method6
: Option
<NetworkConfigMethod
>,
487 comments
: Option
<String
>,
488 comments6
: Option
<String
>,
489 cidr
: Option
<String
>,
490 gateway
: Option
<String
>,
491 cidr6
: Option
<String
>,
492 gateway6
: Option
<String
>,
494 bridge_ports
: Option
<String
>,
495 bridge_vlan_aware
: Option
<bool
>,
496 bond_mode
: Option
<LinuxBondMode
>,
497 bond_primary
: Option
<String
>,
498 bond_xmit_hash_policy
: Option
<BondXmitHashPolicy
>,
499 slaves
: Option
<String
>,
500 delete
: Option
<Vec
<DeletableProperty
>>,
501 digest
: Option
<String
>,
503 ) -> Result
<(), Error
> {
505 let _lock
= open_backup_lockfile(network
::NETWORK_LOCKFILE
, None
, true)?
;
507 let (mut config
, expected_digest
) = network
::config()?
;
509 if let Some(ref digest
) = digest
{
510 let digest
= proxmox
::tools
::hex_to_digest(digest
)?
;
511 crate::tools
::detect_modified_configuration_file(&digest
, &expected_digest
)?
;
514 if gateway
.is_some() { check_duplicate_gateway_v4(&config, &iface)?; }
515 if gateway6
.is_some() { check_duplicate_gateway_v6(&config, &iface)?; }
517 let interface
= config
.lookup_mut(&iface
)?
;
519 if let Some(interface_type
) = param
.get("type") {
520 let interface_type
: NetworkInterfaceType
= serde_json
::from_value(interface_type
.clone())?
;
521 if interface_type
!= interface
.interface_type
{
522 bail
!("got unexpected interface type ({:?} != {:?})", interface_type
, interface
.interface_type
);
526 if let Some(delete
) = delete
{
527 for delete_prop
in delete
{
529 DeletableProperty
::cidr
=> { interface.cidr = None; }
,
530 DeletableProperty
::cidr6
=> { interface.cidr6 = None; }
,
531 DeletableProperty
::gateway
=> { interface.gateway = None; }
,
532 DeletableProperty
::gateway6
=> { interface.gateway6 = None; }
,
533 DeletableProperty
::method
=> { interface.method = None; }
,
534 DeletableProperty
::method6
=> { interface.method6 = None; }
,
535 DeletableProperty
::comments
=> { interface.comments = None; }
,
536 DeletableProperty
::comments6
=> { interface.comments6 = None; }
,
537 DeletableProperty
::mtu
=> { interface.mtu = None; }
,
538 DeletableProperty
::autostart
=> { interface.autostart = false; }
,
539 DeletableProperty
::bridge_ports
=> { interface.set_bridge_ports(Vec::new())?; }
540 DeletableProperty
::bridge_vlan_aware
=> { interface.bridge_vlan_aware = None; }
541 DeletableProperty
::slaves
=> { interface.set_bond_slaves(Vec::new())?; }
542 DeletableProperty
::bond_primary
=> { interface.bond_primary = None; }
543 DeletableProperty
::bond_xmit_hash_policy
=> { interface.bond_xmit_hash_policy = None }
548 if let Some(autostart
) = autostart { interface.autostart = autostart; }
549 if method
.is_some() { interface.method = method; }
550 if method6
.is_some() { interface.method6 = method6; }
551 if mtu
.is_some() { interface.mtu = mtu; }
552 if let Some(ports
) = bridge_ports
{
553 let ports
= split_interface_list(&ports
)?
;
554 interface
.set_bridge_ports(ports
)?
;
556 if bridge_vlan_aware
.is_some() { interface.bridge_vlan_aware = bridge_vlan_aware; }
557 if let Some(slaves
) = slaves
{
558 let slaves
= split_interface_list(&slaves
)?
;
559 interface
.set_bond_slaves(slaves
)?
;
561 if let Some(mode
) = bond_mode
{
562 interface
.bond_mode
= bond_mode
;
563 if bond_primary
.is_some() {
564 if mode
!= LinuxBondMode
::active_backup
{
565 bail
!("bond-primary is only valid with Active/Backup mode");
567 interface
.bond_primary
= bond_primary
;
569 if bond_xmit_hash_policy
.is_some() {
570 if mode
!= LinuxBondMode
::ieee802_3ad
&&
571 mode
!= LinuxBondMode
::balance_xor
573 bail
!("bond_xmit_hash_policy is only valid with LACP(802.3ad) or balance-xor mode");
575 interface
.bond_xmit_hash_policy
= bond_xmit_hash_policy
;
579 if let Some(cidr
) = cidr
{
580 let (_
, _
, is_v6
) = network
::parse_cidr(&cidr
)?
;
581 if is_v6 { bail!("invalid address type (expected IPv4, got IPv6)"); }
582 interface
.cidr
= Some(cidr
);
585 if let Some(cidr6
) = cidr6
{
586 let (_
, _
, is_v6
) = network
::parse_cidr(&cidr6
)?
;
587 if !is_v6 { bail!("invalid address type (expected IPv6, got IPv4)"); }
588 interface
.cidr6
= Some(cidr6
);
591 if let Some(gateway
) = gateway
{
592 let is_v6
= gateway
.contains('
:'
);
593 if is_v6 { bail!("invalid address type (expected IPv4, got IPv6)"); }
594 interface
.gateway
= Some(gateway
);
597 if let Some(gateway6
) = gateway6
{
598 let is_v6
= gateway6
.contains('
:'
);
599 if !is_v6 { bail!("invalid address type (expected IPv6, got IPv4)"); }
600 interface
.gateway6
= Some(gateway6
);
603 if comments
.is_some() { interface.comments = comments; }
604 if comments6
.is_some() { interface.comments6 = comments6; }
606 if interface
.cidr
.is_some() || interface
.gateway
.is_some() {
607 interface
.method
= Some(NetworkConfigMethod
::Static
);
609 interface
.method
= Some(NetworkConfigMethod
::Manual
);
612 if interface
.cidr6
.is_some() || interface
.gateway6
.is_some() {
613 interface
.method6
= Some(NetworkConfigMethod
::Static
);
615 interface
.method6
= Some(NetworkConfigMethod
::Manual
);
618 network
::save_config(&config
)?
;
631 schema
: NETWORK_INTERFACE_NAME_SCHEMA
,
635 schema
: PROXMOX_CONFIG_DIGEST_SCHEMA
,
640 permission
: &Permission
::Privilege(&["system", "network", "interfaces", "{iface}"], PRIV_SYS_MODIFY
, false),
643 /// Remove network interface configuration.
644 pub fn delete_interface(iface
: String
, digest
: Option
<String
>) -> Result
<(), Error
> {
645 let _lock
= open_backup_lockfile(network
::NETWORK_LOCKFILE
, None
, true)?
;
647 let (mut config
, expected_digest
) = network
::config()?
;
649 if let Some(ref digest
) = digest
{
650 let digest
= proxmox
::tools
::hex_to_digest(digest
)?
;
651 crate::tools
::detect_modified_configuration_file(&digest
, &expected_digest
)?
;
654 let _interface
= config
.lookup(&iface
)?
; // check if interface exists
656 config
.interfaces
.remove(&iface
);
658 network
::save_config(&config
)?
;
673 permission
: &Permission
::Privilege(&["system", "network", "interfaces"], PRIV_SYS_MODIFY
, false),
676 /// Reload network configuration (requires ifupdown2).
677 pub async
fn reload_network_config(
678 rpcenv
: &mut dyn RpcEnvironment
,
679 ) -> Result
<String
, Error
> {
681 network
::assert_ifupdown2_installed()?
;
683 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
685 let upid_str
= WorkerTask
::spawn("srvreload", Some(String
::from("networking")), auth_id
, true, |_worker
| async
{
687 let _
= std
::fs
::rename(network
::NETWORK_INTERFACES_NEW_FILENAME
, network
::NETWORK_INTERFACES_FILENAME
);
689 network
::network_reload()?
;
706 permission
: &Permission
::Privilege(&["system", "network", "interfaces"], PRIV_SYS_MODIFY
, false),
709 /// Revert network configuration (rm /etc/network/interfaces.new).
710 pub fn revert_network_config() -> Result
<(), Error
> {
712 let _
= std
::fs
::remove_file(network
::NETWORK_INTERFACES_NEW_FILENAME
);
717 const ITEM_ROUTER
: Router
= Router
::new()
718 .get(&API_METHOD_READ_INTERFACE
)
719 .put(&API_METHOD_UPDATE_INTERFACE
)
720 .delete(&API_METHOD_DELETE_INTERFACE
);
722 pub const ROUTER
: Router
= Router
::new()
723 .get(&API_METHOD_LIST_NETWORK_DEVICES
)
724 .put(&API_METHOD_RELOAD_NETWORK_CONFIG
)
725 .post(&API_METHOD_CREATE_INTERFACE
)
726 .delete(&API_METHOD_REVERT_NETWORK_CONFIG
)
727 .match_all("iface", &ITEM_ROUTER
);