]>
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; |
a2479cfa | 7 | |
c2ffc685 | 8 | use crate::config::network::{self, NetworkConfig}; |
26d9aebc | 9 | use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_SYS_MODIFY}; |
4ebf0eab | 10 | use crate::api2::types::*; |
7b22acd0 | 11 | use crate::server::{WorkerTask}; |
b2b3485d | 12 | |
3aedb738 DM |
13 | fn split_interface_list(list: &str) -> Result<Vec<String>, Error> { |
14 | let value = parse_property_string(&list, &NETWORK_INTERFACE_ARRAY_SCHEMA)?; | |
15 | Ok(value.as_array().unwrap().iter().map(|v| v.as_str().unwrap().to_string()).collect()) | |
16 | } | |
17 | ||
c2ffc685 DM |
18 | fn check_duplicate_gateway_v4(config: &NetworkConfig, iface: &str) -> Result<(), Error> { |
19 | ||
20 | let current_gateway_v4 = config.interfaces.iter() | |
21 | .find(|(_, interface)| interface.gateway.is_some()) | |
22 | .map(|(name, _)| name.to_string()); | |
23 | ||
24 | if let Some(current_gateway_v4) = current_gateway_v4 { | |
25 | if current_gateway_v4 != iface { | |
26 | bail!("Default IPv4 gateway already exists on interface '{}'", current_gateway_v4); | |
27 | } | |
28 | } | |
29 | Ok(()) | |
30 | } | |
31 | ||
32 | fn check_duplicate_gateway_v6(config: &NetworkConfig, iface: &str) -> Result<(), Error> { | |
33 | ||
34 | let current_gateway_v6 = config.interfaces.iter() | |
35 | .find(|(_, interface)| interface.gateway6.is_some()) | |
36 | .map(|(name, _)| name.to_string()); | |
37 | ||
38 | if let Some(current_gateway_v6) = current_gateway_v6 { | |
39 | if current_gateway_v6 != iface { | |
40 | bail!("Default IPv6 gateway already exists on interface '{}'", current_gateway_v6); | |
41 | } | |
42 | } | |
43 | Ok(()) | |
44 | } | |
45 | ||
4b40148c DM |
46 | #[api( |
47 | input: { | |
48 | properties: { | |
49 | node: { | |
50 | schema: NODE_SCHEMA, | |
51 | }, | |
52 | }, | |
53 | }, | |
54 | returns: { | |
26d9aebc DM |
55 | description: "List network devices (with config digest).", |
56 | type: Array, | |
57 | items: { | |
58 | type: Interface, | |
4b40148c DM |
59 | }, |
60 | }, | |
61 | access: { | |
74c08a57 | 62 | permission: &Permission::Privilege(&["system", "network", "interfaces"], PRIV_SYS_AUDIT, false), |
4b40148c DM |
63 | }, |
64 | )] | |
26d9aebc DM |
65 | /// List all datastores |
66 | pub fn list_network_devices( | |
6049b71f | 67 | _param: Value, |
26d9aebc | 68 | _info: &ApiMethod, |
e8d1da6a | 69 | mut rpcenv: &mut dyn RpcEnvironment, |
6049b71f | 70 | ) -> Result<Value, Error> { |
b2b3485d | 71 | |
26d9aebc DM |
72 | let (config, digest) = network::config()?; |
73 | let digest = proxmox::tools::digest_to_hex(&digest); | |
74 | ||
75 | let mut list = Vec::new(); | |
76 | ||
7b22acd0 DM |
77 | for (iface, interface) in config.interfaces.iter() { |
78 | if iface == "lo" { continue; } // do not list lo | |
26d9aebc DM |
79 | let mut item: Value = to_value(interface)?; |
80 | item["digest"] = digest.clone().into(); | |
7b22acd0 | 81 | item["iface"] = iface.to_string().into(); |
26d9aebc DM |
82 | list.push(item); |
83 | } | |
84 | ||
85 | let diff = network::changes()?; | |
86 | if !diff.is_empty() { | |
e8d1da6a | 87 | rpcenv["changes"] = diff.into(); |
26d9aebc DM |
88 | } |
89 | ||
90 | Ok(list.into()) | |
91 | } | |
92 | ||
93 | #[api( | |
7b22acd0 | 94 | input: { |
26d9aebc DM |
95 | properties: { |
96 | node: { | |
97 | schema: NODE_SCHEMA, | |
98 | }, | |
7b22acd0 | 99 | iface: { |
26d9aebc DM |
100 | schema: NETWORK_INTERFACE_NAME_SCHEMA, |
101 | }, | |
102 | }, | |
103 | }, | |
104 | returns: { | |
105 | description: "The network interface configuration (with config digest).", | |
106 | type: Interface, | |
107 | }, | |
108 | access: { | |
74c08a57 | 109 | permission: &Permission::Privilege(&["system", "network", "interfaces", "{name}"], PRIV_SYS_AUDIT, false), |
26d9aebc DM |
110 | }, |
111 | )] | |
112 | /// Read a network interface configuration. | |
7b22acd0 | 113 | pub fn read_interface(iface: String) -> Result<Value, Error> { |
26d9aebc DM |
114 | |
115 | let (config, digest) = network::config()?; | |
116 | ||
7b22acd0 | 117 | let interface = config.lookup(&iface)?; |
26d9aebc DM |
118 | |
119 | let mut data: Value = to_value(interface)?; | |
120 | data["digest"] = proxmox::tools::digest_to_hex(&digest).into(); | |
121 | ||
122 | Ok(data) | |
123 | } | |
124 | ||
96518331 DM |
125 | |
126 | #[api( | |
127 | protected: true, | |
128 | input: { | |
129 | properties: { | |
130 | node: { | |
131 | schema: NODE_SCHEMA, | |
132 | }, | |
133 | iface: { | |
134 | schema: NETWORK_INTERFACE_NAME_SCHEMA, | |
135 | }, | |
136 | "type": { | |
137 | description: "Interface type.", | |
138 | type: NetworkInterfaceType, | |
139 | optional: true, | |
140 | }, | |
141 | autostart: { | |
142 | description: "Autostart interface.", | |
143 | type: bool, | |
144 | optional: true, | |
145 | }, | |
146 | method: { | |
147 | type: NetworkConfigMethod, | |
148 | optional: true, | |
149 | }, | |
150 | method6: { | |
151 | type: NetworkConfigMethod, | |
152 | optional: true, | |
153 | }, | |
154 | comments: { | |
155 | description: "Comments (inet, may span multiple lines)", | |
156 | type: String, | |
157 | optional: true, | |
158 | }, | |
159 | comments6: { | |
160 | description: "Comments (inet5, may span multiple lines)", | |
161 | type: String, | |
162 | optional: true, | |
163 | }, | |
164 | cidr: { | |
165 | schema: CIDR_V4_SCHEMA, | |
166 | optional: true, | |
167 | }, | |
168 | cidr6: { | |
169 | schema: CIDR_V6_SCHEMA, | |
170 | optional: true, | |
171 | }, | |
172 | gateway: { | |
173 | schema: IP_V4_SCHEMA, | |
174 | optional: true, | |
175 | }, | |
176 | gateway6: { | |
177 | schema: IP_V6_SCHEMA, | |
178 | optional: true, | |
179 | }, | |
180 | mtu: { | |
181 | description: "Maximum Transmission Unit.", | |
182 | optional: true, | |
183 | minimum: 46, | |
184 | maximum: 65535, | |
185 | default: 1500, | |
186 | }, | |
187 | bridge_ports: { | |
188 | schema: NETWORK_INTERFACE_LIST_SCHEMA, | |
189 | optional: true, | |
190 | }, | |
191 | bridge_vlan_aware: { | |
192 | description: "Enable bridge vlan support.", | |
193 | type: bool, | |
194 | optional: true, | |
195 | }, | |
bab5d18c DM |
196 | bond_mode: { |
197 | type: LinuxBondMode, | |
198 | optional: true, | |
199 | }, | |
200 | slaves: { | |
96518331 DM |
201 | schema: NETWORK_INTERFACE_LIST_SCHEMA, |
202 | optional: true, | |
203 | }, | |
204 | }, | |
205 | }, | |
206 | access: { | |
207 | permission: &Permission::Privilege(&["system", "network", "interfaces", "{iface}"], PRIV_SYS_MODIFY, false), | |
208 | }, | |
209 | )] | |
210 | /// Create network interface configuration. | |
211 | pub fn create_interface( | |
212 | iface: String, | |
213 | autostart: Option<bool>, | |
214 | method: Option<NetworkConfigMethod>, | |
215 | method6: Option<NetworkConfigMethod>, | |
216 | comments: Option<String>, | |
217 | comments6: Option<String>, | |
218 | cidr: Option<String>, | |
219 | gateway: Option<String>, | |
220 | cidr6: Option<String>, | |
221 | gateway6: Option<String>, | |
222 | mtu: Option<u64>, | |
3aedb738 | 223 | bridge_ports: Option<String>, |
96518331 | 224 | bridge_vlan_aware: Option<bool>, |
bab5d18c | 225 | bond_mode: Option<LinuxBondMode>, |
3aedb738 | 226 | slaves: Option<String>, |
96518331 DM |
227 | param: Value, |
228 | ) -> Result<(), Error> { | |
229 | ||
230 | let interface_type = crate::tools::required_string_param(¶m, "type")?; | |
231 | let interface_type: NetworkInterfaceType = serde_json::from_value(interface_type.into())?; | |
232 | ||
233 | let _lock = crate::tools::open_file_locked(network::NETWORK_LOCKFILE, std::time::Duration::new(10, 0))?; | |
234 | ||
235 | let (mut config, _digest) = network::config()?; | |
236 | ||
237 | if config.interfaces.contains_key(&iface) { | |
238 | bail!("interface '{}' already exists", iface); | |
239 | } | |
240 | ||
96518331 DM |
241 | let mut interface = Interface::new(iface.clone()); |
242 | interface.interface_type = interface_type; | |
243 | ||
244 | if let Some(autostart) = autostart { interface.autostart = autostart; } | |
245 | if method.is_some() { interface.method = method; } | |
246 | if method6.is_some() { interface.method6 = method6; } | |
247 | if mtu.is_some() { interface.mtu = mtu; } | |
248 | if comments.is_some() { interface.comments = comments; } | |
249 | if comments6.is_some() { interface.comments6 = comments6; } | |
250 | ||
251 | if let Some(cidr) = cidr { | |
252 | let (_, _, is_v6) = network::parse_cidr(&cidr)?; | |
253 | if is_v6 { bail!("invalid address type (expected IPv4, got IPv6)"); } | |
254 | interface.cidr = Some(cidr); | |
255 | } | |
256 | ||
257 | if let Some(cidr6) = cidr6 { | |
258 | let (_, _, is_v6) = network::parse_cidr(&cidr6)?; | |
259 | if !is_v6 { bail!("invalid address type (expected IPv6, got IPv4)"); } | |
260 | interface.cidr6 = Some(cidr6); | |
261 | } | |
262 | ||
263 | if let Some(gateway) = gateway { | |
264 | let is_v6 = gateway.contains(':'); | |
265 | if is_v6 { bail!("invalid address type (expected IPv4, got IPv6)"); } | |
c2ffc685 | 266 | check_duplicate_gateway_v4(&config, &iface)?; |
96518331 DM |
267 | interface.gateway = Some(gateway); |
268 | } | |
269 | ||
270 | if let Some(gateway6) = gateway6 { | |
271 | let is_v6 = gateway6.contains(':'); | |
272 | if !is_v6 { bail!("invalid address type (expected IPv6, got IPv4)"); } | |
c2ffc685 | 273 | check_duplicate_gateway_v6(&config, &iface)?; |
96518331 DM |
274 | interface.gateway6 = Some(gateway6); |
275 | } | |
276 | ||
277 | match interface_type { | |
278 | NetworkInterfaceType::Bridge => { | |
3aedb738 DM |
279 | if let Some(ports) = bridge_ports { |
280 | let ports = split_interface_list(&ports)?; | |
281 | interface.set_bridge_ports(ports)?; | |
282 | } | |
96518331 DM |
283 | if bridge_vlan_aware.is_some() { interface.bridge_vlan_aware = bridge_vlan_aware; } |
284 | } | |
285 | NetworkInterfaceType::Bond => { | |
bab5d18c | 286 | if bond_mode.is_some() { interface.bond_mode = bond_mode; } |
3aedb738 DM |
287 | if let Some(slaves) = slaves { |
288 | let slaves = split_interface_list(&slaves)?; | |
289 | interface.set_bond_slaves(slaves)?; | |
290 | } | |
96518331 DM |
291 | } |
292 | _ => bail!("creating network interface type '{:?}' is not supported", interface_type), | |
293 | } | |
294 | ||
295 | if interface.cidr.is_some() || interface.gateway.is_some() { | |
296 | interface.method = Some(NetworkConfigMethod::Static); | |
297 | } else if interface.method.is_none() { | |
298 | interface.method = Some(NetworkConfigMethod::Manual); | |
299 | } | |
300 | ||
301 | if interface.cidr6.is_some() || interface.gateway6.is_some() { | |
302 | interface.method6 = Some(NetworkConfigMethod::Static); | |
303 | } else if interface.method6.is_none() { | |
304 | interface.method6 = Some(NetworkConfigMethod::Manual); | |
305 | } | |
306 | ||
307 | config.interfaces.insert(iface, interface); | |
308 | ||
309 | network::save_config(&config)?; | |
310 | ||
311 | Ok(()) | |
312 | } | |
313 | ||
26d9aebc DM |
314 | #[api()] |
315 | #[derive(Serialize, Deserialize)] | |
316 | #[allow(non_camel_case_types)] | |
317 | /// Deletable property name | |
318 | pub enum DeletableProperty { | |
319 | /// Delete the IPv4 address property. | |
7b22acd0 | 320 | cidr, |
26d9aebc | 321 | /// Delete the IPv6 address property. |
7b22acd0 | 322 | cidr6, |
26d9aebc | 323 | /// Delete the IPv4 gateway property. |
7b22acd0 | 324 | gateway, |
26d9aebc | 325 | /// Delete the IPv6 gateway property. |
7b22acd0 | 326 | gateway6, |
26d9aebc | 327 | /// Delete the whole IPv4 configuration entry. |
7b22acd0 | 328 | method, |
26d9aebc | 329 | /// Delete the whole IPv6 configuration entry. |
7b22acd0 | 330 | method6, |
26d9aebc | 331 | /// Delete IPv4 comments |
7b22acd0 | 332 | comments, |
26d9aebc | 333 | /// Delete IPv6 comments |
7b22acd0 | 334 | comments6, |
26d9aebc DM |
335 | /// Delete mtu. |
336 | mtu, | |
7b22acd0 DM |
337 | /// Delete autostart flag |
338 | autostart, | |
26d9aebc DM |
339 | /// Delete bridge ports (set to 'none') |
340 | bridge_ports, | |
add5861e | 341 | /// Delete bridge-vlan-aware flag |
7b22acd0 | 342 | bridge_vlan_aware, |
26d9aebc | 343 | /// Delete bond-slaves (set to 'none') |
bab5d18c | 344 | slaves, |
26d9aebc DM |
345 | } |
346 | ||
347 | ||
348 | #[api( | |
349 | protected: true, | |
350 | input: { | |
351 | properties: { | |
352 | node: { | |
353 | schema: NODE_SCHEMA, | |
354 | }, | |
7b22acd0 | 355 | iface: { |
26d9aebc DM |
356 | schema: NETWORK_INTERFACE_NAME_SCHEMA, |
357 | }, | |
7b22acd0 DM |
358 | "type": { |
359 | description: "Interface type. If specified, need to match the current type.", | |
360 | type: NetworkInterfaceType, | |
361 | optional: true, | |
362 | }, | |
363 | autostart: { | |
26d9aebc DM |
364 | description: "Autostart interface.", |
365 | type: bool, | |
366 | optional: true, | |
367 | }, | |
7b22acd0 | 368 | method: { |
26d9aebc DM |
369 | type: NetworkConfigMethod, |
370 | optional: true, | |
371 | }, | |
7b22acd0 | 372 | method6: { |
26d9aebc DM |
373 | type: NetworkConfigMethod, |
374 | optional: true, | |
375 | }, | |
7b22acd0 | 376 | comments: { |
26d9aebc DM |
377 | description: "Comments (inet, may span multiple lines)", |
378 | type: String, | |
379 | optional: true, | |
380 | }, | |
7b22acd0 | 381 | comments6: { |
26d9aebc DM |
382 | description: "Comments (inet5, may span multiple lines)", |
383 | type: String, | |
384 | optional: true, | |
385 | }, | |
7b22acd0 DM |
386 | cidr: { |
387 | schema: CIDR_V4_SCHEMA, | |
388 | optional: true, | |
389 | }, | |
390 | cidr6: { | |
391 | schema: CIDR_V6_SCHEMA, | |
26d9aebc DM |
392 | optional: true, |
393 | }, | |
394 | gateway: { | |
7b22acd0 DM |
395 | schema: IP_V4_SCHEMA, |
396 | optional: true, | |
397 | }, | |
398 | gateway6: { | |
399 | schema: IP_V6_SCHEMA, | |
26d9aebc DM |
400 | optional: true, |
401 | }, | |
402 | mtu: { | |
403 | description: "Maximum Transmission Unit.", | |
404 | optional: true, | |
405 | minimum: 46, | |
406 | maximum: 65535, | |
407 | default: 1500, | |
408 | }, | |
409 | bridge_ports: { | |
410 | schema: NETWORK_INTERFACE_LIST_SCHEMA, | |
411 | optional: true, | |
412 | }, | |
7b22acd0 DM |
413 | bridge_vlan_aware: { |
414 | description: "Enable bridge vlan support.", | |
415 | type: bool, | |
416 | optional: true, | |
417 | }, | |
bab5d18c DM |
418 | bond_mode: { |
419 | type: LinuxBondMode, | |
420 | optional: true, | |
421 | }, | |
422 | slaves: { | |
26d9aebc DM |
423 | schema: NETWORK_INTERFACE_LIST_SCHEMA, |
424 | optional: true, | |
425 | }, | |
426 | delete: { | |
427 | description: "List of properties to delete.", | |
428 | type: Array, | |
429 | optional: true, | |
430 | items: { | |
431 | type: DeletableProperty, | |
432 | } | |
433 | }, | |
434 | digest: { | |
435 | optional: true, | |
436 | schema: PROXMOX_CONFIG_DIGEST_SCHEMA, | |
437 | }, | |
438 | }, | |
439 | }, | |
440 | access: { | |
7b22acd0 | 441 | permission: &Permission::Privilege(&["system", "network", "interfaces", "{iface}"], PRIV_SYS_MODIFY, false), |
26d9aebc DM |
442 | }, |
443 | )] | |
444 | /// Update network interface config. | |
445 | pub fn update_interface( | |
7b22acd0 DM |
446 | iface: String, |
447 | autostart: Option<bool>, | |
448 | method: Option<NetworkConfigMethod>, | |
449 | method6: Option<NetworkConfigMethod>, | |
450 | comments: Option<String>, | |
451 | comments6: Option<String>, | |
452 | cidr: Option<String>, | |
26d9aebc | 453 | gateway: Option<String>, |
7b22acd0 DM |
454 | cidr6: Option<String>, |
455 | gateway6: Option<String>, | |
26d9aebc | 456 | mtu: Option<u64>, |
3aedb738 | 457 | bridge_ports: Option<String>, |
7b22acd0 | 458 | bridge_vlan_aware: Option<bool>, |
bab5d18c | 459 | bond_mode: Option<LinuxBondMode>, |
3aedb738 | 460 | slaves: Option<String>, |
26d9aebc DM |
461 | delete: Option<Vec<DeletableProperty>>, |
462 | digest: Option<String>, | |
7b22acd0 | 463 | param: Value, |
26d9aebc DM |
464 | ) -> Result<(), Error> { |
465 | ||
466 | let _lock = crate::tools::open_file_locked(network::NETWORK_LOCKFILE, std::time::Duration::new(10, 0))?; | |
467 | ||
468 | let (mut config, expected_digest) = network::config()?; | |
469 | ||
470 | if let Some(ref digest) = digest { | |
471 | let digest = proxmox::tools::hex_to_digest(digest)?; | |
472 | crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?; | |
473 | } | |
474 | ||
c2ffc685 DM |
475 | if gateway.is_some() { check_duplicate_gateway_v4(&config, &iface)?; } |
476 | if gateway6.is_some() { check_duplicate_gateway_v6(&config, &iface)?; } | |
26d9aebc | 477 | |
7b22acd0 DM |
478 | let interface = config.lookup_mut(&iface)?; |
479 | ||
480 | if let Some(interface_type) = param.get("type") { | |
481 | let interface_type: NetworkInterfaceType = serde_json::from_value(interface_type.clone())?; | |
482 | if interface_type != interface.interface_type { | |
483 | bail!("got unexpected interface type ({:?} != {:?})", interface_type, interface.interface_type); | |
484 | } | |
485 | } | |
26d9aebc DM |
486 | |
487 | if let Some(delete) = delete { | |
488 | for delete_prop in delete { | |
489 | match delete_prop { | |
7b22acd0 DM |
490 | DeletableProperty::cidr => { interface.cidr = None; }, |
491 | DeletableProperty::cidr6 => { interface.cidr6 = None; }, | |
492 | DeletableProperty::gateway => { interface.gateway = None; }, | |
493 | DeletableProperty::gateway6 => { interface.gateway6 = None; }, | |
494 | DeletableProperty::method => { interface.method = None; }, | |
495 | DeletableProperty::method6 => { interface.method6 = None; }, | |
496 | DeletableProperty::comments => { interface.comments = None; }, | |
497 | DeletableProperty::comments6 => { interface.comments6 = None; }, | |
26d9aebc | 498 | DeletableProperty::mtu => { interface.mtu = None; }, |
7b22acd0 | 499 | DeletableProperty::autostart => { interface.autostart = false; }, |
26d9aebc | 500 | DeletableProperty::bridge_ports => { interface.set_bridge_ports(Vec::new())?; } |
7b22acd0 | 501 | DeletableProperty::bridge_vlan_aware => { interface.bridge_vlan_aware = None; } |
bab5d18c | 502 | DeletableProperty::slaves => { interface.set_bond_slaves(Vec::new())?; } |
26d9aebc DM |
503 | } |
504 | } | |
505 | } | |
506 | ||
7b22acd0 DM |
507 | if let Some(autostart) = autostart { interface.autostart = autostart; } |
508 | if method.is_some() { interface.method = method; } | |
509 | if method6.is_some() { interface.method6 = method6; } | |
26d9aebc | 510 | if mtu.is_some() { interface.mtu = mtu; } |
3aedb738 DM |
511 | if let Some(ports) = bridge_ports { |
512 | let ports = split_interface_list(&ports)?; | |
513 | interface.set_bridge_ports(ports)?; | |
514 | } | |
7b22acd0 | 515 | if bridge_vlan_aware.is_some() { interface.bridge_vlan_aware = bridge_vlan_aware; } |
3aedb738 DM |
516 | if let Some(slaves) = slaves { |
517 | let slaves = split_interface_list(&slaves)?; | |
518 | interface.set_bond_slaves(slaves)?; | |
519 | } | |
bab5d18c | 520 | if bond_mode.is_some() { interface.bond_mode = bond_mode; } |
26d9aebc | 521 | |
7b22acd0 DM |
522 | if let Some(cidr) = cidr { |
523 | let (_, _, is_v6) = network::parse_cidr(&cidr)?; | |
524 | if is_v6 { bail!("invalid address type (expected IPv4, got IPv6)"); } | |
525 | interface.cidr = Some(cidr); | |
526 | } | |
527 | ||
528 | if let Some(cidr6) = cidr6 { | |
529 | let (_, _, is_v6) = network::parse_cidr(&cidr6)?; | |
530 | if !is_v6 { bail!("invalid address type (expected IPv6, got IPv4)"); } | |
531 | interface.cidr6 = Some(cidr6); | |
26d9aebc DM |
532 | } |
533 | ||
534 | if let Some(gateway) = gateway { | |
535 | let is_v6 = gateway.contains(':'); | |
7b22acd0 | 536 | if is_v6 { bail!("invalid address type (expected IPv4, got IPv6)"); } |
7b22acd0 DM |
537 | interface.gateway = Some(gateway); |
538 | } | |
539 | ||
540 | if let Some(gateway6) = gateway6 { | |
541 | let is_v6 = gateway6.contains(':'); | |
542 | if !is_v6 { bail!("invalid address type (expected IPv6, got IPv4)"); } | |
7b22acd0 DM |
543 | interface.gateway6 = Some(gateway6); |
544 | } | |
545 | ||
546 | if comments.is_some() { interface.comments = comments; } | |
547 | if comments6.is_some() { interface.comments6 = comments6; } | |
548 | ||
549 | if interface.cidr.is_some() || interface.gateway.is_some() { | |
550 | interface.method = Some(NetworkConfigMethod::Static); | |
551 | } else { | |
552 | interface.method = Some(NetworkConfigMethod::Manual); | |
26d9aebc DM |
553 | } |
554 | ||
7b22acd0 DM |
555 | if interface.cidr6.is_some() || interface.gateway6.is_some() { |
556 | interface.method6 = Some(NetworkConfigMethod::Static); | |
557 | } else { | |
558 | interface.method6 = Some(NetworkConfigMethod::Manual); | |
559 | } | |
26d9aebc DM |
560 | |
561 | network::save_config(&config)?; | |
562 | ||
563 | Ok(()) | |
b2b3485d DM |
564 | } |
565 | ||
26d9aebc DM |
566 | #[api( |
567 | protected: true, | |
568 | input: { | |
569 | properties: { | |
570 | node: { | |
571 | schema: NODE_SCHEMA, | |
572 | }, | |
7b22acd0 | 573 | iface: { |
26d9aebc DM |
574 | schema: NETWORK_INTERFACE_NAME_SCHEMA, |
575 | }, | |
576 | digest: { | |
577 | optional: true, | |
578 | schema: PROXMOX_CONFIG_DIGEST_SCHEMA, | |
579 | }, | |
580 | }, | |
581 | }, | |
582 | access: { | |
7b22acd0 | 583 | permission: &Permission::Privilege(&["system", "network", "interfaces", "{iface}"], PRIV_SYS_MODIFY, false), |
26d9aebc DM |
584 | }, |
585 | )] | |
586 | /// Remove network interface configuration. | |
7b22acd0 | 587 | pub fn delete_interface(iface: String, digest: Option<String>) -> Result<(), Error> { |
26d9aebc DM |
588 | |
589 | let _lock = crate::tools::open_file_locked(network::NETWORK_LOCKFILE, std::time::Duration::new(10, 0))?; | |
590 | ||
591 | let (mut config, expected_digest) = network::config()?; | |
592 | ||
593 | if let Some(ref digest) = digest { | |
594 | let digest = proxmox::tools::hex_to_digest(digest)?; | |
595 | crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?; | |
596 | } | |
597 | ||
7b22acd0 | 598 | let _interface = config.lookup(&iface)?; // check if interface exists |
26d9aebc | 599 | |
7b22acd0 | 600 | config.interfaces.remove(&iface); |
26d9aebc DM |
601 | |
602 | network::save_config(&config)?; | |
603 | ||
604 | Ok(()) | |
605 | } | |
606 | ||
607 | #[api( | |
7b22acd0 | 608 | protected: true, |
26d9aebc DM |
609 | input: { |
610 | properties: { | |
611 | node: { | |
612 | schema: NODE_SCHEMA, | |
613 | }, | |
614 | }, | |
615 | }, | |
616 | access: { | |
74c08a57 | 617 | permission: &Permission::Privilege(&["system", "network", "interfaces"], PRIV_SYS_MODIFY, false), |
26d9aebc DM |
618 | }, |
619 | )] | |
620 | /// Reload network configuration (requires ifupdown2). | |
7b22acd0 DM |
621 | pub async fn reload_network_config( |
622 | rpcenv: &mut dyn RpcEnvironment, | |
623 | ) -> Result<String, Error> { | |
26d9aebc DM |
624 | |
625 | network::assert_ifupdown2_installed()?; | |
626 | ||
7b22acd0 | 627 | let username = rpcenv.get_user().unwrap(); |
26d9aebc | 628 | |
7b22acd0 | 629 | let upid_str = WorkerTask::spawn("srvreload", Some(String::from("networking")), &username.clone(), true, |_worker| async { |
26d9aebc | 630 | |
7b22acd0 DM |
631 | let _ = std::fs::rename(network::NETWORK_INTERFACES_NEW_FILENAME, network::NETWORK_INTERFACES_FILENAME); |
632 | ||
633 | network::network_reload()?; | |
634 | Ok(()) | |
635 | })?; | |
636 | ||
637 | Ok(upid_str) | |
26d9aebc DM |
638 | } |
639 | ||
640 | #[api( | |
3dd27a3b | 641 | protected: true, |
26d9aebc DM |
642 | input: { |
643 | properties: { | |
644 | node: { | |
645 | schema: NODE_SCHEMA, | |
646 | }, | |
647 | }, | |
648 | }, | |
649 | access: { | |
74c08a57 | 650 | permission: &Permission::Privilege(&["system", "network", "interfaces"], PRIV_SYS_MODIFY, false), |
26d9aebc DM |
651 | }, |
652 | )] | |
653 | /// Revert network configuration (rm /etc/network/interfaces.new). | |
654 | pub fn revert_network_config() -> Result<(), Error> { | |
655 | ||
656 | let _ = std::fs::remove_file(network::NETWORK_INTERFACES_NEW_FILENAME); | |
657 | ||
658 | Ok(()) | |
659 | } | |
660 | ||
661 | const ITEM_ROUTER: Router = Router::new() | |
662 | .get(&API_METHOD_READ_INTERFACE) | |
663 | .put(&API_METHOD_UPDATE_INTERFACE) | |
664 | .delete(&API_METHOD_DELETE_INTERFACE); | |
665 | ||
255f378a | 666 | pub const ROUTER: Router = Router::new() |
26d9aebc DM |
667 | .get(&API_METHOD_LIST_NETWORK_DEVICES) |
668 | .put(&API_METHOD_RELOAD_NETWORK_CONFIG) | |
96518331 | 669 | .post(&API_METHOD_CREATE_INTERFACE) |
26d9aebc | 670 | .delete(&API_METHOD_REVERT_NETWORK_CONFIG) |
7b22acd0 | 671 | .match_all("iface", &ITEM_ROUTER); |