mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-31 23:45:13 +08:00
🌴 Sync 2026-04-03 08:49:56
This commit is contained in:
1 parent
c361065f64
commit
687ee81cf2
27 files changed
+1571
-1687
No files matched your search
+245
@@ -0,0 +1,245 @@
|
||||
#!/usr/bin/env lua
|
||||
|
||||
-- Copyright 2018-2023 Ycarus (Yannick Chabanois) <ycarus@zugaina.org>
|
||||
-- Licensed to the public under the Apache License 2.0.
|
||||
--
|
||||
-- rpcd script exposing MPTCP bandwidth and diagnostic data as UBUS/RPC methods,
|
||||
-- replacing the legacy Lua HTTP controller handlers.
|
||||
|
||||
local jsonc = require "luci.jsonc"
|
||||
local uci = require "luci.model.uci".cursor()
|
||||
|
||||
-- -------------------------------------------------------------------------
|
||||
-- Helpers
|
||||
-- -------------------------------------------------------------------------
|
||||
|
||||
local function get_device(interface)
|
||||
if not interface or interface == "" then return "" end
|
||||
local dump = require("luci.util").ubus("network.interface." .. interface, "status", {})
|
||||
if dump and dump["l3_device"] then
|
||||
return dump["l3_device"]
|
||||
end
|
||||
return ""
|
||||
end
|
||||
|
||||
local function exec_output(cmd)
|
||||
local lines = {}
|
||||
local f = io.popen(cmd)
|
||||
if f then
|
||||
for line in f:lines() do
|
||||
lines[#lines + 1] = line
|
||||
end
|
||||
f:close()
|
||||
end
|
||||
return table.concat(lines, "\n")
|
||||
end
|
||||
|
||||
-- Returns an array of [ts, rx_bytes, rx_pkts, tx_bytes, tx_pkts] rows from luci-bwc.
|
||||
-- luci-bwc outputs one "[ ts, rx, rxp, tx, txp ]," entry per line (no outer brackets).
|
||||
-- The trailing comma is determined by file position, not by whether the next entry is
|
||||
-- non-zero: when the circular buffer is partially filled the last valid row still gets
|
||||
-- a trailing comma. Strip it before wrapping with "[...]" to produce valid JSON.
|
||||
local function bwc_rows(dev)
|
||||
local f = io.popen(string.format("luci-bwc -i %q 2>/dev/null", dev))
|
||||
if not f then return {} end
|
||||
local output = f:read("*all")
|
||||
f:close()
|
||||
if not output or output:match("^%s*$") then return {} end
|
||||
output = output:gsub(",%s*$", "")
|
||||
local ok, rows = pcall(jsonc.parse, "[" .. output .. "]")
|
||||
if ok and type(rows) == "table" then return rows end
|
||||
return {}
|
||||
end
|
||||
|
||||
-- -------------------------------------------------------------------------
|
||||
-- RPC methods
|
||||
-- -------------------------------------------------------------------------
|
||||
|
||||
local methods = {
|
||||
|
||||
-- Returns per-interface bandwidth arrays plus a "total" entry.
|
||||
-- Each value is an array of [ts, rx_bytes, rx_pkts, tx_bytes, tx_pkts].
|
||||
multipath_bandwidth = {
|
||||
call = function()
|
||||
local result = {}
|
||||
local iface_rows = {} -- interface -> rows (only non-empty)
|
||||
|
||||
uci:foreach("network", "interface", function(s)
|
||||
local intname = s[".name"]
|
||||
local dev = get_device(intname)
|
||||
if dev == "" then dev = s["device"] or s["ifname"] or "" end
|
||||
|
||||
local multipath = s["multipath"] or ""
|
||||
if dev ~= "lo" and dev ~= "" then
|
||||
if multipath == "" then
|
||||
multipath = uci:get("openmptcprouter", intname, "multipath") or ""
|
||||
end
|
||||
if multipath == "" then multipath = "off" end
|
||||
|
||||
if multipath == "on" or multipath == "master" or
|
||||
multipath == "backup" or multipath == "handover" then
|
||||
local rows = bwc_rows(dev)
|
||||
result[intname] = rows
|
||||
if #rows > 0 then
|
||||
iface_rows[intname] = rows
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- Build a correctly aligned cumulative total using last-known-value
|
||||
-- interpolation. The old approach summed raw cumulative counters at
|
||||
-- each timestamp, but different interfaces record data at different
|
||||
-- (interleaved) timestamps. When consecutive total entries came from
|
||||
-- different interfaces, rate = (total[i] - total[i-1]) / dt could be
|
||||
-- negative or zero even under real traffic.
|
||||
--
|
||||
-- Fix: for every timestamp in the union of all interface timestamps,
|
||||
-- use the latest known cumulative bytes from EACH interface (carry the
|
||||
-- last seen value forward). This keeps the total monotonically
|
||||
-- increasing so JS rate computation stays correct.
|
||||
local ts_set = {}
|
||||
local all_ts = {}
|
||||
for _, rows in pairs(iface_rows) do
|
||||
for _, row in ipairs(rows) do
|
||||
local ts = row[1]
|
||||
if not ts_set[ts] then
|
||||
ts_set[ts] = true
|
||||
all_ts[#all_ts + 1] = ts
|
||||
end
|
||||
end
|
||||
end
|
||||
table.sort(all_ts)
|
||||
|
||||
-- One forward-scan pointer per interface
|
||||
local iface_ptrs = {}
|
||||
for itf, rows in pairs(iface_rows) do
|
||||
iface_ptrs[itf] = { idx = 1, rows = rows }
|
||||
end
|
||||
|
||||
local iface_last = {} -- latest row seen so far per interface
|
||||
local total_rows = {}
|
||||
for _, ts in ipairs(all_ts) do
|
||||
-- Advance each pointer up to (and including) this timestamp
|
||||
for itf, ptr in pairs(iface_ptrs) do
|
||||
while ptr.idx <= #ptr.rows and ptr.rows[ptr.idx][1] <= ts do
|
||||
iface_last[itf] = ptr.rows[ptr.idx]
|
||||
ptr.idx = ptr.idx + 1
|
||||
end
|
||||
end
|
||||
-- Sum latest known cumulative bytes from every interface
|
||||
local total = { ts, 0, 0, 0, 0 }
|
||||
for _, last_row in pairs(iface_last) do
|
||||
for j = 2, 5 do
|
||||
total[j] = total[j] + (last_row[j] or 0)
|
||||
end
|
||||
end
|
||||
total_rows[#total_rows + 1] = total
|
||||
end
|
||||
|
||||
result["total"] = total_rows
|
||||
return result
|
||||
end
|
||||
},
|
||||
|
||||
-- Returns bandwidth rows for a single device as { data: [...] }.
|
||||
interface_bandwidth = {
|
||||
args = { iface = "" },
|
||||
call = function(args)
|
||||
return { data = bwc_rows(args.iface or "") }
|
||||
end
|
||||
},
|
||||
|
||||
-- Runs "multipath -f" and returns its output.
|
||||
mptcp_fullmesh = {
|
||||
call = function()
|
||||
return { output = exec_output("multipath -f 2>/dev/null") }
|
||||
end
|
||||
},
|
||||
|
||||
-- Runs "multipath -m" and returns its output.
|
||||
mptcp_monitor = {
|
||||
call = function()
|
||||
return { output = exec_output("multipath -m 2>/dev/null") }
|
||||
end
|
||||
},
|
||||
|
||||
-- Runs "multipath -c" and returns its output.
|
||||
mptcp_connections = {
|
||||
call = function()
|
||||
return { output = exec_output("multipath -c 2>/dev/null") }
|
||||
end
|
||||
},
|
||||
|
||||
-- Runs tracebox against the configured VPN server via the given interface.
|
||||
mptcp_check_trace = {
|
||||
args = { iface = "" },
|
||||
call = function(args)
|
||||
local iface = args.iface or ""
|
||||
local interface = get_device(iface)
|
||||
local server = uci:get("shadowsocks-libev", "sss0", "server") or ""
|
||||
if server == "" then return { output = "" } end
|
||||
local cmd
|
||||
if interface == "" then
|
||||
cmd = "tracebox -s /usr/share/tracebox/omr-mptcp-trace.lua " .. server
|
||||
else
|
||||
cmd = "tracebox -s /usr/share/tracebox/omr-mptcp-trace.lua -i " .. interface .. " " .. server
|
||||
end
|
||||
return { output = exec_output(cmd) }
|
||||
end
|
||||
},
|
||||
}
|
||||
|
||||
-- -------------------------------------------------------------------------
|
||||
-- rpcd dispatch (same boilerplate used by all rpcd Lua scripts in this repo)
|
||||
-- -------------------------------------------------------------------------
|
||||
|
||||
local function parseInput()
|
||||
local parse = jsonc.new()
|
||||
local done, err
|
||||
while true do
|
||||
local chunk = io.read(4096)
|
||||
if not chunk then break end
|
||||
if not done and not err then
|
||||
done, err = parse:parse(chunk)
|
||||
end
|
||||
end
|
||||
if not done then
|
||||
print(jsonc.stringify({ error = err or "Incomplete input" }))
|
||||
os.exit(1)
|
||||
end
|
||||
return parse:get()
|
||||
end
|
||||
|
||||
local function validateArgs(func, uargs)
|
||||
local method = methods[func]
|
||||
if not method then
|
||||
print(jsonc.stringify({ error = "Method not found" }))
|
||||
os.exit(1)
|
||||
end
|
||||
if type(uargs) ~= "table" then
|
||||
print(jsonc.stringify({ error = "Invalid arguments" }))
|
||||
os.exit(1)
|
||||
end
|
||||
uargs.ubus_rpc_session = nil
|
||||
local margs = method.args or {}
|
||||
for k, v in pairs(uargs) do
|
||||
if margs[k] == nil or (v ~= nil and type(v) ~= type(margs[k])) then
|
||||
print(jsonc.stringify({ error = "Invalid arguments" }))
|
||||
os.exit(1)
|
||||
end
|
||||
end
|
||||
return method
|
||||
end
|
||||
|
||||
if arg[1] == "list" then
|
||||
local rv = {}
|
||||
for name, method in pairs(methods) do rv[name] = method.args or {} end
|
||||
print((jsonc.stringify(rv):gsub(":%[%]", ":{}")))
|
||||
elseif arg[1] == "call" then
|
||||
local args = parseInput()
|
||||
local method = validateArgs(arg[2], args)
|
||||
local result, code = method.call(args)
|
||||
print((jsonc.stringify(result):gsub("^%[%]$", "{}")))
|
||||
os.exit(code or 0)
|
||||
end
|
||||
Reference in new issue
Block a user