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