]> git.proxmox.com Git - proxmox-backup.git/blame - src/api2/node/network.rs
move worker_task.rs into proxmox-rest-server crate
[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
6f422880
DM
8use 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,
8cc3760e 12 NODE_SCHEMA, PRIV_SYS_AUDIT, PRIV_SYS_MODIFY,
6f422880
DM
13};
14use pbs_config::network::{self, NetworkConfig};
15
b9700a9f 16use proxmox_rest_server::WorkerTask;
b2b3485d 17
3aedb738
DM
18fn 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
c2ffc685
DM
23fn 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
37fn 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
6f422880
DM
51
52fn 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
60fn 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
4b40148c
DM
68#[api(
69 input: {
70 properties: {
71 node: {
72 schema: NODE_SCHEMA,
73 },
74 },
75 },
76 returns: {
26d9aebc
DM
77 description: "List network devices (with config digest).",
78 type: Array,
79 items: {
80 type: Interface,
4b40148c
DM
81 },
82 },
83 access: {
74c08a57 84 permission: &Permission::Privilege(&["system", "network", "interfaces"], PRIV_SYS_AUDIT, false),
4b40148c
DM
85 },
86)]
26d9aebc
DM
87/// List all datastores
88pub fn list_network_devices(
6049b71f 89 _param: Value,
26d9aebc 90 _info: &ApiMethod,
e8d1da6a 91 mut rpcenv: &mut dyn RpcEnvironment,
6049b71f 92) -> Result<Value, Error> {
b2b3485d 93
26d9aebc
DM
94 let (config, digest) = network::config()?;
95 let digest = proxmox::tools::digest_to_hex(&digest);
96
97 let mut list = Vec::new();
98
7b22acd0
DM
99 for (iface, interface) in config.interfaces.iter() {
100 if iface == "lo" { continue; } // do not list lo
26d9aebc
DM
101 let mut item: Value = to_value(interface)?;
102 item["digest"] = digest.clone().into();
7b22acd0 103 item["iface"] = iface.to_string().into();
26d9aebc
DM
104 list.push(item);
105 }
106
107 let diff = network::changes()?;
108 if !diff.is_empty() {
e8d1da6a 109 rpcenv["changes"] = diff.into();
26d9aebc
DM
110 }
111
112 Ok(list.into())
113}
114
115#[api(
7b22acd0 116 input: {
26d9aebc
DM
117 properties: {
118 node: {
119 schema: NODE_SCHEMA,
120 },
7b22acd0 121 iface: {
26d9aebc
DM
122 schema: NETWORK_INTERFACE_NAME_SCHEMA,
123 },
124 },
125 },
9b93c620 126 returns: { type: Interface },
26d9aebc 127 access: {
74c08a57 128 permission: &Permission::Privilege(&["system", "network", "interfaces", "{name}"], PRIV_SYS_AUDIT, false),
26d9aebc
DM
129 },
130)]
131/// Read a network interface configuration.
7b22acd0 132pub fn read_interface(iface: String) -> Result<Value, Error> {
26d9aebc
DM
133
134 let (config, digest) = network::config()?;
135
7b22acd0 136 let interface = config.lookup(&iface)?;
26d9aebc
DM
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
96518331
DM
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": {
96518331
DM
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 },
bab5d18c
DM
214 bond_mode: {
215 type: LinuxBondMode,
216 optional: true,
217 },
85959a99
DC
218 "bond-primary": {
219 schema: NETWORK_INTERFACE_NAME_SCHEMA,
220 optional: true,
221 },
8f2f3dd7
DC
222 bond_xmit_hash_policy: {
223 type: BondXmitHashPolicy,
224 optional: true,
225 },
bab5d18c 226 slaves: {
96518331
DM
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.
367c0ff7 237#[allow(clippy::too_many_arguments)]
96518331
DM
238pub 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>,
3aedb738 250 bridge_ports: Option<String>,
96518331 251 bridge_vlan_aware: Option<bool>,
bab5d18c 252 bond_mode: Option<LinuxBondMode>,
85959a99 253 bond_primary: Option<String>,
8f2f3dd7 254 bond_xmit_hash_policy: Option<BondXmitHashPolicy>,
3aedb738 255 slaves: Option<String>,
96518331
DM
256 param: Value,
257) -> Result<(), Error> {
258
3c8c2827 259 let interface_type = pbs_tools::json::required_string_param(&param, "type")?;
96518331
DM
260 let interface_type: NetworkInterfaceType = serde_json::from_value(interface_type.into())?;
261
6f422880 262 let _lock = network::lock_config()?;
96518331
DM
263
264 let (mut config, _digest) = network::config()?;
265
266 if config.interfaces.contains_key(&iface) {
267 bail!("interface '{}' already exists", iface);
268 }
269
96518331
DM
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)"); }
c2ffc685 295 check_duplicate_gateway_v4(&config, &iface)?;
96518331
DM
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)"); }
c2ffc685 302 check_duplicate_gateway_v6(&config, &iface)?;
96518331
DM
303 interface.gateway6 = Some(gateway6);
304 }
305
306 match interface_type {
307 NetworkInterfaceType::Bridge => {
3aedb738
DM
308 if let Some(ports) = bridge_ports {
309 let ports = split_interface_list(&ports)?;
6f422880 310 set_bridge_ports(&mut interface, ports)?;
3aedb738 311 }
96518331
DM
312 if bridge_vlan_aware.is_some() { interface.bridge_vlan_aware = bridge_vlan_aware; }
313 }
314 NetworkInterfaceType::Bond => {
85959a99
DC
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 }
8f2f3dd7
DC
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 }
85959a99 331 }
3aedb738
DM
332 if let Some(slaves) = slaves {
333 let slaves = split_interface_list(&slaves)?;
6f422880 334 set_bond_slaves(&mut interface, slaves)?;
3aedb738 335 }
96518331
DM
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
26d9aebc
DM
359#[api()]
360#[derive(Serialize, Deserialize)]
361#[allow(non_camel_case_types)]
362/// Deletable property name
363pub enum DeletableProperty {
364 /// Delete the IPv4 address property.
7b22acd0 365 cidr,
26d9aebc 366 /// Delete the IPv6 address property.
7b22acd0 367 cidr6,
26d9aebc 368 /// Delete the IPv4 gateway property.
7b22acd0 369 gateway,
26d9aebc 370 /// Delete the IPv6 gateway property.
7b22acd0 371 gateway6,
26d9aebc 372 /// Delete the whole IPv4 configuration entry.
7b22acd0 373 method,
26d9aebc 374 /// Delete the whole IPv6 configuration entry.
7b22acd0 375 method6,
26d9aebc 376 /// Delete IPv4 comments
7b22acd0 377 comments,
26d9aebc 378 /// Delete IPv6 comments
7b22acd0 379 comments6,
26d9aebc
DM
380 /// Delete mtu.
381 mtu,
7b22acd0
DM
382 /// Delete autostart flag
383 autostart,
26d9aebc
DM
384 /// Delete bridge ports (set to 'none')
385 bridge_ports,
add5861e 386 /// Delete bridge-vlan-aware flag
7b22acd0 387 bridge_vlan_aware,
26d9aebc 388 /// Delete bond-slaves (set to 'none')
bab5d18c 389 slaves,
85959a99
DC
390 /// Delete bond-primary
391 #[serde(rename = "bond-primary")]
392 bond_primary,
8f2f3dd7
DC
393 /// Delete bond transmit hash policy
394 bond_xmit_hash_policy,
26d9aebc
DM
395}
396
397
398#[api(
399 protected: true,
400 input: {
401 properties: {
402 node: {
403 schema: NODE_SCHEMA,
404 },
7b22acd0 405 iface: {
26d9aebc
DM
406 schema: NETWORK_INTERFACE_NAME_SCHEMA,
407 },
7b22acd0 408 "type": {
7b22acd0
DM
409 type: NetworkInterfaceType,
410 optional: true,
411 },
412 autostart: {
26d9aebc
DM
413 description: "Autostart interface.",
414 type: bool,
415 optional: true,
416 },
7b22acd0 417 method: {
26d9aebc
DM
418 type: NetworkConfigMethod,
419 optional: true,
420 },
7b22acd0 421 method6: {
26d9aebc
DM
422 type: NetworkConfigMethod,
423 optional: true,
424 },
7b22acd0 425 comments: {
26d9aebc
DM
426 description: "Comments (inet, may span multiple lines)",
427 type: String,
428 optional: true,
429 },
7b22acd0 430 comments6: {
26d9aebc
DM
431 description: "Comments (inet5, may span multiple lines)",
432 type: String,
433 optional: true,
434 },
7b22acd0
DM
435 cidr: {
436 schema: CIDR_V4_SCHEMA,
437 optional: true,
438 },
439 cidr6: {
440 schema: CIDR_V6_SCHEMA,
26d9aebc
DM
441 optional: true,
442 },
443 gateway: {
7b22acd0
DM
444 schema: IP_V4_SCHEMA,
445 optional: true,
446 },
447 gateway6: {
448 schema: IP_V6_SCHEMA,
26d9aebc
DM
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 },
7b22acd0
DM
462 bridge_vlan_aware: {
463 description: "Enable bridge vlan support.",
464 type: bool,
465 optional: true,
466 },
bab5d18c
DM
467 bond_mode: {
468 type: LinuxBondMode,
469 optional: true,
470 },
85959a99
DC
471 "bond-primary": {
472 schema: NETWORK_INTERFACE_NAME_SCHEMA,
473 optional: true,
474 },
8f2f3dd7
DC
475 bond_xmit_hash_policy: {
476 type: BondXmitHashPolicy,
477 optional: true,
478 },
bab5d18c 479 slaves: {
26d9aebc
DM
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: {
7b22acd0 498 permission: &Permission::Privilege(&["system", "network", "interfaces", "{iface}"], PRIV_SYS_MODIFY, false),
26d9aebc
DM
499 },
500)]
501/// Update network interface config.
367c0ff7 502#[allow(clippy::too_many_arguments)]
26d9aebc 503pub fn update_interface(
7b22acd0
DM
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>,
26d9aebc 511 gateway: Option<String>,
7b22acd0
DM
512 cidr6: Option<String>,
513 gateway6: Option<String>,
26d9aebc 514 mtu: Option<u64>,
3aedb738 515 bridge_ports: Option<String>,
7b22acd0 516 bridge_vlan_aware: Option<bool>,
bab5d18c 517 bond_mode: Option<LinuxBondMode>,
85959a99 518 bond_primary: Option<String>,
8f2f3dd7 519 bond_xmit_hash_policy: Option<BondXmitHashPolicy>,
3aedb738 520 slaves: Option<String>,
26d9aebc
DM
521 delete: Option<Vec<DeletableProperty>>,
522 digest: Option<String>,
7b22acd0 523 param: Value,
26d9aebc
DM
524) -> Result<(), Error> {
525
6f422880 526 let _lock = network::lock_config()?;
26d9aebc
DM
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
c2ffc685
DM
535 if gateway.is_some() { check_duplicate_gateway_v4(&config, &iface)?; }
536 if gateway6.is_some() { check_duplicate_gateway_v6(&config, &iface)?; }
26d9aebc 537
7b22acd0
DM
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 }
26d9aebc
DM
546
547 if let Some(delete) = delete {
548 for delete_prop in delete {
549 match delete_prop {
7b22acd0
DM
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; },
26d9aebc 558 DeletableProperty::mtu => { interface.mtu = None; },
7b22acd0 559 DeletableProperty::autostart => { interface.autostart = false; },
6f422880 560 DeletableProperty::bridge_ports => { set_bridge_ports(interface, Vec::new())?; }
7b22acd0 561 DeletableProperty::bridge_vlan_aware => { interface.bridge_vlan_aware = None; }
6f422880 562 DeletableProperty::slaves => { set_bond_slaves(interface, Vec::new())?; }
85959a99 563 DeletableProperty::bond_primary => { interface.bond_primary = None; }
8f2f3dd7 564 DeletableProperty::bond_xmit_hash_policy => { interface.bond_xmit_hash_policy = None }
26d9aebc
DM
565 }
566 }
567 }
568
7b22acd0
DM
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; }
26d9aebc 572 if mtu.is_some() { interface.mtu = mtu; }
3aedb738
DM
573 if let Some(ports) = bridge_ports {
574 let ports = split_interface_list(&ports)?;
6f422880 575 set_bridge_ports(interface, ports)?;
3aedb738 576 }
7b22acd0 577 if bridge_vlan_aware.is_some() { interface.bridge_vlan_aware = bridge_vlan_aware; }
3aedb738
DM
578 if let Some(slaves) = slaves {
579 let slaves = split_interface_list(&slaves)?;
6f422880 580 set_bond_slaves(interface, slaves)?;
3aedb738 581 }
85959a99
DC
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 }
8f2f3dd7
DC
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 }
85959a99 598 }
26d9aebc 599
7b22acd0
DM
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);
26d9aebc
DM
610 }
611
612 if let Some(gateway) = gateway {
613 let is_v6 = gateway.contains(':');
7b22acd0 614 if is_v6 { bail!("invalid address type (expected IPv4, got IPv6)"); }
7b22acd0
DM
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)"); }
7b22acd0
DM
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);
26d9aebc
DM
631 }
632
7b22acd0
DM
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 }
26d9aebc
DM
638
639 network::save_config(&config)?;
640
641 Ok(())
b2b3485d
DM
642}
643
26d9aebc
DM
644#[api(
645 protected: true,
646 input: {
647 properties: {
648 node: {
649 schema: NODE_SCHEMA,
650 },
7b22acd0 651 iface: {
26d9aebc
DM
652 schema: NETWORK_INTERFACE_NAME_SCHEMA,
653 },
654 digest: {
655 optional: true,
656 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
657 },
658 },
659 },
660 access: {
7b22acd0 661 permission: &Permission::Privilege(&["system", "network", "interfaces", "{iface}"], PRIV_SYS_MODIFY, false),
26d9aebc
DM
662 },
663)]
664/// Remove network interface configuration.
7b22acd0 665pub fn delete_interface(iface: String, digest: Option<String>) -> Result<(), Error> {
6f422880 666 let _lock = network::lock_config()?;
26d9aebc
DM
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
7b22acd0 675 let _interface = config.lookup(&iface)?; // check if interface exists
26d9aebc 676
7b22acd0 677 config.interfaces.remove(&iface);
26d9aebc
DM
678
679 network::save_config(&config)?;
680
681 Ok(())
682}
683
684#[api(
7b22acd0 685 protected: true,
26d9aebc
DM
686 input: {
687 properties: {
688 node: {
689 schema: NODE_SCHEMA,
690 },
691 },
692 },
693 access: {
74c08a57 694 permission: &Permission::Privilege(&["system", "network", "interfaces"], PRIV_SYS_MODIFY, false),
26d9aebc
DM
695 },
696)]
697/// Reload network configuration (requires ifupdown2).
7b22acd0
DM
698pub async fn reload_network_config(
699 rpcenv: &mut dyn RpcEnvironment,
700) -> Result<String, Error> {
26d9aebc
DM
701
702 network::assert_ifupdown2_installed()?;
703
e6dc35ac 704 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
26d9aebc 705
049a22a3 706 let upid_str = WorkerTask::spawn("srvreload", Some(String::from("networking")), auth_id.to_string(), true, |_worker| async {
26d9aebc 707
7b22acd0
DM
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)
26d9aebc
DM
715}
716
717#[api(
3dd27a3b 718 protected: true,
26d9aebc
DM
719 input: {
720 properties: {
721 node: {
722 schema: NODE_SCHEMA,
723 },
724 },
725 },
726 access: {
74c08a57 727 permission: &Permission::Privilege(&["system", "network", "interfaces"], PRIV_SYS_MODIFY, false),
26d9aebc
DM
728 },
729)]
730/// Revert network configuration (rm /etc/network/interfaces.new).
731pub fn revert_network_config() -> Result<(), Error> {
732
733 let _ = std::fs::remove_file(network::NETWORK_INTERFACES_NEW_FILENAME);
734
735 Ok(())
736}
737
738const ITEM_ROUTER: Router = Router::new()
739 .get(&API_METHOD_READ_INTERFACE)
740 .put(&API_METHOD_UPDATE_INTERFACE)
741 .delete(&API_METHOD_DELETE_INTERFACE);
742
255f378a 743pub const ROUTER: Router = Router::new()
26d9aebc
DM
744 .get(&API_METHOD_LIST_NETWORK_DEVICES)
745 .put(&API_METHOD_RELOAD_NETWORK_CONFIG)
96518331 746 .post(&API_METHOD_CREATE_INTERFACE)
26d9aebc 747 .delete(&API_METHOD_REVERT_NETWORK_CONFIG)
7b22acd0 748 .match_all("iface", &ITEM_ROUTER);