From e6cb58bc650d1d3265e3fa466f8da2b3189fe4d9 Mon Sep 17 00:00:00 2001 From: Herbert Wolverson Date: Fri, 31 Mar 2023 16:20:56 +0000 Subject: [PATCH 01/67] Adds "client throughput" to the queue tree tab of circuit_queue Also moves all the circuit graphs to use the same graphing system as the other graphs on the plot. FIXES #315 --- .../static/circuit_queue.html | 124 +++++++++++------- 1 file changed, 75 insertions(+), 49 deletions(-) diff --git a/src/rust/lqos_node_manager/static/circuit_queue.html b/src/rust/lqos_node_manager/static/circuit_queue.html index 43815d67..3e7daeb6 100644 --- a/src/rust/lqos_node_manager/static/circuit_queue.html +++ b/src/rust/lqos_node_manager/static/circuit_queue.html @@ -94,8 +94,9 @@
- Pause - Slow Mode + Pause + Slow + Mode
@@ -385,16 +386,17 @@ { x: this.x_axis, y: this.backlog.tins[2].y, type: 'scattergl', mode: 'markers', name: 'Video', marker: { size: 4 } }, { x: this.x_axis, y: this.backlog.tins[3].y, type: 'scattergl', mode: 'markers', name: 'Voice', marker: { size: 4 } }, ]; - + if (this.backlogPlotted == null) { this.backlogPlotted = true; Plotly.newPlot( - graph, - graphData, - { - margin: { l: 0, r: 0, b: 0, t: 0, pad: 4 }, - yaxis: { automargin: true, title: "Bytes" }, - xaxis: { automargin: true, title: "Time since now" } }); + graph, + graphData, + { + margin: { l: 0, r: 0, b: 0, t: 0, pad: 4 }, + yaxis: { automargin: true, title: "Bytes" }, + xaxis: { automargin: true, title: "Time since now" } + }); } else { Plotly.redraw(graph, graphData); } @@ -411,11 +413,13 @@ if (this.delaysPlotted == null) { Plotly.newPlot( - graph, - graphData, - { margin: { l: 8, r: 0, b: 0, t: 0, pad: 4 }, - yaxis: { automargin: true, title: "log10(ms)", range: [-1.0, 1.0] }, - xaxis: { automargin: true, title: "Time since now" } }); + graph, + graphData, + { + margin: { l: 8, r: 0, b: 0, t: 0, pad: 4 }, + yaxis: { automargin: true, title: "log10(ms)", range: [-1.0, 1.0] }, + xaxis: { automargin: true, title: "Time since now" } + }); this.delaysPlotted = true; } else { Plotly.redraw(graph, graphData); @@ -441,7 +445,7 @@ let graphData = [ { x: this.x_axis, y: this.throughput.tins[tin].y, type: 'scatter', mode: 'markers' } ]; - if (this.tinsPlotted == null) { + if (this.tinsPlotted == null) { Plotly.newPlot(graph, graphData, { margin: { l: 0, r: 0, b: 0, t: 0, pad: 4 }, yaxis: { automargin: true, title: "Bits" }, xaxis: { automargin: true, title: "Time since now" } }); } else { Plotly.redraw(graph, graphData); @@ -502,7 +506,7 @@ this.per_ip = {}; this.y = {}; this.x_axis = []; - for (let i=0; i this.capacity*2) { + if (this.head > this.capacity * 2) { this.head = 0; } } @@ -557,30 +561,30 @@ prepare() { this.clearQuantiles(); for (const ip in this.per_ip) { - let counter = this.capacity*2; - for (let i = this.head; i < this.capacity*2; i++) { - this.y[ip][counter] = this.per_ip[ip][i]; + let counter = this.capacity * 2; + for (let i = this.head; i < this.capacity * 2; i++) { + this.y[ip][counter] = this.per_ip[ip][i]; counter--; } for (let i = 0; i < this.head; i++) { this.y[ip][counter] = this.per_ip[ip][i]; counter--; } - for (let i=2; i<22; i+=2) { - this.addQuantile(this.y[ip][i], this.y[ip][i+1]); + for (let i = 2; i < 22; i += 2) { + this.addQuantile(this.y[ip][i], this.y[ip][i + 1]); } } } - plot() { - let graph = document.getElementById("throughputGraph"); + plot(target) { + let graph = document.getElementById(target); let graphData = []; for (const ip in this.per_ip) { graphData.push({ x: this.x_axis, y: this.y[ip], name: ip, mode: 'markers', type: 'scattergl', marker: { size: 3 } }); } - if (this.plotted == null) { - Plotly.newPlot(graph, graphData, { margin: { l: 0, r: 0, b: 0, t: 0, pad: 4 }, yaxis: { automargin: true, title: "Traffic (bits)" }, xaxis: { automargin: true, title: "Time since now" } }); - this.plotted = true; + if (!this.hasOwnProperty("plotted" + target)) { + Plotly.newPlot(graph, graphData, { margin: { l: 0, r: 0, b: 0, t: 0, pad: 4 }, yaxis: { automargin: true, title: "Traffic (bits)" }, xaxis: { automargin: true, title: "Time since now" } }); + this["plotted" + target] = true; } else { Plotly.redraw(graph, graphData); } @@ -608,7 +612,7 @@ msgPackGet("/api/circuit_throughput/" + encodeURI(id), (data) => { if (tpData == null) tpData = new ThroughputMonitor(300); ips = []; - for (let i=0; i < data.length; i++) { + for (let i = 0; i < data.length; i++) { let ip = data[i][0]; ips.push(ip); let down = data[i][1]; @@ -616,13 +620,13 @@ tpData.ingest(ip, down, up); } tpData.prepare(); - tpData.plot(); + tpData.plot("throughputGraph"); tpData.plotQuantiles(); }); } } - let funnels = new MultiRingBuffer(300); + let funnels = new ThroughputMonitor(300); let rtts = {}; let circuitId = ""; let builtFunnelDivs = false; @@ -635,8 +639,24 @@ circuitId = encodeURI(id); msgPackGet("/api/funnel_for_queue/" + circuitId, (data) => { let html = ""; + + // Add the client on top + let row = "
"; + + row += "
"; + row += "
"; + row += "
Client Throughput
"; + row += "
"; + row += "
"; + row += "
"; + + row += "
"; + html += row; + + // Funnels for (let i = 0; i < data.length; ++i) { - funnels.push(data[i][0], data[i][1][NetTrans.current_throughput][0] * 8, data[i][1][NetTrans.current_throughput][1] * 8); + //funnels.push(data[i][0], data[i][1][NetTrans.current_throughput][0] * 8, data[i][1][NetTrans.current_throughput][1] * 8); + funnels.ingest(data[i][0], data[i][1][NetTrans.current_throughput][0] * 8, data[i][1][NetTrans.current_throughput][1] * 8); rtts[data[i][0]] = new RttHistogram(); let row = "
"; @@ -660,33 +680,38 @@ } $("#pills-funnel").html(html); builtFunnelDivs = true; - setTimeout(plotFunnels, 10); }); } let plottedFunnels = {}; function plotFunnels() { + if (tpData != null) tpData.plot("tp_client"); + funnels.prepare(); msgPackGet("/api/funnel_for_queue/" + encodeURI(circuitId), (data) => { for (let i = 0; i < data.length; ++i) { - funnels.push(data[i][0], data[i][1][NetTrans.current_throughput][0] * 8, data[i][1][NetTrans.current_throughput][1] * 8); - for (const [k, v] of Object.entries(funnels.data)) { - let target_div = "tp" + k; - let graphData = v.toScatterGraphData(); - let graph = document.getElementById(target_div); - if (!plotFunnels.hasOwnProperty(target_div)) { - Plotly.newPlot(graph, graphData, { margin: { l: 0, r: 0, b: 0, t: 0, pad: 4 }, yaxis: { automargin: true }, xaxis: { automargin: true, title: "Time since now" } }, { responsive: true }); - } else { - Plotly.redraw(graph, graphData); - } - } - rtts[data[i][0]].clear(); + funnels.ingest(data[i][0], data[i][1][NetTrans.current_throughput][0] * 8, data[i][1][NetTrans.current_throughput][1] * 8); for (let j = 0; j < data[i][1][NetTrans.rtts].length; j++) { rtts[data[i][0]].push(data[i][1][NetTrans.rtts][j]); } + rtts[data[i][0]].plot("rtt" + data[i][0]); } + + for (const [k, v] of Object.entries(funnels.y)) { + let target_div = "tp" + k; + let graphData = [ + { x: funnels.x_axis, y: v, type: 'scatter', mode: 'markers', marker: { size: 3 } } + ]; + let graph = document.getElementById(target_div); + if (!plotFunnels.hasOwnProperty(target_div)) { + Plotly.newPlot(graph, graphData, { margin: { l: 0, r: 0, b: 0, t: 0, pad: 4 }, yaxis: { automargin: true, title: "Traffic (bits)" }, xaxis: { automargin: true, title: "Time since now" } }); + } else { + Plotly.redraw(graph, graphData); + } + } + }); } @@ -828,14 +853,14 @@ var slowMode = false; function showFps() { - if(!lastCalledTime) { + if (!lastCalledTime) { lastCalledTime = Date.now(); fps = 0; return; } - delta = (Date.now() - lastCalledTime)/1000; + delta = (Date.now() - lastCalledTime) / 1000; lastCalledTime = Date.now(); - fps = 1/delta; + fps = 1 / delta; //$("#fps").text(fps.toFixed(0)); worstDelta = Math.max(delta, worstDelta); } @@ -846,6 +871,7 @@ switch (activeTab) { case "pills-funnel-tab": { getFunnel(); + getThroughput(); } break; case "pills-flows-tab": { getFlows(); From aa7cf1fc684d1b8e1602bb457ed4f75c739aa958 Mon Sep 17 00:00:00 2001 From: Herbert Wolverson Date: Fri, 31 Mar 2023 17:10:43 +0000 Subject: [PATCH 02/67] Add DNS resolution to displayed flow IP addresses --- src/rust/Cargo.lock | 14 ++++++++++++++ src/rust/lqos_node_manager/Cargo.toml | 2 ++ src/rust/lqos_node_manager/src/main.rs | 1 + src/rust/lqos_node_manager/src/queue_info.rs | 11 ++++++++++- .../lqos_node_manager/src/tracker/cache/mod.rs | 2 ++ src/rust/lqos_node_manager/src/tracker/mod.rs | 1 + .../lqos_node_manager/static/circuit_queue.html | 4 ++-- src/rust/lqos_node_manager/static/lqos.js | 16 ++++++++++++++++ 8 files changed, 48 insertions(+), 3 deletions(-) diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index 5d681b67..299ff8b1 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -733,6 +733,18 @@ dependencies = [ "subtle", ] +[[package]] +name = "dns-lookup" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53ecafc952c4528d9b51a458d1a8904b81783feff9fde08ab6ed2545ff396872" +dependencies = [ + "cfg-if", + "libc", + "socket2", + "winapi", +] + [[package]] name = "either" version = "1.8.1" @@ -1448,7 +1460,9 @@ name = "lqos_node_manager" version = "0.1.0" dependencies = [ "anyhow", + "dashmap", "default-net", + "dns-lookup", "jemallocator", "lqos_bus", "lqos_config", diff --git a/src/rust/lqos_node_manager/Cargo.toml b/src/rust/lqos_node_manager/Cargo.toml index 69b3c05b..818c8d42 100644 --- a/src/rust/lqos_node_manager/Cargo.toml +++ b/src/rust/lqos_node_manager/Cargo.toml @@ -19,6 +19,8 @@ sysinfo = "0" default-net = "0" nix = "0" once_cell = "1" +dns-lookup = "1" +dashmap = "5" # Support JemAlloc on supported platforms [target.'cfg(any(target_arch = "x86", target_arch = "x86_64"))'.dependencies] diff --git a/src/rust/lqos_node_manager/src/main.rs b/src/rust/lqos_node_manager/src/main.rs index c0e13f32..1fe50750 100644 --- a/src/rust/lqos_node_manager/src/main.rs +++ b/src/rust/lqos_node_manager/src/main.rs @@ -77,6 +77,7 @@ fn rocket() -> _ { queue_info::packet_dump, queue_info::pcap, queue_info::request_analysis, + queue_info::dns_query, config_control::get_nic_list, config_control::get_current_python_config, config_control::get_current_lqosd_config, diff --git a/src/rust/lqos_node_manager/src/queue_info.rs b/src/rust/lqos_node_manager/src/queue_info.rs index fe5e7d46..3a0a34ab 100644 --- a/src/rust/lqos_node_manager/src/queue_info.rs +++ b/src/rust/lqos_node_manager/src/queue_info.rs @@ -1,6 +1,6 @@ use crate::auth_guard::AuthGuard; use crate::cache_control::NoCache; -use crate::tracker::SHAPED_DEVICES; +use crate::tracker::{SHAPED_DEVICES, lookup_dns}; use lqos_bus::{bus_request, BusRequest, BusResponse, FlowTransport, PacketHeader, QueueStoreTransit}; use rocket::fs::NamedFile; use rocket::http::Status; @@ -162,6 +162,15 @@ pub async fn pcap(id: usize, filename: String) -> Result, Sta Err(Status::NotFound) } +#[get("/api/dns/")] +pub async fn dns_query(ip: String) -> NoCache { + if let Ok(ip) = ip.parse::() { + NoCache::new(lookup_dns(ip)) + } else { + NoCache::new(ip) + } +} + #[cfg(feature = "equinix_tests")] #[get("/api/run_btest")] pub async fn run_btest() -> NoCache> { diff --git a/src/rust/lqos_node_manager/src/tracker/cache/mod.rs b/src/rust/lqos_node_manager/src/tracker/cache/mod.rs index 8829e9f5..2e0a7d89 100644 --- a/src/rust/lqos_node_manager/src/tracker/cache/mod.rs +++ b/src/rust/lqos_node_manager/src/tracker/cache/mod.rs @@ -5,7 +5,9 @@ mod cpu_ram; mod shaped_devices; mod throughput; +mod dns_cache; pub use cpu_ram::*; pub use shaped_devices::*; pub use throughput::THROUGHPUT_BUFFER; +pub use dns_cache::lookup_dns; diff --git a/src/rust/lqos_node_manager/src/tracker/mod.rs b/src/rust/lqos_node_manager/src/tracker/mod.rs index 9eed911f..67cdf69c 100644 --- a/src/rust/lqos_node_manager/src/tracker/mod.rs +++ b/src/rust/lqos_node_manager/src/tracker/mod.rs @@ -10,6 +10,7 @@ pub use cache::SHAPED_DEVICES; pub use cache_manager::{update_tracking, update_total_throughput_buffer}; use lqos_bus::{bus_request, BusRequest, BusResponse, IpStats, TcHandle}; use rocket::serde::{Deserialize, Serialize, msgpack::MsgPack}; +pub use cache::lookup_dns; #[derive(Serialize, Deserialize, Clone, Debug)] #[serde(crate = "rocket::serde")] diff --git a/src/rust/lqos_node_manager/static/circuit_queue.html b/src/rust/lqos_node_manager/static/circuit_queue.html index 3e7daeb6..718a5327 100644 --- a/src/rust/lqos_node_manager/static/circuit_queue.html +++ b/src/rust/lqos_node_manager/static/circuit_queue.html @@ -813,13 +813,13 @@ } html += ""; html += "" + data[i][0][FlowTrans.proto] + ""; - html += "" + data[i][0][FlowTrans.src] + ""; + html += "" + ipToHostname(data[i][0][FlowTrans.src]) + ""; if (data[i][0].proto == "ICMP") { html += "" + icmpType(data[i][0][FlowTrans.src_port]) + ""; } else { html += "" + data[i][0][FlowTrans.src_port] + ""; } - html += "" + data[i][0][FlowTrans.dst] + ""; + html += "" + ipToHostname(data[i][0][FlowTrans.dst]) + ""; if (data[i][0][FlowTrans.proto] == "ICMP") { if (data[i][1] != null) { html += "" + icmpType(data[i][1][FlowTrans.src_port]) + ""; diff --git a/src/rust/lqos_node_manager/static/lqos.js b/src/rust/lqos_node_manager/static/lqos.js index dd40994a..b1633dd5 100644 --- a/src/rust/lqos_node_manager/static/lqos.js +++ b/src/rust/lqos_node_manager/static/lqos.js @@ -447,4 +447,20 @@ function zero_to_null(array) { for (let i=0; i" + dnsCache[ip] + ""; + } else { + return ip; + } + } + $.get("/api/dns/" + encodeURI(ip), (hostname) => { + dnsCache[ip] = hostname; + }) + return ip; } \ No newline at end of file From 28e1b020afd3f400c2c900bb4024fee8755af02e Mon Sep 17 00:00:00 2001 From: Herbert Wolverson Date: Fri, 31 Mar 2023 17:12:18 +0000 Subject: [PATCH 03/67] Add file missing from previous commit --- .../src/tracker/cache/dns_cache.rs | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/rust/lqos_node_manager/src/tracker/cache/dns_cache.rs diff --git a/src/rust/lqos_node_manager/src/tracker/cache/dns_cache.rs b/src/rust/lqos_node_manager/src/tracker/cache/dns_cache.rs new file mode 100644 index 00000000..7165a137 --- /dev/null +++ b/src/rust/lqos_node_manager/src/tracker/cache/dns_cache.rs @@ -0,0 +1,38 @@ +//! Implements a lock-free DNS least-recently-used DNS cache. + +use std::net::IpAddr; +use dashmap::DashMap; +use dns_lookup::lookup_addr; +use lqos_utils::unix_time::unix_now; +use once_cell::sync::Lazy; + +const CACHE_SIZE: usize = 1000; + +struct DnsEntry { + hostname: String, + last_accessed: u64, +} + +static DNS_CACHE: Lazy> = Lazy::new(|| DashMap::with_capacity(CACHE_SIZE)); + +pub fn lookup_dns(ip: IpAddr) -> String { + // If the cached value exists, just return it + if let Some(mut dns) = DNS_CACHE.get_mut(&ip) { + if let Ok(now) = unix_now() { + dns.last_accessed = now; + } + return dns.hostname.clone(); + } + + // If it doesn't, we'll be adding it. + if DNS_CACHE.len() >= CACHE_SIZE { + let mut entries : Vec<(IpAddr, u64)> = DNS_CACHE.iter().map(|v| (*v.key(), v.last_accessed)).collect(); + entries.sort_by(|a,b| b.1.cmp(&a.1)); + DNS_CACHE.remove(&entries[0].0); + } + let hostname = lookup_addr(&ip).unwrap_or(ip.to_string()); + DNS_CACHE.insert(ip, DnsEntry { hostname, last_accessed: unix_now().unwrap_or(0) }); + + + String::new() +} \ No newline at end of file From 7b571213d95962ff33dc9a0e75abed2f27d19be0 Mon Sep 17 00:00:00 2001 From: Herbert Wolverson Date: Fri, 31 Mar 2023 17:31:59 +0000 Subject: [PATCH 04/67] Fix a missing break - switch fallthrough by mistake - in dissector.h. This fixes a problem with misidentifying traffic as TCP when it is't. --- src/rust/lqos_sys/src/bpf/common/dissector.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/rust/lqos_sys/src/bpf/common/dissector.h b/src/rust/lqos_sys/src/bpf/common/dissector.h index 25653d3f..cd43eb0d 100644 --- a/src/rust/lqos_sys/src/bpf/common/dissector.h +++ b/src/rust/lqos_sys/src/bpf/common/dissector.h @@ -345,8 +345,7 @@ static __always_inline void snoop(struct dissector_t *dissector) parse_tcp_ts(hdr, dissector->end, &dissector->tsval, &dissector->tsecr); } - } - break; + } break; case IPPROTO_UDP: { struct udphdr *hdr = get_udp_header(dissector); @@ -354,12 +353,13 @@ static __always_inline void snoop(struct dissector_t *dissector) { if (hdr + 1 > dissector->end) { + bpf_debug("UDP header past end"); return; } dissector->src_port = hdr->source; dissector->dst_port = hdr->dest; } - } + } break; case IPPROTO_ICMP: { struct icmphdr *hdr = get_icmp_header(dissector); @@ -367,14 +367,14 @@ static __always_inline void snoop(struct dissector_t *dissector) { if ((char *)hdr + sizeof(struct icmphdr) > dissector->end) { + bpf_debug("ICMP header past end"); return; } dissector->ip_protocol = 1; dissector->src_port = bpf_ntohs(hdr->type); - dissector->dst_port = bpf_ntohs(hdr->type); - } - } - break; + dissector->dst_port = bpf_ntohs(hdr->code); + } + } break; } } From 182dbcf8d9e408f409e45ed6521325d493f39797 Mon Sep 17 00:00:00 2001 From: Herbert Wolverson Date: Mon, 27 Mar 2023 16:21:46 +0000 Subject: [PATCH 05/67] IP Dump supports flow filtering Every packet is assigned a "flow id", and the UI allows you to filter displayed packet data by flow id. --- .../lqos_node_manager/static/ip_dump.html | 115 +++++++++++++----- 1 file changed, 82 insertions(+), 33 deletions(-) diff --git a/src/rust/lqos_node_manager/static/ip_dump.html b/src/rust/lqos_node_manager/static/ip_dump.html index df56005d..f98759fb 100644 --- a/src/rust/lqos_node_manager/static/ip_dump.html +++ b/src/rust/lqos_node_manager/static/ip_dump.html @@ -73,11 +73,26 @@