Skip to main content

retina_core/
config.rs

1//! Configuration options.
2//!
3//! While applications that use Retina are free to define their own command line arguments, Retina
4//! requires a separate configuration file that defines runtime options for CPU and memory usage,
5//! network interface(s), logging, protocol-specific items, and more. The path to the configuration
6//! file itself will typically be a command line argument passed to the application.
7//!
8//!  Retina can run in either "online" mode (reading packets from a live network interface) or
9//! "offline" mode (reading packets from a capture file). See
10//! [configs](https://github.com/stanford-esrg/retina/tree/main/configs) for examples.
11
12use crate::lcore::{CoreId, SocketId};
13
14use std::fs;
15use std::path::Path;
16
17use serde::{Deserialize, Serialize};
18
19/// Loads a configuration file from `path`.
20pub fn load_config<P: AsRef<Path>>(path: P) -> RuntimeConfig {
21    let config_str = fs::read_to_string(path).expect("ERROR: File read failed");
22    let config: RuntimeConfig = toml::from_str(&config_str).expect("Invalid config file");
23
24    // error check config
25    if config.online.is_some() == config.offline.is_some() {
26        log::error!(
27            "Configure either live ports or offline analysis: {:#?}",
28            config
29        );
30        panic!();
31    }
32    config
33}
34
35/// Loads a default configuration file.
36///
37/// For demonstration purposes only, not configured for performance. The default configuration
38/// assumes Retina is being run from the crate root in offline mode:
39/// ```toml
40/// main_core = 0
41///
42/// [mempool]
43///     capacity = 8192
44///
45/// [offline]
46///     pcap = "./traces/small_flows.pcap"
47///     mtu = 9702
48///
49/// [conntrack]
50///     max_connections = 100_000
51/// ```
52pub fn default_config() -> RuntimeConfig {
53    RuntimeConfig::default()
54}
55
56/* --------------------------------------------------------------------------------- */
57
58/// Runtime configuration options.
59#[derive(Deserialize, Serialize, Debug, Clone)]
60pub struct RuntimeConfig {
61    /// Main core identifier. Initializes and manages packet processing cores and logging, but does
62    /// not process packets itself.
63    pub main_core: u32,
64
65    /// Sets the number of memory channels to use.
66    ///
67    /// This controls the spread layout used by the memory allocator and is mainly for performance
68    /// optimization. Can be configured up to be the number of channels per CPU socket if the
69    /// platform supports multiple memory channels. Defaults to `1`.
70    #[serde(default = "default_nb_memory_channels")]
71    pub nb_memory_channels: usize,
72
73    /// Suppress DPDK runtime logging and telemetry output. Defaults to `true`.
74    #[serde(default = "default_suppress_dpdk_output")]
75    pub suppress_dpdk_output: bool,
76
77    /// Per-mempool settings.
78    pub mempool: MempoolConfig,
79
80    /// Online mode settings. Either `online` or `offline` must be specified.
81    #[serde(default = "default_online")]
82    pub online: Option<OnlineConfig>,
83
84    /// Offline mode settings. Either `online` or `offline` must be specified.
85    #[serde(default = "default_offline")]
86    pub offline: Option<OfflineConfig>,
87
88    /// Connection tracking settings.
89    pub conntrack: ConnTrackConfig,
90
91    #[doc(hidden)]
92    /// Runtime filter for testing purposes.
93    #[serde(default = "default_filter")]
94    pub filter: Option<String>,
95}
96
97impl RuntimeConfig {
98    /// Returns a list of core IDs assigned to the runtime.
99    fn get_all_core_ids(&self) -> Vec<CoreId> {
100        let mut cores = vec![CoreId(self.main_core)];
101        if let Some(online) = &self.online {
102            for port in online.ports.iter() {
103                cores.extend(port.cores.iter().map(|c| CoreId(*c)));
104                if let Some(sink) = &port.sink {
105                    cores.push(CoreId(sink.core));
106                }
107            }
108        }
109        cores.sort();
110        cores.dedup();
111        cores
112    }
113
114    /// Returns a list of socket IDs in use.
115    pub(crate) fn get_all_socket_ids(&self) -> Vec<SocketId> {
116        let mut sockets = vec![];
117        for core_id in self.get_all_core_ids() {
118            sockets.push(core_id.socket_id());
119        }
120        sockets.sort();
121        sockets.dedup();
122        sockets
123    }
124
125    /// Returns DPDK EAL parameters.
126    #[allow(clippy::vec_init_then_push)]
127    pub(crate) fn get_eal_params(&self) -> Vec<String> {
128        let mut eal_params = vec![];
129
130        eal_params.push("--main-lcore".to_owned());
131        eal_params.push(self.main_core.to_string());
132
133        eal_params.push("-l".to_owned());
134        let core_list: Vec<String> = self
135            .get_all_core_ids()
136            .iter()
137            .map(|c| c.raw().to_string())
138            .collect();
139        eal_params.push(core_list.join(","));
140
141        if let Some(online) = &self.online {
142            for supl_arg in online.dpdk_supl_args.iter() {
143                eal_params.push(supl_arg.to_string())
144            }
145            for port in online.ports.iter() {
146                eal_params.push("-a".to_owned());
147                eal_params.push(port.device.to_string());
148            }
149        }
150
151        eal_params.push("-n".to_owned());
152        eal_params.push(self.nb_memory_channels.to_string());
153
154        if self.suppress_dpdk_output {
155            eal_params.push("--log-level=6".to_owned());
156            eal_params.push("--no-telemetry".to_owned());
157        }
158
159        eal_params
160    }
161}
162
163fn default_nb_memory_channels() -> usize {
164    1
165}
166
167fn default_suppress_dpdk_output() -> bool {
168    true
169}
170
171fn default_online() -> Option<OnlineConfig> {
172    None
173}
174
175fn default_offline() -> Option<OfflineConfig> {
176    None
177}
178
179fn default_filter() -> Option<String> {
180    None
181}
182
183impl Default for RuntimeConfig {
184    fn default() -> Self {
185        RuntimeConfig {
186            main_core: 0,
187            nb_memory_channels: 1,
188            suppress_dpdk_output: true,
189            mempool: MempoolConfig {
190                capacity: 8192,
191                cache_size: 512,
192            },
193            online: None,
194            offline: Some(OfflineConfig {
195                mtu: 9702,
196                // assumes Retina is being run from crate root
197                pcap: "./traces/small_flows.pcap".to_string(),
198            }),
199            conntrack: ConnTrackConfig {
200                max_connections: 100_000,
201                max_out_of_order: 100,
202                timeout_resolution: 100,
203                udp_inactivity_timeout: 60_000,
204                tcp_inactivity_timeout: 300_000,
205                tcp_establish_timeout: 5000,
206                init_synack: false,
207                init_fin: false,
208                init_rst: false,
209                init_data: false,
210            },
211            filter: None,
212        }
213    }
214}
215
216/* --------------------------------------------------------------------------------- */
217
218/// Memory pool options.
219///
220/// Retina manages packet buffer memory using DPDK's pool-based memory allocator. This takes
221/// advantage of built-in DPDK huge page support, NUMA affinity, and access to DMA addresses. See
222/// [Memory in DPDK](https://www.dpdk.org/blog/2019/08/21/memory-in-dpdk-part-1-general-concepts/)
223/// for more details.
224///
225/// ## Example
226/// ```toml
227/// [mempool]
228///     capacity = 1_048_576
229///     cache_size = 512
230/// ```
231#[derive(Deserialize, Serialize, Debug, Clone)]
232pub struct MempoolConfig {
233    /// Number of mbufs allocated per mempool. The maximum value that can be set will depend on
234    /// the available memory (number of hugepages allocated) and the MTU. Defaults to `65536`.
235    #[serde(default = "default_capacity")]
236    pub capacity: usize,
237
238    /// The size of the per-core object cache. It is recommended that `cache_size` evenly divides
239    /// `capacity`. Defaults to `512`.
240    #[serde(default = "default_cache_size")]
241    pub cache_size: usize,
242}
243
244fn default_capacity() -> usize {
245    65536
246}
247
248fn default_cache_size() -> usize {
249    512
250}
251
252/* --------------------------------------------------------------------------------- */
253
254/// Live traffic analysis options.
255///
256/// Online mode performs traffic analysis on a live network interface. Either
257/// [OnlineConfig](OnlineConfig) or [OfflineConfig](OfflineConfig) must be specified, but not both.
258///
259/// ## Example
260/// ```toml
261/// [online]
262///     duration = 30
263///     nb_rxd = 32768
264///     promiscuous = true
265///     mtu = 1500
266///     hardware_assist = true
267///     dpdk_supl_args = []
268///
269/// [online.monitor.display]
270///     throughput = true
271///     mempool_usage = true
272///
273///     [online.monitor.log]
274///         directory = "./log"
275///         interval = 1000
276///
277///     [[online.ports]]
278///         device = "0000:3b:00.0"
279///         cores = [1,2,3,4]
280///
281///     [[online.ports]]
282///         device = "0000:3b:00.1"
283///         cores = [5,6,7,8]
284/// ```
285#[derive(Deserialize, Serialize, Debug, Clone)]
286pub struct OnlineConfig {
287    /// If set, the applicaton will stop after `duration` seconds. Defaults to `None`.
288    #[serde(default = "default_duration")]
289    pub duration: Option<u64>,
290
291    /// Whether promiscuous mode is enabled for all ports. Defaults to `true`.
292    #[serde(default = "default_promiscuous")]
293    pub promiscuous: bool,
294
295    /// The number of RX descriptors per receive queue. Defaults to `4096`.
296    ///
297    /// Receive queues are polled for packets using a run-to-completion model. Deeper queues will be
298    /// more tolerant of processing delays at the cost of higher memory usage and hugepage
299    /// reservation.
300    #[serde(default = "default_portqueue_nb_rxd")]
301    pub nb_rxd: usize,
302
303    /// Maximum transmission unit (in bytes) allowed for ingress packets. Defaults to `1500`.
304    ///
305    /// To capture jumbo frames, set this value higher (e.g., `9702`).
306    #[serde(default = "default_mtu")]
307    pub mtu: usize,
308
309    /// If set, will attempt to offload parts of the filter to the NIC, depending on its hardware
310    /// filtering support. Defaults to `true`.
311    #[serde(default = "default_hardware_assist")]
312    pub hardware_assist: bool,
313
314    /// If set, will pass supplementary arguments to DPDK EAL (see DPDK
315    /// configuration). For instance `--no-huge`.
316    /// Defaults to empty string.
317    #[serde(default = "default_dpdk_supl_args")]
318    pub dpdk_supl_args: Vec<String>,
319
320    /// Live performance monitoring. Defaults to `None`.
321    #[serde(default = "default_monitor")]
322    pub monitor: Option<MonitorConfig>,
323
324    /// List of network interfaces to read from.
325    pub ports: Vec<PortMap>,
326}
327
328fn default_duration() -> Option<u64> {
329    None
330}
331
332fn default_hardware_assist() -> bool {
333    true
334}
335
336fn default_dpdk_supl_args() -> Vec<String> {
337    Vec::new()
338}
339
340fn default_promiscuous() -> bool {
341    true
342}
343
344fn default_portqueue_nb_rxd() -> usize {
345    4096
346}
347
348fn default_mtu() -> usize {
349    1500
350}
351
352fn default_monitor() -> Option<MonitorConfig> {
353    None
354}
355
356/* --------------------------------------------------------------------------------- */
357
358/// Sink core options.
359///
360/// A "sink" core is a utility core whose sole purpose is to drop received traffic. This is useful
361/// for connection sampling, as entire 4-tuples can be discarded by redirecting them to the sink
362/// core.
363///
364/// ## Remarks
365/// Adding a sink core prevents ethtool counters from classifying intentionally discarded packets as
366/// packet loss. However, it can be quite wasteful of system resources, as it requires configuring
367/// one additional core per interface and thrashes the cache.
368///
369/// ## Example
370/// ```toml
371/// [online.ports.sink]
372///     core = 9
373///     nb_buckets = 384   # drops 25% of 4-tuples
374/// ```
375#[derive(Deserialize, Serialize, Debug, Clone)]
376pub struct SinkConfig {
377    /// Sink core identifier.
378    pub core: u32,
379
380    /// Number of RSS redirection table buckets to use for receive queues. Defaults to `512`, which
381    /// indicates no sampling.
382    ///
383    /// ## Remarks
384    /// Connection sampling is implemented by only polling from a fraction of the available RSS
385    /// redirection buckets. `nb_buckets` must range from the number of cores polling the port (call
386    /// this `n`) to `512`, which is the maximum number of buckets in the RSS redirection table. It
387    /// is recommended that `nb_buckets` be a multiple of `n` for better load balancing. For
388    /// example, setting `nb_buckets = 256` would drop 50% of connections.
389    #[serde(default = "default_nb_buckets")]
390    pub nb_buckets: usize,
391}
392
393fn default_nb_buckets() -> usize {
394    512
395}
396
397/* --------------------------------------------------------------------------------- */
398
399/// Network interface options.
400///
401/// ## Example
402/// ```toml
403/// [[online.ports]]
404///     device = "0000:3b:00.0"
405///     cores = [1,2,3,4,5,6,7,8]
406/// ```
407#[derive(Deserialize, Serialize, Debug, Clone)]
408pub struct PortMap {
409    /// PCI address of interface.
410    pub device: String,
411
412    /// List of packet processing cores used to poll the interface.
413    ///
414    /// ## Remarks
415    /// For performance, it is recommended that the processing cores reside on the same NUMA node as
416    /// the PCI device.
417    pub cores: Vec<u32>,
418
419    /// Sink core configuration. Defaults to `None`.
420    #[serde(default = "default_sink")]
421    pub sink: Option<SinkConfig>,
422}
423
424fn default_sink() -> Option<SinkConfig> {
425    None
426}
427
428/* --------------------------------------------------------------------------------- */
429
430/// Statistics logging and live monitoring operations.
431///
432/// ## Example
433/// ```toml
434/// [online.monitor.display]
435///     throughput = true
436///     mempool_usage = true
437///
438/// [online.monitor.log]
439///     directory = "./log"
440///     interval = 1000
441/// ```
442#[derive(Deserialize, Serialize, Debug, Clone)]
443pub struct MonitorConfig {
444    /// Live display configuration. Defaults to `None` (no output).
445    #[serde(default = "default_display")]
446    pub display: Option<DisplayConfig>,
447
448    /// Logging configuration. Defaults to `None` (no logs).
449    #[serde(default = "default_log")]
450    pub log: Option<LogConfig>,
451}
452
453fn default_display() -> Option<DisplayConfig> {
454    None
455}
456
457fn default_log() -> Option<LogConfig> {
458    None
459}
460
461/* --------------------------------------------------------------------------------- */
462
463/// Live statistics display options.
464///
465/// If enabled, live statistics will be displayed to stdout once per second.
466///
467/// ## Example
468/// ```toml
469/// [online.monitor.display]
470///     throughput = true
471///     mempool_usage = true
472/// ```
473#[derive(Deserialize, Serialize, Debug, Clone)]
474pub struct DisplayConfig {
475    /// Display live throughputs. Defaults to `true`.
476    #[serde(default = "default_display_throughput")]
477    pub throughput: bool,
478
479    /// Display live mempool usage. Defaults to `true`.
480    #[serde(default = "default_display_mempool_usage")]
481    pub mempool_usage: bool,
482
483    /// List of live port statistics to display.
484    ///
485    /// ## Remarks
486    /// Available options vary depending on the NIC driver and its supported counters. A port
487    /// statistic will be displayed if it contains (as a substring) any item in the `port_stats`
488    /// list. To display all available port statistics, set this value to a list containing the
489    /// empty string (`port_stats = [""]`). Defaults to displaying no statistics (`port_stats =
490    /// []`).
491    #[serde(default = "default_display_port_stats")]
492    pub port_stats: Vec<String>,
493}
494
495fn default_display_throughput() -> bool {
496    true
497}
498
499fn default_display_mempool_usage() -> bool {
500    true
501}
502
503fn default_display_port_stats() -> Vec<String> {
504    vec![]
505}
506
507/* --------------------------------------------------------------------------------- */
508
509/// Logging options.
510///
511/// ## Example
512/// ```toml
513/// [online.monitor.log]
514///     directory = "./log"
515///     interval = 1000
516///     port_stats = ["rx"]   # only log stats with "rx" in its name
517/// ```
518#[derive(Deserialize, Serialize, Debug, Clone)]
519pub struct LogConfig {
520    /// Log directory path. If logging is enabled, Retina will write logs to a timestamped folder
521    /// inside `directory`. Defaults to `"./log"`.
522    #[serde(default = "default_log_directory")]
523    pub directory: String,
524
525    /// How often to log port statistics (in milliseconds). Defaults to `1000`.
526    #[serde(default = "default_log_interval")]
527    pub interval: u64,
528
529    /// List of port statistics to log.
530    ///
531    /// Available options vary depending on the NIC driver and its supported counters. A port
532    /// statistic will be logged if it contains (as a substring) any item in the `port_stats` list.
533    /// To log all available port statistics, set this value to a list containing the empty string
534    /// (`port_stats = [""]`). Defaults to logging receive statistics (`port_stats = ["rx"]`).
535    #[serde(default = "default_log_port_stats")]
536    pub port_stats: Vec<String>,
537}
538
539fn default_log_directory() -> String {
540    "./log/".to_string()
541}
542
543fn default_log_interval() -> u64 {
544    1000
545}
546
547fn default_log_port_stats() -> Vec<String> {
548    vec!["rx".to_string()]
549}
550
551/* --------------------------------------------------------------------------------- */
552
553/// Offline traffic analysis options.
554///
555/// Offline mode runs using a single core and performs offline analysis of already captured pcap
556/// files. Either [OnlineConfig](OnlineConfig) or [OfflineConfig](OfflineConfig) must be specified,
557/// but not both. This mode is primarily intended for functional testing.
558///
559/// ## Example
560/// ```toml
561/// [offline]
562///     pcap = "sample_pcaps/smallFlows.pcap"
563///     mtu = 9702
564/// ```
565#[derive(Deserialize, Serialize, Debug, Clone)]
566pub struct OfflineConfig {
567    /// Path to packet capture (pcap) file.
568    pub pcap: String,
569
570    /// Maximum frame size, equivalent to MTU on a live interface. Defaults to `1500`.
571    ///
572    /// To include jumbo frames, set this value higher (e.g., `9702`).
573    #[serde(default = "default_mtu")]
574    pub mtu: usize,
575}
576
577/* --------------------------------------------------------------------------------- */
578
579/// Connection tracking options.
580///
581/// These options can be used to tune for resource usage vs. accuracy depending on expected network
582/// characteristics.
583///
584/// ## Example
585/// ```toml
586/// [conntrack]
587///     max_connections = 10_000_000
588///     max_out_of_order = 100
589///     timeout_resolution = 100
590///     udp_inactivity_timeout = 60_000
591///     tcp_inactivity_timeout = 300_000
592///     tcp_establish_timeout = 5000
593/// ```
594#[derive(Deserialize, Serialize, Debug, Clone)]
595pub struct ConnTrackConfig {
596    /// Maximum number of connections that can be tracked simultaneously per-core. Defaults to
597    /// `10_000_000`.
598    #[serde(default = "default_max_connections")]
599    pub max_connections: usize,
600
601    /// Maximum number of out-of-order packets allowed per TCP connection before it is force
602    /// expired. Defaults to `100`.
603    #[serde(default = "default_max_out_of_order")]
604    pub max_out_of_order: usize,
605
606    /// Frequency to check for inactive streams (in milliseconds). Defaults to `1000` (1 second).
607    #[serde(default = "default_timeout_resolution")]
608    pub timeout_resolution: usize,
609
610    /// A UDP connection can be inactive for up to this amount of time (in milliseconds) before it
611    /// is force expired. Defaults to `60_000` (1 minute).
612    #[serde(default = "default_udp_inactivity_timeout")]
613    pub udp_inactivity_timeout: usize,
614
615    /// A TCP connection can be inactive for up to this amount of time (in milliseconds) before it
616    /// is force expired. Defaults to `300_000` (5 minutes).
617    #[serde(default = "default_tcp_inactivity_timeout")]
618    pub tcp_inactivity_timeout: usize,
619
620    /// Inactivity time between the first and second packet of a TCP connection before it is force
621    /// expired (in milliseconds).
622    ///
623    /// This approximates connections that remain inactive in either the `SYN-SENT` or
624    /// `SYN-RECEIVED` state without progressing. It is used to prevent memory exhaustion due to SYN
625    /// scans and SYN floods. Defaults to `5000` (5 seconds).
626    #[serde(default = "default_tcp_establish_timeout")]
627    pub tcp_establish_timeout: usize,
628
629    #[doc(hidden)]
630    /// Whether to track TCP connections where the first observed packet is a SYN/ACK. Defaults to
631    /// `false`.
632    #[serde(default = "default_init_synack")]
633    pub init_synack: bool,
634
635    #[doc(hidden)]
636    /// Whether to track TCP connections where the first observed packet is a FIN. Defaults to
637    /// `false`.
638    #[serde(default = "default_init_fin")]
639    pub init_fin: bool,
640
641    #[doc(hidden)]
642    /// Whether to track TCP connections where the first observed packet is a RST. Defaults to
643    /// `false`.
644    #[serde(default = "default_init_rst")]
645    pub init_rst: bool,
646
647    #[doc(hidden)]
648    /// Whether to track TCP connections where the first observed packet is a DATA. Defaults to
649    /// `false`.
650    #[serde(default = "default_init_data")]
651    pub init_data: bool,
652}
653
654fn default_max_connections() -> usize {
655    10_000_000
656}
657
658fn default_max_out_of_order() -> usize {
659    100
660}
661
662fn default_timeout_resolution() -> usize {
663    1000
664}
665
666fn default_udp_inactivity_timeout() -> usize {
667    60_000
668}
669
670fn default_tcp_inactivity_timeout() -> usize {
671    300_000
672}
673
674fn default_tcp_establish_timeout() -> usize {
675    5000
676}
677
678fn default_init_synack() -> bool {
679    false
680}
681
682fn default_init_fin() -> bool {
683    false
684}
685
686fn default_init_rst() -> bool {
687    false
688}
689
690fn default_init_data() -> bool {
691    false
692}