v46.1-ui-mt76: Argon theme + hardened WG tunnel + Link Health dashboard
基于 v46 正式版(mt76)只恢复 UI 层: - luci-theme-argon 2.4.3: local-background-wins 登录页 + bg1.jpg fallback - luci-app-argon-config: 去 ui.changes.apply, ACL mutator 移 write - luci-app-wgtunnel: rpcd ucode 后端(get/status/prepare/apply/rollback/reconnect), JSONMap 前端, 60s 一次性 token, 快照回滚, 无全局 network ACL - luci-app-tr3000-status: 只读 rpcd ucode + 5s 轮询仪表盘 - tools/: audit_ui_packages.py + install_preview.sh + rollback_watchdog.sh - docs/: 开发经历与翻车记录 + 固件哈希记录 固件本体(含烤入 WG 私钥/PSK)不入 git, 仅 K 盘保存. kernel 成员与 v46 byte-identical; 尚未刷机.
This commit is contained in:
+13
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"admin/status/tr3000": {
|
||||
"title": "Link Health",
|
||||
"order": 25,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "status/tr3000"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [ "luci-app-tr3000-status" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"luci-app-tr3000-status": {
|
||||
"description": "Read sanitized TR3000 Link Health status",
|
||||
"read": {
|
||||
"ubus": {
|
||||
"luci.tr3000_status": [ "get" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+565
@@ -0,0 +1,565 @@
|
||||
#!/usr/bin/ucode
|
||||
/* SPDX-License-Identifier: GPL-3.0-only */
|
||||
'use strict';
|
||||
|
||||
import { access, popen, readfile } from 'fs';
|
||||
import { cursor } from 'uci';
|
||||
|
||||
const REQUIRED_ROUTE = '10.99.0.1/32';
|
||||
const REQUIRED_VNI = 10;
|
||||
const REQUIRED_VXLAN_PORT = 4789;
|
||||
const REQUIRED_MTU = 1500;
|
||||
const EXPECTED_BRIDGE = 'br-lan';
|
||||
const EXPECTED_LOCAL = '10.99.0.2';
|
||||
const EXPECTED_REMOTE = '10.99.0.1';
|
||||
const SAFE_IDENT_RE = /^[A-Za-z0-9_.-]{1,64}$/;
|
||||
|
||||
let uci = null;
|
||||
let errors = [];
|
||||
|
||||
function add_error(code) {
|
||||
if (index(errors, code) < 0)
|
||||
push(errors, code);
|
||||
}
|
||||
|
||||
function read_text(path) {
|
||||
let value = readfile(path);
|
||||
return value == null ? null : trim(value);
|
||||
}
|
||||
|
||||
function read_first(paths) {
|
||||
for (let path in paths) {
|
||||
let value = read_text(path);
|
||||
if (value != null)
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function bool_value(value) {
|
||||
if (value == null)
|
||||
return false;
|
||||
if (type(value) == 'array')
|
||||
value = value[0];
|
||||
value = lc(`${value}`);
|
||||
return value == '1' || value == 'y' || value == 'yes' || value == 'true' || value == 'on' || value == 'enabled';
|
||||
}
|
||||
|
||||
function contains_value(value, needle) {
|
||||
if (type(value) == 'array')
|
||||
return index(value, needle) >= 0;
|
||||
if (value == null)
|
||||
return false;
|
||||
return index(split(`${value}`, /\s+/), needle) >= 0;
|
||||
}
|
||||
|
||||
function run_capture(command) {
|
||||
let fd = popen(command, 'r');
|
||||
if (!fd)
|
||||
return { ok: false, status: -1, out: '' };
|
||||
let out = fd.read('all') || '';
|
||||
let status = fd.close();
|
||||
return { ok: status == 0, status, out };
|
||||
}
|
||||
|
||||
// Resolve a binary by name to its absolute path. ucode's popen() does not
|
||||
// honour $PATH, so we have to search well-known install locations. The
|
||||
// result is memoized so each command is stat'd at most once per process.
|
||||
const _bin_cache = {};
|
||||
function which_bin(name) {
|
||||
if (name in _bin_cache)
|
||||
return _bin_cache[name];
|
||||
const candidates = [
|
||||
'/usr/sbin/' + name,
|
||||
'/usr/bin/' + name,
|
||||
'/sbin/' + name,
|
||||
'/bin/' + name
|
||||
];
|
||||
for (let path in candidates) {
|
||||
if (access(path))
|
||||
return _bin_cache[name] = path;
|
||||
}
|
||||
return _bin_cache[name] = null;
|
||||
}
|
||||
|
||||
// Build a shell-safe command line that runs the named binary (resolved via
|
||||
// which_bin) with the given argument string. Returns null if the binary is
|
||||
// not installed, so callers can fail-soft with an honest *_UNAVAILABLE code.
|
||||
function bin_cmd(name, args) {
|
||||
const path = which_bin(name);
|
||||
return path == null ? null : `${path} ${args}`;
|
||||
}
|
||||
|
||||
function nft_rules(value) {
|
||||
let rules = [];
|
||||
if (type(value?.nftables) != 'array')
|
||||
return rules;
|
||||
for (let entry in value.nftables)
|
||||
if (entry?.rule != null)
|
||||
push(rules, entry.rule);
|
||||
return rules;
|
||||
}
|
||||
|
||||
function nft_match_meta(expr, key, value) {
|
||||
return expr?.match?.left?.meta?.key == key && expr?.match?.right == value;
|
||||
}
|
||||
|
||||
function nft_mangles_tcp_mss(expr, value) {
|
||||
let key = expr?.mangle?.key?.['tcp option'];
|
||||
return key?.name == 'maxseg' && key?.field == 'size' && expr?.mangle?.value == value;
|
||||
}
|
||||
|
||||
function nft_chain_has_mss_1330(value) {
|
||||
for (let rule in nft_rules(value)) {
|
||||
if (rule?.comment != 'tr3000-vxlan-mss-1330' || type(rule?.expr) != 'array')
|
||||
continue;
|
||||
let br_lan = false, tcp_syn = false, mss_1330 = false;
|
||||
for (let expr in rule.expr) {
|
||||
if (nft_match_meta(expr, 'iifname', 'br-lan'))
|
||||
br_lan = true;
|
||||
if (expr?.match?.left?.payload?.protocol == 'tcp' && expr?.match?.left?.payload?.field == 'flags' && expr?.match?.right == 'syn')
|
||||
tcp_syn = true;
|
||||
if (nft_mangles_tcp_mss(expr, 1330))
|
||||
mss_1330 = true;
|
||||
}
|
||||
if (br_lan && tcp_syn && mss_1330)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function nft_table_has_flowtable(value) {
|
||||
if (type(value?.nftables) != 'array')
|
||||
return false;
|
||||
for (let entry in value.nftables)
|
||||
if (entry?.flowtable != null)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function nft_table_has_statement(value, name) {
|
||||
for (let rule in nft_rules(value)) {
|
||||
if (type(rule?.expr) != 'array')
|
||||
continue;
|
||||
for (let expr in rule.expr)
|
||||
if (expr?.[name] != null)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function link_state(link) {
|
||||
if (link == null)
|
||||
return { up: null, available: false };
|
||||
let up = link?.operstate == 'UP' || contains_value(link?.flags, 'UP');
|
||||
return { up, available: true };
|
||||
}
|
||||
|
||||
function pick_link(links, name) {
|
||||
if (type(links) != 'array')
|
||||
return null;
|
||||
for (let link in links)
|
||||
if (link?.ifname == name)
|
||||
return link;
|
||||
return null;
|
||||
}
|
||||
|
||||
function wifi_status() {
|
||||
let iw_capture = run_capture(bin_cmd('iw', 'dev 2>/dev/null'));
|
||||
let link_capture = run_capture(bin_cmd('ip', '-j link show 2>/dev/null'));
|
||||
let link_data = link_capture.ok ? json(link_capture.out) : null;
|
||||
if (!link_capture.ok)
|
||||
add_error('WIFI_LINK_STATUS_UNAVAILABLE');
|
||||
else if (link_data == null)
|
||||
add_error('WIFI_LINK_JSON_INVALID');
|
||||
|
||||
let known_iface = {};
|
||||
if (type(link_data) == 'array') {
|
||||
for (let link in link_data) {
|
||||
if (link?.ifname == null)
|
||||
continue;
|
||||
known_iface[link.ifname] = link;
|
||||
}
|
||||
}
|
||||
|
||||
let result = {
|
||||
available: iw_capture.ok,
|
||||
link_data_available: link_capture.ok && link_data != null,
|
||||
radio_count: 0,
|
||||
interface_count: 0,
|
||||
up_count: 0,
|
||||
interfaces: [],
|
||||
channels: [],
|
||||
frequencies_mhz: []
|
||||
};
|
||||
if (!iw_capture.ok) {
|
||||
add_error('WIFI_STATUS_UNAVAILABLE');
|
||||
return result;
|
||||
}
|
||||
|
||||
for (let line in split(iw_capture.out, /\n/)) {
|
||||
let phy = match(line, /^phy#([0-9]+)/);
|
||||
if (phy) {
|
||||
result.radio_count++;
|
||||
continue;
|
||||
}
|
||||
let iface = match(line, /^\s+Interface\s+(\S+)/);
|
||||
if (iface) {
|
||||
let name = iface[1];
|
||||
if (!match(name, SAFE_IDENT_RE))
|
||||
continue;
|
||||
result.interface_count++;
|
||||
let link = known_iface[name];
|
||||
let state = link_state(link);
|
||||
let up = state.up;
|
||||
if (up)
|
||||
result.up_count++;
|
||||
push(result.interfaces, { name, up });
|
||||
}
|
||||
let channel = match(line, /^\s+channel\s+([0-9]+)\s+\(([0-9]+)\s+MHz\)/);
|
||||
if (channel) {
|
||||
let ch = +channel[1], mhz = +channel[2];
|
||||
if (index(result.channels, ch) < 0)
|
||||
push(result.channels, ch);
|
||||
if (index(result.frequencies_mhz, mhz) < 0)
|
||||
push(result.frequencies_mhz, mhz);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function wireguard_status(now) {
|
||||
let hs_capture = run_capture('/usr/bin/wg show wg0 latest-handshakes 2>/dev/null');
|
||||
let tx_capture = run_capture('/usr/bin/wg show wg0 transfer 2>/dev/null');
|
||||
let link_capture = run_capture(bin_cmd('ip', '-j link show 2>/dev/null'));
|
||||
let link_data = link_capture.ok ? json(link_capture.out) : null;
|
||||
if (!link_capture.ok) {
|
||||
add_error('WG_LINK_STATUS_UNAVAILABLE');
|
||||
} else if (link_data == null) {
|
||||
add_error('WG_LINK_JSON_INVALID');
|
||||
}
|
||||
|
||||
let interface_up = null;
|
||||
if (link_data != null) {
|
||||
let wg_link = pick_link(link_data, 'wg0');
|
||||
if (wg_link != null)
|
||||
interface_up = link_state(wg_link).up;
|
||||
}
|
||||
|
||||
let result = {
|
||||
available: hs_capture.ok && tx_capture.ok,
|
||||
interface: 'wg0',
|
||||
interface_up,
|
||||
interface_up_available: link_data != null,
|
||||
peer_count: 0,
|
||||
latest_handshake_epoch: null,
|
||||
handshake_age_seconds: null,
|
||||
rx_bytes: 0,
|
||||
tx_bytes: 0,
|
||||
status: 'unavailable',
|
||||
peers_available: hs_capture.ok
|
||||
};
|
||||
if (!hs_capture.ok) {
|
||||
add_error('WG_STATUS_UNAVAILABLE');
|
||||
return result;
|
||||
}
|
||||
if (!tx_capture.ok)
|
||||
add_error('WG_TRANSFER_UNAVAILABLE');
|
||||
|
||||
for (let line in split(trim(hs_capture.out), /\n/)) {
|
||||
let fields = split(line, /\t/);
|
||||
if (length(fields) < 2)
|
||||
continue;
|
||||
result.peer_count++;
|
||||
let handshake = +fields[1];
|
||||
if (handshake > (result.latest_handshake_epoch || 0))
|
||||
result.latest_handshake_epoch = handshake;
|
||||
}
|
||||
|
||||
if (tx_capture.ok) {
|
||||
for (let line in split(trim(tx_capture.out), /\n/)) {
|
||||
let fields = split(line, /\t/);
|
||||
if (length(fields) < 3)
|
||||
continue;
|
||||
result.rx_bytes += +fields[1];
|
||||
result.tx_bytes += +fields[2];
|
||||
}
|
||||
}
|
||||
|
||||
if (result.latest_handshake_epoch > 0)
|
||||
result.handshake_age_seconds = max(0, now - result.latest_handshake_epoch);
|
||||
if (!tx_capture.ok)
|
||||
result.rx_bytes = null, result.tx_bytes = null;
|
||||
result.status = result.peer_count == 0 ? 'no_peer' : (result.handshake_age_seconds == null ? 'never_handshaken' : (result.handshake_age_seconds <= 180 ? 'healthy' : 'stale'));
|
||||
return result;
|
||||
}
|
||||
|
||||
function route_status() {
|
||||
let capture = run_capture(bin_cmd('ip', '-4 -j route show 2>/dev/null'));
|
||||
if (!capture.ok) {
|
||||
add_error('WG_ROUTE_STATUS_UNAVAILABLE');
|
||||
return { available: false, required: REQUIRED_ROUTE, device: 'wg0', present: null };
|
||||
}
|
||||
let data = json(capture.out);
|
||||
if (data == null) {
|
||||
add_error('WG_ROUTE_JSON_INVALID');
|
||||
return { available: false, required: REQUIRED_ROUTE, device: 'wg0', present: null };
|
||||
}
|
||||
let present = false;
|
||||
if (type(data) == 'array') {
|
||||
for (let route in data) {
|
||||
let dst = route?.dst || '';
|
||||
// iproute2 emits a WG scope-link host route as "10.99.0.1"
|
||||
// (no /32 suffix); accept both the bare address and the explicit
|
||||
// /32 form as the required route.
|
||||
if (route?.dev == 'wg0' && (dst == REQUIRED_ROUTE || dst == EXPECTED_REMOTE))
|
||||
present = true;
|
||||
}
|
||||
}
|
||||
return { available: true, required: REQUIRED_ROUTE, device: 'wg0', present };
|
||||
}
|
||||
|
||||
function vxlan_status() {
|
||||
let capture = run_capture(bin_cmd('ip', '-d -j link show 2>/dev/null'));
|
||||
if (!capture.ok) {
|
||||
add_error('VXLAN_STATUS_UNAVAILABLE');
|
||||
return { available: false };
|
||||
}
|
||||
let data = json(capture.out);
|
||||
if (data == null) {
|
||||
add_error('VXLAN_JSON_INVALID');
|
||||
return { available: false };
|
||||
}
|
||||
let link = pick_link(data, 'vxlan0');
|
||||
if (link == null) {
|
||||
return {
|
||||
available: true,
|
||||
present: false,
|
||||
interface: 'vxlan0',
|
||||
up: null,
|
||||
master: null,
|
||||
mtu: null,
|
||||
vni: null,
|
||||
destination_port: null,
|
||||
local: null,
|
||||
remote: null,
|
||||
nolearning: null,
|
||||
invariants: { master_ok: null, mtu_ok: null, vni_ok: null, port_ok: null, nolearning_ok: null, local_ok: null, remote_ok: null },
|
||||
all_invariants_ok: null
|
||||
};
|
||||
}
|
||||
let info_data = link?.linkinfo?.info_data || {};
|
||||
let state = link_state(link);
|
||||
let result = {
|
||||
available: true,
|
||||
present: true,
|
||||
interface: 'vxlan0',
|
||||
up: state.up,
|
||||
master: link?.master || null,
|
||||
mtu: link?.mtu == null ? null : +link.mtu,
|
||||
vni: info_data?.id == null ? null : +info_data.id,
|
||||
destination_port: info_data?.dstport == null ? REQUIRED_VXLAN_PORT : +info_data.dstport,
|
||||
local: info_data?.local || null,
|
||||
remote: info_data?.remote || null,
|
||||
nolearning: info_data?.learning == null ? null : !info_data.learning
|
||||
};
|
||||
let i = {};
|
||||
i.master_ok = result.master == EXPECTED_BRIDGE;
|
||||
i.mtu_ok = result.mtu == REQUIRED_MTU;
|
||||
i.vni_ok = result.vni == REQUIRED_VNI;
|
||||
i.port_ok = result.destination_port == REQUIRED_VXLAN_PORT;
|
||||
i.nolearning_ok = result.nolearning === true;
|
||||
i.local_ok = result.local == EXPECTED_LOCAL;
|
||||
i.remote_ok = result.remote == EXPECTED_REMOTE;
|
||||
result.invariants = i;
|
||||
result.all_invariants_ok = result.up === true && i.master_ok && i.mtu_ok && i.vni_ok && i.port_ok && i.nolearning_ok && i.local_ok && i.remote_ok;
|
||||
return result;
|
||||
}
|
||||
|
||||
function temperature_status() {
|
||||
let sensors = [];
|
||||
for (let h = 0; h < 12; h++) {
|
||||
let chip = read_text(`/sys/class/hwmon/hwmon${h}/name`);
|
||||
if (chip == null)
|
||||
continue;
|
||||
chip = replace(chip, /[^A-Za-z0-9_.-]/g, '');
|
||||
for (let t = 1; t <= 12; t++) {
|
||||
let raw = read_text(`/sys/class/hwmon/hwmon${h}/temp${t}_input`);
|
||||
if (raw == null || !match(raw, /^-?[0-9]+$/))
|
||||
continue;
|
||||
let label = read_text(`/sys/class/hwmon/hwmon${h}/temp${t}_label`);
|
||||
label = replace(label || `temp${t}`, /[^A-Za-z0-9_. -]/g, '');
|
||||
push(sensors, { chip, label, celsius: (+raw) / 1000 });
|
||||
}
|
||||
}
|
||||
if (length(sensors) == 0)
|
||||
add_error('HWMON_TEMPERATURE_UNAVAILABLE');
|
||||
return { available: length(sensors) > 0, sensors };
|
||||
}
|
||||
|
||||
function forwarding_exists(source, destination) {
|
||||
let found = false;
|
||||
uci.foreach('firewall', 'forwarding', function(section) {
|
||||
if (section?.src == source && section?.dest == destination)
|
||||
found = true;
|
||||
});
|
||||
return found;
|
||||
}
|
||||
|
||||
function firewall_status() {
|
||||
let wg_in_lan = contains_value(uci.get('firewall', 'lan', 'network'), 'wg0');
|
||||
let lan_to_wan_absent = !forwarding_exists('lan', 'wan');
|
||||
let wan_masq_disabled = !bool_value(uci.get('firewall', 'wan', 'masq'));
|
||||
let dhcp_ignored = bool_value(uci.get('dhcp', 'lan', 'ignore'));
|
||||
let ra_disabled = uci.get('dhcp', 'lan', 'ra') == 'disabled';
|
||||
let dhcpv6_disabled = uci.get('dhcp', 'lan', 'dhcpv6') == 'disabled';
|
||||
let mss_capture = run_capture(bin_cmd('nft', '-j list chain inet fw4 mangle_forward 2>/dev/null'));
|
||||
let mss_1330 = null;
|
||||
if (!mss_capture.ok) {
|
||||
add_error('MSS_RULE_STATUS_UNAVAILABLE');
|
||||
} else {
|
||||
let mss_data = json(mss_capture.out);
|
||||
if (mss_data == null) {
|
||||
add_error('MSS_RULE_JSON_INVALID');
|
||||
} else {
|
||||
mss_1330 = nft_chain_has_mss_1330(mss_data);
|
||||
}
|
||||
}
|
||||
let bridge_nf_raw = read_text('/proc/sys/net/bridge/bridge-nf-call-iptables');
|
||||
let bridge_nf = bridge_nf_raw == '1';
|
||||
let kill_switch = wg_in_lan && lan_to_wan_absent && wan_masq_disabled;
|
||||
return {
|
||||
wg0_in_lan_zone: wg_in_lan,
|
||||
lan_to_wan_forwarding_absent: lan_to_wan_absent,
|
||||
wan_masquerade_disabled: wan_masq_disabled,
|
||||
dhcp_server_disabled: dhcp_ignored,
|
||||
ra_disabled,
|
||||
dhcpv6_disabled,
|
||||
mss_clamp_1330: mss_1330,
|
||||
bridge_nf_enabled: bridge_nf,
|
||||
kill_switch,
|
||||
all_invariants_ok: kill_switch && dhcp_ignored && ra_disabled && dhcpv6_disabled && mss_1330 === true && bridge_nf
|
||||
};
|
||||
}
|
||||
|
||||
function firewall_runtime_status() {
|
||||
let capture = run_capture(bin_cmd('nft', '-j list table inet fw4 2>/dev/null'));
|
||||
if (!capture.ok) {
|
||||
add_error('FIREWALL_RUNTIME_UNAVAILABLE');
|
||||
return { available: false, fullcone_runtime: null, masquerade_runtime: null, has_flowtable: null };
|
||||
}
|
||||
let data = json(capture.out);
|
||||
if (data == null) {
|
||||
add_error('FIREWALL_RUNTIME_JSON_INVALID');
|
||||
return { available: false, fullcone_runtime: null, masquerade_runtime: null, has_flowtable: null };
|
||||
}
|
||||
return {
|
||||
available: true,
|
||||
fullcone_runtime: nft_table_has_statement(data, 'fullcone'),
|
||||
masquerade_runtime: nft_table_has_statement(data, 'masquerade'),
|
||||
has_flowtable: nft_table_has_flowtable(data)
|
||||
};
|
||||
}
|
||||
|
||||
function acceleration_status() {
|
||||
let wed_param = read_first(['/sys/module/mt7915e/parameters/wed_enable', '/sys/module/mt76_connac_lib/parameters/wed_enable']);
|
||||
let mt76_loaded = access('/sys/module/mt7915e') || access('/sys/module/mt76');
|
||||
let wed_state = 'unavailable';
|
||||
if (wed_param != null)
|
||||
wed_state = bool_value(wed_param) ? 'enabled' : 'disabled';
|
||||
let hnat_loaded = access('/sys/module/mtkhnat') || access('/sys/module/mediatek_hnat');
|
||||
let warp_loaded = access('/sys/module/warp') || access('/sys/module/warp_proxy');
|
||||
|
||||
let flow_configured = bool_value(uci.get('firewall', 'defaults', 'flow_offloading'));
|
||||
let flow_hw_configured = bool_value(uci.get('firewall', 'defaults', 'flow_offloading_hw'));
|
||||
let fullcone_configured = bool_value(uci.get('firewall', 'defaults', 'fullcone')) || bool_value(uci.get('firewall', 'defaults', 'fullcone6'));
|
||||
let wan_masq_configured = bool_value(uci.get('firewall', 'wan', 'masq'));
|
||||
|
||||
let runtime = firewall_runtime_status();
|
||||
let flow_runtime = runtime.has_flowtable;
|
||||
let fullcone_runtime = runtime.fullcone_runtime;
|
||||
let masquerade_runtime = runtime.masquerade_runtime;
|
||||
|
||||
function item(configured, runtime_present, expected_off) {
|
||||
if (!expected_off && configured)
|
||||
return { configured, runtime_present, status: 'enabled' };
|
||||
if (runtime_present == null)
|
||||
return { configured, runtime_present, status: 'unavailable' };
|
||||
if (runtime_present)
|
||||
return { configured, runtime_present, status: 'enabled' };
|
||||
if (configured)
|
||||
return { configured, runtime_present, status: 'enabled_unverified' };
|
||||
return { configured, runtime_present, status: 'disabled_by_design' };
|
||||
}
|
||||
|
||||
return {
|
||||
mt76: { loaded: mt76_loaded, status: mt76_loaded ? 'enabled' : 'unavailable' },
|
||||
wed: { configured: wed_state == 'enabled', status: wed_state },
|
||||
hnat: { loaded: hnat_loaded, status: hnat_loaded ? 'enabled' : 'disabled_by_design' },
|
||||
warp: { loaded: warp_loaded, status: warp_loaded ? 'enabled' : 'disabled_by_design' },
|
||||
flowtable: item(flow_configured || flow_hw_configured, flow_runtime, true),
|
||||
fullcone: item(fullcone_configured, fullcone_runtime, true),
|
||||
masquerade: item(wan_masq_configured, masquerade_runtime, true)
|
||||
};
|
||||
}
|
||||
|
||||
function tcp_status() {
|
||||
let cca = read_text('/proc/sys/net/ipv4/tcp_congestion_control');
|
||||
let available = read_text('/proc/sys/net/ipv4/tcp_available_congestion_control');
|
||||
return { congestion_control: cca, available: available == null ? [] : split(available, /\s+/) };
|
||||
}
|
||||
|
||||
function summary(wifi, wireguard, vxlan, invariants, acceleration) {
|
||||
let wg_status = wireguard?.status || 'unavailable';
|
||||
let link_ok = wireguard?.interface_up !== false;
|
||||
let route_ok = wireguard?.required_route?.present === true;
|
||||
let vxlan_ok = vxlan?.all_invariants_ok === true;
|
||||
let invariant_ok = invariants?.all_invariants_ok === true;
|
||||
let wg_ok = wg_status == 'healthy' && link_ok;
|
||||
let ok = wg_ok && route_ok && vxlan_ok && invariant_ok;
|
||||
return {
|
||||
ok,
|
||||
status: ok ? 'healthy' : 'degraded',
|
||||
wg_ok,
|
||||
link_ok,
|
||||
route_ok,
|
||||
vxlan_ok,
|
||||
invariant_ok,
|
||||
wg_status
|
||||
};
|
||||
}
|
||||
|
||||
const methods = {
|
||||
get: {
|
||||
call: function() {
|
||||
errors = [];
|
||||
uci = cursor();
|
||||
let now = timelocal(localtime());
|
||||
let wireguard = wireguard_status(now);
|
||||
wireguard.required_route = route_status();
|
||||
let wifi = wifi_status();
|
||||
let vxlan = vxlan_status();
|
||||
let temperatures = temperature_status();
|
||||
let invariants = firewall_status();
|
||||
let acceleration = acceleration_status();
|
||||
let tcp = tcp_status();
|
||||
let summary_state = summary(wifi, wireguard, vxlan, invariants, acceleration);
|
||||
return {
|
||||
schema_version: 2,
|
||||
generated_at: now,
|
||||
partial: length(errors) > 0,
|
||||
errors,
|
||||
wifi,
|
||||
wireguard,
|
||||
vxlan,
|
||||
temperatures,
|
||||
invariants,
|
||||
acceleration,
|
||||
tcp,
|
||||
summary: summary_state
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return { 'luci.tr3000_status': methods };
|
||||
Reference in New Issue
Block a user