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}
107
108#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
109#[serde(into = "InterfaceIp")]
110#[non_exhaustive]
111/// IPv4 configuration of interface.
112/// Example YAML output of interface holding static IPv4:
113/// ```yaml
114/// ---
115/// interfaces:
116/// - name: eth1
117///   state: up
118///   mtu: 1500
119///   ipv4:
120///     address:
121///     - ip: 192.0.2.252
122///       prefix-length: 24
123///     - ip: 192.0.2.251
124///       prefix-length: 24
125///     dhcp: false
126///     enabled: true
127/// ```
128pub struct InterfaceIpv4 {
129    /// Whether IPv4 stack is enabled. When set to false, all IPv4 address will
130    /// be removed from this interface.
131    pub enabled: bool,
132    /// Indicate whether `enabled` is defined by user or learn from
133    /// default/current value.
134    pub(crate) enabled_defined: bool,
135    /// Whether DHCPv4 is enabled.
136    pub dhcp: Option<bool>,
137    /// DHCPv4 client ID.
138    /// Serialize and deserialize to/from `dhcp-client-id`.
139    pub dhcp_client_id: Option<Dhcpv4ClientId>,
140    /// IPv4 addresses.
141    /// When applying with `None`, current IP address will be preserved.
142    /// When applying with `Some(Vec::new())`, all IP address will be removed.
143    /// When switch from DHCP on to off with `addresses` set to None or all
144    /// `addresses` are dynamic, nmstate will convert current dynamic IP
145    /// address to static.
146    /// The IP addresses will apply to kernel with the same order specified
147    /// which result the IP addresses after first one holding the `secondary`
148    /// flag.
149    pub addresses: Option<Vec<InterfaceIpAddr>>,
150    /// Whether to apply DNS resolver information retrieved from DHCP server.
151    /// Serialize and deserialize to/from `auto-dns`.
152    /// If you want to append static DNS name server before DHCP provided
153    /// servers, please set `auto-dns: true` explicitly a along with static
154    /// DNS server.
155    pub auto_dns: Option<bool>,
156    /// Whether to set default gateway retrieved from DHCP server.
157    /// Serialize and deserialize to/from `auto-gateway`.
158    pub auto_gateway: Option<bool>,
159    /// Whether to set routes(including default gateway) retrieved from DHCP
160    /// server.
161    /// Serialize and deserialize to/from `auto-routes`.
162    pub auto_routes: Option<bool>,
163    /// The route table ID used to hold routes(including default gateway)
164    /// retrieved from DHCP server.
165    /// If not defined, the main(254) will be used.
166    /// Serialize and deserialize to/from `auto-table-id`.
167    pub auto_table_id: Option<u32>,
168    /// If not defined or set to true, nmstate verification process
169    /// allows extra IP address found as long as desired IP address matched.
170    /// When set to false, the verification process of nmstate do exact equal
171    /// check on IP address.
172    /// Serialize/deserialize to/from `allow-extra-address`
173    pub allow_extra_address: Option<bool>,
174    /// Metric for routes retrieved from DHCP server.
175    /// Only available for DHCPv4 enabled interface.
176    /// Deserialize from `auto-route-metric`
177    pub auto_route_metric: Option<u32>,
178    /// Whether to include hostname in DHCP request.
179    /// If the hostname is FQDN, the `Fully Qualified Domain Name (FQDN)`
180    /// option(81) defined in RFC 4702 will be used.
181    /// If the hostname is not FQDN, the `Host Name` option(12) defined in RFC
182    /// 2132 will be used.
183    /// If not defined, set to True when DHCPv4 enabled.
184    /// Deserialize from `dhcp-send-hostname`
185    pub dhcp_send_hostname: Option<bool>,
186    /// Custom string to override hostname used for DHCP request.
187    /// If the hostname is FQDN, the `Fully Qualified Domain Name (FQDN)`
188    /// option(81) defined in RFC 4702 will be used.
189    /// If the hostname is not FQDN, the `Host Name` option(12) defined in RFC
190    /// 2132 will be used.
191    /// If not defined, current non-dynamic hostname will be used.
192    /// Deserialize from `dhcp-custom-hostname`
193    pub dhcp_custom_hostname: Option<String>,
194    pub(crate) dns: Option<DnsClientState>,
195    pub(crate) rules: Option<HashSet<RouteRuleEntry>>,
196}
197
198impl InterfaceIpv4 {
199    /// Create [InterfaceIpv4] with IP disabled.
200    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        // Normally, we expect backend to preserve configuration which not
222        // mentioned in desire or all auto ip address, but when DHCP switch from
223        // ON to OFF, the design of nmstate is expecting dynamic IP address goes
224        // static. This should be done by top level code.
225        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(&current.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    // Special action for generating merged state from desired and current.
242    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        // Normally, we expect backend to preserve configuration which not
251        // mentioned in desire, but when DHCP switch from ON to OFF, the design
252        // of nmstate is expecting dynamic IP address goes static. This should
253        // be done by top level code.
254        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(&current.addresses);
261        }
262
263        // The rules is `pub(crate)`, it will not merged by `merge_json_value()`
264        self.rules.clone_from(&current.rules);
265
266        self.sanitize(false).ok();
267    }
268
269    // * Remove link-local address
270    // * Set auto_dns, auto_gateway and auto_routes to true if DHCP enabled and
271    //   those options is None
272    // * Disable DHCP and remove address if enabled: false
273    // * Remove auto IP address.
274    // * Set DHCP options to None if DHCP is false
275    // * Remove mptcp_flags is they are for query only
276    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")]
463/// IPv6 configurations of interface.
464/// Example output of interface holding automatic IPv6 settings:
465/// ```yaml
466/// ---
467/// interfaces:
468/// - name: eth1
469///   state: up
470///   mtu: 1500
471///   ipv4:
472///     enabled: false
473///   ipv6:
474///     address:
475///       - ip: 2001:db8:2::1
476///         prefix-length: 64
477///       - ip: 2001:db8:1::1
478///         prefix-length: 64
479///       - ip: fe80::1ec1:cff:fe32:3bd3
480///         prefix-length: 64
481///     autoconf: true
482///     dhcp: true
483///     enabled: true
484/// ```
485pub struct InterfaceIpv6 {
486    /// Whether IPv6 stack is enable. When set to false, the IPv6 stack is
487    /// disabled with IPv6 link-local address purged also.
488    pub enabled: bool,
489    pub(crate) enabled_defined: bool,
490    /// Whether DHCPv6 enabled.
491    pub dhcp: Option<bool>,
492    /// DHCPv6 Unique Identifier
493    /// Serialize and deserialize to/from `dhcp-duid`.
494    pub dhcp_duid: Option<Dhcpv6Duid>,
495    /// Whether autoconf via IPv6 router announcement enabled.
496    pub autoconf: Option<bool>,
497    /// IPv6 address generation mode.
498    /// Serialize and deserialize to/from `addr-gen-mode`.
499    pub addr_gen_mode: Option<Ipv6AddrGenMode>,
500    /// IPv6 addresses. Will be ignored when applying with
501    /// DHCPv6 or autoconf is enabled.
502    /// When applying with `None`, current IP address will be preserved.
503    /// When applying with `Some(Vec::new())`, all IP address will be removed.
504    /// The IP addresses will apply to kernel with the same order specified.
505    pub addresses: Option<Vec<InterfaceIpAddr>>,
506    /// Whether to apply DNS resolver information retrieved from DHCPv6 or
507    /// autoconf.
508    /// Serialize and deserialize to/from `auto-dns`.
509    /// If you want to append static DNS name server before DHCP/Autoconf
510    /// provided servers, please set `auto-dns: true` explicitly a along with
511    /// static DNS server.
512    pub auto_dns: Option<bool>,
513    /// Whether to set default gateway retrieved from autoconf.
514    /// Serialize and deserialize to/from `auto-gateway`.
515    pub auto_gateway: Option<bool>,
516    /// Whether to set routes(including default gateway) retrieved from
517    /// autoconf.
518    /// Serialize and deserialize to/from `auto-routes`.
519    pub auto_routes: Option<bool>,
520    /// The route table ID used to hold routes(including default gateway)
521    /// retrieved from autoconf.
522    /// If not defined, the main(254) will be used.
523    /// Serialize and deserialize to/from `auto-table-id`.
524    pub auto_table_id: Option<u32>,
525    /// By default(true), nmstate verification process allows extra IP address
526    /// found as long as desired IP address matched.
527    /// When set to false, the verification process of nmstate do exact equal
528    /// check on IP address.
529    /// Ignored when serializing.
530    /// Deserialize from `allow-extra-address`.
531    pub allow_extra_address: Option<bool>,
532    /// Metric for routes retrieved from DHCP server.
533    /// Only available for autoconf enabled interface.
534    /// Deserialize from `auto-route-metric`.
535    pub auto_route_metric: Option<u32>,
536    /// IETF draft(expired) Tokenised IPv6 Identifiers. Should be only
537    /// containing the tailing 64 bites for IPv6 address.
538    pub token: Option<String>,
539    /// Whether to include hostname in DHCP request in
540    /// `Fully Qualified Domain Name (FQDN)` option(39) defined in RFC 4704.
541    /// If not defined, set to True when DHCPv6 enabled.
542    /// Deserialize from `dhcp-send-hostname`
543    pub dhcp_send_hostname: Option<bool>,
544    /// Custom string to override hostname used for DHCP request in
545    /// `Fully Qualified Domain Name (FQDN)` option(29) defined in RFC 4704.
546    /// If not defined, current non-dynamic hostname will be used.
547    /// Deserialize from `dhcp-custom-hostname`
548    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    /// New [InterfaceIpv6] with IP disabled.
556    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    // * Set auto_dns, auto_gateway and auto_routes to true if DHCP enabled and
571    //   those options is None
572    // * Disable DHCP and remove address if enabled: false
573    // * Set DHCP options to None if DHCP is false
574    // * Remove `mptcp_flags` as they are for query only
575    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    // Special action for generating merged state from desired and current.
712    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        // Normally, we expect backend to preserve configuration which not
723        // mentioned in desire, but when DHCP switch from ON to OFF, the design
724        // of nmstate is expecting dynamic IP address goes static. This should
725        // be done by top level code.
726        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(&current.addresses);
733        }
734
735        // The rules is `pub(crate)`, it will not merged by `merge_json_value()`
736        self.rules.clone_from(&current.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        // Normally, we expect backend to preserve configuration which not
752        // mentioned in desire, but when DHCP switch from ON to OFF, the design
753        // of nmstate is expecting dynamic IP address goes static. This should
754        // be done by top level code.
755        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(&current.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    /// IP address.
856    pub ip: IpAddr,
857    #[serde(deserialize_with = "crate::deserializer::u8_or_string")]
858    /// Prefix length.
859    /// Serialize and deserialize to/from `prefix-length`.
860    pub prefix_length: u8,
861    #[serde(skip_serializing_if = "is_none_or_empty_mptcp_flags", default)]
862    /// MPTCP flag on this IP address.
863    /// Ignored when applying as nmstate does not support support IP address
864    /// specific MPTCP flags. You should apply MPTCP flags at interface level
865    /// via [BaseInterface.mptcp].
866    pub mptcp_flags: Option<Vec<MptcpAddressFlag>>,
867    /// Remaining time for IP address been valid. The output format is
868    /// "32sec" or "forever".
869    /// This property is query only, it will be ignored when applying.
870    /// Serialize to `valid-life-time`.
871    /// Deserialize from `valid-life-time` or `valid-left` or `valid-lft`.
872    #[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    /// Remaining time for IP address been preferred. The output format is
879    /// "32sec" or "forever".
880    /// This property is query only, it will be ignored when applying.
881    /// Serialize to `preferred-life-time`.
882    /// Deserialize from `preferred-life-time` or `preferred-left` or
883    /// `preferred-lft`.
884    #[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
932// Copy from Rust official std::net::Ipv6Addr::is_unicast_link_local() which
933// is experimental.
934pub(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]
980/// DHCPv4 client ID
981pub enum Dhcpv4ClientId {
982    /// Use link layer address as DHCPv4 client ID.
983    /// Serialize and deserialize to/from `ll`.
984    #[serde(rename = "ll", alias = "LL")]
985    LinkLayerAddress,
986    /// RFC 4361 type 255, 32 bits IAID followed by DUID.
987    /// Serialize and deserialize to/from `iaid+duid`.
988    #[serde(rename = "iaid+duid", alias = "IAID+DUID")]
989    IaidPlusDuid,
990    /// hex string or backend specific client id type
991    #[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")]
1012/// DHCPv6 Unique Identifier
1013pub enum Dhcpv6Duid {
1014    /// DUID Based on Link-Layer Address Plus Time
1015    /// Serialize and deserialize to/from `llt`.
1016    #[serde(rename = "llt", alias = "LLT")]
1017    LinkLayerAddressPlusTime,
1018    /// DUID Assigned by Vendor Based on Enterprise Number
1019    /// Serialize and deserialize to/from `en`.
1020    #[serde(rename = "en", alias = "EN")]
1021    EnterpriseNumber,
1022    /// DUID Assigned by Vendor Based on Enterprise Number
1023    /// Serialize and deserialize to/from `ll`.
1024    #[serde(rename = "ll", alias = "LL")]
1025    LinkLayerAddress,
1026    /// DUID Based on Universally Unique Identifier
1027    /// Serialize and deserialize to/from `uuid`.
1028    #[serde(alias = "UUID")]
1029    Uuid,
1030    /// Backend specific
1031    #[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)]
1052/// IPv6 address generation mode
1053pub enum Ipv6AddrGenMode {
1054    /// EUI-64 format defined by RFC 4862
1055    /// Serialize and deserialize to/from `eui64`.
1056    #[serde(rename = "eui64", alias = "EUI64")]
1057    Eui64,
1058    /// Semantically Opaque Interface Identifiers defined by RFC 7217
1059    /// Serialize and deserialize to/from `stable-privacy`.
1060    #[serde(rename = "stable-privacy", alias = "STABLE-PRIVACY")]
1061    StablePrivacy,
1062    /// Backend specific
1063    #[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]
1084/// Which IP stack should network backend wait before considering the interface
1085/// activation finished.
1086pub enum WaitIp {
1087    /// The activation is considered done once IPv4 stack or IPv6 stack is
1088    /// configure
1089    /// Serialize and deserialize to/from `any`.
1090    Any,
1091    /// The activation is considered done once IPv4 stack is configured.
1092    /// Serialize and deserialize to/from `ipv4`.
1093    Ipv4,
1094    /// The activation is considered done once IPv6 stack is configured.
1095    /// Serialize and deserialize to/from `ipv6`.
1096    Ipv6,
1097    /// The activation is considered done once both IPv4 and IPv6 stack are
1098    /// configured.
1099    /// Serialize and deserialize to/from `ipv4+ipv6`.
1100    #[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    // * Merge `enabled: true` from current if not mentioned in desired.
1284    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
1323// User might define IPv6 token in the format of `::0.0.250.193`, which should
1324// be sanitize to `::fac1`.
1325fn sanitize_ipv6_token_to_string(
1326    token: &mut String,
1327) -> Result<(), NmstateError> {
1328    // Empty token means reverting to default "::"
1329    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                // The Ipv6Addr::to_string() will convert
1344                //  ::fac1 to ::0.0.250.193
1345                // Which is no ideal in this case
1346                // To workaround that, we set leading 64 bits to '2001:db8::',
1347                // and then trip it out from string.
1348                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}