]> git.proxmox.com Git - proxmox-backup.git/blame - src/api2/node/network.rs
bump proxmox dependency to 0.3.2 for timer / file locking
[proxmox-backup.git] / src / api2 / node / network.rs
CommitLineData
26d9aebc
DM
1use anyhow::{Error, bail};
2use serde_json::{Value, to_value};
3use ::serde::{Deserialize, Serialize};
b2b3485d 4
26d9aebc 5use proxmox::api::{api, ApiMethod, Router, RpcEnvironment, Permission};
3aedb738 6use proxmox::api::schema::parse_property_string;
a2479cfa 7
c2ffc685 8use crate::config::network::{self, NetworkConfig};
26d9aebc 9use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_SYS_MODIFY};
4ebf0eab 10use crate::api2::types::*;
7b22acd0 11use crate::server::{WorkerTask};
b2b3485d 12
3aedb738
DM
13fn split_interface_list(list: &str) -> Result<Vec<String>, Error> {
14 let value = parse_property_string(&list, &NETWORK_INTERFACE_ARRAY_SCHEMA)?;
15 Ok(value.as_array().unwrap().iter().map(|v| v.as_str().unwrap().to_string()).collect())
16}
17
c2ffc685
DM
18fn check_duplicate_gateway_v4(config: &NetworkConfig, iface: &str) -> Result<(), Error> {
19
20 let current_gateway_v4 = config.interfaces.iter()
21 .find(|(_, interface)| interface.gateway.is_some())
22 .map(|(name, _)| name.to_string());
23
24 if let Some(current_gateway_v4) = current_gateway_v4 {
25 if current_gateway_v4 != iface {
26 bail!("Default IPv4 gateway already exists on interface '{}'", current_gateway_v4);
27 }
28 }
29 Ok(())
30}
31
32fn check_duplicate_gateway_v6(config: &NetworkConfig, iface: &str) -> Result<(), Error> {
33
34 let current_gateway_v6 = config.interfaces.iter()
35 .find(|(_, interface)| interface.gateway6.is_some())
36 .map(|(name, _)| name.to_string());
37
38 if let Some(current_gateway_v6) = current_gateway_v6 {
39 if current_gateway_v6 != iface {
40 bail!("Default IPv6 gateway already exists on interface '{}'", current_gateway_v6);
41 }
42 }
43 Ok(())
44}
45
4b40148c
DM
46#[api(
47 input: {
48 properties: {
49 node: {
50 schema: NODE_SCHEMA,
51 },
52 },
53 },
54 returns: {
26d9aebc
DM
55 description: "List network devices (with config digest).",
56 type: Array,
57 items: {
58 type: Interface,
4b40148c
DM
59 },
60 },
61 access: {
74c08a57 62 permission: &Permission::Privilege(&["system", "network", "interfaces"], PRIV_SYS_AUDIT, false),
4b40148c
DM
63 },
64)]
26d9aebc
DM
65/// List all datastores
66pub fn list_network_devices(
6049b71f 67 _param: Value,
26d9aebc 68 _info: &ApiMethod,
e8d1da6a 69 mut rpcenv: &mut dyn RpcEnvironment,
6049b71f 70) -> Result<Value, Error> {
b2b3485d 71
26d9aebc
DM
72 let (config, digest) = network::config()?;
73 let digest = proxmox::tools::digest_to_hex(&digest);
74
75 let mut list = Vec::new();
76
7b22acd0
DM
77 for (iface, interface) in config.interfaces.iter() {
78 if iface == "lo" { continue; } // do not list lo
26d9aebc
DM
79 let mut item: Value = to_value(interface)?;
80 item["digest"] = digest.clone().into();
7b22acd0 81 item["iface"] = iface.to_string().into();
26d9aebc
DM
82 list.push(item);
83 }
84
85 let diff = network::changes()?;
86 if !diff.is_empty() {
e8d1da6a 87 rpcenv["changes"] = diff.into();
26d9aebc
DM
88 }
89
90 Ok(list.into())
91}
92
93#[api(
7b22acd0 94 input: {
26d9aebc
DM
95 properties: {
96 node: {
97 schema: NODE_SCHEMA,
98 },
7b22acd0 99 iface: {
26d9aebc
DM
100 schema: NETWORK_INTERFACE_NAME_SCHEMA,
101 },
102 },
103 },
104 returns: {
105 description: "The network interface configuration (with config digest).",
106 type: Interface,
107 },
108 access: {
74c08a57 109 permission: &Permission::Privilege(&["system", "network", "interfaces", "{name}"], PRIV_SYS_AUDIT, false),
26d9aebc
DM
110 },
111)]
112/// Read a network interface configuration.
7b22acd0 113pub fn read_interface(iface: String) -> Result<Value, Error> {
26d9aebc
DM
114
115 let (config, digest) = network::config()?;
116
7b22acd0 117 let interface = config.lookup(&iface)?;
26d9aebc
DM
118
119 let mut data: Value = to_value(interface)?;
120 data["digest"] = proxmox::tools::digest_to_hex(&digest).into();
121
122 Ok(data)
123}
124
96518331
DM
125
126#[api(
127 protected: true,
128 input: {
129 properties: {
130 node: {
131 schema: NODE_SCHEMA,
132 },
133 iface: {
134 schema: NETWORK_INTERFACE_NAME_SCHEMA,
135 },
136 "type": {
137 description: "Interface type.",
138 type: NetworkInterfaceType,
139 optional: true,
140 },
141 autostart: {
142 description: "Autostart interface.",
143 type: bool,
144 optional: true,
145 },
146 method: {
147 type: NetworkConfigMethod,
148 optional: true,
149 },
150 method6: {
151 type: NetworkConfigMethod,
152 optional: true,
153 },
154 comments: {
155 description: "Comments (inet, may span multiple lines)",
156 type: String,
157 optional: true,
158 },
159 comments6: {
160 description: "Comments (inet5, may span multiple lines)",
161 type: String,
162 optional: true,
163 },
164 cidr: {
165 schema: CIDR_V4_SCHEMA,
166 optional: true,
167 },
168 cidr6: {
169 schema: CIDR_V6_SCHEMA,
170 optional: true,
171 },
172 gateway: {
173 schema: IP_V4_SCHEMA,
174 optional: true,
175 },
176 gateway6: {
177 schema: IP_V6_SCHEMA,
178 optional: true,
179 },
180 mtu: {
181 description: "Maximum Transmission Unit.",
182 optional: true,
183 minimum: 46,
184 maximum: 65535,
185 default: 1500,
186 },
187 bridge_ports: {
188 schema: NETWORK_INTERFACE_LIST_SCHEMA,
189 optional: true,
190 },
191 bridge_vlan_aware: {
192 description: "Enable bridge vlan support.",
193 type: bool,
194 optional: true,
195 },
bab5d18c
DM
196 bond_mode: {
197 type: LinuxBondMode,
198 optional: true,
199 },
200 slaves: {
96518331
DM
201 schema: NETWORK_INTERFACE_LIST_SCHEMA,
202 optional: true,
203 },
204 },
205 },
206 access: {
207 permission: &Permission::Privilege(&["system", "network", "interfaces", "{iface}"], PRIV_SYS_MODIFY, false),
208 },
209)]
210/// Create network interface configuration.
211pub fn create_interface(
212 iface: String,
213 autostart: Option<bool>,
214 method: Option<NetworkConfigMethod>,
215 method6: Option<NetworkConfigMethod>,
216 comments: Option<String>,
217 comments6: Option<String>,
218 cidr: Option<String>,
219 gateway: Option<String>,
220 cidr6: Option<String>,
221 gateway6: Option<String>,
222 mtu: Option<u64>,
3aedb738 223 bridge_ports: Option<String>,
96518331 224 bridge_vlan_aware: Option<bool>,
bab5d18c 225 bond_mode: Option<LinuxBondMode>,
3aedb738 226 slaves: Option<String>,
96518331
DM
227 param: Value,
228) -> Result<(), Error> {
229
230 let interface_type = crate::tools::required_string_param(&param, "type")?;
231 let interface_type: NetworkInterfaceType = serde_json::from_value(interface_type.into())?;
232
233 let _lock = crate::tools::open_file_locked(network::NETWORK_LOCKFILE, std::time::Duration::new(10, 0))?;
234
235 let (mut config, _digest) = network::config()?;
236
237 if config.interfaces.contains_key(&iface) {
238 bail!("interface '{}' already exists", iface);
239 }
240
96518331
DM
241 let mut interface = Interface::new(iface.clone());
242 interface.interface_type = interface_type;
243
244 if let Some(autostart) = autostart { interface.autostart = autostart; }
245 if method.is_some() { interface.method = method; }
246 if method6.is_some() { interface.method6 = method6; }
247 if mtu.is_some() { interface.mtu = mtu; }
248 if comments.is_some() { interface.comments = comments; }
249 if comments6.is_some() { interface.comments6 = comments6; }
250
251 if let Some(cidr) = cidr {
252 let (_, _, is_v6) = network::parse_cidr(&cidr)?;
253 if is_v6 { bail!("invalid address type (expected IPv4, got IPv6)"); }
254 interface.cidr = Some(cidr);
255 }
256
257 if let Some(cidr6) = cidr6 {
258 let (_, _, is_v6) = network::parse_cidr(&cidr6)?;
259 if !is_v6 { bail!("invalid address type (expected IPv6, got IPv4)"); }
260 interface.cidr6 = Some(cidr6);
261 }
262
263 if let Some(gateway) = gateway {
264 let is_v6 = gateway.contains(':');
265 if is_v6 { bail!("invalid address type (expected IPv4, got IPv6)"); }
c2ffc685 266 check_duplicate_gateway_v4(&config, &iface)?;
96518331
DM
267 interface.gateway = Some(gateway);
268 }
269
270 if let Some(gateway6) = gateway6 {
271 let is_v6 = gateway6.contains(':');
272 if !is_v6 { bail!("invalid address type (expected IPv6, got IPv4)"); }
c2ffc685 273 check_duplicate_gateway_v6(&config, &iface)?;
96518331
DM
274 interface.gateway6 = Some(gateway6);
275 }
276
277 match interface_type {
278 NetworkInterfaceType::Bridge => {
3aedb738
DM
279 if let Some(ports) = bridge_ports {
280 let ports = split_interface_list(&ports)?;
281 interface.set_bridge_ports(ports)?;
282 }
96518331
DM
283 if bridge_vlan_aware.is_some() { interface.bridge_vlan_aware = bridge_vlan_aware; }
284 }
285 NetworkInterfaceType::Bond => {
bab5d18c 286 if bond_mode.is_some() { interface.bond_mode = bond_mode; }
3aedb738
DM
287 if let Some(slaves) = slaves {
288 let slaves = split_interface_list(&slaves)?;
289 interface.set_bond_slaves(slaves)?;
290 }
96518331
DM
291 }
292 _ => bail!("creating network interface type '{:?}' is not supported", interface_type),
293 }
294
295 if interface.cidr.is_some() || interface.gateway.is_some() {
296 interface.method = Some(NetworkConfigMethod::Static);
297 } else if interface.method.is_none() {
298 interface.method = Some(NetworkConfigMethod::Manual);
299 }
300
301 if interface.cidr6.is_some() || interface.gateway6.is_some() {
302 interface.method6 = Some(NetworkConfigMethod::Static);
303 } else if interface.method6.is_none() {
304 interface.method6 = Some(NetworkConfigMethod::Manual);
305 }
306
307 config.interfaces.insert(iface, interface);
308
309 network::save_config(&config)?;
310
311 Ok(())
312}
313
26d9aebc
DM
314#[api()]
315#[derive(Serialize, Deserialize)]
316#[allow(non_camel_case_types)]
317/// Deletable property name
318pub enum DeletableProperty {
319 /// Delete the IPv4 address property.
7b22acd0 320 cidr,
26d9aebc 321 /// Delete the IPv6 address property.
7b22acd0 322 cidr6,
26d9aebc 323 /// Delete the IPv4 gateway property.
7b22acd0 324 gateway,
26d9aebc 325 /// Delete the IPv6 gateway property.
7b22acd0 326 gateway6,
26d9aebc 327 /// Delete the whole IPv4 configuration entry.
7b22acd0 328 method,
26d9aebc 329 /// Delete the whole IPv6 configuration entry.
7b22acd0 330 method6,
26d9aebc 331 /// Delete IPv4 comments
7b22acd0 332 comments,
26d9aebc 333 /// Delete IPv6 comments
7b22acd0 334 comments6,
26d9aebc
DM
335 /// Delete mtu.
336 mtu,
7b22acd0
DM
337 /// Delete autostart flag
338 autostart,
26d9aebc
DM
339 /// Delete bridge ports (set to 'none')
340 bridge_ports,
add5861e 341 /// Delete bridge-vlan-aware flag
7b22acd0 342 bridge_vlan_aware,
26d9aebc 343 /// Delete bond-slaves (set to 'none')
bab5d18c 344 slaves,
26d9aebc
DM
345}
346
347
348#[api(
349 protected: true,
350 input: {
351 properties: {
352 node: {
353 schema: NODE_SCHEMA,
354 },
7b22acd0 355 iface: {
26d9aebc
DM
356 schema: NETWORK_INTERFACE_NAME_SCHEMA,
357 },
7b22acd0
DM
358 "type": {
359 description: "Interface type. If specified, need to match the current type.",
360 type: NetworkInterfaceType,
361 optional: true,
362 },
363 autostart: {
26d9aebc
DM
364 description: "Autostart interface.",
365 type: bool,
366 optional: true,
367 },
7b22acd0 368 method: {
26d9aebc
DM
369 type: NetworkConfigMethod,
370 optional: true,
371 },
7b22acd0 372 method6: {
26d9aebc
DM
373 type: NetworkConfigMethod,
374 optional: true,
375 },
7b22acd0 376 comments: {
26d9aebc
DM
377 description: "Comments (inet, may span multiple lines)",
378 type: String,
379 optional: true,
380 },
7b22acd0 381 comments6: {
26d9aebc
DM
382 description: "Comments (inet5, may span multiple lines)",
383 type: String,
384 optional: true,
385 },
7b22acd0
DM
386 cidr: {
387 schema: CIDR_V4_SCHEMA,
388 optional: true,
389 },
390 cidr6: {
391 schema: CIDR_V6_SCHEMA,
26d9aebc
DM
392 optional: true,
393 },
394 gateway: {
7b22acd0
DM
395 schema: IP_V4_SCHEMA,
396 optional: true,
397 },
398 gateway6: {
399 schema: IP_V6_SCHEMA,
26d9aebc
DM
400 optional: true,
401 },
402 mtu: {
403 description: "Maximum Transmission Unit.",
404 optional: true,
405 minimum: 46,
406 maximum: 65535,
407 default: 1500,
408 },
409 bridge_ports: {
410 schema: NETWORK_INTERFACE_LIST_SCHEMA,
411 optional: true,
412 },
7b22acd0
DM
413 bridge_vlan_aware: {
414 description: "Enable bridge vlan support.",
415 type: bool,
416 optional: true,
417 },
bab5d18c
DM
418 bond_mode: {
419 type: LinuxBondMode,
420 optional: true,
421 },
422 slaves: {
26d9aebc
DM
423 schema: NETWORK_INTERFACE_LIST_SCHEMA,
424 optional: true,
425 },
426 delete: {
427 description: "List of properties to delete.",
428 type: Array,
429 optional: true,
430 items: {
431 type: DeletableProperty,
432 }
433 },
434 digest: {
435 optional: true,
436 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
437 },
438 },
439 },
440 access: {
7b22acd0 441 permission: &Permission::Privilege(&["system", "network", "interfaces", "{iface}"], PRIV_SYS_MODIFY, false),
26d9aebc
DM
442 },
443)]
444/// Update network interface config.
445pub fn update_interface(
7b22acd0
DM
446 iface: String,
447 autostart: Option<bool>,
448 method: Option<NetworkConfigMethod>,
449 method6: Option<NetworkConfigMethod>,
450 comments: Option<String>,
451 comments6: Option<String>,
452 cidr: Option<String>,
26d9aebc 453 gateway: Option<String>,
7b22acd0
DM
454 cidr6: Option<String>,
455 gateway6: Option<String>,
26d9aebc 456 mtu: Option<u64>,
3aedb738 457 bridge_ports: Option<String>,
7b22acd0 458 bridge_vlan_aware: Option<bool>,
bab5d18c 459 bond_mode: Option<LinuxBondMode>,
3aedb738 460 slaves: Option<String>,
26d9aebc
DM
461 delete: Option<Vec<DeletableProperty>>,
462 digest: Option<String>,
7b22acd0 463 param: Value,
26d9aebc
DM
464) -> Result<(), Error> {
465
466 let _lock = crate::tools::open_file_locked(network::NETWORK_LOCKFILE, std::time::Duration::new(10, 0))?;
467
468 let (mut config, expected_digest) = network::config()?;
469
470 if let Some(ref digest) = digest {
471 let digest = proxmox::tools::hex_to_digest(digest)?;
472 crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
473 }
474
c2ffc685
DM
475 if gateway.is_some() { check_duplicate_gateway_v4(&config, &iface)?; }
476 if gateway6.is_some() { check_duplicate_gateway_v6(&config, &iface)?; }
26d9aebc 477
7b22acd0
DM
478 let interface = config.lookup_mut(&iface)?;
479
480 if let Some(interface_type) = param.get("type") {
481 let interface_type: NetworkInterfaceType = serde_json::from_value(interface_type.clone())?;
482 if interface_type != interface.interface_type {
483 bail!("got unexpected interface type ({:?} != {:?})", interface_type, interface.interface_type);
484 }
485 }
26d9aebc
DM
486
487 if let Some(delete) = delete {
488 for delete_prop in delete {
489 match delete_prop {
7b22acd0
DM
490 DeletableProperty::cidr => { interface.cidr = None; },
491 DeletableProperty::cidr6 => { interface.cidr6 = None; },
492 DeletableProperty::gateway => { interface.gateway = None; },
493 DeletableProperty::gateway6 => { interface.gateway6 = None; },
494 DeletableProperty::method => { interface.method = None; },
495 DeletableProperty::method6 => { interface.method6 = None; },
496 DeletableProperty::comments => { interface.comments = None; },
497 DeletableProperty::comments6 => { interface.comments6 = None; },
26d9aebc 498 DeletableProperty::mtu => { interface.mtu = None; },
7b22acd0 499 DeletableProperty::autostart => { interface.autostart = false; },
26d9aebc 500 DeletableProperty::bridge_ports => { interface.set_bridge_ports(Vec::new())?; }
7b22acd0 501 DeletableProperty::bridge_vlan_aware => { interface.bridge_vlan_aware = None; }
bab5d18c 502 DeletableProperty::slaves => { interface.set_bond_slaves(Vec::new())?; }
26d9aebc
DM
503 }
504 }
505 }
506
7b22acd0
DM
507 if let Some(autostart) = autostart { interface.autostart = autostart; }
508 if method.is_some() { interface.method = method; }
509 if method6.is_some() { interface.method6 = method6; }
26d9aebc 510 if mtu.is_some() { interface.mtu = mtu; }
3aedb738
DM
511 if let Some(ports) = bridge_ports {
512 let ports = split_interface_list(&ports)?;
513 interface.set_bridge_ports(ports)?;
514 }
7b22acd0 515 if bridge_vlan_aware.is_some() { interface.bridge_vlan_aware = bridge_vlan_aware; }
3aedb738
DM
516 if let Some(slaves) = slaves {
517 let slaves = split_interface_list(&slaves)?;
518 interface.set_bond_slaves(slaves)?;
519 }
bab5d18c 520 if bond_mode.is_some() { interface.bond_mode = bond_mode; }
26d9aebc 521
7b22acd0
DM
522 if let Some(cidr) = cidr {
523 let (_, _, is_v6) = network::parse_cidr(&cidr)?;
524 if is_v6 { bail!("invalid address type (expected IPv4, got IPv6)"); }
525 interface.cidr = Some(cidr);
526 }
527
528 if let Some(cidr6) = cidr6 {
529 let (_, _, is_v6) = network::parse_cidr(&cidr6)?;
530 if !is_v6 { bail!("invalid address type (expected IPv6, got IPv4)"); }
531 interface.cidr6 = Some(cidr6);
26d9aebc
DM
532 }
533
534 if let Some(gateway) = gateway {
535 let is_v6 = gateway.contains(':');
7b22acd0 536 if is_v6 { bail!("invalid address type (expected IPv4, got IPv6)"); }
7b22acd0
DM
537 interface.gateway = Some(gateway);
538 }
539
540 if let Some(gateway6) = gateway6 {
541 let is_v6 = gateway6.contains(':');
542 if !is_v6 { bail!("invalid address type (expected IPv6, got IPv4)"); }
7b22acd0
DM
543 interface.gateway6 = Some(gateway6);
544 }
545
546 if comments.is_some() { interface.comments = comments; }
547 if comments6.is_some() { interface.comments6 = comments6; }
548
549 if interface.cidr.is_some() || interface.gateway.is_some() {
550 interface.method = Some(NetworkConfigMethod::Static);
551 } else {
552 interface.method = Some(NetworkConfigMethod::Manual);
26d9aebc
DM
553 }
554
7b22acd0
DM
555 if interface.cidr6.is_some() || interface.gateway6.is_some() {
556 interface.method6 = Some(NetworkConfigMethod::Static);
557 } else {
558 interface.method6 = Some(NetworkConfigMethod::Manual);
559 }
26d9aebc
DM
560
561 network::save_config(&config)?;
562
563 Ok(())
b2b3485d
DM
564}
565
26d9aebc
DM
566#[api(
567 protected: true,
568 input: {
569 properties: {
570 node: {
571 schema: NODE_SCHEMA,
572 },
7b22acd0 573 iface: {
26d9aebc
DM
574 schema: NETWORK_INTERFACE_NAME_SCHEMA,
575 },
576 digest: {
577 optional: true,
578 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
579 },
580 },
581 },
582 access: {
7b22acd0 583 permission: &Permission::Privilege(&["system", "network", "interfaces", "{iface}"], PRIV_SYS_MODIFY, false),
26d9aebc
DM
584 },
585)]
586/// Remove network interface configuration.
7b22acd0 587pub fn delete_interface(iface: String, digest: Option<String>) -> Result<(), Error> {
26d9aebc
DM
588
589 let _lock = crate::tools::open_file_locked(network::NETWORK_LOCKFILE, std::time::Duration::new(10, 0))?;
590
591 let (mut config, expected_digest) = network::config()?;
592
593 if let Some(ref digest) = digest {
594 let digest = proxmox::tools::hex_to_digest(digest)?;
595 crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
596 }
597
7b22acd0 598 let _interface = config.lookup(&iface)?; // check if interface exists
26d9aebc 599
7b22acd0 600 config.interfaces.remove(&iface);
26d9aebc
DM
601
602 network::save_config(&config)?;
603
604 Ok(())
605}
606
607#[api(
7b22acd0 608 protected: true,
26d9aebc
DM
609 input: {
610 properties: {
611 node: {
612 schema: NODE_SCHEMA,
613 },
614 },
615 },
616 access: {
74c08a57 617 permission: &Permission::Privilege(&["system", "network", "interfaces"], PRIV_SYS_MODIFY, false),
26d9aebc
DM
618 },
619)]
620/// Reload network configuration (requires ifupdown2).
7b22acd0
DM
621pub async fn reload_network_config(
622 rpcenv: &mut dyn RpcEnvironment,
623) -> Result<String, Error> {
26d9aebc
DM
624
625 network::assert_ifupdown2_installed()?;
626
7b22acd0 627 let username = rpcenv.get_user().unwrap();
26d9aebc 628
7b22acd0 629 let upid_str = WorkerTask::spawn("srvreload", Some(String::from("networking")), &username.clone(), true, |_worker| async {
26d9aebc 630
7b22acd0
DM
631 let _ = std::fs::rename(network::NETWORK_INTERFACES_NEW_FILENAME, network::NETWORK_INTERFACES_FILENAME);
632
633 network::network_reload()?;
634 Ok(())
635 })?;
636
637 Ok(upid_str)
26d9aebc
DM
638}
639
640#[api(
3dd27a3b 641 protected: true,
26d9aebc
DM
642 input: {
643 properties: {
644 node: {
645 schema: NODE_SCHEMA,
646 },
647 },
648 },
649 access: {
74c08a57 650 permission: &Permission::Privilege(&["system", "network", "interfaces"], PRIV_SYS_MODIFY, false),
26d9aebc
DM
651 },
652)]
653/// Revert network configuration (rm /etc/network/interfaces.new).
654pub fn revert_network_config() -> Result<(), Error> {
655
656 let _ = std::fs::remove_file(network::NETWORK_INTERFACES_NEW_FILENAME);
657
658 Ok(())
659}
660
661const ITEM_ROUTER: Router = Router::new()
662 .get(&API_METHOD_READ_INTERFACE)
663 .put(&API_METHOD_UPDATE_INTERFACE)
664 .delete(&API_METHOD_DELETE_INTERFACE);
665
255f378a 666pub const ROUTER: Router = Router::new()
26d9aebc
DM
667 .get(&API_METHOD_LIST_NETWORK_DEVICES)
668 .put(&API_METHOD_RELOAD_NETWORK_CONFIG)
96518331 669 .post(&API_METHOD_CREATE_INTERFACE)
26d9aebc 670 .delete(&API_METHOD_REVERT_NETWORK_CONFIG)
7b22acd0 671 .match_all("iface", &ITEM_ROUTER);