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