]>
Commit | Line | Data |
---|---|---|
26d9aebc DM |
1 | use anyhow::{Error, bail}; |
2 | use serde_json::{Value, to_value}; | |
3 | use ::serde::{Deserialize, Serialize}; | |
b2b3485d | 4 | |
26d9aebc | 5 | use proxmox::api::{api, ApiMethod, Router, RpcEnvironment, Permission}; |
3aedb738 | 6 | use proxmox::api::schema::parse_property_string; |
98c259b4 | 7 | use proxmox::tools::fs::open_file_locked; |
a2479cfa | 8 | |
c2ffc685 | 9 | use crate::config::network::{self, NetworkConfig}; |
26d9aebc | 10 | use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_SYS_MODIFY}; |
4ebf0eab | 11 | use crate::api2::types::*; |
7b22acd0 | 12 | use crate::server::{WorkerTask}; |
b2b3485d | 13 | |
3aedb738 DM |
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 | ||
c2ffc685 DM |
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 | ||
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 |
67 | pub 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 | 114 | pub 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. | |
220 | pub 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(¶m, "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 | |
345 | pub 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. | |
485 | pub 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 | 647 | pub 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 |
681 | pub 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). | |
714 | pub fn revert_network_config() -> Result<(), Error> { | |
715 | ||
716 | let _ = std::fs::remove_file(network::NETWORK_INTERFACES_NEW_FILENAME); | |
717 | ||
718 | Ok(()) | |
719 | } | |
720 | ||
721 | const 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 | 726 | pub 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); |