mirror of
https://github.com/LibreQoE/LibreQoS.git
synced 2025-02-25 18:55:32 -06:00
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:
parent
a32949c76d
commit
25801b6445
@ -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
1
src/rust/Cargo.lock
generated
@ -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",
|
||||||
|
@ -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>>),
|
||||||
|
@ -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
|
||||||
|
@ -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"
|
||||||
|
@ -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;
|
||||||
|
@ -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());
|
||||||
|
@ -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()));
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 />";
|
||||||
|
|
||||||
@ -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 });
|
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();
|
||||||
updateHostCounts();
|
updateHostCounts();
|
||||||
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);
|
||||||
});
|
});
|
||||||
|
@ -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())
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user