1use std::collections::HashSet;
4use std::fmt::Write;
5use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
6use std::str::FromStr;
7
8use serde::{Deserialize, Deserializer, Serialize};
9
10use crate::{
11 BaseInterface, DnsClientState, ErrorKind, MergedInterface,
12 MptcpAddressFlag, NmstateError, RouteRuleEntry,
13};
14
15const AF_INET: u8 = 2;
16const AF_INET6: u8 = 10;
17const IPV4_ADDR_LEN: usize = 32;
18const IPV6_ADDR_LEN: usize = 128;
19const FOREVER: &str = "forever";
20
21#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
22#[non_exhaustive]
23#[serde(deny_unknown_fields)]
24struct InterfaceIp {
25 #[serde(
26 skip_serializing_if = "Option::is_none",
27 default,
28 deserialize_with = "crate::deserializer::option_bool_or_string"
29 )]
30 pub enabled: Option<bool>,
31 #[serde(
32 skip_serializing_if = "Option::is_none",
33 default,
34 deserialize_with = "crate::deserializer::option_bool_or_string"
35 )]
36 pub dhcp: Option<bool>,
37 #[serde(
38 skip_serializing_if = "Option::is_none",
39 default,
40 deserialize_with = "crate::deserializer::option_bool_or_string"
41 )]
42 pub autoconf: Option<bool>,
43 #[serde(
44 skip_serializing_if = "Option::is_none",
45 rename = "dhcp-client-id"
46 )]
47 pub dhcp_client_id: Option<Dhcpv4ClientId>,
48 #[serde(skip_serializing_if = "Option::is_none", rename = "dhcp-duid")]
49 pub dhcp_duid: Option<Dhcpv6Duid>,
50 #[serde(skip_serializing_if = "Option::is_none", rename = "address")]
51 pub addresses: Option<Vec<InterfaceIpAddr>>,
52 #[serde(
53 skip_serializing_if = "Option::is_none",
54 rename = "auto-dns",
55 default,
56 deserialize_with = "crate::deserializer::option_bool_or_string"
57 )]
58 pub auto_dns: Option<bool>,
59 #[serde(
60 skip_serializing_if = "Option::is_none",
61 rename = "auto-gateway",
62 default,
63 deserialize_with = "crate::deserializer::option_bool_or_string"
64 )]
65 pub auto_gateway: Option<bool>,
66 #[serde(
67 skip_serializing_if = "Option::is_none",
68 rename = "auto-routes",
69 default,
70 deserialize_with = "crate::deserializer::option_bool_or_string"
71 )]
72 pub auto_routes: Option<bool>,
73 #[serde(
74 skip_serializing_if = "Option::is_none",
75 rename = "auto-route-table-id",
76 default,
77 deserialize_with = "crate::deserializer::option_u32_or_string"
78 )]
79 pub auto_table_id: Option<u32>,
80 #[serde(
81 skip_serializing_if = "Option::is_none",
82 rename = "auto-route-metric",
83 default,
84 deserialize_with = "crate::deserializer::option_u32_or_string"
85 )]
86 pub auto_route_metric: Option<u32>,
87 #[serde(skip_serializing_if = "Option::is_none", rename = "addr-gen-mode")]
88 pub addr_gen_mode: Option<Ipv6AddrGenMode>,
89 #[serde(
90 skip_serializing_if = "Option::is_none",
91 rename = "allow-extra-address"
92 )]
93 pub allow_extra_address: Option<bool>,
94 #[serde(skip_serializing_if = "Option::is_none")]
95 pub token: Option<String>,
96 #[serde(
97 skip_serializing_if = "Option::is_none",
98 rename = "dhcp-send-hostname"
99 )]
100 pub dhcp_send_hostname: Option<bool>,
101 #[serde(
102 skip_serializing_if = "Option::is_none",
103 rename = "dhcp-custom-hostname"
104 )]
105 pub dhcp_custom_hostname: Option<String>,
106 #[serde(skip_serializing_if = "Option::is_none")]
107 pub forwarding: Option<bool>,
108}
109
110#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
111#[serde(into = "InterfaceIp")]
112#[non_exhaustive]
113pub struct InterfaceIpv4 {
131 pub enabled: bool,
134 pub(crate) enabled_defined: bool,
137 pub dhcp: Option<bool>,
139 pub dhcp_client_id: Option<Dhcpv4ClientId>,
142 pub addresses: Option<Vec<InterfaceIpAddr>>,
152 pub forwarding: Option<bool>,
154 pub auto_dns: Option<bool>,
160 pub auto_gateway: Option<bool>,
163 pub auto_routes: Option<bool>,
167 pub auto_table_id: Option<u32>,
172 pub allow_extra_address: Option<bool>,
178 pub auto_route_metric: Option<u32>,
182 pub dhcp_send_hostname: Option<bool>,
190 pub dhcp_custom_hostname: Option<String>,
198 pub(crate) dns: Option<DnsClientState>,
199 pub(crate) rules: Option<HashSet<RouteRuleEntry>>,
200}
201
202impl InterfaceIpv4 {
203 pub fn new() -> Self {
205 Self::default()
206 }
207
208 pub(crate) fn is_auto(&self) -> bool {
209 self.enabled && self.dhcp == Some(true)
210 }
211
212 pub fn is_static(&self) -> bool {
213 self.enabled
214 && !self.is_auto()
215 && !self.addresses.as_deref().unwrap_or_default().is_empty()
216 }
217
218 pub(crate) fn merge_ip(&mut self, current: &Self) {
219 if !self.enabled_defined {
220 self.enabled = current.enabled;
221 }
222 if self.dhcp.is_none() && self.enabled {
223 self.dhcp = current.dhcp;
224 }
225 if current.is_auto()
230 && current.addresses.is_some()
231 && self.enabled
232 && !self.is_auto()
233 && is_ip_addrs_none_or_all_auto(self.addresses.as_deref())
234 {
235 self.addresses.clone_from(¤t.addresses);
236 if let Some(addrs) = self.addresses.as_mut() {
237 addrs.as_mut_slice().iter_mut().for_each(|a| {
238 a.valid_life_time = None;
239 a.preferred_life_time = None;
240 });
241 }
242 }
243 }
244
245 pub(crate) fn special_merge(&mut self, desired: &Self, current: &Self) {
247 if !desired.enabled_defined {
248 self.enabled = current.enabled;
249 }
250 if desired.dhcp.is_none() && self.enabled {
251 self.dhcp = current.dhcp;
252 }
253
254 if current.is_auto()
259 && current.addresses.is_some()
260 && self.enabled
261 && !self.is_auto()
262 && is_ip_addrs_none_or_all_auto(self.addresses.as_deref())
263 {
264 self.addresses.clone_from(¤t.addresses);
265 }
266
267 self.rules.clone_from(¤t.rules);
269
270 self.sanitize(false).ok();
271 }
272
273 pub(crate) fn sanitize(
281 &mut self,
282 is_desired: bool,
283 ) -> Result<(), NmstateError> {
284 if self.is_auto() {
285 if self.auto_dns.is_none() {
286 self.auto_dns = Some(true);
287 }
288 if self.auto_routes.is_none() {
289 self.auto_routes = Some(true);
290 }
291 if self.auto_gateway.is_none() {
292 self.auto_gateway = Some(true);
293 }
294 if let Some(addrs) = self.addresses.as_ref() {
295 if is_desired {
296 for addr in addrs {
297 log::info!(
298 "Static addresses {addr} defined when dynamic IP \
299 is enabled"
300 );
301 }
302 }
303 }
304 }
305
306 if let Some(forwarding) = self.forwarding {
307 if !self.enabled && forwarding && is_desired {
308 log::warn!(
309 "Ignoring `forwarding: {forwarding}` as IPv4 is disabled",
310 );
311 self.forwarding = None;
312 }
313 }
314
315 if let Some(addrs) = self.addresses.as_mut() {
316 if is_desired {
317 for addr in addrs.as_slice().iter().filter(|a| a.is_auto()) {
318 log::info!("Ignoring Auto IP address {addr}");
319 }
320 if let Some(addr) =
321 addrs.as_slice().iter().find(|a| a.ip.is_ipv6())
322 {
323 return Err(NmstateError::new(
324 ErrorKind::InvalidArgument,
325 format!(
326 "Got IPv6 address {addr} in ipv4 config section"
327 ),
328 ));
329 }
330 if let Some(addr) = addrs
331 .iter()
332 .find(|a| a.prefix_length as usize > IPV4_ADDR_LEN)
333 {
334 return Err(NmstateError::new(
335 ErrorKind::InvalidArgument,
336 format!(
337 "Invalid IPv4 network prefix length '{}', should \
338 be in the range of 0 to {IPV4_ADDR_LEN}",
339 addr.prefix_length
340 ),
341 ));
342 }
343 }
344 addrs.retain(|a| !a.is_auto());
345 addrs.iter_mut().for_each(|a| {
346 a.valid_life_time = None;
347 a.preferred_life_time = None
348 });
349 }
350
351 if !self.enabled {
352 self.dhcp = None;
353 self.addresses = None;
354 }
355
356 if self.dhcp != Some(true) {
357 self.auto_dns = None;
358 self.auto_gateway = None;
359 self.auto_routes = None;
360 self.auto_table_id = None;
361 self.auto_route_metric = None;
362 if is_desired && self.dhcp_client_id.is_some() {
363 log::warn!(
364 "Ignoring `dhcp-client-id` setting when DHCPv4 is disabled"
365 );
366 }
367 self.dhcp_client_id = None;
368 self.dhcp_send_hostname = None;
369 self.dhcp_custom_hostname = None;
370 }
371 if self.dhcp_send_hostname == Some(false) {
372 if is_desired {
373 if let Some(custom_hostname) =
374 self.dhcp_custom_hostname.as_deref()
375 {
376 if !custom_hostname.is_empty() {
377 log::warn!(
378 "Ignoring `dhcp-custom-hostname: \
379 {custom_hostname}` as `dhcp-send-hostname` is \
380 disabled"
381 );
382 }
383 }
384 }
385 self.dhcp_custom_hostname = None;
386 }
387 if let Some(addrs) = self.addresses.as_mut() {
388 for addr in addrs.iter_mut() {
389 addr.mptcp_flags = None;
390 }
391 }
392 Ok(())
393 }
394}
395
396impl<'de> Deserialize<'de> for InterfaceIpv4 {
397 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
398 where
399 D: Deserializer<'de>,
400 {
401 let v = serde_json::Value::deserialize(deserializer)?;
402
403 if let Some(v_map) = v.as_object() {
404 if v_map.contains_key("autoconf") {
405 return Err(serde::de::Error::custom(
406 "autoconf is not allowed for IPv4",
407 ));
408 }
409 if v_map.contains_key("dhcp_duid") {
410 return Err(serde::de::Error::custom(
411 "dhcp-duid is not allowed for IPv4",
412 ));
413 }
414 }
415
416 let ip: InterfaceIp = match serde_json::from_value(v) {
417 Ok(i) => i,
418 Err(e) => {
419 return Err(serde::de::Error::custom(format!("{e}")));
420 }
421 };
422 let ret = Self::from(ip);
423 Ok(ret)
424 }
425}
426
427impl From<InterfaceIp> for InterfaceIpv4 {
428 fn from(ip: InterfaceIp) -> Self {
429 Self {
430 enabled: ip.enabled.unwrap_or_default(),
431 enabled_defined: ip.enabled.is_some(),
432 dhcp: ip.dhcp,
433 addresses: ip.addresses,
434 dhcp_client_id: ip.dhcp_client_id,
435 auto_dns: ip.auto_dns,
436 auto_routes: ip.auto_routes,
437 auto_gateway: ip.auto_gateway,
438 auto_table_id: ip.auto_table_id,
439 allow_extra_address: ip.allow_extra_address,
440 auto_route_metric: ip.auto_route_metric,
441 dhcp_send_hostname: ip.dhcp_send_hostname,
442 dhcp_custom_hostname: ip.dhcp_custom_hostname,
443 forwarding: ip.forwarding,
444 ..Default::default()
445 }
446 }
447}
448
449impl From<InterfaceIpv4> for InterfaceIp {
450 fn from(ip: InterfaceIpv4) -> Self {
451 let enabled = if ip.enabled_defined {
452 Some(ip.enabled)
453 } else {
454 None
455 };
456 Self {
457 enabled,
458 dhcp: ip.dhcp,
459 addresses: ip.addresses,
460 dhcp_client_id: ip.dhcp_client_id,
461 auto_dns: ip.auto_dns,
462 auto_routes: ip.auto_routes,
463 auto_gateway: ip.auto_gateway,
464 auto_table_id: ip.auto_table_id,
465 allow_extra_address: ip.allow_extra_address,
466 auto_route_metric: ip.auto_route_metric,
467 dhcp_send_hostname: ip.dhcp_send_hostname,
468 dhcp_custom_hostname: ip.dhcp_custom_hostname,
469 forwarding: ip.forwarding,
470 ..Default::default()
471 }
472 }
473}
474
475#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
476#[non_exhaustive]
477#[serde(into = "InterfaceIp")]
478pub struct InterfaceIpv6 {
501 pub enabled: bool,
504 pub(crate) enabled_defined: bool,
505 pub dhcp: Option<bool>,
507 pub dhcp_duid: Option<Dhcpv6Duid>,
510 pub autoconf: Option<bool>,
512 pub addr_gen_mode: Option<Ipv6AddrGenMode>,
515 pub addresses: Option<Vec<InterfaceIpAddr>>,
521 pub auto_dns: Option<bool>,
528 pub auto_gateway: Option<bool>,
531 pub auto_routes: Option<bool>,
535 pub auto_table_id: Option<u32>,
540 pub allow_extra_address: Option<bool>,
547 pub auto_route_metric: Option<u32>,
551 pub token: Option<String>,
554 pub dhcp_send_hostname: Option<bool>,
559 pub dhcp_custom_hostname: Option<String>,
564
565 pub(crate) dns: Option<DnsClientState>,
566 pub(crate) rules: Option<HashSet<RouteRuleEntry>>,
567}
568
569impl InterfaceIpv6 {
570 pub fn new() -> Self {
572 Self::default()
573 }
574
575 pub(crate) fn is_auto(&self) -> bool {
576 self.enabled && (self.dhcp == Some(true) || self.autoconf == Some(true))
577 }
578
579 pub fn is_static(&self) -> bool {
580 self.enabled
581 && !self.is_auto()
582 && !self.addresses.as_deref().unwrap_or_default().is_empty()
583 }
584
585 pub(crate) fn sanitize(
591 &mut self,
592 is_desired: bool,
593 ) -> Result<(), NmstateError> {
594 if let Some(addrs) = self.addresses.as_mut() {
595 if is_desired {
596 for addr in addrs.as_slice().iter().filter(|a| a.is_auto()) {
597 log::info!("Ignoring Auto IP address {addr}");
598 }
599 if let Some(addr) = addrs.iter().find(|a| a.ip.is_ipv4()) {
600 return Err(NmstateError::new(
601 ErrorKind::InvalidArgument,
602 format!(
603 "Got IPv4 address {addr} in ipv6 config section"
604 ),
605 ));
606 }
607 if let Some(addr) = addrs
608 .iter()
609 .find(|a| a.prefix_length as usize > IPV6_ADDR_LEN)
610 {
611 return Err(NmstateError::new(
612 ErrorKind::InvalidArgument,
613 format!(
614 "Invalid IPv6 network prefix length '{}', should \
615 be in the range of 0 to {IPV6_ADDR_LEN}",
616 addr.prefix_length
617 ),
618 ));
619 }
620 }
621 addrs.retain(|a| !a.is_auto());
622 addrs.iter_mut().for_each(|a| {
623 a.valid_life_time = None;
624 a.preferred_life_time = None
625 });
626 }
627
628 if self.is_auto() {
629 if self.auto_dns.is_none() {
630 self.auto_dns = Some(true);
631 }
632 if self.auto_routes.is_none() {
633 self.auto_routes = Some(true);
634 }
635 if self.auto_gateway.is_none() {
636 self.auto_gateway = Some(true);
637 }
638 if let Some(addrs) = self.addresses.as_ref() {
639 if is_desired {
640 for addr in addrs {
641 log::info!(
642 "Static addresses {addr} defined when dynamic IP \
643 is enabled"
644 );
645 }
646 }
647 }
648 }
649
650 if let Some(addrs) = self.addresses.as_mut() {
651 addrs.retain(|addr| {
652 if let IpAddr::V6(ip_addr) = addr.ip {
653 if is_ipv6_unicast_link_local(&ip_addr) {
654 if is_desired {
655 log::warn!(
656 "Ignoring IPv6 link local address {}/{}",
657 &addr.ip,
658 addr.prefix_length
659 );
660 }
661 false
662 } else {
663 true
664 }
665 } else {
666 false
667 }
668 })
669 };
670
671 if !self.enabled {
672 self.dhcp = None;
673 self.autoconf = None;
674 self.addresses = None;
675 }
676
677 if !self.is_auto() {
678 self.auto_dns = None;
679 self.auto_gateway = None;
680 self.auto_routes = None;
681 self.auto_table_id = None;
682 self.auto_route_metric = None;
683 self.dhcp_send_hostname = None;
684 self.dhcp_custom_hostname = None;
685 }
686 if let Some(addrs) = self.addresses.as_mut() {
687 for addr in addrs.iter_mut() {
688 addr.mptcp_flags = None;
689 }
690 }
691 if let Some(token) = self.token.as_mut() {
692 if is_desired
693 && self.autoconf == Some(false)
694 && !(token.is_empty() || token == "::")
695 {
696 return Err(NmstateError::new(
697 ErrorKind::InvalidArgument,
698 format!(
699 "Desired IPv6 token '{token}' cannot be applied with \
700 IPv6 autoconf disabled, you may remove IPv6 token by \
701 setting as empty string or `::`"
702 ),
703 ));
704 }
705 sanitize_ipv6_token_to_string(token)?;
706 }
707 if self.dhcp_send_hostname == Some(false) {
708 if is_desired {
709 if let Some(custom_hostname) =
710 self.dhcp_custom_hostname.as_deref()
711 {
712 if !custom_hostname.is_empty() {
713 log::warn!(
714 "Ignoring `dhcp-custom-hostname: \
715 {custom_hostname}` as `dhcp-send-hostname` is \
716 disabled"
717 );
718 }
719 }
720 }
721 self.dhcp_custom_hostname = None;
722 }
723 Ok(())
724 }
725
726 pub(crate) fn special_merge(&mut self, desired: &Self, current: &Self) {
728 if !desired.enabled_defined {
729 self.enabled = current.enabled;
730 }
731 if desired.dhcp.is_none() && self.enabled {
732 self.dhcp = current.dhcp;
733 }
734 if desired.autoconf.is_none() && self.enabled {
735 self.autoconf = current.autoconf;
736 }
737 if current.is_auto()
742 && current.addresses.is_some()
743 && self.enabled
744 && !self.is_auto()
745 && is_ip_addrs_none_or_all_auto(desired.addresses.as_deref())
746 {
747 self.addresses.clone_from(¤t.addresses);
748 }
749
750 self.rules.clone_from(¤t.rules);
752
753 self.sanitize(false).ok();
754 }
755
756 pub(crate) fn merge_ip(&mut self, current: &Self) {
757 if !self.enabled_defined {
758 self.enabled = current.enabled;
759 }
760 if self.dhcp.is_none() && self.enabled {
761 self.dhcp = current.dhcp;
762 }
763 if self.autoconf.is_none() && self.enabled {
764 self.autoconf = current.autoconf;
765 }
766 if current.is_auto()
771 && current.addresses.is_some()
772 && self.enabled
773 && !self.is_auto()
774 && is_ip_addrs_none_or_all_auto(self.addresses.as_deref())
775 {
776 self.addresses.clone_from(¤t.addresses);
777 if let Some(addrs) = self.addresses.as_mut() {
778 addrs.as_mut_slice().iter_mut().for_each(|a| {
779 a.valid_life_time = None;
780 a.preferred_life_time = None;
781 });
782 }
783 }
784 }
785}
786
787impl<'de> Deserialize<'de> for InterfaceIpv6 {
788 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
789 where
790 D: Deserializer<'de>,
791 {
792 let v = serde_json::Value::deserialize(deserializer)?;
793
794 if let Some(v_map) = v.as_object() {
795 if v_map.contains_key("dhcp_client_id") {
796 return Err(serde::de::Error::custom(
797 "dhcp-client-id is not allowed for IPv6",
798 ));
799 }
800 if v_map.contains_key("forwarding") {
801 return Err(serde::de::Error::custom(NmstateError::new(
802 ErrorKind::InvalidArgument,
803 "forwarding is not supported for IPv6".to_string(),
804 )));
805 }
806 }
807 let ip: InterfaceIp = match serde_json::from_value(v) {
808 Ok(i) => i,
809 Err(e) => {
810 return Err(serde::de::Error::custom(format!("{e}")));
811 }
812 };
813 let ret = Self::from(ip);
814 Ok(ret)
815 }
816}
817
818impl From<InterfaceIp> for InterfaceIpv6 {
819 fn from(ip: InterfaceIp) -> Self {
820 Self {
821 enabled: ip.enabled.unwrap_or_default(),
822 enabled_defined: ip.enabled.is_some(),
823 dhcp: ip.dhcp,
824 autoconf: ip.autoconf,
825 addresses: ip.addresses,
826 dhcp_duid: ip.dhcp_duid,
827 auto_dns: ip.auto_dns,
828 auto_routes: ip.auto_routes,
829 auto_gateway: ip.auto_gateway,
830 auto_table_id: ip.auto_table_id,
831 addr_gen_mode: ip.addr_gen_mode,
832 allow_extra_address: ip.allow_extra_address,
833 auto_route_metric: ip.auto_route_metric,
834 token: ip.token,
835 dhcp_send_hostname: ip.dhcp_send_hostname,
836 dhcp_custom_hostname: ip.dhcp_custom_hostname,
837 ..Default::default()
838 }
839 }
840}
841
842impl From<InterfaceIpv6> for InterfaceIp {
843 fn from(ip: InterfaceIpv6) -> Self {
844 let enabled = if ip.enabled_defined {
845 Some(ip.enabled)
846 } else {
847 None
848 };
849 Self {
850 enabled,
851 dhcp: ip.dhcp,
852 autoconf: ip.autoconf,
853 addresses: ip.addresses,
854 dhcp_duid: ip.dhcp_duid,
855 auto_dns: ip.auto_dns,
856 auto_routes: ip.auto_routes,
857 auto_gateway: ip.auto_gateway,
858 auto_table_id: ip.auto_table_id,
859 addr_gen_mode: ip.addr_gen_mode,
860 allow_extra_address: ip.allow_extra_address,
861 auto_route_metric: ip.auto_route_metric,
862 token: ip.token,
863 dhcp_send_hostname: ip.dhcp_send_hostname,
864 dhcp_custom_hostname: ip.dhcp_custom_hostname,
865 ..Default::default()
866 }
867 }
868}
869
870#[derive(
871 Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize,
872)]
873#[serde(rename_all = "kebab-case", deny_unknown_fields)]
874#[non_exhaustive]
875pub struct InterfaceIpAddr {
876 pub ip: IpAddr,
878 #[serde(deserialize_with = "crate::deserializer::u8_or_string")]
879 pub prefix_length: u8,
882 #[serde(skip_serializing_if = "is_none_or_empty_mptcp_flags", default)]
883 pub mptcp_flags: Option<Vec<MptcpAddressFlag>>,
888 #[serde(
894 skip_serializing_if = "Option::is_none",
895 alias = "valid-left",
896 alias = "valid-lft"
897 )]
898 pub valid_life_time: Option<String>,
899 #[serde(
906 skip_serializing_if = "Option::is_none",
907 alias = "preferred-left",
908 alias = "preferred-lft"
909 )]
910 pub preferred_life_time: Option<String>,
911}
912
913impl Default for InterfaceIpAddr {
914 fn default() -> Self {
915 Self {
916 ip: IpAddr::V6(std::net::Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)),
917 prefix_length: 128,
918 mptcp_flags: None,
919 valid_life_time: None,
920 preferred_life_time: None,
921 }
922 }
923}
924
925impl std::fmt::Display for InterfaceIpAddr {
926 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
927 if self.is_auto() {
928 write!(
929 f,
930 "{}/{} valid_life_time {} preferred_lft {}",
931 self.ip,
932 self.prefix_length,
933 self.valid_life_time.as_deref().unwrap_or(FOREVER),
934 self.preferred_life_time.as_deref().unwrap_or(FOREVER)
935 )
936 } else {
937 write!(f, "{}/{}", self.ip, self.prefix_length)
938 }
939 }
940}
941
942impl InterfaceIpAddr {
943 pub(crate) fn is_auto(&self) -> bool {
944 self.valid_life_time.is_some()
945 && self.valid_life_time.as_deref() != Some(FOREVER)
946 }
947}
948
949pub(crate) fn is_ipv6_addr(addr: &str) -> bool {
950 addr.contains(':')
951}
952
953pub(crate) fn is_ipv6_unicast_link_local(ip: &Ipv6Addr) -> bool {
956 (ip.segments()[0] & 0xffc0) == 0xfe80
957}
958
959impl std::convert::TryFrom<&str> for InterfaceIpAddr {
960 type Error = NmstateError;
961 fn try_from(value: &str) -> Result<Self, Self::Error> {
962 let mut addr: Vec<&str> = value.split('/').collect();
963 addr.resize(2, "");
964 let ip = IpAddr::from_str(addr[0]).map_err(|e| {
965 let e = NmstateError::new(
966 ErrorKind::InvalidArgument,
967 format!("Invalid IP address {}: {e}", addr[0]),
968 );
969 log::error!("{e}");
970 e
971 })?;
972
973 let prefix_length = if addr[1].is_empty() {
974 if ip.is_ipv6() {
975 128
976 } else {
977 32
978 }
979 } else {
980 addr[1].parse::<u8>().map_err(|parse_error| {
981 let e = NmstateError::new(
982 ErrorKind::InvalidArgument,
983 format!("Invalid IP address {value}: {parse_error}"),
984 );
985 log::error!("{e}");
986 e
987 })?
988 };
989 Ok(Self {
990 ip,
991 prefix_length,
992 mptcp_flags: None,
993 valid_life_time: None,
994 preferred_life_time: None,
995 })
996 }
997}
998
999#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1000#[non_exhaustive]
1001pub enum Dhcpv4ClientId {
1003 #[serde(rename = "ll", alias = "LL")]
1006 LinkLayerAddress,
1007 #[serde(rename = "iaid+duid", alias = "IAID+DUID")]
1010 IaidPlusDuid,
1011 #[serde(untagged)]
1013 Other(String),
1014}
1015
1016impl std::fmt::Display for Dhcpv4ClientId {
1017 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1018 write!(
1019 f,
1020 "{}",
1021 match self {
1022 Dhcpv4ClientId::LinkLayerAddress => "ll",
1023 Dhcpv4ClientId::IaidPlusDuid => "iaid+duid",
1024 Dhcpv4ClientId::Other(s) => s,
1025 }
1026 )
1027 }
1028}
1029
1030#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1031#[non_exhaustive]
1032#[serde(rename_all = "kebab-case")]
1033pub enum Dhcpv6Duid {
1035 #[serde(rename = "llt", alias = "LLT")]
1038 LinkLayerAddressPlusTime,
1039 #[serde(rename = "en", alias = "EN")]
1042 EnterpriseNumber,
1043 #[serde(rename = "ll", alias = "LL")]
1046 LinkLayerAddress,
1047 #[serde(alias = "UUID")]
1050 Uuid,
1051 #[serde(untagged)]
1053 Other(String),
1054}
1055
1056impl std::fmt::Display for Dhcpv6Duid {
1057 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1058 write!(
1059 f,
1060 "{}",
1061 match self {
1062 Dhcpv6Duid::LinkLayerAddressPlusTime => "llt",
1063 Dhcpv6Duid::EnterpriseNumber => "en",
1064 Dhcpv6Duid::LinkLayerAddress => "ll",
1065 Dhcpv6Duid::Uuid => "uuid",
1066 Dhcpv6Duid::Other(s) => s,
1067 }
1068 )
1069 }
1070}
1071
1072#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1073pub enum Ipv6AddrGenMode {
1075 #[serde(rename = "eui64", alias = "EUI64")]
1078 Eui64,
1079 #[serde(rename = "stable-privacy", alias = "STABLE-PRIVACY")]
1082 StablePrivacy,
1083 #[serde(untagged)]
1085 Other(String),
1086}
1087
1088impl std::fmt::Display for Ipv6AddrGenMode {
1089 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1090 write!(
1091 f,
1092 "{}",
1093 match self {
1094 Ipv6AddrGenMode::Eui64 => "eui64",
1095 Ipv6AddrGenMode::StablePrivacy => "stable-privacy",
1096 Ipv6AddrGenMode::Other(s) => s,
1097 }
1098 )
1099 }
1100}
1101
1102#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1103#[serde(rename_all = "kebab-case", deny_unknown_fields)]
1104#[non_exhaustive]
1105pub enum WaitIp {
1108 Any,
1112 Ipv4,
1115 Ipv6,
1118 #[serde(rename = "ipv4+ipv6")]
1122 Ipv4AndIpv6,
1123}
1124
1125impl std::fmt::Display for WaitIp {
1126 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1127 write!(
1128 f,
1129 "{}",
1130 match self {
1131 Self::Any => "any",
1132 Self::Ipv4 => "ipv4",
1133 Self::Ipv6 => "ipv6",
1134 Self::Ipv4AndIpv6 => "ipv4+ipv6",
1135 }
1136 )
1137 }
1138}
1139
1140fn validate_wait_ip(base_iface: &BaseInterface) -> Result<(), NmstateError> {
1141 if let Some(wait_ip) = base_iface.wait_ip.as_ref() {
1142 if (wait_ip == &WaitIp::Ipv4 || wait_ip == &WaitIp::Ipv4AndIpv6)
1143 && !base_iface
1144 .ipv4
1145 .as_ref()
1146 .map(|i| i.enabled)
1147 .unwrap_or_default()
1148 {
1149 let e = NmstateError::new(
1150 ErrorKind::InvalidArgument,
1151 format!(
1152 "Cannot set 'wait-ip: {}' with IPv4 disabled. Interface: \
1153 {}({})",
1154 wait_ip, &base_iface.name, &base_iface.iface_type
1155 ),
1156 );
1157 log::error!("{e}");
1158 return Err(e);
1159 }
1160 if (wait_ip == &WaitIp::Ipv6 || wait_ip == &WaitIp::Ipv4AndIpv6)
1161 && !base_iface
1162 .ipv6
1163 .as_ref()
1164 .map(|i| i.enabled)
1165 .unwrap_or_default()
1166 {
1167 let e = NmstateError::new(
1168 ErrorKind::InvalidArgument,
1169 format!(
1170 "Cannot set 'wait-ip: {}' with IPv6 disabled. Interface: \
1171 {}({})",
1172 wait_ip, &base_iface.name, &base_iface.iface_type
1173 ),
1174 );
1175 log::error!("{e}");
1176 return Err(e);
1177 }
1178 }
1179
1180 Ok(())
1181}
1182
1183pub(crate) fn sanitize_ip_network(
1184 ip_net: &str,
1185) -> Result<String, NmstateError> {
1186 let ip_nets: Vec<&str> = ip_net.split('/').collect();
1187 match ip_nets.len() {
1188 0 => Err(NmstateError::new(
1189 ErrorKind::InvalidArgument,
1190 "Invalid IP network string, got empty string".into(),
1191 )),
1192 1 => {
1193 let ip = IpAddr::from_str(ip_nets[0]).map_err(|e| {
1194 NmstateError::new(
1195 ErrorKind::InvalidArgument,
1196 format!("Invalid IP network '{ip_net}': {e}",),
1197 )
1198 })?;
1199 Ok(if ip.is_ipv6() {
1200 format!("{ip}/{IPV6_ADDR_LEN}")
1201 } else {
1202 format!("{ip}/{IPV4_ADDR_LEN}")
1203 })
1204 }
1205 2 => {
1206 let prefix_len = ip_nets[1].parse::<usize>().map_err(|e| {
1207 NmstateError::new(
1208 ErrorKind::InvalidArgument,
1209 format!(
1210 "Invalid IP network prefix length '{}' in '{ip_net}': \
1211 {e}",
1212 ip_nets[1]
1213 ),
1214 )
1215 })?;
1216 let ip =
1217 apply_ip_prefix_len(IpAddr::from_str(ip_nets[0])?, prefix_len);
1218 if ip.is_ipv6() {
1219 if prefix_len > IPV6_ADDR_LEN {
1220 Err(NmstateError::new(
1221 ErrorKind::InvalidArgument,
1222 format!(
1223 "Invalid IPv6 network prefix length '{}' in \
1224 '{ip_net}', should be smaller than \
1225 {IPV6_ADDR_LEN}'",
1226 ip_nets[1],
1227 ),
1228 ))
1229 } else {
1230 Ok(format!("{ip}/{prefix_len}"))
1231 }
1232 } else if prefix_len > IPV4_ADDR_LEN {
1233 Err(NmstateError::new(
1234 ErrorKind::InvalidArgument,
1235 format!(
1236 "Invalid IPv4 network prefix length '{}' in \
1237 '{ip_net}', should be smaller than {IPV4_ADDR_LEN}'",
1238 ip_nets[1],
1239 ),
1240 ))
1241 } else {
1242 Ok(format!("{ip}/{prefix_len}"))
1243 }
1244 }
1245 _ => Err(NmstateError::new(
1246 ErrorKind::InvalidArgument,
1247 format!(
1248 "Invalid IP network string: '{ip_net}', expecting 'ip/prefix' \
1249 or 'ip' format, for example: 192.0.2.0/24 or 2001:db8:1::/64 \
1250 or 192.0.2.1"
1251 ),
1252 )),
1253 }
1254}
1255
1256fn is_none_or_empty_mptcp_flags(v: &Option<Vec<MptcpAddressFlag>>) -> bool {
1257 if let Some(v) = v {
1258 v.is_empty()
1259 } else {
1260 true
1261 }
1262}
1263
1264#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
1265#[serde(rename_all = "lowercase")]
1266#[non_exhaustive]
1267#[derive(Default)]
1268pub enum AddressFamily {
1269 #[default]
1270 IPv4,
1271 IPv6,
1272 Unknown,
1273}
1274
1275impl From<u8> for AddressFamily {
1276 fn from(d: u8) -> Self {
1277 match d {
1278 AF_INET => AddressFamily::IPv4,
1279 AF_INET6 => AddressFamily::IPv6,
1280 _ => AddressFamily::Unknown,
1281 }
1282 }
1283}
1284
1285impl std::fmt::Display for AddressFamily {
1286 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1287 write!(
1288 f,
1289 "{}",
1290 match self {
1291 Self::IPv4 => "ipv4",
1292 Self::IPv6 => "ipv6",
1293 Self::Unknown => "unknown",
1294 }
1295 )
1296 }
1297}
1298
1299impl MergedInterface {
1300 pub(crate) fn post_inter_ifaces_process_ip(
1302 &mut self,
1303 ) -> Result<(), NmstateError> {
1304 if let (Some(current), Some(apply_iface), Some(verify_iface)) = (
1305 self.current.as_ref().map(|i| i.base_iface()),
1306 self.for_apply.as_mut().map(|i| i.base_iface_mut()),
1307 self.for_verify.as_mut().map(|i| i.base_iface_mut()),
1308 ) {
1309 if let (Some(des_ipv4), Some(cur_ipv4)) =
1310 (apply_iface.ipv4.as_mut(), current.ipv4.as_ref())
1311 {
1312 des_ipv4.merge_ip(cur_ipv4);
1313 }
1314 if let (Some(des_ipv6), Some(cur_ipv6)) =
1315 (apply_iface.ipv6.as_mut(), current.ipv6.as_ref())
1316 {
1317 des_ipv6.merge_ip(cur_ipv6);
1318 }
1319 if let (Some(des_ipv4), Some(cur_ipv4)) =
1320 (verify_iface.ipv4.as_mut(), current.ipv4.as_ref())
1321 {
1322 des_ipv4.merge_ip(cur_ipv4);
1323 }
1324 if let (Some(des_ipv6), Some(cur_ipv6)) =
1325 (verify_iface.ipv6.as_mut(), current.ipv6.as_ref())
1326 {
1327 des_ipv6.merge_ip(cur_ipv6);
1328 }
1329 validate_wait_ip(apply_iface)?;
1330 if !apply_iface.can_have_ip() {
1331 apply_iface.wait_ip = None;
1332 verify_iface.wait_ip = None;
1333 }
1334 }
1335
1336 Ok(())
1337 }
1338}
1339
1340fn sanitize_ipv6_token_to_string(
1343 token: &mut String,
1344) -> Result<(), NmstateError> {
1345 if token.is_empty() {
1347 write!(token, "::").ok();
1348 } else {
1349 match Ipv6Addr::from_str(token.as_str()) {
1350 Ok(ip) => {
1351 if ip.octets()[..8] != [0; 8] {
1352 return Err(NmstateError::new(
1353 ErrorKind::InvalidArgument,
1354 format!(
1355 "Desired IPv6 token should be lead by 64 bits 0. \
1356 But got {token}"
1357 ),
1358 ));
1359 }
1360 let mut segments = ip.segments();
1366 segments[0] = 0x2001;
1367 segments[1] = 0xdb8;
1368 let new_ip = Ipv6Addr::from(segments);
1369 token.clear();
1370 write!(token, "{}", &new_ip.to_string()["2001:db8".len()..])
1371 .ok();
1372 }
1373 Err(e) => {
1374 return Err(NmstateError::new(
1375 ErrorKind::InvalidArgument,
1376 format!(
1377 "Desired IPv6 token '{token}' is not a valid IPv6 \
1378 address: {e}"
1379 ),
1380 ));
1381 }
1382 }
1383 }
1384 Ok(())
1385}
1386
1387fn is_ip_addrs_none_or_all_auto(addrs: Option<&[InterfaceIpAddr]>) -> bool {
1388 addrs.map_or(true, |addrs| {
1389 addrs.iter().all(|a| {
1390 if let IpAddr::V6(ip_addr) = a.ip {
1391 is_ipv6_unicast_link_local(&ip_addr) || a.is_auto()
1392 } else {
1393 a.is_auto()
1394 }
1395 })
1396 })
1397}
1398
1399fn apply_ip_prefix_len(ip: IpAddr, prefix_length: usize) -> IpAddr {
1400 if prefix_length == 0 {
1401 return if ip.is_ipv6() {
1402 IpAddr::V6(0.into())
1403 } else {
1404 IpAddr::V4(0.into())
1405 };
1406 }
1407
1408 if (ip.is_ipv6() && prefix_length >= IPV6_ADDR_LEN)
1409 || (ip.is_ipv4() && prefix_length >= IPV4_ADDR_LEN)
1410 {
1411 return ip;
1412 }
1413
1414 match ip {
1415 IpAddr::V6(i) => Ipv6Addr::from(
1416 u128::from(i) & (u128::MAX << (IPV6_ADDR_LEN - prefix_length)),
1417 )
1418 .into(),
1419 IpAddr::V4(i) => Ipv4Addr::from(
1420 u32::from(i) & (u32::MAX << (IPV4_ADDR_LEN - prefix_length)),
1421 )
1422 .into(),
1423 }
1424}