Implement packet capture per IP, and download pcap format file at the end.

This commit is contained in:
Herbert Wolverson 2024-07-17 12:00:57 -05:00
parent 6359118147
commit 1b7a6f5098
6 changed files with 125 additions and 1 deletions

1
src/rust/Cargo.lock generated
View File

@ -2069,6 +2069,7 @@ dependencies = [
"lqos_sys",
"lqos_utils",
"lts_client",
"mime_guess",
"nix 0.29.0",
"once_cell",
"rand",

View File

@ -44,6 +44,7 @@ strum = { version = "0.26.3", features = ["derive"] }
default-net = { workspace = true }
surge-ping = "0.8.1"
rand = "0.8.5"
mime_guess = "2.0.4"
# Support JemAlloc on supported platforms
[target.'cfg(any(target_arch = "x86", target_arch = "x86_64"))'.dependencies]

View File

@ -48,7 +48,7 @@ function connectPrivateChannel() {
});
}
function connectPingers(circuits) {
function fullIpList(circuits) {
let ipList = [];
circuits.forEach((circuit) => {
circuit.ipv4.forEach((ip) => {
@ -58,6 +58,11 @@ function connectPingers(circuits) {
ipList.push([ip[0], circuit.device_id]);
});
});
return ipList;
}
function connectPingers(circuits) {
let ipList = fullIpList(circuits);
pinger = new DirectChannel({
PingMonitor: {
@ -595,6 +600,84 @@ function subscribeToCake() {
});
}
function wireupAnalysis(circuits) {
let ipAddresses = fullIpList(circuits);
let list = document.createElement("div");
let listBtn = document.createElement("button");
listBtn.type = "button";
listBtn.id = "CaptureTopBtn";
listBtn.classList.add("btn", "btn-primary", "dropdown-toggle", "btn-sm");
listBtn.setAttribute("data-bs-toggle", "dropdown");
listBtn.innerHTML = "<i class='fa fa-search'></i> Packet Capture";
list.appendChild(listBtn);
let listUl = document.createElement("ul");
listUl.classList.add("dropdown-menu", "dropdown-menu-sized");
ipAddresses.forEach((ip) => {
let entry = document.createElement("li");
let item = document.createElement("a");
item.classList.add("dropdown-item");
item.innerHTML = "<i class='fa fa-search'></i> Capture packets from " + ip[0];
let address = ip[0]; // For closure capture
item.onclick = () => {
//console.log("Clicky " + address);
$.get("/local-api/requestAnalysis/" + encodeURI(address), (data) => {
//console.log(data);
if (data === "Fail") {
alert("Packet capture is already active for another IP. Please try again when it is finished.")
return;
}
let counter = parseInt(data.Ok.countdown)+1;
let sessionId = data.Ok.session_id;
let btn = document.getElementById("CaptureTopBtn");
btn.disabled = true;
btn.innerHTML = "<i class='fa fa-spinner fa-spin'></i> Capturing Packets (" + counter + ")";
let interval = setInterval(() => {
counter--;
if (counter === -1) {
clearInterval(interval);
btn.disabled = false;
btn.innerHTML = "<i class='fa fa-download'></i> Download Packet Capture for " + address;
btn.classList.remove("btn-primary");
btn.classList.add("btn-success");
btn.onclick = () => {
let url = "/local-api/pcapDump/" + sessionId;
download(url, "capture.pcap");
//console.log(url);
// Restore the buttons
$.ajax({
type: "POST",
url: "/local-api/circuitById",
data: JSON.stringify({id: circuit_id}),
contentType: 'application/json',
success: (circuits) => {
wireupAnalysis(circuits);
}
});
}
return;
}
btn.innerHTML = "<i class='fa fa-spinner fa-spin'></i> Capturing Packets (" + counter + ")";
}, 1000);
});
}
entry.appendChild(item);
listUl.appendChild(entry);
});
list.appendChild(listUl);
let parent = document.getElementById("captureButton");
clearDiv(parent);
parent.appendChild(list);
}
function download(dataurl, filename) {
const link = document.createElement("a");
link.href = dataurl;
link.download = filename;
link.click();
}
function loadInitial() {
$.ajax({
type: "POST",
@ -623,6 +706,7 @@ function loadInitial() {
connectFlowChannel();
initialFunnel(circuit.parent_node);
subscribeToCake();
wireupAnalysis(circuits);
},
error: () => {
alert("Circuit with id " + circuit_id + " not found");

View File

@ -10,6 +10,7 @@ mod unknown_ips;
mod reload_libreqos;
mod config;
mod circuit;
mod packet_analysis;
use axum::Router;
use axum::routing::{get, post};
@ -41,5 +42,7 @@ pub fn local_api() -> Router {
.route("/updateConfig", post(config::update_lqosd_config))
.route("/updateNetworkAndDevices", post(config::update_network_and_devices))
.route("/circuitById", post(circuit::get_circuit_by_id))
.route("/requestAnalysis/:ip", get(packet_analysis::request_analysis))
.route("/pcapDump/:id", get(packet_analysis::pcap_dump))
.route_layer(axum::middleware::from_fn(auth_layer))
}

View File

@ -0,0 +1,31 @@
use std::net::IpAddr;
use axum::body::Body;
use axum::extract::Path;
use axum::http::{HeaderMap, Request};
use axum::Json;
use axum::response::IntoResponse;
use serde::Serialize;
use tower_http::services::ServeFile;
use lqos_heimdall::n_second_pcap;
#[derive(Serialize, Clone)]
pub enum RequestAnalysisResult {
Fail,
Ok{ session_id: usize, countdown: usize }
}
pub async fn request_analysis(Path(ip): Path<String>) -> Json<RequestAnalysisResult> {
if let Ok(ip) = ip.parse::<IpAddr>() {
if let Some((session_id, countdown)) = lqos_heimdall::hyperfocus_on_target(ip.into()) {
return Json(RequestAnalysisResult::Ok{ session_id, countdown });
}
}
Json(RequestAnalysisResult::Fail)
}
pub async fn pcap_dump(Path(id): Path<usize>, headers: HeaderMap) -> impl IntoResponse {
let filename = n_second_pcap(id).unwrap();
let mut req = Request::new(Body::empty());
*req.headers_mut() = headers;
ServeFile::new(filename).try_call(req).await.unwrap()
}

View File

@ -14,6 +14,10 @@
<td style="font-weight: bold">Minimum Speed</td>
<td><span id="bwMin"></span></td>
</tr>
<tr>
<td colspan="2" id="captureButton">
</td>
</tr>
</table>
</div>
<div class="col-3">