fix(core,nft,singbox,luci): resolve parental domain blocking priority, client bypass and CI apt hash mismatch

This commit is contained in:
Dushnilin
2026-09-09 21:01:43 +03:00
parent c2908d484a
commit ef45d93311
11 changed files with 414 additions and 34 deletions
+74 -1
View File
@@ -1,5 +1,6 @@
#!/usr/bin/env ucode
let fs = require("fs");
let common = require("core.common");
let as_string = common.as_string;
@@ -143,6 +144,77 @@ function ipv6_supported() {
return common.ipv6_supported();
}
function resolve_mac_to_ips(mac) {
mac = lc(replace(trim(as_string(mac)), "-", ":"));
if (mac == "")
return [];
let matched = [];
let seen = {};
let lease_files = [ "/tmp/dhcp.leases", "/var/lib/misc/dnsmasq.leases", "/tmp/hosts/dhcp" ];
for (let lpath in lease_files) {
let data = fs.readfile(lpath);
if (!data)
continue;
for (let line in split(as_string(data), "\n")) {
line = trim(line);
if (line == "" || index(line, "#") == 0)
continue;
let fields = split(line, /[ \t]+/);
if (length(fields) >= 3) {
let m = lc(replace(fields[1], "-", ":"));
let ip = fields[2];
if (m == mac && valid_ip(ip) && !seen[ip]) {
seen[ip] = true;
push(matched, ip);
}
}
}
}
let arp_data = fs.readfile("/proc/net/arp");
if (arp_data) {
for (let line in split(as_string(arp_data), "\n")) {
line = trim(line);
if (line == "" || index(line, "IP address") == 0)
continue;
let fields = split(line, /[ \t]+/);
if (length(fields) >= 4) {
let ip = fields[0];
let m = lc(replace(fields[3], "-", ":"));
if (m == mac && valid_ip(ip) && !seen[ip]) {
seen[ip] = true;
push(matched, ip);
}
}
}
}
try {
let uci_core = require("core.uci");
let cursor = uci_core ? uci_core.cursor() : null;
if (cursor) {
cursor.load("dhcp");
cursor.foreach("dhcp", "host", function(host) {
let m_list = host.mac;
if (type(m_list) != "array")
m_list = [ m_list ];
for (let m in m_list) {
if (lc(replace(trim(as_string(m)), "-", ":")) == mac) {
let ip = host.ip;
if (ip && valid_ip(ip) && !seen[ip]) {
seen[ip] = true;
push(matched, ip);
}
}
}
});
}
} catch (e) {}
return matched;
}
return {
valid_ipv4,
valid_ipv4_cidr,
@@ -154,5 +226,6 @@ return {
nft_ip_or_cidr,
ip_family,
format_ipv6_tproxy_target,
ipv6_supported
ipv6_supported,
resolve_mac_to_ips
};
+74
View File
@@ -1504,6 +1504,51 @@ function nft_create_runtime_base(table, localv4_set, common_set, port_set, ip_po
return false;
}
let excluded_clients_list = [];
append_array(excluded_clients_list, list_option(uci_settings(), "excluded_clients"));
append_array(excluded_clients_list, list_option(uci_settings(), "excluded_ips"));
if (length(excluded_clients_list) > 0) {
let exc_v4 = [];
let exc_v6 = [];
let exc_mac = [];
for (let item in excluded_clients_list) {
let val = trim(as_string(item));
if (val == "") continue;
let is_mac = match(val, /^([0-9a-fA-F]{2}[:-]){5}[0-9a-fA-F]{2}$/) != null;
if (is_mac) {
push(exc_mac, lc(replace(val, "-", ":")));
for (let res_ip in core_ip.resolve_mac_to_ips(val)) {
if (core_ip.ip_family(res_ip) == 4) push(exc_v4, res_ip);
else if (core_ip.ip_family(res_ip) == 6) push(exc_v6, res_ip);
}
} else if (core_ip.ip_family(val) == 4 || (core_ip.valid_ip_cidr(val) && index(val, ":") == -1)) {
push(exc_v4, val);
} else if (core_ip.ip_family(val) == 6 || (core_ip.valid_ip_cidr(val) && index(val, ":") != -1)) {
push(exc_v6, val);
}
}
for (let mac in exc_mac) {
if (!nft_add_rule(table, "mangle", [ "iifname", "@" + as_string(interface_set), "ether", "saddr", mac, "counter", "return" ]))
return false;
if (!nft_add_rule(table, "dns_redirect", [ "iifname", "@" + as_string(interface_set), "ether", "saddr", mac, "counter", "return" ]))
return false;
}
if (length(exc_v4) > 0) {
if (!nft_create_ipv4_set(table, "tachyon_excluded") ||
!nft_add_set_elements(table, "tachyon_excluded", join(",", exc_v4)) ||
!nft_add_rule(table, "mangle", [ "iifname", "@" + as_string(interface_set), "ip", "saddr", "@tachyon_excluded", "counter", "return" ]) ||
!nft_add_rule(table, "dns_redirect", [ "iifname", "@" + as_string(interface_set), "ip", "saddr", "@tachyon_excluded", "counter", "return" ]))
return false;
}
if (length(exc_v6) > 0) {
if (!nft_create_ipv6_set(table, "tachyon_excluded6") ||
!nft_add_set_elements(table, "tachyon_excluded6", join(",", exc_v6)) ||
!nft_add_rule(table, "mangle", [ "iifname", "@" + as_string(interface_set), "ip6", "saddr", "@tachyon_excluded6", "counter", "return" ]) ||
!nft_add_rule(table, "dns_redirect", [ "iifname", "@" + as_string(interface_set), "ip6", "saddr", "@tachyon_excluded6", "counter", "return" ]))
return false;
}
}
if (!nft_add_rule(table, "mangle", [ "jump", "priority_rules" ]) ||
!nft_add_rule(table, "mangle", [ "iifname", "@" + as_string(interface_set), "ip", "daddr", "@" + as_string(common_set), "meta", "l4proto", "tcp", "meta", "mark", "set", fakeip_mark, "counter" ]) ||
!nft_add_rule(table, "mangle", [ "iifname", "@" + as_string(interface_set), "ip", "daddr", "@" + as_string(common_set), "meta", "l4proto", "udp", "meta", "mark", "set", fakeip_mark, "counter" ]) ||
@@ -2335,6 +2380,8 @@ function nft_runtime_signature_from_settings_and_sections(settings, sections, sc
body = signature_add_value(body, "settings.exclude_ntp", bool_option(settings, "exclude_ntp", false) ? "1" : "0");
body = signature_add_value(body, "settings.game_console_optimizer", option(settings, "game_console_optimizer", "0"));
body = signature_add_value(body, "settings.game_console_ips", option(settings, "game_console_ips", ""));
body = signature_add_value(body, "settings.excluded_clients", option(settings, "excluded_clients", ""));
body = signature_add_value(body, "settings.excluded_ips", option(settings, "excluded_ips", ""));
for (let section in sections)
body = nft_rule_signature_body(body, object_or_empty(section));
@@ -2654,6 +2701,12 @@ function source_aware_dns_values(sections, deferred_sections) {
push(values, value);
}
}
for (let value in nft_csv_values(section_source_ip_values(section))) {
if (!seen[value]) {
seen[value] = true;
push(values, value);
}
}
}
if (action == "dns") {
@@ -2666,6 +2719,27 @@ function source_aware_dns_values(sections, deferred_sections) {
}
}
let settings = uci_settings();
let exc_items = [];
append_array(exc_items, list_option(settings, "excluded_clients"));
append_array(exc_items, list_option(settings, "excluded_ips"));
for (let item in exc_items) {
let val = trim(as_string(item));
if (val == "") continue;
let is_mac = match(val, /^([0-9a-fA-F]{2}[:-]){5}[0-9a-fA-F]{2}$/) != null;
if (is_mac) {
for (let res_ip in core_ip.resolve_mac_to_ips(val)) {
if (!seen[res_ip]) {
seen[res_ip] = true;
push(values, res_ip);
}
}
} else if (!seen[val]) {
seen[val] = true;
push(values, val);
}
}
return values;
}
+2
View File
@@ -935,6 +935,8 @@ function nft_runtime_signature_body(settings, sections) {
body = signature_add_value(body, "settings.exclude_ntp", bool_option(settings, "exclude_ntp", false) ? "1" : "0");
body = signature_add_value(body, "settings.game_console_optimizer", option(settings, "game_console_optimizer", "0"));
body = signature_add_value(body, "settings.game_console_ips", option(settings, "game_console_ips", ""));
body = signature_add_value(body, "settings.excluded_clients", option(settings, "excluded_clients", ""));
body = signature_add_value(body, "settings.excluded_ips", option(settings, "excluded_ips", ""));
for (let section in sections) {
section = object_or_empty(section);
+131 -18
View File
@@ -773,6 +773,10 @@ function schedule_has_time_window(schedule) {
trim(option(schedule, "end_time", "")) != "";
}
function resolve_mac_to_ips(mac) {
return core_ip.resolve_mac_to_ips(mac);
}
function profile_source_ip_cidrs(profile) {
let result = [];
for (let raw in schedule_list_value(profile, "device_ip")) {
@@ -780,8 +784,14 @@ function profile_source_ip_cidrs(profile) {
if (device == "")
continue;
let is_mac = match(device, /^([0-9a-fA-F]{2}[:-]){5}[0-9a-fA-F]{2}$/) != null;
if (is_mac)
if (is_mac) {
for (let ip in resolve_mac_to_ips(device)) {
let cidr = core_ip.valid_ipv6(ip) ? ip + "/128" : ip + "/32";
if (index(result, cidr) < 0)
push(result, cidr);
}
continue;
}
if (core_ip.valid_ip(device)) {
let cidr = core_ip.valid_ipv6(device) ? device + "/128" : device + "/32";
if (index(result, cidr) < 0) push(result, cidr);
@@ -799,8 +809,14 @@ function schedule_source_ip_cidrs(schedule) {
if (device == "")
continue;
let is_mac = match(device, /^([0-9a-fA-F]{2}[:-]){5}[0-9a-fA-F]{2}$/) != null;
if (is_mac)
if (is_mac) {
for (let ip in resolve_mac_to_ips(device)) {
let cidr = core_ip.valid_ipv6(ip) ? ip + "/128" : ip + "/32";
if (index(result, cidr) < 0)
push(result, cidr);
}
continue;
}
if (core_ip.valid_ip(device)) {
let cidr = core_ip.valid_ipv6(device) ? device + "/128" : device + "/32";
if (index(result, cidr) < 0) push(result, cidr);
@@ -907,10 +923,14 @@ function enabled_safesearch_profiles() {
return result;
}
function add_safesearch_dns_rules(config, profiles) {
function add_safesearch_dns_rules(target, profiles) {
if (length(profiles) == 0)
return;
let rules_arr = (type(target) == "object" && type(target.dns) == "object" && type(target.dns.rules) == "array")
? target.dns.rules
: target;
let all_sources = [];
for (let profile in profiles) {
for (let cidr in profile_source_ip_cidrs(profile)) {
@@ -951,7 +971,7 @@ function add_safesearch_dns_rules(config, profiles) {
};
if (length(all_sources) > 0)
rule.source_ip_cidr = all_sources;
push(config.dns.rules, rule);
push(rules_arr, rule);
}
}
}
@@ -965,7 +985,10 @@ function add_content_block_dns_inbound(config) {
});
}
function add_content_block_dns_rules(config, schedules) {
function add_content_block_dns_rules(target, schedules) {
let rules_arr = (type(target) == "object" && type(target.dns) == "object" && type(target.dns.rules) == "array")
? target.dns.rules
: target;
let added = false;
for (let schedule in schedules) {
let mode = option(schedule, "mode", "block");
@@ -989,14 +1012,17 @@ function add_content_block_dns_rules(config, schedules) {
rule.source_ip_cidr = sources;
if (mode == "allow")
rule.invert = true;
push(config.dns.rules, rule);
push(rules_arr, rule);
added = true;
}
}
return added;
}
function add_content_block_route_rules(config, schedules) {
function add_content_block_route_rules(target, schedules) {
let rules_arr = (type(target) == "object" && type(target.route) == "object" && type(target.route.rules) == "array")
? target.route.rules
: target;
for (let schedule in schedules) {
if (schedule_has_time_window(schedule))
continue;
@@ -1012,7 +1038,7 @@ function add_content_block_route_rules(config, schedules) {
rule.source_ip_cidr = sources;
if (mode == "allow")
rule.invert = true;
push(config.route.rules, rule);
push(rules_arr, rule);
}
}
@@ -1029,11 +1055,25 @@ function add_content_blocking(config) {
action: "hijack-dns",
inbound: [ runtime_constants.DNS_BLOCK_INBOUND_TAG ]
});
add_content_block_dns_rules(config, schedules);
add_content_block_dns_rules(config, profiles);
add_safesearch_dns_rules(config, safesearch_profiles);
add_content_block_route_rules(config, schedules);
add_content_block_route_rules(config, profiles);
let new_dns_rules = [];
add_content_block_dns_rules(new_dns_rules, schedules);
add_content_block_dns_rules(new_dns_rules, profiles);
add_safesearch_dns_rules(new_dns_rules, safesearch_profiles);
// Place content blocking DNS rules at the front of config.dns.rules so
// queries on dns-block-in are rejected before any section FakeIP rule matches!
for (let i = length(new_dns_rules) - 1; i >= 0; i--)
unshift(config.dns.rules, new_dns_rules[i]);
let new_route_rules = [];
add_content_block_route_rules(new_route_rules, schedules);
add_content_block_route_rules(new_route_rules, profiles);
// Insert content blocking route rules right after hijack-dns (index 1)
// so sniffed domains from blocked clients are rejected before any section proxy route matches!
for (let i = length(new_route_rules) - 1; i >= 0; i--)
splice(config.route.rules, 1, 0, new_route_rules[i]);
}
function single_or_array(values) {
@@ -1044,7 +1084,48 @@ function single_or_array(values) {
return values;
}
function source_aware_dns_sources(sections) {
function get_excluded_client_cidrs(settings) {
let result = [];
let seen = {};
let add_cidr = function(ip) {
ip = trim(as_string(ip));
if (ip == "") return;
let is_mac = match(ip, /^([0-9a-fA-F]{2}[:-]){5}[0-9a-fA-F]{2}$/) != null;
if (is_mac) {
for (let res_ip in resolve_mac_to_ips(ip)) {
let cidr = core_ip.valid_ipv6(res_ip) ? res_ip + "/128" : res_ip + "/32";
if (!seen[cidr]) {
seen[cidr] = true;
push(result, cidr);
}
}
return;
}
if (core_ip.valid_ip(ip)) {
let cidr = core_ip.valid_ipv6(ip) ? ip + "/128" : ip + "/32";
if (!seen[cidr]) {
seen[cidr] = true;
push(result, cidr);
}
} else if (core_ip.valid_ip_cidr(ip)) {
if (!seen[ip]) {
seen[ip] = true;
push(result, ip);
}
}
};
if (settings) {
for (let item in list_option(settings, "excluded_clients"))
add_cidr(item);
for (let item in list_option(settings, "excluded_ips"))
add_cidr(item);
}
return result;
}
function source_aware_dns_sources(sections, settings) {
let sources = [];
let seen = {};
let add_source = function(value) {
@@ -1055,6 +1136,11 @@ function source_aware_dns_sources(sections) {
push(sources, value);
};
if (settings) {
for (let cidr in get_excluded_client_cidrs(settings))
add_source(cidr);
}
for (let section in sections) {
let action = option(section, "action", "");
let has_dns_matchers = connections.has_dns_matchers(section);
@@ -1067,6 +1153,8 @@ function source_aware_dns_sources(sections) {
if (action == "bypass") {
for (let ip in list_option(section, "fully_routed_ips"))
add_source(ip);
for (let ip in list_option(section, "source_ip_cidr"))
add_source(ip);
}
if (action == "dns") {
@@ -1099,12 +1187,25 @@ function add_source_aware_dns_fallback(config, source_aware_dns) {
let settings = runtime_settings_cache || {};
let rewrite_ttl = int_option(settings, "dns_rewrite_ttl", "60");
push(config.dns.rules, {
let rule = {
action: "route",
server: runtime_constants.DNSMASQ_DNS_SERVER_TAG,
inbound: [ runtime_constants.SOURCE_DNS_INBOUND_TAG ],
source_ip_cidr: single_or_array(source_aware_dns),
rewrite_ttl
};
unshift(config.dns.rules, rule);
}
function add_excluded_clients_route_rule(config, settings) {
let excluded_cidrs = get_excluded_client_cidrs(settings);
if (length(excluded_cidrs) == 0)
return;
splice(config.route.rules, 1, 0, {
action: "route",
outbound: runtime_constants.BYPASS_OUTBOUND_TAG,
source_ip_cidr: single_or_array(excluded_cidrs)
});
}
@@ -1124,7 +1225,7 @@ function generate_config(output_path, service_address, mwan3_active, supports_xh
if (length(sections) == 0 && length(servers) == 0)
runtime_generate_unsupported("no enabled sections");
let source_aware_dns = source_aware_dns_sources(sections);
let source_aware_dns = source_aware_dns_sources(sections, settings);
let config = base_config(settings, service_address, {
mwan3_active: cli_bool(mwan3_active),
source_aware_dns: length(source_aware_dns) > 0
@@ -1137,10 +1238,22 @@ function generate_config(output_path, service_address, mwan3_active, supports_xh
for (let section in sections)
add_outbound_for_section(config, section, taken, sections);
add_service_route_rules(config, sections);
for (let section in sections)
add_route_for_section(config, section);
// Add block and bypass sections before proxy sections so exceptions take precedence
for (let section in sections) {
let act = option(section, "action", "");
if (act == "block" || act == "bypass")
add_route_for_section(config, section);
}
for (let section in sections) {
let act = option(section, "action", "");
if (act != "block" && act != "bypass")
add_route_for_section(config, section);
}
add_server_routes(config, servers, sections);
add_source_aware_dns_fallback(config, source_aware_dns);
add_excluded_clients_route_rule(config, settings);
// Append dns_hosts predefined rules AFTER section DNS rules so that
// FakeIP/section-level DNS routing takes precedence over hardcoded IPs