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? # Where is LibreQoS installed?
lqos_directory = '/opt/libreqos/src' lqos_directory = '/opt/libreqos/src'
queue_check_period_ms = 1000 queue_check_period_ms = 1000
packet_capture_time = 10 # Number of seconds to capture packets in an analysis session
[usage_stats] [usage_stats]
send_anonymous = true send_anonymous = true

1
src/rust/Cargo.lock generated
View File

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

View File

@ -91,7 +91,12 @@ pub enum BusResponse {
FlowData(Vec<(FlowTransport, Option<FlowTransport>)>), FlowData(Vec<(FlowTransport, Option<FlowTransport>)>),
/// The index of the new packet collection session /// 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 /// Packet header dump
PacketDump(Option<Vec<PacketHeader>>), PacketDump(Option<Vec<PacketHeader>>),

View File

@ -30,6 +30,11 @@ pub struct EtcLqos {
/// If present, defined anonymous usage stat sending /// If present, defined anonymous usage stat sending
pub usage_stats: Option<UsageStats>, 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 /// 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_utils = { path = "../lqos_utils" }
lqos_bus = { path = "../lqos_bus" } lqos_bus = { path = "../lqos_bus" }
lqos_sys = { path = "../lqos_sys" } lqos_sys = { path = "../lqos_sys" }
lqos_config = { path = "../lqos_config" }
log = "0" log = "0"
zerocopy = {version = "0.6.1", features = [ "simd" ] } zerocopy = {version = "0.6.1", features = [ "simd" ] }
once_cell = "1.17.1" once_cell = "1.17.1"

View File

@ -9,7 +9,7 @@ pub use config::{HeimdalConfig, HeimdallMode};
mod flows; mod flows;
pub use flows::{expire_heimdall_flows, get_flow_stats}; pub use flows::{expire_heimdall_flows, get_flow_stats};
mod timeline; 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 pcap;
mod watchlist; mod watchlist;
use lqos_utils::fdtimer::periodic; use lqos_utils::fdtimer::periodic;

View File

@ -7,6 +7,7 @@ use crate::{
}; };
use dashmap::{DashMap, DashSet}; use dashmap::{DashMap, DashSet};
use lqos_bus::{tos_parser, PacketHeader}; use lqos_bus::{tos_parser, PacketHeader};
use lqos_config::EtcLqos;
use lqos_utils::{unix_time::time_since_boot, XdpIpAddress}; use lqos_utils::{unix_time::time_since_boot, XdpIpAddress};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use std::{ use std::{
@ -99,7 +100,7 @@ static FOCUS_SESSIONS: Lazy<DashMap<usize, FocusSession>> =
/// ///
/// * Either `None` or... /// * Either `None` or...
/// * The id number of the collection session for analysis. /// * 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( if HYPERFOCUSED.compare_exchange(
false, false,
true, true,
@ -107,10 +108,17 @@ pub fn hyperfocus_on_target(ip: XdpIpAddress) -> Option<usize> {
std::sync::atomic::Ordering::Relaxed, std::sync::atomic::Ordering::Relaxed,
) == Ok(false) ) == 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 = let new_id =
FOCUS_SESSION_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed); FOCUS_SESSION_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
std::thread::spawn(move || { std::thread::spawn(move || {
for _ in 0..10 { for _ in 0..capture_time {
let _ = set_heimdall_mode(HeimdallMode::Analysis); let _ = set_heimdall_mode(HeimdallMode::Analysis);
heimdall_watch_ip(ip); heimdall_watch_ip(ip);
std::thread::sleep(Duration::from_secs(1)); 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); HYPERFOCUSED.store(false, std::sync::atomic::Ordering::Relaxed);
}); });
Some(new_id) Some((new_id, capture_time))
} else { } else {
log::warn!( log::warn!(
"Heimdall was busy and won't start another collection session." "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) { if let Some(session) = FOCUS_SESSIONS.get(&session_id) {
Some(session.data.iter().map(|e| e.as_header()).collect()) Some(session.data.iter().map(|e| e.as_header()).collect())
} else { } 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) { if let Some(mut session) = FOCUS_SESSIONS.get_mut(&session_id) {
let filename = format!("/tmp/cap_sess_{session_id}"); let filename = format!("/tmp/cap_sess_{session_id}");
session.dump_filename = Some(filename.clone()); 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")] #[serde(crate = "rocket::serde")]
pub enum RequestAnalysisResult { pub enum RequestAnalysisResult {
Fail, Fail,
Ok(usize) Ok{ session_id: usize, countdown: usize }
} }
#[get("/api/request_analysis/<ip>")] #[get("/api/request_analysis/<ip>")]
pub async fn request_analysis(ip: String) -> NoCache<Json<RequestAnalysisResult>> { pub async fn request_analysis(ip: String) -> NoCache<Json<RequestAnalysisResult>> {
for r in bus_request(vec![BusRequest::GatherPacketData(ip)]).await.unwrap() { for r in bus_request(vec![BusRequest::GatherPacketData(ip)]).await.unwrap() {
if let BusResponse::PacketCollectionSession(id) = r { if let BusResponse::PacketCollectionSession{session_id, countdown} = r {
return NoCache::new(Json(RequestAnalysisResult::Ok(id))); 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)) NoCache::new(Json(result))
} }
#[get("/api/pcap/<id>/capture.pcap")] #[allow(unused_variables)]
pub async fn pcap(id: usize) -> Result<NoCache<NamedFile>, Status> { #[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() { for r in bus_request(vec![BusRequest::GetPcapDump(id)]).await.unwrap() {
if let BusResponse::PcapDump(Some(filename)) = r { if let BusResponse::PcapDump(Some(filename)) = r {
return Ok(NoCache::new(NamedFile::open(filename).await.unwrap())); return Ok(NoCache::new(NamedFile::open(filename).await.unwrap()));

View File

@ -195,7 +195,7 @@
<div class="card-body"> <div class="card-body">
<h5 class="card-title"><i class="fa fa-bar-chart"></i> Flows (Last 30 Seconds)</h5> <h5 class="card-title"><i class="fa fa-bar-chart"></i> Flows (Last 30 Seconds)</h5>
<p class="alert alert-warning" role="alert"> <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> </p>
<div id="packetButtons"></div> <div id="packetButtons"></div>
<div id="flowList"></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.") alert("Heimdall is busy serving other customers. Your desire is important to him, please try again later.")
return; return;
} }
analysisId = data.Ok; analysisId = data.Ok.session_id;
analysisBtn = "#dumpBtn_" + id; analysisBtn = "#dumpBtn_" + id;
analysisTimer = 10; analysisTimer = data.Ok.countdown;
analyzeTick(); analyzeTick();
}); });
} }
@ -608,7 +608,7 @@
if (analysisTimer > -1) { if (analysisTimer > -1) {
setTimeout(analyzeTick, 1000); setTimeout(analyzeTick, 1000);
} else { } 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> </button>
<div class="collapse navbar-collapse" id="navbarSupportedContent"> <div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0"> <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"> <li class="nav-item">
<a class="nav-link" href="/tree?parent=0"><i class="fa fa-tree"></i> Tree</a> <a class="nav-link" href="/tree?parent=0"><i class="fa fa-tree"></i> Tree</a>
</li> </li>
@ -130,7 +127,7 @@ if (hdr->cwr) flags |= 128;
} }
function paginator(active) { 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='zoomIn();'>Zoom In</a> ";
paginator += "<a href='#' class='btn btn-info' onClick='zoomOut();'>Zoom Out</a> ( Or drag an area of the graph) <br />"; paginator += "<a href='#' class='btn btn-info' onClick='zoomOut();'>Zoom Out</a> ( Or drag an area of the graph) <br />";
@ -198,7 +195,10 @@ if (hdr->cwr) flags |= 128;
{x: x_axis, y:y2_axis, name: 'Upload', type: 'scatter', mode: 'markers', error_x: { type: 'percent', value: capacity[1], symetric: false, valueminus: 0 }}, {x: x_axis, y:y2_axis, name: 'Upload', type: 'scatter', mode: 'markers', error_x: { type: 'percent', value: capacity[1], symetric: false, valueminus: 0 }},
]; ];
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 }); 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() { function start() {
colorReloadButton(); colorReloadButton();
@ -206,6 +206,7 @@ if (hdr->cwr) flags |= 128;
const params = new Proxy(new URLSearchParams(window.location.search), { const params = new Proxy(new URLSearchParams(window.location.search), {
get: (searchParams, prop) => searchParams.get(prop), get: (searchParams, prop) => searchParams.get(prop),
}); });
circuit_id = params.circuit_id;
capacity = [ params.dn, params.up ]; // Bits per second capacity = [ params.dn, params.up ]; // Bits per second
capacity = [ capacity[0] / 8, capacity[1] / 8 ]; // Bytes per second capacity = [ capacity[0] / 8, capacity[1] / 8 ]; // Bytes per second
@ -226,6 +227,7 @@ if (hdr->cwr) flags |= 128;
} }
packets = data; packets = data;
pages = Math.ceil((packets.length / PAGE_SIZE)); pages = Math.ceil((packets.length / PAGE_SIZE));
starting_timestamp = min_ts;
paginator(0); paginator(0);
viewPage(0); viewPage(0);
}); });

View File

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