nmstate/
ip.rs

1// SPDX-License-Identifier: Apache-2.0
2
3use 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]
113/// IPv4 configuration of interface.
114/// Example YAML output of interface holding static IPv4:
115/// ```yaml
116/// ---
117/// interfaces:
118/// - name: eth1
119///   state: up
120///   mtu: 1500
121///   ipv4:
122///     address:
123///     - ip: 192.0.2.252
124///       prefix-length: 24
125///     - ip: 192.0.2.251
126///       prefix-length: 24
127///     dhcp: false
128///     enabled: true
129/// ```
130pub struct InterfaceIpv4 {
131    /// Whether IPv4 stack is enabled. When set to false, all IPv4 address will
132    /// be removed from this interface.
133    pub enabled: bool,
134    /// Indicate whether `enabled` is defined by user or learn from
135    /// default/current value.
136    pub(crate) enabled_defined: bool,
137    /// Whether DHCPv4 is enabled.
138    pub dhcp: Option<bool>,
139    /// DHCPv4 client ID.
140    /// Serialize and deserialize to/from `dhcp-client-id`.
141    pub dhcp_client_id: Option<Dhcpv4ClientId>,
142    /// IPv4 addresses.
143    /// When applying with `None`, current IP address will be preserved.
144    /// When applying with `Some(Vec::new())`, all IP address will be removed.
145    /// When switch from DHCP on to off with `addresses` set to None or all
146    /// `addresses` are dynamic, nmstate will convert current dynamic IP
147    /// address to static.
148    /// The IP addresses will apply to kernel with the same order specified
149    /// which result the IP addresses after first one holding the `secondary`
150    /// flag.
151    pub addresses: Option<Vec<InterfaceIpAddr>>,
152    /// Whether per‐interface IPv4 sysctl forwarding is enabled.
153    pub forwarding: Option<bool>,
154    /// Whether to apply DNS resolver information retrieved from DHCP server.
155    /// Serialize and deserialize to/from `auto-dns`.
156    /// If you want to append static DNS name server before DHCP provided
157    /// servers, please set `auto-dns: true` explicitly a along with static
158    /// DNS server.
159    pub auto_dns: Option<bool>,
160    /// Whether to set default gateway retrieved from DHCP server.
161    /// Serialize and deserialize to/from `auto-gateway`.
162    pub auto_gateway: Option<bool>,
163    /// Whether to set routes(including default gateway) retrieved from DHCP
164    /// server.
165    /// Serialize and deserialize to/from `auto-routes`.
166    pub auto_routes: Option<bool>,
167    /// The route table ID used to hold routes(including default gateway)
168    /// retrieved from DHCP server.
169    /// If not defined, the main(254) will be used.
170    /// Serialize and deserialize to/from `auto-table-id`.
171    pub auto_table_id: Option<u32>,
172    /// If not defined or set to true, nmstate verification process
173    /// allows extra IP address found as long as desired IP address matched.
174    /// When set to false, the verification process of nmstate do exact equal
175    /// check on IP address.
176    /// Serialize/deserialize to/from `allow-extra-address`
177    pub allow_extra_address: Option<bool>,
178    /// Metric for routes retrieved from DHCP server.
179    /// Only available for DHCPv4 enabled interface.
180    /// Deserialize from `auto-route-metric`
181    pub auto_route_metric: Option<u32>,
182    /// Whether to include hostname in DHCP request.
183    /// If the hostname is FQDN, the `Fully Qualified Domain Name (FQDN)`
184    /// option(81) defined in RFC 4702 will be used.
185    /// If the hostname is not FQDN, the `Host Name` option(12) defined in RFC
186    /// 2132 will be used.
187    /// If not defined, set to True when DHCPv4 enabled.
188    /// Deserialize from `dhcp-send-hostname`
189    pub dhcp_send_hostname: Option<bool>,
190    /// Custom string to override hostname used for DHCP request.
191    /// If the hostname is FQDN, the `Fully Qualified Domain Name (FQDN)`
192    /// option(81) defined in RFC 4702 will be used.
193    /// If the hostname is not FQDN, the `Host Name` option(12) defined in RFC
194    /// 2132 will be used.
195    /// If not defined, current non-dynamic hostname will be used.
196    /// Deserialize from `dhcp-custom-hostname`
197    pub dhcp_custom_hostname: Option<String>,
198    pub(crate) dns: Option<DnsClientState>,
199    pub(crate) rules: Option<HashSet<RouteRuleEntry>>,
200}
201
202impl InterfaceIpv4 {
203    /// Create [InterfaceIpv4] with IP disabled.
204    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        // Normally, we expect backend to preserve configuration which not
226        // mentioned in desire or all auto ip address, but when DHCP switch from
227        // ON to OFF, the design of nmstate is expecting dynamic IP address goes
228        // static. This should be done by top level code.
229        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(&current.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    // Special action for generating merged state from desired and current.
246    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        // Normally, we expect backend to preserve configuration which not
255        // mentioned in desire, but when DHCP switch from ON to OFF, the design
256        // of nmstate is expecting dynamic IP address goes static. This should
257        // be done by top level code.
258        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(&current.addresses);
265        }
266
267        // The rules is `pub(crate)`, it will not merged by `merge_json_value()`
268        self.rules.clone_from(&current.rules);
269
270        self.sanitize(false).ok();
271    }
272
273    // * Remove link-local address
274    // * Set auto_dns, auto_gateway and auto_routes to true if DHCP enabled and
275    //   those options is None
276    // * Disable DHCP and remove address if enabled: false
277    // * Remove auto IP address.
278    // * Set DHCP options to None if DHCP is false
279    // * Remove mptcp_flags is they are for query only
280    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")]
478/// IPv6 configurations of interface.
479/// Example output of interface holding automatic IPv6 settings:
480/// ```yaml
481/// ---
482/// interfaces:
483/// - name: eth1
484///   state: up
485///   mtu: 1500
486///   ipv4:
487///     enabled: false
488///   ipv6:
489///     address:
490///       - ip: 2001:db8:2::1
491///         prefix-length: 64
492///       - ip: 2001:db8:1::1
493///         prefix-length: 64
494///       - ip: fe80::1ec1:cff:fe32:3bd3
495///         prefix-length: 64
496///     autoconf: true
497///     dhcp: true
498///     enabled: true
499/// ```
500pub struct InterfaceIpv6 {
501    /// Whether IPv6 stack is enable. When set to false, the IPv6 stack is
502    /// disabled with IPv6 link-local address purged also.
503    pub enabled: bool,
504    pub(crate) enabled_defined: bool,
505    /// Whether DHCPv6 enabled.
506    pub dhcp: Option<bool>,
507    /// DHCPv6 Unique Identifier
508    /// Serialize and deserialize to/from `dhcp-duid`.
509    pub dhcp_duid: Option<Dhcpv6Duid>,
510    /// Whether autoconf via IPv6 router announcement enabled.
511    pub autoconf: Option<bool>,
512    /// IPv6 address generation mode.
513    /// Serialize and deserialize to/from `addr-gen-mode`.
514    pub addr_gen_mode: Option<Ipv6AddrGenMode>,
515    /// IPv6 addresses. Will be ignored when applying with
516    /// DHCPv6 or autoconf is enabled.
517    /// When applying with `None`, current IP address will be preserved.
518    /// When applying with `Some(Vec::new())`, all IP address will be removed.
519    /// The IP addresses will apply to kernel with the same order specified.
520    pub addresses: Option<Vec<InterfaceIpAddr>>,
521    /// Whether to apply DNS resolver information retrieved from DHCPv6 or
522    /// autoconf.
523    /// Serialize and deserialize to/from `auto-dns`.
524    /// If you want to append static DNS name server before DHCP/Autoconf
525    /// provided servers, please set `auto-dns: true` explicitly a along with
526    /// static DNS server.
527    pub auto_dns: Option<bool>,
528    /// Whether to set default gateway retrieved from autoconf.
529    /// Serialize and deserialize to/from `auto-gateway`.
530    pub auto_gateway: Option<bool>,
531    /// Whether to set routes(including default gateway) retrieved from
532    /// autoconf.
533    /// Serialize and deserialize to/from `auto-routes`.
534    pub auto_routes: Option<bool>,
535    /// The route table ID used to hold routes(including default gateway)
536    /// retrieved from autoconf.
537    /// If not defined, the main(254) will be used.
538    /// Serialize and deserialize to/from `auto-table-id`.
539    pub auto_table_id: Option<u32>,
540    /// By default(true), nmstate verification process allows extra IP address
541    /// found as long as desired IP address matched.
542    /// When set to false, the verification process of nmstate do exact equal
543    /// check on IP address.
544    /// Ignored when serializing.
545    /// Deserialize from `allow-extra-address`.
546    pub allow_extra_address: Option<bool>,
547    /// Metric for routes retrieved from DHCP server.
548    /// Only available for autoconf enabled interface.
549    /// Deserialize from `auto-route-metric`.
550    pub auto_route_metric: Option<u32>,
551    /// IETF draft(expired) Tokenised IPv6 Identifiers. Should be only
552    /// containing the tailing 64 bites for IPv6 address.
553    pub token: Option<String>,
554    /// Whether to include hostname in DHCP request in
555    /// `Fully Qualified Domain Name (FQDN)` option(39) defined in RFC 4704.
556    /// If not defined, set to True when DHCPv6 enabled.
557    /// Deserialize from `dhcp-send-hostname`
558    pub dhcp_send_hostname: Option<bool>,
559    /// Custom string to override hostname used for DHCP request in
560    /// `Fully Qualified Domain Name (FQDN)` option(29) defined in RFC 4704.
561    /// If not defined, current non-dynamic hostname will be used.
562    /// Deserialize from `dhcp-custom-hostname`
563    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    /// New [InterfaceIpv6] with IP disabled.
571    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    // * Set auto_dns, auto_gateway and auto_routes to true if DHCP enabled and
586    //   those options is None
587    // * Disable DHCP and remove address if enabled: false
588    // * Set DHCP options to None if DHCP is false
589    // * Remove `mptcp_flags` as they are for query only
590    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    // Special action for generating merged state from desired and current.
727    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        // Normally, we expect backend to preserve configuration which not
738        // mentioned in desire, but when DHCP switch from ON to OFF, the design
739        // of nmstate is expecting dynamic IP address goes static. This should
740        // be done by top level code.
741        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(&current.addresses);
748        }
749
750        // The rules is `pub(crate)`, it will not merged by `merge_json_value()`
751        self.rules.clone_from(&current.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        // Normally, we expect backend to preserve configuration which not
767        // mentioned in desire, but when DHCP switch from ON to OFF, the design
768        // of nmstate is expecting dynamic IP address goes static. This should
769        // be done by top level code.
770        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(&current.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    /// IP address.
877    pub ip: IpAddr,
878    #[serde(deserialize_with = "crate::deserializer::u8_or_string")]
879    /// Prefix length.
880    /// Serialize and deserialize to/from `prefix-length`.
881    pub prefix_length: u8,
882    #[serde(skip_serializing_if = "is_none_or_empty_mptcp_flags", default)]
883    /// MPTCP flag on this IP address.
884    /// Ignored when applying as nmstate does not support support IP address
885    /// specific MPTCP flags. You should apply MPTCP flags at interface level
886    /// via [BaseInterface.mptcp].
887    pub mptcp_flags: Option<Vec<MptcpAddressFlag>>,
888    /// Remaining time for IP address been valid. The output format is
889    /// "32sec" or "forever".
890    /// This property is query only, it will be ignored when applying.
891    /// Serialize to `valid-life-time`.
892    /// Deserialize from `valid-life-time` or `valid-left` or `valid-lft`.
893    #[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    /// Remaining time for IP address been preferred. The output format is
900    /// "32sec" or "forever".
901    /// This property is query only, it will be ignored when applying.
902    /// Serialize to `preferred-life-time`.
903    /// Deserialize from `preferred-life-time` or `preferred-left` or
904    /// `preferred-lft`.
905    #[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
953// Copy from Rust official std::net::Ipv6Addr::is_unicast_link_local() which
954// is experimental.
955pub(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]
1001/// DHCPv4 client ID
1002pub enum Dhcpv4ClientId {
1003    /// Use link layer address as DHCPv4 client ID.
1004    /// Serialize and deserialize to/from `ll`.
1005    #[serde(rename = "ll", alias = "LL")]
1006    LinkLayerAddress,
1007    /// RFC 4361 type 255, 32 bits IAID followed by DUID.
1008    /// Serialize and deserialize to/from `iaid+duid`.
1009    #[serde(rename = "iaid+duid", alias = "IAID+DUID")]
1010    IaidPlusDuid,
1011    /// hex string or backend specific client id type
1012    #[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")]
1033/// DHCPv6 Unique Identifier
1034pub enum Dhcpv6Duid {
1035    /// DUID Based on Link-Layer Address Plus Time
1036    /// Serialize and deserialize to/from `llt`.
1037    #[serde(rename = "llt", alias = "LLT")]
1038    LinkLayerAddressPlusTime,
1039    /// DUID Assigned by Vendor Based on Enterprise Number
1040    /// Serialize and deserialize to/from `en`.
1041    #[serde(rename = "en", alias = "EN")]
1042    EnterpriseNumber,
1043    /// DUID Assigned by Vendor Based on Enterprise Number
1044    /// Serialize and deserialize to/from `ll`.
1045    #[serde(rename = "ll", alias = "LL")]
1046    LinkLayerAddress,
1047    /// DUID Based on Universally Unique Identifier
1048    /// Serialize and deserialize to/from `uuid`.
1049    #[serde(alias = "UUID")]
1050    Uuid,
1051    /// Backend specific
1052    #[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)]
1073/// IPv6 address generation mode
1074pub enum Ipv6AddrGenMode {
1075    /// EUI-64 format defined by RFC 4862
1076    /// Serialize and deserialize to/from `eui64`.
1077    #[serde(rename = "eui64", alias = "EUI64")]
1078    Eui64,
1079    /// Semantically Opaque Interface Identifiers defined by RFC 7217
1080    /// Serialize and deserialize to/from `stable-privacy`.
1081    #[serde(rename = "stable-privacy", alias = "STABLE-PRIVACY")]
1082    StablePrivacy,
1083    /// Backend specific
1084    #[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]
1105/// Which IP stack should network backend wait before considering the interface
1106/// activation finished.
1107pub enum WaitIp {
1108    /// The activation is considered done once IPv4 stack or IPv6 stack is
1109    /// configure
1110    /// Serialize and deserialize to/from `any`.
1111    Any,
1112    /// The activation is considered done once IPv4 stack is configured.
1113    /// Serialize and deserialize to/from `ipv4`.
1114    Ipv4,
1115    /// The activation is considered done once IPv6 stack is configured.
1116    /// Serialize and deserialize to/from `ipv6`.
1117    Ipv6,
1118    /// The activation is considered done once both IPv4 and IPv6 stack are
1119    /// configured.
1120    /// Serialize and deserialize to/from `ipv4+ipv6`.
1121    #[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    // * Merge `enabled: true` from current if not mentioned in desired.
1301    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
1340// User might define IPv6 token in the format of `::0.0.250.193`, which should
1341// be sanitize to `::fac1`.
1342fn sanitize_ipv6_token_to_string(
1343    token: &mut String,
1344) -> Result<(), NmstateError> {
1345    // Empty token means reverting to default "::"
1346    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                // The Ipv6Addr::to_string() will convert
1361                //  ::fac1 to ::0.0.250.193
1362                // Which is no ideal in this case
1363                // To workaround that, we set leading 64 bits to '2001:db8::',
1364                // and then trip it out from string.
1365                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}