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
;
9 Authid
, Interface
, NetworkInterfaceType
, LinuxBondMode
, NetworkConfigMethod
, BondXmitHashPolicy
,
10 NETWORK_INTERFACE_ARRAY_SCHEMA
, NETWORK_INTERFACE_LIST_SCHEMA
, NETWORK_INTERFACE_NAME_SCHEMA
,
11 CIDR_V4_SCHEMA
, CIDR_V6_SCHEMA
, IP_V4_SCHEMA
, IP_V6_SCHEMA
, PROXMOX_CONFIG_DIGEST_SCHEMA
,
12 NODE_SCHEMA
, PRIV_SYS_AUDIT
, PRIV_SYS_MODIFY
,
14 use pbs_config
::network
::{self, NetworkConfig}
;
16 use proxmox_rest_server
::WorkerTask
;
18 fn split_interface_list(list
: &str) -> Result
<Vec
<String
>, Error
> {
19 let value
= parse_property_string(&list
, &NETWORK_INTERFACE_ARRAY_SCHEMA
)?
;
20 Ok(value
.as_array().unwrap().iter().map(|v
| v
.as_str().unwrap().to_string()).collect())
23 fn check_duplicate_gateway_v4(config
: &NetworkConfig
, iface
: &str) -> Result
<(), Error
> {
25 let current_gateway_v4
= config
.interfaces
.iter()
26 .find(|(_
, interface
)| interface
.gateway
.is_some())
27 .map(|(name
, _
)| name
.to_string());
29 if let Some(current_gateway_v4
) = current_gateway_v4
{
30 if current_gateway_v4
!= iface
{
31 bail
!("Default IPv4 gateway already exists on interface '{}'", current_gateway_v4
);
37 fn check_duplicate_gateway_v6(config
: &NetworkConfig
, iface
: &str) -> Result
<(), Error
> {
39 let current_gateway_v6
= config
.interfaces
.iter()
40 .find(|(_
, interface
)| interface
.gateway6
.is_some())
41 .map(|(name
, _
)| name
.to_string());
43 if let Some(current_gateway_v6
) = current_gateway_v6
{
44 if current_gateway_v6
!= iface
{
45 bail
!("Default IPv6 gateway already exists on interface '{}'", current_gateway_v6
);
52 fn set_bridge_ports(iface
: &mut Interface
, ports
: Vec
<String
>) -> Result
<(), Error
> {
53 if iface
.interface_type
!= NetworkInterfaceType
::Bridge
{
54 bail
!("interface '{}' is no bridge (type is {:?})", iface
.name
, iface
.interface_type
);
56 iface
.bridge_ports
= Some(ports
);
60 fn set_bond_slaves(iface
: &mut Interface
, slaves
: Vec
<String
>) -> Result
<(), Error
> {
61 if iface
.interface_type
!= NetworkInterfaceType
::Bond
{
62 bail
!("interface '{}' is no bond (type is {:?})", iface
.name
, iface
.interface_type
);
64 iface
.slaves
= Some(slaves
);
77 description
: "List network devices (with config digest).",
84 permission
: &Permission
::Privilege(&["system", "network", "interfaces"], PRIV_SYS_AUDIT
, false),
87 /// List all datastores
88 pub fn list_network_devices(
91 mut rpcenv
: &mut dyn RpcEnvironment
,
92 ) -> Result
<Value
, Error
> {
94 let (config
, digest
) = network
::config()?
;
95 let digest
= proxmox
::tools
::digest_to_hex(&digest
);
97 let mut list
= Vec
::new();
99 for (iface
, interface
) in config
.interfaces
.iter() {
100 if iface
== "lo" { continue; }
// do not list lo
101 let mut item
: Value
= to_value(interface
)?
;
102 item
["digest"] = digest
.clone().into();
103 item
["iface"] = iface
.to_string().into();
107 let diff
= network
::changes()?
;
108 if !diff
.is_empty() {
109 rpcenv
["changes"] = diff
.into();
122 schema
: NETWORK_INTERFACE_NAME_SCHEMA
,
126 returns
: { type: Interface }
,
128 permission
: &Permission
::Privilege(&["system", "network", "interfaces", "{name}"], PRIV_SYS_AUDIT
, false),
131 /// Read a network interface configuration.
132 pub fn read_interface(iface
: String
) -> Result
<Value
, Error
> {
134 let (config
, digest
) = network
::config()?
;
136 let interface
= config
.lookup(&iface
)?
;
138 let mut data
: Value
= to_value(interface
)?
;
139 data
["digest"] = proxmox
::tools
::digest_to_hex(&digest
).into();
153 schema
: NETWORK_INTERFACE_NAME_SCHEMA
,
156 type: NetworkInterfaceType
,
160 description
: "Autostart interface.",
165 type: NetworkConfigMethod
,
169 type: NetworkConfigMethod
,
173 description
: "Comments (inet, may span multiple lines)",
178 description
: "Comments (inet5, may span multiple lines)",
183 schema
: CIDR_V4_SCHEMA
,
187 schema
: CIDR_V6_SCHEMA
,
191 schema
: IP_V4_SCHEMA
,
195 schema
: IP_V6_SCHEMA
,
199 description
: "Maximum Transmission Unit.",
206 schema
: NETWORK_INTERFACE_LIST_SCHEMA
,
210 description
: "Enable bridge vlan support.",
219 schema
: NETWORK_INTERFACE_NAME_SCHEMA
,
222 bond_xmit_hash_policy
: {
223 type: BondXmitHashPolicy
,
227 schema
: NETWORK_INTERFACE_LIST_SCHEMA
,
233 permission
: &Permission
::Privilege(&["system", "network", "interfaces", "{iface}"], PRIV_SYS_MODIFY
, false),
236 /// Create network interface configuration.
237 #[allow(clippy::too_many_arguments)]
238 pub fn create_interface(
240 autostart
: Option
<bool
>,
241 method
: Option
<NetworkConfigMethod
>,
242 method6
: Option
<NetworkConfigMethod
>,
243 comments
: Option
<String
>,
244 comments6
: Option
<String
>,
245 cidr
: Option
<String
>,
246 gateway
: Option
<String
>,
247 cidr6
: Option
<String
>,
248 gateway6
: Option
<String
>,
250 bridge_ports
: Option
<String
>,
251 bridge_vlan_aware
: Option
<bool
>,
252 bond_mode
: Option
<LinuxBondMode
>,
253 bond_primary
: Option
<String
>,
254 bond_xmit_hash_policy
: Option
<BondXmitHashPolicy
>,
255 slaves
: Option
<String
>,
257 ) -> Result
<(), Error
> {
259 let interface_type
= pbs_tools
::json
::required_string_param(¶m
, "type")?
;
260 let interface_type
: NetworkInterfaceType
= serde_json
::from_value(interface_type
.into())?
;
262 let _lock
= network
::lock_config()?
;
264 let (mut config
, _digest
) = network
::config()?
;
266 if config
.interfaces
.contains_key(&iface
) {
267 bail
!("interface '{}' already exists", iface
);
270 let mut interface
= Interface
::new(iface
.clone());
271 interface
.interface_type
= interface_type
;
273 if let Some(autostart
) = autostart { interface.autostart = autostart; }
274 if method
.is_some() { interface.method = method; }
275 if method6
.is_some() { interface.method6 = method6; }
276 if mtu
.is_some() { interface.mtu = mtu; }
277 if comments
.is_some() { interface.comments = comments; }
278 if comments6
.is_some() { interface.comments6 = comments6; }
280 if let Some(cidr
) = cidr
{
281 let (_
, _
, is_v6
) = network
::parse_cidr(&cidr
)?
;
282 if is_v6 { bail!("invalid address type (expected IPv4, got IPv6)"); }
283 interface
.cidr
= Some(cidr
);
286 if let Some(cidr6
) = cidr6
{
287 let (_
, _
, is_v6
) = network
::parse_cidr(&cidr6
)?
;
288 if !is_v6 { bail!("invalid address type (expected IPv6, got IPv4)"); }
289 interface
.cidr6
= Some(cidr6
);
292 if let Some(gateway
) = gateway
{
293 let is_v6
= gateway
.contains('
:'
);
294 if is_v6 { bail!("invalid address type (expected IPv4, got IPv6)"); }
295 check_duplicate_gateway_v4(&config
, &iface
)?
;
296 interface
.gateway
= Some(gateway
);
299 if let Some(gateway6
) = gateway6
{
300 let is_v6
= gateway6
.contains('
:'
);
301 if !is_v6 { bail!("invalid address type (expected IPv6, got IPv4)"); }
302 check_duplicate_gateway_v6(&config
, &iface
)?
;
303 interface
.gateway6
= Some(gateway6
);
306 match interface_type
{
307 NetworkInterfaceType
::Bridge
=> {
308 if let Some(ports
) = bridge_ports
{
309 let ports
= split_interface_list(&ports
)?
;
310 set_bridge_ports(&mut interface
, ports
)?
;
312 if bridge_vlan_aware
.is_some() { interface.bridge_vlan_aware = bridge_vlan_aware; }
314 NetworkInterfaceType
::Bond
=> {
315 if let Some(mode
) = bond_mode
{
316 interface
.bond_mode
= bond_mode
;
317 if bond_primary
.is_some() {
318 if mode
!= LinuxBondMode
::active_backup
{
319 bail
!("bond-primary is only valid with Active/Backup mode");
321 interface
.bond_primary
= bond_primary
;
323 if bond_xmit_hash_policy
.is_some() {
324 if mode
!= LinuxBondMode
::ieee802_3ad
&&
325 mode
!= LinuxBondMode
::balance_xor
327 bail
!("bond_xmit_hash_policy is only valid with LACP(802.3ad) or balance-xor mode");
329 interface
.bond_xmit_hash_policy
= bond_xmit_hash_policy
;
332 if let Some(slaves
) = slaves
{
333 let slaves
= split_interface_list(&slaves
)?
;
334 set_bond_slaves(&mut interface
, slaves
)?
;
337 _
=> bail
!("creating network interface type '{:?}' is not supported", interface_type
),
340 if interface
.cidr
.is_some() || interface
.gateway
.is_some() {
341 interface
.method
= Some(NetworkConfigMethod
::Static
);
342 } else if interface
.method
.is_none() {
343 interface
.method
= Some(NetworkConfigMethod
::Manual
);
346 if interface
.cidr6
.is_some() || interface
.gateway6
.is_some() {
347 interface
.method6
= Some(NetworkConfigMethod
::Static
);
348 } else if interface
.method6
.is_none() {
349 interface
.method6
= Some(NetworkConfigMethod
::Manual
);
352 config
.interfaces
.insert(iface
, interface
);
354 network
::save_config(&config
)?
;
360 #[derive(Serialize, Deserialize)]
361 #[allow(non_camel_case_types)]
362 /// Deletable property name
363 pub enum DeletableProperty
{
364 /// Delete the IPv4 address property.
366 /// Delete the IPv6 address property.
368 /// Delete the IPv4 gateway property.
370 /// Delete the IPv6 gateway property.
372 /// Delete the whole IPv4 configuration entry.
374 /// Delete the whole IPv6 configuration entry.
376 /// Delete IPv4 comments
378 /// Delete IPv6 comments
382 /// Delete autostart flag
384 /// Delete bridge ports (set to 'none')
386 /// Delete bridge-vlan-aware flag
388 /// Delete bond-slaves (set to 'none')
390 /// Delete bond-primary
391 #[serde(rename = "bond-primary")]
393 /// Delete bond transmit hash policy
394 bond_xmit_hash_policy
,
406 schema
: NETWORK_INTERFACE_NAME_SCHEMA
,
409 type: NetworkInterfaceType
,
413 description
: "Autostart interface.",
418 type: NetworkConfigMethod
,
422 type: NetworkConfigMethod
,
426 description
: "Comments (inet, may span multiple lines)",
431 description
: "Comments (inet5, may span multiple lines)",
436 schema
: CIDR_V4_SCHEMA
,
440 schema
: CIDR_V6_SCHEMA
,
444 schema
: IP_V4_SCHEMA
,
448 schema
: IP_V6_SCHEMA
,
452 description
: "Maximum Transmission Unit.",
459 schema
: NETWORK_INTERFACE_LIST_SCHEMA
,
463 description
: "Enable bridge vlan support.",
472 schema
: NETWORK_INTERFACE_NAME_SCHEMA
,
475 bond_xmit_hash_policy
: {
476 type: BondXmitHashPolicy
,
480 schema
: NETWORK_INTERFACE_LIST_SCHEMA
,
484 description
: "List of properties to delete.",
488 type: DeletableProperty
,
493 schema
: PROXMOX_CONFIG_DIGEST_SCHEMA
,
498 permission
: &Permission
::Privilege(&["system", "network", "interfaces", "{iface}"], PRIV_SYS_MODIFY
, false),
501 /// Update network interface config.
502 #[allow(clippy::too_many_arguments)]
503 pub fn update_interface(
505 autostart
: Option
<bool
>,
506 method
: Option
<NetworkConfigMethod
>,
507 method6
: Option
<NetworkConfigMethod
>,
508 comments
: Option
<String
>,
509 comments6
: Option
<String
>,
510 cidr
: Option
<String
>,
511 gateway
: Option
<String
>,
512 cidr6
: Option
<String
>,
513 gateway6
: Option
<String
>,
515 bridge_ports
: Option
<String
>,
516 bridge_vlan_aware
: Option
<bool
>,
517 bond_mode
: Option
<LinuxBondMode
>,
518 bond_primary
: Option
<String
>,
519 bond_xmit_hash_policy
: Option
<BondXmitHashPolicy
>,
520 slaves
: Option
<String
>,
521 delete
: Option
<Vec
<DeletableProperty
>>,
522 digest
: Option
<String
>,
524 ) -> Result
<(), Error
> {
526 let _lock
= network
::lock_config()?
;
528 let (mut config
, expected_digest
) = network
::config()?
;
530 if let Some(ref digest
) = digest
{
531 let digest
= proxmox
::tools
::hex_to_digest(digest
)?
;
532 crate::tools
::detect_modified_configuration_file(&digest
, &expected_digest
)?
;
535 if gateway
.is_some() { check_duplicate_gateway_v4(&config, &iface)?; }
536 if gateway6
.is_some() { check_duplicate_gateway_v6(&config, &iface)?; }
538 let interface
= config
.lookup_mut(&iface
)?
;
540 if let Some(interface_type
) = param
.get("type") {
541 let interface_type
: NetworkInterfaceType
= serde_json
::from_value(interface_type
.clone())?
;
542 if interface_type
!= interface
.interface_type
{
543 bail
!("got unexpected interface type ({:?} != {:?})", interface_type
, interface
.interface_type
);
547 if let Some(delete
) = delete
{
548 for delete_prop
in delete
{
550 DeletableProperty
::cidr
=> { interface.cidr = None; }
,
551 DeletableProperty
::cidr6
=> { interface.cidr6 = None; }
,
552 DeletableProperty
::gateway
=> { interface.gateway = None; }
,
553 DeletableProperty
::gateway6
=> { interface.gateway6 = None; }
,
554 DeletableProperty
::method
=> { interface.method = None; }
,
555 DeletableProperty
::method6
=> { interface.method6 = None; }
,
556 DeletableProperty
::comments
=> { interface.comments = None; }
,
557 DeletableProperty
::comments6
=> { interface.comments6 = None; }
,
558 DeletableProperty
::mtu
=> { interface.mtu = None; }
,
559 DeletableProperty
::autostart
=> { interface.autostart = false; }
,
560 DeletableProperty
::bridge_ports
=> { set_bridge_ports(interface, Vec::new())?; }
561 DeletableProperty
::bridge_vlan_aware
=> { interface.bridge_vlan_aware = None; }
562 DeletableProperty
::slaves
=> { set_bond_slaves(interface, Vec::new())?; }
563 DeletableProperty
::bond_primary
=> { interface.bond_primary = None; }
564 DeletableProperty
::bond_xmit_hash_policy
=> { interface.bond_xmit_hash_policy = None }
569 if let Some(autostart
) = autostart { interface.autostart = autostart; }
570 if method
.is_some() { interface.method = method; }
571 if method6
.is_some() { interface.method6 = method6; }
572 if mtu
.is_some() { interface.mtu = mtu; }
573 if let Some(ports
) = bridge_ports
{
574 let ports
= split_interface_list(&ports
)?
;
575 set_bridge_ports(interface
, ports
)?
;
577 if bridge_vlan_aware
.is_some() { interface.bridge_vlan_aware = bridge_vlan_aware; }
578 if let Some(slaves
) = slaves
{
579 let slaves
= split_interface_list(&slaves
)?
;
580 set_bond_slaves(interface
, slaves
)?
;
582 if let Some(mode
) = bond_mode
{
583 interface
.bond_mode
= bond_mode
;
584 if bond_primary
.is_some() {
585 if mode
!= LinuxBondMode
::active_backup
{
586 bail
!("bond-primary is only valid with Active/Backup mode");
588 interface
.bond_primary
= bond_primary
;
590 if bond_xmit_hash_policy
.is_some() {
591 if mode
!= LinuxBondMode
::ieee802_3ad
&&
592 mode
!= LinuxBondMode
::balance_xor
594 bail
!("bond_xmit_hash_policy is only valid with LACP(802.3ad) or balance-xor mode");
596 interface
.bond_xmit_hash_policy
= bond_xmit_hash_policy
;
600 if let Some(cidr
) = cidr
{
601 let (_
, _
, is_v6
) = network
::parse_cidr(&cidr
)?
;
602 if is_v6 { bail!("invalid address type (expected IPv4, got IPv6)"); }
603 interface
.cidr
= Some(cidr
);
606 if let Some(cidr6
) = cidr6
{
607 let (_
, _
, is_v6
) = network
::parse_cidr(&cidr6
)?
;
608 if !is_v6 { bail!("invalid address type (expected IPv6, got IPv4)"); }
609 interface
.cidr6
= Some(cidr6
);
612 if let Some(gateway
) = gateway
{
613 let is_v6
= gateway
.contains('
:'
);
614 if is_v6 { bail!("invalid address type (expected IPv4, got IPv6)"); }
615 interface
.gateway
= Some(gateway
);
618 if let Some(gateway6
) = gateway6
{
619 let is_v6
= gateway6
.contains('
:'
);
620 if !is_v6 { bail!("invalid address type (expected IPv6, got IPv4)"); }
621 interface
.gateway6
= Some(gateway6
);
624 if comments
.is_some() { interface.comments = comments; }
625 if comments6
.is_some() { interface.comments6 = comments6; }
627 if interface
.cidr
.is_some() || interface
.gateway
.is_some() {
628 interface
.method
= Some(NetworkConfigMethod
::Static
);
630 interface
.method
= Some(NetworkConfigMethod
::Manual
);
633 if interface
.cidr6
.is_some() || interface
.gateway6
.is_some() {
634 interface
.method6
= Some(NetworkConfigMethod
::Static
);
636 interface
.method6
= Some(NetworkConfigMethod
::Manual
);
639 network
::save_config(&config
)?
;
652 schema
: NETWORK_INTERFACE_NAME_SCHEMA
,
656 schema
: PROXMOX_CONFIG_DIGEST_SCHEMA
,
661 permission
: &Permission
::Privilege(&["system", "network", "interfaces", "{iface}"], PRIV_SYS_MODIFY
, false),
664 /// Remove network interface configuration.
665 pub fn delete_interface(iface
: String
, digest
: Option
<String
>) -> Result
<(), Error
> {
666 let _lock
= network
::lock_config()?
;
668 let (mut config
, expected_digest
) = network
::config()?
;
670 if let Some(ref digest
) = digest
{
671 let digest
= proxmox
::tools
::hex_to_digest(digest
)?
;
672 crate::tools
::detect_modified_configuration_file(&digest
, &expected_digest
)?
;
675 let _interface
= config
.lookup(&iface
)?
; // check if interface exists
677 config
.interfaces
.remove(&iface
);
679 network
::save_config(&config
)?
;
694 permission
: &Permission
::Privilege(&["system", "network", "interfaces"], PRIV_SYS_MODIFY
, false),
697 /// Reload network configuration (requires ifupdown2).
698 pub async
fn reload_network_config(
699 rpcenv
: &mut dyn RpcEnvironment
,
700 ) -> Result
<String
, Error
> {
702 network
::assert_ifupdown2_installed()?
;
704 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
706 let upid_str
= WorkerTask
::spawn("srvreload", Some(String
::from("networking")), auth_id
.to_string(), true, |_worker
| async
{
708 let _
= std
::fs
::rename(network
::NETWORK_INTERFACES_NEW_FILENAME
, network
::NETWORK_INTERFACES_FILENAME
);
710 network
::network_reload()?
;
727 permission
: &Permission
::Privilege(&["system", "network", "interfaces"], PRIV_SYS_MODIFY
, false),
730 /// Revert network configuration (rm /etc/network/interfaces.new).
731 pub fn revert_network_config() -> Result
<(), Error
> {
733 let _
= std
::fs
::remove_file(network
::NETWORK_INTERFACES_NEW_FILENAME
);
738 const ITEM_ROUTER
: Router
= Router
::new()
739 .get(&API_METHOD_READ_INTERFACE
)
740 .put(&API_METHOD_UPDATE_INTERFACE
)
741 .delete(&API_METHOD_DELETE_INTERFACE
);
743 pub const ROUTER
: Router
= Router
::new()
744 .get(&API_METHOD_LIST_NETWORK_DEVICES
)
745 .put(&API_METHOD_RELOAD_NETWORK_CONFIG
)
746 .post(&API_METHOD_CREATE_INTERFACE
)
747 .delete(&API_METHOD_REVERT_NETWORK_CONFIG
)
748 .match_all("iface", &ITEM_ROUTER
);