Skip to content

Commit 34a01a3

Browse files
redo packet capture dump
1 parent d21bcad commit 34a01a3

File tree

6 files changed

+493
-6
lines changed

6 files changed

+493
-6
lines changed

core/src/ffi/dpdk.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ pub(crate) fn pktmbuf_pool_create<S: Into<String>>(
125125
}
126126

127127
/// Looks up a mempool by the name.
128+
#[cfg(test)]
128129
pub(crate) fn mempool_lookup<S: Into<String>>(name: S) -> Result<MempoolPtr> {
129130
let name: String = name.into();
130131

@@ -407,6 +408,57 @@ pub(crate) fn eth_rx_queue_setup(
407408
}
408409
}
409410

411+
/// Removes an RX or TX packet callback from a given port and queue.
412+
#[allow(dead_code)]
413+
pub(crate) enum RxTxCallbackGuard {
414+
Rx(PortId, PortRxQueueId, *const cffi::rte_eth_rxtx_callback),
415+
Tx(PortId, PortTxQueueId, *const cffi::rte_eth_rxtx_callback),
416+
}
417+
418+
impl Drop for RxTxCallbackGuard {
419+
fn drop(&mut self) {
420+
if let Err(error) = match self {
421+
RxTxCallbackGuard::Rx(port_id, queue_id, ptr) => {
422+
debug!(port = ?port_id, rxq = ?queue_id, "remove rx callback.");
423+
unsafe {
424+
cffi::rte_eth_remove_rx_callback(port_id.0, queue_id.0, *ptr)
425+
.into_result(DpdkError::from_errno)
426+
}
427+
}
428+
RxTxCallbackGuard::Tx(port_id, queue_id, ptr) => {
429+
debug!(port = ?port_id, txq = ?queue_id, "remove tx callback.");
430+
unsafe {
431+
cffi::rte_eth_remove_tx_callback(port_id.0, queue_id.0, *ptr)
432+
.into_result(DpdkError::from_errno)
433+
}
434+
}
435+
} {
436+
error!(?error);
437+
}
438+
}
439+
}
440+
441+
/// Adds a callback to be called on packet RX on a given port and queue.
442+
#[allow(dead_code)]
443+
pub(crate) fn eth_add_rx_callback<T>(
444+
port_id: PortId,
445+
queue_id: PortRxQueueId,
446+
callback: cffi::rte_rx_callback_fn,
447+
user_param: &mut T,
448+
) -> Result<RxTxCallbackGuard> {
449+
let ptr = unsafe {
450+
cffi::rte_eth_add_rx_callback(
451+
port_id.0,
452+
queue_id.0,
453+
callback,
454+
user_param as *mut T as *mut raw::c_void,
455+
)
456+
.into_result(|_| DpdkError::new())?
457+
};
458+
459+
Ok(RxTxCallbackGuard::Rx(port_id, queue_id, ptr))
460+
}
461+
410462
/// Retrieves a burst of input packets from a receive queue of a device.
411463
pub(crate) fn eth_rx_burst(port_id: PortId, queue_id: PortRxQueueId, rx_pkts: &mut Vec<MbufPtr>) {
412464
let nb_pkts = rx_pkts.capacity();
@@ -460,6 +512,27 @@ pub(crate) fn eth_tx_queue_setup(
460512
}
461513
}
462514

515+
/// Adds a callback to be called on packet TX on a given port and queue.
516+
#[allow(dead_code)]
517+
pub(crate) fn eth_add_tx_callback<T>(
518+
port_id: PortId,
519+
queue_id: PortTxQueueId,
520+
callback: cffi::rte_tx_callback_fn,
521+
user_param: &mut T,
522+
) -> Result<RxTxCallbackGuard> {
523+
let ptr = unsafe {
524+
cffi::rte_eth_add_tx_callback(
525+
port_id.0,
526+
queue_id.0,
527+
callback,
528+
user_param as *mut T as *mut raw::c_void,
529+
)
530+
.into_result(|_| DpdkError::new())?
531+
};
532+
533+
Ok(RxTxCallbackGuard::Tx(port_id, queue_id, ptr))
534+
}
535+
463536
/// Sends a burst of output packets on a transmit queue of a device.
464537
pub(crate) fn eth_tx_burst(
465538
port_id: PortId,

core/src/ffi/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
*/
1818

1919
pub(crate) mod dpdk;
20+
#[cfg(feature = "pcap-dump")]
21+
pub(crate) mod pcap;
2022

2123
pub(crate) use capsule_ffi::*;
2224

core/src/ffi/pcap.rs

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/*
2+
* Copyright 2019 Comcast Cable Communications Management, LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* SPDX-License-Identifier: Apache-2.0
17+
*/
18+
19+
use super::{AsStr, EasyPtr, ToCString, ToResult};
20+
use crate::ffi::dpdk::MbufPtr;
21+
use anyhow::Result;
22+
use capsule_ffi as cffi;
23+
use libc;
24+
use std::ops::DerefMut;
25+
use std::os::raw;
26+
use std::ptr;
27+
use thiserror::Error;
28+
29+
// Ethernet (10Mb, 100Mb, 1000Mb, and up); the 10MB in the DLT_ name is historical.
30+
const DLT_EN10MB: raw::c_int = 1;
31+
32+
// https://github.com/the-tcpdump-group/libpcap/blob/master/pcap/pcap.h#L152
33+
#[allow(dead_code)]
34+
const PCAP_ERRBUF_SIZE: usize = 256;
35+
36+
/// A `pcap_t` pointer.
37+
pub(crate) type PcapPtr = EasyPtr<cffi::pcap_t>;
38+
39+
/// Creates a `libpcap` handle needed to call other functions.
40+
pub(crate) fn open_dead() -> Result<PcapPtr> {
41+
let ptr = unsafe {
42+
cffi::pcap_open_dead(DLT_EN10MB, cffi::RTE_MBUF_DEFAULT_BUF_SIZE as raw::c_int)
43+
.into_result(|_| PcapError::new("Cannot create libpcap handle."))?
44+
};
45+
46+
Ok(EasyPtr(ptr))
47+
}
48+
49+
/// A `pcap_dumper_t` pointer.
50+
pub(crate) type DumperPtr = EasyPtr<cffi::pcap_dumper_t>;
51+
52+
/// Opens a file to which to write packets.
53+
pub(crate) fn dump_open<S: Into<String>>(handle: &mut PcapPtr, filename: S) -> Result<DumperPtr> {
54+
let filename: String = filename.into();
55+
let ptr = unsafe {
56+
cffi::pcap_dump_open(handle.deref_mut(), filename.into_cstring().as_ptr())
57+
.into_result(|_| PcapError::get_error(handle))?
58+
};
59+
60+
Ok(EasyPtr(ptr))
61+
}
62+
63+
/// Writes a packet to a capture file.
64+
pub(crate) fn dump(dumper: &mut DumperPtr, mbuf: &MbufPtr) {
65+
let mut pkthdr = cffi::pcap_pkthdr::default();
66+
pkthdr.len = mbuf.data_len as u32;
67+
pkthdr.caplen = pkthdr.len;
68+
69+
unsafe {
70+
// If this errors, we'll still want to write packet(s) to the pcap,
71+
let _ = libc::gettimeofday(
72+
&mut pkthdr.ts as *mut cffi::timeval as *mut libc::timeval,
73+
ptr::null_mut(),
74+
);
75+
76+
cffi::pcap_dump(
77+
dumper.deref_mut() as *mut cffi::pcap_dumper_t as *mut raw::c_uchar,
78+
&pkthdr,
79+
(mbuf.buf_addr as *mut u8).offset(mbuf.data_off as isize),
80+
);
81+
}
82+
}
83+
84+
/// Flushes to a savefile packets dumped.
85+
pub(crate) fn dump_flush(dumper: &mut DumperPtr) -> Result<()> {
86+
unsafe {
87+
cffi::pcap_dump_flush(dumper.deref_mut())
88+
.into_result(|_| PcapError::new("Cannot flush packets to capture file."))
89+
.map(|_| ())
90+
}
91+
}
92+
93+
/// Closes a savefile being written to.
94+
pub(crate) fn dump_close(dumper: &mut DumperPtr) {
95+
unsafe {
96+
cffi::pcap_dump_close(dumper.deref_mut());
97+
}
98+
}
99+
100+
/// Closes a capture device or savefile
101+
pub(crate) fn close(handle: &mut PcapPtr) {
102+
unsafe {
103+
cffi::pcap_close(handle.deref_mut());
104+
}
105+
}
106+
107+
/// Opens a saved capture file for reading.
108+
#[cfg(test)]
109+
pub(crate) fn open_offline<S: Into<String>>(filename: S) -> Result<PcapPtr> {
110+
let filename: String = filename.into();
111+
let mut errbuf: [raw::c_char; PCAP_ERRBUF_SIZE] = [0; PCAP_ERRBUF_SIZE];
112+
113+
let ptr = unsafe {
114+
cffi::pcap_open_offline(filename.into_cstring().as_ptr(), errbuf.as_mut_ptr())
115+
.into_result(|_| PcapError::new(errbuf.as_str()))?
116+
};
117+
118+
Ok(EasyPtr(ptr))
119+
}
120+
121+
/// Reads the next packet from a `pcap_t` handle.
122+
#[cfg(test)]
123+
pub(crate) fn next(handle: &mut PcapPtr) -> Result<&[u8]> {
124+
let mut pkthdr: *mut cffi::pcap_pkthdr = ptr::null_mut();
125+
let mut pktdata: *const raw::c_uchar = ptr::null();
126+
127+
unsafe {
128+
match cffi::pcap_next_ex(handle.deref_mut(), &mut pkthdr, &mut pktdata) {
129+
1 => Ok(std::slice::from_raw_parts(
130+
pktdata,
131+
(*pkthdr).caplen as usize,
132+
)),
133+
_ => Err(PcapError::get_error(handle).into()),
134+
}
135+
}
136+
}
137+
138+
/// An error generated in `libpcap`.
139+
#[derive(Debug, Error)]
140+
#[error("{0}")]
141+
pub(crate) struct PcapError(String);
142+
143+
impl PcapError {
144+
/// Returns the `PcapError` with the given error message.
145+
#[inline]
146+
fn new(msg: &str) -> Self {
147+
PcapError(msg.into())
148+
}
149+
150+
/// Returns the `PcapError` pertaining to the last `libpcap` error.
151+
#[inline]
152+
fn get_error(handle: &mut PcapPtr) -> Self {
153+
let msg = unsafe { cffi::pcap_geterr(handle.deref_mut()) };
154+
PcapError::new((msg as *const raw::c_char).as_str())
155+
}
156+
}

core/src/rt2/config.rs

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ pub struct RuntimeConfig {
8686
/// execution.
8787
pub worker_cores: Vec<usize>,
8888

89+
/// The root data directory the application writes to.
90+
///
91+
/// If unset, the default is `/var/capsule/{app_name}`.
92+
#[serde(default)]
93+
pub data_dir: Option<String>,
94+
8995
/// Per mempool settings. On a system with multiple sockets, aka NUMA
9096
/// nodes, one mempool will be allocated for each socket the apllication
9197
/// uses.
@@ -193,6 +199,18 @@ impl RuntimeConfig {
193199

194200
eal_args
195201
}
202+
203+
/// Returns the data directory.
204+
#[allow(dead_code)]
205+
pub(crate) fn data_dir(&self) -> String {
206+
self.data_dir.clone().unwrap_or_else(|| {
207+
let base_dir = "/var/capsule";
208+
match &self.app_group {
209+
Some(group) => format!("{}/{}/{}", base_dir, group, self.app_name),
210+
None => format!("{}/{}", base_dir, self.app_name),
211+
}
212+
})
213+
}
196214
}
197215

198216
impl fmt::Debug for RuntimeConfig {
@@ -367,7 +385,7 @@ mod tests {
367385
use super::*;
368386

369387
#[test]
370-
fn config_defaults() {
388+
fn config_defaults() -> Result<()> {
371389
const CONFIG: &str = r#"
372390
app_name = "myapp"
373391
main_core = 0
@@ -377,10 +395,11 @@ mod tests {
377395
device = "0000:00:01.0"
378396
"#;
379397

380-
let config: RuntimeConfig = toml::from_str(CONFIG).unwrap();
398+
let config: RuntimeConfig = toml::from_str(CONFIG)?;
381399

382400
assert_eq!(false, config.secondary);
383401
assert_eq!(None, config.app_group);
402+
assert_eq!(None, config.data_dir);
384403
assert_eq!(None, config.dpdk_args);
385404
assert_eq!(default_capacity(), config.mempool.capacity);
386405
assert_eq!(default_cache_size(), config.mempool.cache_size);
@@ -391,10 +410,14 @@ mod tests {
391410
assert_eq!(default_port_txqs(), config.ports[0].txqs);
392411
assert_eq!(default_promiscuous_mode(), config.ports[0].promiscuous);
393412
assert_eq!(default_multicast_mode(), config.ports[0].multicast);
413+
414+
assert_eq!("/var/capsule/myapp", &config.data_dir());
415+
416+
Ok(())
394417
}
395418

396419
#[test]
397-
fn config_to_eal_args() {
420+
fn config_to_eal_args() -> Result<()> {
398421
const CONFIG: &str = r#"
399422
app_name = "myapp"
400423
secondary = false
@@ -421,7 +444,7 @@ mod tests {
421444
txqs = 32
422445
"#;
423446

424-
let config: RuntimeConfig = toml::from_str(CONFIG).unwrap();
447+
let config: RuntimeConfig = toml::from_str(CONFIG)?;
425448

426449
assert_eq!(
427450
&[
@@ -443,6 +466,8 @@ mod tests {
443466
"eal:8"
444467
],
445468
config.to_eal_args().as_slice(),
446-
)
469+
);
470+
471+
Ok(())
447472
}
448473
}

core/src/rt2/mod.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
mod config;
2222
mod lcore;
2323
mod mempool;
24+
#[cfg(feature = "pcap-dump")]
25+
#[cfg_attr(docsrs, doc(cfg(feature = "pcap-dump")))]
26+
mod pcap_dump;
2427
mod port;
2528

2629
pub use self::config::*;
@@ -89,6 +92,8 @@ pub struct Runtime {
8992
mempool: ManuallyDrop<Mempool>,
9093
lcores: ManuallyDrop<LcoreMap>,
9194
ports: ManuallyDrop<PortMap>,
95+
#[cfg(feature = "pcap-dump")]
96+
pcap_dump: ManuallyDrop<self::pcap_dump::PcapDump>,
9297
}
9398

9499
impl Runtime {
@@ -155,13 +160,19 @@ impl Runtime {
155160
port.start()?;
156161
ports.push(port);
157162
}
163+
let ports: PortMap = ports.into();
164+
165+
#[cfg(feature = "pcap-dump")]
166+
let pcap_dump = self::pcap_dump::enable_pcap_dump(&config.data_dir(), &ports, &lcores)?;
158167

159168
info!("runtime ready.");
160169

161170
Ok(Runtime {
162171
mempool: ManuallyDrop::new(mempool),
163172
lcores: ManuallyDrop::new(lcores),
164-
ports: ManuallyDrop::new(ports.into()),
173+
ports: ManuallyDrop::new(ports),
174+
#[cfg(feature = "pcap-dump")]
175+
pcap_dump: ManuallyDrop::new(pcap_dump),
165176
})
166177
}
167178

@@ -203,6 +214,8 @@ impl Drop for RuntimeGuard {
203214
}
204215

205216
unsafe {
217+
#[cfg(feature = "pcap-dump")]
218+
ManuallyDrop::drop(&mut self.runtime.pcap_dump);
206219
ManuallyDrop::drop(&mut self.runtime.ports);
207220
ManuallyDrop::drop(&mut self.runtime.lcores);
208221
ManuallyDrop::drop(&mut self.runtime.mempool);

0 commit comments

Comments
 (0)