feat: локализация исключений GeoIP по секциям и мониторинг памяти DNS-кэша dnsmasq
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
TACHYON_LIB="$ROOT_DIR/tachyon/files/usr/lib"
|
||||
GENERATOR_UC="$TACHYON_LIB/singbox/generator.uc"
|
||||
WORK_DIR="$(mktemp -d)"
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$WORK_DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
fail() {
|
||||
printf 'FAIL: %s\n' "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cat >"$WORK_DIR/fixture.json" <<'JSON'
|
||||
{
|
||||
"settings": {
|
||||
".name": "settings",
|
||||
".type": "settings",
|
||||
"enabled": "1",
|
||||
"dns_type": "udp",
|
||||
"dns_server": "1.1.1.1",
|
||||
"service_listen_address": "127.0.0.1"
|
||||
},
|
||||
"section": [
|
||||
{
|
||||
".name": "sec_yt",
|
||||
".type": "section",
|
||||
"enabled": "1",
|
||||
"action": "connection",
|
||||
"outbound_jsons": [ "{\"type\":\"direct\",\"tag\":\"sec_yt-out\"}" ],
|
||||
"domain_suffix": [ "youtube.com" ],
|
||||
"geoip_country": [ "ru" ],
|
||||
"geoip_mode": "exclude"
|
||||
},
|
||||
{
|
||||
".name": "sec_all_noru",
|
||||
".type": "section",
|
||||
"enabled": "1",
|
||||
"action": "connection",
|
||||
"outbound_jsons": [ "{\"type\":\"direct\",\"tag\":\"sec_noru-out\"}" ],
|
||||
"match_all": "1",
|
||||
"geoip_country": [ "ru" ],
|
||||
"geoip_mode": "exclude"
|
||||
},
|
||||
{
|
||||
".name": "sec_pure_ru",
|
||||
".type": "section",
|
||||
"enabled": "1",
|
||||
"action": "connection",
|
||||
"outbound_jsons": [ "{\"type\":\"direct\",\"tag\":\"sec_ru-out\"}" ],
|
||||
"geoip_country": [ "ru" ],
|
||||
"geoip_mode": "include"
|
||||
},
|
||||
{
|
||||
".name": "sec_sub",
|
||||
".type": "section",
|
||||
"enabled": "1",
|
||||
"action": "connection",
|
||||
"outbound_jsons": [ "{\"type\":\"direct\",\"tag\":\"sec_sub-out\"}" ],
|
||||
"domain_suffix": [ "other.com" ]
|
||||
},
|
||||
{
|
||||
".name": "sec_both",
|
||||
".type": "section",
|
||||
"enabled": "1",
|
||||
"action": "connection",
|
||||
"outbound_jsons": [ "{\"type\":\"direct\",\"tag\":\"sec_both-out\"}" ],
|
||||
"domain_suffix": [ "twitch.tv" ],
|
||||
"geoip_country": [ "ru" ],
|
||||
"geoip_mode": "exclude",
|
||||
"excluded_ips": [ "192.168.1.99" ]
|
||||
}
|
||||
]
|
||||
}
|
||||
JSON
|
||||
|
||||
output="$WORK_DIR/out.json"
|
||||
mkdir -p "$output.section-cache" "$output.rulesets"
|
||||
ucode -L "$TACHYON_LIB" "$GENERATOR_UC" generate-config-fixture \
|
||||
"$WORK_DIR/fixture.json" "$output" "127.0.0.1" "0" "1"
|
||||
|
||||
# Verify route rules in out.json
|
||||
ucode -e '
|
||||
let fs = require("fs");
|
||||
let raw = fs.readfile("'"$output"'");
|
||||
let cfg = json(raw);
|
||||
let rules = cfg.route.rules || [];
|
||||
|
||||
let sec_yt_logical_found = false;
|
||||
let sec_yt_unscoped_found = false;
|
||||
let sec_noru_rule_count = 0;
|
||||
let sec_noru_inverted_found = false;
|
||||
let sec_noru_unconstrained_all = false;
|
||||
let sec_pure_ru_found = false;
|
||||
let sec_sub_found = false;
|
||||
let sec_both_found = false;
|
||||
|
||||
for (let r in rules) {
|
||||
if (r.domain == "ip.podkop.fyi" || (type(r.domain) == "array" && index(r.domain, "ip.podkop.fyi") >= 0))
|
||||
continue;
|
||||
|
||||
// 1. Check sec_yt: Should be a logical rule combining domain_suffix and inverted geoip/geosite rule_set
|
||||
if (r.outbound == "sec_yt-out") {
|
||||
if (r.type == "logical" && r.mode == "and" && type(r.rules) == "array") {
|
||||
let has_domain = false;
|
||||
let has_inverted_geo = false;
|
||||
for (let sub in r.rules) {
|
||||
if (sub.domain_suffix && index(sub.domain_suffix, "youtube.com") >= 0)
|
||||
has_domain = true;
|
||||
if (sub.invert == true && sub.rule_set) {
|
||||
let rs = type(sub.rule_set) == "array" ? sub.rule_set : [ sub.rule_set ];
|
||||
for (let tag in rs) {
|
||||
if (index(tag, "sec_yt-geoip_ru") >= 0 || index(tag, "sec_yt-geosite_ru") >= 0)
|
||||
has_inverted_geo = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (has_domain && has_inverted_geo)
|
||||
sec_yt_logical_found = true;
|
||||
} else {
|
||||
// A non-logical rule for sec_yt-out that is unscoped
|
||||
sec_yt_unscoped_found = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Check sec_all_noru: Should have an inverted rule_set and NOT have an unconstrained all-traffic rule
|
||||
if (r.outbound == "sec_all_noru-out") {
|
||||
sec_noru_rule_count++;
|
||||
if (r.type == "logical" && r.mode == "and") {
|
||||
for (let sub in r.rules) {
|
||||
if (sub.invert == true && sub.rule_set)
|
||||
sec_noru_inverted_found = true;
|
||||
}
|
||||
} else if (r.invert == true && r.rule_set) {
|
||||
sec_noru_inverted_found = true;
|
||||
} else if (!r.domain && !r.domain_suffix && !r.rule_set && !r.ip_cidr && !r.source_ip_cidr) {
|
||||
sec_noru_unconstrained_all = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Check sec_pure_ru: Pure GeoIP section without domain
|
||||
if (r.outbound == "sec_pure_ru-out") {
|
||||
if (r.type == "logical" && r.mode == "and") {
|
||||
for (let sub in r.rules) {
|
||||
if (!sub.invert && sub.rule_set)
|
||||
sec_pure_ru_found = true;
|
||||
}
|
||||
} else if (r.rule_set && !r.invert) {
|
||||
sec_pure_ru_found = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Check sec_sub
|
||||
if (r.outbound == "sec_sub-out") {
|
||||
sec_sub_found = true;
|
||||
}
|
||||
|
||||
// 5. Check sec_both: Single logical rule combining domain, inverted GeoIP, AND inverted client IP
|
||||
if (r.outbound == "sec_both-out") {
|
||||
if (r.type == "logical" && r.mode == "and" && type(r.rules) == "array") {
|
||||
let has_domain = false;
|
||||
let has_inverted_geo = false;
|
||||
let has_inverted_src = false;
|
||||
for (let sub in r.rules) {
|
||||
if (sub.domain_suffix && index(sub.domain_suffix, "twitch.tv") >= 0)
|
||||
has_domain = true;
|
||||
if (sub.invert == true && sub.rule_set)
|
||||
has_inverted_geo = true;
|
||||
if (sub.invert == true && (sub.source_ip_cidr == "192.168.1.99/32" || (type(sub.source_ip_cidr) == "array" && index(sub.source_ip_cidr, "192.168.1.99/32") >= 0)))
|
||||
has_inverted_src = true;
|
||||
}
|
||||
if (has_domain && has_inverted_geo && has_inverted_src)
|
||||
sec_both_found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!sec_yt_logical_found)
|
||||
die("Expected scoped logical rule for sec_yt-out (youtube.com AND NOT ru) not found!\n");
|
||||
|
||||
if (sec_yt_unscoped_found)
|
||||
die("Found unwanted unscoped rule for sec_yt-out!\n");
|
||||
|
||||
if (!sec_noru_inverted_found)
|
||||
die("Expected inverted geoip rule for sec_noru-out not found!\n");
|
||||
|
||||
if (sec_noru_unconstrained_all)
|
||||
die("Found unconstrained all-traffic rule for sec_noru-out that negates GeoIP exclusion!\n");
|
||||
|
||||
if (!sec_pure_ru_found)
|
||||
die("Expected GeoIP include rule for sec_ru-out not found!\n");
|
||||
|
||||
if (!sec_sub_found)
|
||||
die("sec_sub-out rule not found!\n");
|
||||
|
||||
if (!sec_both_found)
|
||||
die("Expected combined logical rule for sec_both-out (domain + inverted geoip + inverted client ip) not found!\n");
|
||||
' || fail "singbox_section_geoip verification failed"
|
||||
|
||||
printf 'PASS: singbox_section_geoip\n'
|
||||
Reference in New Issue
Block a user