nmstate/
net_state.rs

1// SPDX-License-Identifier: Apache-2.0
2
3#[cfg(not(feature = "gen_conf"))]
4use std::collections::HashMap;
5
6use serde::{Deserialize, Serialize};
7
8use crate::{
9    DnsState, ErrorKind, HostNameState, Interface, Interfaces, MergedDnsState,
10    MergedHostNameState, MergedInterfaces, MergedOvnConfiguration,
11    MergedOvsDbGlobalConfig, MergedRouteRules, MergedRoutes, NmstateError,
12    OvnConfiguration, OvsDbGlobalConfig, RouteRules, Routes,
13};
14
15/// The [NetworkState] represents the whole network state including both
16/// kernel status and configurations provides by backends(NetworkManager,
17/// OpenvSwitch databas, and etc).
18///
19/// Example yaml(many lines omitted) serialized NetworkState would be:
20///
21/// ```yaml
22/// hostname:
23///   running: host.example.org
24///   config: host.example.org
25/// dns-resolver:
26///   config:
27///     server:
28///     - 2001:db8:1::
29///     - 192.0.2.1
30///     search: []
31/// route-rules:
32///   config:
33///   - ip-from: 2001:db8:b::/64
34///     priority: 30000
35///     route-table: 200
36///   - ip-from: 192.0.2.2/32
37///     priority: 30000
38///     route-table: 200
39/// routes:
40///   config:
41///   - destination: 2001:db8:a::/64
42///     next-hop-interface: eth1
43///     next-hop-address: 2001:db8:1::2
44///     metric: 108
45///     table-id: 200
46///   - destination: 192.168.2.0/24
47///     next-hop-interface: eth1
48///     next-hop-address: 192.168.1.3
49///     metric: 108
50///     table-id: 200
51/// interfaces:
52/// - name: eth1
53///   type: ethernet
54///   state: up
55///   mac-address: 0E:F9:2B:28:42:D9
56///   mtu: 1500
57///   ipv4:
58///     enabled: true
59///     dhcp: false
60///     address:
61///     - ip: 192.168.1.3
62///       prefix-length: 24
63///   ipv6:
64///     enabled: true
65///     dhcp: false
66///     autoconf: false
67///     address:
68///     - ip: 2001:db8:1::1
69///       prefix-length: 64
70/// ovs-db:
71///   external_ids:
72///     hostname: host.example.org
73///     rundir: /var/run/openvswitch
74///     system-id: 176866c7-6dc8-400f-98ac-c658509ec09f
75///   other_config: {}
76/// ```
77#[derive(Clone, Debug, Deserialize, Serialize, Default, PartialEq, Eq)]
78#[serde(deny_unknown_fields)]
79#[non_exhaustive]
80pub struct NetworkState {
81    #[serde(default, skip_serializing_if = "String::is_empty")]
82    /// Description for the whole desire state. Currently it will not be
83    /// persisted by network backend and will be ignored during applying or
84    /// querying.
85    pub description: String,
86    #[serde(skip_serializing_if = "Option::is_none")]
87    /// Hostname of current host.
88    pub hostname: Option<HostNameState>,
89    /// DNS resolver status, deserialize and serialize from/to `dns-resolver`.
90    #[serde(rename = "dns-resolver", skip_serializing_if = "Option::is_none")]
91    pub dns: Option<DnsState>,
92    /// Route rule, deserialize and serialize from/to `route-rules`.
93    #[serde(
94        rename = "route-rules",
95        default,
96        skip_serializing_if = "RouteRules::is_empty"
97    )]
98    pub rules: RouteRules,
99    /// Route
100    #[serde(default, skip_serializing_if = "Routes::is_empty")]
101    pub routes: Routes,
102    /// Network interfaces
103    #[serde(default)]
104    pub interfaces: Interfaces,
105    /// The global configurations of OpenvSwitach daemon
106    #[serde(rename = "ovs-db", skip_serializing_if = "Option::is_none")]
107    pub ovsdb: Option<OvsDbGlobalConfig>,
108    #[serde(default, skip_serializing_if = "OvnConfiguration::is_none")]
109    /// The OVN configuration in the system
110    pub ovn: OvnConfiguration,
111    #[serde(skip)]
112    pub(crate) kernel_only: bool,
113    #[serde(skip)]
114    pub(crate) no_verify: bool,
115    #[serde(skip)]
116    pub(crate) no_commit: bool,
117    #[serde(skip)]
118    pub(crate) timeout: Option<u32>,
119    #[serde(skip)]
120    pub(crate) include_secrets: bool,
121    #[serde(skip)]
122    pub(crate) include_status_data: bool,
123    #[serde(skip)]
124    pub(crate) running_config_only: bool,
125    #[serde(skip)]
126    pub(crate) memory_only: bool,
127    #[serde(skip)]
128    pub(crate) override_iface: bool,
129}
130
131impl NetworkState {
132    pub fn is_empty(&self) -> bool {
133        self.hostname.is_none()
134            && self.dns.is_none()
135            && self.ovsdb.is_none()
136            && self.rules.is_empty()
137            && self.routes.is_empty()
138            && self.interfaces.is_empty()
139            && self.ovn.is_none()
140    }
141
142    pub(crate) const PASSWORD_HID_BY_NMSTATE: &'static str =
143        "<_password_hid_by_nmstate>";
144
145    /// Whether to perform kernel actions(also known as `kernel only` mode
146    /// through the document this project) only or not.
147    /// When set to false, nmstate will contact NetworkManager plugin for
148    /// querying/applying the network state.
149    /// Default is false.
150    pub fn set_kernel_only(&mut self, value: bool) -> &mut Self {
151        self.kernel_only = value;
152        self
153    }
154
155    pub fn kernel_only(&self) -> bool {
156        self.kernel_only
157    }
158
159    /// By default(true), When nmstate applying the network state, after applied
160    /// the network state, nmstate will verify whether the outcome network
161    /// configuration matches with desired, if not, will rollback to state
162    /// before apply(only when [NetworkState::set_kernel_only()] set to false.
163    /// When set to false, no verification will be performed.
164    pub fn set_verify_change(&mut self, value: bool) -> &mut Self {
165        self.no_verify = !value;
166        self
167    }
168
169    /// Only available when [NetworkState::set_kernel_only()] set to false.
170    /// When set to false, the network configuration will not commit
171    /// persistently, and will rollback after timeout defined by
172    /// [NetworkState::set_timeout()].  Default to true for making the network
173    /// state persistent.
174    pub fn set_commit(&mut self, value: bool) -> &mut Self {
175        self.no_commit = !value;
176        self
177    }
178
179    /// Only available when [NetworkState::set_commit()] set to false.
180    /// The time to wait before rolling back the network state to the state
181    /// before [NetworkState::apply()` invoked.
182    pub fn set_timeout(&mut self, value: u32) -> &mut Self {
183        self.timeout = Some(value);
184        self
185    }
186
187    /// Whether to include secrets(like password) in [NetworkState::retrieve()]
188    /// Default is false.
189    pub fn set_include_secrets(&mut self, value: bool) -> &mut Self {
190        self.include_secrets = value;
191        self
192    }
193
194    /// Deprecated. No use at all.
195    pub fn set_include_status_data(&mut self, value: bool) -> &mut Self {
196        self.include_status_data = value;
197        self
198    }
199
200    /// Query activated/running network configuration excluding:
201    /// * IP address retrieved by DHCP or IPv6 auto configuration.
202    /// * DNS client resolver retrieved by DHCP or IPv6 auto configuration.
203    /// * Routes retrieved by DHCPv4 or IPv6 router advertisement.
204    /// * LLDP neighbor information.
205    pub fn set_running_config_only(&mut self, value: bool) -> &mut Self {
206        self.running_config_only = value;
207        self
208    }
209
210    /// When set to true, the network state be applied and only stored in memory
211    /// which will be purged after system reboot.
212    pub fn set_memory_only(&mut self, value: bool) -> &mut Self {
213        self.memory_only = value;
214        self
215    }
216
217    /// Nmstate by default will merge information from current state.
218    /// When set to true, for network state to apply, nmstate will:
219    ///
220    /// * For `interfaces` section, any defined interface will be configured by
221    ///   desired state information only, undefined properties of this interface
222    ///   will use default value instead of merging from current state.
223    ///
224    /// * For other sections, no changes to their original behavior.
225    ///
226    /// For example, if desired interface has no IPv4 section defined, nmstate
227    /// will treat it as disabled regardless current network status.
228    pub fn set_override_iface(&mut self, value: bool) -> &mut Self {
229        self.override_iface = value;
230        self
231    }
232
233    /// Create empty [NetworkState]
234    pub fn new() -> Self {
235        Default::default()
236    }
237
238    /// Wrapping function of [serde_json::from_str()] with error mapped to
239    /// [NmstateError].
240    pub fn new_from_json(net_state_json: &str) -> Result<Self, NmstateError> {
241        match serde_json::from_str(net_state_json) {
242            Ok(s) => Ok(s),
243            Err(e) => Err(NmstateError::new(
244                ErrorKind::InvalidArgument,
245                format!("Invalid JSON string: {e}"),
246            )),
247        }
248    }
249
250    /// Wrapping function of [serde_yaml::from_str()] with error mapped to
251    /// [NmstateError].
252    pub fn new_from_yaml(net_state_yaml: &str) -> Result<Self, NmstateError> {
253        match serde_yaml::from_str(net_state_yaml) {
254            Ok(s) => Ok(s),
255            Err(e) => Err(NmstateError::new(
256                ErrorKind::InvalidArgument,
257                format!("Invalid YAML string: {e}"),
258            )),
259        }
260    }
261
262    /// Append [Interface] into [NetworkState]
263    pub fn append_interface_data(&mut self, iface: Interface) {
264        self.interfaces.push(iface);
265    }
266
267    #[cfg(not(feature = "query_apply"))]
268    pub fn retrieve(&mut self) -> Result<&mut Self, NmstateError> {
269        Err(NmstateError::new(
270            ErrorKind::DependencyError,
271            "NetworkState::retrieve() need `query_apply` feature enabled"
272                .into(),
273        ))
274    }
275
276    #[cfg(not(feature = "query_apply"))]
277    pub async fn retrieve_async(&mut self) -> Result<&mut Self, NmstateError> {
278        Err(NmstateError::new(
279            ErrorKind::DependencyError,
280            "NetworkState::retrieve_async() need `query_apply` feature enabled"
281                .into(),
282        ))
283    }
284
285    /// Replace secret string with `<_password_hid_by_nmstate>`
286    pub fn hide_secrets(&mut self) {
287        self.interfaces.hide_secrets();
288    }
289
290    #[cfg(not(feature = "query_apply"))]
291    pub fn apply(&mut self) -> Result<(), NmstateError> {
292        Err(NmstateError::new(
293            ErrorKind::DependencyError,
294            "NetworkState::apply() need `query_apply` feature enabled".into(),
295        ))
296    }
297
298    #[cfg(not(feature = "query_apply"))]
299    pub async fn apply_async(&mut self) -> Result<(), NmstateError> {
300        Err(NmstateError::new(
301            ErrorKind::DependencyError,
302            "NetworkState::apply() need `query_apply` feature enabled".into(),
303        ))
304    }
305
306    #[cfg(not(feature = "gen_conf"))]
307    pub fn gen_conf(
308        &self,
309    ) -> Result<HashMap<String, Vec<(String, String)>>, NmstateError> {
310        Err(NmstateError::new(
311            ErrorKind::DependencyError,
312            "NetworkState::gen_conf() need `genconf` feature enabled".into(),
313        ))
314    }
315
316    #[cfg(not(feature = "query_apply"))]
317    pub fn checkpoint_rollback(_checkpoint: &str) -> Result<(), NmstateError> {
318        Err(NmstateError::new(
319            ErrorKind::DependencyError,
320            "NetworkState::checkpoint_rollback() need `query_apply` \
321            feature enabled"
322                .into(),
323        ))
324    }
325
326    #[cfg(not(feature = "query_apply"))]
327    pub fn checkpoint_commit(_checkpoint: &str) -> Result<(), NmstateError> {
328        Err(NmstateError::new(
329            ErrorKind::DependencyError,
330            "NetworkState::checkpoint_commit() need `query_apply` \
331            feature enabled"
332                .into(),
333        ))
334    }
335}
336
337#[derive(Clone, Debug, Default, PartialEq, Eq)]
338pub(crate) struct MergedNetworkState {
339    pub(crate) interfaces: MergedInterfaces,
340    pub(crate) hostname: MergedHostNameState,
341    pub(crate) dns: MergedDnsState,
342    pub(crate) ovn: MergedOvnConfiguration,
343    pub(crate) ovsdb: MergedOvsDbGlobalConfig,
344    pub(crate) routes: MergedRoutes,
345    pub(crate) rules: MergedRouteRules,
346    pub(crate) memory_only: bool,
347    pub(crate) override_iface: bool,
348}
349
350impl MergedNetworkState {
351    pub(crate) fn new(
352        desired: NetworkState,
353        current: NetworkState,
354        gen_conf_mode: bool,
355        memory_only: bool,
356    ) -> Result<Self, NmstateError> {
357        let mut current = current;
358        if desired.override_iface {
359            current.interfaces = current.interfaces.clone_name_type_only();
360        };
361
362        let interfaces = MergedInterfaces::new(
363            desired.interfaces,
364            current.interfaces,
365            gen_conf_mode,
366            memory_only,
367        )?;
368        let ignored_ifaces = interfaces.ignored_ifaces.as_slice();
369
370        let mut routes =
371            MergedRoutes::new(desired.routes, current.routes, &interfaces)?;
372        routes.remove_routes_to_ignored_ifaces(ignored_ifaces);
373
374        let mut rules = MergedRouteRules::new(desired.rules, current.rules)?;
375        rules.remove_rules_to_ignored_ifaces(ignored_ifaces);
376
377        let hostname =
378            MergedHostNameState::new(desired.hostname, current.hostname);
379
380        let ovn = MergedOvnConfiguration::new(desired.ovn, current.ovn)?;
381
382        let ovsdb = MergedOvsDbGlobalConfig::new(
383            desired.ovsdb,
384            current.ovsdb.unwrap_or_default(),
385        )?;
386
387        let ret = Self {
388            interfaces,
389            routes,
390            rules,
391            dns: MergedDnsState::new(
392                desired.dns,
393                current.dns.unwrap_or_default(),
394            )?,
395            ovn,
396            ovsdb,
397            hostname,
398            memory_only,
399            override_iface: desired.override_iface,
400        };
401        ret.validate_ipv6_link_local_address_dns_srv()?;
402
403        Ok(ret)
404    }
405}