]>
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 | }, | |
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 | 111 | pub 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 |
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>, | |
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(¶m, "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 | |
342 | pub 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 | 482 | pub 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 | 644 | pub 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 |
678 | pub 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). | |
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 | ||
255f378a | 723 | pub 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); |