fix(parental): resolve quota increase unblocking and preserve domain rules

- Unblock devices when admin increases daily_quota_minutes in parental_quota.uc
- Unblock devices removed from active quota schedules and flush sets on disable
- Ignore target != 'all' schedules in parental_quota.uc screen-time dropper
- Do not suppress domain DNS reject or nft redirect rules when quota is set
- Run parental_quota.uc tick on service start and reload for instant sync
- Add target:all dependency to daily_quota_minutes in LuCI parental view
- Add tests in content_blocking.sh and parental_control.sh
This commit is contained in:
Dushnilin
2026-09-12 21:31:03 +03:00
parent dceb04677e
commit 040b1291c6
7 changed files with 265 additions and 39 deletions
+43 -1
View File
@@ -373,5 +373,47 @@ assert_contains "$OUT_PROF" '192.168.1.180/32' "profile: SafeSearch scoped to pr
# Profile blocked domains must be present in DNS rules
assert_contains "$OUT_PROF" 'roblox.com' "profile: roblox.com blocked"
printf 'Content blocking and family profile checks passed\n'
# ─── Schedule with blocked_domains AND daily_quota_minutes must generate DNS block rules ───
cat >"$WORK_DIR/fixture_quota_domains.json" <<'JSON'
{
"settings": {
".name": "settings",
".type": "settings",
"config_path": "/tmp/sing-box/config.json",
"dns_server": "1.1.1.1",
"service_listen_address": "127.0.0.1"
},
"section": [
{
".name": "sec_proxy",
"enabled": "1",
"action": "connection",
"label": "Main proxy",
"selector_proxy_links": [ "socks5://127.0.0.1:1080#Proxy" ]
}
],
"schedule": [
{
".name": "quota_with_domains",
"enabled": "1",
"device_ip": [ "192.168.1.249" ],
"target": "domains",
"blocked_domains": [ "tiktok.com" ],
"daily_quota_minutes": "5"
}
]
}
JSON
OUT_QD="$WORK_DIR/out_quota_domains.json"
generate_config "$WORK_DIR/fixture_quota_domains.json" "$OUT_QD"
assert_contains "$OUT_QD" '"tag": "dns-block-in"' "quota+domains: dns-block-in inbound"
assert_contains "$OUT_QD" 'tiktok.com' "quota+domains: tiktok.com must be in DNS block rules"
assert_contains "$OUT_QD" '192.168.1.249/32' "quota+domains: 192.168.1.249 must be scoped"
rm -f "$NFT_LOG"
touch "$NFT_LOG"
nft_ucode nft-add-dns-block-rules-fixture "$WORK_DIR/fixture_quota_domains.json" "tachyon"
assert_contains "$NFT_LOG" "192.168.1.249" "quota+domains: nftables DNS redirect rule must be generated"
printf 'Content blocking and family profile checks passed\n'
+132 -1
View File
@@ -353,5 +353,136 @@ if grep -Eq "parental_(forward|control).*192\.168\.1\.222.*counter.*drop" "$NFT_
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'
# ─── Test: Parental Quota tick unblocks device when quota is increased ────────
PARENTAL_QUOTA="$TACHYON_LIB/service/parental_quota.uc"
QUOTA_STATE="$WORK_DIR/parental_quotas.json"
MOCK_UCI_STATE="$WORK_DIR/uci_quota_state.json"
cat >"$WORK_DIR/bin/logger" <<'EOF'
#!/usr/bin/env bash
exit 0
EOF
chmod +x "$WORK_DIR/bin/logger"
cat >"$WORK_DIR/bin/ip" <<'EOF'
#!/usr/bin/env bash
if [ "$1" = "neigh" ] && [ "$2" = "show" ]; then
echo "192.168.1.249 dev br-lan lladdr 00:11:22:33:44:55 REACHABLE"
fi
exit 0
EOF
chmod +x "$WORK_DIR/bin/ip"
# 1. State where device was previously blocked at 5 minutes
cat >"$QUOTA_STATE" <<'JSON'
{
"date": "2026-09-12",
"devices": {
"192.168.1.249": {
"minutes": 5,
"blocked": true
}
},
"guest_devices": {}
}
JSON
# Quota increased to 60 minutes in schedule (target: all)
cat >"$MOCK_UCI_STATE" <<'JSON'
{
"schedule": [
{
".name": "kids_quota",
"enabled": "1",
"device_ip": [ "192.168.1.249" ],
"target": "all",
"daily_quota_minutes": "60"
}
]
}
JSON
rm -f "$NFT_LOG"
touch "$NFT_LOG"
PARENTAL_QUOTA_STATE_FILE="$QUOTA_STATE" TACHYON_UCI_STATE_FILE="$MOCK_UCI_STATE" \
ucode -L "$TACHYON_LIB" "$PARENTAL_QUOTA" tick
# Device must now be unblocked in state
grep -Eq '"192\.168\.1\.249":\s*\{[^}]*"blocked":\s*false' "$QUOTA_STATE" || \
fail "Device 192.168.1.249 should be unblocked after daily_quota_minutes increased to 60"
# Verify nftables unblock (set flushed and device not re-added) was executed
assert_contains "$NFT_LOG" "flush set inet TachyonTable tachyon_quota_block_ip" "nft flush set for unblocked device"
if grep -q "add.*tachyon_quota_block_ip.*192\.168\.1\.249" "$NFT_LOG"; then
fail "192.168.1.249 should not be in tachyon_quota_block_ip after quota increase"
fi
# 2. Schedule with target=domains must NOT be treated as a network-wide quota in parental_quota.uc
cat >"$MOCK_UCI_STATE" <<'JSON'
{
"schedule": [
{
".name": "domain_sched",
"enabled": "1",
"device_ip": [ "192.168.1.249" ],
"target": "domains",
"blocked_domains": [ "youtube.com" ],
"daily_quota_minutes": "5"
}
]
}
JSON
# Initial clean state with 0 minutes
cat >"$QUOTA_STATE" <<'JSON'
{
"date": "2026-09-12",
"devices": {},
"guest_devices": {}
}
JSON
PARENTAL_QUOTA_STATE_FILE="$QUOTA_STATE" TACHYON_UCI_STATE_FILE="$MOCK_UCI_STATE" \
ucode -L "$TACHYON_LIB" "$PARENTAL_QUOTA" tick
# Schedule with target=domains must not have been added to devices or blocked
if grep -q '"192\.168\.1\.249"' "$QUOTA_STATE"; then
fail "Schedule with target=domains must NOT track device screen-time quota"
fi
# 3. Disabling all rules must unblock previously blocked devices and flush nft set
cat >"$QUOTA_STATE" <<'JSON'
{
"date": "2026-09-12",
"devices": {
"192.168.1.249": {
"minutes": 60,
"blocked": true
}
},
"guest_devices": {}
}
JSON
# Empty/disabled UCI rules
cat >"$MOCK_UCI_STATE" <<'JSON'
{
"schedule": []
}
JSON
rm -f "$NFT_LOG"
touch "$NFT_LOG"
PARENTAL_QUOTA_STATE_FILE="$QUOTA_STATE" TACHYON_UCI_STATE_FILE="$MOCK_UCI_STATE" \
ucode -L "$TACHYON_LIB" "$PARENTAL_QUOTA" tick
# Device must now be unblocked in state
grep -Eq '"192\.168\.1\.249":\s*\{[^}]*"blocked":\s*false' "$QUOTA_STATE" || \
fail "Device 192.168.1.249 should be unblocked when all rules are disabled/removed"
assert_contains "$NFT_LOG" "flush set inet TachyonTable tachyon_quota_block_ip" "nft flush set when rule removed"
if grep -q "add.*tachyon_quota_block_ip.*192\.168\.1\.249" "$NFT_LOG"; then
fail "192.168.1.249 should not be added to block set when rules removed"
fi
printf 'Parental control, profiles and schedule tests passed\n'