Add libpcap format support and ability to download a snapshot of the last 10 seconds in pcap format for Wireshark (or other tool) analysis.

This commit is contained in:
Herbert Wolverson 2023-03-15 18:34:55 +00:00
parent 8288bb3f9b
commit 17100415dd
13 changed files with 205 additions and 5 deletions

104
src/rust/Cargo.lock generated
View File

@ -733,6 +733,18 @@ dependencies = [
"subtle",
]
[[package]]
name = "educe"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb0188e3c3ba8df5753894d54461f0e39bc91741dc5b22e1c46999ec2c71f4e4"
dependencies = [
"enum-ordinalize",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "either"
version = "1.8.1"
@ -748,6 +760,20 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "enum-ordinalize"
version = "3.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a62bb1df8b45ecb7ffa78dca1c17a438fb193eb083db0b1b494d2a61bcb5096a"
dependencies = [
"num-bigint",
"num-traits",
"proc-macro2",
"quote",
"rustc_version",
"syn",
]
[[package]]
name = "env_logger"
version = "0.10.0"
@ -1453,6 +1479,7 @@ dependencies = [
"nix",
"once_cell",
"rocket",
"rocket-download-response",
"rocket_async_compression",
"sysinfo",
]
@ -1630,6 +1657,16 @@ version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
[[package]]
name = "mime_guess"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef"
dependencies = [
"mime",
"unicase",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
@ -1736,6 +1773,27 @@ dependencies = [
"winapi",
]
[[package]]
name = "num-bigint"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.15"
@ -2218,6 +2276,19 @@ dependencies = [
"yansi",
]
[[package]]
name = "rocket-download-response"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98c0a60b1b9d018d3cd73b4b69a1f49656a4adbb1552a997e18de30d7413c6ce"
dependencies = [
"educe",
"mime",
"mime_guess",
"rocket",
"url-escape",
]
[[package]]
name = "rocket_async_compression"
version = "0.2.0"
@ -2281,6 +2352,15 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver",
]
[[package]]
name = "rustix"
version = "0.36.9"
@ -2328,6 +2408,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "semver"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
[[package]]
name = "serde"
version = "1.0.153"
@ -2940,6 +3026,15 @@ dependencies = [
"version_check",
]
[[package]]
name = "unicase"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
dependencies = [
"version_check",
]
[[package]]
name = "unicode-ident"
version = "1.0.8"
@ -2980,6 +3075,15 @@ dependencies = [
"subtle",
]
[[package]]
name = "url-escape"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44e0ce4d1246d075ca5abec4b41d33e87a6054d08e2366b63205665e950db218"
dependencies = [
"percent-encoding",
]
[[package]]
name = "uuid"
version = "1.3.0"

View File

@ -139,6 +139,9 @@ pub enum BusRequest {
/// Give me a dump of the last 10 seconds of packet headers
GetPacketHeaderDump(String),
/// Give me a libpcap format packet dump (shortened) of the last 10 seconds
GetPcapDump,
/// If running on Equinix (the `equinix_test` feature is enabled),
/// display a "run bandwidht test" link.
#[cfg(feature = "equinix_tests")]

View File

@ -92,4 +92,7 @@ pub enum BusResponse {
/// Packet header dump
PacketDump(Vec<PacketHeader>),
/// Pcap format dump
PcapDump(Vec<u8>),
}

View File

@ -9,4 +9,5 @@ pub use config::{HeimdallMode, HeimdalConfig};
mod flows;
pub use flows::{expire_heimdall_flows, get_flow_stats};
mod timeline;
pub use timeline::ten_second_packet_dump;
pub use timeline::{ten_second_packet_dump, ten_second_pcap};
mod pcap;

View File

@ -0,0 +1,50 @@
use std::time::Duration;
use zerocopy::AsBytes;
use crate::perf_interface::HeimdallEvent;
#[derive(AsBytes)]
#[repr(C)]
pub(crate) struct PcapFileHeader {
magic: u32,
version_major: u16,
version_minor: u16,
thiszone: i32,
sigfigs: u32,
snaplen: u32,
link_type: u32,
}
impl PcapFileHeader {
pub(crate) fn new() -> Self {
Self {
magic: 0xa1b2c3d4,
version_major: 2,
version_minor: 4,
thiszone: 0,
sigfigs: 0,
snaplen: 64,
link_type: 1,
}
}
}
#[derive(AsBytes)]
#[repr(C)]
pub(crate) struct PcapPacketHeader {
ts_sec: u32,
ts_usec: u32,
inc_len: u32, // Octets included
orig_len: u32, // Length the packet used to be
}
impl PcapPacketHeader {
pub(crate) fn from_heimdall(event: &HeimdallEvent) -> Self {
let timestamp_nanos = Duration::from_nanos(event.timestamp);
Self {
ts_sec: timestamp_nanos.as_secs() as u32,
ts_usec: timestamp_nanos.subsec_micros(),
inc_len: u32::min(64, event.size),
orig_len: event.size
}
}
}

View File

@ -19,6 +19,7 @@ pub struct HeimdallEvent {
pub tcp_window: u16,
pub tcp_tsval: u32,
pub tcp_tsecr: u32,
pub packet_data: [u8; 64],
}
/*

View File

@ -3,7 +3,8 @@ use dashmap::DashSet;
use lqos_bus::{PacketHeader, tos_parser};
use lqos_utils::{unix_time::time_since_boot, XdpIpAddress};
use once_cell::sync::Lazy;
use crate::perf_interface::HeimdallEvent;
use zerocopy::AsBytes;
use crate::{perf_interface::HeimdallEvent, pcap::{PcapFileHeader, PcapPacketHeader}};
impl HeimdallEvent {
fn as_header(&self) -> PacketHeader {
@ -58,3 +59,17 @@ pub fn ten_second_packet_dump(ip: XdpIpAddress) -> Vec<PacketHeader> {
.map(|e| e.as_header())
.collect()
}
pub fn ten_second_pcap() -> Vec<u8> {
let mut bytes : Vec<u8> = Vec::new();
let file_header = PcapFileHeader::new();
bytes.extend(file_header.as_bytes());
let mut packets: Vec<HeimdallEvent> = TIMELINE.data.iter().map(|e| e.clone()).collect();
packets.sort_by(|a,b| a.timestamp.cmp(&b.timestamp));
packets.iter().for_each(|p| {
let packet_header = PcapPacketHeader::from_heimdall(p);
bytes.extend(packet_header.as_bytes());
bytes.extend(p.packet_data);
});
bytes
}

View File

@ -18,7 +18,8 @@ sysinfo = "0"
default-net = "0"
nix = "0"
once_cell = "1"
rocket-download-response = "0.5"
# Support JemAlloc on supported platforms
[target.'cfg(any(target_arch = "x86", target_arch = "x86_64"))'.dependencies]
jemallocator = "0.5"
jemallocator = "0.5"

View File

@ -70,6 +70,7 @@ fn rocket() -> _ {
queue_info::watch_circuit,
queue_info::flow_stats,
queue_info::packet_dump,
queue_info::pcap,
config_control::get_nic_list,
config_control::get_current_python_config,
config_control::get_current_lqosd_config,

View File

@ -2,9 +2,11 @@ use crate::auth_guard::AuthGuard;
use crate::cache_control::NoCache;
use crate::tracker::SHAPED_DEVICES;
use lqos_bus::{bus_request, BusRequest, BusResponse, FlowTransport, PacketHeader};
use rocket::http::Status;
use rocket::response::content::RawJson;
use rocket::serde::json::Json;
use rocket::serde::Serialize;
use rocket_download_response::DownloadResponse;
use std::net::IpAddr;
#[derive(Serialize, Clone)]
@ -123,6 +125,17 @@ pub async fn packet_dump(ip: String, _auth: AuthGuard) -> NoCache<Json<Vec<Packe
NoCache::new(Json(result))
}
#[get("/api/pcap")]
pub async fn pcap() -> Result<DownloadResponse, Status> {
for r in bus_request(vec![BusRequest::GetPcapDump]).await.unwrap() {
if let BusResponse::PcapDump(bytes) = r {
return Ok(DownloadResponse::from_vec(bytes, Some("capture.pcap"), None));
}
}
Err(Status::NoContent)
}
#[cfg(feature = "equinix_tests")]
#[get("/api/run_btest")]
pub async fn run_btest() -> NoCache<RawJson<String>> {

View File

@ -564,11 +564,12 @@
for (let i=0; i<ips.length; ++i) {
ip_list += ips[i] + ",";
if (circuit_info != null) {
ip_btns += "<a href='/ip_dump?ip=" + ips[i] + "&dn=" + circuit_info.capacity[0] + "&up=" + circuit_info.capacity[1] + "' class='btn btn-info'>Packet Dump: " + ips[i] + "</a>"
ip_btns += "<a href='/ip_dump?ip=" + ips[i] + "&dn=" + circuit_info.capacity[0] + "&up=" + circuit_info.capacity[1] + "' class='btn btn-info'>Packet Dump: " + ips[i] + "</a> "
}
}
ip_btns += "<br />";
if (!madeButtons && ips.length > 0 && circuit_info != null) {
ip_btns += "<a href='/api/pcap' class='btn btn-warning'>Download PCAP Dump</a>";
ip_btns += "<br />";
madeButtons = true;
$("#packetButtons").html(ip_btns);
}

View File

@ -54,6 +54,7 @@ struct heimdall_event {
__u16 tcp_window;
__u32 tsval;
__u32 tsecr;
__u8 dump[64];
};
static __always_inline __u8 get_heimdall_mode()
@ -99,6 +100,9 @@ static __always_inline void update_heimdall(struct dissector_t *dissector, __u32
event.tcp_window = dissector->window;
event.tsval = dissector->tsval;
event.tsecr = dissector->tsecr;
if (size > 64 && ((char *)dissector->start + 64 < dissector->end)) {
__builtin_memcpy(&event.dump, dissector->start, 64);
}
long err = bpf_ringbuf_output(&heimdall_events, &event, sizeof(event), 0);
if (err != 0) {
bpf_debug("Failed to send perf event %d", err);

View File

@ -205,6 +205,9 @@ fn handle_bus_requests(
BusResponse::Fail("Invalid IP".to_string())
}
}
BusRequest::GetPcapDump => {
BusResponse::PcapDump(lqos_heimdall::ten_second_pcap())
}
});
}
}