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