Skip to main content

nmstate/ifaces/
ethtool.rs

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