Issue 291 capture time (#293)

* Make packet capture time user configurable.

* Add `packet_capture_time` to /etc/lqos.conf as a number (seconds)
* Rework the capture logic to obtain the capture time and wait for
  the specified period.
* Rename some functions that specified ten seconds in the name.

Relates to ISSUE #291

* Remove a dangling dashboard link

* Change libpcap download filename to <capture-circuit_id.pcap>

Relates to ISSUE #291

* Pass the circuit_id to the ip_dump page.
* Include the circuit_id in the downloaded packet capture
  filename.

* Add starting timestamp to capture filename

Adds the starting timestamp (in ns) to the capture filename when
you download a libcap dump. Ending timestamp isn't included; the
starting stamp is almost certainly unique.

Relates to ISSUE #291 and fixes the parts that I intend to
touch.
This commit is contained in:
Herbert "TheBracket 2023-03-23 13:49:36 -05:00 committed by GitHub
parent a32949c76d
commit 25801b6445
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 52 additions and 26 deletions

View File

@ -4,6 +4,7 @@
# Where is LibreQoS installed?
lqos_directory = '/opt/libreqos/src'
queue_check_period_ms = 1000
packet_capture_time = 10 # Number of seconds to capture packets in an analysis session
[usage_stats]
send_anonymous = true

1
src/rust/Cargo.lock generated
View File

@ -1436,6 +1436,7 @@ dependencies = [
"dashmap",
"log",
"lqos_bus",
"lqos_config",
"lqos_sys",
"lqos_utils",
"once_cell",

View File

@ -91,7 +91,12 @@ pub enum BusResponse {
FlowData(Vec<(FlowTransport, Option<FlowTransport>)>),
/// The index of the new packet collection session
PacketCollectionSession(usize),
PacketCollectionSession{
/// The identifier of the capture session
session_id: usize,
/// Number of seconds for which data will be captured
countdown: usize
},
/// Packet header dump
PacketDump(Option<Vec<PacketHeader>>),

View File

@ -30,6 +30,11 @@ pub struct EtcLqos {
/// If present, defined anonymous usage stat sending
pub usage_stats: Option<UsageStats>,
/// Defines for how many seconds a libpcap compatible capture should
/// run. Short times are good, there's a real performance penalty to
/// capturing high-throughput streams. Defaults to 10 seconds.
pub packet_capture_time: Option<usize>,
}
/// Represents a set of `sysctl` and `ethtool` tweaks that may be

View File

@ -8,6 +8,7 @@ license = "GPL-2.0-only"
lqos_utils = { path = "../lqos_utils" }
lqos_bus = { path = "../lqos_bus" }
lqos_sys = { path = "../lqos_sys" }
lqos_config = { path = "../lqos_config" }
log = "0"
zerocopy = {version = "0.6.1", features = [ "simd" ] }
once_cell = "1.17.1"

View File

@ -9,7 +9,7 @@ pub use config::{HeimdalConfig, HeimdallMode};
mod flows;
pub use flows::{expire_heimdall_flows, get_flow_stats};
mod timeline;
pub use timeline::{ten_second_packet_dump, ten_second_pcap, hyperfocus_on_target};
pub use timeline::{n_second_packet_dump, n_second_pcap, hyperfocus_on_target};
mod pcap;
mod watchlist;
use lqos_utils::fdtimer::periodic;

View File

@ -7,6 +7,7 @@ use crate::{
};
use dashmap::{DashMap, DashSet};
use lqos_bus::{tos_parser, PacketHeader};
use lqos_config::EtcLqos;
use lqos_utils::{unix_time::time_since_boot, XdpIpAddress};
use once_cell::sync::Lazy;
use std::{
@ -99,7 +100,7 @@ static FOCUS_SESSIONS: Lazy<DashMap<usize, FocusSession>> =
///
/// * Either `None` or...
/// * The id number of the collection session for analysis.
pub fn hyperfocus_on_target(ip: XdpIpAddress) -> Option<usize> {
pub fn hyperfocus_on_target(ip: XdpIpAddress) -> Option<(usize, usize)> {
if HYPERFOCUSED.compare_exchange(
false,
true,
@ -107,10 +108,17 @@ pub fn hyperfocus_on_target(ip: XdpIpAddress) -> Option<usize> {
std::sync::atomic::Ordering::Relaxed,
) == Ok(false)
{
// If explicitly set, obtain the capture time. Otherwise, default to
// a reasonable 10 seconds.
let capture_time = if let Ok(cfg) = EtcLqos::load() {
cfg.packet_capture_time.unwrap_or(10)
} else {
10
};
let new_id =
FOCUS_SESSION_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
std::thread::spawn(move || {
for _ in 0..10 {
for _ in 0..capture_time {
let _ = set_heimdall_mode(HeimdallMode::Analysis);
heimdall_watch_ip(ip);
std::thread::sleep(Duration::from_secs(1));
@ -133,7 +141,7 @@ pub fn hyperfocus_on_target(ip: XdpIpAddress) -> Option<usize> {
HYPERFOCUSED.store(false, std::sync::atomic::Ordering::Relaxed);
});
Some(new_id)
Some((new_id, capture_time))
} else {
log::warn!(
"Heimdall was busy and won't start another collection session."
@ -142,7 +150,7 @@ pub fn hyperfocus_on_target(ip: XdpIpAddress) -> Option<usize> {
}
}
pub fn ten_second_packet_dump(session_id: usize) -> Option<Vec<PacketHeader>> {
pub fn n_second_packet_dump(session_id: usize) -> Option<Vec<PacketHeader>> {
if let Some(session) = FOCUS_SESSIONS.get(&session_id) {
Some(session.data.iter().map(|e| e.as_header()).collect())
} else {
@ -150,7 +158,7 @@ pub fn ten_second_packet_dump(session_id: usize) -> Option<Vec<PacketHeader>> {
}
}
pub fn ten_second_pcap(session_id: usize) -> Option<String> {
pub fn n_second_pcap(session_id: usize) -> Option<String> {
if let Some(mut session) = FOCUS_SESSIONS.get_mut(&session_id) {
let filename = format!("/tmp/cap_sess_{session_id}");
session.dump_filename = Some(filename.clone());

View File

@ -118,14 +118,14 @@ pub async fn flow_stats(ip_list: String, _auth: AuthGuard) -> NoCache<Json<Vec<(
#[serde(crate = "rocket::serde")]
pub enum RequestAnalysisResult {
Fail,
Ok(usize)
Ok{ session_id: usize, countdown: usize }
}
#[get("/api/request_analysis/<ip>")]
pub async fn request_analysis(ip: String) -> NoCache<Json<RequestAnalysisResult>> {
for r in bus_request(vec![BusRequest::GatherPacketData(ip)]).await.unwrap() {
if let BusResponse::PacketCollectionSession(id) = r {
return NoCache::new(Json(RequestAnalysisResult::Ok(id)));
if let BusResponse::PacketCollectionSession{session_id, countdown} = r {
return NoCache::new(Json(RequestAnalysisResult::Ok{session_id, countdown}));
}
}
@ -143,8 +143,11 @@ pub async fn packet_dump(id: usize, _auth: AuthGuard) -> NoCache<Json<Vec<Packet
NoCache::new(Json(result))
}
#[get("/api/pcap/<id>/capture.pcap")]
pub async fn pcap(id: usize) -> Result<NoCache<NamedFile>, Status> {
#[allow(unused_variables)]
#[get("/api/pcap/<id>/<filename>")]
pub async fn pcap(id: usize, filename: String) -> Result<NoCache<NamedFile>, Status> {
// The unusued _filename parameter is there to allow the changing of the
// filename on the client side. See Github issue 291.
for r in bus_request(vec![BusRequest::GetPcapDump(id)]).await.unwrap() {
if let BusResponse::PcapDump(Some(filename)) = r {
return Ok(NoCache::new(NamedFile::open(filename).await.unwrap()));

View File

@ -195,7 +195,7 @@
<div class="card-body">
<h5 class="card-title"><i class="fa fa-bar-chart"></i> Flows (Last 30 Seconds)</h5>
<p class="alert alert-warning" role="alert">
<i class="fa fa-warning"></i> Gathering packet data can cause high CPU load during the 10 second capture window.
<i class="fa fa-warning"></i> Gathering packet data can cause high CPU load during the capture window.
</p>
<div id="packetButtons"></div>
<div id="flowList"></div>
@ -595,9 +595,9 @@
alert("Heimdall is busy serving other customers. Your desire is important to him, please try again later.")
return;
}
analysisId = data.Ok;
analysisId = data.Ok.session_id;
analysisBtn = "#dumpBtn_" + id;
analysisTimer = 10;
analysisTimer = data.Ok.countdown;
analyzeTick();
});
}
@ -608,7 +608,7 @@
if (analysisTimer > -1) {
setTimeout(analyzeTick, 1000);
} else {
window.location.href = "/ip_dump?id=" + analysisId;
window.location.href = "/ip_dump?id=" + analysisId + "&circuit_id=" + encodeURI(id);
}
}

View File

@ -22,9 +22,6 @@
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link" href="/"><i class="fa fa-home"></i> Dashboard</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/tree?parent=0"><i class="fa fa-tree"></i> Tree</a>
</li>
@ -130,7 +127,7 @@ if (hdr->cwr) flags |= 128;
}
function paginator(active) {
let paginator = "<a href='/api/pcap/" + target + "/capture.pcap' class='btn btn-warning'>Download PCAP Dump</a> ";
let paginator = "<a href='/api/pcap/" + target + "/capture-" + circuit_id + "-" + starting_timestamp + ".pcap' class='btn btn-warning'>Download PCAP Dump</a> ";
paginator += "<a href='#' class='btn btn-info' onClick='zoomIn();'>Zoom In</a> ";
paginator += "<a href='#' class='btn btn-info' onClick='zoomOut();'>Zoom Out</a> ( Or drag an area of the graph) <br />";
@ -200,12 +197,16 @@ if (hdr->cwr) flags |= 128;
Plotly.newPlot(graph, data, { margin: { l:0,r:0,b:0,t:0,pad:4 }, yaxis: { automargin: true, title: 'Bytes' }, xaxis: {automargin: true, title: "Nanoseconds"} }, { responsive: true });
}
let circuit_id = null;
let starting_timestamp = null;
function start() {
colorReloadButton();
updateHostCounts();
const params = new Proxy(new URLSearchParams(window.location.search), {
get: (searchParams, prop) => searchParams.get(prop),
});
circuit_id = params.circuit_id;
capacity = [ params.dn, params.up ]; // Bits per second
capacity = [ capacity[0] / 8, capacity[1] / 8 ]; // Bytes per second
@ -226,6 +227,7 @@ if (hdr->cwr) flags |= 128;
}
packets = data;
pages = Math.ceil((packets.length / PAGE_SIZE));
starting_timestamp = min_ts;
paginator(0);
viewPage(0);
});

View File

@ -18,7 +18,7 @@ use anyhow::Result;
use log::{info, warn};
use lqos_bus::{BusRequest, BusResponse, UnixSocketServer};
use lqos_config::LibreQoSConfig;
use lqos_heimdall::{ten_second_packet_dump, perf_interface::heimdall_handle_events, start_heimdall};
use lqos_heimdall::{n_second_packet_dump, perf_interface::heimdall_handle_events, start_heimdall};
use lqos_queue_tracker::{
add_watched_queue, get_raw_circuit_data, spawn_queue_monitor,
spawn_queue_structure_monitor,
@ -197,16 +197,16 @@ fn handle_bus_requests(
}
BusRequest::GetFlowStats(ip) => get_flow_stats(ip),
BusRequest::GetPacketHeaderDump(id) => {
BusResponse::PacketDump(ten_second_packet_dump(*id))
BusResponse::PacketDump(n_second_packet_dump(*id))
}
BusRequest::GetPcapDump(id) => {
BusResponse::PcapDump(lqos_heimdall::ten_second_pcap(*id))
BusResponse::PcapDump(lqos_heimdall::n_second_pcap(*id))
}
BusRequest::GatherPacketData(ip) => {
let ip = ip.parse::<IpAddr>();
if let Ok(ip) = ip {
if let Some(id) = lqos_heimdall::hyperfocus_on_target(ip.into()) {
BusResponse::PacketCollectionSession(id)
if let Some((session_id, countdown)) = lqos_heimdall::hyperfocus_on_target(ip.into()) {
BusResponse::PacketCollectionSession{session_id, countdown}
} else {
BusResponse::Fail("Busy".to_string())
}