]> git.proxmox.com Git - proxmox-backup.git/blob - src/api2/node/network.rs
d496b5f8ea9dc003785ec9acde097d990df586c9
[proxmox-backup.git] / src / api2 / node / network.rs
1 use anyhow::{Error, bail};
2 use serde_json::{Value, to_value};
3 use ::serde::{Deserialize, Serialize};
4
5 use proxmox::api::{api, ApiMethod, Router, RpcEnvironment, Permission};
6 use proxmox::api::schema::parse_property_string;
7
8 use pbs_api_types::{
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,
13 };
14 use pbs_config::network::{self, NetworkConfig};
15
16 use proxmox_rest_server::WorkerTask;
17
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())
21 }
22
23 fn check_duplicate_gateway_v4(config: &NetworkConfig, iface: &str) -> Result<(), Error> {
24
25 let current_gateway_v4 = config.interfaces.iter()
26 .find(|(_, interface)| interface.gateway.is_some())
27 .map(|(name, _)| name.to_string());
28
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);
32 }
33 }
34 Ok(())
35 }
36
37 fn check_duplicate_gateway_v6(config: &NetworkConfig, iface: &str) -> Result<(), Error> {
38
39 let current_gateway_v6 = config.interfaces.iter()
40 .find(|(_, interface)| interface.gateway6.is_some())
41 .map(|(name, _)| name.to_string());
42
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);
46 }
47 }
48 Ok(())
49 }
50
51
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);
55 }
56 iface.bridge_ports = Some(ports);
57 Ok(())
58 }
59
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);
63 }
64 iface.slaves = Some(slaves);
65 Ok(())
66 }
67
68 #[api(
69 input: {
70 properties: {
71 node: {
72 schema: NODE_SCHEMA,
73 },
74 },
75 },
76 returns: {
77 description: "List network devices (with config digest).",
78 type: Array,
79 items: {
80 type: Interface,
81 },
82 },
83 access: {
84 permission: &Permission::Privilege(&["system", "network", "interfaces"], PRIV_SYS_AUDIT, false),
85 },
86 )]
87 /// List all datastores
88 pub fn list_network_devices(
89 _param: Value,
90 _info: &ApiMethod,
91 mut rpcenv: &mut dyn RpcEnvironment,
92 ) -> Result<Value, Error> {
93
94 let (config, digest) = network::config()?;
95 let digest = proxmox::tools::digest_to_hex(&digest);
96
97 let mut list = Vec::new();
98
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();
104 list.push(item);
105 }
106
107 let diff = network::changes()?;
108 if !diff.is_empty() {
109 rpcenv["changes"] = diff.into();
110 }
111
112 Ok(list.into())
113 }
114
115 #[api(
116 input: {
117 properties: {
118 node: {
119 schema: NODE_SCHEMA,
120 },
121 iface: {
122 schema: NETWORK_INTERFACE_NAME_SCHEMA,
123 },
124 },
125 },
126 returns: { type: Interface },
127 access: {
128 permission: &Permission::Privilege(&["system", "network", "interfaces", "{name}"], PRIV_SYS_AUDIT, false),
129 },
130 )]
131 /// Read a network interface configuration.
132 pub fn read_interface(iface: String) -> Result<Value, Error> {
133
134 let (config, digest) = network::config()?;
135
136 let interface = config.lookup(&iface)?;
137
138 let mut data: Value = to_value(interface)?;
139 data["digest"] = proxmox::tools::digest_to_hex(&digest).into();
140
141 Ok(data)
142 }
143
144
145 #[api(
146 protected: true,
147 input: {
148 properties: {
149 node: {
150 schema: NODE_SCHEMA,
151 },
152 iface: {
153 schema: NETWORK_INTERFACE_NAME_SCHEMA,
154 },
155 "type": {
156 type: NetworkInterfaceType,
157 optional: true,
158 },
159 autostart: {
160 description: "Autostart interface.",
161 type: bool,
162 optional: true,
163 },
164 method: {
165 type: NetworkConfigMethod,
166 optional: true,
167 },
168 method6: {
169 type: NetworkConfigMethod,
170 optional: true,
171 },
172 comments: {
173 description: "Comments (inet, may span multiple lines)",
174 type: String,
175 optional: true,
176 },
177 comments6: {
178 description: "Comments (inet5, may span multiple lines)",
179 type: String,
180 optional: true,
181 },
182 cidr: {
183 schema: CIDR_V4_SCHEMA,
184 optional: true,
185 },
186 cidr6: {
187 schema: CIDR_V6_SCHEMA,
188 optional: true,
189 },
190 gateway: {
191 schema: IP_V4_SCHEMA,
192 optional: true,
193 },
194 gateway6: {
195 schema: IP_V6_SCHEMA,
196 optional: true,
197 },
198 mtu: {
199 description: "Maximum Transmission Unit.",
200 optional: true,
201 minimum: 46,
202 maximum: 65535,
203 default: 1500,
204 },
205 bridge_ports: {
206 schema: NETWORK_INTERFACE_LIST_SCHEMA,
207 optional: true,
208 },
209 bridge_vlan_aware: {
210 description: "Enable bridge vlan support.",
211 type: bool,
212 optional: true,
213 },
214 bond_mode: {
215 type: LinuxBondMode,
216 optional: true,
217 },
218 "bond-primary": {
219 schema: NETWORK_INTERFACE_NAME_SCHEMA,
220 optional: true,
221 },
222 bond_xmit_hash_policy: {
223 type: BondXmitHashPolicy,
224 optional: true,
225 },
226 slaves: {
227 schema: NETWORK_INTERFACE_LIST_SCHEMA,
228 optional: true,
229 },
230 },
231 },
232 access: {
233 permission: &Permission::Privilege(&["system", "network", "interfaces", "{iface}"], PRIV_SYS_MODIFY, false),
234 },
235 )]
236 /// Create network interface configuration.
237 #[allow(clippy::too_many_arguments)]
238 pub fn create_interface(
239 iface: String,
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>,
249 mtu: Option<u64>,
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>,
256 param: Value,
257 ) -> Result<(), Error> {
258
259 let interface_type = pbs_tools::json::required_string_param(&param, "type")?;
260 let interface_type: NetworkInterfaceType = serde_json::from_value(interface_type.into())?;
261
262 let _lock = network::lock_config()?;
263
264 let (mut config, _digest) = network::config()?;
265
266 if config.interfaces.contains_key(&iface) {
267 bail!("interface '{}' already exists", iface);
268 }
269
270 let mut interface = Interface::new(iface.clone());
271 interface.interface_type = interface_type;
272
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; }
279
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);
284 }
285
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);
290 }
291
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);
297 }
298
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);
304 }
305
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)?;
311 }
312 if bridge_vlan_aware.is_some() { interface.bridge_vlan_aware = bridge_vlan_aware; }
313 }
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");
320 }
321 interface.bond_primary = bond_primary;
322 }
323 if bond_xmit_hash_policy.is_some() {
324 if mode != LinuxBondMode::ieee802_3ad &&
325 mode != LinuxBondMode::balance_xor
326 {
327 bail!("bond_xmit_hash_policy is only valid with LACP(802.3ad) or balance-xor mode");
328 }
329 interface.bond_xmit_hash_policy = bond_xmit_hash_policy;
330 }
331 }
332 if let Some(slaves) = slaves {
333 let slaves = split_interface_list(&slaves)?;
334 set_bond_slaves(&mut interface, slaves)?;
335 }
336 }
337 _ => bail!("creating network interface type '{:?}' is not supported", interface_type),
338 }
339
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);
344 }
345
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);
350 }
351
352 config.interfaces.insert(iface, interface);
353
354 network::save_config(&config)?;
355
356 Ok(())
357 }
358
359 #[api()]
360 #[derive(Serialize, Deserialize)]
361 #[allow(non_camel_case_types)]
362 /// Deletable property name
363 pub enum DeletableProperty {
364 /// Delete the IPv4 address property.
365 cidr,
366 /// Delete the IPv6 address property.
367 cidr6,
368 /// Delete the IPv4 gateway property.
369 gateway,
370 /// Delete the IPv6 gateway property.
371 gateway6,
372 /// Delete the whole IPv4 configuration entry.
373 method,
374 /// Delete the whole IPv6 configuration entry.
375 method6,
376 /// Delete IPv4 comments
377 comments,
378 /// Delete IPv6 comments
379 comments6,
380 /// Delete mtu.
381 mtu,
382 /// Delete autostart flag
383 autostart,
384 /// Delete bridge ports (set to 'none')
385 bridge_ports,
386 /// Delete bridge-vlan-aware flag
387 bridge_vlan_aware,
388 /// Delete bond-slaves (set to 'none')
389 slaves,
390 /// Delete bond-primary
391 #[serde(rename = "bond-primary")]
392 bond_primary,
393 /// Delete bond transmit hash policy
394 bond_xmit_hash_policy,
395 }
396
397
398 #[api(
399 protected: true,
400 input: {
401 properties: {
402 node: {
403 schema: NODE_SCHEMA,
404 },
405 iface: {
406 schema: NETWORK_INTERFACE_NAME_SCHEMA,
407 },
408 "type": {
409 type: NetworkInterfaceType,
410 optional: true,
411 },
412 autostart: {
413 description: "Autostart interface.",
414 type: bool,
415 optional: true,
416 },
417 method: {
418 type: NetworkConfigMethod,
419 optional: true,
420 },
421 method6: {
422 type: NetworkConfigMethod,
423 optional: true,
424 },
425 comments: {
426 description: "Comments (inet, may span multiple lines)",
427 type: String,
428 optional: true,
429 },
430 comments6: {
431 description: "Comments (inet5, may span multiple lines)",
432 type: String,
433 optional: true,
434 },
435 cidr: {
436 schema: CIDR_V4_SCHEMA,
437 optional: true,
438 },
439 cidr6: {
440 schema: CIDR_V6_SCHEMA,
441 optional: true,
442 },
443 gateway: {
444 schema: IP_V4_SCHEMA,
445 optional: true,
446 },
447 gateway6: {
448 schema: IP_V6_SCHEMA,
449 optional: true,
450 },
451 mtu: {
452 description: "Maximum Transmission Unit.",
453 optional: true,
454 minimum: 46,
455 maximum: 65535,
456 default: 1500,
457 },
458 bridge_ports: {
459 schema: NETWORK_INTERFACE_LIST_SCHEMA,
460 optional: true,
461 },
462 bridge_vlan_aware: {
463 description: "Enable bridge vlan support.",
464 type: bool,
465 optional: true,
466 },
467 bond_mode: {
468 type: LinuxBondMode,
469 optional: true,
470 },
471 "bond-primary": {
472 schema: NETWORK_INTERFACE_NAME_SCHEMA,
473 optional: true,
474 },
475 bond_xmit_hash_policy: {
476 type: BondXmitHashPolicy,
477 optional: true,
478 },
479 slaves: {
480 schema: NETWORK_INTERFACE_LIST_SCHEMA,
481 optional: true,
482 },
483 delete: {
484 description: "List of properties to delete.",
485 type: Array,
486 optional: true,
487 items: {
488 type: DeletableProperty,
489 }
490 },
491 digest: {
492 optional: true,
493 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
494 },
495 },
496 },
497 access: {
498 permission: &Permission::Privilege(&["system", "network", "interfaces", "{iface}"], PRIV_SYS_MODIFY, false),
499 },
500 )]
501 /// Update network interface config.
502 #[allow(clippy::too_many_arguments)]
503 pub fn update_interface(
504 iface: String,
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>,
514 mtu: Option<u64>,
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>,
523 param: Value,
524 ) -> Result<(), Error> {
525
526 let _lock = network::lock_config()?;
527
528 let (mut config, expected_digest) = network::config()?;
529
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)?;
533 }
534
535 if gateway.is_some() { check_duplicate_gateway_v4(&config, &iface)?; }
536 if gateway6.is_some() { check_duplicate_gateway_v6(&config, &iface)?; }
537
538 let interface = config.lookup_mut(&iface)?;
539
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);
544 }
545 }
546
547 if let Some(delete) = delete {
548 for delete_prop in delete {
549 match delete_prop {
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 }
565 }
566 }
567 }
568
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)?;
576 }
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)?;
581 }
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");
587 }
588 interface.bond_primary = bond_primary;
589 }
590 if bond_xmit_hash_policy.is_some() {
591 if mode != LinuxBondMode::ieee802_3ad &&
592 mode != LinuxBondMode::balance_xor
593 {
594 bail!("bond_xmit_hash_policy is only valid with LACP(802.3ad) or balance-xor mode");
595 }
596 interface.bond_xmit_hash_policy = bond_xmit_hash_policy;
597 }
598 }
599
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);
604 }
605
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);
610 }
611
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);
616 }
617
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);
622 }
623
624 if comments.is_some() { interface.comments = comments; }
625 if comments6.is_some() { interface.comments6 = comments6; }
626
627 if interface.cidr.is_some() || interface.gateway.is_some() {
628 interface.method = Some(NetworkConfigMethod::Static);
629 } else {
630 interface.method = Some(NetworkConfigMethod::Manual);
631 }
632
633 if interface.cidr6.is_some() || interface.gateway6.is_some() {
634 interface.method6 = Some(NetworkConfigMethod::Static);
635 } else {
636 interface.method6 = Some(NetworkConfigMethod::Manual);
637 }
638
639 network::save_config(&config)?;
640
641 Ok(())
642 }
643
644 #[api(
645 protected: true,
646 input: {
647 properties: {
648 node: {
649 schema: NODE_SCHEMA,
650 },
651 iface: {
652 schema: NETWORK_INTERFACE_NAME_SCHEMA,
653 },
654 digest: {
655 optional: true,
656 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
657 },
658 },
659 },
660 access: {
661 permission: &Permission::Privilege(&["system", "network", "interfaces", "{iface}"], PRIV_SYS_MODIFY, false),
662 },
663 )]
664 /// Remove network interface configuration.
665 pub fn delete_interface(iface: String, digest: Option<String>) -> Result<(), Error> {
666 let _lock = network::lock_config()?;
667
668 let (mut config, expected_digest) = network::config()?;
669
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)?;
673 }
674
675 let _interface = config.lookup(&iface)?; // check if interface exists
676
677 config.interfaces.remove(&iface);
678
679 network::save_config(&config)?;
680
681 Ok(())
682 }
683
684 #[api(
685 protected: true,
686 input: {
687 properties: {
688 node: {
689 schema: NODE_SCHEMA,
690 },
691 },
692 },
693 access: {
694 permission: &Permission::Privilege(&["system", "network", "interfaces"], PRIV_SYS_MODIFY, false),
695 },
696 )]
697 /// Reload network configuration (requires ifupdown2).
698 pub async fn reload_network_config(
699 rpcenv: &mut dyn RpcEnvironment,
700 ) -> Result<String, Error> {
701
702 network::assert_ifupdown2_installed()?;
703
704 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
705
706 let upid_str = WorkerTask::spawn("srvreload", Some(String::from("networking")), auth_id.to_string(), true, |_worker| async {
707
708 let _ = std::fs::rename(network::NETWORK_INTERFACES_NEW_FILENAME, network::NETWORK_INTERFACES_FILENAME);
709
710 network::network_reload()?;
711 Ok(())
712 })?;
713
714 Ok(upid_str)
715 }
716
717 #[api(
718 protected: true,
719 input: {
720 properties: {
721 node: {
722 schema: NODE_SCHEMA,
723 },
724 },
725 },
726 access: {
727 permission: &Permission::Privilege(&["system", "network", "interfaces"], PRIV_SYS_MODIFY, false),
728 },
729 )]
730 /// Revert network configuration (rm /etc/network/interfaces.new).
731 pub fn revert_network_config() -> Result<(), Error> {
732
733 let _ = std::fs::remove_file(network::NETWORK_INTERFACES_NEW_FILENAME);
734
735 Ok(())
736 }
737
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);
742
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);