nmstate/ifaces/
ethtool.rs

1// SPDX-License-Identifier: Apache-2.0
2
3use std::collections::hash_map::Iter;
4use std::collections::{BTreeMap, HashMap};
5use std::marker::PhantomData;
6
7use serde::{
8    de, de::MapAccess, de::Visitor, Deserialize, Deserializer, Serialize,
9    Serializer,
10};
11
12use crate::MergedInterface;
13
14const ETHTOOL_FEATURE_CLI_ALIAS: [(&str, &str); 17] = [
15    ("rx", "rx-checksum"),
16    ("rx-checksumming", "rx-checksum"),
17    ("ufo", "tx-udp-fragmentation"),
18    ("gso", "tx-generic-segmentation"),
19    ("generic-segmentation-offload", "tx-generic-segmentation"),
20    ("gro", "rx-gro"),
21    ("generic-receive-offload", "rx-gro"),
22    ("lro", "rx-lro"),
23    ("large-receive-offload", "rx-lro"),
24    ("rxvlan", "rx-vlan-hw-parse"),
25    ("rx-vlan-offload", "rx-vlan-hw-parse"),
26    ("txvlan", "tx-vlan-hw-insert"),
27    ("tx-vlan-offload", "tx-vlan-hw-insert"),
28    ("ntuple", "rx-ntuple-filter"),
29    ("ntuple-filters", "rx-ntuple-filter"),
30    ("rxhash", "rx-hashing"),
31    ("receive-hashing", "rx-hashing"),
32];
33
34#[derive(Deserialize, Debug, Eq, PartialEq, Clone, Default)]
35#[serde(from = "HashMap<String, bool>")]
36#[non_exhaustive]
37pub struct EthtoolFeatureConfig {
38    data: HashMap<String, bool>,
39}
40
41impl EthtoolFeatureConfig {
42    pub fn remove(&mut self, key: &str) -> Option<bool> {
43        self.data.remove(key)
44    }
45
46    pub fn insert(&mut self, key: String, value: bool) -> Option<bool> {
47        self.data.insert(key, value)
48    }
49
50    pub fn get(&self, key: &str) -> Option<&bool> {
51        self.data.get(key)
52    }
53}
54
55impl<'a> IntoIterator for &'a EthtoolFeatureConfig {
56    type Item = (&'a String, &'a bool);
57    type IntoIter = Iter<'a, String, bool>;
58
59    fn into_iter(self) -> Self::IntoIter {
60        self.data.iter()
61    }
62}
63
64impl From<HashMap<String, bool>> for EthtoolFeatureConfig {
65    fn from(data: HashMap<String, bool>) -> Self {
66        Self { data }
67    }
68}
69
70impl From<EthtoolFeatureConfig> for HashMap<String, bool> {
71    fn from(c: EthtoolFeatureConfig) -> Self {
72        c.data
73    }
74}
75
76impl Serialize for EthtoolFeatureConfig {
77    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
78    where
79        S: Serializer,
80    {
81        let ordered: BTreeMap<_, _> = self.data.iter().collect();
82        ordered.serialize(serializer)
83    }
84}
85
86#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone, Default)]
87#[non_exhaustive]
88#[serde(deny_unknown_fields)]
89/// The ethtool configurations.
90/// The yaml output of [crate::NetworkState] containing ethtool information of
91/// an ethernet interface would be:
92/// ```yml
93/// interfaces:
94/// - name: ens3
95///   type: ethernet
96///   state: up
97///   ethtool:
98///     feature:
99///       tx-tcp-ecn-segmentation: true
100///       tx-tcp-mangleid-segmentation: false
101///       tx-tcp6-segmentation: true
102///       tx-tcp-segmentation: true
103///       rx-gro-list: false
104///       rx-udp-gro-forwarding: false
105///       rx-gro-hw: true
106///       tx-checksum-ip-generic: true
107///       tx-generic-segmentation: true
108///       rx-gro: true
109///       tx-nocache-copy: false
110///     coalesce:
111///       rx-frames: 1
112///       tx-frames: 1
113///     ring:
114///       rx: 256
115///       rx-max: 256
116///       tx: 256
117///       tx-max: 256
118/// ```
119pub struct EthtoolConfig {
120    #[serde(skip_serializing_if = "Option::is_none")]
121    /// The pause parameters of the specified Ethernet device.
122    pub pause: Option<EthtoolPauseConfig>,
123    #[serde(
124        skip_serializing_if = "Option::is_none",
125        default,
126        deserialize_with = "parse_ethtool_feature"
127    )]
128    /// The protocol offload and other features of specified network device.
129    /// Only changeable features are included when querying.
130    pub feature: Option<EthtoolFeatureConfig>,
131    #[serde(skip_serializing_if = "Option::is_none")]
132    /// The coalescing settings of the specified network device.
133    pub coalesce: Option<EthtoolCoalesceConfig>,
134    #[serde(skip_serializing_if = "Option::is_none")]
135    /// The rx/tx ring parameters of the specified network device.
136    pub ring: Option<EthtoolRingConfig>,
137    /// Forward Error Correction
138    #[serde(skip_serializing_if = "Option::is_none")]
139    pub fec: Option<EthtoolFecConfig>,
140}
141
142impl EthtoolConfig {
143    pub fn new() -> Self {
144        Self::default()
145    }
146
147    // There are some alias on ethtool features.
148    pub(crate) fn apply_feature_alias(&mut self) {
149        if let Some(features) = self.feature.as_mut() {
150            for (cli_alias, kernel_name) in ETHTOOL_FEATURE_CLI_ALIAS {
151                if let Some(v) = features.remove(cli_alias) {
152                    features.insert(kernel_name.to_string(), v);
153                }
154            }
155        }
156    }
157
158    pub(crate) fn sanitize(&mut self, is_desired: bool) {
159        if let Some(fec_conf) = self.fec.as_mut() {
160            fec_conf.sanitize(is_desired)
161        }
162    }
163}
164
165#[derive(
166    Serialize, Deserialize, Debug, Eq, PartialEq, Clone, Default, Copy,
167)]
168#[non_exhaustive]
169#[serde(deny_unknown_fields)]
170pub struct EthtoolPauseConfig {
171    #[serde(
172        skip_serializing_if = "Option::is_none",
173        default,
174        deserialize_with = "crate::deserializer::option_bool_or_string"
175    )]
176    pub rx: Option<bool>,
177    #[serde(
178        skip_serializing_if = "Option::is_none",
179        default,
180        deserialize_with = "crate::deserializer::option_bool_or_string"
181    )]
182    pub tx: Option<bool>,
183    #[serde(
184        skip_serializing_if = "Option::is_none",
185        default,
186        deserialize_with = "crate::deserializer::option_bool_or_string"
187    )]
188    pub autoneg: Option<bool>,
189}
190
191impl EthtoolPauseConfig {
192    pub fn new() -> Self {
193        Self::default()
194    }
195}
196
197#[derive(
198    Serialize, Deserialize, Debug, Eq, PartialEq, Clone, Default, Copy,
199)]
200#[serde(rename_all = "kebab-case", deny_unknown_fields)]
201#[non_exhaustive]
202pub struct EthtoolCoalesceConfig {
203    #[serde(
204        skip_serializing_if = "Option::is_none",
205        default,
206        deserialize_with = "crate::deserializer::option_bool_or_string"
207    )]
208    /// Deserialize and serialize from/to `adaptive-rx`.
209    pub adaptive_rx: Option<bool>,
210    #[serde(
211        skip_serializing_if = "Option::is_none",
212        default,
213        deserialize_with = "crate::deserializer::option_bool_or_string"
214    )]
215    /// Deserialize and serialize from/to `adaptive-tx`.
216    pub adaptive_tx: Option<bool>,
217    #[serde(
218        skip_serializing_if = "Option::is_none",
219        default,
220        deserialize_with = "crate::deserializer::option_u32_or_string"
221    )]
222    /// Deserialize and serialize from/to `pkt-rate-high`.
223    pub pkt_rate_high: Option<u32>,
224    #[serde(
225        skip_serializing_if = "Option::is_none",
226        default,
227        deserialize_with = "crate::deserializer::option_u32_or_string"
228    )]
229    /// Deserialize and serialize from/to `pkt-rate-low`.
230    pub pkt_rate_low: Option<u32>,
231    #[serde(
232        skip_serializing_if = "Option::is_none",
233        default,
234        deserialize_with = "crate::deserializer::option_u32_or_string"
235    )]
236    /// Deserialize and serialize from/to `rx-frames`.
237    pub rx_frames: Option<u32>,
238    #[serde(
239        skip_serializing_if = "Option::is_none",
240        default,
241        deserialize_with = "crate::deserializer::option_u32_or_string"
242    )]
243    /// Deserialize and serialize from/to `rx-frames-high`.
244    pub rx_frames_high: Option<u32>,
245    #[serde(
246        skip_serializing_if = "Option::is_none",
247        default,
248        deserialize_with = "crate::deserializer::option_u32_or_string"
249    )]
250    /// Deserialize and serialize from/to `rx-frames-irq`.
251    pub rx_frames_irq: Option<u32>,
252    #[serde(
253        skip_serializing_if = "Option::is_none",
254        default,
255        deserialize_with = "crate::deserializer::option_u32_or_string"
256    )]
257    /// Deserialize and serialize from/to `rx-frames-low`.
258    pub rx_frames_low: Option<u32>,
259    #[serde(
260        skip_serializing_if = "Option::is_none",
261        default,
262        deserialize_with = "crate::deserializer::option_u32_or_string"
263    )]
264    /// Deserialize and serialize from/to `rx-usecs`.
265    pub rx_usecs: Option<u32>,
266    #[serde(
267        skip_serializing_if = "Option::is_none",
268        default,
269        deserialize_with = "crate::deserializer::option_u32_or_string"
270    )]
271    /// Deserialize and serialize from/to `rx-usecs-high`.
272    pub rx_usecs_high: Option<u32>,
273    #[serde(
274        skip_serializing_if = "Option::is_none",
275        default,
276        deserialize_with = "crate::deserializer::option_u32_or_string"
277    )]
278    /// Deserialize and serialize from/to `rx-usecs-irq`.
279    pub rx_usecs_irq: Option<u32>,
280    #[serde(
281        skip_serializing_if = "Option::is_none",
282        default,
283        deserialize_with = "crate::deserializer::option_u32_or_string"
284    )]
285    /// Deserialize and serialize from/to `rx-usecs-low`.
286    pub rx_usecs_low: Option<u32>,
287    #[serde(
288        skip_serializing_if = "Option::is_none",
289        default,
290        deserialize_with = "crate::deserializer::option_u32_or_string"
291    )]
292    /// Deserialize and serialize from/to `sample-interval`.
293    pub sample_interval: Option<u32>,
294    #[serde(
295        skip_serializing_if = "Option::is_none",
296        default,
297        deserialize_with = "crate::deserializer::option_u32_or_string"
298    )]
299    /// Deserialize and serialize from/to `stats-block-usecs`.
300    pub stats_block_usecs: Option<u32>,
301    #[serde(
302        skip_serializing_if = "Option::is_none",
303        default,
304        deserialize_with = "crate::deserializer::option_u32_or_string"
305    )]
306    /// Deserialize and serialize from/to `tx-frames`.
307    pub tx_frames: Option<u32>,
308    #[serde(
309        skip_serializing_if = "Option::is_none",
310        default,
311        deserialize_with = "crate::deserializer::option_u32_or_string"
312    )]
313    /// Deserialize and serialize from/to `tx-frames-high`.
314    pub tx_frames_high: Option<u32>,
315    #[serde(
316        skip_serializing_if = "Option::is_none",
317        default,
318        deserialize_with = "crate::deserializer::option_u32_or_string"
319    )]
320    /// Deserialize and serialize from/to `tx-frames-irq`.
321    pub tx_frames_irq: Option<u32>,
322    #[serde(
323        skip_serializing_if = "Option::is_none",
324        default,
325        deserialize_with = "crate::deserializer::option_u32_or_string"
326    )]
327    /// Deserialize and serialize from/to `tx-frames-low`.
328    pub tx_frames_low: Option<u32>,
329    #[serde(
330        skip_serializing_if = "Option::is_none",
331        default,
332        deserialize_with = "crate::deserializer::option_u32_or_string"
333    )]
334    /// Deserialize and serialize from/to `tx-usecs`.
335    pub tx_usecs: Option<u32>,
336    #[serde(
337        skip_serializing_if = "Option::is_none",
338        default,
339        deserialize_with = "crate::deserializer::option_u32_or_string"
340    )]
341    /// Deserialize and serialize from/to `tx-usecs-high`.
342    pub tx_usecs_high: Option<u32>,
343    #[serde(
344        skip_serializing_if = "Option::is_none",
345        default,
346        deserialize_with = "crate::deserializer::option_u32_or_string"
347    )]
348    /// Deserialize and serialize from/to `tx-usecs-irq`.
349    pub tx_usecs_irq: Option<u32>,
350    #[serde(
351        skip_serializing_if = "Option::is_none",
352        default,
353        deserialize_with = "crate::deserializer::option_u32_or_string"
354    )]
355    /// Deserialize and serialize from/to `tx-usecs-low`.
356    pub tx_usecs_low: Option<u32>,
357}
358
359impl EthtoolCoalesceConfig {
360    pub fn new() -> Self {
361        Self::default()
362    }
363}
364
365#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone, Default)]
366#[serde(rename_all = "kebab-case", deny_unknown_fields)]
367#[non_exhaustive]
368pub struct EthtoolRingConfig {
369    #[serde(
370        skip_serializing_if = "Option::is_none",
371        default,
372        deserialize_with = "crate::deserializer::option_u32_or_string"
373    )]
374    pub rx: Option<u32>,
375    #[serde(
376        skip_serializing_if = "Option::is_none",
377        default,
378        deserialize_with = "crate::deserializer::option_u32_or_string"
379    )]
380    /// Deserialize and serialize from/to `rx-max`.
381    pub rx_max: Option<u32>,
382    #[serde(
383        skip_serializing_if = "Option::is_none",
384        default,
385        deserialize_with = "crate::deserializer::option_u32_or_string"
386    )]
387    /// Deserialize and serialize from/to `rx-jumbo`.
388    pub rx_jumbo: Option<u32>,
389    #[serde(
390        skip_serializing_if = "Option::is_none",
391        default,
392        deserialize_with = "crate::deserializer::option_u32_or_string"
393    )]
394    /// Deserialize and serialize from/to `rx-jumbo-max`.
395    pub rx_jumbo_max: Option<u32>,
396    #[serde(
397        skip_serializing_if = "Option::is_none",
398        default,
399        deserialize_with = "crate::deserializer::option_u32_or_string"
400    )]
401    /// Deserialize and serialize from/to `rx-mini`.
402    pub rx_mini: Option<u32>,
403    #[serde(
404        skip_serializing_if = "Option::is_none",
405        default,
406        deserialize_with = "crate::deserializer::option_u32_or_string"
407    )]
408    /// Deserialize and serialize from/to `rx-mini-max`.
409    pub rx_mini_max: Option<u32>,
410    #[serde(
411        skip_serializing_if = "Option::is_none",
412        default,
413        deserialize_with = "crate::deserializer::option_u32_or_string"
414    )]
415    pub tx: Option<u32>,
416    #[serde(
417        skip_serializing_if = "Option::is_none",
418        default,
419        deserialize_with = "crate::deserializer::option_u32_or_string"
420    )]
421    /// Deserialize and serialize from/to `tx-max`.
422    pub tx_max: Option<u32>,
423}
424
425impl EthtoolRingConfig {
426    pub fn new() -> Self {
427        Self::default()
428    }
429}
430
431fn parse_ethtool_feature<'de, D>(
432    deserializer: D,
433) -> Result<Option<EthtoolFeatureConfig>, D::Error>
434where
435    D: Deserializer<'de>,
436{
437    struct FeatureVisitor(PhantomData<fn() -> Option<EthtoolFeatureConfig>>);
438
439    impl<'de> Visitor<'de> for FeatureVisitor {
440        type Value = Option<EthtoolFeatureConfig>;
441
442        fn expecting(
443            &self,
444            formatter: &mut std::fmt::Formatter,
445        ) -> std::fmt::Result {
446            formatter.write_str("Need to hash map of String:bool")
447        }
448
449        fn visit_map<M>(
450            self,
451            mut access: M,
452        ) -> Result<Option<EthtoolFeatureConfig>, M::Error>
453        where
454            M: MapAccess<'de>,
455        {
456            let mut ret =
457                HashMap::with_capacity(access.size_hint().unwrap_or(0));
458
459            while let Some((key, value)) =
460                access.next_entry::<String, serde_json::Value>()?
461            {
462                match value {
463                    serde_json::Value::Bool(b) => {
464                        ret.insert(key, b);
465                    }
466                    serde_json::Value::String(b)
467                        if b.to_lowercase().as_str() == "false"
468                            || b.as_str() == "0" =>
469                    {
470                        ret.insert(key, false);
471                    }
472                    serde_json::Value::String(b)
473                        if b.to_lowercase().as_str() == "true"
474                            || b.as_str() == "1" =>
475                    {
476                        ret.insert(key, true);
477                    }
478                    _ => {
479                        return Err(de::Error::custom(
480                            "Invalid feature value, should be boolean",
481                        ));
482                    }
483                }
484            }
485
486            Ok(Some(ret.into()))
487        }
488    }
489
490    deserializer.deserialize_any(FeatureVisitor(PhantomData))
491}
492
493impl MergedInterface {
494    pub(crate) fn post_inter_ifaces_process_ethtool(&mut self) {
495        if let Some(ethtool_conf) = self
496            .for_apply
497            .as_mut()
498            .map(|i| i.base_iface_mut())
499            .and_then(|b| b.ethtool.as_mut())
500        {
501            ethtool_conf.apply_feature_alias();
502        }
503        if let Some(ethtool_conf) = self
504            .for_verify
505            .as_mut()
506            .map(|i| i.base_iface_mut())
507            .and_then(|b| b.ethtool.as_mut())
508        {
509            ethtool_conf.apply_feature_alias();
510        }
511    }
512}
513
514#[derive(
515    Serialize, Deserialize, Debug, Eq, PartialEq, Clone, Default, Copy,
516)]
517#[non_exhaustive]
518#[serde(deny_unknown_fields)]
519pub struct EthtoolFecConfig {
520    /// Request the driver to choose FEC mode based on SFP module parameters.
521    #[serde(
522        skip_serializing_if = "Option::is_none",
523        default,
524        deserialize_with = "crate::deserializer::option_bool_or_string"
525    )]
526    pub auto: Option<bool>,
527    /// For querying, this property is for active FEC mode.
528    /// For applying, this property is for desired FEC mode.
529    /// When applying with `auto: true`, this property will be ignored.
530    #[serde(skip_serializing_if = "Option::is_none")]
531    pub mode: Option<EthtoolFecMode>,
532}
533
534impl EthtoolFecConfig {
535    pub(crate) fn sanitize(&mut self, is_desired: bool) {
536        if is_desired && self.auto == Some(true) {
537            if let Some(mode) = self.mode.take() {
538                log::info!(
539                    "Ignoring ethtool fec mode setting {mode} because auto \
540                     enabled"
541                );
542            }
543        }
544    }
545}
546
547#[derive(
548    Serialize, Deserialize, Debug, Eq, PartialEq, Clone, Default, Copy,
549)]
550#[non_exhaustive]
551#[serde(rename_all = "kebab-case", deny_unknown_fields)]
552pub enum EthtoolFecMode {
553    #[default]
554    Off,
555    Rs,
556    Baser,
557    Llrs,
558}
559
560impl std::fmt::Display for EthtoolFecMode {
561    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
562        write!(
563            f,
564            "{}",
565            match self {
566                Self::Off => "off",
567                Self::Rs => "rs",
568                Self::Baser => "baser",
569                Self::Llrs => "llrs",
570            }
571        )
572    }
573}