mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-31 12:35:13 +08:00
🗽 Sync 2026-09-18 20:49:54
This commit is contained in:
1 parent
31e2e4316b
commit
bdfd06d2c8
61 files changed
+19500
-59
No files matched your search
@@ -11,8 +11,8 @@ LUCI_DESCRIPTION:=Live firewall log table (nftables/fw4 and iptables LOG) with f
|
||||
LUCI_DEPENDS:=+luci-base +logd +jsonfilter
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
PKG_VERSION:=0.1.43
|
||||
PKG_RELEASE:=23
|
||||
PKG_VERSION:=0.1.44
|
||||
PKG_RELEASE:=24
|
||||
|
||||
# Reproducible build: honor SOURCE_DATE_EPOCH from the build environment (set by docker-sdk.sh / CI).
|
||||
ifdef SOURCE_DATE_EPOCH
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
*/
|
||||
return baseclass.extend({
|
||||
/* Keep in sync with openwrt-feed/luci-app-fwlive/Makefile PKG_VERSION. */
|
||||
APP_VERSION: '0.1.43',
|
||||
APP_VERSION: '0.1.44',
|
||||
ROW_LIMIT_OPTIONS: [25, 50, 100, 250, 500, 1000, 2000],
|
||||
DEFAULT_ROW_LIMIT: 100,
|
||||
FETCH_MODE_OPTIONS: ['auto', 'manual'],
|
||||
|
||||
@@ -111,6 +111,8 @@ return view.extend({
|
||||
pollCoordinator: null,
|
||||
renderScheduler: null,
|
||||
pagehideHandler: null,
|
||||
/* Terminal after a non-persisted pagehide; late startup RPCs must not paint. */
|
||||
viewDisposed: false,
|
||||
/* Layer 2 — visibility / RTT cadence / shed surfacing. */
|
||||
rttStreakKind: null,
|
||||
rttStreakCount: 0,
|
||||
@@ -615,12 +617,14 @@ return view.extend({
|
||||
async loadRulesMap() {
|
||||
try {
|
||||
const res = await callFwliveRules();
|
||||
if (this.viewDisposed) return;
|
||||
this.rulesMap = (res && res.rules) || {};
|
||||
this.firewallBackend = (res && res.backend) || 'nft';
|
||||
/* Bounds / mktemp failures are reply.error — same idea as poll. */
|
||||
this.lastRulesError = (res && res.error) || null;
|
||||
if (this.lastRulesError) console.warn('fwlive rules map error:', this.lastRulesError);
|
||||
} catch (e) {
|
||||
if (this.viewDisposed) return;
|
||||
this.rulesMap = {};
|
||||
this.firewallBackend = 'nft';
|
||||
this.lastRulesError = 'rules_unavailable';
|
||||
@@ -668,9 +672,12 @@ return view.extend({
|
||||
async loadLoggingStatus() {
|
||||
const wasWeakDevice = this.weakDevice;
|
||||
try {
|
||||
this.loggingStatus = await callFwliveLoggingStatus();
|
||||
const status = await callFwliveLoggingStatus();
|
||||
if (this.viewDisposed) return;
|
||||
this.loggingStatus = status;
|
||||
this.weakDevice = !!(this.loggingStatus && this.loggingStatus.weak_device === true);
|
||||
} catch (e) {
|
||||
if (this.viewDisposed) return;
|
||||
this.loggingStatus = null;
|
||||
}
|
||||
this.updateBackendUi();
|
||||
@@ -860,19 +867,10 @@ return view.extend({
|
||||
return { rows: normalized, pollNew: pollNew };
|
||||
},
|
||||
|
||||
async fetchEntries() {
|
||||
if (!this.sessionSeen) this.sessionSeen = new Set();
|
||||
|
||||
const epoch = this.currentPollEpoch();
|
||||
const resumeMerge = !!this.resumeMerge;
|
||||
const fetchLines = this.requestedFetchLines();
|
||||
const beforeLength = this.entries.length;
|
||||
this.lastPollRequestedLines = fetchLines;
|
||||
this.lastPollReturnedMessages = null;
|
||||
this.lastPollEffectiveLimit = null;
|
||||
/* Keep RPC timing and reply acquisition separate from view-state mutation. */
|
||||
async fetchPollReply(fetchLines) {
|
||||
const t0 = this.nowMs();
|
||||
let reply;
|
||||
let errored = false;
|
||||
try {
|
||||
/* Raw logd lines, not post-filter rows. Fetch a multiple of the
|
||||
* display limit so mixed syslog still fills the table; pause
|
||||
@@ -881,14 +879,24 @@ return view.extend({
|
||||
addresses: [String(fetchLines)]
|
||||
});
|
||||
} catch (e) {
|
||||
errored = true;
|
||||
reply = null;
|
||||
}
|
||||
return {
|
||||
reply: reply,
|
||||
rtt: this.nowMs() - t0
|
||||
};
|
||||
},
|
||||
|
||||
/* Visibility changes and disposal invalidate all application of this reply. */
|
||||
if (epoch !== this.currentPollEpoch()) return;
|
||||
|
||||
const rtt = this.nowMs() - t0;
|
||||
/* Caller must discard stale epochs before this synchronous application.
|
||||
* This updates transport/adaptive state, summary/banner UI, rows, and buffer. */
|
||||
applyPollReply(poll, context) {
|
||||
const reply = poll.reply;
|
||||
const rtt = poll.rtt;
|
||||
const resumeMerge = context.resumeMerge;
|
||||
const fetchLines = context.fetchLines;
|
||||
const beforeLength = context.beforeLength;
|
||||
this.lastPollReturnedMessages = null;
|
||||
this.lastPollEffectiveLimit = null;
|
||||
|
||||
if (!reply || typeof reply !== 'object' || Array.isArray(reply)) {
|
||||
this.lastPollError = true;
|
||||
@@ -934,7 +942,7 @@ return view.extend({
|
||||
this.lastPollEffectiveLimit = reply.effective_limit;
|
||||
}
|
||||
|
||||
this.notePollRtt(rtt, errored);
|
||||
this.notePollRtt(rtt, false);
|
||||
this.updateAdaptiveBanner();
|
||||
if (this.clientBackoffEnabled() && rtt > constants.POLL_RTT_SLOW_MS) {
|
||||
if (!this.summaryMode) this.enterSummaryMode(reply.summary);
|
||||
@@ -952,16 +960,40 @@ return view.extend({
|
||||
this.entries = buffer.applyFetchedEntries(this.entries, batch.rows, {
|
||||
/* buffer.js retains its public paused option; this is the view's table state. */
|
||||
paused: this.tablePaused,
|
||||
resumeMerge: resumeMerge,
|
||||
resumeMerge: resumeMerge || context.pausedAtStart,
|
||||
rowLimit: this.rowLimit,
|
||||
fetchLinesMax: constants.FETCH_LINES_MAX
|
||||
});
|
||||
this.updateFillingState(beforeLength, reply, fetchLines);
|
||||
/* A stale request returns above. Keep this obligation until a current
|
||||
* request has actually applied the merged batch. */
|
||||
/* Clear the merge obligation only after the current batch is applied. */
|
||||
if (resumeMerge) this.resumeMerge = false;
|
||||
},
|
||||
|
||||
async fetchEntries() {
|
||||
if (!this.sessionSeen) this.sessionSeen = new Set();
|
||||
|
||||
const epoch = this.currentPollEpoch();
|
||||
const pausedAtStart = !!this.tablePaused;
|
||||
const resumeMerge = !!this.resumeMerge;
|
||||
const fetchLines = this.requestedFetchLines();
|
||||
const beforeLength = this.entries.length;
|
||||
this.lastPollRequestedLines = fetchLines;
|
||||
this.lastPollReturnedMessages = null;
|
||||
this.lastPollEffectiveLimit = null;
|
||||
|
||||
const poll = await this.fetchPollReply(fetchLines);
|
||||
|
||||
/* Visibility changes and disposal invalidate all application of this reply. */
|
||||
if (epoch !== this.currentPollEpoch()) return;
|
||||
|
||||
this.applyPollReply(poll, {
|
||||
beforeLength: beforeLength,
|
||||
fetchLines: fetchLines,
|
||||
pausedAtStart: pausedAtStart,
|
||||
resumeMerge: resumeMerge
|
||||
});
|
||||
},
|
||||
|
||||
rememberSessionId(id) {
|
||||
if (!this.sessionSeen) this.sessionSeen = new Set();
|
||||
if (this.sessionSeen.has(id)) return false;
|
||||
@@ -1126,6 +1158,7 @@ return view.extend({
|
||||
},
|
||||
|
||||
disposeView() {
|
||||
this.viewDisposed = true;
|
||||
if (this.pollCoordinator) this.pollCoordinator.dispose();
|
||||
if (this.renderScheduler) this.renderScheduler.dispose();
|
||||
this.resolveGeneration = (this.resolveGeneration || 0) + 1;
|
||||
@@ -1952,6 +1985,9 @@ return view.extend({
|
||||
},
|
||||
|
||||
load() {
|
||||
/* A late LuCI lifecycle callback may re-enter load() after pagehide has
|
||||
* made disposal terminal; do not restore state or re-register polling. */
|
||||
if (this.viewDisposed) return Promise.resolve();
|
||||
/* RPC-affecting preferences must precede poll registration and the first
|
||||
* request; filter widgets still restore in addFooter after render. */
|
||||
this.resolveRpcPreferences();
|
||||
@@ -1966,12 +2002,15 @@ return view.extend({
|
||||
window.addEventListener('pagehide', this.pagehideHandler);
|
||||
}
|
||||
coordinator.startPolling();
|
||||
return Promise.all([this.loadRulesMap(), this.loadLoggingStatus()]).then(() =>
|
||||
this.requestPoll()
|
||||
);
|
||||
return Promise.all([this.loadRulesMap(), this.loadLoggingStatus()]).then(() => {
|
||||
if (this.viewDisposed) return;
|
||||
return this.requestPoll();
|
||||
});
|
||||
},
|
||||
|
||||
render() {
|
||||
/* LuCI may finish the load/render/addFooter sequence after pagehide. */
|
||||
if (this.viewDisposed) return E('div', { 'class': 'cbi-map' });
|
||||
return E(
|
||||
'div',
|
||||
{ 'class': 'cbi-map fwlive-map', 'data-view': 'simple', 'data-row-tint': 'classic' },
|
||||
@@ -2399,6 +2438,7 @@ return view.extend({
|
||||
},
|
||||
|
||||
addFooter() {
|
||||
if (this.viewDisposed) return;
|
||||
this.viewMode = this.readViewMode();
|
||||
this.messageLayout = this.readMessageLayout();
|
||||
this.showHostnames = this.readShowHostnames();
|
||||
|
||||
@@ -171,9 +171,9 @@ release_wan_log_lock() {
|
||||
}
|
||||
|
||||
find_wan_zone_section() {
|
||||
# Match anonymous (@zone[N]) and named (e.g. wan) sections whose name option
|
||||
# is 'wan'. Prefer the first section whose type is zone; skip
|
||||
# non-zone sections that happen to share name='wan'.
|
||||
# Match anonymous (@zone[N]) and named (e.g. wan) sections whose
|
||||
# name option is 'wan'. Prefer the first section whose type is
|
||||
# zone; skip non-zone sections that happen to share name='wan'.
|
||||
# uci missing / no wan zone is empty, not fatal. pipefail + set -e
|
||||
# cannot apply to this pipeline.
|
||||
_zones=$(uci -q show firewall 2>/dev/null \
|
||||
@@ -669,9 +669,9 @@ commit_wan_log_change() {
|
||||
# would block a concurrent toggle until the holder exits — BusyBox flock has
|
||||
# no -w timeout).
|
||||
#
|
||||
# The ROLLBACK re-acquires the lock: read->compare->
|
||||
# restore is only atomic when no other writer can commit between the read and
|
||||
# the restore. All writers hold the same flock, so re-acquiring it makes the
|
||||
# The ROLLBACK re-acquires the lock: read->compare->restore is only
|
||||
# atomic when no other writer can commit between the read and the restore.
|
||||
# All writers hold the same flock, so re-acquiring it makes the
|
||||
# decision-and-restore a serialized unit. The lock is held only for the few
|
||||
# uci commands of the restore (short critical section), never across the
|
||||
# reload. If the lock cannot be re-acquired, skip the rollback (report the
|
||||
|
||||
@@ -255,8 +255,8 @@ _fwlive_tmp_dir_ok() {
|
||||
# Fail closed unless the dump dir is a real directory with the sticky
|
||||
# bit. Without sticky, another uid can unlink a just-created mktemp
|
||||
# file and replace it with a symlink before we reopen with `>` as
|
||||
# root (same class as the WAN logging lock). Optional $1 is for CLI tests; production
|
||||
# always uses /tmp.
|
||||
# root (same class as the WAN logging lock). Optional $1 is for
|
||||
# CLI tests; production always uses /tmp.
|
||||
dir="${1:-/tmp}"
|
||||
[ -n "$dir" ] || return 1
|
||||
[ -L "$dir" ] && return 1
|
||||
@@ -544,9 +544,9 @@ is_resolvable_address() {
|
||||
[ -n "$addr" ] || return 1
|
||||
# Reject anything outside the address alphabet BEFORE awk, so embedded
|
||||
# newlines/whitespace (which awk would treat as record separators) can
|
||||
# never let a valid first record mask trailing garbage that still reaches
|
||||
# nslookup. It must see a single token. Note the check intentionally allows
|
||||
# only hex/colon/dot.
|
||||
# never let a valid first record mask trailing garbage that still
|
||||
# reaches nslookup: nslookup only ever sees a single token. Note
|
||||
# the check intentionally allows only hex/colon/dot.
|
||||
case "$addr" in
|
||||
*[!0-9a-fA-F:.]*) return 1 ;;
|
||||
esac
|
||||
@@ -632,12 +632,11 @@ resolve_addresses() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Wall-clock budget clock starts at the TRUE function entry (luna
|
||||
# folds 2026-08-10): the budget must bound the whole call — including
|
||||
# read_rpc_input (stalled stdin must not escape the budget) and json
|
||||
# parsing. `date +%s` can jump under NTP sync — accepted (worst case
|
||||
# the budget over- or under-runs by the jump; worker starvation is
|
||||
# still prevented).
|
||||
# Wall-clock budget clock starts at the TRUE function entry: the budget
|
||||
# must bound the whole call — including read_rpc_input (stalled stdin
|
||||
# must not escape the budget) and json parsing. `date +%s` can jump
|
||||
# under NTP sync — accepted (worst case the budget over- or under-runs
|
||||
# by the jump; worker starvation is still prevented).
|
||||
start=$(date +%s)
|
||||
# Optional JSON on $1 (rpcd may also pass argv $3 at the call site); else stdin.
|
||||
input=$(read_rpc_input "$1")
|
||||
@@ -751,9 +750,10 @@ run_selftest() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Hostname-shaped tokens of hex chars + dots/colons passed the
|
||||
# old char-class check and made the resolver fall back to upstream DNS. Each case
|
||||
# below must REJECT (and would be ACCEPTED by the reverted char-class code).
|
||||
# hostname-shaped tokens of hex chars + dots/colons passed the old
|
||||
# char-class check and made the resolver fall back to upstream DNS.
|
||||
# Each case below must REJECT (and would be ACCEPTED by the reverted
|
||||
# char-class code).
|
||||
if is_resolvable_address 'ab.cd.ef.01'; then
|
||||
echo 'is_resolvable_address: expected reject for dotted-hex ab.cd.ef.01' >&2
|
||||
return 1
|
||||
@@ -779,8 +779,8 @@ run_selftest() {
|
||||
echo 'is_resolvable_address: expected accept IPv6-mapped ::ffff:192.0.2.1' >&2
|
||||
return 1
|
||||
fi
|
||||
# Multi-line input must be rejected wholesale (CodeRabbit/luna fold): awk
|
||||
# reads newline-separated records, so a valid first record must not mask
|
||||
# Multi-line input must be rejected wholesale: awk reads
|
||||
# newline-separated records, so a valid first record must not mask
|
||||
# trailing garbage that would still reach nslookup.
|
||||
if is_resolvable_address "$(printf '192.0.2.1\nnot-an-address')"; then
|
||||
echo 'is_resolvable_address: expected reject for multi-line IPv4 token' >&2
|
||||
|
||||
Reference in new issue
Block a user