From cf8abafd6599763fc2d9d561c0763e159b70b68e Mon Sep 17 00:00:00 2001 From: Tim Wilkinson Date: Wed, 20 Jul 2022 12:42:05 -0700 Subject: [PATCH] iPerf3 as part of the api (#443) --- files/usr/lib/lua/aredn/utils.lua | 76 +++++++++++++++++++++++++++---- files/www/cgi-bin/api | 12 +++++ 2 files changed, 80 insertions(+), 8 deletions(-) diff --git a/files/usr/lib/lua/aredn/utils.lua b/files/usr/lib/lua/aredn/utils.lua index 3d6601e3..57e7015d 100755 --- a/files/usr/lib/lua/aredn/utils.lua +++ b/files/usr/lib/lua/aredn/utils.lua @@ -39,6 +39,7 @@ local nxo = require("nixio") local ipc = require("luci.ip") local auci = require("aredn.uci") require("uci") +require("luci.sys") function round2(num, idp) return tonumber(string.format("%." .. (idp or 0) .. "f", num)) @@ -305,7 +306,7 @@ function getTraceroute(target) end ------------------------------------- --- Returns traceroute +-- Returns ping ------------------------------------- function getPing(target) local pings = {} @@ -320,19 +321,19 @@ function getPing(target) else local ip, seq, ttl, time = line:match("bytes from ([%d%.]+): seq=(%d+) ttl=(%d+) time=(%S+) ms") if ip then - pings[#pings + 1] = { ip = ip, seq = seq, ttl = ttl, timeMs = time } + pings[#pings + 1] = { ip = ip, seq = tonumber(seq), ttl = tonumber(ttl), timeMs = tonumber(time) } else local tx, rx, loss = line:match("^(%d+) packets transmitted, (%d+) packets received, (%d+)%% packet loss") if tx then - summary.tx = tx - summary.rx = rx - summary.lossPercentage = loss + summary.tx = tonumber(tx) + summary.rx = tonumber(rx) + summary.lossPercentage = tonumber(loss) else local min, avg, max = line:match("min/avg/max = ([%d%.]+)/([%d%.]+)/([%d%.]+) ms") if min then - summary.minMs = min - summary.maxMs = max - summary.avgMs = avg + summary.minMs = tonumber(min) + summary.maxMs = tonumber(max) + summary.avgMs = tonumber(avg) end end end @@ -341,6 +342,65 @@ function getPing(target) return { summary = summary, pings = pings } end +------------------------------------- +-- Returns iperf3 +------------------------------------- +function getIperf3(target, protocol) + if protocol ~= "udp" then + protocol = "tcp" + end + function toK(value, unit) + return tonumber(value) * (unit == "M" and 1024 or 1) + end + function toM(value, unit) + return tonumber(value) / (unit == "K" and 1024 or 1) + end + local summary = { protocol = protocol, client = {}, server = {}, sender = {}, receiver = {} } + local trace = {} + -- start remote server + luci.sys.httpget("http://" .. target .. ":8080/cgi-bin/iperf?server=") + local output = capture("/usr/bin/iperf3 -b 0 -c " .. target .. (protocol == "udp" and " -u" or "") .. " 2>&1") + for _, line in ipairs(output:splitNewLine()) + do + local chost, cport, shost, sport = line:match("local ([%d%.]+) port (%d+) connected to ([%d%.]+) port (%d+)") + if chost then + summary.client = { host = chost, port = tonumber(cport) } + summary.server = { host = shost, port = tonumber(sport) } + else + local from, to, transfer, tu, bitrate, bu, retr = line:match("([%d%.]+)-([%d%.]+)%s+sec%s+([%d%.]+) ([KM])Bytes%s+([%d%.]+) ([MK])bits/sec%s+(%d+)%s+sender") + if from then + summary.sender = { from = tonumber(from), to = tonumber(to), transferMB = toM(transfer, tu), bitrateMb = toM(bitrate, bu), retr = tonumber(retr) } + else + local from, to, transfer, tu, bitrate, bu = line:match("([%d%.]+)-([%d%.]+)%s+sec%s+([%d%.]+) ([KM])Bytes%s+([%d%.]+) ([MK])bits/sec%s+receiver") + if from then + summary.receiver = { from = tonumber(from), to = tonumber(to), transferMB = toM(transfer, tu), bitrateMb = toM(bitrate, bu) } + else + local from, to, transfer, tu, bitrate, bu, jitter, lost, total, percent = line:match("([%d%.]+)-([%d%.]+)%s+sec%s+([%d%.]+) ([KM])Bytes%s+([%d%.]+) ([MK])bits/sec%s+([%d%.]+) ms%s+(%d+)/(%d+) %(([%d%.]+)%%%)%s+sender") + if from then + summary.sender = { from = tonumber(from), to = tonumber(to), transferMB = toM(transfer, tu), bitrateMb = toM(bitrate, bu), jitterMs = tonumber(jitter), lostDgrams = tonumber(lost), totalDgrams = tonumber(total), lossPercentage = tonumber(precent) } + else + local from, to, transfer, tu, bitrate, bu, jitter, lost, total, percent = line:match("([%d%.]+)-([%d%.]+)%s+sec%s+([%d%.]+) ([KM])Bytes%s+([%d%.]+) ([MK])bits/sec%s+([%d%.]+) ms%s+(%d+)/(%d+) %(([%d%.]+)%%%)%s+receiver") + if from then + summary.receiver = { from = tonumber(from), to = tonumber(to), transferMB = toM(transfer, tu), bitrateMb = toM(bitrate, bu), jitterMs = tonumber(jitter), lostDgrams = tonumber(lost), totalDgrams = tonumber(total), lossPercentage = tonumber(precent) } + else + local from, to, transfer, tu, bitrate, bu, retr, cwnd, cu = line:match("([%d%.]+)-([%d%.]+)%s+sec%s+([%d%.]+) ([KM])Bytes%s+([%d%.]+) ([MK])bits/sec%s+(%d+)%s+([%d%.]+) ([KM])Bytes") + if from then + trace[#trace + 1] = { from = tonumber(from), to = tonumber(to), transferMB = toM(transfer, tu), bitrateMb = toM(bitrate, by), retr = tonumber(retr), cwndKB = toK(cwnd, cu) } + else + local from, to, transfer, tu, bitrate, bu, dgrams = line:match("([%d%.]+)-([%d%.]+)%s+sec%s+([%d%.]+) ([KM])Bytes%s+([%d%.]+) ([MK])bits/sec%s+(%d+)") + if from then + trace[#trace + 1] = { from = tonumber(from), to = tonumber(to), transferMB = toM(transfer, tu), bitrateMb = toM(bitrate, bu), dgrams = tonumber(dgrams) } + end + end + end + end + end + end + end + end + return { summary = summary, trace = trace } +end + function file_trim(filename, maxl) local lines={} local tmpfilename=filename..".tmp" diff --git a/files/www/cgi-bin/api b/files/www/cgi-bin/api index 60801a90..ddc1a521 100755 --- a/files/www/cgi-bin/api +++ b/files/www/cgi-bin/api @@ -387,6 +387,18 @@ for page, comps in pairs(qsset) do info['pages'][page][tonode]="Invalid input!" end end + elseif page=="iperf3" then + local protocol = "tcp" + for i,tonode in pairs(comps:split(',')) do + -- Validate that input as ip or hostname inside the mesh + if tonode == "tcp" or tonode == "udp" then + protocol = tonode + elseif tonode:match("^[%d%.]+$") or tonode:match("^[%d%a%-%.%_]+$") then + info['pages'][page][tonode]=getIperf3(tonode, protocol) + else + info['pages'][page][tonode]="Invalid input!" + end + end elseif page=="mesh" then for i,comp in pairs(comps:split(',')) do if comp=="sysinfo" then