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}
107
108#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
109#[serde(into = "InterfaceIp")]
110#[non_exhaustive]
111pub struct InterfaceIpv4 {
129 pub enabled: bool,
132 pub(crate) enabled_defined: bool,
135 pub dhcp: Option<bool>,
137 pub dhcp_client_id: Option<Dhcpv4ClientId>,
140 pub addresses: Option<Vec<InterfaceIpAddr>>,
150 pub auto_dns: Option<bool>,
156 pub auto_gateway: Option<bool>,
159 pub auto_routes: Option<bool>,
163 pub auto_table_id: Option<u32>,
168 pub allow_extra_address: Option<bool>,
174 pub auto_route_metric: Option<u32>,
178 pub dhcp_send_hostname: Option<bool>,
186 pub dhcp_custom_hostname: Option<String>,
194 pub(crate) dns: Option<DnsClientState>,
195 pub(crate) rules: Option<HashSet<RouteRuleEntry>>,
196}
197
198impl InterfaceIpv4 {
199 pub fn new() -> Self {
201 Self::default()
202 }
203
204 pub(crate) fn is_auto(&self) -> bool {
205 self.enabled && self.dhcp == Some(true)
206 }
207
208 pub fn is_static(&self) -> bool {
209 self.enabled
210 && !self.is_auto()
211 && !self.addresses.as_deref().unwrap_or_default().is_empty()
212 }
213
214 pub(crate) fn merge_ip(&mut self, current: &Self) {
215 if !self.enabled_defined {
216 self.enabled = current.enabled;
217 }
218 if self.dhcp.is_none() && self.enabled {
219 self.dhcp = current.dhcp;
220 }
221 if current.is_auto()
226 && current.addresses.is_some()
227 && self.enabled
228 && !self.is_auto()
229 && is_ip_addrs_none_or_all_auto(self.addresses.as_deref())
230 {
231 self.addresses.clone_from(¤t.addresses);
232 if let Some(addrs) = self.addresses.as_mut() {
233 addrs.as_mut_slice().iter_mut().for_each(|a| {
234 a.valid_life_time = None;
235 a.preferred_life_time = None;
236 });
237 }
238 }
239 }
240
241 pub(crate) fn special_merge(&mut self, desired: &Self, current: &Self) {
243 if !desired.enabled_defined {
244 self.enabled = current.enabled;
245 }
246 if desired.dhcp.is_none() && self.enabled {
247 self.dhcp = current.dhcp;
248 }
249
250 if current.is_auto()
255 && current.addresses.is_some()
256 && self.enabled
257 && !self.is_auto()
258 && is_ip_addrs_none_or_all_auto(self.addresses.as_deref())
259 {
260 self.addresses.clone_from(¤t.addresses);
261 }
262
263 self.rules.clone_from(¤t.rules);
265
266 self.sanitize(false).ok();
267 }
268
269 pub(crate) fn sanitize(
277 &mut self,
278 is_desired: bool,
279 ) -> Result<(), NmstateError> {
280 if self.is_auto() {
281 if self.auto_dns.is_none() {
282 self.auto_dns = Some(true);
283 }
284 if self.auto_routes.is_none() {
285 self.auto_routes = Some(true);
286 }
287 if self.auto_gateway.is_none() {
288 self.auto_gateway = Some(true);
289 }
290 if let Some(addrs) = self.addresses.as_ref() {
291 if is_desired {
292 for addr in addrs {
293 log::info!(
294 "Static addresses {addr} defined when dynamic IP \
295 is enabled"
296 );
297 }
298 }
299 }
300 }
301
302 if let Some(addrs) = self.addresses.as_mut() {
303 if is_desired {
304 for addr in addrs.as_slice().iter().filter(|a| a.is_auto()) {
305 log::info!("Ignoring Auto IP address {addr}");
306 }
307 if let Some(addr) =
308 addrs.as_slice().iter().find(|a| a.ip.is_ipv6())
309 {
310 return Err(NmstateError::new(
311 ErrorKind::InvalidArgument,
312 format!(
313 "Got IPv6 address {addr} in ipv4 config section"
314 ),
315 ));
316 }
317 if let Some(addr) = addrs
318 .iter()
319 .find(|a| a.prefix_length as usize > IPV4_ADDR_LEN)
320 {
321 return Err(NmstateError::new(
322 ErrorKind::InvalidArgument,
323 format!(
324 "Invalid IPv4 network prefix length '{}', should \
325 be in the range of 0 to {IPV4_ADDR_LEN}",
326 addr.prefix_length
327 ),
328 ));
329 }
330 }
331 addrs.retain(|a| !a.is_auto());
332 addrs.iter_mut().for_each(|a| {
333 a.valid_life_time = None;
334 a.preferred_life_time = None
335 });
336 }
337
338 if !self.enabled {
339 self.dhcp = None;
340 self.addresses = None;
341 }
342
343 if self.dhcp != Some(true) {
344 self.auto_dns = None;
345 self.auto_gateway = None;
346 self.auto_routes = None;
347 self.auto_table_id = None;
348 self.auto_route_metric = None;
349 if is_desired && self.dhcp_client_id.is_some() {
350 log::warn!(
351 "Ignoring `dhcp-client-id` setting when DHCPv4 is disabled"
352 );
353 }
354 self.dhcp_client_id = None;
355 self.dhcp_send_hostname = None;
356 self.dhcp_custom_hostname = None;
357 }
358 if self.dhcp_send_hostname == Some(false) {
359 if is_desired {
360 if let Some(custom_hostname) =
361 self.dhcp_custom_hostname.as_deref()
362 {
363 if !custom_hostname.is_empty() {
364 log::warn!(
365 "Ignoring `dhcp-custom-hostname: \
366 {custom_hostname}` as `dhcp-send-hostname` is \
367 disabled"
368 );
369 }
370 }
371 }
372 self.dhcp_custom_hostname = None;
373 }
374 if let Some(addrs) = self.addresses.as_mut() {
375 for addr in addrs.iter_mut() {
376 addr.mptcp_flags = None;
377 }
378 }
379 Ok(())
380 }
381}
382
383impl<'de> Deserialize<'de> for InterfaceIpv4 {
384 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
385 where
386 D: Deserializer<'de>,
387 {
388 let v = serde_json::Value::deserialize(deserializer)?;
389
390 if let Some(v_map) = v.as_object() {
391 if v_map.contains_key("autoconf") {
392 return Err(serde::de::Error::custom(
393 "autoconf is not allowed for IPv4",
394 ));
395 }
396 if v_map.contains_key("dhcp_duid") {
397 return Err(serde::de::Error::custom(
398 "dhcp-duid is not allowed for IPv4",
399 ));
400 }
401 }
402
403 let ip: InterfaceIp = match serde_json::from_value(v) {
404 Ok(i) => i,
405 Err(e) => {
406 return Err(serde::de::Error::custom(format!("{e}")));
407 }
408 };
409 let ret = Self::from(ip);
410 Ok(ret)
411 }
412}
413
414impl From<InterfaceIp> for InterfaceIpv4 {
415 fn from(ip: InterfaceIp) -> Self {
416 Self {
417 enabled: ip.enabled.unwrap_or_default(),
418 enabled_defined: ip.enabled.is_some(),
419 dhcp: ip.dhcp,
420 addresses: ip.addresses,
421 dhcp_client_id: ip.dhcp_client_id,
422 auto_dns: ip.auto_dns,
423 auto_routes: ip.auto_routes,
424 auto_gateway: ip.auto_gateway,
425 auto_table_id: ip.auto_table_id,
426 allow_extra_address: ip.allow_extra_address,
427 auto_route_metric: ip.auto_route_metric,
428 dhcp_send_hostname: ip.dhcp_send_hostname,
429 dhcp_custom_hostname: ip.dhcp_custom_hostname,
430 ..Default::default()
431 }
432 }
433}
434
435impl From<InterfaceIpv4> for InterfaceIp {
436 fn from(ip: InterfaceIpv4) -> Self {
437 let enabled = if ip.enabled_defined {
438 Some(ip.enabled)
439 } else {
440 None
441 };
442 Self {
443 enabled,
444 dhcp: ip.dhcp,
445 addresses: ip.addresses,
446 dhcp_client_id: ip.dhcp_client_id,
447 auto_dns: ip.auto_dns,
448 auto_routes: ip.auto_routes,
449 auto_gateway: ip.auto_gateway,
450 auto_table_id: ip.auto_table_id,
451 allow_extra_address: ip.allow_extra_address,
452 auto_route_metric: ip.auto_route_metric,
453 dhcp_send_hostname: ip.dhcp_send_hostname,
454 dhcp_custom_hostname: ip.dhcp_custom_hostname,
455 ..Default::default()
456 }
457 }
458}
459
460#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
461#[non_exhaustive]
462#[serde(into = "InterfaceIp")]
463pub struct InterfaceIpv6 {
486 pub enabled: bool,
489 pub(crate) enabled_defined: bool,
490 pub dhcp: Option<bool>,
492 pub dhcp_duid: Option<Dhcpv6Duid>,
495 pub autoconf: Option<bool>,
497 pub addr_gen_mode: Option<Ipv6AddrGenMode>,
500 pub addresses: Option<Vec<InterfaceIpAddr>>,
506 pub auto_dns: Option<bool>,
513 pub auto_gateway: Option<bool>,
516 pub auto_routes: Option<bool>,
520 pub auto_table_id: Option<u32>,
525 pub allow_extra_address: Option<bool>,
532 pub auto_route_metric: Option<u32>,
536 pub token: Option<String>,
539 pub dhcp_send_hostname: Option<bool>,
544 pub dhcp_custom_hostname: Option<String>,
549
550 pub(crate) dns: Option<DnsClientState>,
551 pub(crate) rules: Option<HashSet<RouteRuleEntry>>,
552}
553
554impl InterfaceIpv6 {
555 pub fn new() -> Self {
557 Self::default()
558 }
559
560 pub(crate) fn is_auto(&self) -> bool {
561 self.enabled && (self.dhcp == Some(true) || self.autoconf == Some(true))
562 }
563
564 pub fn is_static(&self) -> bool {
565 self.enabled
566 && !self.is_auto()
567 && !self.addresses.as_deref().unwrap_or_default().is_empty()
568 }
569
570 pub(crate) fn sanitize(
576 &mut self,
577 is_desired: bool,
578 ) -> Result<(), NmstateError> {
579 if let Some(addrs) = self.addresses.as_mut() {
580 if is_desired {
581 for addr in addrs.as_slice().iter().filter(|a| a.is_auto()) {
582 log::info!("Ignoring Auto IP address {addr}");
583 }
584 if let Some(addr) = addrs.iter().find(|a| a.ip.is_ipv4()) {
585 return Err(NmstateError::new(
586 ErrorKind::InvalidArgument,
587 format!(
588 "Got IPv4 address {addr} in ipv6 config section"
589 ),
590 ));
591 }
592 if let Some(addr) = addrs
593 .iter()
594 .find(|a| a.prefix_length as usize > IPV6_ADDR_LEN)
595 {
596 return Err(NmstateError::new(
597 ErrorKind::InvalidArgument,
598 format!(
599 "Invalid IPv6 network prefix length '{}', should \
600 be in the range of 0 to {IPV6_ADDR_LEN}",
601 addr.prefix_length
602 ),
603 ));
604 }
605 }
606 addrs.retain(|a| !a.is_auto());
607 addrs.iter_mut().for_each(|a| {
608 a.valid_life_time = None;
609 a.preferred_life_time = None
610 });
611 }
612
613 if self.is_auto() {
614 if self.auto_dns.is_none() {
615 self.auto_dns = Some(true);
616 }
617 if self.auto_routes.is_none() {
618 self.auto_routes = Some(true);
619 }
620 if self.auto_gateway.is_none() {
621 self.auto_gateway = Some(true);
622 }
623 if let Some(addrs) = self.addresses.as_ref() {
624 if is_desired {
625 for addr in addrs {
626 log::info!(
627 "Static addresses {addr} defined when dynamic IP \
628 is enabled"
629 );
630 }
631 }
632 }
633 }
634
635 if let Some(addrs) = self.addresses.as_mut() {
636 addrs.retain(|addr| {
637 if let IpAddr::V6(ip_addr) = addr.ip {
638 if is_ipv6_unicast_link_local(&ip_addr) {
639 if is_desired {
640 log::warn!(
641 "Ignoring IPv6 link local address {}/{}",
642 &addr.ip,
643 addr.prefix_length
644 );
645 }
646 false
647 } else {
648 true
649 }
650 } else {
651 false
652 }
653 })
654 };
655
656 if !self.enabled {
657 self.dhcp = None;
658 self.autoconf = None;
659 self.addresses = None;
660 }
661
662 if !self.is_auto() {
663 self.auto_dns = None;
664 self.auto_gateway = None;
665 self.auto_routes = None;
666 self.auto_table_id = None;
667 self.auto_route_metric = None;
668 self.dhcp_send_hostname = None;
669 self.dhcp_custom_hostname = None;
670 }
671 if let Some(addrs) = self.addresses.as_mut() {
672 for addr in addrs.iter_mut() {
673 addr.mptcp_flags = None;
674 }
675 }
676 if let Some(token) = self.token.as_mut() {
677 if is_desired
678 && self.autoconf == Some(false)
679 && !(token.is_empty() || token == "::")
680 {
681 return Err(NmstateError::new(
682 ErrorKind::InvalidArgument,
683 format!(
684 "Desired IPv6 token '{token}' cannot be applied with \
685 IPv6 autoconf disabled, you may remove IPv6 token by \
686 setting as empty string or `::`"
687 ),
688 ));
689 }
690 sanitize_ipv6_token_to_string(token)?;
691 }
692 if self.dhcp_send_hostname == Some(false) {
693 if is_desired {
694 if let Some(custom_hostname) =
695 self.dhcp_custom_hostname.as_deref()
696 {
697 if !custom_hostname.is_empty() {
698 log::warn!(
699 "Ignoring `dhcp-custom-hostname: \
700 {custom_hostname}` as `dhcp-send-hostname` is \
701 disabled"
702 );
703 }
704 }
705 }
706 self.dhcp_custom_hostname = None;
707 }
708 Ok(())
709 }
710
711 pub(crate) fn special_merge(&mut self, desired: &Self, current: &Self) {
713 if !desired.enabled_defined {
714 self.enabled = current.enabled;
715 }
716 if desired.dhcp.is_none() && self.enabled {
717 self.dhcp = current.dhcp;
718 }
719 if desired.autoconf.is_none() && self.enabled {
720 self.autoconf = current.autoconf;
721 }
722 if current.is_auto()
727 && current.addresses.is_some()
728 && self.enabled
729 && !self.is_auto()
730 && is_ip_addrs_none_or_all_auto(desired.addresses.as_deref())
731 {
732 self.addresses.clone_from(¤t.addresses);
733 }
734
735 self.rules.clone_from(¤t.rules);
737
738 self.sanitize(false).ok();
739 }
740
741 pub(crate) fn merge_ip(&mut self, current: &Self) {
742 if !self.enabled_defined {
743 self.enabled = current.enabled;
744 }
745 if self.dhcp.is_none() && self.enabled {
746 self.dhcp = current.dhcp;
747 }
748 if self.autoconf.is_none() && self.enabled {
749 self.autoconf = current.autoconf;
750 }
751 if current.is_auto()
756 && current.addresses.is_some()
757 && self.enabled
758 && !self.is_auto()
759 && is_ip_addrs_none_or_all_auto(self.addresses.as_deref())
760 {
761 self.addresses.clone_from(¤t.addresses);
762 if let Some(addrs) = self.addresses.as_mut() {
763 addrs.as_mut_slice().iter_mut().for_each(|a| {
764 a.valid_life_time = None;
765 a.preferred_life_time = None;
766 });
767 }
768 }
769 }
770}
771
772impl<'de> Deserialize<'de> for InterfaceIpv6 {
773 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
774 where
775 D: Deserializer<'de>,
776 {
777 let v = serde_json::Value::deserialize(deserializer)?;
778
779 if let Some(v_map) = v.as_object() {
780 if v_map.contains_key("dhcp_client_id") {
781 return Err(serde::de::Error::custom(
782 "dhcp-client-id is not allowed for IPv6",
783 ));
784 }
785 }
786 let ip: InterfaceIp = match serde_json::from_value(v) {
787 Ok(i) => i,
788 Err(e) => {
789 return Err(serde::de::Error::custom(format!("{e}")));
790 }
791 };
792 let ret = Self::from(ip);
793 Ok(ret)
794 }
795}
796
797impl From<InterfaceIp> for InterfaceIpv6 {
798 fn from(ip: InterfaceIp) -> Self {
799 Self {
800 enabled: ip.enabled.unwrap_or_default(),
801 enabled_defined: ip.enabled.is_some(),
802 dhcp: ip.dhcp,
803 autoconf: ip.autoconf,
804 addresses: ip.addresses,
805 dhcp_duid: ip.dhcp_duid,
806 auto_dns: ip.auto_dns,
807 auto_routes: ip.auto_routes,
808 auto_gateway: ip.auto_gateway,
809 auto_table_id: ip.auto_table_id,
810 addr_gen_mode: ip.addr_gen_mode,
811 allow_extra_address: ip.allow_extra_address,
812 auto_route_metric: ip.auto_route_metric,
813 token: ip.token,
814 dhcp_send_hostname: ip.dhcp_send_hostname,
815 dhcp_custom_hostname: ip.dhcp_custom_hostname,
816 ..Default::default()
817 }
818 }
819}
820
821impl From<InterfaceIpv6> for InterfaceIp {
822 fn from(ip: InterfaceIpv6) -> Self {
823 let enabled = if ip.enabled_defined {
824 Some(ip.enabled)
825 } else {
826 None
827 };
828 Self {
829 enabled,
830 dhcp: ip.dhcp,
831 autoconf: ip.autoconf,
832 addresses: ip.addresses,
833 dhcp_duid: ip.dhcp_duid,
834 auto_dns: ip.auto_dns,
835 auto_routes: ip.auto_routes,
836 auto_gateway: ip.auto_gateway,
837 auto_table_id: ip.auto_table_id,
838 addr_gen_mode: ip.addr_gen_mode,
839 allow_extra_address: ip.allow_extra_address,
840 auto_route_metric: ip.auto_route_metric,
841 token: ip.token,
842 dhcp_send_hostname: ip.dhcp_send_hostname,
843 dhcp_custom_hostname: ip.dhcp_custom_hostname,
844 ..Default::default()
845 }
846 }
847}
848
849#[derive(
850 Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize,
851)]
852#[serde(rename_all = "kebab-case", deny_unknown_fields)]
853#[non_exhaustive]
854pub struct InterfaceIpAddr {
855 pub ip: IpAddr,
857 #[serde(deserialize_with = "crate::deserializer::u8_or_string")]
858 pub prefix_length: u8,
861 #[serde(skip_serializing_if = "is_none_or_empty_mptcp_flags", default)]
862 pub mptcp_flags: Option<Vec<MptcpAddressFlag>>,
867 #[serde(
873 skip_serializing_if = "Option::is_none",
874 alias = "valid-left",
875 alias = "valid-lft"
876 )]
877 pub valid_life_time: Option<String>,
878 #[serde(
885 skip_serializing_if = "Option::is_none",
886 alias = "preferred-left",
887 alias = "preferred-lft"
888 )]
889 pub preferred_life_time: Option<String>,
890}
891
892impl Default for InterfaceIpAddr {
893 fn default() -> Self {
894 Self {
895 ip: IpAddr::V6(std::net::Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)),
896 prefix_length: 128,
897 mptcp_flags: None,
898 valid_life_time: None,
899 preferred_life_time: None,
900 }
901 }
902}
903
904impl std::fmt::Display for InterfaceIpAddr {
905 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
906 if self.is_auto() {
907 write!(
908 f,
909 "{}/{} valid_life_time {} preferred_lft {}",
910 self.ip,
911 self.prefix_length,
912 self.valid_life_time.as_deref().unwrap_or(FOREVER),
913 self.preferred_life_time.as_deref().unwrap_or(FOREVER)
914 )
915 } else {
916 write!(f, "{}/{}", self.ip, self.prefix_length)
917 }
918 }
919}
920
921impl InterfaceIpAddr {
922 pub(crate) fn is_auto(&self) -> bool {
923 self.valid_life_time.is_some()
924 && self.valid_life_time.as_deref() != Some(FOREVER)
925 }
926}
927
928pub(crate) fn is_ipv6_addr(addr: &str) -> bool {
929 addr.contains(':')
930}
931
932pub(crate) fn is_ipv6_unicast_link_local(ip: &Ipv6Addr) -> bool {
935 (ip.segments()[0] & 0xffc0) == 0xfe80
936}
937
938impl std::convert::TryFrom<&str> for InterfaceIpAddr {
939 type Error = NmstateError;
940 fn try_from(value: &str) -> Result<Self, Self::Error> {
941 let mut addr: Vec<&str> = value.split('/').collect();
942 addr.resize(2, "");
943 let ip = IpAddr::from_str(addr[0]).map_err(|e| {
944 let e = NmstateError::new(
945 ErrorKind::InvalidArgument,
946 format!("Invalid IP address {}: {e}", addr[0]),
947 );
948 log::error!("{e}");
949 e
950 })?;
951
952 let prefix_length = if addr[1].is_empty() {
953 if ip.is_ipv6() {
954 128
955 } else {
956 32
957 }
958 } else {
959 addr[1].parse::<u8>().map_err(|parse_error| {
960 let e = NmstateError::new(
961 ErrorKind::InvalidArgument,
962 format!("Invalid IP address {value}: {parse_error}"),
963 );
964 log::error!("{e}");
965 e
966 })?
967 };
968 Ok(Self {
969 ip,
970 prefix_length,
971 mptcp_flags: None,
972 valid_life_time: None,
973 preferred_life_time: None,
974 })
975 }
976}
977
978#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
979#[non_exhaustive]
980pub enum Dhcpv4ClientId {
982 #[serde(rename = "ll", alias = "LL")]
985 LinkLayerAddress,
986 #[serde(rename = "iaid+duid", alias = "IAID+DUID")]
989 IaidPlusDuid,
990 #[serde(untagged)]
992 Other(String),
993}
994
995impl std::fmt::Display for Dhcpv4ClientId {
996 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
997 write!(
998 f,
999 "{}",
1000 match self {
1001 Dhcpv4ClientId::LinkLayerAddress => "ll",
1002 Dhcpv4ClientId::IaidPlusDuid => "iaid+duid",
1003 Dhcpv4ClientId::Other(s) => s,
1004 }
1005 )
1006 }
1007}
1008
1009#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1010#[non_exhaustive]
1011#[serde(rename_all = "kebab-case")]
1012pub enum Dhcpv6Duid {
1014 #[serde(rename = "llt", alias = "LLT")]
1017 LinkLayerAddressPlusTime,
1018 #[serde(rename = "en", alias = "EN")]
1021 EnterpriseNumber,
1022 #[serde(rename = "ll", alias = "LL")]
1025 LinkLayerAddress,
1026 #[serde(alias = "UUID")]
1029 Uuid,
1030 #[serde(untagged)]
1032 Other(String),
1033}
1034
1035impl std::fmt::Display for Dhcpv6Duid {
1036 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1037 write!(
1038 f,
1039 "{}",
1040 match self {
1041 Dhcpv6Duid::LinkLayerAddressPlusTime => "llt",
1042 Dhcpv6Duid::EnterpriseNumber => "en",
1043 Dhcpv6Duid::LinkLayerAddress => "ll",
1044 Dhcpv6Duid::Uuid => "uuid",
1045 Dhcpv6Duid::Other(s) => s,
1046 }
1047 )
1048 }
1049}
1050
1051#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1052pub enum Ipv6AddrGenMode {
1054 #[serde(rename = "eui64", alias = "EUI64")]
1057 Eui64,
1058 #[serde(rename = "stable-privacy", alias = "STABLE-PRIVACY")]
1061 StablePrivacy,
1062 #[serde(untagged)]
1064 Other(String),
1065}
1066
1067impl std::fmt::Display for Ipv6AddrGenMode {
1068 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1069 write!(
1070 f,
1071 "{}",
1072 match self {
1073 Ipv6AddrGenMode::Eui64 => "eui64",
1074 Ipv6AddrGenMode::StablePrivacy => "stable-privacy",
1075 Ipv6AddrGenMode::Other(s) => s,
1076 }
1077 )
1078 }
1079}
1080
1081#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1082#[serde(rename_all = "kebab-case", deny_unknown_fields)]
1083#[non_exhaustive]
1084pub enum WaitIp {
1087 Any,
1091 Ipv4,
1094 Ipv6,
1097 #[serde(rename = "ipv4+ipv6")]
1101 Ipv4AndIpv6,
1102}
1103
1104impl std::fmt::Display for WaitIp {
1105 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1106 write!(
1107 f,
1108 "{}",
1109 match self {
1110 Self::Any => "any",
1111 Self::Ipv4 => "ipv4",
1112 Self::Ipv6 => "ipv6",
1113 Self::Ipv4AndIpv6 => "ipv4+ipv6",
1114 }
1115 )
1116 }
1117}
1118
1119fn validate_wait_ip(base_iface: &BaseInterface) -> Result<(), NmstateError> {
1120 if let Some(wait_ip) = base_iface.wait_ip.as_ref() {
1121 if (wait_ip == &WaitIp::Ipv4 || wait_ip == &WaitIp::Ipv4AndIpv6)
1122 && !base_iface
1123 .ipv4
1124 .as_ref()
1125 .map(|i| i.enabled)
1126 .unwrap_or_default()
1127 {
1128 let e = NmstateError::new(
1129 ErrorKind::InvalidArgument,
1130 format!(
1131 "Cannot set 'wait-ip: {}' with IPv4 disabled. Interface: \
1132 {}({})",
1133 wait_ip, &base_iface.name, &base_iface.iface_type
1134 ),
1135 );
1136 log::error!("{e}");
1137 return Err(e);
1138 }
1139 if (wait_ip == &WaitIp::Ipv6 || wait_ip == &WaitIp::Ipv4AndIpv6)
1140 && !base_iface
1141 .ipv6
1142 .as_ref()
1143 .map(|i| i.enabled)
1144 .unwrap_or_default()
1145 {
1146 let e = NmstateError::new(
1147 ErrorKind::InvalidArgument,
1148 format!(
1149 "Cannot set 'wait-ip: {}' with IPv6 disabled. Interface: \
1150 {}({})",
1151 wait_ip, &base_iface.name, &base_iface.iface_type
1152 ),
1153 );
1154 log::error!("{e}");
1155 return Err(e);
1156 }
1157 }
1158
1159 Ok(())
1160}
1161
1162pub(crate) fn sanitize_ip_network(
1163 ip_net: &str,
1164) -> Result<String, NmstateError> {
1165 let ip_nets: Vec<&str> = ip_net.split('/').collect();
1166 match ip_nets.len() {
1167 0 => Err(NmstateError::new(
1168 ErrorKind::InvalidArgument,
1169 "Invalid IP network string, got empty string".into(),
1170 )),
1171 1 => {
1172 let ip = IpAddr::from_str(ip_nets[0]).map_err(|e| {
1173 NmstateError::new(
1174 ErrorKind::InvalidArgument,
1175 format!("Invalid IP network '{ip_net}': {e}",),
1176 )
1177 })?;
1178 Ok(if ip.is_ipv6() {
1179 format!("{ip}/{IPV6_ADDR_LEN}")
1180 } else {
1181 format!("{ip}/{IPV4_ADDR_LEN}")
1182 })
1183 }
1184 2 => {
1185 let prefix_len = ip_nets[1].parse::<usize>().map_err(|e| {
1186 NmstateError::new(
1187 ErrorKind::InvalidArgument,
1188 format!(
1189 "Invalid IP network prefix length '{}' in '{ip_net}': \
1190 {e}",
1191 ip_nets[1]
1192 ),
1193 )
1194 })?;
1195 let ip =
1196 apply_ip_prefix_len(IpAddr::from_str(ip_nets[0])?, prefix_len);
1197 if ip.is_ipv6() {
1198 if prefix_len > IPV6_ADDR_LEN {
1199 Err(NmstateError::new(
1200 ErrorKind::InvalidArgument,
1201 format!(
1202 "Invalid IPv6 network prefix length '{}' in \
1203 '{ip_net}', should be smaller than \
1204 {IPV6_ADDR_LEN}'",
1205 ip_nets[1],
1206 ),
1207 ))
1208 } else {
1209 Ok(format!("{ip}/{prefix_len}"))
1210 }
1211 } else if prefix_len > IPV4_ADDR_LEN {
1212 Err(NmstateError::new(
1213 ErrorKind::InvalidArgument,
1214 format!(
1215 "Invalid IPv4 network prefix length '{}' in \
1216 '{ip_net}', should be smaller than {IPV4_ADDR_LEN}'",
1217 ip_nets[1],
1218 ),
1219 ))
1220 } else {
1221 Ok(format!("{ip}/{prefix_len}"))
1222 }
1223 }
1224 _ => Err(NmstateError::new(
1225 ErrorKind::InvalidArgument,
1226 format!(
1227 "Invalid IP network string: '{ip_net}', expecting 'ip/prefix' \
1228 or 'ip' format, for example: 192.0.2.0/24 or 2001:db8:1::/64 \
1229 or 192.0.2.1"
1230 ),
1231 )),
1232 }
1233}
1234
1235fn is_none_or_empty_mptcp_flags(v: &Option<Vec<MptcpAddressFlag>>) -> bool {
1236 if let Some(v) = v {
1237 v.is_empty()
1238 } else {
1239 true
1240 }
1241}
1242
1243#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
1244#[serde(rename_all = "lowercase")]
1245#[non_exhaustive]
1246pub enum AddressFamily {
1247 IPv4,
1248 IPv6,
1249 Unknown,
1250}
1251
1252impl From<u8> for AddressFamily {
1253 fn from(d: u8) -> Self {
1254 match d {
1255 AF_INET => AddressFamily::IPv4,
1256 AF_INET6 => AddressFamily::IPv6,
1257 _ => AddressFamily::Unknown,
1258 }
1259 }
1260}
1261
1262impl Default for AddressFamily {
1263 fn default() -> Self {
1264 Self::IPv4
1265 }
1266}
1267
1268impl std::fmt::Display for AddressFamily {
1269 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1270 write!(
1271 f,
1272 "{}",
1273 match self {
1274 Self::IPv4 => "ipv4",
1275 Self::IPv6 => "ipv6",
1276 Self::Unknown => "unknown",
1277 }
1278 )
1279 }
1280}
1281
1282impl MergedInterface {
1283 pub(crate) fn post_inter_ifaces_process_ip(
1285 &mut self,
1286 ) -> Result<(), NmstateError> {
1287 if let (Some(current), Some(apply_iface), Some(verify_iface)) = (
1288 self.current.as_ref().map(|i| i.base_iface()),
1289 self.for_apply.as_mut().map(|i| i.base_iface_mut()),
1290 self.for_verify.as_mut().map(|i| i.base_iface_mut()),
1291 ) {
1292 if let (Some(des_ipv4), Some(cur_ipv4)) =
1293 (apply_iface.ipv4.as_mut(), current.ipv4.as_ref())
1294 {
1295 des_ipv4.merge_ip(cur_ipv4);
1296 }
1297 if let (Some(des_ipv6), Some(cur_ipv6)) =
1298 (apply_iface.ipv6.as_mut(), current.ipv6.as_ref())
1299 {
1300 des_ipv6.merge_ip(cur_ipv6);
1301 }
1302 if let (Some(des_ipv4), Some(cur_ipv4)) =
1303 (verify_iface.ipv4.as_mut(), current.ipv4.as_ref())
1304 {
1305 des_ipv4.merge_ip(cur_ipv4);
1306 }
1307 if let (Some(des_ipv6), Some(cur_ipv6)) =
1308 (verify_iface.ipv6.as_mut(), current.ipv6.as_ref())
1309 {
1310 des_ipv6.merge_ip(cur_ipv6);
1311 }
1312 validate_wait_ip(apply_iface)?;
1313 if !apply_iface.can_have_ip() {
1314 apply_iface.wait_ip = None;
1315 verify_iface.wait_ip = None;
1316 }
1317 }
1318
1319 Ok(())
1320 }
1321}
1322
1323fn sanitize_ipv6_token_to_string(
1326 token: &mut String,
1327) -> Result<(), NmstateError> {
1328 if token.is_empty() {
1330 write!(token, "::").ok();
1331 } else {
1332 match Ipv6Addr::from_str(token.as_str()) {
1333 Ok(ip) => {
1334 if ip.octets()[..8] != [0; 8] {
1335 return Err(NmstateError::new(
1336 ErrorKind::InvalidArgument,
1337 format!(
1338 "Desired IPv6 token should be lead by 64 bits 0. \
1339 But got {token}"
1340 ),
1341 ));
1342 }
1343 let mut segments = ip.segments();
1349 segments[0] = 0x2001;
1350 segments[1] = 0xdb8;
1351 let new_ip = Ipv6Addr::from(segments);
1352 token.clear();
1353 write!(token, "{}", &new_ip.to_string()["2001:db8".len()..])
1354 .ok();
1355 }
1356 Err(e) => {
1357 return Err(NmstateError::new(
1358 ErrorKind::InvalidArgument,
1359 format!(
1360 "Desired IPv6 token '{token}' is not a valid IPv6 \
1361 address: {e}"
1362 ),
1363 ));
1364 }
1365 }
1366 }
1367 Ok(())
1368}
1369
1370fn is_ip_addrs_none_or_all_auto(addrs: Option<&[InterfaceIpAddr]>) -> bool {
1371 addrs.map_or(true, |addrs| {
1372 addrs.iter().all(|a| {
1373 if let IpAddr::V6(ip_addr) = a.ip {
1374 is_ipv6_unicast_link_local(&ip_addr) || a.is_auto()
1375 } else {
1376 a.is_auto()
1377 }
1378 })
1379 })
1380}
1381
1382fn apply_ip_prefix_len(ip: IpAddr, prefix_length: usize) -> IpAddr {
1383 if prefix_length == 0 {
1384 return if ip.is_ipv6() {
1385 IpAddr::V6(0.into())
1386 } else {
1387 IpAddr::V4(0.into())
1388 };
1389 }
1390
1391 if (ip.is_ipv6() && prefix_length >= IPV6_ADDR_LEN)
1392 || (ip.is_ipv4() && prefix_length >= IPV4_ADDR_LEN)
1393 {
1394 return ip;
1395 }
1396
1397 match ip {
1398 IpAddr::V6(i) => Ipv6Addr::from(
1399 u128::from(i) & (u128::MAX << (IPV6_ADDR_LEN - prefix_length)),
1400 )
1401 .into(),
1402 IpAddr::V4(i) => Ipv4Addr::from(
1403 u32::from(i) & (u32::MAX << (IPV4_ADDR_LEN - prefix_length)),
1404 )
1405 .into(),
1406 }
1407}