mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-04 01:13:56 +08:00
🌴 Sync 2026-03-05 23:51:42
This commit is contained in:
1 parent
97c049ed0a
commit
0acff03804
14253 files changed
+3893239
No files matched your search
Executable
+19
@@ -0,0 +1,19 @@
|
||||
# Copyright (C) 2020-2026 Hyy2001X <https://github.com/Hyy2001X>
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-iperf3-server
|
||||
PKG_VERSION:=3.0.0
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_LICENSE:=GPL-3.0-or-later
|
||||
|
||||
LUCI_TITLE:=LuCI support for iPerf3 Server
|
||||
LUCI_DESCRIPTION:=Configure and manage multiple iperf3 server instances with LuCI status/actions.
|
||||
LUCI_DEPENDS:=+iperf3
|
||||
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
# luci-app-iperf3-server
|
||||
|
||||
一个简单的 iPerf3 服务端
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
module("luci.controller.iperf3-server", package.seeall)
|
||||
|
||||
function index()
|
||||
local fs = require "nixio.fs"
|
||||
|
||||
if not fs.access("/etc/config/iperf3-server") then
|
||||
return
|
||||
end
|
||||
|
||||
local page
|
||||
|
||||
page = entry({"admin", "services", "iperf3-server"}, cbi("iperf3-server"), _("iPerf3 Server"), 99)
|
||||
page.acl_depends = { "luci-app-iperf3-server" }
|
||||
|
||||
page = entry({"admin", "services", "iperf3-server", "status"}, call("act_status"))
|
||||
page.leaf = true
|
||||
page.acl_depends = { "luci-app-iperf3-server" }
|
||||
|
||||
page = entry({"admin", "services", "iperf3-server", "start"}, call("act_start"))
|
||||
page.leaf = true
|
||||
page.acl_depends = { "luci-app-iperf3-server" }
|
||||
|
||||
page = entry({"admin", "services", "iperf3-server", "stop"}, call("act_stop"))
|
||||
page.leaf = true
|
||||
page.acl_depends = { "luci-app-iperf3-server" }
|
||||
|
||||
page = entry({"admin", "services", "iperf3-server", "restart"}, call("act_restart"))
|
||||
page.leaf = true
|
||||
page.acl_depends = { "luci-app-iperf3-server" }
|
||||
end
|
||||
|
||||
|
||||
local function json_ok(extra)
|
||||
local e = extra or {}
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json(e)
|
||||
end
|
||||
|
||||
function act_status()
|
||||
local uci = require("luci.model.uci").cursor()
|
||||
local sys = require("luci.sys")
|
||||
|
||||
local result = {
|
||||
running = false, -- 是否任意一个 server 正常运行
|
||||
servers = {} -- 每个端口的详细状态
|
||||
}
|
||||
|
||||
-- 解析 /proc/net/tcp & /proc/net/tcp6,判断端口是否 LISTEN
|
||||
local function is_listening_in_proc(path, port)
|
||||
local f = io.open(path, "r")
|
||||
if not f then return false end
|
||||
|
||||
local hex = string.format("%04X", port) -- 端口 16 进制,大写,固定 4 位
|
||||
-- /proc/net/tcp 列格式: sl local_address rem_address st ...
|
||||
-- local_address: AAAAAAAA:PPPP
|
||||
-- st = 0A 表示 LISTEN
|
||||
for line in f:lines() do
|
||||
-- 跳过表头
|
||||
if not line:match("^%s*sl%s+") then
|
||||
local st = line:match("^%s*%d+:%s+%x+:" .. hex .. "%s+%x+:%x+%s+(%x+)")
|
||||
if st == "0A" then
|
||||
f:close()
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
f:close()
|
||||
return false
|
||||
end
|
||||
|
||||
local function is_listening(port)
|
||||
-- IPv4 LISTEN
|
||||
if is_listening_in_proc("/proc/net/tcp", port) then
|
||||
return true
|
||||
end
|
||||
-- IPv6 LISTEN(有的系统会只在 tcp6 里出现)
|
||||
if is_listening_in_proc("/proc/net/tcp6", port) then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- 用 ps 判断:是否存在 iperf3 server / delay 阶段 sleep+iperf3
|
||||
local ps = sys.exec("ps w 2>/dev/null") or ""
|
||||
|
||||
local function has_iperf3_server_proc(port)
|
||||
local p = tostring(port)
|
||||
|
||||
for line in ps:gmatch("[^\r\n]+") do
|
||||
-- 只要这行同时包含:
|
||||
-- 1) iperf3
|
||||
-- 2) -s(server)
|
||||
-- 3) -p 端口(支持 "-p 5201" 或 "-p5201")
|
||||
if line:find("iperf3", 1, true) then
|
||||
local hasS = line:match("%-s") ~= nil
|
||||
local hasP = (line:match("%-p%s*" .. p .. "%f[^%d]") ~= nil) or (line:match("%-p" .. p .. "%f[^%d]") ~= nil)
|
||||
if hasS and hasP then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
local function has_delay_pending_proc(port)
|
||||
local p = tostring(port)
|
||||
for line in ps:gmatch("[^\r\n]+") do
|
||||
-- 匹配 sh -c "sleep N; exec iperf3 -s -p <port> ..."
|
||||
if (line:find("/bin/sh", 1, true) or line:find("sh -c", 1, true))
|
||||
and line:find("sleep", 1, true)
|
||||
and line:find("iperf3", 1, true)
|
||||
and (line:match("%-p%s*" .. p .. "%f[^%d]") or line:match("%-p" .. p .. "%f[^%d]"))
|
||||
then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
uci:foreach("iperf3-server", "servers", function(s)
|
||||
local port = tonumber(s.port)
|
||||
local enabled = (s.enable_server == "1")
|
||||
|
||||
if not port then
|
||||
return
|
||||
end
|
||||
|
||||
local listen = is_listening(port)
|
||||
local iperf3_proc = has_iperf3_server_proc(port)
|
||||
local delay_pending = has_delay_pending_proc(port)
|
||||
|
||||
local state, detail
|
||||
if not enabled then
|
||||
state = "disabled"
|
||||
detail = "detail_disabled"
|
||||
else
|
||||
if delay_pending and not listen then
|
||||
state = "delay"
|
||||
detail = "detail_delay"
|
||||
elseif listen and iperf3_proc then
|
||||
state = "running"
|
||||
detail = "detail_running"
|
||||
result.running = true
|
||||
elseif listen and not iperf3_proc then
|
||||
state = "conflict"
|
||||
detail = "detail_conflict"
|
||||
else
|
||||
state = "stopped"
|
||||
detail = "detail_stopped"
|
||||
end
|
||||
end
|
||||
|
||||
result.servers[#result.servers + 1] = {
|
||||
port = port,
|
||||
enable = enabled,
|
||||
listen = listen,
|
||||
state = state,
|
||||
detail = detail
|
||||
}
|
||||
end)
|
||||
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json(result)
|
||||
end
|
||||
|
||||
|
||||
|
||||
function act_start()
|
||||
local rc = luci.sys.call("/etc/init.d/iperf3-server start >/dev/null 2>&1")
|
||||
json_ok({ ok = (rc == 0) })
|
||||
end
|
||||
|
||||
function act_stop()
|
||||
local rc = luci.sys.call("/etc/init.d/iperf3-server stop >/dev/null 2>&1")
|
||||
json_ok({ ok = (rc == 0) })
|
||||
end
|
||||
|
||||
function act_restart()
|
||||
local rc = luci.sys.call("/etc/init.d/iperf3-server restart >/dev/null 2>&1")
|
||||
json_ok({ ok = (rc == 0) })
|
||||
end
|
||||
@@ -0,0 +1,95 @@
|
||||
m = Map("iperf3-server",
|
||||
translate("iPerf3 Server"),
|
||||
translate("iPerf3 - The ultimate speed test tool for TCP, UDP and SCTP")
|
||||
)
|
||||
|
||||
m:section(SimpleSection).template = "iperf3-server/iperf3-server_status"
|
||||
|
||||
-- 主配置段
|
||||
local g = m:section(TypedSection, "iperf3-server", "")
|
||||
g.addremove = false
|
||||
g.anonymous = true
|
||||
|
||||
local main_enable = g:option(Flag, "main_enable",
|
||||
translate("Enable"),
|
||||
translate("Enable iPerf3 Servers")
|
||||
)
|
||||
main_enable.default = "0"
|
||||
main_enable.rmempty = false
|
||||
|
||||
-- servers 表格
|
||||
local s = m:section(TypedSection, "servers",
|
||||
translate("Server Settings"),
|
||||
translate("Set up Multi-iPerf3 Servers")
|
||||
)
|
||||
s.anonymous = true
|
||||
s.addremove = true
|
||||
s.template = "cbi/tblsection"
|
||||
|
||||
local enable_server = s:option(Flag, "enable_server", translate("Enable"))
|
||||
enable_server.default = "1"
|
||||
enable_server.rmempty = false
|
||||
|
||||
local port = s:option(Value, "port", translate("Port"))
|
||||
port.datatype = "port"
|
||||
port.default = "5201"
|
||||
port.rmempty = true -- 关键:允许新增行先空着,避免“点添加无反应”
|
||||
|
||||
function port.validate(self, value, section)
|
||||
-- 新增行刚创建时可能是空值:先放行,让它能显示出来
|
||||
if value == nil or value == "" then
|
||||
return value
|
||||
end
|
||||
|
||||
local v = tonumber(value)
|
||||
if not v or v < 1 or v > 65535 then
|
||||
return nil, translate("Invalid port.")
|
||||
end
|
||||
|
||||
-- 端口去重:仅在有值时检查
|
||||
local dup = false
|
||||
m.uci:foreach("iperf3-server", "servers", function(s2)
|
||||
if s2[".name"] ~= section and s2.port and tonumber(s2.port) == v then
|
||||
dup = true
|
||||
end
|
||||
end)
|
||||
|
||||
if dup then
|
||||
return nil, translate("Port must be unique.")
|
||||
end
|
||||
|
||||
return tostring(v)
|
||||
end
|
||||
|
||||
local delay = s:option(Value, "delay", translate("Start delay (Seconds)"))
|
||||
delay.default = "0"
|
||||
delay.datatype = "uinteger"
|
||||
delay.rmempty = true -- 同理:新增行先允许空
|
||||
|
||||
function delay.validate(self, value, section)
|
||||
if value == nil or value == "" then
|
||||
return value
|
||||
end
|
||||
local v = tonumber(value)
|
||||
if v == nil or v < 0 or v > 3600 then
|
||||
return nil, translate("Delay must be between 0 and 3600 seconds.")
|
||||
end
|
||||
return tostring(v)
|
||||
end
|
||||
|
||||
local extra_options = s:option(Value, "extra_options", translate("Extra Options"))
|
||||
extra_options.rmempty = true
|
||||
extra_options.password = false
|
||||
|
||||
function extra_options.validate(self, value, section)
|
||||
if not value or value == "" then
|
||||
return value
|
||||
end
|
||||
-- 简单拦截 shell 元字符(安全兜底)
|
||||
if value:match("[;&|`$()<>\"']") then
|
||||
return nil, translate("Invalid characters in Extra Options.")
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
return m
|
||||
@@ -0,0 +1,181 @@
|
||||
<fieldset class="cbi-section">
|
||||
<legend><%:Status%></legend>
|
||||
|
||||
<div style="display:flex; gap:8px; align-items:center; flex-wrap:wrap;">
|
||||
<div id="iperf3_status">-</div>
|
||||
|
||||
<button class="btn cbi-button cbi-button-apply" id="btn_start" type="button"><%:Start%></button>
|
||||
<button class="btn cbi-button cbi-button-reset" id="btn_stop" type="button"><%:Stop%></button>
|
||||
<button class="btn cbi-button cbi-button-action" id="btn_restart" type="button"><%:Restart%></button>
|
||||
|
||||
<span id="iperf3_busy" style="display:none; color:#999;">...</span>
|
||||
</div>
|
||||
|
||||
<div id="iperf3_list" style="margin-top:8px;"></div>
|
||||
</fieldset>
|
||||
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
(function () {
|
||||
var token = '<%=token%>';
|
||||
|
||||
function esc(s) {
|
||||
if (s === null || s === undefined) return '';
|
||||
return String(s).replace(/[&<>"']/g, function (c) {
|
||||
return ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]);
|
||||
});
|
||||
}
|
||||
|
||||
function setBusy(b) {
|
||||
document.getElementById('iperf3_busy').style.display = b ? '' : 'none';
|
||||
document.getElementById('btn_start').disabled = b;
|
||||
document.getElementById('btn_stop').disabled = b;
|
||||
document.getElementById('btn_restart').disabled = b;
|
||||
}
|
||||
|
||||
function badge(text, color) {
|
||||
return '<span style="color:' + color + '">' + text + '</span>';
|
||||
}
|
||||
|
||||
function stateBadge(it) {
|
||||
// 新字段优先:it.state;旧字段兼容:it.running
|
||||
var st = it.state;
|
||||
|
||||
if (!st) {
|
||||
if (it.enable === false || it.enable === "0") st = "disabled";
|
||||
else st = it.running ? "running" : "stopped";
|
||||
}
|
||||
|
||||
if (st === "running") return badge('<%:Running%>', 'green');
|
||||
if (st === "delay") return badge('<%:Delay%>', '#d67f00');
|
||||
if (st === "conflict") return badge('<%:Conflict%>', 'red');
|
||||
if (st === "disabled") return badge('<%:Disabled%>', '#999');
|
||||
return badge('<%:Stopped%>', '#999');
|
||||
}
|
||||
|
||||
function overallBadge(data) {
|
||||
// controller 返回 data.running=true 表示至少一个 running
|
||||
// 如果没有 running,但存在 delay/conflict,也给更准确的总体状态
|
||||
if (data && data.running) {
|
||||
return badge('<%:Running%>', 'green');
|
||||
}
|
||||
|
||||
var hasDelay = false, hasConflict = false;
|
||||
if (data && data.servers && data.servers.length) {
|
||||
for (var i = 0; i < data.servers.length; i++) {
|
||||
var st = data.servers[i].state;
|
||||
if (st === "delay") hasDelay = true;
|
||||
else if (st === "conflict") hasConflict = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasConflict) return badge('<%:Conflict%>', 'red');
|
||||
if (hasDelay) return badge('<%:Delay%>', '#d67f00');
|
||||
return badge('<%:Not running%>', 'red');
|
||||
}
|
||||
|
||||
function translateDetail(detail) {
|
||||
// detail 来自 controller 的英文字符串,这里做映射为可翻译字符串
|
||||
// 未命中映射时,直接原样显示
|
||||
var map = {
|
||||
"disabled in config": "<%:disabled in config%>",
|
||||
"delay pending (sleep)": "<%:delay pending (sleep)%>",
|
||||
"listening and iperf3 process found": "<%:listening and iperf3 process found%>",
|
||||
"port is listening but not iperf3 (occupied)": "<%:port is listening but not iperf3 (occupied)%>",
|
||||
"not listening": "<%:not listening%>"
|
||||
};
|
||||
return map[detail] || detail || "";
|
||||
}
|
||||
|
||||
function renderStatus(data) {
|
||||
var s = document.getElementById('iperf3_status');
|
||||
s.innerHTML = overallBadge(data);
|
||||
|
||||
var list = document.getElementById('iperf3_list');
|
||||
var html = '';
|
||||
|
||||
if (data && data.servers && data.servers.length) {
|
||||
html += '<ul>';
|
||||
|
||||
for (var i = 0; i < data.servers.length; i++) {
|
||||
var it = data.servers[i];
|
||||
|
||||
var port = esc(it.port);
|
||||
var en = (it.enable !== false) && (it.enable !== "0");
|
||||
|
||||
// 旧字段 pid 兼容
|
||||
var pidInfo = '';
|
||||
if (it.pid) {
|
||||
pidInfo = ' <span style="color:#999"><%:PID%> ' + esc(it.pid) + '</span>';
|
||||
}
|
||||
|
||||
// 监听信息(新字段 listen)
|
||||
var listenInfo = '';
|
||||
if (typeof it.listen === "boolean") {
|
||||
listenInfo = it.listen
|
||||
? ' <span style="color:#999">[<%:LISTEN%>]</span>'
|
||||
: ' <span style="color:#999">[<%:NO LISTEN%>]</span>';
|
||||
}
|
||||
|
||||
// detail(来自 controller)
|
||||
var dt = translateDetail(it.detail);
|
||||
var detail = dt ? (' <span style="color:#999">(' + esc(dt) + ')</span>') : '';
|
||||
|
||||
html += '<li>'
|
||||
+ '<%:Port%> ' + port + ' : '
|
||||
+ stateBadge(it)
|
||||
+ pidInfo
|
||||
+ listenInfo
|
||||
+ detail
|
||||
+ (en ? '' : ' <span style="color:#999">(<%:disabled%>)</span>')
|
||||
+ '</li>';
|
||||
}
|
||||
|
||||
html += '</ul>';
|
||||
|
||||
// 状态说明(简短)
|
||||
html += '<div style="margin-top:6px; color:#999;">'
|
||||
+ '<span><%:Delay%></span>: <%:starting after configured delay%> '
|
||||
+ '<span><%:Conflict%></span>: <%:port is occupied%>'
|
||||
+ '</div>';
|
||||
}
|
||||
|
||||
list.innerHTML = html;
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
XHR.get('<%=luci.dispatcher.build_url("admin/services/iperf3-server/status")%>', null,
|
||||
function (x, data) { renderStatus(data || {}); }
|
||||
);
|
||||
}
|
||||
|
||||
function doAction(url, confirmText) {
|
||||
if (confirmText && !window.confirm(confirmText)) return;
|
||||
|
||||
setBusy(true);
|
||||
XHR.post(url, { token: token }, function (x, data) {
|
||||
setBusy(false);
|
||||
refresh();
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('btn_start').addEventListener('click', function () {
|
||||
doAction('<%=luci.dispatcher.build_url("admin/services/iperf3-server/start")%>', '<%:Start iPerf3 servers?%>');
|
||||
});
|
||||
document.getElementById('btn_stop').addEventListener('click', function () {
|
||||
doAction('<%=luci.dispatcher.build_url("admin/services/iperf3-server/stop")%>', '<%:Stop iPerf3 servers?%>');
|
||||
});
|
||||
document.getElementById('btn_restart').addEventListener('click', function () {
|
||||
doAction('<%=luci.dispatcher.build_url("admin/services/iperf3-server/restart")%>', '<%:Restart iPerf3 servers?%>');
|
||||
});
|
||||
|
||||
// 轮询刷新
|
||||
XHR.poll(2, '<%=luci.dispatcher.build_url("admin/services/iperf3-server/status")%>', null,
|
||||
function (x, data) { renderStatus(data || {}); }
|
||||
);
|
||||
|
||||
// 首次刷新
|
||||
refresh();
|
||||
})();
|
||||
//]]>
|
||||
</script>
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
#
|
||||
# App / Menu
|
||||
#
|
||||
msgid "iPerf3 Server"
|
||||
msgstr "iPerf3 服务器"
|
||||
|
||||
msgid "iPerf3 - The ultimate speed test tool for TCP, UDP and SCTP"
|
||||
msgstr "iPerf3 是一款集 TCP、UDP 和 SCTP 于一身的终极速度测试工具"
|
||||
|
||||
#
|
||||
# CBI (model)
|
||||
#
|
||||
msgid "Enable"
|
||||
msgstr "启用"
|
||||
|
||||
msgid "Enable iPerf3 Servers"
|
||||
msgstr "启用 iPerf3 服务器"
|
||||
|
||||
msgid "Server Settings"
|
||||
msgstr "服务端设置"
|
||||
|
||||
msgid "Set up Multi-iPerf3 Servers"
|
||||
msgstr "设置多个 iPerf3 服务端"
|
||||
|
||||
msgid "Port"
|
||||
msgstr "端口"
|
||||
|
||||
msgid "Start delay (Seconds)"
|
||||
msgstr "启动延迟(秒)"
|
||||
|
||||
msgid "Extra Options"
|
||||
msgstr "额外参数"
|
||||
|
||||
msgid "Invalid port."
|
||||
msgstr "端口无效。"
|
||||
|
||||
msgid "Port must be unique."
|
||||
msgstr "端口必须唯一,不能重复。"
|
||||
|
||||
msgid "Delay must be between 0 and 3600 seconds."
|
||||
msgstr "启动延迟必须在 0 到 3600 秒之间。"
|
||||
|
||||
msgid "Invalid characters in Extra Options."
|
||||
msgstr "额外参数包含非法字符。"
|
||||
|
||||
#
|
||||
# Status page (.htm)
|
||||
#
|
||||
msgid "Status"
|
||||
msgstr "状态"
|
||||
|
||||
msgid "Running"
|
||||
msgstr "运行中"
|
||||
|
||||
msgid "Not running"
|
||||
msgstr "未运行"
|
||||
|
||||
msgid "Stopped"
|
||||
msgstr "已停止"
|
||||
|
||||
msgid "Start"
|
||||
msgstr "启动"
|
||||
|
||||
msgid "Stop"
|
||||
msgstr "停止"
|
||||
|
||||
msgid "Restart"
|
||||
msgstr "重启"
|
||||
|
||||
msgid "Delay"
|
||||
msgstr "延迟中"
|
||||
|
||||
msgid "Conflict"
|
||||
msgstr "端口冲突"
|
||||
|
||||
msgid "Disabled"
|
||||
msgstr "已禁用"
|
||||
|
||||
msgid "disabled"
|
||||
msgstr "已禁用"
|
||||
|
||||
msgid "PID"
|
||||
msgstr "进程号"
|
||||
|
||||
msgid "LISTEN"
|
||||
msgstr "监听中"
|
||||
|
||||
msgid "NO LISTEN"
|
||||
msgstr "未监听"
|
||||
|
||||
msgid "starting after configured delay"
|
||||
msgstr "将在设定的延迟时间后启动"
|
||||
|
||||
msgid "port is occupied"
|
||||
msgstr "端口已被其他服务占用"
|
||||
|
||||
msgid "Start iPerf3 servers?"
|
||||
msgstr "确认启动 iPerf3 服务?"
|
||||
|
||||
msgid "Stop iPerf3 servers?"
|
||||
msgstr "确认停止 iPerf3 服务?"
|
||||
|
||||
msgid "Restart iPerf3 servers?"
|
||||
msgstr "确认重启 iPerf3 服务?"
|
||||
|
||||
#
|
||||
# Controller detail keys (act_status -> detail)
|
||||
# 你现在返回的是 detail_* key,前端应做 key->文本映射或直接 <%:detail_xxx%>
|
||||
#
|
||||
msgid "detail_disabled"
|
||||
msgstr "已在配置中禁用"
|
||||
|
||||
msgid "detail_delay"
|
||||
msgstr "延迟启动中"
|
||||
|
||||
msgid "detail_running"
|
||||
msgstr "正在监听,且 iPerf3 进程已启动"
|
||||
|
||||
msgid "detail_conflict"
|
||||
msgstr "端口正在监听,但不是 iPerf3(被占用)"
|
||||
|
||||
msgid "detail_stopped"
|
||||
msgstr "未监听端口"
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
#
|
||||
# App / Menu
|
||||
#
|
||||
msgid "iPerf3 Server"
|
||||
msgstr "iPerf3 服务器"
|
||||
|
||||
msgid "iPerf3 - The ultimate speed test tool for TCP, UDP and SCTP"
|
||||
msgstr "iPerf3 是一款集 TCP、UDP 和 SCTP 于一身的终极速度测试工具"
|
||||
|
||||
#
|
||||
# CBI (model)
|
||||
#
|
||||
msgid "Enable"
|
||||
msgstr "启用"
|
||||
|
||||
msgid "Enable iPerf3 Servers"
|
||||
msgstr "启用 iPerf3 服务器"
|
||||
|
||||
msgid "Server Settings"
|
||||
msgstr "服务端设置"
|
||||
|
||||
msgid "Set up Multi-iPerf3 Servers"
|
||||
msgstr "设置多个 iPerf3 服务端"
|
||||
|
||||
msgid "Port"
|
||||
msgstr "端口"
|
||||
|
||||
msgid "Start delay (Seconds)"
|
||||
msgstr "启动延迟(秒)"
|
||||
|
||||
msgid "Extra Options"
|
||||
msgstr "额外参数"
|
||||
|
||||
msgid "Invalid port."
|
||||
msgstr "端口无效。"
|
||||
|
||||
msgid "Port must be unique."
|
||||
msgstr "端口必须唯一,不能重复。"
|
||||
|
||||
msgid "Delay must be between 0 and 3600 seconds."
|
||||
msgstr "启动延迟必须在 0 到 3600 秒之间。"
|
||||
|
||||
msgid "Invalid characters in Extra Options."
|
||||
msgstr "额外参数包含非法字符。"
|
||||
|
||||
#
|
||||
# Status page (.htm)
|
||||
#
|
||||
msgid "Status"
|
||||
msgstr "状态"
|
||||
|
||||
msgid "Running"
|
||||
msgstr "运行中"
|
||||
|
||||
msgid "Not running"
|
||||
msgstr "未运行"
|
||||
|
||||
msgid "Stopped"
|
||||
msgstr "已停止"
|
||||
|
||||
msgid "Start"
|
||||
msgstr "启动"
|
||||
|
||||
msgid "Stop"
|
||||
msgstr "停止"
|
||||
|
||||
msgid "Restart"
|
||||
msgstr "重启"
|
||||
|
||||
msgid "Delay"
|
||||
msgstr "延迟中"
|
||||
|
||||
msgid "Conflict"
|
||||
msgstr "端口冲突"
|
||||
|
||||
msgid "Disabled"
|
||||
msgstr "已禁用"
|
||||
|
||||
msgid "disabled"
|
||||
msgstr "已禁用"
|
||||
|
||||
msgid "PID"
|
||||
msgstr "进程号"
|
||||
|
||||
msgid "LISTEN"
|
||||
msgstr "监听中"
|
||||
|
||||
msgid "NO LISTEN"
|
||||
msgstr "未监听"
|
||||
|
||||
msgid "starting after configured delay"
|
||||
msgstr "将在设定的延迟时间后启动"
|
||||
|
||||
msgid "port is occupied"
|
||||
msgstr "端口已被其他服务占用"
|
||||
|
||||
msgid "Start iPerf3 servers?"
|
||||
msgstr "确认启动 iPerf3 服务?"
|
||||
|
||||
msgid "Stop iPerf3 servers?"
|
||||
msgstr "确认停止 iPerf3 服务?"
|
||||
|
||||
msgid "Restart iPerf3 servers?"
|
||||
msgstr "确认重启 iPerf3 服务?"
|
||||
|
||||
#
|
||||
# Controller detail keys (act_status -> detail)
|
||||
# 你现在返回的是 detail_* key,前端应做 key->文本映射或直接 <%:detail_xxx%>
|
||||
#
|
||||
msgid "detail_disabled"
|
||||
msgstr "已在配置中禁用"
|
||||
|
||||
msgid "detail_delay"
|
||||
msgstr "延迟启动中"
|
||||
|
||||
msgid "detail_running"
|
||||
msgstr "正在监听,且 iPerf3 进程已启动"
|
||||
|
||||
msgid "detail_conflict"
|
||||
msgstr "端口正在监听,但不是 iPerf3(被占用)"
|
||||
|
||||
msgid "detail_stopped"
|
||||
msgstr "未监听端口"
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
config iperf3-server 'main'
|
||||
option main_enable '0'
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
# iPerf3 Server init (OpenWrt 23.05) - procd multi-instance (UCI-only fetch)
|
||||
# UCI: /etc/config/iperf3-server
|
||||
#
|
||||
# config iperf3-server
|
||||
# option main_enable '1'
|
||||
#
|
||||
# config servers
|
||||
# option enable_server '1'
|
||||
# option port '5201'
|
||||
# option delay '0'
|
||||
# option extra_options ''
|
||||
|
||||
USE_PROCD=1
|
||||
START=99
|
||||
|
||||
PROG="/usr/bin/iperf3"
|
||||
CONFIG="iperf3-server"
|
||||
LOGGER="logger -t iPerf3-Server"
|
||||
|
||||
# 与 CBI 的 extra_options 校验保持一致:拦截明显 shell 元字符
|
||||
is_safe_extra_opts() {
|
||||
[ -z "$1" ] && return 0
|
||||
case "$1" in
|
||||
*";"*|*"&"*|*"|"*|*"\`"*|*"\$"*|*"<"*|*">"*|*"\""*|*"'"* )
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
return 0
|
||||
}
|
||||
|
||||
# 停止某端口:既杀 iperf3 server,也杀 delay 阶段的 sh/sleep
|
||||
kill_by_port() {
|
||||
local port="$1"
|
||||
local pids
|
||||
|
||||
# 1) 已经起来的 iperf3 server
|
||||
pids="$(ps w | grep '[i]perf3' | grep ' -s' | grep " -p $port" | awk '{print $1}')"
|
||||
[ -n "$pids" ] && kill $pids 2>/dev/null
|
||||
|
||||
# 2) delay 阶段:/bin/sh -c "sleep N; exec iperf3 -s -p PORT ..."
|
||||
pids="$(ps w | grep '[s]leep' | grep '[i]perf3' | grep " -p $port" | awk '{print $1}')"
|
||||
[ -n "$pids" ] && kill $pids 2>/dev/null
|
||||
|
||||
sleep 1
|
||||
|
||||
# hard kill 兜底
|
||||
pids="$(ps w | grep '[i]perf3' | grep ' -s' | grep " -p $port" | awk '{print $1}')"
|
||||
[ -n "$pids" ] && kill -9 $pids 2>/dev/null
|
||||
|
||||
pids="$(ps w | grep '[s]leep' | grep '[i]perf3' | grep " -p $port" | awk '{print $1}')"
|
||||
[ -n "$pids" ] && kill -9 $pids 2>/dev/null
|
||||
}
|
||||
|
||||
# 获取 servers 索引列表(0 1 2 ...)
|
||||
get_server_indexes() {
|
||||
# 从 uci show 里提取 @servers[NUM]
|
||||
uci -q show "$CONFIG" 2>/dev/null \
|
||||
| sed -n "s/^$CONFIG\.@servers\[\([0-9]\+\)\]=servers$/\1/p"
|
||||
}
|
||||
|
||||
start_one_idx() {
|
||||
local idx="$1"
|
||||
|
||||
local enable_server port delay extra_options
|
||||
enable_server="$(uci -q get $CONFIG.@servers[$idx].enable_server)"
|
||||
port="$(uci -q get $CONFIG.@servers[$idx].port)"
|
||||
delay="$(uci -q get $CONFIG.@servers[$idx].delay)"
|
||||
extra_options="$(uci -q get $CONFIG.@servers[$idx].extra_options)"
|
||||
|
||||
# 默认值
|
||||
[ -n "$enable_server" ] || enable_server="1"
|
||||
[ -n "$port" ] || port="5201"
|
||||
[ -n "$delay" ] || delay="0"
|
||||
|
||||
[ "$enable_server" = "1" ] || return 0
|
||||
|
||||
# 端口合法性兜底
|
||||
case "$port" in
|
||||
*[!0-9]*|'') $LOGGER "Skip invalid port in @servers[$idx]"; return 0 ;;
|
||||
esac
|
||||
[ "$port" -ge 1 ] 2>/dev/null && [ "$port" -le 65535 ] 2>/dev/null || {
|
||||
$LOGGER "Skip invalid port [$port] in @servers[$idx]"
|
||||
return 0
|
||||
}
|
||||
|
||||
# delay 合法性兜底
|
||||
case "$delay" in
|
||||
*[!0-9]*|'') delay=0 ;;
|
||||
esac
|
||||
|
||||
# extra_options 安全兜底
|
||||
if [ -n "$extra_options" ] && ! is_safe_extra_opts "$extra_options"; then
|
||||
$LOGGER "Rejected unsafe extra_options on port [$port] (@servers[$idx])"
|
||||
extra_options=""
|
||||
fi
|
||||
|
||||
$LOGGER "Starting @servers[$idx] port=[$port] delay=[$delay] ..."
|
||||
|
||||
# procd 实例名按端口
|
||||
procd_open_instance "iperf3_${port}"
|
||||
|
||||
# 不用 -D;delay 用 sh -c 不阻塞
|
||||
if [ "$delay" -gt 0 ] 2>/dev/null; then
|
||||
if [ -n "$extra_options" ]; then
|
||||
procd_set_param command /bin/sh -c "sleep $delay; exec $PROG -s -p $port $extra_options"
|
||||
else
|
||||
procd_set_param command /bin/sh -c "sleep $delay; exec $PROG -s -p $port"
|
||||
fi
|
||||
else
|
||||
if [ -n "$extra_options" ]; then
|
||||
procd_set_param command "$PROG" -s -p "$port" $extra_options
|
||||
else
|
||||
procd_set_param command "$PROG" -s -p "$port"
|
||||
fi
|
||||
fi
|
||||
|
||||
procd_set_param respawn 3600 5 5
|
||||
procd_set_param stdout 1
|
||||
procd_set_param stderr 1
|
||||
procd_close_instance
|
||||
}
|
||||
|
||||
start_service() {
|
||||
[ -x "$PROG" ] || { $LOGGER "iperf3 not found: $PROG"; return 1; }
|
||||
|
||||
# 主开关:直接用 uci 读取匿名段
|
||||
local main_enable
|
||||
main_enable="$(uci -q get $CONFIG.@iperf3-server[0].main_enable)"
|
||||
[ -n "$main_enable" ] || main_enable="0"
|
||||
|
||||
[ "$main_enable" = "1" ] || {
|
||||
$LOGGER "iPerf3 Server is disabled ..."
|
||||
return 0
|
||||
}
|
||||
|
||||
# 遍历 servers 索引
|
||||
local idx
|
||||
for idx in $(get_server_indexes); do
|
||||
start_one_idx "$idx"
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
stop_one_idx() {
|
||||
local idx="$1"
|
||||
local enable_server port
|
||||
|
||||
enable_server="$(uci -q get $CONFIG.@servers[$idx].enable_server)"
|
||||
[ -n "$enable_server" ] || enable_server="1"
|
||||
[ "$enable_server" = "1" ] || return 0
|
||||
|
||||
port="$(uci -q get $CONFIG.@servers[$idx].port)"
|
||||
[ -n "$port" ] || port="5201"
|
||||
|
||||
case "$port" in
|
||||
*[!0-9]*|'') return 0 ;;
|
||||
esac
|
||||
|
||||
kill_by_port "$port"
|
||||
}
|
||||
|
||||
stop_service() {
|
||||
$LOGGER "Stopping iPerf3 Server ..."
|
||||
|
||||
# 按 servers 列表逐个端口清理(不误杀其它 iperf3)
|
||||
local idx
|
||||
for idx in $(get_server_indexes); do
|
||||
stop_one_idx "$idx"
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
reload_service() {
|
||||
# /etc/init.d/iperf3-server reload
|
||||
stop
|
||||
start
|
||||
}
|
||||
|
||||
service_triggers() {
|
||||
procd_add_reload_trigger "$CONFIG"
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#!/bin/sh
|
||||
[ ! -f "/usr/share/ucitrack/luci-app-iperf3-server.json" ] && {
|
||||
cat > /usr/share/ucitrack/luci-app-iperf3-server.json << EEOF
|
||||
{
|
||||
"config": "iperf3-server",
|
||||
"init": "iperf3-server"
|
||||
}
|
||||
EEOF
|
||||
}
|
||||
|
||||
# 已存在就不重复写
|
||||
if uci -q show ucitrack | grep -q "=iperf3-server"; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
uci -q batch <<'EOF' >/dev/null
|
||||
add ucitrack iperf3-server
|
||||
set ucitrack.@iperf3-server[-1].init='iperf3-server'
|
||||
commit ucitrack
|
||||
EOF
|
||||
|
||||
exit 0
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"luci-app-iperf3-server": {
|
||||
"description": "Grant access to LuCI app iPerf3 Server",
|
||||
"read": {
|
||||
"uci": [ "iperf3-server" ]
|
||||
},
|
||||
"write": {
|
||||
"uci": [ "iperf3-server" ],
|
||||
"file": {
|
||||
"/etc/init.d/iperf3-server": [ "exec" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user