]>
Commit | Line | Data |
---|---|---|
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 | use proxmox::tools::fs::open_file_locked; | |
8 | ||
9 | use crate::config::network::{self, NetworkConfig}; | |
10 | use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_SYS_MODIFY}; | |
11 | use crate::api2::types::*; | |
12 | use crate::server::{WorkerTask}; | |
13 | ||
14 | fn 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 | ||
19 | fn 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 | ||
33 | fn 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 | ||
47 | #[api( | |
48 | input: { | |
49 | properties: { | |
50 | node: { | |
51 | schema: NODE_SCHEMA, | |
52 | }, | |
53 | }, | |
54 | }, | |
55 | returns: { | |
56 | description: "List network devices (with config digest).", | |
57 | type: Array, | |
58 | items: { | |
59 | type: Interface, | |
60 | }, | |
61 | }, | |
62 | access: { | |
63 | permission: &Permission::Privilege(&["system", "network", "interfaces"], PRIV_SYS_AUDIT, false), | |
64 | }, | |
65 | )] | |
66 | /// List all datastores | |
67 | pub fn list_network_devices( | |
68 | _param: Value, | |
69 | _info: &ApiMethod, | |
70 | mut rpcenv: &mut dyn RpcEnvironment, | |
71 | ) -> Result<Value, Error> { | |
72 | ||
73 | let (config, digest) = network::config()?; | |
74 | let digest = proxmox::tools::digest_to_hex(&digest); | |
75 | ||
76 | let mut list = Vec::new(); | |
77 | ||
78 | for (iface, interface) in config.interfaces.iter() { | |
79 | if iface == "lo" { continue; } // do not list lo | |
80 | let mut item: Value = to_value(interface)?; | |
81 | item["digest"] = digest.clone().into(); | |
82 | item["iface"] = iface.to_string().into(); | |
83 | list.push(item); | |
84 | } | |
85 | ||
86 | let diff = network::changes()?; | |
87 | if !diff.is_empty() { | |
88 | rpcenv["changes"] = diff.into(); | |
89 | } | |
90 | ||
91 | Ok(list.into()) | |
92 | } | |
93 | ||
94 | #[api( | |
95 | input: { | |
96 | properties: { | |
97 | node: { | |
98 | schema: NODE_SCHEMA, | |
99 | }, | |
100 | iface: { | |
101 | schema: NETWORK_INTERFACE_NAME_SCHEMA, | |
102 | }, | |
103 | }, | |
104 | }, | |
105 | returns: { type: Interface }, | |
106 | access: { | |
107 | permission: &Permission::Privilege(&["system", "network", "interfaces", "{name}"], PRIV_SYS_AUDIT, false), | |
108 | }, | |
109 | )] | |
110 | /// Read a network interface configuration. | |
111 | pub fn read_interface(iface: String) -> Result<Value, Error> { | |
112 | ||
113 | let (config, digest) = network::config()?; | |
114 | ||
115 | let interface = config.lookup(&iface)?; | |
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 | ||
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": { | |
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 | }, | |
193 | bond_mode: { | |
194 | type: LinuxBondMode, | |
195 | optional: true, | |
196 | }, | |
197 | "bond-primary": { | |
198 | schema: NETWORK_INTERFACE_NAME_SCHEMA, | |
199 | optional: true, | |
200 | }, | |
201 | bond_xmit_hash_policy: { | |
202 | type: BondXmitHashPolicy, | |
203 | optional: true, | |
204 | }, | |
205 | slaves: { | |
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. | |
216 | #[allow(clippy::too_many_arguments)] | |
217 | pub 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>, | |
229 | bridge_ports: Option<String>, | |
230 | bridge_vlan_aware: Option<bool>, | |
231 | bond_mode: Option<LinuxBondMode>, | |
232 | bond_primary: Option<String>, | |
233 | bond_xmit_hash_policy: Option<BondXmitHashPolicy>, | |
234 | slaves: Option<String>, | |
235 | param: Value, | |
236 | ) -> Result<(), Error> { | |
237 | ||
238 | let interface_type = pbs_tools::json::required_string_param(¶m, "type")?; | |
239 | let interface_type: NetworkInterfaceType = serde_json::from_value(interface_type.into())?; | |
240 | ||
241 | let _lock = open_file_locked(network::NETWORK_LOCKFILE, std::time::Duration::new(10, 0), true)?; | |
242 | ||
243 | let (mut config, _digest) = network::config()?; | |
244 | ||
245 | if config.interfaces.contains_key(&iface) { | |
246 | bail!("interface '{}' already exists", iface); | |
247 | } | |
248 | ||
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)"); } | |
274 | check_duplicate_gateway_v4(&config, &iface)?; | |
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)"); } | |
281 | check_duplicate_gateway_v6(&config, &iface)?; | |
282 | interface.gateway6 = Some(gateway6); | |
283 | } | |
284 | ||
285 | match interface_type { | |
286 | NetworkInterfaceType::Bridge => { | |
287 | if let Some(ports) = bridge_ports { | |
288 | let ports = split_interface_list(&ports)?; | |
289 | interface.set_bridge_ports(ports)?; | |
290 | } | |
291 | if bridge_vlan_aware.is_some() { interface.bridge_vlan_aware = bridge_vlan_aware; } | |
292 | } | |
293 | NetworkInterfaceType::Bond => { | |
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 | } | |
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 | } | |
310 | } | |
311 | if let Some(slaves) = slaves { | |
312 | let slaves = split_interface_list(&slaves)?; | |
313 | interface.set_bond_slaves(slaves)?; | |
314 | } | |
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 | ||
338 | #[api()] | |
339 | #[derive(Serialize, Deserialize)] | |
340 | #[allow(non_camel_case_types)] | |
341 | /// Deletable property name | |
342 | pub enum DeletableProperty { | |
343 | /// Delete the IPv4 address property. | |
344 | cidr, | |
345 | /// Delete the IPv6 address property. | |
346 | cidr6, | |
347 | /// Delete the IPv4 gateway property. | |
348 | gateway, | |
349 | /// Delete the IPv6 gateway property. | |
350 | gateway6, | |
351 | /// Delete the whole IPv4 configuration entry. | |
352 | method, | |
353 | /// Delete the whole IPv6 configuration entry. | |
354 | method6, | |
355 | /// Delete IPv4 comments | |
356 | comments, | |
357 | /// Delete IPv6 comments | |
358 | comments6, | |
359 | /// Delete mtu. | |
360 | mtu, | |
361 | /// Delete autostart flag | |
362 | autostart, | |
363 | /// Delete bridge ports (set to 'none') | |
364 | bridge_ports, | |
365 | /// Delete bridge-vlan-aware flag | |
366 | bridge_vlan_aware, | |
367 | /// Delete bond-slaves (set to 'none') | |
368 | slaves, | |
369 | /// Delete bond-primary | |
370 | #[serde(rename = "bond-primary")] | |
371 | bond_primary, | |
372 | /// Delete bond transmit hash policy | |
373 | bond_xmit_hash_policy, | |
374 | } | |
375 | ||
376 | ||
377 | #[api( | |
378 | protected: true, | |
379 | input: { | |
380 | properties: { | |
381 | node: { | |
382 | schema: NODE_SCHEMA, | |
383 | }, | |
384 | iface: { | |
385 | schema: NETWORK_INTERFACE_NAME_SCHEMA, | |
386 | }, | |
387 | "type": { | |
388 | type: NetworkInterfaceType, | |
389 | optional: true, | |
390 | }, | |
391 | autostart: { | |
392 | description: "Autostart interface.", | |
393 | type: bool, | |
394 | optional: true, | |
395 | }, | |
396 | method: { | |
397 | type: NetworkConfigMethod, | |
398 | optional: true, | |
399 | }, | |
400 | method6: { | |
401 | type: NetworkConfigMethod, | |
402 | optional: true, | |
403 | }, | |
404 | comments: { | |
405 | description: "Comments (inet, may span multiple lines)", | |
406 | type: String, | |
407 | optional: true, | |
408 | }, | |
409 | comments6: { | |
410 | description: "Comments (inet5, may span multiple lines)", | |
411 | type: String, | |
412 | optional: true, | |
413 | }, | |
414 | cidr: { | |
415 | schema: CIDR_V4_SCHEMA, | |
416 | optional: true, | |
417 | }, | |
418 | cidr6: { | |
419 | schema: CIDR_V6_SCHEMA, | |
420 | optional: true, | |
421 | }, | |
422 | gateway: { | |
423 | schema: IP_V4_SCHEMA, | |
424 | optional: true, | |
425 | }, | |
426 | gateway6: { | |
427 | schema: IP_V6_SCHEMA, | |
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 | }, | |
441 | bridge_vlan_aware: { | |
442 | description: "Enable bridge vlan support.", | |
443 | type: bool, | |
444 | optional: true, | |
445 | }, | |
446 | bond_mode: { | |
447 | type: LinuxBondMode, | |
448 | optional: true, | |
449 | }, | |
450 | "bond-primary": { | |
451 | schema: NETWORK_INTERFACE_NAME_SCHEMA, | |
452 | optional: true, | |
453 | }, | |
454 | bond_xmit_hash_policy: { | |
455 | type: BondXmitHashPolicy, | |
456 | optional: true, | |
457 | }, | |
458 | slaves: { | |
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: { | |
477 | permission: &Permission::Privilege(&["system", "network", "interfaces", "{iface}"], PRIV_SYS_MODIFY, false), | |
478 | }, | |
479 | )] | |
480 | /// Update network interface config. | |
481 | #[allow(clippy::too_many_arguments)] | |
482 | pub fn update_interface( | |
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>, | |
490 | gateway: Option<String>, | |
491 | cidr6: Option<String>, | |
492 | gateway6: Option<String>, | |
493 | mtu: Option<u64>, | |
494 | bridge_ports: Option<String>, | |
495 | bridge_vlan_aware: Option<bool>, | |
496 | bond_mode: Option<LinuxBondMode>, | |
497 | bond_primary: Option<String>, | |
498 | bond_xmit_hash_policy: Option<BondXmitHashPolicy>, | |
499 | slaves: Option<String>, | |
500 | delete: Option<Vec<DeletableProperty>>, | |
501 | digest: Option<String>, | |
502 | param: Value, | |
503 | ) -> Result<(), Error> { | |
504 | ||
505 | let _lock = open_file_locked(network::NETWORK_LOCKFILE, std::time::Duration::new(10, 0), true)?; | |
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 | ||
514 | if gateway.is_some() { check_duplicate_gateway_v4(&config, &iface)?; } | |
515 | if gateway6.is_some() { check_duplicate_gateway_v6(&config, &iface)?; } | |
516 | ||
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 | } | |
525 | ||
526 | if let Some(delete) = delete { | |
527 | for delete_prop in delete { | |
528 | match delete_prop { | |
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; }, | |
537 | DeletableProperty::mtu => { interface.mtu = None; }, | |
538 | DeletableProperty::autostart => { interface.autostart = false; }, | |
539 | DeletableProperty::bridge_ports => { interface.set_bridge_ports(Vec::new())?; } | |
540 | DeletableProperty::bridge_vlan_aware => { interface.bridge_vlan_aware = None; } | |
541 | DeletableProperty::slaves => { interface.set_bond_slaves(Vec::new())?; } | |
542 | DeletableProperty::bond_primary => { interface.bond_primary = None; } | |
543 | DeletableProperty::bond_xmit_hash_policy => { interface.bond_xmit_hash_policy = None } | |
544 | } | |
545 | } | |
546 | } | |
547 | ||
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; } | |
551 | if mtu.is_some() { interface.mtu = mtu; } | |
552 | if let Some(ports) = bridge_ports { | |
553 | let ports = split_interface_list(&ports)?; | |
554 | interface.set_bridge_ports(ports)?; | |
555 | } | |
556 | if bridge_vlan_aware.is_some() { interface.bridge_vlan_aware = bridge_vlan_aware; } | |
557 | if let Some(slaves) = slaves { | |
558 | let slaves = split_interface_list(&slaves)?; | |
559 | interface.set_bond_slaves(slaves)?; | |
560 | } | |
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 | } | |
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 | } | |
577 | } | |
578 | ||
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); | |
589 | } | |
590 | ||
591 | if let Some(gateway) = gateway { | |
592 | let is_v6 = gateway.contains(':'); | |
593 | if is_v6 { bail!("invalid address type (expected IPv4, got IPv6)"); } | |
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)"); } | |
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); | |
610 | } | |
611 | ||
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 | } | |
617 | ||
618 | network::save_config(&config)?; | |
619 | ||
620 | Ok(()) | |
621 | } | |
622 | ||
623 | #[api( | |
624 | protected: true, | |
625 | input: { | |
626 | properties: { | |
627 | node: { | |
628 | schema: NODE_SCHEMA, | |
629 | }, | |
630 | iface: { | |
631 | schema: NETWORK_INTERFACE_NAME_SCHEMA, | |
632 | }, | |
633 | digest: { | |
634 | optional: true, | |
635 | schema: PROXMOX_CONFIG_DIGEST_SCHEMA, | |
636 | }, | |
637 | }, | |
638 | }, | |
639 | access: { | |
640 | permission: &Permission::Privilege(&["system", "network", "interfaces", "{iface}"], PRIV_SYS_MODIFY, false), | |
641 | }, | |
642 | )] | |
643 | /// Remove network interface configuration. | |
644 | pub fn delete_interface(iface: String, digest: Option<String>) -> Result<(), Error> { | |
645 | ||
646 | let _lock = open_file_locked(network::NETWORK_LOCKFILE, std::time::Duration::new(10, 0), true)?; | |
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 | ||
655 | let _interface = config.lookup(&iface)?; // check if interface exists | |
656 | ||
657 | config.interfaces.remove(&iface); | |
658 | ||
659 | network::save_config(&config)?; | |
660 | ||
661 | Ok(()) | |
662 | } | |
663 | ||
664 | #[api( | |
665 | protected: true, | |
666 | input: { | |
667 | properties: { | |
668 | node: { | |
669 | schema: NODE_SCHEMA, | |
670 | }, | |
671 | }, | |
672 | }, | |
673 | access: { | |
674 | permission: &Permission::Privilege(&["system", "network", "interfaces"], PRIV_SYS_MODIFY, false), | |
675 | }, | |
676 | )] | |
677 | /// Reload network configuration (requires ifupdown2). | |
678 | pub async fn reload_network_config( | |
679 | rpcenv: &mut dyn RpcEnvironment, | |
680 | ) -> Result<String, Error> { | |
681 | ||
682 | network::assert_ifupdown2_installed()?; | |
683 | ||
684 | let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; | |
685 | ||
686 | let upid_str = WorkerTask::spawn("srvreload", Some(String::from("networking")), auth_id, true, |_worker| async { | |
687 | ||
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) | |
695 | } | |
696 | ||
697 | #[api( | |
698 | protected: true, | |
699 | input: { | |
700 | properties: { | |
701 | node: { | |
702 | schema: NODE_SCHEMA, | |
703 | }, | |
704 | }, | |
705 | }, | |
706 | access: { | |
707 | permission: &Permission::Privilege(&["system", "network", "interfaces"], PRIV_SYS_MODIFY, false), | |
708 | }, | |
709 | )] | |
710 | /// Revert network configuration (rm /etc/network/interfaces.new). | |
711 | pub fn revert_network_config() -> Result<(), Error> { | |
712 | ||
713 | let _ = std::fs::remove_file(network::NETWORK_INTERFACES_NEW_FILENAME); | |
714 | ||
715 | Ok(()) | |
716 | } | |
717 | ||
718 | const ITEM_ROUTER: Router = Router::new() | |
719 | .get(&API_METHOD_READ_INTERFACE) | |
720 | .put(&API_METHOD_UPDATE_INTERFACE) | |
721 | .delete(&API_METHOD_DELETE_INTERFACE); | |
722 | ||
723 | pub const ROUTER: Router = Router::new() | |
724 | .get(&API_METHOD_LIST_NETWORK_DEVICES) | |
725 | .put(&API_METHOD_RELOAD_NETWORK_CONFIG) | |
726 | .post(&API_METHOD_CREATE_INTERFACE) | |
727 | .delete(&API_METHOD_REVERT_NETWORK_CONFIG) | |
728 | .match_all("iface", &ITEM_ROUTER); |