2251 lines
96 KiB
Ucode
2251 lines
96 KiB
Ucode
#!/usr/bin/env ucode
|
|
|
|
let fs = require("fs");
|
|
let common = require("core.common");
|
|
let helpers = require("core.helpers");
|
|
let core_ip = require("core.ip");
|
|
let uci_core = require("core.uci");
|
|
let rule_config = require("config.rule");
|
|
let domain_config = require("config.domain");
|
|
let connections = require("config.connections");
|
|
let routing_rulesets = require("routing.rulesets");
|
|
const CONFIG_NAME = getenv("TACHYON_CONFIG_NAME") || "tachyon";
|
|
|
|
let common_read_json_file = common.read_json_file;
|
|
let list_option = common.list_option;
|
|
let bool_option = common.bool_option;
|
|
|
|
function as_string(value) {
|
|
return value == null ? "" : "" + value;
|
|
}
|
|
|
|
function arg_bool(value) {
|
|
value = lc(as_string(value));
|
|
return value == "1" || value == "true" || value == "yes";
|
|
}
|
|
|
|
function object_or_empty(value) {
|
|
return type(value) == "object" ? value : {};
|
|
}
|
|
|
|
function option(section, key, fallback) {
|
|
if (fallback == null)
|
|
fallback = "";
|
|
|
|
let value = object_or_empty(section)[key];
|
|
if (value == null)
|
|
return fallback;
|
|
if (type(value) == "array")
|
|
return join(" ", value);
|
|
return as_string(value);
|
|
}
|
|
|
|
function uci_section(section_name) {
|
|
return object_or_empty(uci_core.get_all(CONFIG_NAME, section_name));
|
|
}
|
|
|
|
function uci_sections(type_name) {
|
|
return uci_core.section_objects(CONFIG_NAME, type_name);
|
|
}
|
|
|
|
function uci_settings() {
|
|
return uci_section("settings");
|
|
}
|
|
|
|
function section_by_name(sections, section_name) {
|
|
section_name = as_string(section_name);
|
|
for (let section in sections)
|
|
if (as_string(section[".name"]) == section_name)
|
|
return section;
|
|
return null;
|
|
}
|
|
|
|
function write_compact_string_array(values) {
|
|
print("[");
|
|
for (let i = 0; i < length(values); i++) {
|
|
if (i > 0)
|
|
print(",");
|
|
print(sprintf("%J", as_string(values[i])));
|
|
}
|
|
print("]\n");
|
|
}
|
|
|
|
function write_text_file(path, text) {
|
|
let result = fs.writefile(path, as_string(text));
|
|
if (result == null)
|
|
return false;
|
|
if (type(result) == "boolean" && !result)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
function file_executable(path) {
|
|
let stat = fs.stat(as_string(path));
|
|
if (stat == null || stat.mode == null)
|
|
return false;
|
|
|
|
return (int(stat.mode) & 73) != 0;
|
|
}
|
|
|
|
// The empty catch is the point: every caller means "make sure this path is
|
|
// gone", and an absent file already satisfies that. fs.unlink throws on ENOENT,
|
|
// so the alternative is a stat() race with no better outcome.
|
|
function unlink_file(path) {
|
|
try {
|
|
fs.unlink(as_string(path));
|
|
}
|
|
catch (e) {
|
|
}
|
|
}
|
|
|
|
function shell_quote(value) {
|
|
return "'" + replace(as_string(value), /'/g, "'\\''") + "'";
|
|
}
|
|
|
|
function command_from_args(args) {
|
|
let parts = [];
|
|
|
|
for (let arg in args)
|
|
push(parts, shell_quote(arg));
|
|
|
|
return join(" ", parts);
|
|
}
|
|
|
|
function run_args(args) {
|
|
return system(command_from_args(args)) == 0;
|
|
}
|
|
|
|
function run_args_quiet(args) {
|
|
return system(command_from_args(args) + " >/dev/null 2>&1") == 0;
|
|
}
|
|
|
|
function command_output_from_args(args) {
|
|
let pipe = fs.popen(command_from_args(args), "r");
|
|
if (!pipe)
|
|
return "";
|
|
|
|
let data = pipe.read("all");
|
|
let status = pipe.close();
|
|
if (status != 0 || data == null)
|
|
return "";
|
|
|
|
return as_string(data);
|
|
}
|
|
|
|
function command_output_quiet_from_args(args) {
|
|
let pipe = fs.popen(command_from_args(args) + " 2>/dev/null", "r");
|
|
if (!pipe)
|
|
return "";
|
|
|
|
let data = pipe.read("all");
|
|
let status = pipe.close();
|
|
if (status != 0 || data == null)
|
|
return "";
|
|
|
|
return as_string(data);
|
|
}
|
|
|
|
function log_to_kmsg(message, level) {
|
|
if (getenv("LOGGER_LOG") != null)
|
|
return false;
|
|
|
|
let priority = 6;
|
|
let lvl = as_string(level || "info");
|
|
if (lvl == "warn") priority = 4;
|
|
else if (lvl == "fatal") priority = 3;
|
|
else if (lvl == "debug") priority = 7;
|
|
|
|
let kmsg = fs.open("/dev/kmsg", "w");
|
|
if (kmsg) {
|
|
kmsg.write(sprintf("<%d>tachyon: [%s] %s\n", priority, lvl, as_string(message)));
|
|
kmsg.close();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function log_debug(message) {
|
|
if (!log_to_kmsg(message, "debug"))
|
|
run_args([ "logger", "-t", "tachyon", "[debug] " + as_string(message) ]);
|
|
}
|
|
|
|
function log_warn(message) {
|
|
if (!log_to_kmsg(message, "warn"))
|
|
run_args([ "logger", "-t", "tachyon", "[warn] " + as_string(message) ]);
|
|
}
|
|
|
|
function log_fatal(message) {
|
|
if (!log_to_kmsg(message, "fatal"))
|
|
run_args([ "logger", "-t", "tachyon", "[fatal] " + as_string(message) ]);
|
|
}
|
|
|
|
function strip_list_comment(line) {
|
|
line = replace(as_string(line), /[[:space:]]*\/\/.*$/, "");
|
|
return replace(line, /[[:space:]]*#.*$/, "");
|
|
}
|
|
|
|
function print_csv(values) {
|
|
for (let i = 0; i < length(values); i++) {
|
|
if (i > 0)
|
|
print(",");
|
|
print(as_string(values[i]));
|
|
}
|
|
if (length(values) > 0)
|
|
print("\n");
|
|
}
|
|
|
|
function text_list_values(value, separator_mode) {
|
|
let result = [];
|
|
separator_mode = as_string(separator_mode);
|
|
|
|
for (let line in split(as_string(value), "\n")) {
|
|
line = strip_list_comment(line);
|
|
line = separator_mode == "comma-space"
|
|
? replace(line, /[ ,]/g, "\n")
|
|
: replace(line, /,/g, "\n");
|
|
|
|
for (let item in split(line, "\n")) {
|
|
item = trim(replace(item, /\r/g, ""));
|
|
if (item != "")
|
|
push(result, item);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function text_list_to_csv(value, separator_mode) {
|
|
print_csv(text_list_values(value, separator_mode));
|
|
}
|
|
|
|
function csv_to_json_array(value) {
|
|
value = as_string(value);
|
|
if (value == "") {
|
|
print("[]\n");
|
|
return;
|
|
}
|
|
|
|
write_compact_string_array(split(value, ","));
|
|
}
|
|
|
|
function csv_list_contains(value, needle) {
|
|
needle = as_string(needle);
|
|
if (needle == "")
|
|
return false;
|
|
|
|
for (let item in split(as_string(value), ",")) {
|
|
if (item == needle)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function cache_key_is_safe(value) {
|
|
value = as_string(value);
|
|
return value != "" && match(value, /^[A-Za-z0-9_]+$/) != null;
|
|
}
|
|
|
|
function cache_path(enabled, cache_dir, namespace, section, key, kind) {
|
|
if (as_string(enabled) != "1")
|
|
exit(1);
|
|
|
|
cache_dir = as_string(cache_dir);
|
|
if (cache_dir == "")
|
|
exit(1);
|
|
|
|
if (!cache_key_is_safe(namespace) || !cache_key_is_safe(section) ||
|
|
!cache_key_is_safe(key) || !cache_key_is_safe(kind))
|
|
exit(1);
|
|
|
|
print(cache_dir, "/", namespace, "_", section, "_", key, "_", kind, "\n");
|
|
}
|
|
|
|
function valid_ipv4(value) {
|
|
return core_ip.valid_ipv4(value, false, false);
|
|
}
|
|
|
|
function valid_ipv4_cidr(value) {
|
|
return core_ip.valid_ipv4_cidr(value, false);
|
|
}
|
|
|
|
function nft_ip_or_cidr(value) {
|
|
return core_ip.nft_ip_or_cidr(value);
|
|
}
|
|
|
|
function domain_subnet_line_values(data) {
|
|
let result = [];
|
|
|
|
for (let line in split(as_string(data), "\n")) {
|
|
line = trim(replace(strip_list_comment(line), /\r/g, ""));
|
|
if (line != "")
|
|
push(result, line);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function normalize_domain_subnet_value(value, kind) {
|
|
kind = as_string(kind);
|
|
if (kind == "domains")
|
|
return domain_config.suffix_to_ascii(value);
|
|
if (kind == "subnets")
|
|
return core_ip.valid_ip_or_cidr(value) ? value : null;
|
|
|
|
exit(1);
|
|
}
|
|
|
|
function filter_domain_subnet_values(values, kind) {
|
|
let result = [];
|
|
kind = as_string(kind);
|
|
|
|
if (kind != "domains" && kind != "subnets")
|
|
exit(1);
|
|
|
|
for (let value in values) {
|
|
let normalized = normalize_domain_subnet_value(value, kind);
|
|
if (normalized != null)
|
|
push(result, normalized);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function combined_domain_text_csv(value, requested_kind) {
|
|
let result = rule_config.combined_domain_text_csv_value(value, requested_kind);
|
|
if (result != "")
|
|
print(result, "\n");
|
|
}
|
|
|
|
function combined_domain_csv(value, requested_kind) {
|
|
let result = rule_config.combined_domain_csv_value(value, requested_kind);
|
|
if (result != "")
|
|
print(result, "\n");
|
|
}
|
|
|
|
function list_value_csv(value) {
|
|
value = as_string(value);
|
|
if (value != "")
|
|
print(replace(value, / /g, ","), "\n");
|
|
}
|
|
|
|
function legacy_condition_csv_value(kind, text_mode, conditions_text_mode, text_value, list_value) {
|
|
return rule_config.legacy_condition_csv_value(kind, text_mode, conditions_text_mode, text_value, list_value);
|
|
}
|
|
|
|
function rule_condition_csv_value(key, kind, text_mode, conditions_text_mode, text_value, list_value, combined_text_value, combined_list_value) {
|
|
return rule_config.rule_condition_csv_value(key, kind, text_mode, conditions_text_mode, text_value, list_value, combined_text_value, combined_list_value);
|
|
}
|
|
|
|
function rule_condition_csv(key, kind, text_mode, conditions_text_mode, text_value, list_value, combined_text_value, combined_list_value) {
|
|
let value = rule_condition_csv_value(key, kind, text_mode, conditions_text_mode, text_value, list_value, combined_text_value, combined_list_value);
|
|
|
|
if (value != "")
|
|
print(value, "\n");
|
|
}
|
|
|
|
function legacy_condition_csv(kind, text_mode, conditions_text_mode, text_value, list_value) {
|
|
let value = legacy_condition_csv_value(kind, text_mode, conditions_text_mode, text_value, list_value);
|
|
if (value != "")
|
|
print(value, "\n");
|
|
}
|
|
|
|
function domain_subnet_text_csv(value, kind) {
|
|
print_csv(filter_domain_subnet_values(text_list_values(value, "comma-space"), kind));
|
|
}
|
|
|
|
function domain_subnet_file_csv(path, kind) {
|
|
let data = fs.readfile(path);
|
|
if (data == null)
|
|
exit(1);
|
|
|
|
print_csv(filter_domain_subnet_values(domain_subnet_line_values(data), kind));
|
|
}
|
|
|
|
function split_domain_subnet_file(path, domains_path, subnets_path) {
|
|
let data = fs.readfile(path);
|
|
if (data == null)
|
|
exit(1);
|
|
|
|
let domains = [];
|
|
let subnets = [];
|
|
|
|
for (let value in domain_subnet_line_values(data)) {
|
|
let domain = normalize_domain_subnet_value(value, "domains");
|
|
if (domain != null)
|
|
push(domains, domain);
|
|
else if (core_ip.valid_ip_or_cidr(value))
|
|
push(subnets, value);
|
|
}
|
|
|
|
if (!write_text_file(domains_path, length(domains) > 0 ? join("\n", domains) + "\n" : ""))
|
|
exit(1);
|
|
if (!write_text_file(subnets_path, length(subnets) > 0 ? join("\n", subnets) + "\n" : ""))
|
|
exit(1);
|
|
}
|
|
|
|
function normalize_port_number_value(value) {
|
|
return rule_config.normalize_port_number_value(value);
|
|
}
|
|
|
|
function normalize_port_condition_value(value) {
|
|
return rule_config.normalize_port_condition_value(value);
|
|
}
|
|
|
|
function normalize_port_condition_for_nft(value) {
|
|
let normalized = normalize_port_condition_value(value);
|
|
if (normalized == null)
|
|
exit(1);
|
|
print(normalized, "\n");
|
|
}
|
|
|
|
function normalize_port_range_value(value) {
|
|
return rule_config.normalize_port_range_value(value);
|
|
}
|
|
|
|
function rule_ports_csv_value(list_values, text_value) {
|
|
return rule_config.rule_ports_csv_value(list_values, text_value);
|
|
}
|
|
|
|
function rule_ports_csv(list_values, text_value) {
|
|
let value = rule_ports_csv_value(list_values, text_value);
|
|
if (value != "")
|
|
print(value, "\n");
|
|
}
|
|
|
|
function rule_port_values(csv) {
|
|
let result = [];
|
|
|
|
for (let item in split(as_string(csv), ",")) {
|
|
if (index(item, "-") >= 0)
|
|
continue;
|
|
|
|
let port = normalize_port_number_value(item);
|
|
if (port != null)
|
|
push(result, port);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function rule_port_ranges(csv) {
|
|
let result = [];
|
|
|
|
for (let item in split(as_string(csv), ",")) {
|
|
if (index(item, "-") < 0)
|
|
continue;
|
|
|
|
let range = normalize_port_range_value(item);
|
|
if (range != null)
|
|
push(result, range);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function csv_to_lines_file(csv, path) {
|
|
if (!fs.writefile(path, replace(as_string(csv) + "\n", /,/g, "\n")))
|
|
exit(1);
|
|
}
|
|
|
|
function nft_create_table(name) {
|
|
return run_args([ "nft", "add", "table", "inet", name ]);
|
|
}
|
|
|
|
function nft_create_set(table, name, definition) {
|
|
return run_args([ "nft", "add", "set", "inet", table, name, definition ]);
|
|
}
|
|
|
|
function nft_create_ipv4_set(table, name) {
|
|
return nft_create_set(table, name, "{ type ipv4_addr; flags interval; auto-merge; }");
|
|
}
|
|
|
|
function nft_create_ipv6_set(table, name) {
|
|
return nft_create_set(table, name, "{ type ipv6_addr; flags interval; auto-merge; }");
|
|
}
|
|
|
|
function nft_create_inet_service_set(table, name) {
|
|
return nft_create_set(table, name, "{ type inet_service; flags interval; auto-merge; }");
|
|
}
|
|
|
|
function nft_create_ipv4_port_set(table, name) {
|
|
return nft_create_set(table, name, "{ type ipv4_addr . inet_service; flags interval; auto-merge; }");
|
|
}
|
|
|
|
function nft_create_ipv6_port_set(table, name) {
|
|
return nft_create_set(table, name, "{ type ipv6_addr . inet_service; flags interval; auto-merge; }");
|
|
}
|
|
|
|
function nft_create_ifname_set(table, name) {
|
|
return nft_create_set(table, name, "{ type ifname; flags interval; }");
|
|
}
|
|
|
|
function nft_add_set_elements(table, set_name, elements) {
|
|
let stamp = clock();
|
|
let tmp_path = sprintf("/tmp/nft_elements.%d.%d.tmp", stamp[0], stamp[1]);
|
|
let rules_content = sprintf("add element inet %s %s { %s }\n", table, set_name, as_string(elements));
|
|
|
|
let write_stamp = clock();
|
|
let real_tmp = sprintf("%s.write.%d.%d", tmp_path, write_stamp[0], write_stamp[1]);
|
|
if (fs.writefile(real_tmp, rules_content) == null) {
|
|
fs.unlink(real_tmp);
|
|
return false;
|
|
}
|
|
if (!fs.rename(real_tmp, tmp_path)) {
|
|
fs.unlink(real_tmp);
|
|
return false;
|
|
}
|
|
|
|
let cmd_str = sprintf("nft -f %s", shell_quote(tmp_path));
|
|
let res = system(cmd_str + " 2>/tmp/nft_err.log");
|
|
fs.unlink(tmp_path);
|
|
|
|
if (res != 0) {
|
|
let err_msg = trim(as_string(fs.readfile("/tmp/nft_err.log") || ""));
|
|
log_fatal("nft add element failed: cmd='" + cmd_str + "', code=" + res + ", err='" + err_msg + "'");
|
|
}
|
|
return res == 0;
|
|
}
|
|
|
|
function whitespace_values(value) {
|
|
let result = [];
|
|
|
|
for (let item in split(replace(as_string(value), /[[:space:]]+/g, " "), " ")) {
|
|
item = trim(item);
|
|
if (item != "")
|
|
push(result, item);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function nft_create_chain(table, name, definition) {
|
|
return run_args([ "nft", "add", "chain", "inet", table, name, definition ]);
|
|
}
|
|
|
|
function nft_add_rule(table, chain, args) {
|
|
let command = [ "nft", "add", "rule", "inet", table, chain ];
|
|
for (let arg in args)
|
|
push(command, arg);
|
|
return run_args(command);
|
|
}
|
|
|
|
function nft_insert_rule(table, chain, args) {
|
|
let command = [ "nft", "insert", "rule", "inet", table, chain ];
|
|
for (let arg in args)
|
|
push(command, arg);
|
|
return run_args(command);
|
|
}
|
|
|
|
let LOCALV4_RANGES = [
|
|
"0.0.0.0/8",
|
|
"10.0.0.0/8",
|
|
"127.0.0.0/8",
|
|
"169.254.0.0/16",
|
|
"172.16.0.0/12",
|
|
"192.0.0.0/24",
|
|
"192.0.2.0/24",
|
|
"192.88.99.0/24",
|
|
"192.168.0.0/16",
|
|
"198.51.100.0/24",
|
|
"203.0.113.0/24",
|
|
"224.0.0.0/4",
|
|
"240.0.0.0-255.255.255.255"
|
|
];
|
|
|
|
let LOCALV6_RANGES = [
|
|
"::/128",
|
|
"::1/128",
|
|
"64:ff9b::/96",
|
|
"100::/64",
|
|
"2001:db8::/32",
|
|
"fc00::/7",
|
|
"fe80::/10",
|
|
"ff00::/8"
|
|
];
|
|
|
|
function default_arg(value, fallback) {
|
|
value = as_string(value);
|
|
return value == "" ? fallback : value;
|
|
}
|
|
|
|
function combined_domain_condition_text(section) {
|
|
if (type(object_or_empty(section)["domain"]) != "array") {
|
|
let value = option(section, "domain", "");
|
|
if (value != "")
|
|
return value;
|
|
}
|
|
|
|
return option(section, "domain_suffix_text", "");
|
|
}
|
|
|
|
function section_rule_condition_csv(section, key, kind) {
|
|
return rule_condition_csv_value(
|
|
key,
|
|
kind,
|
|
option(section, key + "_text_mode", "0"),
|
|
option(section, "conditions_text_mode", "0"),
|
|
option(section, key + "_text", ""),
|
|
option(section, key, ""),
|
|
combined_domain_condition_text(section),
|
|
option(section, "domain_suffix", "")
|
|
);
|
|
}
|
|
|
|
function section_rule_ports_csv(section) {
|
|
return rule_ports_csv_value(option(section, "ports", ""), option(section, "ports_text", ""));
|
|
}
|
|
|
|
function section_option_nonempty(section, key) {
|
|
return option(section, key, "") != "";
|
|
}
|
|
|
|
function section_has_destination_matchers(section) {
|
|
return section_rule_condition_csv(section, "domain", "domains") != "" ||
|
|
section_rule_condition_csv(section, "domain_suffix", "domains") != "" ||
|
|
section_rule_condition_csv(section, "domain_keyword", "generic") != "" ||
|
|
section_rule_condition_csv(section, "domain_regex", "generic") != "" ||
|
|
section_rule_condition_csv(section, "ip_cidr", "subnets") != "" ||
|
|
length(connections.community_lists(section)) > 0 ||
|
|
length(connections.rule_sets(section)) > 0 ||
|
|
length(connections.rule_sets_with_subnets(section)) > 0 ||
|
|
section_option_nonempty(section, "domain_ip_lists");
|
|
}
|
|
|
|
function section_action(section) {
|
|
return option(section, "action", "");
|
|
}
|
|
|
|
function action_captures_traffic(action) {
|
|
return action == "connection" || action == "proxy" || action == "outbound" || action == "vpn" ||
|
|
action == "awg" || action == "warp" || action == "block" || action == "zapret" || action == "zapret2" || action == "byedpi";
|
|
}
|
|
|
|
function section_priority_action(section) {
|
|
let action = section_action(section);
|
|
if (action == "bypass")
|
|
return "bypass";
|
|
if (action_captures_traffic(action))
|
|
return "capture";
|
|
return "";
|
|
}
|
|
|
|
function section_priority_prefix(section) {
|
|
return "tachyon_rule_" + as_string(section[".name"]);
|
|
}
|
|
|
|
function section_priority_sets(section) {
|
|
let prefix = section_priority_prefix(section);
|
|
return {
|
|
subnets: prefix + "_subnets",
|
|
subnets6: prefix + "_subnets6",
|
|
ports: prefix + "_ports",
|
|
ip_ports: prefix + "_ip_ports",
|
|
ip6_ports: prefix + "_ip6_ports",
|
|
sources: prefix + "_sources",
|
|
sources6: prefix + "_sources6"
|
|
};
|
|
}
|
|
|
|
function section_source_ip_values(section) {
|
|
return section_rule_condition_csv(section, "source_ip_cidr", "subnets");
|
|
}
|
|
|
|
function section_has_source_ip_matchers(section) {
|
|
return section_source_ip_values(section) != "";
|
|
}
|
|
|
|
function section_has_subnet_update_sources(section) {
|
|
return rule_config.has_community_subnet_list(connections.community_lists_value(section)) ||
|
|
length(connections.rule_sets_with_subnets(section)) > 0 ||
|
|
option(section, "domain_ip_lists", "") != "";
|
|
}
|
|
|
|
function section_has_nft_ip_matchers(section) {
|
|
return section_rule_condition_csv(section, "ip_cidr", "subnets") != "" ||
|
|
section_has_subnet_update_sources(section);
|
|
}
|
|
|
|
function section_has_nft_port_only_matchers(section) {
|
|
return section_rule_ports_csv(section) != "" && !section_has_destination_matchers(section);
|
|
}
|
|
|
|
function section_priority_needs_plain_ip_rules(section) {
|
|
return section_has_nft_ip_matchers(section) && section_rule_ports_csv(section) == "";
|
|
}
|
|
|
|
function section_priority_needs_ip_port_rules(section) {
|
|
return section_has_nft_ip_matchers(section) &&
|
|
(section_rule_ports_csv(section) != "" || length(connections.rule_sets_with_subnets(section)) > 0);
|
|
}
|
|
|
|
function section_has_dscp_matchers(section) {
|
|
return length(connections.dscp_list(section)) > 0;
|
|
}
|
|
|
|
function section_needs_priority_sets(section) {
|
|
return section_priority_action(section) != "" &&
|
|
(section_has_nft_ip_matchers(section) || section_has_nft_port_only_matchers(section) || section_has_dscp_matchers(section));
|
|
}
|
|
|
|
function nft_create_priority_chains(table) {
|
|
return nft_create_chain(table, "priority_rules", "{ }") &&
|
|
nft_create_chain(table, "priority_output_rules", "{ }") &&
|
|
nft_add_rule(table, "priority_output_rules", [ "meta", "mark", "!=", "0", "return" ]);
|
|
}
|
|
|
|
function nft_create_priority_sets(table, sets) {
|
|
return nft_create_ipv4_set(table, sets.subnets) &&
|
|
nft_create_ipv6_set(table, sets.subnets6) &&
|
|
nft_create_inet_service_set(table, sets.ports) &&
|
|
nft_create_ipv4_port_set(table, sets.ip_ports) &&
|
|
nft_create_ipv6_port_set(table, sets.ip6_ports) &&
|
|
nft_create_ipv4_set(table, sets.sources) &&
|
|
nft_create_ipv6_set(table, sets.sources6);
|
|
}
|
|
|
|
function nft_priority_verdict_args(priority_action, mark) {
|
|
if (priority_action == "bypass")
|
|
return [ "counter", "accept" ];
|
|
return [ "meta", "mark", "set", mark, "counter", "accept" ];
|
|
}
|
|
|
|
function append_array(target, additions) {
|
|
for (let item in additions)
|
|
push(target, item);
|
|
return target;
|
|
}
|
|
|
|
function nft_source_match_args(section, family, sets) {
|
|
if (!section_has_source_ip_matchers(section))
|
|
return [];
|
|
return family == 6
|
|
? [ "ip6", "saddr", "@" + as_string(sets.sources6) ]
|
|
: [ "ip", "saddr", "@" + as_string(sets.sources) ];
|
|
}
|
|
|
|
// Build a negative nftables source-IP match for excluded_ips.
|
|
// nft accepts: ip saddr != { addr1, addr2, ... } as a single argument.
|
|
function nft_excluded_source_match_args(section, family) {
|
|
let excluded = list_option(section, "excluded_ips");
|
|
if (length(excluded) == 0)
|
|
return [];
|
|
let ip_key = family == 6 ? "ip6" : "ip";
|
|
let want_family = family;
|
|
let addrs = [];
|
|
for (let ip in excluded)
|
|
if (core_ip.ip_family(as_string(ip)) == want_family)
|
|
push(addrs, as_string(ip));
|
|
if (length(addrs) == 0)
|
|
return [];
|
|
return [ ip_key, "saddr", "!=", "{ " + join(", ", addrs) + " }" ];
|
|
}
|
|
|
|
function nft_priority_rule_args(section, family, local_set, match_args, mark) {
|
|
let sets = section_priority_sets(section);
|
|
let args = [];
|
|
if (family == 4)
|
|
append_array(args, nft_source_match_args(section, 4, sets));
|
|
else
|
|
append_array(args, nft_source_match_args(section, 6, sets));
|
|
append_array(args, nft_excluded_source_match_args(section, family));
|
|
append_array(args, [ family == 6 ? "ip6" : "ip", "daddr", "!=", "@" + as_string(local_set) ]);
|
|
append_array(args, match_args);
|
|
append_array(args, nft_priority_verdict_args(section_priority_action(section), mark));
|
|
return args;
|
|
}
|
|
|
|
|
|
function nft_priority_prerouting_args(section, family, interface_set, local_set, match_args, mark) {
|
|
let args = [ "iifname", "@" + as_string(interface_set) ];
|
|
append_array(args, nft_priority_rule_args(section, family, local_set, match_args, mark));
|
|
return args;
|
|
}
|
|
|
|
function nft_add_priority_rule_pair(table, chain, section, interface_set, localv4_set, localv6_set, match4, match6, mark) {
|
|
if (chain == "priority_rules") {
|
|
return nft_add_rule(table, chain, nft_priority_prerouting_args(section, 4, interface_set, localv4_set, match4, mark)) &&
|
|
nft_add_rule(table, chain, nft_priority_prerouting_args(section, 6, interface_set, localv6_set, match6, mark));
|
|
}
|
|
|
|
return nft_add_rule(table, chain, nft_priority_rule_args(section, 4, localv4_set, match4, mark)) &&
|
|
nft_add_rule(table, chain, nft_priority_rule_args(section, 6, localv6_set, match6, mark));
|
|
}
|
|
|
|
function nft_add_section_priority_rules(table, section, interface_set, localv4_set, localv6_set, mark) {
|
|
if (!section_needs_priority_sets(section))
|
|
return true;
|
|
|
|
let sets = section_priority_sets(section);
|
|
if (!nft_create_priority_sets(table, sets))
|
|
return false;
|
|
|
|
let needs_plain_ip_rules = section_priority_needs_plain_ip_rules(section);
|
|
let needs_ip_port_rules = section_priority_needs_ip_port_rules(section);
|
|
let has_port_only_matchers = section_has_nft_port_only_matchers(section);
|
|
let is_bypass = (section_priority_action(section) == "bypass");
|
|
let match_ip4 = [ "ip", "daddr", "@" + as_string(sets.subnets) ];
|
|
let match_ip6 = [ "ip6", "daddr", "@" + as_string(sets.subnets6) ];
|
|
let match_ip4_tcp = [ "ip", "daddr", "@" + as_string(sets.subnets), "meta", "l4proto", "tcp" ];
|
|
let match_ip4_udp = [ "ip", "daddr", "@" + as_string(sets.subnets), "meta", "l4proto", "udp" ];
|
|
let match_ip6_tcp = [ "ip6", "daddr", "@" + as_string(sets.subnets6), "meta", "l4proto", "tcp" ];
|
|
let match_ip6_udp = [ "ip6", "daddr", "@" + as_string(sets.subnets6), "meta", "l4proto", "udp" ];
|
|
let match_ip_port4_tcp = [ "ip", "daddr", ".", "tcp", "dport", "@" + as_string(sets.ip_ports) ];
|
|
let match_ip_port4_udp = [ "ip", "daddr", ".", "udp", "dport", "@" + as_string(sets.ip_ports) ];
|
|
let match_ip_port6_tcp = [ "ip6", "daddr", ".", "tcp", "dport", "@" + as_string(sets.ip6_ports) ];
|
|
let match_ip_port6_udp = [ "ip6", "daddr", ".", "udp", "dport", "@" + as_string(sets.ip6_ports) ];
|
|
let match_port4_tcp = [ "tcp", "dport", "@" + as_string(sets.ports) ];
|
|
let match_port4_udp = [ "udp", "dport", "@" + as_string(sets.ports) ];
|
|
let match_port6_tcp = [ "tcp", "dport", "@" + as_string(sets.ports) ];
|
|
let match_port6_udp = [ "udp", "dport", "@" + as_string(sets.ports) ];
|
|
|
|
if (needs_plain_ip_rules) {
|
|
if (is_bypass) {
|
|
if (!nft_add_priority_rule_pair(table, "priority_rules", section, interface_set, localv4_set, localv6_set, match_ip4, match_ip6, mark) ||
|
|
!nft_add_priority_rule_pair(table, "priority_output_rules", section, interface_set, localv4_set, localv6_set, match_ip4, match_ip6, mark))
|
|
return false;
|
|
} else {
|
|
if (!nft_add_priority_rule_pair(table, "priority_rules", section, interface_set, localv4_set, localv6_set, match_ip4_tcp, match_ip6_tcp, mark) ||
|
|
!nft_add_priority_rule_pair(table, "priority_rules", section, interface_set, localv4_set, localv6_set, match_ip4_udp, match_ip6_udp, mark) ||
|
|
!nft_add_priority_rule_pair(table, "priority_output_rules", section, interface_set, localv4_set, localv6_set, match_ip4_tcp, match_ip6_tcp, mark) ||
|
|
!nft_add_priority_rule_pair(table, "priority_output_rules", section, interface_set, localv4_set, localv6_set, match_ip4_udp, match_ip6_udp, mark))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (needs_ip_port_rules &&
|
|
(!nft_add_priority_rule_pair(table, "priority_rules", section, interface_set, localv4_set, localv6_set, match_ip_port4_tcp, match_ip_port6_tcp, mark) ||
|
|
!nft_add_priority_rule_pair(table, "priority_rules", section, interface_set, localv4_set, localv6_set, match_ip_port4_udp, match_ip_port6_udp, mark) ||
|
|
!nft_add_priority_rule_pair(table, "priority_output_rules", section, interface_set, localv4_set, localv6_set, match_ip_port4_tcp, match_ip_port6_tcp, mark) ||
|
|
!nft_add_priority_rule_pair(table, "priority_output_rules", section, interface_set, localv4_set, localv6_set, match_ip_port4_udp, match_ip_port6_udp, mark)))
|
|
return false;
|
|
|
|
if (has_port_only_matchers &&
|
|
(!nft_add_priority_rule_pair(table, "priority_rules", section, interface_set, localv4_set, localv6_set, match_port4_tcp, match_port6_tcp, mark) ||
|
|
!nft_add_priority_rule_pair(table, "priority_rules", section, interface_set, localv4_set, localv6_set, match_port4_udp, match_port6_udp, mark) ||
|
|
!nft_add_priority_rule_pair(table, "priority_output_rules", section, interface_set, localv4_set, localv6_set, match_port4_tcp, match_port6_tcp, mark) ||
|
|
!nft_add_priority_rule_pair(table, "priority_output_rules", section, interface_set, localv4_set, localv6_set, match_port4_udp, match_port6_udp, mark)))
|
|
return false;
|
|
|
|
if (section_has_dscp_matchers(section)) {
|
|
let dscp_vals = connections.dscp_list(section);
|
|
let dscp_str = length(dscp_vals) == 1 ? as_string(dscp_vals[0]) : "{ " + join(", ", dscp_vals) + " }";
|
|
let match_dscp4 = [ "ip", "dscp", dscp_str ];
|
|
let match_dscp6 = [ "ip6", "dscp", dscp_str ];
|
|
if (!nft_add_priority_rule_pair(table, "priority_rules", section, interface_set, localv4_set, localv6_set, match_dscp4, match_dscp6, mark) ||
|
|
!nft_add_priority_rule_pair(table, "priority_output_rules", section, interface_set, localv4_set, localv6_set, match_dscp4, match_dscp6, mark))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function nft_add_section_priority_rules_from_sections(sections, table, interface_set, localv4_set, localv6_set, mark) {
|
|
localv6_set = default_arg(localv6_set, "localv6");
|
|
let zapret2_idx = 1;
|
|
let zapret_idx = 1;
|
|
for (let section in sections) {
|
|
section = object_or_empty(section);
|
|
if (!bool_option(section, "enabled", true))
|
|
continue;
|
|
let sec_mark = mark;
|
|
let action = option(section, "action", "connection");
|
|
if (action == "zapret2") {
|
|
sec_mark = sprintf("0x%08x", 0x02000000 + zapret2_idx);
|
|
zapret2_idx++;
|
|
} else if (action == "zapret") {
|
|
sec_mark = sprintf("0x%08x", 0x01000000 + zapret_idx);
|
|
zapret_idx++;
|
|
}
|
|
if (!nft_add_section_priority_rules(table, section, interface_set, localv4_set, localv6_set, sec_mark))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function normalize_schedule_day_name(day) {
|
|
let d = lc(trim(as_string(day)));
|
|
let map = {
|
|
"mon": "Monday", "monday": "Monday", "1": "Monday",
|
|
"tue": "Tuesday", "tuesday": "Tuesday", "2": "Tuesday",
|
|
"wed": "Wednesday", "wednesday": "Wednesday", "3": "Wednesday",
|
|
"thu": "Thursday", "thursday": "Thursday", "4": "Thursday",
|
|
"fri": "Friday", "friday": "Friday", "5": "Friday",
|
|
"sat": "Saturday", "saturday": "Saturday", "6": "Saturday",
|
|
"sun": "Sunday", "sunday": "Sunday", "7": "Sunday", "0": "Sunday"
|
|
};
|
|
return map[d] || null;
|
|
}
|
|
|
|
function nft_schedule_days_match_args(schedule) {
|
|
let raw_days = list_option(schedule, "days");
|
|
if (length(raw_days) == 0)
|
|
return [];
|
|
|
|
let days_set = {};
|
|
for (let day in raw_days) {
|
|
let norm = normalize_schedule_day_name(day);
|
|
if (norm) days_set[norm] = true;
|
|
}
|
|
let day_names = sort(keys(days_set));
|
|
if (length(day_names) == 0 || length(day_names) == 7)
|
|
return [];
|
|
|
|
if (length(day_names) == 1)
|
|
return [ "meta", "day", day_names[0] ];
|
|
|
|
return [ "meta", "day", "{ " + join(", ", day_names) + " }" ];
|
|
}
|
|
|
|
function nft_schedule_time_intervals(start_time, end_time) {
|
|
start_time = trim(as_string(start_time));
|
|
end_time = trim(as_string(end_time));
|
|
|
|
if (start_time == "" || end_time == "")
|
|
return [ [ "00:00:00", "23:59:59" ] ];
|
|
|
|
if (length(start_time) == 5) start_time += ":00";
|
|
if (length(end_time) == 5) end_time += ":00";
|
|
|
|
if (start_time > end_time) {
|
|
// Crosses midnight, e.g. 22:00:00 to 08:00:00
|
|
return [
|
|
[ start_time, "23:59:59" ],
|
|
[ "00:00:00", end_time ]
|
|
];
|
|
}
|
|
|
|
return [ [ start_time, end_time ] ];
|
|
}
|
|
|
|
function nft_add_schedule_rules_from_schedules(schedules, sections, table) {
|
|
for (let schedule in schedules) {
|
|
schedule = object_or_empty(schedule);
|
|
if (!bool_option(schedule, "enabled", true))
|
|
continue;
|
|
|
|
let raw_ips = list_option(schedule, "device_ip");
|
|
if (length(raw_ips) == 0) {
|
|
let single_ip = option(schedule, "device_ip", "");
|
|
if (single_ip != "") raw_ips = [ single_ip ];
|
|
}
|
|
if (length(raw_ips) == 0)
|
|
continue;
|
|
|
|
let start_time = option(schedule, "start_time", "");
|
|
let end_time = option(schedule, "end_time", "");
|
|
let intervals = nft_schedule_time_intervals(start_time, end_time);
|
|
let days_args = nft_schedule_days_match_args(schedule);
|
|
let target = option(schedule, "target", "all");
|
|
let action = option(schedule, "action", "block");
|
|
let verdict = action == "allow" ? "accept" : "drop";
|
|
|
|
let target_sec_names = [];
|
|
if (target == "sections" || (target != "all" && target != "")) {
|
|
let raw_secs = list_option(schedule, "sections");
|
|
if (length(raw_secs) == 0) {
|
|
let single_sec = option(schedule, "sections", "");
|
|
if (single_sec != "") raw_secs = [ single_sec ];
|
|
else if (target != "sections") raw_secs = [ target ];
|
|
}
|
|
target_sec_names = raw_secs;
|
|
}
|
|
|
|
for (let raw_ip in raw_ips) {
|
|
let dev_str = trim(as_string(raw_ip));
|
|
if (dev_str == "") continue;
|
|
let is_mac = match(dev_str, /^([0-9a-fA-F]{2}[:-]){5}[0-9a-fA-F]{2}$/) != null;
|
|
let family = is_mac ? 0 : core_ip.ip_family(dev_str);
|
|
let saddr_key = family == 6 ? "ip6" : "ip";
|
|
|
|
for (let interval in intervals) {
|
|
let time_args = [ "meta", "hour", sprintf("\"%s\"-\"%s\"", interval[0], interval[1]) ];
|
|
let base_match = is_mac ?
|
|
[ "ether", "saddr", lc(replace(dev_str, "-", ":")) ] :
|
|
[ saddr_key, "saddr", dev_str ];
|
|
append_array(base_match, days_args);
|
|
append_array(base_match, time_args);
|
|
|
|
if (target == "all" || (length(target_sec_names) == 0 && target != "sections")) {
|
|
let fwd_rule = [];
|
|
append_array(fwd_rule, base_match);
|
|
append_array(fwd_rule, [ "counter", verdict ]);
|
|
nft_add_rule(table, "parental_forward", fwd_rule);
|
|
|
|
let ctrl_rule = [];
|
|
append_array(ctrl_rule, base_match);
|
|
append_array(ctrl_rule, [ "counter", verdict ]);
|
|
nft_add_rule(table, "parental_control", ctrl_rule);
|
|
} else {
|
|
for (let sec_name in target_sec_names) {
|
|
let target_section = section_by_name(sections, sec_name);
|
|
if (!target_section) continue;
|
|
|
|
let sets = section_priority_sets(target_section);
|
|
if (!sets) continue;
|
|
|
|
if (sets.subnets) {
|
|
let sub_rule = [];
|
|
append_array(sub_rule, base_match);
|
|
append_array(sub_rule, [ "ip", "daddr", "@" + as_string(sets.subnets), "counter", verdict ]);
|
|
nft_add_rule(table, "parental_control", sub_rule);
|
|
nft_add_rule(table, "parental_forward", sub_rule);
|
|
}
|
|
if (sets.subnets6 && (family == 6 || is_mac)) {
|
|
let sub6_rule = [];
|
|
append_array(sub6_rule, base_match);
|
|
append_array(sub6_rule, [ "ip6", "daddr", "@" + as_string(sets.subnets6), "counter", verdict ]);
|
|
nft_add_rule(table, "parental_control", sub6_rule);
|
|
nft_add_rule(table, "parental_forward", sub6_rule);
|
|
}
|
|
if (sets.ip_ports) {
|
|
let ipport_tcp = [];
|
|
append_array(ipport_tcp, base_match);
|
|
append_array(ipport_tcp, [ "ip", "daddr", ".", "tcp", "dport", "@" + as_string(sets.ip_ports), "counter", verdict ]);
|
|
nft_add_rule(table, "parental_control", ipport_tcp);
|
|
nft_add_rule(table, "parental_forward", ipport_tcp);
|
|
|
|
let ipport_udp = [];
|
|
append_array(ipport_udp, base_match);
|
|
append_array(ipport_udp, [ "ip", "daddr", ".", "udp", "dport", "@" + as_string(sets.ip_ports), "counter", verdict ]);
|
|
nft_add_rule(table, "parental_control", ipport_udp);
|
|
nft_add_rule(table, "parental_forward", ipport_udp);
|
|
}
|
|
if (sets.ports) {
|
|
let port_tcp = [];
|
|
append_array(port_tcp, base_match);
|
|
append_array(port_tcp, [ "tcp", "dport", "@" + as_string(sets.ports), "counter", verdict ]);
|
|
nft_add_rule(table, "parental_control", port_tcp);
|
|
nft_add_rule(table, "parental_forward", port_tcp);
|
|
|
|
let port_udp = [];
|
|
append_array(port_udp, base_match);
|
|
append_array(port_udp, [ "udp", "dport", "@" + as_string(sets.ports), "counter", verdict ]);
|
|
nft_add_rule(table, "parental_control", port_udp);
|
|
nft_add_rule(table, "parental_forward", port_udp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function nft_add_schedule_rules_from_uci(table, sections) {
|
|
return nft_add_schedule_rules_from_schedules(uci_sections("schedule"), sections, table);
|
|
}
|
|
|
|
function nft_create_runtime_base(table, localv4_set, common_set, port_set, ip_port_set, interface_set, source_interfaces, fakeip_mark, outbound_mark, fakeip_range, tproxy_port, exclude_ntp, localv6_set, common6_set, ip_port6_set, fakeip6_range, tproxy6_address) {
|
|
localv6_set = default_arg(localv6_set, "localv6");
|
|
common6_set = default_arg(common6_set, "tachyon_subnets6");
|
|
ip_port6_set = default_arg(ip_port6_set, "tachyon_ip6_ports");
|
|
fakeip6_range = default_arg(fakeip6_range, "fc00::/18");
|
|
tproxy6_address = default_arg(tproxy6_address, "::1");
|
|
|
|
if (!nft_create_table(table) ||
|
|
!nft_create_ipv4_set(table, localv4_set) ||
|
|
!nft_add_set_elements(table, localv4_set, join(",", LOCALV4_RANGES)) ||
|
|
!nft_create_ipv6_set(table, localv6_set) ||
|
|
!nft_add_set_elements(table, localv6_set, join(",", LOCALV6_RANGES)) ||
|
|
!nft_create_ipv4_set(table, common_set) ||
|
|
!nft_create_ipv6_set(table, common6_set) ||
|
|
!nft_create_inet_service_set(table, port_set) ||
|
|
!nft_create_ipv4_port_set(table, ip_port_set) ||
|
|
!nft_create_ipv6_port_set(table, ip_port6_set) ||
|
|
!nft_create_ifname_set(table, interface_set))
|
|
return false;
|
|
|
|
for (let interface in whitespace_values(source_interfaces))
|
|
if (!nft_add_set_elements(table, interface_set, interface))
|
|
return false;
|
|
|
|
if (!nft_create_chain(table, "mangle", "{ type filter hook prerouting priority -149; policy accept; }") ||
|
|
!nft_create_chain(table, "mangle_output", "{ type route hook output priority -150; policy accept; }") ||
|
|
!nft_create_priority_chains(table) ||
|
|
!nft_create_chain(table, "parental_control", "{ }") ||
|
|
!nft_create_chain(table, "parental_forward", "{ }") ||
|
|
!nft_create_chain(table, "proxy", "{ type filter hook prerouting priority -100; policy accept; }"))
|
|
return false;
|
|
|
|
if (!nft_add_rule(table, "mangle", [ "ct", "status", "dnat", "return" ]) ||
|
|
!nft_add_rule(table, "mangle", [ "jump", "parental_control" ]) ||
|
|
!nft_add_rule(table, "mangle", [ "iifname", "@" + as_string(interface_set), "ip", "daddr", "@" + as_string(localv4_set), "return" ]) ||
|
|
!nft_add_rule(table, "mangle", [ "iifname", "@" + as_string(interface_set), "ip6", "daddr", "@" + as_string(localv6_set), "return" ]))
|
|
return false;
|
|
|
|
let console_ips_list = list_option(uci_settings(), "game_console_ips");
|
|
if (bool_option(uci_settings(), "game_console_optimizer", false) && length(console_ips_list) > 0) {
|
|
let v4_ips = [];
|
|
let v6_ips = [];
|
|
for (let ip in console_ips_list) {
|
|
if (core_ip.ip_family(ip) == 4) push(v4_ips, ip);
|
|
else if (core_ip.ip_family(ip) == 6) push(v6_ips, ip);
|
|
}
|
|
if (length(v4_ips) > 0) {
|
|
if (!nft_create_ipv4_set(table, "tachyon_consoles") ||
|
|
!nft_add_set_elements(table, "tachyon_consoles", join(",", v4_ips)) ||
|
|
!nft_add_rule(table, "mangle", [ "iifname", "@" + as_string(interface_set), "ip", "saddr", "@tachyon_consoles", "meta", "l4proto", "udp", "counter", "return" ]))
|
|
return false;
|
|
}
|
|
if (length(v6_ips) > 0) {
|
|
if (!nft_create_ipv6_set(table, "tachyon_consoles6") ||
|
|
!nft_add_set_elements(table, "tachyon_consoles6", join(",", v6_ips)) ||
|
|
!nft_add_rule(table, "mangle", [ "iifname", "@" + as_string(interface_set), "ip6", "saddr", "@tachyon_consoles6", "meta", "l4proto", "udp", "counter", "return" ]))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (bool_option(uci_settings(), "webrtc_leak_protect", false)) {
|
|
if (!nft_add_rule(table, "mangle", [ "iifname", "@" + as_string(interface_set), "udp", "dport", "3478", "counter", "drop" ]) ||
|
|
!nft_add_rule(table, "mangle", [ "iifname", "@" + as_string(interface_set), "udp", "dport", "5349", "counter", "drop" ]) ||
|
|
!nft_add_rule(table, "mangle", [ "iifname", "@" + as_string(interface_set), "udp", "dport", "19302", "counter", "drop" ]))
|
|
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" ]) ||
|
|
!nft_add_rule(table, "mangle", [ "iifname", "@" + as_string(interface_set), "ip6", "daddr", "@" + as_string(common6_set), "meta", "l4proto", "tcp", "meta", "mark", "set", fakeip_mark, "counter" ]) ||
|
|
!nft_add_rule(table, "mangle", [ "iifname", "@" + as_string(interface_set), "ip6", "daddr", "@" + as_string(common6_set), "meta", "l4proto", "udp", "meta", "mark", "set", fakeip_mark, "counter" ]) ||
|
|
!nft_add_rule(table, "mangle", [ "iifname", "@" + as_string(interface_set), "ip", "daddr", ".", "tcp", "dport", "@" + as_string(ip_port_set), "meta", "mark", "set", fakeip_mark, "counter" ]) ||
|
|
!nft_add_rule(table, "mangle", [ "iifname", "@" + as_string(interface_set), "ip", "daddr", ".", "udp", "dport", "@" + as_string(ip_port_set), "meta", "mark", "set", fakeip_mark, "counter" ]) ||
|
|
!nft_add_rule(table, "mangle", [ "iifname", "@" + as_string(interface_set), "ip6", "daddr", ".", "tcp", "dport", "@" + as_string(ip_port6_set), "meta", "mark", "set", fakeip_mark, "counter" ]) ||
|
|
!nft_add_rule(table, "mangle", [ "iifname", "@" + as_string(interface_set), "ip6", "daddr", ".", "udp", "dport", "@" + as_string(ip_port6_set), "meta", "mark", "set", fakeip_mark, "counter" ]) ||
|
|
!nft_add_rule(table, "mangle", [ "iifname", "@" + as_string(interface_set), "ip", "daddr", "!=", "@" + as_string(localv4_set), "tcp", "dport", "@" + as_string(port_set), "meta", "mark", "set", fakeip_mark, "counter" ]) ||
|
|
!nft_add_rule(table, "mangle", [ "iifname", "@" + as_string(interface_set), "ip", "daddr", "!=", "@" + as_string(localv4_set), "udp", "dport", "@" + as_string(port_set), "meta", "mark", "set", fakeip_mark, "counter" ]) ||
|
|
!nft_add_rule(table, "mangle", [ "iifname", "@" + as_string(interface_set), "ip6", "daddr", "!=", "@" + as_string(localv6_set), "tcp", "dport", "@" + as_string(port_set), "meta", "mark", "set", fakeip_mark, "counter" ]) ||
|
|
!nft_add_rule(table, "mangle", [ "iifname", "@" + as_string(interface_set), "ip6", "daddr", "!=", "@" + as_string(localv6_set), "udp", "dport", "@" + as_string(port_set), "meta", "mark", "set", fakeip_mark, "counter" ]) ||
|
|
!nft_add_rule(table, "mangle", [ "iifname", "@" + as_string(interface_set), "ip", "daddr", fakeip_range, "meta", "l4proto", "tcp", "meta", "mark", "set", fakeip_mark, "counter" ]) ||
|
|
!nft_add_rule(table, "mangle", [ "iifname", "@" + as_string(interface_set), "ip", "daddr", fakeip_range, "meta", "l4proto", "udp", "meta", "mark", "set", fakeip_mark, "counter" ]) ||
|
|
!nft_add_rule(table, "mangle", [ "iifname", "@" + as_string(interface_set), "ip6", "daddr", fakeip6_range, "meta", "l4proto", "tcp", "meta", "mark", "set", fakeip_mark, "counter" ]) ||
|
|
!nft_add_rule(table, "mangle", [ "iifname", "@" + as_string(interface_set), "ip6", "daddr", fakeip6_range, "meta", "l4proto", "udp", "meta", "mark", "set", fakeip_mark, "counter" ]) ||
|
|
!nft_add_rule(table, "proxy", [ "meta", "mark", "&", fakeip_mark, "==", fakeip_mark, "meta", "l4proto", "tcp", "tproxy", "ip", "to", ":" + as_string(tproxy_port), "counter" ]) ||
|
|
!nft_add_rule(table, "proxy", [ "meta", "mark", "&", fakeip_mark, "==", fakeip_mark, "meta", "l4proto", "udp", "tproxy", "ip", "to", ":" + as_string(tproxy_port), "counter" ]) ||
|
|
!nft_add_rule(table, "proxy", [ "meta", "mark", "&", fakeip_mark, "==", fakeip_mark, "meta", "l4proto", "tcp", "tproxy", "ip6", "to", core_ip.format_ipv6_tproxy_target(tproxy6_address, tproxy_port), "counter" ]) ||
|
|
!nft_add_rule(table, "proxy", [ "meta", "mark", "&", fakeip_mark, "==", fakeip_mark, "meta", "l4proto", "udp", "tproxy", "ip6", "to", core_ip.format_ipv6_tproxy_target(tproxy6_address, tproxy_port), "counter" ]) ||
|
|
!nft_add_rule(table, "mangle_output", [ "ip", "daddr", "@" + as_string(localv4_set), "return" ]) ||
|
|
!nft_add_rule(table, "mangle_output", [ "ip6", "daddr", "@" + as_string(localv6_set), "return" ]) ||
|
|
!nft_add_rule(table, "mangle_output", [ "meta", "mark", outbound_mark, "counter", "return" ]) ||
|
|
!nft_add_rule(table, "mangle_output", [ "jump", "priority_output_rules" ]))
|
|
return false;
|
|
|
|
if (!nft_create_chain(table, "mangle_forward", "{ type filter hook forward priority -150; policy accept; }") ||
|
|
!nft_add_rule(table, "mangle_forward", [ "jump", "parental_forward" ]))
|
|
return false;
|
|
|
|
let rtmtu_ok = nft_add_rule(table, "mangle_forward", [ "tcp", "flags", "syn", "tcp", "option", "maxseg", "size", "set", "rt", "mtu" ]);
|
|
if (rtmtu_ok) {
|
|
if (!nft_add_rule(table, "mangle_output", [ "tcp", "flags", "syn", "tcp", "option", "maxseg", "size", "set", "rt", "mtu" ]))
|
|
return false;
|
|
} else {
|
|
if (!nft_add_rule(table, "mangle_forward", [ "tcp", "flags", "syn", "tcp", "option", "maxseg", "size", "set", "1400" ]) ||
|
|
!nft_add_rule(table, "mangle_output", [ "tcp", "flags", "syn", "tcp", "option", "maxseg", "size", "set", "1400" ]))
|
|
return false;
|
|
}
|
|
|
|
if (arg_bool(exclude_ntp) && !nft_insert_rule(table, "mangle", [ "udp", "dport", "123", "return" ]))
|
|
return false;
|
|
|
|
// QoS Low-Latency Gaming & Voice Acceleration Engine
|
|
let qos_enabled = uci_settings().qos_priority_engine != "0";
|
|
if (qos_enabled) {
|
|
// Voice & Discord RTC (DSCP EF 0x2e)
|
|
nft_add_rule(table, "mangle_forward", [ "udp", "dport", "{ 5000-5020, 3478, 19302, 50000-65535 }", "ip", "dscp", "set", "0x2e" ]);
|
|
nft_add_rule(table, "mangle_output", [ "udp", "dport", "{ 5000-5020, 3478, 19302, 50000-65535 }", "ip", "dscp", "set", "0x2e" ]);
|
|
|
|
// Gaming Traffic (Steam, CS, Dota, Valorant, Apex, PUBG, Roblox) (DSCP AF41 0x22)
|
|
nft_add_rule(table, "mangle_forward", [ "udp", "dport", "{ 3074, 7000-9000, 27000-27050, 28960 }", "ip", "dscp", "set", "0x22" ]);
|
|
nft_add_rule(table, "mangle_output", [ "udp", "dport", "{ 3074, 7000-9000, 27000-27050, 28960 }", "ip", "dscp", "set", "0x22" ]);
|
|
|
|
// TCP ACK Acceleration (DSCP CS2)
|
|
nft_add_rule(table, "mangle_forward", [ "tcp", "flags", "&", "(fin|syn|rst|ack)", "==", "ack", "ip", "dscp", "set", "cs2" ]);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function nft_create_runtime_base_from_uci(table, localv4_set, common_set, port_set, ip_port_set, interface_set, fakeip_mark, outbound_mark, fakeip_range, tproxy_port, localv6_set, common6_set, ip_port6_set, fakeip6_range, tproxy6_address) {
|
|
let settings = uci_settings();
|
|
|
|
return nft_create_runtime_base(
|
|
table,
|
|
localv4_set,
|
|
common_set,
|
|
port_set,
|
|
ip_port_set,
|
|
interface_set,
|
|
option(settings, "source_network_interfaces", "br-lan"),
|
|
fakeip_mark,
|
|
outbound_mark,
|
|
fakeip_range,
|
|
tproxy_port,
|
|
option(settings, "exclude_ntp", "0"),
|
|
localv6_set,
|
|
common6_set,
|
|
ip_port6_set,
|
|
fakeip6_range,
|
|
tproxy6_address
|
|
);
|
|
}
|
|
|
|
function nft_create_runtime_output_rules(table, localv4_set, common_set, port_set, ip_port_set, fakeip_mark, fakeip_range, localv6_set, common6_set, ip_port6_set, fakeip6_range) {
|
|
localv6_set = default_arg(localv6_set, "localv6");
|
|
common6_set = default_arg(common6_set, "tachyon_subnets6");
|
|
ip_port6_set = default_arg(ip_port6_set, "tachyon_ip6_ports");
|
|
fakeip6_range = default_arg(fakeip6_range, "fc00::/18");
|
|
|
|
return (
|
|
nft_add_rule(table, "mangle_output", [ "ip", "daddr", "@" + as_string(common_set), "meta", "l4proto", "tcp", "meta", "mark", "set", fakeip_mark, "counter" ]) &&
|
|
nft_add_rule(table, "mangle_output", [ "ip", "daddr", "@" + as_string(common_set), "meta", "l4proto", "udp", "meta", "mark", "set", fakeip_mark, "counter" ]) &&
|
|
nft_add_rule(table, "mangle_output", [ "ip6", "daddr", "@" + as_string(common6_set), "meta", "l4proto", "tcp", "meta", "mark", "set", fakeip_mark, "counter" ]) &&
|
|
nft_add_rule(table, "mangle_output", [ "ip6", "daddr", "@" + as_string(common6_set), "meta", "l4proto", "udp", "meta", "mark", "set", fakeip_mark, "counter" ]) &&
|
|
nft_add_rule(table, "mangle_output", [ "ip", "daddr", ".", "tcp", "dport", "@" + as_string(ip_port_set), "meta", "mark", "set", fakeip_mark, "counter" ]) &&
|
|
nft_add_rule(table, "mangle_output", [ "ip", "daddr", ".", "udp", "dport", "@" + as_string(ip_port_set), "meta", "mark", "set", fakeip_mark, "counter" ]) &&
|
|
nft_add_rule(table, "mangle_output", [ "ip6", "daddr", ".", "tcp", "dport", "@" + as_string(ip_port6_set), "meta", "mark", "set", fakeip_mark, "counter" ]) &&
|
|
nft_add_rule(table, "mangle_output", [ "ip6", "daddr", ".", "udp", "dport", "@" + as_string(ip_port6_set), "meta", "mark", "set", fakeip_mark, "counter" ]) &&
|
|
nft_add_rule(table, "mangle_output", [ "tcp", "dport", "@" + as_string(port_set), "meta", "mark", "set", fakeip_mark, "counter" ]) &&
|
|
nft_add_rule(table, "mangle_output", [ "udp", "dport", "@" + as_string(port_set), "meta", "mark", "set", fakeip_mark, "counter" ]) &&
|
|
nft_add_rule(table, "mangle_output", [ "ip", "daddr", fakeip_range, "meta", "l4proto", "tcp", "meta", "mark", "set", fakeip_mark, "counter" ]) &&
|
|
nft_add_rule(table, "mangle_output", [ "ip", "daddr", fakeip_range, "meta", "l4proto", "udp", "meta", "mark", "set", fakeip_mark, "counter" ]) &&
|
|
nft_add_rule(table, "mangle_output", [ "ip6", "daddr", fakeip6_range, "meta", "l4proto", "tcp", "meta", "mark", "set", fakeip_mark, "counter" ]) &&
|
|
nft_add_rule(table, "mangle_output", [ "ip6", "daddr", fakeip6_range, "meta", "l4proto", "udp", "meta", "mark", "set", fakeip_mark, "counter" ])
|
|
);
|
|
}
|
|
|
|
function hex_digit_value(value) {
|
|
let pos = index("0123456789abcdef", lc(as_string(value)));
|
|
return pos >= 0 ? pos : null;
|
|
}
|
|
|
|
function parse_mark_number(value) {
|
|
value = lc(trim(as_string(value)));
|
|
if (value == "")
|
|
return null;
|
|
|
|
if (substr(value, 0, 2) == "0x") {
|
|
value = substr(value, 2);
|
|
if (value == "")
|
|
return null;
|
|
|
|
let result = 0;
|
|
for (let i = 0; i < length(value); i++) {
|
|
let digit = hex_digit_value(substr(value, i, 1));
|
|
if (digit == null)
|
|
return null;
|
|
result = result * 16 + digit;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
return match(value, /^[0-9]+$/) == null ? null : int(value);
|
|
}
|
|
|
|
function nft_provider_mark_hex(route_mark_base, index) {
|
|
let base = parse_mark_number(route_mark_base);
|
|
index = int(index || 0);
|
|
if (base == null || index < 1)
|
|
return "";
|
|
|
|
return sprintf("0x%08x", base + index);
|
|
}
|
|
|
|
function nft_create_provider_output_rules_from_sections(sections, table, action, provider_bin, route_mark_base, queue_base, desync_mark, desync_mark_postnat) {
|
|
if (!file_executable(provider_bin))
|
|
return true;
|
|
|
|
let index = 0;
|
|
let added = false;
|
|
|
|
for (let section in sections) {
|
|
section = object_or_empty(section);
|
|
if (!bool_option(section, "enabled", true) || option(section, "action", "") != action)
|
|
continue;
|
|
|
|
index++;
|
|
let mark_hex = nft_provider_mark_hex(route_mark_base, index);
|
|
let queue_number = int(queue_base || 0) + index - 1;
|
|
if (mark_hex == "" || queue_number < 0)
|
|
return false;
|
|
|
|
if (!added) {
|
|
if (!nft_add_rule(table, "mangle_output", [ "meta", "mark", "&", desync_mark, "==", desync_mark, "return" ]) ||
|
|
!nft_add_rule(table, "mangle_output", [ "meta", "mark", "&", desync_mark_postnat, "==", desync_mark_postnat, "return" ]))
|
|
return false;
|
|
added = true;
|
|
}
|
|
|
|
if (!nft_add_rule(table, "mangle_output", [ "meta", "mark", mark_hex, "meta", "l4proto", "tcp", "counter", "queue", "num", queue_number, "bypass" ]) ||
|
|
!nft_add_rule(table, "mangle_output", [ "meta", "mark", mark_hex, "meta", "l4proto", "udp", "counter", "queue", "num", queue_number, "bypass" ]))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function nft_write_chunk(chunks, chunk) {
|
|
if (length(chunk) > 0)
|
|
push(chunks, "" + length(chunk) + "\t" + join(",", chunk));
|
|
}
|
|
|
|
function nft_push_chunk_value(chunks, chunk, value, chunk_size) {
|
|
push(chunk, value);
|
|
if (length(chunk) < chunk_size)
|
|
return chunk;
|
|
|
|
nft_write_chunk(chunks, chunk);
|
|
return [];
|
|
}
|
|
|
|
function nft_invalid(invalid, value, message) {
|
|
push(invalid, as_string(value) + "\t" + message);
|
|
}
|
|
|
|
function nft_trimmed_lines(path) {
|
|
let data = fs.readfile(path);
|
|
if (data == null)
|
|
exit(1);
|
|
|
|
let result = [];
|
|
for (let line in split(as_string(data), "\n")) {
|
|
line = trim(replace(as_string(line), /\r/g, ""));
|
|
if (line != "")
|
|
push(result, line);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function nft_chunk_size(value) {
|
|
value = int(value || 5000);
|
|
return value > 0 ? value : 5000;
|
|
}
|
|
|
|
function nft_csv_values(csv) {
|
|
let result = [];
|
|
|
|
for (let item in split(as_string(csv), ",")) {
|
|
item = trim(replace(as_string(item), /\r/g, ""));
|
|
if (item != "")
|
|
push(result, item);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function nft_build_chunks_from_values(values, kind, ports_csv, chunk_size_text, family_filter) {
|
|
let chunk_size = nft_chunk_size(chunk_size_text);
|
|
let chunks = [];
|
|
let invalid = [];
|
|
let chunk = [];
|
|
let ports = split(as_string(ports_csv), ",");
|
|
family_filter = int(family_filter || 0);
|
|
|
|
for (let line in values) {
|
|
if (kind == "ports") {
|
|
let port = normalize_port_condition_value(line);
|
|
if (port == null) {
|
|
nft_invalid(invalid, line, "is not a valid port or port range");
|
|
continue;
|
|
}
|
|
chunk = nft_push_chunk_value(chunks, chunk, port, chunk_size);
|
|
continue;
|
|
}
|
|
|
|
if (kind == "ip-ports") {
|
|
let separator = index(line, " . ");
|
|
let last_separator = rindex(line, " . ");
|
|
if (separator < 0 || last_separator < 0) {
|
|
nft_invalid(invalid, line, "is not an IP/CIDR and port nft tuple");
|
|
continue;
|
|
}
|
|
|
|
let ip = substr(line, 0, separator);
|
|
let port = substr(line, last_separator + 3);
|
|
let original_port = port;
|
|
if (!nft_ip_or_cidr(ip)) {
|
|
nft_invalid(invalid, ip, "is not IP or CIDR");
|
|
continue;
|
|
}
|
|
|
|
if (family_filter != 0 && core_ip.ip_family(ip) != family_filter)
|
|
continue;
|
|
|
|
port = normalize_port_condition_value(port);
|
|
if (port == null) {
|
|
nft_invalid(invalid, original_port, "is not a valid port or port range");
|
|
continue;
|
|
}
|
|
|
|
chunk = nft_push_chunk_value(chunks, chunk, ip + " . " + port, chunk_size);
|
|
continue;
|
|
}
|
|
|
|
if (!nft_ip_or_cidr(line)) {
|
|
nft_invalid(invalid, line, "is not IP or CIDR");
|
|
continue;
|
|
}
|
|
|
|
if (family_filter != 0 && core_ip.ip_family(line) != family_filter)
|
|
continue;
|
|
|
|
if (kind == "ip-port-from-ip") {
|
|
for (let port in ports) {
|
|
if (port == "")
|
|
continue;
|
|
|
|
let normalized = normalize_port_condition_value(port);
|
|
if (normalized == null) {
|
|
nft_invalid(invalid, port, "is not a valid port or port range");
|
|
continue;
|
|
}
|
|
|
|
chunk = nft_push_chunk_value(chunks, chunk, line + " . " + normalized, chunk_size);
|
|
}
|
|
}
|
|
else if (kind == "ips") {
|
|
chunk = nft_push_chunk_value(chunks, chunk, line, chunk_size);
|
|
}
|
|
else {
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
nft_write_chunk(chunks, chunk);
|
|
|
|
return {
|
|
chunks: chunks,
|
|
invalid: invalid
|
|
};
|
|
}
|
|
|
|
function nft_build_chunks(path, kind, ports_csv, chunk_size_text) {
|
|
return nft_build_chunks_from_values(nft_trimmed_lines(path), kind, ports_csv, chunk_size_text, 0);
|
|
}
|
|
|
|
function nft_prepare_chunks(path, kind, ports_csv, chunk_size_text, chunks_path, invalid_path) {
|
|
let prepared = nft_build_chunks(path, kind, ports_csv, chunk_size_text);
|
|
|
|
if (!write_text_file(chunks_path, length(prepared.chunks) > 0 ? join("\n", prepared.chunks) + "\n" : ""))
|
|
exit(1);
|
|
if (!write_text_file(invalid_path, length(prepared.invalid) > 0 ? join("\n", prepared.invalid) + "\n" : ""))
|
|
exit(1);
|
|
}
|
|
|
|
function nft_log_invalid_elements(invalid) {
|
|
for (let item in invalid) {
|
|
let separator = index(item, "\t");
|
|
if (separator < 0)
|
|
continue;
|
|
|
|
let value = substr(item, 0, separator);
|
|
let message = substr(item, separator + 1);
|
|
if (value != "")
|
|
log_debug("'" + value + "' " + message);
|
|
}
|
|
}
|
|
|
|
function nft_add_chunks_to_set(table, set_name, chunks, invalid) {
|
|
nft_log_invalid_elements(invalid);
|
|
|
|
for (let item in chunks) {
|
|
let separator = index(item, "\t");
|
|
if (separator < 0)
|
|
continue;
|
|
|
|
let count = substr(item, 0, separator);
|
|
let elements = substr(item, separator + 1);
|
|
if (elements == "")
|
|
continue;
|
|
|
|
log_debug("Adding " + count + " elements to nft set " + set_name);
|
|
if (!nft_add_set_elements(table, set_name, elements))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function nft_add_file_chunks_to_set(path, table, set_name, kind, ports_csv, chunk_size_text, family_filter) {
|
|
let prepared = nft_build_chunks_from_values(nft_trimmed_lines(path), kind, ports_csv, chunk_size_text, family_filter);
|
|
return nft_add_chunks_to_set(table, set_name, prepared.chunks, prepared.invalid);
|
|
}
|
|
|
|
function nft_add_csv_chunks_to_set(csv, table, set_name, kind, ports_csv, chunk_size_text, family_filter) {
|
|
let prepared = nft_build_chunks_from_values(nft_csv_values(csv), kind, ports_csv, chunk_size_text, family_filter);
|
|
return nft_add_chunks_to_set(table, set_name, prepared.chunks, prepared.invalid);
|
|
}
|
|
|
|
function nft_add_file_chunks_to_family_sets(path, table, ipv4_set, ipv6_set, kind, ports_csv, chunk_size_text) {
|
|
return nft_add_file_chunks_to_set(path, table, ipv4_set, kind, ports_csv, chunk_size_text, 4) &&
|
|
nft_add_file_chunks_to_set(path, table, ipv6_set, kind, ports_csv, chunk_size_text, 6);
|
|
}
|
|
|
|
function nft_add_csv_chunks_to_family_sets(csv, table, ipv4_set, ipv6_set, kind, ports_csv, chunk_size_text) {
|
|
return nft_add_csv_chunks_to_set(csv, table, ipv4_set, kind, ports_csv, chunk_size_text, 4) &&
|
|
nft_add_csv_chunks_to_set(csv, table, ipv6_set, kind, ports_csv, chunk_size_text, 6);
|
|
}
|
|
|
|
function nft_add_inline_ip_cidr_matchers(csv, ports_csv, table, common_set, ip_port_set, chunk_size_text, common6_set, ip_port6_set) {
|
|
if (as_string(csv) == "")
|
|
return true;
|
|
|
|
if (as_string(ports_csv) != "")
|
|
return nft_add_csv_chunks_to_family_sets(csv, table, ip_port_set, default_arg(ip_port6_set, "tachyon_ip6_ports"), "ip-port-from-ip", ports_csv, chunk_size_text);
|
|
|
|
return nft_add_csv_chunks_to_family_sets(csv, table, common_set, default_arg(common6_set, "tachyon_subnets6"), "ips", "", chunk_size_text);
|
|
}
|
|
|
|
function nft_insert_fully_routed_ip_rules(source_ip, table, interface_set, localv4_set, localv6_set, mark) {
|
|
let family = core_ip.ip_family(source_ip);
|
|
let ip_key = family == 6 ? "ip6" : "ip";
|
|
let local_set = family == 6 ? default_arg(localv6_set, "localv6") : localv4_set;
|
|
|
|
if (family == 0)
|
|
return true;
|
|
|
|
return (
|
|
run_args([ "nft", "insert", "rule", "inet", table, "mangle", "iifname", "@" + as_string(interface_set), ip_key, "saddr", source_ip, "meta", "l4proto", "tcp", "meta", "mark", "set", mark, "counter" ]) &&
|
|
run_args([ "nft", "insert", "rule", "inet", table, "mangle", "iifname", "@" + as_string(interface_set), ip_key, "saddr", source_ip, "meta", "l4proto", "udp", "meta", "mark", "set", mark, "counter" ]) &&
|
|
run_args([ "nft", "insert", "rule", "inet", table, "mangle", ip_key, "saddr", source_ip, ip_key, "daddr", "@" + as_string(local_set), "return" ])
|
|
);
|
|
}
|
|
|
|
function nft_source_ip_display_value(source_ip) {
|
|
source_ip = as_string(source_ip);
|
|
let suffix = "/32";
|
|
|
|
if (length(source_ip) > length(suffix) &&
|
|
substr(source_ip, length(source_ip) - length(suffix), length(suffix)) == suffix) {
|
|
let address = substr(source_ip, 0, length(source_ip) - length(suffix));
|
|
if (valid_ipv4(address))
|
|
return address;
|
|
}
|
|
|
|
suffix = "/128";
|
|
if (length(source_ip) > length(suffix) &&
|
|
substr(source_ip, length(source_ip) - length(suffix), length(suffix)) == suffix) {
|
|
let address = substr(source_ip, 0, length(source_ip) - length(suffix));
|
|
if (core_ip.valid_ipv6(address))
|
|
return address;
|
|
}
|
|
|
|
return source_ip;
|
|
}
|
|
|
|
function nft_chain_has_source_ip(chain_text, source_ip) {
|
|
chain_text = as_string(chain_text);
|
|
source_ip = as_string(source_ip);
|
|
|
|
let ip_key = core_ip.ip_family(source_ip) == 6 ? "ip6" : "ip";
|
|
|
|
if (index(chain_text, ip_key + " saddr " + source_ip) >= 0)
|
|
return true;
|
|
|
|
let display_source_ip = nft_source_ip_display_value(source_ip);
|
|
return display_source_ip != source_ip && index(chain_text, ip_key + " saddr " + display_source_ip) >= 0;
|
|
}
|
|
|
|
function nft_ensure_fully_routed_ip_rules_from_chain(source_ip, table, interface_set, localv4_set, localv6_set, mark, chain_text, inserted) {
|
|
source_ip = as_string(source_ip);
|
|
if (source_ip == "")
|
|
return true;
|
|
|
|
if (inserted[source_ip] || nft_chain_has_source_ip(chain_text, source_ip))
|
|
return true;
|
|
|
|
if (!nft_insert_fully_routed_ip_rules(source_ip, table, interface_set, localv4_set, localv6_set, mark))
|
|
return false;
|
|
|
|
inserted[source_ip] = true;
|
|
return true;
|
|
}
|
|
|
|
function normalized_fields(line) {
|
|
line = trim(replace(as_string(line), /\r/g, ""));
|
|
line = replace(line, /[[:space:]]+/g, " ");
|
|
return line == "" ? [] : split(line, " ");
|
|
}
|
|
|
|
function rule_line_has_lookup_table(fields, table) {
|
|
table = as_string(table);
|
|
|
|
for (let i = 0; i + 1 < length(fields); i++)
|
|
if (fields[i] == "lookup" && fields[i + 1] == table)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
function rule_line_has_fwmark(fields, expected_mark) {
|
|
for (let i = 0; i + 1 < length(fields); i++) {
|
|
if (fields[i] != "fwmark")
|
|
continue;
|
|
|
|
let parts = split(fields[i + 1], "/");
|
|
if (length(parts) != 2)
|
|
continue;
|
|
|
|
if (parse_mark_number(parts[0]) == expected_mark && parse_mark_number(parts[1]) == expected_mark)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function has_tproxy_marking_rule_text(rule_list, table, mark) {
|
|
let expected_mark = parse_mark_number(mark);
|
|
let has_lookup = false;
|
|
let has_fwmark = false;
|
|
|
|
if (expected_mark == null)
|
|
return false;
|
|
|
|
for (let line in split(rule_list, "\n")) {
|
|
let fields = normalized_fields(line);
|
|
if (length(fields) == 0)
|
|
continue;
|
|
|
|
if (!has_lookup && rule_line_has_lookup_table(fields, table))
|
|
has_lookup = true;
|
|
if (!has_fwmark && rule_line_has_fwmark(fields, expected_mark))
|
|
has_fwmark = true;
|
|
|
|
if (has_lookup && has_fwmark)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function has_local_default_route_text(route_list, family) {
|
|
family = int(family || 4);
|
|
|
|
for (let line in split(as_string(route_list), "\n")) {
|
|
line = trim(replace(as_string(line), /\r/g, ""));
|
|
line = replace(line, /[[:space:]]+/g, " ");
|
|
if (family == 4 && index(line, "local default dev lo scope host") >= 0)
|
|
return true;
|
|
if (family == 6 && (index(line, "local ::") >= 0 || index(line, "local default") >= 0) && index(line, " dev lo") >= 0)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function rt_table_has_entry(text, table_id, table_name) {
|
|
table_id = as_string(table_id);
|
|
table_name = as_string(table_name);
|
|
|
|
for (let line in split(as_string(text), "\n")) {
|
|
let fields = normalized_fields(line);
|
|
if (length(fields) >= 2 && fields[0] == table_id && fields[1] == table_name)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function ensure_rt_table_entry(path, table_id, table_name) {
|
|
let data = fs.readfile(path);
|
|
if (data != null && rt_table_has_entry(data, table_id, table_name))
|
|
return true;
|
|
|
|
data = data == null ? "" : as_string(data);
|
|
let out = [];
|
|
for (let line in split(data, "\n")) {
|
|
let fields = normalized_fields(line);
|
|
if (length(fields) >= 2 && fields[0] == as_string(table_id))
|
|
continue;
|
|
push(out, line);
|
|
}
|
|
|
|
while (length(out) > 0 && out[length(out) - 1] == "")
|
|
pop(out);
|
|
|
|
push(out, as_string(table_id) + " " + as_string(table_name));
|
|
return write_text_file(path, join("\n", out) + "\n");
|
|
}
|
|
|
|
function tproxy_route4_present(table) {
|
|
return has_local_default_route_text(command_output_quiet_from_args([ "ip", "route", "list", "table", table ]), 4);
|
|
}
|
|
|
|
function tproxy_route6_present(table) {
|
|
return has_local_default_route_text(command_output_quiet_from_args([ "ip", "-6", "route", "list", "table", table ]), 6);
|
|
}
|
|
|
|
function tproxy_route_present(table) {
|
|
return tproxy_route4_present(table) && tproxy_route6_present(table);
|
|
}
|
|
|
|
function tproxy_marking_rule4_present(table, mark) {
|
|
return has_tproxy_marking_rule_text(command_output_from_args([ "ip", "-4", "rule", "list" ]), table, mark);
|
|
}
|
|
|
|
function tproxy_marking_rule6_present(table, mark) {
|
|
return has_tproxy_marking_rule_text(command_output_from_args([ "ip", "-6", "rule", "list" ]), table, mark);
|
|
}
|
|
|
|
function tproxy_marking_rule_present(table, mark) {
|
|
return tproxy_marking_rule4_present(table, mark) && tproxy_marking_rule6_present(table, mark);
|
|
}
|
|
|
|
function tproxy_route_rule_present(table, mark) {
|
|
return tproxy_route_present(table) && tproxy_marking_rule_present(table, mark);
|
|
}
|
|
|
|
function ensure_tproxy_route_rule(table, mark, rt_tables_path) {
|
|
rt_tables_path = as_string(rt_tables_path || "/etc/iproute2/rt_tables");
|
|
|
|
if (!ensure_rt_table_entry(rt_tables_path, "105", table)) {
|
|
log_fatal("Failed to update route table registry. Aborted.");
|
|
return false;
|
|
}
|
|
|
|
if (!tproxy_route4_present(table)) {
|
|
log_debug("Added IPv4 TPROXY route");
|
|
if (!run_args([ "ip", "route", "add", "local", "0.0.0.0/0", "dev", "lo", "table", table ]) && !tproxy_route4_present(table)) {
|
|
log_fatal("Failed to add IPv4 route for tproxy. Aborted.");
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
log_debug("IPv4 TPROXY route already exists");
|
|
}
|
|
|
|
if (!tproxy_route6_present(table)) {
|
|
log_debug("Added IPv6 TPROXY route");
|
|
if (!run_args([ "ip", "-6", "route", "add", "local", "::/0", "dev", "lo", "table", table ]) && !tproxy_route6_present(table)) {
|
|
log_fatal("Failed to add IPv6 route for tproxy. Aborted.");
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
log_debug("IPv6 TPROXY route already exists");
|
|
}
|
|
|
|
if (!tproxy_marking_rule4_present(table, mark)) {
|
|
log_debug("Creating IPv4 TPROXY marking rule");
|
|
if (!run_args([ "ip", "-4", "rule", "add", "fwmark", as_string(mark) + "/" + as_string(mark), "table", table, "priority", "105" ]) && !tproxy_marking_rule4_present(table, mark)) {
|
|
log_fatal("Failed to create IPv4 marking rule. Aborted.");
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
log_debug("IPv4 TPROXY marking rule already exists");
|
|
}
|
|
|
|
if (!tproxy_marking_rule6_present(table, mark)) {
|
|
log_debug("Creating IPv6 TPROXY marking rule");
|
|
if (!run_args([ "ip", "-6", "rule", "add", "fwmark", as_string(mark) + "/" + as_string(mark), "table", table, "priority", "105" ]) && !tproxy_marking_rule6_present(table, mark)) {
|
|
log_fatal("Failed to create IPv6 marking rule. Aborted.");
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
log_debug("IPv6 TPROXY marking rule already exists");
|
|
}
|
|
|
|
let fw4_forward_out = command_output_from_args([ "nft", "list", "chain", "inet", "fw4", "forward" ]);
|
|
if (index(fw4_forward_out, "meta mark " + as_string(mark)) < 0) {
|
|
run_args([ "nft", "insert", "rule", "inet", "fw4", "forward", "meta", "mark", as_string(mark), "return" ]);
|
|
}
|
|
|
|
let fw4_input_out = command_output_from_args([ "nft", "list", "chain", "inet", "fw4", "input" ]);
|
|
if (index(fw4_input_out, "meta mark & " + as_string(mark)) < 0 && index(fw4_input_out, "meta mark " + as_string(mark)) < 0) {
|
|
run_args([ "nft", "insert", "rule", "inet", "fw4", "input", "meta", "mark", "&", as_string(mark), "==", as_string(mark), "accept", "comment", "\"Allow Tachyon TPROXY marked traffic\"" ]);
|
|
}
|
|
|
|
let fw4_include_dir = "/usr/share/nftables.d/chain-pre/input";
|
|
let fw4_include_file = fw4_include_dir + "/10-tachyon.nft";
|
|
if (fs.stat("/usr/share/nftables.d") != null) {
|
|
system(sprintf("mkdir -p %s >/dev/null 2>&1", shell_quote(fw4_include_dir)));
|
|
write_text_file(fw4_include_file, sprintf("meta mark & %s == %s accept comment \"Allow Tachyon TPROXY marked traffic\"\n", as_string(mark), as_string(mark)));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function ensure_bridge_netfilter_disabled() {
|
|
if (index(command_output_from_args([ "lsmod" ]), "br_netfilter") < 0)
|
|
return true;
|
|
|
|
if (trim(command_output_from_args([ "sysctl", "-n", "net.bridge.bridge-nf-call-iptables" ])) != "1")
|
|
return true;
|
|
|
|
log_debug("br_netfilter is enabled; disabling it for transparent proxy routing");
|
|
return run_args([ "sysctl", "-w", "net.bridge.bridge-nf-call-iptables=0" ]) &&
|
|
run_args([ "sysctl", "-w", "net.bridge.bridge-nf-call-ip6tables=0" ]);
|
|
}
|
|
|
|
function community_service_has_subnet_list(value) {
|
|
return rule_config.community_service_has_subnet_list(value);
|
|
}
|
|
|
|
function filter_community_subnet_lists_value(value) {
|
|
return rule_config.filter_community_subnet_lists_value(value);
|
|
}
|
|
|
|
function signature_add_value(body, key, value) {
|
|
return body + "[" + as_string(key) + "]\n" + as_string(value) + "\n";
|
|
}
|
|
|
|
function signature_hash(body) {
|
|
let path = trim(command_output_from_args([ "mktemp", "/tmp/tachyon-XXXXXX" ]));
|
|
if (path == "")
|
|
return "";
|
|
|
|
if (!write_text_file(path, body)) {
|
|
unlink_file(path);
|
|
return "";
|
|
}
|
|
|
|
let hash_line = command_output_from_args([ "md5sum", path ]);
|
|
unlink_file(path);
|
|
hash_line = trim(hash_line);
|
|
|
|
return length(hash_line) >= 32 ? substr(hash_line, 0, 32) : "";
|
|
}
|
|
|
|
function nft_rule_signature_body(body, section) {
|
|
let section_name = as_string(section[".name"]);
|
|
|
|
if (section_name == "" || !bool_option(section, "enabled", true))
|
|
return body;
|
|
|
|
let action = option(section, "action", "");
|
|
body = signature_add_value(body, "rule." + section_name + ".action", action);
|
|
if (action == "dns" || action == "hosts")
|
|
return body;
|
|
body = signature_add_value(body, "rule." + section_name + ".ip_cidr", section_rule_condition_csv(section, "ip_cidr", "subnets"));
|
|
body = signature_add_value(body, "rule." + section_name + ".source_ip_cidr", section_rule_condition_csv(section, "source_ip_cidr", "subnets"));
|
|
body = signature_add_value(body, "rule." + section_name + ".ports", section_rule_ports_csv(section));
|
|
body = signature_add_value(body, "rule." + section_name + ".fully_routed_ips", option(section, "fully_routed_ips", ""));
|
|
body = signature_add_value(body, "rule." + section_name + ".excluded_ips", option(section, "excluded_ips", ""));
|
|
body = signature_add_value(body, "rule." + section_name + ".excluded_protocol", option(section, "excluded_protocol", ""));
|
|
body = signature_add_value(body, "rule." + section_name + ".protocol", option(section, "protocol", ""));
|
|
body = signature_add_value(body, "rule." + section_name + ".community_subnet_lists", filter_community_subnet_lists_value(connections.community_lists_value(section)));
|
|
body = signature_add_value(body, "rule." + section_name + ".remote_subnet_lists", option(section, "remote_subnet_lists", ""));
|
|
body = signature_add_value(body, "rule." + section_name + ".rule_set_with_subnets", connections.rule_sets_with_subnets_value(section));
|
|
body = signature_add_value(body, "rule." + section_name + ".domain_ip_lists", option(section, "domain_ip_lists", ""));
|
|
body = signature_add_value(body, "rule." + section_name + ".dscp", connections.dscp_value(section));
|
|
|
|
return body;
|
|
}
|
|
|
|
function nft_schedule_signature_body(body, schedule) {
|
|
let name = as_string(schedule[".name"]);
|
|
body = signature_add_value(body, "schedule." + name + ".enabled", bool_option(schedule, "enabled", true) ? "1" : "0");
|
|
body = signature_add_value(body, "schedule." + name + ".device_ip", option(schedule, "device_ip", ""));
|
|
body = signature_add_value(body, "schedule." + name + ".target", option(schedule, "target", "all"));
|
|
body = signature_add_value(body, "schedule." + name + ".sections", join(",", list_option(schedule, "sections")));
|
|
body = signature_add_value(body, "schedule." + name + ".action", option(schedule, "action", "block"));
|
|
body = signature_add_value(body, "schedule." + name + ".start_time", option(schedule, "start_time", ""));
|
|
body = signature_add_value(body, "schedule." + name + ".end_time", option(schedule, "end_time", ""));
|
|
body = signature_add_value(body, "schedule." + name + ".days", join(",", list_option(schedule, "days")));
|
|
return body;
|
|
}
|
|
|
|
function nft_runtime_signature_from_settings_and_sections(settings, sections, schedules) {
|
|
let body = "";
|
|
|
|
body = signature_add_value(body, "settings.source_network_interfaces", option(settings, "source_network_interfaces", "br-lan"));
|
|
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", ""));
|
|
|
|
for (let section in sections)
|
|
body = nft_rule_signature_body(body, object_or_empty(section));
|
|
|
|
for (let schedule in schedules)
|
|
body = nft_schedule_signature_body(body, object_or_empty(schedule));
|
|
|
|
return signature_hash(body);
|
|
}
|
|
|
|
function print_nft_runtime_signature_from_settings_and_sections(settings, sections, schedules) {
|
|
let hash = nft_runtime_signature_from_settings_and_sections(settings, sections, schedules);
|
|
if (hash == "")
|
|
return false;
|
|
|
|
print(hash, "\n");
|
|
return true;
|
|
}
|
|
|
|
function word_set(value) {
|
|
let result = {};
|
|
for (let item in whitespace_values(value))
|
|
result[item] = true;
|
|
return result;
|
|
}
|
|
|
|
function fixture_section_list(data, type_name) {
|
|
let value = object_or_empty(data)[type_name];
|
|
if (type(value) == "array")
|
|
return value;
|
|
if (type(value) == "object")
|
|
return [ value ];
|
|
|
|
let plural = object_or_empty(data)[type_name + "s"];
|
|
return type(plural) == "array" ? plural : [];
|
|
}
|
|
|
|
function nft_create_provider_output_rules_from_uci(table, action, provider_bin, route_mark_base, queue_base, desync_mark, desync_mark_postnat) {
|
|
return nft_create_provider_output_rules_from_sections(
|
|
uci_sections("section"),
|
|
table,
|
|
action,
|
|
provider_bin,
|
|
route_mark_base,
|
|
queue_base,
|
|
desync_mark,
|
|
desync_mark_postnat
|
|
);
|
|
}
|
|
|
|
function nft_create_full_runtime_from_uci(rt_table, table, localv4_set, common_set, port_set, ip_port_set, interface_set, fakeip_mark, outbound_mark, fakeip_range, tproxy_port, zapret_bin, zapret_route_mark_base, zapret_queue_base, zapret_desync_mark, zapret_desync_mark_postnat, zapret2_bin, zapret2_route_mark_base, zapret2_queue_base, zapret2_desync_mark, zapret2_desync_mark_postnat, localv6_set, common6_set, ip_port6_set, fakeip6_range, tproxy6_address) {
|
|
log_debug("Building nftables runtime model");
|
|
|
|
return ensure_bridge_netfilter_disabled() &&
|
|
ensure_tproxy_route_rule(rt_table, fakeip_mark) &&
|
|
nft_create_runtime_base_from_uci(table, localv4_set, common_set, port_set, ip_port_set, interface_set, fakeip_mark, outbound_mark, fakeip_range, tproxy_port, localv6_set, common6_set, ip_port6_set, fakeip6_range, tproxy6_address) &&
|
|
nft_add_section_priority_rules_from_sections(uci_sections("section"), table, interface_set, localv4_set, localv6_set, fakeip_mark) &&
|
|
nft_add_schedule_rules_from_uci(table, uci_sections("section")) &&
|
|
nft_create_provider_output_rules_from_uci(table, "zapret", zapret_bin, zapret_route_mark_base, zapret_queue_base, zapret_desync_mark, zapret_desync_mark_postnat) &&
|
|
nft_create_provider_output_rules_from_uci(table, "zapret2", zapret2_bin, zapret2_route_mark_base, zapret2_queue_base, zapret2_desync_mark, zapret2_desync_mark_postnat) &&
|
|
nft_create_runtime_output_rules(table, localv4_set, common_set, port_set, ip_port_set, fakeip_mark, fakeip_range, localv6_set, common6_set, ip_port6_set, fakeip6_range);
|
|
}
|
|
|
|
function nft_table_present(table) {
|
|
return run_args_quiet([ "nft", "list", "table", "inet", table ]);
|
|
}
|
|
|
|
function nft_delete_table(table) {
|
|
return run_args([ "nft", "delete", "table", "inet", table ]);
|
|
}
|
|
|
|
function nft_rebuild_runtime_from_uci(rt_table, table, localv4_set, common_set, port_set, ip_port_set, interface_set, fakeip_mark, outbound_mark, fakeip_range, tproxy_port, zapret_bin, zapret_route_mark_base, zapret_queue_base, zapret_desync_mark, zapret_desync_mark_postnat, zapret2_bin, zapret2_route_mark_base, zapret2_queue_base, zapret2_desync_mark, zapret2_desync_mark_postnat, localv6_set, common6_set, ip_port6_set, fakeip6_range, tproxy6_address) {
|
|
log_debug("Applying nftables runtime rules");
|
|
|
|
if (nft_table_present(table) && !nft_delete_table(table))
|
|
return false;
|
|
|
|
return nft_create_full_runtime_from_uci(rt_table, table, localv4_set, common_set, port_set, ip_port_set, interface_set, fakeip_mark, outbound_mark, fakeip_range, tproxy_port, zapret_bin, zapret_route_mark_base, zapret_queue_base, zapret_desync_mark, zapret_desync_mark_postnat, zapret2_bin, zapret2_route_mark_base, zapret2_queue_base, zapret2_desync_mark, zapret2_desync_mark_postnat, localv6_set, common6_set, ip_port6_set, fakeip6_range, tproxy6_address);
|
|
}
|
|
|
|
function nft_runtime_signature_from_uci() {
|
|
return print_nft_runtime_signature_from_settings_and_sections(
|
|
uci_settings(),
|
|
uci_sections("section"),
|
|
uci_sections("schedule")
|
|
);
|
|
}
|
|
|
|
function fixture_section(path, section_name) {
|
|
let data = object_or_empty(common_read_json_file(path));
|
|
connections.set_item_sections_from_data(data);
|
|
return section_by_name(fixture_section_list(data, "section"), section_name);
|
|
}
|
|
|
|
function fixture_settings(data) {
|
|
return object_or_empty(object_or_empty(data).settings);
|
|
}
|
|
|
|
function nft_runtime_signature_from_fixture(path) {
|
|
let data = object_or_empty(common_read_json_file(path));
|
|
connections.set_item_sections_from_data(data);
|
|
return print_nft_runtime_signature_from_settings_and_sections(
|
|
fixture_settings(data),
|
|
fixture_section_list(data, "section"),
|
|
fixture_section_list(data, "schedule")
|
|
);
|
|
}
|
|
|
|
function nft_mangle_chain_text(context, table) {
|
|
if (context.text == null)
|
|
context.text = command_output_from_args([ "nft", "list", "chain", "inet", table, "mangle" ]);
|
|
return context.text;
|
|
}
|
|
|
|
function nft_add_section_source_matchers(section, table, chunk_size_text) {
|
|
let source_values = section_source_ip_values(section);
|
|
if (source_values == "")
|
|
return true;
|
|
|
|
let sets = section_priority_sets(section);
|
|
return nft_add_csv_chunks_to_family_sets(source_values, table, sets.sources, sets.sources6, "ips", "", chunk_size_text);
|
|
}
|
|
|
|
function nft_populate_runtime_set_for_section(section, deferred_sections, table, common_set, port_set, ip_port_set, interface_set, localv4_set, mark, mangle_chain_context, inserted_fully_routed_ips, common6_set, ip_port6_set, localv6_set) {
|
|
if (!bool_option(section, "enabled", true))
|
|
return true;
|
|
if (section_action(section) == "dns" || section_action(section) == "hosts")
|
|
return true;
|
|
|
|
let ports = section_rule_ports_csv(section);
|
|
let ip_values = section_rule_condition_csv(section, "ip_cidr", "subnets");
|
|
let sets = section_priority_sets(section);
|
|
|
|
if (section_needs_priority_sets(section) && !nft_add_section_source_matchers(section, table, 5000))
|
|
return false;
|
|
|
|
if (deferred_sections[as_string(section[".name"])])
|
|
return true;
|
|
|
|
if (section_needs_priority_sets(section)) {
|
|
if (!nft_add_inline_ip_cidr_matchers(ip_values, ports, table, sets.subnets, sets.ip_ports, 5000, sets.subnets6, sets.ip6_ports))
|
|
return false;
|
|
|
|
if (ports != "" && !section_has_destination_matchers(section) &&
|
|
!nft_add_set_elements(table, sets.ports, ports))
|
|
return false;
|
|
|
|
// Load cached community subnet files into nftables (populated at list-update and persisted)
|
|
// Note: call nft_add_file_chunks_to_family_sets directly because ucode does not hoist
|
|
// function declarations, and nft_add_subnet_file_for_section is defined below this function.
|
|
for (let community in connections.community_lists(section)) {
|
|
let service = as_string(community);
|
|
let cached_paths = [
|
|
"/tmp/sing-box/rulesets/community-subnets-" + service + ".lst",
|
|
"/etc/tachyon/rulesets/community-subnets-" + service + ".lst"
|
|
];
|
|
for (let path in cached_paths) {
|
|
if (helpers.file_is_usable(path, 50)) {
|
|
nft_add_file_chunks_to_family_sets(path, table, sets.subnets, sets.subnets6, "ips", "", "5000");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (let source_ip in list_option(section, "fully_routed_ips"))
|
|
if (!nft_ensure_fully_routed_ip_rules_from_chain(source_ip, table, interface_set, localv4_set, localv6_set, mark, nft_mangle_chain_text(mangle_chain_context, table), inserted_fully_routed_ips))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
function nft_add_subnet_file_for_section(section, filepath, table, common_set, ip_port_set, chunk_size_text, common6_set, ip_port6_set) {
|
|
let ports = section_rule_ports_csv(section);
|
|
let sets = section_priority_sets(section);
|
|
|
|
if (!section_needs_priority_sets(section))
|
|
return true;
|
|
|
|
if (!nft_create_priority_sets(table, sets))
|
|
return false;
|
|
|
|
if (ports != "")
|
|
return nft_add_file_chunks_to_family_sets(filepath, table, sets.ip_ports, sets.ip6_ports, "ip-port-from-ip", ports, chunk_size_text);
|
|
|
|
return nft_add_file_chunks_to_family_sets(filepath, table, sets.subnets, sets.subnets6, "ips", "", chunk_size_text);
|
|
}
|
|
|
|
function file_nonempty(path) {
|
|
return helpers.file_is_usable(path, 0);
|
|
}
|
|
|
|
function nft_add_extracted_ruleset_subnets(unscoped_path, scoped_path, label, table, common_set, ip_port_set, chunk_size_text, common6_set, ip_port6_set) {
|
|
let has_entries = false;
|
|
|
|
if (file_nonempty(unscoped_path)) {
|
|
if (!nft_add_file_chunks_to_family_sets(unscoped_path, table, common_set, default_arg(common6_set, "tachyon_subnets6"), "ips", "", chunk_size_text))
|
|
return false;
|
|
has_entries = true;
|
|
}
|
|
|
|
if (file_nonempty(scoped_path)) {
|
|
if (!nft_add_file_chunks_to_family_sets(scoped_path, table, ip_port_set, default_arg(ip_port6_set, "tachyon_ip6_ports"), "ip-ports", "", chunk_size_text))
|
|
return false;
|
|
has_entries = true;
|
|
}
|
|
|
|
if (!has_entries)
|
|
log_warn(as_string(label) + " has no ip_cidr entries for nftables");
|
|
|
|
return true;
|
|
}
|
|
|
|
function nft_add_json_ruleset_subnets_for_section(section, json_path, label, table, common_set, ip_port_set, unscoped_path, scoped_path, chunk_size_text, common6_set, ip_port6_set) {
|
|
let ports = section_rule_ports_csv(section);
|
|
let sets = section_priority_sets(section);
|
|
|
|
if (!section_needs_priority_sets(section))
|
|
return true;
|
|
|
|
if (!nft_create_priority_sets(table, sets))
|
|
return false;
|
|
|
|
routing_rulesets.extract_ip_cidr_nft_elements(
|
|
json_path,
|
|
unscoped_path,
|
|
scoped_path,
|
|
sprintf("%J", rule_port_values(ports)),
|
|
sprintf("%J", rule_port_ranges(ports))
|
|
);
|
|
|
|
return nft_add_extracted_ruleset_subnets(unscoped_path, scoped_path, label, table, sets.subnets, sets.ip_ports, chunk_size_text, sets.subnets6, sets.ip6_ports);
|
|
}
|
|
|
|
function nft_add_community_subnet_file_for_section(section, service, filepath, table, common_set, ip_port_set, interface_set, discord_set, mark, chunk_size_text, common6_set, ip_port6_set, discord6_set) {
|
|
if (section_needs_priority_sets(section))
|
|
return nft_add_subnet_file_for_section(section, filepath, table, common_set, ip_port_set, chunk_size_text, common6_set, ip_port6_set);
|
|
|
|
return nft_add_file_chunks_to_family_sets(filepath, table, common_set, default_arg(common6_set, "tachyon_subnets6"), "ips", "", chunk_size_text);
|
|
}
|
|
|
|
function nft_add_subnet_file_for_uci_section(section_name, filepath, table, common_set, ip_port_set, chunk_size_text, common6_set, ip_port6_set) {
|
|
return nft_add_subnet_file_for_section(uci_section(section_name), filepath, table, common_set, ip_port_set, chunk_size_text, common6_set, ip_port6_set);
|
|
}
|
|
|
|
function nft_add_json_ruleset_subnets_for_uci_section(section_name, json_path, label, table, common_set, ip_port_set, unscoped_path, scoped_path, chunk_size_text, common6_set, ip_port6_set) {
|
|
return nft_add_json_ruleset_subnets_for_section(uci_section(section_name), json_path, label, table, common_set, ip_port_set, unscoped_path, scoped_path, chunk_size_text, common6_set, ip_port6_set);
|
|
}
|
|
|
|
function nft_add_community_subnet_file_for_uci_section(section_name, service, filepath, table, common_set, ip_port_set, interface_set, discord_set, mark, chunk_size_text, common6_set, ip_port6_set, discord6_set) {
|
|
return nft_add_community_subnet_file_for_section(uci_section(section_name), service, filepath, table, common_set, ip_port_set, interface_set, discord_set, mark, chunk_size_text, common6_set, ip_port6_set, discord6_set);
|
|
}
|
|
|
|
function nft_add_subnet_file_for_fixture_section(fixture_path, section_name, filepath, table, common_set, ip_port_set, chunk_size_text, common6_set, ip_port6_set) {
|
|
return nft_add_subnet_file_for_section(fixture_section(fixture_path, section_name), filepath, table, common_set, ip_port_set, chunk_size_text, common6_set, ip_port6_set);
|
|
}
|
|
|
|
function nft_add_json_ruleset_subnets_for_fixture_section(fixture_path, section_name, json_path, label, table, common_set, ip_port_set, unscoped_path, scoped_path, chunk_size_text, common6_set, ip_port6_set) {
|
|
return nft_add_json_ruleset_subnets_for_section(fixture_section(fixture_path, section_name), json_path, label, table, common_set, ip_port_set, unscoped_path, scoped_path, chunk_size_text, common6_set, ip_port6_set);
|
|
}
|
|
|
|
function nft_add_community_subnet_file_for_fixture_section(fixture_path, section_name, service, filepath, table, common_set, ip_port_set, interface_set, discord_set, mark, chunk_size_text, common6_set, ip_port6_set, discord6_set) {
|
|
return nft_add_community_subnet_file_for_section(fixture_section(fixture_path, section_name), service, filepath, table, common_set, ip_port_set, interface_set, discord_set, mark, chunk_size_text, common6_set, ip_port6_set, discord6_set);
|
|
}
|
|
|
|
function nft_populate_runtime_sets_from_sections(sections, populate_enabled, deferred_section_names, table, common_set, port_set, ip_port_set, interface_set, localv4_set, mark, common6_set, ip_port6_set, localv6_set) {
|
|
if (!arg_bool(populate_enabled))
|
|
return true;
|
|
|
|
let deferred_sections = word_set(deferred_section_names);
|
|
let mangle_chain_context = {};
|
|
let inserted_fully_routed_ips = {};
|
|
|
|
for (let section in sections)
|
|
if (!nft_populate_runtime_set_for_section(section, deferred_sections, table, common_set, port_set, ip_port_set, interface_set, localv4_set, mark, mangle_chain_context, inserted_fully_routed_ips, common6_set, ip_port6_set, localv6_set))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
function nft_populate_runtime_sets_from_uci(populate_enabled, deferred_section_names, table, common_set, port_set, ip_port_set, interface_set, localv4_set, mark, common6_set, ip_port6_set, localv6_set) {
|
|
if (!arg_bool(populate_enabled))
|
|
return true;
|
|
|
|
if (!nft_table_present(table)) {
|
|
log_warn("nft_populate_runtime_sets_from_uci: Table " + table + " does not exist. Rebuilding firewall rules dynamically.");
|
|
system(common.background_command("/usr/bin/tachyon reload_firewall"));
|
|
}
|
|
|
|
return nft_populate_runtime_sets_from_sections(uci_sections("section"), populate_enabled, deferred_section_names, table, common_set, port_set, ip_port_set, interface_set, localv4_set, mark, common6_set, ip_port6_set, localv6_set);
|
|
}
|
|
|
|
function nft_populate_runtime_sets_fixture(path, populate_enabled, deferred_section_names, table, common_set, port_set, ip_port_set, interface_set, localv4_set, mark, common6_set, ip_port6_set, localv6_set) {
|
|
let data = object_or_empty(common_read_json_file(path));
|
|
connections.set_item_sections_from_data(data);
|
|
return nft_populate_runtime_sets_from_sections(fixture_section_list(data, "section"), populate_enabled, deferred_section_names, table, common_set, port_set, ip_port_set, interface_set, localv4_set, mark, common6_set, ip_port6_set, localv6_set);
|
|
}
|
|
|
|
let mode = ARGV[0] || "";
|
|
|
|
if (mode == "text-list-to-csv")
|
|
text_list_to_csv(ARGV[1], ARGV[2]);
|
|
else if (mode == "csv-to-json-array")
|
|
csv_to_json_array(ARGV[1]);
|
|
else if (mode == "cache-path")
|
|
cache_path(ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5], ARGV[6]);
|
|
else if (mode == "list-value-to-csv")
|
|
list_value_csv(ARGV[1]);
|
|
else if (mode == "csv-list-contains")
|
|
exit(csv_list_contains(ARGV[1], ARGV[2]) ? 0 : 1);
|
|
else if (mode == "domain-subnet-text-csv")
|
|
domain_subnet_text_csv(ARGV[1], ARGV[2]);
|
|
else if (mode == "combined-domain-text-csv")
|
|
combined_domain_text_csv(ARGV[1], ARGV[2]);
|
|
else if (mode == "combined-domain-csv")
|
|
combined_domain_csv(ARGV[1], ARGV[2]);
|
|
else if (mode == "rule-condition-csv")
|
|
rule_condition_csv(ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5], ARGV[6], ARGV[7], ARGV[8]);
|
|
else if (mode == "legacy-condition-csv")
|
|
legacy_condition_csv(ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5]);
|
|
else if (mode == "domain-subnet-file-csv")
|
|
domain_subnet_file_csv(ARGV[1], ARGV[2]);
|
|
else if (mode == "split-domain-subnet-file")
|
|
split_domain_subnet_file(ARGV[1], ARGV[2], ARGV[3]);
|
|
else if (mode == "normalize-port-condition-for-nft")
|
|
normalize_port_condition_for_nft(ARGV[1]);
|
|
else if (mode == "rule-ports-csv")
|
|
rule_ports_csv(ARGV[1], ARGV[2]);
|
|
else if (mode == "csv-to-lines-file")
|
|
csv_to_lines_file(ARGV[1], ARGV[2]);
|
|
else if (mode == "nft-create-runtime-base")
|
|
exit(nft_create_runtime_base(ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5], ARGV[6], ARGV[7], ARGV[8], ARGV[9], ARGV[10], ARGV[11], ARGV[12], ARGV[13], ARGV[14], ARGV[15], ARGV[16], ARGV[17]) ? 0 : 1);
|
|
else if (mode == "nft-create-runtime-base-from-uci")
|
|
exit(nft_create_runtime_base_from_uci(ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5], ARGV[6], ARGV[7], ARGV[8], ARGV[9], ARGV[10], ARGV[11], ARGV[12], ARGV[13], ARGV[14], ARGV[15]) ? 0 : 1);
|
|
else if (mode == "nft-create-runtime-output-rules")
|
|
exit(nft_create_runtime_output_rules(ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5], ARGV[6], ARGV[7], ARGV[8], ARGV[9], ARGV[10], ARGV[11]) ? 0 : 1);
|
|
else if (mode == "nft-create-provider-output-rules-from-uci")
|
|
exit(nft_create_provider_output_rules_from_uci(ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5], ARGV[6], ARGV[7]) ? 0 : 1);
|
|
else if (mode == "nft-create-provider-output-rules-fixture")
|
|
exit(nft_create_provider_output_rules_from_sections(fixture_section_list(object_or_empty(common_read_json_file(ARGV[1])), "section"), ARGV[2], ARGV[3], ARGV[4], ARGV[5], ARGV[6], ARGV[7], ARGV[8]) ? 0 : 1);
|
|
else if (mode == "nft-add-section-priority-rules-fixture")
|
|
exit(nft_add_section_priority_rules_from_sections(fixture_section_list(object_or_empty(common_read_json_file(ARGV[1])), "section"), ARGV[2], ARGV[3], ARGV[4], ARGV[5], ARGV[6]) ? 0 : 1);
|
|
else if (mode == "nft-add-schedule-rules-fixture")
|
|
exit(nft_add_schedule_rules_from_schedules(fixture_section_list(object_or_empty(common_read_json_file(ARGV[1])), "schedule"), fixture_section_list(object_or_empty(common_read_json_file(ARGV[1])), "section"), ARGV[2]) ? 0 : 1);
|
|
else if (mode == "nft-create-full-runtime-from-uci")
|
|
exit(nft_create_full_runtime_from_uci(ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5], ARGV[6], ARGV[7], ARGV[8], ARGV[9], ARGV[10], ARGV[11], ARGV[12], ARGV[13], ARGV[14], ARGV[15], ARGV[16], ARGV[17], ARGV[18], ARGV[19], ARGV[20], ARGV[21], ARGV[22], ARGV[23], ARGV[24], ARGV[25], ARGV[26]) ? 0 : 1);
|
|
else if (mode == "nft-rebuild-runtime-from-uci")
|
|
exit(nft_rebuild_runtime_from_uci(ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5], ARGV[6], ARGV[7], ARGV[8], ARGV[9], ARGV[10], ARGV[11], ARGV[12], ARGV[13], ARGV[14], ARGV[15], ARGV[16], ARGV[17], ARGV[18], ARGV[19], ARGV[20], ARGV[21], ARGV[22], ARGV[23], ARGV[24], ARGV[25], ARGV[26]) ? 0 : 1);
|
|
else if (mode == "nft-prepare-chunks")
|
|
nft_prepare_chunks(ARGV[1], ARGV[2], ARGV[3] || "", ARGV[4], ARGV[5], ARGV[6]);
|
|
else if (mode == "nft-add-file-chunks-to-set")
|
|
exit(nft_add_file_chunks_to_set(ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5] || "", ARGV[6]) ? 0 : 1);
|
|
else if (mode == "nft-add-subnet-file-for-uci-section")
|
|
exit(nft_add_subnet_file_for_uci_section(ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5], ARGV[6], ARGV[7], ARGV[8]) ? 0 : 1);
|
|
else if (mode == "nft-add-json-ruleset-subnets-for-uci-section")
|
|
exit(nft_add_json_ruleset_subnets_for_uci_section(ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5], ARGV[6], ARGV[7], ARGV[8], ARGV[9], ARGV[10], ARGV[11]) ? 0 : 1);
|
|
else if (mode == "nft-add-community-subnet-file-for-uci-section")
|
|
exit(nft_add_community_subnet_file_for_uci_section(ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5], ARGV[6], ARGV[7], ARGV[8], ARGV[9], ARGV[10], ARGV[11], ARGV[12], ARGV[13]) ? 0 : 1);
|
|
else if (mode == "nft-add-subnet-file-for-section-fixture")
|
|
exit(nft_add_subnet_file_for_fixture_section(ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5], ARGV[6], ARGV[7], ARGV[8], ARGV[9]) ? 0 : 1);
|
|
else if (mode == "nft-add-json-ruleset-subnets-for-section-fixture")
|
|
exit(nft_add_json_ruleset_subnets_for_fixture_section(ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5], ARGV[6], ARGV[7], ARGV[8], ARGV[9], ARGV[10], ARGV[11], ARGV[12]) ? 0 : 1);
|
|
else if (mode == "nft-add-community-subnet-file-for-section-fixture")
|
|
exit(nft_add_community_subnet_file_for_fixture_section(ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5], ARGV[6], ARGV[7], ARGV[8], ARGV[9], ARGV[10], ARGV[11], ARGV[12], ARGV[13], ARGV[14]) ? 0 : 1);
|
|
else if (mode == "nft-populate-runtime-sets-from-uci")
|
|
exit(nft_populate_runtime_sets_from_uci(ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5], ARGV[6], ARGV[7], ARGV[8], ARGV[9], ARGV[10], ARGV[11], ARGV[12]) ? 0 : 1);
|
|
else if (mode == "nft-populate-runtime-sets-fixture")
|
|
exit(nft_populate_runtime_sets_fixture(ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5], ARGV[6], ARGV[7], ARGV[8], ARGV[9], ARGV[10], ARGV[11], ARGV[12], ARGV[13]) ? 0 : 1);
|
|
else if (mode == "nft-runtime-signature")
|
|
exit(nft_runtime_signature_from_uci() ? 0 : 1);
|
|
else if (mode == "nft-runtime-signature-fixture")
|
|
exit(nft_runtime_signature_from_fixture(ARGV[1]) ? 0 : 1);
|
|
else if (mode == "nft-table-present-fixture")
|
|
exit(nft_table_present(ARGV[1]) ? 0 : 1);
|
|
else if (mode == "ensure-tproxy-route-rule")
|
|
exit(ensure_tproxy_route_rule(ARGV[1], ARGV[2], ARGV[3]) ? 0 : 1);
|
|
else if (mode == "tproxy-route-present")
|
|
exit(tproxy_route_present(ARGV[1]) ? 0 : 1);
|
|
else if (mode == "tproxy-route4-present")
|
|
exit(tproxy_route4_present(ARGV[1]) ? 0 : 1);
|
|
else if (mode == "tproxy-route6-present")
|
|
exit(tproxy_route6_present(ARGV[1]) ? 0 : 1);
|
|
else if (mode == "tproxy-marking-rule-present")
|
|
exit(tproxy_marking_rule_present(ARGV[1], ARGV[2]) ? 0 : 1);
|
|
else if (mode == "tproxy-marking-rule4-present")
|
|
exit(tproxy_marking_rule4_present(ARGV[1], ARGV[2]) ? 0 : 1);
|
|
else if (mode == "tproxy-marking-rule6-present")
|
|
exit(tproxy_marking_rule6_present(ARGV[1], ARGV[2]) ? 0 : 1);
|
|
else if (mode == "tproxy-route-rule-present")
|
|
exit(tproxy_route_rule_present(ARGV[1], ARGV[2]) ? 0 : 1);
|
|
else if (mode == "ensure-bridge-netfilter-disabled")
|
|
exit(ensure_bridge_netfilter_disabled() ? 0 : 1);
|
|
else {
|
|
warn("Usage: nft/apply.uc <operation> ...\n");
|
|
exit(1);
|
|
} |