fix(parental): resolve daily quota from profile in nft/singbox rules and harden cron tick invocation
This commit is contained in:
@@ -363,12 +363,16 @@ function run_agent(user_args) {
|
||||
}
|
||||
|
||||
function main() {
|
||||
let command = ARGV[0] || "";
|
||||
let start_idx = 0;
|
||||
while (start_idx < length(ARGV) && trim(as_string(ARGV[start_idx])) == "")
|
||||
start_idx++;
|
||||
|
||||
let command = ARGV[start_idx] || "";
|
||||
|
||||
// agent command gets all remaining args passed verbatim
|
||||
if (command == "agent") {
|
||||
let user_args = [];
|
||||
for (let i = 1; i < length(ARGV); i++)
|
||||
for (let i = start_idx + 1; i < length(ARGV); i++)
|
||||
push(user_args, ARGV[i]);
|
||||
return run_agent(user_args);
|
||||
}
|
||||
@@ -380,7 +384,7 @@ function main() {
|
||||
}
|
||||
|
||||
let user_args = [];
|
||||
for (let i = 1; i < length(ARGV); i++)
|
||||
for (let i = start_idx + 1; i < length(ARGV); i++)
|
||||
push(user_args, ARGV[i]);
|
||||
|
||||
return run_spec(command, spec, user_args);
|
||||
|
||||
@@ -959,6 +959,25 @@ function resolve_schedule_devices(schedule, profiles) {
|
||||
return result;
|
||||
}
|
||||
|
||||
function resolve_schedule_quota_minutes(schedule, profiles) {
|
||||
let q = int(option(schedule, "daily_quota_minutes", 0));
|
||||
if (q > 0) return q;
|
||||
let prof_names = list_option(schedule, "profile");
|
||||
if (length(prof_names) == 0) {
|
||||
let single_p = option(schedule, "profile", "");
|
||||
if (single_p != "") prof_names = [ single_p ];
|
||||
}
|
||||
for (let p_name in prof_names) {
|
||||
for (let p in (profiles || [])) {
|
||||
if (as_string(object_or_empty(p)[".name"]) == p_name && bool_option(p, "enabled", true)) {
|
||||
let pq = int(option(p, "daily_quota_minutes", 0));
|
||||
if (pq > 0) return pq;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function nft_add_profile_doh_block_rules(profiles, table) {
|
||||
if (!profiles || length(profiles) == 0)
|
||||
return true;
|
||||
@@ -1055,7 +1074,7 @@ function nft_add_schedule_rules_from_schedules(schedules, sections, table, profi
|
||||
[ "meta", "hour", sprintf("\"%s\"-\"%s\"", interval[0], interval[1]) ] : null;
|
||||
let base_match = nft_schedule_rule_base_match(dev_str, is_mac, family, days_args, time_args);
|
||||
|
||||
let quota_minutes = int(option(schedule, "daily_quota_minutes", 0));
|
||||
let quota_minutes = resolve_schedule_quota_minutes(schedule, profiles);
|
||||
let has_sched_domains = length(list_option(schedule, "blocked_domains")) > 0 || option(schedule, "blocked_domains", "") != "";
|
||||
if ((target == "all" || (length(target_sec_names) == 0 && target != "sections" && target != "domains")) && !has_sched_domains && target != "domains") {
|
||||
if (always_on && quota_minutes > 0)
|
||||
@@ -1163,7 +1182,7 @@ function nft_add_dns_block_rules_from_schedules(schedules, table, profiles) {
|
||||
let intervals = nft_schedule_time_intervals(start_time, end_time);
|
||||
let days_args = nft_schedule_days_match_args(schedule);
|
||||
let always_on = length(intervals) == 0;
|
||||
let quota_minutes = int(option(schedule, "daily_quota_minutes", 0));
|
||||
let quota_minutes = resolve_schedule_quota_minutes(schedule, profiles);
|
||||
if (always_on && quota_minutes > 0)
|
||||
continue;
|
||||
|
||||
|
||||
@@ -239,6 +239,8 @@ function write_state(state) {
|
||||
}
|
||||
|
||||
function device_active(ident) {
|
||||
if (!ident || trim(as_string(ident)) == "")
|
||||
return false;
|
||||
for (let line in split(command_output_from_args([ "ip", "neigh", "show" ]), "\n")) {
|
||||
line = as_string(line);
|
||||
if (index(lc(line), ident) < 0)
|
||||
@@ -289,12 +291,20 @@ function nft_insert_quota_rule(chain, match_type) {
|
||||
return rc == 0;
|
||||
}
|
||||
|
||||
function nft_chain_exists(chain) {
|
||||
let rc = command_status(command_from_args([ "nft", "list", "chain", "inet", NFT_TABLE_NAME, chain ]) + " >/dev/null 2>&1");
|
||||
return rc == 0;
|
||||
}
|
||||
|
||||
function ensure_nft_rules() {
|
||||
// The whole table disappears on every Tachyon nft rebuild; recreate our
|
||||
// pieces on demand so ticks self-heal without touching apply.uc ordering.
|
||||
if (!nft_table_exists())
|
||||
return false;
|
||||
|
||||
if (!nft_chain_exists("parental_control") || !nft_chain_exists("parental_forward"))
|
||||
return false;
|
||||
|
||||
// Create quota sets if missing
|
||||
if (!nft_set_exists(SET_ETHER))
|
||||
nft_create_set(SET_ETHER, "ether_addr");
|
||||
@@ -343,10 +353,13 @@ function cron_line() {
|
||||
function write_crontab(lines) {
|
||||
let text = "";
|
||||
for (let i = 0; i < length(lines); i++) {
|
||||
if (as_string(lines[i]) == "" && i == length(lines) - 1)
|
||||
let l = trim(as_string(lines[i]));
|
||||
if (l == "")
|
||||
continue;
|
||||
text += as_string(lines[i]) + "\n";
|
||||
text += l + "\n";
|
||||
}
|
||||
if (text == "")
|
||||
text = "\n";
|
||||
let tmp = "/tmp/tachyon-parental-cron." + as_string(int(time()));
|
||||
if (!fs.writefile(tmp, text))
|
||||
return false;
|
||||
@@ -559,7 +572,11 @@ function reset_quotas() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let mode = ARGV[0] || "";
|
||||
let mode = "";
|
||||
for (let a in ARGV) {
|
||||
let t = trim(as_string(a));
|
||||
if (t != "") { mode = t; break; }
|
||||
}
|
||||
|
||||
if (mode == "tick")
|
||||
exit(tick());
|
||||
|
||||
@@ -945,8 +945,22 @@ function schedule_blocked_domains(schedule) {
|
||||
|
||||
function enabled_content_block_schedules() {
|
||||
let result = [];
|
||||
let profiles_by_name = {};
|
||||
ctx.uci_cursor().foreach(CONFIG_NAME, "profile", function(profile) {
|
||||
if (section_enabled(profile))
|
||||
profiles_by_name[profile[".name"]] = profile;
|
||||
});
|
||||
ctx.uci_cursor().foreach(CONFIG_NAME, "schedule", function(schedule) {
|
||||
let quota_minutes = int(option(schedule, "daily_quota_minutes", 0));
|
||||
if (quota_minutes <= 0) {
|
||||
for (let prof_ref in list_option(schedule, "profile")) {
|
||||
let p = profiles_by_name[prof_ref];
|
||||
if (p) {
|
||||
let pq = int(option(p, "daily_quota_minutes", 0));
|
||||
if (pq > 0) { quota_minutes = pq; break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
let has_interval = trim(option(schedule, "start_time", "")) != "" || trim(option(schedule, "end_time", "")) != "";
|
||||
if (section_enabled(schedule) && length(schedule_blocked_domains(schedule)) > 0) {
|
||||
if (!has_interval && quota_minutes > 0)
|
||||
|
||||
@@ -322,5 +322,36 @@ if grep -Eq "parental_(forward|control).*192\.168\.1\.181.*counter.*drop" "$NFT_
|
||||
fail "Schedule with target=domains must NOT add global counter drop in parental_forward/parental_control"
|
||||
fi
|
||||
|
||||
# ─── 24/7 Schedule referencing Profile with daily_quota_minutes must NOT drop statically
|
||||
cat >"$WORK_DIR/quota_schedule_fixture.json" <<'JSON'
|
||||
{
|
||||
"profile": [
|
||||
{
|
||||
".name": "prof_quota_only",
|
||||
"enabled": "1",
|
||||
"device_ip": [ "192.168.1.222" ],
|
||||
"daily_quota_minutes": "120"
|
||||
}
|
||||
],
|
||||
"schedule": [
|
||||
{
|
||||
".name": "quota_24_7_sched",
|
||||
"enabled": "1",
|
||||
"profile": [ "prof_quota_only" ],
|
||||
"target": "all",
|
||||
"action": "block"
|
||||
}
|
||||
]
|
||||
}
|
||||
JSON
|
||||
|
||||
rm -f "$NFT_LOG"
|
||||
touch "$NFT_LOG"
|
||||
nft_ucode nft-add-schedule-rules-fixture "$WORK_DIR/quota_schedule_fixture.json" "tachyon"
|
||||
|
||||
if grep -Eq "parental_(forward|control).*192\.168\.1\.222.*counter.*drop" "$NFT_LOG"; then
|
||||
fail "24/7 Schedule with profile daily_quota_minutes must NOT add static counter drop"
|
||||
fi
|
||||
|
||||
printf 'Parental control, profiles and schedule tests passed\n'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user