small-packages/homeproxy/root/etc/homeproxy/scripts/generate_client.uc
2026-07-12 00:50:47 +08:00

869 lines
24 KiB
Ucode
Executable File

#!/usr/bin/ucode
/*
* SPDX-License-Identifier: GPL-2.0-only
*
* Copyright (C) 2023-2025 ImmortalWrt.org
*/
'use strict';
import { readfile, writefile } from 'fs';
import { isnan } from 'math';
import { connect } from 'ubus';
import { cursor } from 'uci';
import {
isEmpty, parseURL, strToBool, strToInt, strToTime,
removeBlankAttrs, renderEndpoint, renderOutbound, validation, HP_DIR, RUN_DIR
} from 'homeproxy';
const ubus = connect();
/* const features = ubus.call('luci.homeproxy', 'singbox_get_features') || {}; */
/* UCI config start */
const uci = cursor();
const uciconfig = 'homeproxy';
uci.load(uciconfig);
const uciinfra = 'infra',
ucimain = 'config',
ucicontrol = 'control';
const ucidnssetting = 'dns',
ucidnsserver = 'dns_server',
ucidnsrule = 'dns_rule';
const uciroutingsetting = 'routing',
uciroutingnode = 'routing_node',
uciroutingrule = 'routing_rule';
const ucinode = 'node';
const uciruleset = 'ruleset';
const routing_mode = uci.get(uciconfig, ucimain, 'routing_mode') || 'bypass_mainland_china';
function normalize_list(value) {
if (isEmpty(value))
return [];
if (type(value) === 'array')
return value;
return [value];
}
let wan_dns = ubus.call('network.interface', 'status', {'interface': 'wan'})?.['dns-server']?.[0];
if (!wan_dns)
wan_dns = (routing_mode in ['proxy_mainland_china', 'global']) ? '9.9.9.9' : '223.5.5.5';
const dns_port = uci.get(uciconfig, uciinfra, 'dns_port') || '5333';
const ntp_server = uci.get(uciconfig, uciinfra, 'ntp_server') || 'time.apple.com';
const ipv6_support = uci.get(uciconfig, ucimain, 'ipv6_support') || '0';
let main_node, main_udp_node, dedicated_udp_node, default_outbound, default_outbound_dns,
domain_strategy, sniff_override, dns_server, china_dns_server, dns_default_strategy,
dns_default_server, dns_disable_cache, dns_disable_cache_expire, dns_independent_cache,
dns_client_subnet, cache_file_store_rdrc, cache_file_rdrc_timeout, direct_domain_list,
proxy_domain_list;
if (routing_mode !== 'custom') {
main_node = uci.get(uciconfig, ucimain, 'main_node') || 'nil';
main_udp_node = uci.get(uciconfig, ucimain, 'main_udp_node') || 'nil';
dedicated_udp_node = !isEmpty(main_udp_node) && !(main_udp_node in ['same', main_node]);
dns_server = uci.get(uciconfig, ucimain, 'dns_server');
if (isEmpty(dns_server) || dns_server === 'wan')
dns_server = wan_dns;
if (routing_mode === 'bypass_mainland_china') {
china_dns_server = uci.get(uciconfig, ucimain, 'china_dns_server');
if (isEmpty(china_dns_server) || type(china_dns_server) !== 'string' || china_dns_server === 'wan')
china_dns_server = wan_dns;
}
dns_default_strategy = (ipv6_support !== '1') ? 'ipv4_only' : null;
direct_domain_list = trim(readfile(HP_DIR + '/resources/direct_list.txt'));
if (direct_domain_list)
direct_domain_list = split(direct_domain_list, /[\r\n]/);
proxy_domain_list = trim(readfile(HP_DIR + '/resources/proxy_list.txt'));
if (proxy_domain_list)
proxy_domain_list = split(proxy_domain_list, /[\r\n]/);
sniff_override = uci.get(uciconfig, uciinfra, 'sniff_override') || '1';
} else {
/* DNS settings */
dns_default_strategy = uci.get(uciconfig, ucidnssetting, 'default_strategy');
dns_default_server = uci.get(uciconfig, ucidnssetting, 'default_server');
dns_disable_cache = uci.get(uciconfig, ucidnssetting, 'disable_cache');
dns_disable_cache_expire = uci.get(uciconfig, ucidnssetting, 'disable_cache_expire');
dns_independent_cache = uci.get(uciconfig, ucidnssetting, 'independent_cache');
dns_client_subnet = uci.get(uciconfig, ucidnssetting, 'client_subnet');
cache_file_store_rdrc = uci.get(uciconfig, ucidnssetting, 'cache_file_store_rdrc'),
cache_file_rdrc_timeout = uci.get(uciconfig, ucidnssetting, 'cache_file_rdrc_timeout');
/* Routing settings */
default_outbound = uci.get(uciconfig, uciroutingsetting, 'default_outbound') || 'nil';
default_outbound_dns = uci.get(uciconfig, uciroutingsetting, 'default_outbound_dns') || 'default-dns';
domain_strategy = uci.get(uciconfig, uciroutingsetting, 'domain_strategy');
sniff_override = uci.get(uciconfig, uciroutingsetting, 'sniff_override');
}
const proxy_mode = uci.get(uciconfig, ucimain, 'proxy_mode') || 'redirect_tproxy',
default_interface = uci.get(uciconfig, ucicontrol, 'bind_interface');
const mixed_port = uci.get(uciconfig, uciinfra, 'mixed_port') || '5330';
let self_mark, redirect_port, tproxy_port, tun_name,
tun_addr4, tun_addr6, tun_mtu, tcpip_stack, udp_timeout;
if (routing_mode === 'custom')
udp_timeout = uci.get(uciconfig, uciroutingsetting, 'udp_timeout');
else
udp_timeout = uci.get(uciconfig, 'infra', 'udp_timeout');
if (match(proxy_mode, /redirect/)) {
self_mark = uci.get(uciconfig, 'infra', 'self_mark') || '100';
redirect_port = uci.get(uciconfig, 'infra', 'redirect_port') || '5331';
}
if (match(proxy_mode, /tproxy/))
if (main_udp_node !== 'nil' || routing_mode === 'custom')
tproxy_port = uci.get(uciconfig, 'infra', 'tproxy_port') || '5332';
if (match(proxy_mode, /tun/)) {
tun_name = uci.get(uciconfig, uciinfra, 'tun_name') || 'singtun0';
tun_addr4 = uci.get(uciconfig, uciinfra, 'tun_addr4') || '172.19.0.1/30';
tun_addr6 = uci.get(uciconfig, uciinfra, 'tun_addr6') || 'fdfe:dcba:9876::1/126';
tun_mtu = uci.get(uciconfig, uciinfra, 'tun_mtu') || '9000';
tcpip_stack = 'system';
if (routing_mode === 'custom') {
tcpip_stack = uci.get(uciconfig, uciroutingsetting, 'tcpip_stack') || 'system';
}
}
const log_level = uci.get(uciconfig, ucimain, 'log_level') || 'warn';
/* UCI config end */
/* Config helper start */
function parse_port(strport) {
if (type(strport) !== 'array' || isEmpty(strport))
return null;
let ports = [];
for (let i in strport)
push(ports, int(i));
return ports;
}
function parse_dnsserver(server_addr, default_protocol) {
if (isEmpty(server_addr))
return null;
if (!match(server_addr, /:\/\//))
server_addr = (default_protocol || 'udp') + '://' + (validation('ip6addr', server_addr) ? `[${server_addr}]` : server_addr);
server_addr = parseURL(server_addr);
return {
type: server_addr.protocol,
server: server_addr.hostname,
server_port: strToInt(server_addr.port),
path: (server_addr.pathname !== '/') ? server_addr.pathname : null,
}
}
function parse_dnsquery(strquery) {
if (type(strquery) !== 'array' || isEmpty(strquery))
return null;
let querys = [];
for (let i in strquery)
isnan(int(i)) ? push(querys, i) : push(querys, int(i));
return querys;
}
function filter_existing_nodes(nodes) {
if (type(nodes) !== 'array' || isEmpty(nodes))
return [];
return filter(nodes, (k) => {
const node = uci.get_all(uciconfig, k) || {};
return !isEmpty(node);
});
}
const generate_endpoint = renderEndpoint;
function generate_outbound(node) {
return renderOutbound(node, self_mark);
}
function get_outbound(cfg) {
if (isEmpty(cfg))
return null;
if (type(cfg) === 'array') {
if ('any-out' in cfg)
return 'any';
let outbounds = [];
for (let i in cfg)
push(outbounds, get_outbound(i));
return outbounds;
} else {
switch (cfg) {
case 'block-out':
case 'direct-out':
return cfg;
default:
const node = uci.get(uciconfig, cfg, 'node');
if (isEmpty(node))
die(sprintf("%s's node is missing, please check your configuration.", cfg));
else if (node === 'urltest')
return 'cfg-' + cfg + '-out';
else
return 'cfg-' + node + '-out';
}
}
}
function get_resolver(cfg) {
if (isEmpty(cfg))
return null;
switch (cfg) {
case 'default-dns':
case 'system-dns':
return cfg;
default:
return 'cfg-' + cfg + '-dns';
}
}
function get_ruleset(cfg) {
if (isEmpty(cfg))
return null;
let rules = [];
for (let i in cfg)
push(rules, isEmpty(i) ? null : 'cfg-' + i + '-rule');
return rules;
}
/* Config helper end */
const config = {};
/* Log */
config.log = {
disabled: false,
level: log_level,
output: RUN_DIR + '/sing-box-c.log',
timestamp: true
};
/* NTP */
if (!isEmpty(ntp_server))
config.ntp = {
enabled: true,
server: ntp_server,
detour: 'direct-out',
domain_resolver: 'default-dns',
};
/* DNS start */
/* Default settings */
config.dns = {
servers: [
{
tag: 'default-dns',
type: 'udp',
server: wan_dns,
detour: self_mark ? 'direct-out' : null
},
{
tag: 'system-dns',
type: 'local',
detour: self_mark ? 'direct-out' : null
}
],
rules: [],
strategy: dns_default_strategy,
disable_cache: strToBool(dns_disable_cache),
disable_expire: strToBool(dns_disable_cache_expire),
independent_cache: strToBool(dns_independent_cache),
client_subnet: dns_client_subnet
};
if (!isEmpty(main_node)) {
/* Main DNS */
push(config.dns.servers, {
tag: 'main-dns',
domain_resolver: {
server: 'default-dns',
strategy: (ipv6_support !== '1') ? 'ipv4_only' : null
},
detour: 'main-out',
...parse_dnsserver(dns_server, 'tcp')
});
config.dns.final = 'main-dns';
if (length(direct_domain_list))
push(config.dns.rules, {
rule_set: 'direct-domain',
action: 'route',
server: (routing_mode === 'bypass_mainland_china') ? 'china-dns' : 'default-dns'
});
/* Filter out SVCB/HTTPS queries for "exquisite" Apple devices */
if (routing_mode === 'gfwlist' || length(proxy_domain_list))
push(config.dns.rules, {
rule_set: (routing_mode !== 'gfwlist') ? 'proxy-domain' : null,
query_type: [64, 65],
action: 'reject'
});
if (routing_mode === 'bypass_mainland_china') {
push(config.dns.servers, {
tag: 'china-dns',
domain_resolver: {
server: 'default-dns',
strategy: 'prefer_ipv6'
},
detour: self_mark ? 'direct-out' : null,
...parse_dnsserver(china_dns_server)
});
if (length(proxy_domain_list))
push(config.dns.rules, {
rule_set: 'proxy-domain',
action: 'route',
server: 'main-dns'
});
push(config.dns.rules, {
rule_set: 'geosite-cn',
action: 'route',
server: 'china-dns',
strategy: 'prefer_ipv6'
});
push(config.dns.rules, {
type: 'logical',
mode: 'and',
rules: [
{
rule_set: 'geosite-noncn',
invert: true
},
{
rule_set: 'geoip-cn'
}
],
action: 'route',
server: 'china-dns',
strategy: 'prefer_ipv6'
});
}
} else if (!isEmpty(default_outbound)) {
/* DNS servers */
uci.foreach(uciconfig, ucidnsserver, (cfg) => {
if (cfg.enabled !== '1')
return;
let outbound = get_outbound(cfg.outbound);
if (outbound === 'direct-out' && isEmpty(self_mark))
outbound = null;
push(config.dns.servers, {
tag: 'cfg-' + cfg['.name'] + '-dns',
type: cfg.type,
server: cfg.server,
server_port: strToInt(cfg.server_port),
path: cfg.path,
headers: cfg.headers,
tls: cfg.tls_sni ? {
enabled: true,
server_name: cfg.tls_sni
} : null,
domain_resolver: (cfg.address_resolver || cfg.address_strategy) ? {
server: get_resolver(cfg.address_resolver || dns_default_server),
strategy: cfg.address_strategy
} : null,
detour: outbound
});
});
/* DNS rules */
uci.foreach(uciconfig, ucidnsrule, (cfg) => {
if (cfg.enabled !== '1')
return;
push(config.dns.rules, {
ip_version: strToInt(cfg.ip_version),
query_type: parse_dnsquery(cfg.query_type),
network: cfg.network,
protocol: cfg.protocol,
domain: cfg.domain,
domain_suffix: cfg.domain_suffix,
domain_keyword: cfg.domain_keyword,
domain_regex: cfg.domain_regex,
port: parse_port(cfg.port),
port_range: cfg.port_range,
source_ip_cidr: cfg.source_ip_cidr,
source_ip_is_private: strToBool(cfg.source_ip_is_private),
ip_cidr: cfg.ip_cidr,
ip_is_private: strToBool(cfg.ip_is_private),
source_port: parse_port(cfg.source_port),
source_port_range: cfg.source_port_range,
process_name: cfg.process_name,
process_path: cfg.process_path,
process_path_regex: cfg.process_path_regex,
user: cfg.user,
rule_set: get_ruleset(cfg.rule_set),
rule_set_ip_cidr_match_source: strToBool(cfg.rule_set_ip_cidr_match_source),
rule_set_ip_cidr_accept_empty: strToBool(cfg.rule_set_ip_cidr_accept_empty),
invert: strToBool(cfg.invert),
action: cfg.action,
server: get_resolver(cfg.server),
strategy: cfg.domain_strategy,
disable_cache: strToBool(cfg.dns_disable_cache),
rewrite_ttl: strToInt(cfg.rewrite_ttl),
client_subnet: cfg.client_subnet,
method: cfg.reject_method,
no_drop: strToBool(cfg.reject_no_drop),
rcode: cfg.predefined_rcode,
answer: cfg.predefined_answer,
ns: cfg.predefined_ns,
extra: cfg.predefined_extra
});
});
if (isEmpty(config.dns.rules))
config.dns.rules = null;
config.dns.final = get_resolver(dns_default_server);
}
/* DNS end */
/* Inbound start */
config.inbounds = [];
push(config.inbounds, {
type: 'direct',
tag: 'dns-in',
listen: '::',
listen_port: int(dns_port)
});
push(config.inbounds, {
type: 'mixed',
tag: 'mixed-in',
listen: '::',
listen_port: int(mixed_port),
udp_timeout: strToTime(udp_timeout),
set_system_proxy: false
});
if (match(proxy_mode, /redirect/))
push(config.inbounds, {
type: 'redirect',
tag: 'redirect-in',
listen: '::',
listen_port: int(redirect_port)
});
if (match(proxy_mode, /tproxy/))
push(config.inbounds, {
type: 'tproxy',
tag: 'tproxy-in',
listen: '::',
listen_port: int(tproxy_port),
network: 'udp',
udp_timeout: strToTime(udp_timeout)
});
if (match(proxy_mode, /tun/))
push(config.inbounds, {
type: 'tun',
tag: 'tun-in',
interface_name: tun_name,
address: (ipv6_support === '1') ? [tun_addr4, tun_addr6] : [tun_addr4],
mtu: strToInt(tun_mtu),
auto_route: false,
udp_timeout: strToTime(udp_timeout),
stack: tcpip_stack
});
/* Inbound end */
/* Outbound start */
config.endpoints = [];
/* Default outbounds */
config.outbounds = [
{
type: 'direct',
tag: 'direct-out',
routing_mark: strToInt(self_mark)
},
{
type: 'block',
tag: 'block-out'
}
];
/* Main outbounds */
if (!isEmpty(main_node)) {
let urltest_nodes = [];
if (main_node === 'urltest') {
const main_urltest_nodes = filter_existing_nodes(
normalize_list(uci.get(uciconfig, ucimain, 'main_urltest_nodes'))
);
const main_urltest_interval = uci.get(uciconfig, ucimain, 'main_urltest_interval');
const main_urltest_tolerance = uci.get(uciconfig, ucimain, 'main_urltest_tolerance');
push(config.outbounds, {
type: 'urltest',
tag: 'main-out',
outbounds: map(main_urltest_nodes, (k) => `cfg-${k}-out`),
interval: strToTime(main_urltest_interval),
tolerance: strToInt(main_urltest_tolerance),
idle_timeout: (strToInt(main_urltest_interval) > 1800) ? `${main_urltest_interval * 2}s` : null,
});
urltest_nodes = main_urltest_nodes;
} else {
const main_node_cfg = uci.get_all(uciconfig, main_node) || {};
if (main_node_cfg.type === 'wireguard') {
const main_endpoint = generate_endpoint(main_node_cfg);
if (main_endpoint) {
main_endpoint.tag = 'main-out';
push(config.endpoints, main_endpoint);
}
} else {
const main_outbound = generate_outbound(main_node_cfg);
if (main_outbound) {
main_outbound.tag = 'main-out';
push(config.outbounds, main_outbound);
}
}
}
if (main_udp_node === 'urltest') {
const main_udp_urltest_nodes = filter_existing_nodes(
normalize_list(uci.get(uciconfig, ucimain, 'main_udp_urltest_nodes'))
);
const main_udp_urltest_interval = uci.get(uciconfig, ucimain, 'main_udp_urltest_interval');
const main_udp_urltest_tolerance = uci.get(uciconfig, ucimain, 'main_udp_urltest_tolerance');
push(config.outbounds, {
type: 'urltest',
tag: 'main-udp-out',
outbounds: map(main_udp_urltest_nodes, (k) => `cfg-${k}-out`),
interval: strToTime(main_udp_urltest_interval),
tolerance: strToInt(main_udp_urltest_tolerance),
idle_timeout: (strToInt(main_udp_urltest_interval) > 1800) ? `${main_udp_urltest_interval * 2}s` : null,
});
urltest_nodes = [...urltest_nodes, ...filter(main_udp_urltest_nodes, (l) => !~index(urltest_nodes, l))];
} else if (dedicated_udp_node) {
const main_udp_node_cfg = uci.get_all(uciconfig, main_udp_node) || {};
if (main_udp_node_cfg.type === 'wireguard') {
const main_udp_endpoint = generate_endpoint(main_udp_node_cfg);
if (main_udp_endpoint) {
main_udp_endpoint.tag = 'main-udp-out';
push(config.endpoints, main_udp_endpoint);
}
} else {
const main_udp_outbound = generate_outbound(main_udp_node_cfg);
if (main_udp_outbound) {
main_udp_outbound.tag = 'main-udp-out';
push(config.outbounds, main_udp_outbound);
}
}
}
for (let i in urltest_nodes) {
const urltest_node = uci.get_all(uciconfig, i) || {};
if (isEmpty(urltest_node))
continue;
if (urltest_node.type === 'wireguard') {
const endpoint = generate_endpoint(urltest_node);
if (endpoint) {
endpoint.tag = 'cfg-' + i + '-out';
push(config.endpoints, endpoint);
}
} else {
const outbound = generate_outbound(urltest_node);
if (outbound) {
outbound.tag = 'cfg-' + i + '-out';
push(config.outbounds, outbound);
}
}
}
} else if (!isEmpty(default_outbound)) {
let urltest_nodes = [],
routing_nodes = [];
uci.foreach(uciconfig, uciroutingnode, (cfg) => {
if (cfg.enabled !== '1')
return;
if (cfg.node === 'urltest') {
const urltest_list = filter_existing_nodes(normalize_list(cfg.urltest_nodes));
push(config.outbounds, {
type: 'urltest',
tag: 'cfg-' + cfg['.name'] + '-out',
outbounds: map(urltest_list, (k) => `cfg-${k}-out`),
url: cfg.urltest_url,
interval: strToTime(cfg.urltest_interval),
tolerance: strToInt(cfg.urltest_tolerance),
idle_timeout: strToTime(cfg.urltest_idle_timeout),
interrupt_exist_connections: strToBool(cfg.urltest_interrupt_exist_connections)
});
urltest_nodes = [...urltest_nodes, ...filter(urltest_list, (l) => !~index(urltest_nodes, l))];
} else {
const outbound = uci.get_all(uciconfig, cfg.node) || {};
if (isEmpty(outbound))
return;
if (outbound.type === 'wireguard') {
const endpoint = generate_endpoint(outbound);
if (!endpoint)
return;
endpoint.bind_interface = cfg.bind_interface;
endpoint.detour = get_outbound(cfg.outbound);
if (cfg.domain_resolver)
endpoint.domain_resolver = {
server: get_resolver(cfg.domain_resolver),
strategy: cfg.domain_strategy
};
push(config.endpoints, endpoint);
} else {
const routed_outbound = generate_outbound(outbound);
if (!routed_outbound)
return;
routed_outbound.bind_interface = cfg.bind_interface;
routed_outbound.detour = get_outbound(cfg.outbound);
if (cfg.domain_resolver)
routed_outbound.domain_resolver = {
server: get_resolver(cfg.domain_resolver),
strategy: cfg.domain_strategy
};
push(config.outbounds, routed_outbound);
}
push(routing_nodes, cfg.node);
}
});
for (let i in filter(urltest_nodes, (l) => !~index(routing_nodes, l))) {
const urltest_node = uci.get_all(uciconfig, i) || {};
if (isEmpty(urltest_node))
continue;
if (urltest_node.type === 'wireguard') {
const endpoint = generate_endpoint(urltest_node);
if (endpoint)
push(config.endpoints, endpoint);
} else {
const outbound = generate_outbound(urltest_node);
if (outbound)
push(config.outbounds, outbound);
}
}
}
if (isEmpty(config.endpoints))
config.endpoints = null;
/* Outbound end */
/* Routing rules start */
/* Default settings */
config.route = {
rules: [
{
inbound: 'dns-in',
action: 'hijack-dns'
},
{
action: 'sniff'
}
],
rule_set: [],
auto_detect_interface: isEmpty(default_interface) ? true : null,
default_interface: default_interface
};
/* Routing rules */
if (!isEmpty(main_node)) {
/* Avoid DNS loop */
config.route.default_domain_resolver = {
server: (routing_mode === 'bypass_mainland_china') ? 'china-dns' : 'default-dns',
strategy: (ipv6_support !== '1') ? 'prefer_ipv4' : null
};
/* Direct list */
if (length(direct_domain_list))
push(config.route.rules, {
rule_set: 'direct-domain',
action: 'route',
outbound: 'direct-out'
});
/* Main UDP out */
if (dedicated_udp_node)
push(config.route.rules, {
network: 'udp',
action: 'route',
outbound: 'main-udp-out'
});
config.route.final = 'main-out';
/* Rule set */
/* Direct list */
if (length(direct_domain_list))
push(config.route.rule_set, {
type: 'inline',
tag: 'direct-domain',
rules: [
{
domain_keyword: direct_domain_list,
}
]
});
/* Proxy list */
if (length(proxy_domain_list))
push(config.route.rule_set, {
type: 'inline',
tag: 'proxy-domain',
rules: [
{
domain_keyword: proxy_domain_list,
}
]
});
if (routing_mode === 'bypass_mainland_china') {
push(config.route.rule_set, {
type: 'remote',
tag: 'geoip-cn',
format: 'binary',
url: 'https://fastly.jsdelivr.net/gh/1715173329/IPCIDR-CHINA@rule-set/cn.srs',
download_detour: 'direct-out'
});
push(config.route.rule_set, {
type: 'remote',
tag: 'geosite-cn',
format: 'binary',
url: 'https://fastly.jsdelivr.net/gh/1715173329/sing-geosite@rule-set-unstable/geosite-geolocation-cn.srs',
download_detour: 'direct-out'
});
push(config.route.rule_set, {
type: 'remote',
tag: 'geosite-noncn',
format: 'binary',
url: 'https://fastly.jsdelivr.net/gh/1715173329/sing-geosite@rule-set-unstable/geosite-geolocation-!cn.srs',
download_detour: 'direct-out'
});
}
if (isEmpty(config.route.rule_set))
config.route.rule_set = null;
} else if (!isEmpty(default_outbound)) {
config.route.default_domain_resolver = {
server: get_resolver(default_outbound_dns)
};
if (domain_strategy)
push(config.route.rules, {
action: 'resolve',
strategy: domain_strategy
});
uci.foreach(uciconfig, uciroutingrule, (cfg) => {
if (cfg.enabled !== '1')
return null;
push(config.route.rules, {
ip_version: strToInt(cfg.ip_version),
protocol: cfg.protocol,
network: cfg.network,
domain: cfg.domain,
domain_suffix: cfg.domain_suffix,
domain_keyword: cfg.domain_keyword,
domain_regex: cfg.domain_regex,
source_ip_cidr: cfg.source_ip_cidr,
source_ip_is_private: strToBool(cfg.source_ip_is_private),
ip_cidr: cfg.ip_cidr,
ip_is_private: strToBool(cfg.ip_is_private),
source_port: parse_port(cfg.source_port),
source_port_range: cfg.source_port_range,
port: parse_port(cfg.port),
port_range: cfg.port_range,
process_name: cfg.process_name,
process_path: cfg.process_path,
process_path_regex: cfg.process_path_regex,
user: cfg.user,
rule_set: get_ruleset(cfg.rule_set),
rule_set_ip_cidr_match_source: strToBool(cfg.rule_set_ip_cidr_match_source),
invert: strToBool(cfg.invert),
action: cfg.action,
outbound: get_outbound(cfg.outbound),
override_address: cfg.override_address,
override_port: strToInt(cfg.override_port),
udp_disable_domain_unmapping: strToBool(cfg.udp_disable_domain_unmapping),
udp_connect: strToBool(cfg.udp_connect),
udp_timeout: strToTime(cfg.udp_timeout),
tls_fragment: strToBool(cfg.tls_fragment),
tls_fragment_fallback_delay: strToTime(cfg.tls_fragment_fallback_delay),
tls_record_fragment: strToBool(cfg.tls_record_fragment)
});
});
config.route.final = get_outbound(default_outbound);
/* Rule set */
uci.foreach(uciconfig, uciruleset, (cfg) => {
if (cfg.enabled !== '1')
return null;
push(config.route.rule_set, {
type: cfg.type,
tag: 'cfg-' + cfg['.name'] + '-rule',
format: cfg.format,
path: cfg.path,
url: cfg.url,
download_detour: get_outbound(cfg.outbound) || ((cfg.type === 'remote') ? 'direct-out' : null),
update_interval: cfg.update_interval
});
});
}
/* Routing rules end */
/* Experimental start */
if (routing_mode in ['bypass_mainland_china', 'custom']) {
config.experimental = {
clash_api: {
external_controller: '127.0.0.1:9090'
},
cache_file: {
enabled: true,
path: HP_DIR + '/cache/cache.db',
store_rdrc: strToBool(cache_file_store_rdrc),
rdrc_timeout: strToTime(cache_file_rdrc_timeout),
}
};
}
/* Experimental end */
system('mkdir -p ' + RUN_DIR);
if (!writefile(RUN_DIR + '/sing-box-c.json.new', sprintf('%.J\n', removeBlankAttrs(config))))
exit(1);