🌴 Sync 2026-03-05 23:51:42

This commit is contained in:
github-actions[bot] committed 2026-03-05 23:51:42 +08:00
1 parent 97c049ed0a
commit 0acff03804
14253 files changed
+3893239

No files matched your search

+14
View File
@@ -0,0 +1,14 @@
#!/bin/sh
main() {
case "$1" in
list)
echo '{"statsquery":{},"statssys":{},"restartlogger":{}}'
;;
call)
shift
xray api $@
esac
}
main "$@"
@@ -0,0 +1,36 @@
{
"admin/services/xray_core": {
"title": "Xray",
"action": {
"type": "view",
"path": "xray/core"
},
"depends": {
"acl": [
"luci-app-xray"
],
"uci": {
"xray_core": true
}
}
},
"admin/services/xray_preview": {
"title": "Xray (preview)",
"action": {
"type": "view",
"path": "xray/preview"
},
"depends": {
"acl": [
"luci-app-xray"
],
"uci": {
"xray_core": {
"@general[0]": {
"preview_or_deprecated": true
}
}
}
}
}
}
@@ -0,0 +1 @@
include "/var/etc/xray/*.nft"
@@ -0,0 +1,15 @@
{
"luci-app-xray": {
"description": "Grant access to xray configurations",
"read": {
"uci": [
"xray_core"
]
},
"write": {
"uci": [
"xray_core"
]
}
}
}
@@ -0,0 +1,9 @@
"use strict";
import { cursor } from "uci";
export function load_config() {
const uci = cursor();
uci.load("xray_core");
return uci.get_all("xray_core") || {};
};
@@ -0,0 +1,225 @@
"use strict";
import { reality_outbound_settings, tls_outbound_settings } from "./tls.mjs";
function stream_tcp_fake_http_request(server) {
if (server["tcp_guise"] === "http") {
return {
version: "1.1",
method: "GET",
path: server["http_path"],
headers: {
"Host": server["http_host"],
"User-Agent": [
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36",
"Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_2 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/53.0.2785.109 Mobile/14A456 Safari/601.1.46"
],
"Accept-Encoding": ["gzip, deflate"],
"Connection": ["keep-alive"],
"Pragma": "no-cache"
}
};
}
return null;
}
function stream_tcp_fake_http_response(server) {
if (server["tcp_guise"] === "http") {
return {
version: "1.1",
status: "200",
reason: "OK",
headers: {
Content_Type: ["application/octet-stream", "video/mpeg"],
Transfer_Encoding: ["chunked"],
Connection: ["keep-alive"],
Pragma: "no-cache"
}
};
}
return null;
}
function stream_tcp(server) {
if (server["transport"] === "tcp") {
return {
header: {
type: server["tcp_guise"],
request: stream_tcp_fake_http_request(server),
response: stream_tcp_fake_http_response(server)
}
};
}
return null;
}
function stream_h2(server) {
if (server["transport"] === "h2") {
return {
path: server["h2_path"],
host: server["h2_host"],
read_idle_timeout: server["h2_health_check"] === "1" ? int(server["h2_read_idle_timeout"] || 10) : null,
health_check_timeout: server["h2_health_check"] === "1" ? int(server["h2_health_check_timeout"] || 20) : null,
};
}
return null;
}
function stream_grpc(server) {
if (server["transport"] === "grpc") {
return {
serviceName: server["grpc_service_name"],
multiMode: server["grpc_multi_mode"] === "1",
initial_windows_size: int(server["grpc_initial_windows_size"] || 0),
idle_timeout: server["grpc_health_check"] === "1" ? int(server["grpc_idle_timeout"] || 10) : null,
health_check_timeout: server["grpc_health_check"] === "1" ? int(server["grpc_health_check_timeout"] || 20) : null,
permit_without_stream: server["grpc_health_check"] === "1" ? (server["grpc_permit_without_stream"] === "1") : null
};
}
return null;
}
function stream_ws(server) {
if (server["transport"] === "ws") {
let headers = null;
if (server["ws_host"] != null) {
headers = {
Host: server["ws_host"]
};
}
return {
path: server["ws_path"],
headers: headers
};
}
return null;
}
function stream_kcp(server) {
if (server["transport"] === "mkcp") {
let mkcp_seed = null;
if (server["mkcp_seed"] != "") {
mkcp_seed = server["mkcp_seed"];
}
return {
mtu: int(server["mkcp_mtu"] || 1350),
tti: int(server["mkcp_tti"] || 50),
uplinkCapacity: int(server["mkcp_uplink_capacity"] || 5),
downlinkCapacity: int(server["mkcp_downlink_capacity"] || 20),
congestion: server["mkcp_congestion"] === "1",
readBufferSize: int(server["mkcp_read_buffer_size"] || 2),
writeBufferSize: int(server["mkcp_write_buffer_size"] || 2),
seed: mkcp_seed,
header: {
type: server["mkcp_guise"] || "none"
}
};
}
return null;
}
function stream_quic(server) {
if (server["transport"] === "quic") {
return {
security: server["quic_security"],
key: server["quic_key"],
header: {
type: server["quic_guise"]
}
};
}
return null;
}
function stream_splithttp(server) {
if (server["transport"] === "splithttp") {
return {
path: server["splithttp_path"],
host: server["splithttp_host"],
};
}
return null;
}
function stream_httpupgrade(server) {
if (server["transport"] === "httpupgrade") {
return {
path: server["httpupgrade_path"],
host: server["httpupgrade_host"],
};
}
return null;
}
function stream_hysteria(server) {
if (server["transport"] === "hysteria") {
let result = {
version: 2,
auth: server["password"],
};
let hysteria_up = server["hysteria_up_mbps"] || "";
if (hysteria_up !== "") {
result["up"] = `${hysteria_up}mbps`;
}
let hysteria_down = server["hysteria_down_mbps"] || "";
if (hysteria_down !== "") {
result["down"] = `${hysteria_down}mbps`;
}
let udphop_port = server["hysteria_udphop_port"] || "";
if (udphop_port !== "") {
result["udphop"] = {
port: udphop_port,
interval: int(server["hysteria_udphop_interval"] || 30)
};
}
return result;
}
return null;
}
export function port_array(i) {
if (type(i) === 'array') {
return map(i, v => int(v));
}
return [int(i)];
};
export function stream_settings(server, protocol, tag) {
const security = server[protocol + "_tls"];
let tlsSettings = null;
let realitySettings = null;
if (security === "tls") {
tlsSettings = tls_outbound_settings(server, protocol);
} else if (security === "reality") {
realitySettings = reality_outbound_settings(server, protocol);
}
let dialer_proxy = null;
let dialer_proxy_tag = null;
if (server["dialer_proxy"] != null && server["dialer_proxy"] != "disabled") {
dialer_proxy = server["dialer_proxy"];
dialer_proxy_tag = tag + `@dialer_proxy:${dialer_proxy}`;
}
return {
stream_settings: {
network: server["transport"],
sockopt: {
domainStrategy: server["domain_strategy"] || "UseIP",
dialerProxy: dialer_proxy_tag
},
security: security,
tlsSettings: tlsSettings,
realitySettings: realitySettings,
quicSettings: stream_quic(server),
tcpSettings: stream_tcp(server),
kcpSettings: stream_kcp(server),
wsSettings: stream_ws(server),
grpcSettings: stream_grpc(server),
httpSettings: stream_h2(server),
splithttpSettings: stream_splithttp(server),
httpupgradeSettings: stream_httpupgrade(server),
hysteriaSettings: stream_hysteria(server)
},
dialer_proxy: dialer_proxy
};
};
@@ -0,0 +1,85 @@
"use strict";
export function fallbacks(proxy, config) {
const fallback = filter(keys(config), k => config[k][".type"] == "fallback") || [];
let f = [];
for (let key in fallback) {
const s = config[key];
if (s["dest"] != null) {
push(f, {
dest: s["dest"],
alpn: s["alpn"],
name: s["name"],
xver: s["xver"],
path: s["path"]
});
}
}
push(f, {
dest: proxy["web_server_address"]
});
return f;
};
export function tls_outbound_settings(server, protocol) {
let result = {
serverName: server[protocol + "_tls_host"],
allowInsecure: server[protocol + "_tls_insecure"] != "0",
fingerprint: server[protocol + "_tls_fingerprint"] || ""
};
if (server[protocol + "_tls_alpn"] != null) {
result["alpn"] = server[protocol + "_tls_alpn"];
}
return result;
};
export function tls_inbound_settings(proxy, protocol_name) {
let wscert = proxy[protocol_name + "_tls_cert_file"];
if (wscert == null) {
wscert = proxy["web_server_cert_file"];
}
let wskey = proxy[protocol_name + "_tls_key_file"];
if (wskey == null) {
wskey = proxy["web_server_key_file"];
}
return {
alpn: [
"http/1.1"
],
certificates: [
{
certificateFile: wscert,
keyFile: wskey
}
]
};
};
export function reality_outbound_settings(server, protocol) {
let result = {
show: server[protocol + "_reality_show"] === "1",
fingerprint: server[protocol + "_reality_fingerprint"],
serverName: server[protocol + "_reality_server_name"],
publicKey: server[protocol + "_reality_public_key"],
shortId: server[protocol + "_reality_short_id"],
spiderX: server[protocol + "_reality_spider_x"],
};
return result;
};
export function reality_inbound_settings(proxy, protocol_name) {
return {
show: proxy[protocol_name + "_reality_show"],
dest: proxy[protocol_name + "_reality_dest"],
xver: proxy[protocol_name + "_reality_xver"],
serverNames: proxy[protocol_name + "_reality_server_names"],
privateKey: proxy[protocol_name + "_reality_private_key"],
minClientVer: proxy[protocol_name + "_reality_min_client_ver"],
maxClientVer: proxy[protocol_name + "_reality_max_client_ver"],
maxTimeDiff: proxy[protocol_name + "_reality_max_time_diff"],
shortIds: proxy[protocol_name + "_reality_short_ids"],
};
};
@@ -0,0 +1,110 @@
#!/usr/bin/ucode
"use strict";
import { open, popen, stat } from "fs";
import { connect } from "ubus";
function network_dump() {
const ubus = connect();
if (ubus) {
const result = ubus.call("network.interface", "dump");
ubus.disconnect();
return result;
}
return {
"interface": []
};
}
function get_default_gateway(dump) {
let dgs = {};
for (let i in dump["interface"] || []) {
for (let j in i["route"] || []) {
if (j["target"] == "0.0.0.0") {
dgs[j["nexthop"]] = true;
if (j["source"] != "0.0.0.0/0") {
dgs[j["source"]] = true;
}
}
}
};
return keys(dgs);
}
function get_prefix_delegate(dump) {
let pds = {};
for (let i in dump["interface"] || []) {
for (let j in i["ipv6-prefix"] || []) {
if (j["assigned"]) {
pds[`${j["address"]}/${j["mask"]}`] = true;
}
}
}
return keys(pds);
}
function gen_tp_spec_dv4_dg(dg) {
if (stat("/usr/share/xray/ignore_tp_spec_def_gw")) {
return "";
}
if (length(dg) > 0) {
return `set tp_spec_dv4_dg {
type ipv4_addr
size 16
flags interval
elements = { ${join(", ", dg)} }
}\n`;
}
return "";
}
function gen_tp_spec_dv6_dg(pd) {
if (length(pd) > 0) {
return `set tp_spec_dv6_dg {
type ipv6_addr
size 16
flags interval
elements = { ${join(", ", pd)} }
}\n`;
}
return "";
}
function generate_include(rule_dg, rule_pd, file_path) {
const handle = open(file_path, "w");
handle.write(rule_dg);
handle.write(rule_pd);
handle.flush();
handle.close();
}
function update_nft(rule_dg, rule_pd) {
const handle = popen("nft -f -", "w");
handle.write(`table inet fw4 {
${rule_dg}
${rule_pd}
}`);
handle.flush();
handle.close();
}
function restart_dnsmasq_if_necessary() {
if (stat("/usr/share/xray/restart_dnsmasq_on_iface_change")) {
system("service dnsmasq restart");
}
}
const dump = network_dump();
const dg = get_default_gateway(dump);
const pd = get_prefix_delegate(dump);
const log = join(", ", [...dg, ...pd]);
if (log == "") {
print("default gateway not available, please wait for interface ready");
} else {
print(`default gateway available at ${log}\n`);
const rule_dg = gen_tp_spec_dv4_dg(dg);
const rule_pd = gen_tp_spec_dv6_dg(pd);
update_nft(rule_dg, rule_pd);
generate_include(rule_dg, rule_pd, "/var/etc/xray/02_default_gateway_include.nft");
}
restart_dnsmasq_if_necessary();
@@ -0,0 +1,27 @@
#!/usr/bin/utpl
{%
"use strict";
import { load_config } from "./common/config.mjs";
const config = load_config();
const general = config[filter(keys(config), k => config[k][".type"] == "general")[0]];
const dns_port = int(general["dns_port"] || 5300);
const dns_count = int(general["dns_count"] || 3);
const manual_tproxy = filter(keys(config), k => config[k][".type"] == "manual_tproxy") || [];
%}
# Generated dnsmasq configurations by luci-app-xray
strict-order
server=/#/127.0.0.1#{{ dns_port }}
{% for (let i = dns_port; i <= dns_port + dns_count; i++): %}
server=127.0.0.1#{{ i }}
{% endfor %}
{% for (let i in manual_tproxy): %}
{% if (config[i]["rebind_domain_ok"] == "1"): %}
{% for (let j in config[i]["domain_names"]): %}
rebind-domain-ok={{ j }}
{% endfor %}
{% endif %}
{% endfor %}
{% if (general["blocked_to_loopback"] != "1"): %}
bogus-nxdomain=127.127.127.127
bogus-nxdomain=100::6c62:636f:656b:2164
{% endif %}
@@ -0,0 +1,46 @@
"use strict";
import { server_outbound } from "./outbound.mjs";
export function bridges(bridge) {
let result = [];
for (let v in bridge) {
push(result, {
tag: sprintf("bridge_inbound:%s", v[".name"]),
domain: v["domain"]
});
}
return result;
};
export function bridge_outbounds(config, bridge) {
let result = [];
for (let v in bridge) {
const bridge_server = config[v["upstream"]];
push(result, {
tag: sprintf("bridge_freedom_outbound:%s", v[".name"]),
protocol: "freedom",
settings: {
redirect: v["redirect"]
}
}, ...server_outbound(bridge_server, sprintf("bridge_upstream_outbound:%s", v[".name"]), config));
}
return result;
};
export function bridge_rules(bridge) {
let result = [];
for (let v in bridge) {
push(result, {
type: "field",
inboundTag: [sprintf("bridge_inbound:%s", v[".name"])],
domain: [sprintf("full:%s", v["domain"])],
outboundTag: sprintf("bridge_upstream_outbound:%s", v[".name"])
}, {
type: "field",
inboundTag: [sprintf("bridge_inbound:%s", v[".name"])],
outboundTag: sprintf("bridge_freedom_outbound:%s", v[".name"])
});
}
return result;
};
@@ -0,0 +1,289 @@
"use strict";
import { access } from "fs";
import { fake_dns_domains } from "./fake_dns.mjs";
import { direct_outbound } from "./outbound.mjs";
const fallback_fast_dns = "223.5.5.5:53";
const fallback_secure_dns = "8.8.8.8:53";
const fallback_default_dns = "1.1.1.1:53";
const geoip_existence = access("/usr/share/xray/geoip.dat") || false;
const geosite_existence = access("/usr/share/xray/geosite.dat") || false;
function parse_ip_port(val, port_default) {
const split_dot = split(val, ".");
if (length(split_dot) > 1) {
const split_ipv4 = split(val, ":");
return {
ip: split_ipv4[0],
port: int(split_ipv4[1])
};
}
const split_ipv6_port = split(val, "]:");
if (length(split_ipv6_port) == 2) {
return {
ip: ltrim(split_ipv6_port[0], "["),
port: int(split_ipv6_port[1]),
};
}
return {
ip: val,
port: port_default
};
}
function format_dns(method, val) {
const parsed = parse_ip_port(val, 53);
if (method === "udp") {
return {
address: parsed["ip"],
port: parsed["port"]
};
}
let url_suffix = "";
if (substr(method, 0, 5) === "https") {
url_suffix = "/dns-query";
}
return {
address: `${method}://${val}${url_suffix}`
};
}
function domain_rules(proxy, k) {
if (proxy[k] === null) {
return [];
}
return filter(proxy[k], function (x) {
if (substr(x, 0, 8) === "geosite:") {
return geosite_existence;
}
return true;
});
}
export function secure_domain_rules(proxy) {
return domain_rules(proxy, "forwarded_domain_rules");
};
export function fast_domain_rules(proxy) {
return domain_rules(proxy, "bypassed_domain_rules");
};
export function blocked_domain_rules(proxy) {
return domain_rules(proxy, "blocked_domain_rules");
};
export function dns_server_inbounds(proxy) {
let result = [];
const dns_port = int(proxy["dns_port"] || 5300);
const dns_count = int(proxy["dns_count"] || 3);
const default_dns = format_dns("udp", proxy["default_dns"] || fallback_default_dns);
for (let i = dns_port; i <= dns_port + dns_count; i++) {
push(result, {
port: i,
protocol: "dokodemo-door",
tag: sprintf("dns_server_inbound:%d", i),
settings: {
address: default_dns["address"],
port: default_dns["port"],
network: "tcp,udp"
}
});
}
return result;
};
export function dns_rules(proxy, tcp_hijack_inbound_tags, udp_hijack_inbound_tags) {
const dns_port = int(proxy["dns_port"] || 5300);
const dns_count = int(proxy["dns_count"] || 3);
const fast_dns = parse_ip_port(proxy["fast_dns"] || fallback_fast_dns, 53);
const secure_dns = parse_ip_port(proxy["secure_dns"] || fallback_secure_dns, 53);
let dns_server_tags = [];
for (let i = dns_port; i <= dns_port + dns_count; i++) {
push(dns_server_tags, sprintf("dns_server_inbound:%d", i));
}
let result = [
{
type: "field",
inboundTag: ["dns_conf_inbound"],
ip: [fast_dns["ip"]],
port: `${fast_dns["port"]}`,
outboundTag: "dynamic_direct"
},
{
type: "field",
inboundTag: ["dns_conf_inbound"],
ip: [secure_dns["ip"]],
port: `${secure_dns["port"]}`,
balancerTag: "udp_outbound_v4"
},
{
type: "field",
inboundTag: dns_server_tags,
outboundTag: "dns_server_outbound"
},
];
if (proxy.dns_tcp_hijack) {
push(result, {
type: "field",
port: "53",
inboundTag: tcp_hijack_inbound_tags,
outboundTag: "dns_tcp_hijack_outbound"
});
}
if (proxy.dns_udp_hijack) {
push(result, {
type: "field",
port: "53",
inboundTag: udp_hijack_inbound_tags,
outboundTag: "dns_udp_hijack_outbound"
});
}
return result;
};
export function dns_server_outbounds(proxy) {
let result = [
{
protocol: "dns",
settings: {
nonIPQuery: "skip"
},
streamSettings: {
sockopt: {
mark: 254
}
},
tag: "dns_server_outbound"
}
];
if (proxy.dns_tcp_hijack) {
push(result, direct_outbound("dns_tcp_hijack_outbound", proxy.dns_tcp_hijack, false));
}
if (proxy.dns_udp_hijack) {
push(result, direct_outbound("dns_udp_hijack_outbound", proxy.dns_udp_hijack, false));
}
return result;
};
export function dns_conf(proxy, config, manual_tproxy, fakedns) {
const fast_dns_object = format_dns("udp", proxy["fast_dns"] || fallback_fast_dns);
const default_dns_object = format_dns("udp", proxy["default_dns"] || fallback_default_dns);
let domain_names_set = {};
let domain_extra_options = {};
for (let server in filter(values(config), i => i[".type"] === "servers")) {
if (iptoarr(server["server"])) {
continue;
}
if (server["domain_resolve_dns"]) {
domain_extra_options[server["server"]] = `${server["domain_resolve_dns_method"] || "udp"};${server["domain_resolve_dns"]};${join(",", server["domain_resolve_expect_ips"] || [])}`;
} else {
domain_names_set[`domain:${server["server"]}`] = true;
}
}
let resolve_merged = {};
for (let k in keys(domain_extra_options)) {
const v = domain_extra_options[k];
let original = resolve_merged[v] || [];
push(original, `domain:${k}`);
resolve_merged[v] = original;
}
let servers = [
...fake_dns_domains(fakedns),
...map(keys(resolve_merged), function (k) {
const dns_split = split(k, ";");
const resolve_dns_object = format_dns(dns_split[0], dns_split[1]);
let result = {
address: resolve_dns_object["address"],
port: resolve_dns_object["port"],
domains: uniq(resolve_merged[k]),
skipFallback: true,
};
if (length(dns_split[2]) > 0) {
const expect_ips = filter(split(dns_split[2], ",") || [], function (i) {
if (!geoip_existence) {
if (substr(i, 0, 6) === "geoip:") {
return false;
}
}
return true;
});
result["expectIPs"] = expect_ips;
}
return result;
}),
default_dns_object,
{
address: fast_dns_object["address"],
port: fast_dns_object["port"],
domains: [...keys(domain_names_set), ...fast_domain_rules(proxy)],
skipFallback: true,
expectIPs: function () {
let ips = [];
if (proxy["align_fast_dns_to_geoip_direct"] === "1") {
if (geoip_existence) {
const geoip_direct_code_list = map(proxy["geoip_direct_code_list"] || [], v => index(v, ":") > 0 ? v : `geoip:${v}`);
const geoip_direct_code_list_v6 = map(proxy["geoip_direct_code_list_v6"] || [], v => index(v, ":") > 0 ? v : `geoip:${v}`);
push(ips, ...geoip_direct_code_list, ...geoip_direct_code_list_v6);
}
}
if (length(ips) > 0) {
return uniq(ips);
}
return null;
}(),
},
];
if (length(secure_domain_rules(proxy)) > 0) {
const secure_dns_object = format_dns("udp", proxy["secure_dns"] || fallback_secure_dns);
push(servers, {
address: secure_dns_object["address"],
port: secure_dns_object["port"],
domains: secure_domain_rules(proxy),
});
}
let hosts = {};
if (length(blocked_domain_rules(proxy)) > 0) {
for (let rule in (blocked_domain_rules(proxy))) {
hosts[rule] = ["127.127.127.127", "100::6c62:636f:656b:2164"]; // blocked!
}
}
for (let v in manual_tproxy) {
if (v.domain_names != null) {
for (let d in v.domain_names) {
if (index(v.source_addr, ":") === -1) {
hosts[d] = [v.source_addr];
}
}
}
}
return {
hosts: hosts,
servers: servers,
tag: "dns_conf_inbound",
queryStrategy: "UseIP"
};
};
export function dns_direct_servers(config) {
let result = [];
for (let server in filter(values(config), i => i[".type"] === "servers")) {
if (iptoarr(server["server"])) {
continue;
}
if (server["domain_resolve_dns"]) {
if (index(server["domain_resolve_dns_method"], "local") > 1) {
push(result, parse_ip_port(server["domain_resolve_dns"]));
}
}
}
return result;
};
@@ -0,0 +1,71 @@
"use strict";
import { balancer } from "./system.mjs";
export function fake_dns_domains(fakedns) {
let domains = [];
for (let f in fakedns) {
push(domains, ...f["fake_dns_domain_names"]);
}
if (length(domains) === 0) {
return [];
}
return [
{
"address": "fakedns",
"domains": domains,
"skipFallback": true
}
];
};
export function fake_dns_rules(fakedns) {
let result = [];
for (let f in fakedns) {
push(result, {
type: "field",
inboundTag: ["tproxy_tcp_inbound_f4", "tproxy_tcp_inbound_f6"],
domain: f["fake_dns_domain_names"],
balancerTag: `fake_dns_balancer:${f[".name"]}@tcp_balancer`
}, {
type: "field",
inboundTag: ["tproxy_udp_inbound_f4", "tproxy_udp_inbound_f6"],
domain: f["fake_dns_domain_names"],
balancerTag: `fake_dns_balancer:${f[".name"]}@udp_balancer`
});
}
return result;
};
export function fake_dns_balancers(fakedns) {
let result = [];
for (let f in fakedns) {
push(result, {
"tag": `fake_dns_balancer:${f[".name"]}@tcp_balancer`,
"selector": balancer(f, "fake_dns_forward_server_tcp", `fake_dns_tcp:${f[".name"]}`),
"strategy": {
"type": f["fake_dns_balancer_strategy"] || "random"
}
}, {
"tag": `fake_dns_balancer:${f[".name"]}@udp_balancer`,
"selector": balancer(f, "fake_dns_forward_server_udp", `fake_dns_udp:${f[".name"]}`),
"strategy": {
"type": f["fake_dns_balancer_strategy"] || "random"
}
});
}
return result;
};
export function fake_dns_conf(proxy) {
return [
{
"ipPool": proxy.pool_v4 || "198.18.0.0/15",
"poolSize": int(proxy.pool_v4_size) || 65535
},
{
"ipPool": proxy.pool_v6 || "fc00::/18",
"poolSize": int(proxy.pool_v6_size) || 65535
}
];
};
@@ -0,0 +1,149 @@
"use strict";
import { https_trojan_inbound } from "../protocol/trojan.mjs";
import { https_vless_inbound } from "../protocol/vless.mjs";
import { balancer } from "./system.mjs";
export function dokodemo_inbound(listen, port, tag, sniffing, sniffing_route_only, sniffing_dest_override, sniffing_metadata_only, network, tproxy, timeout) {
let result = {
port: int(port),
protocol: "dokodemo-door",
tag: tag,
sniffing: sniffing == "1" ? {
enabled: true,
routeOnly: sniffing_route_only == "1",
destOverride: sniffing_dest_override,
metadataOnly: sniffing_metadata_only == "1"
} : null,
settings: {
network: network,
followRedirect: true,
timeout: int(timeout || 300),
},
streamSettings: {
sockopt: {
tproxy: tproxy
}
}
};
if (listen) {
result["listen"] = listen;
}
return result;
};
export function http_inbound(addr, port, tag, username, password) {
let accounts = null;
if (username && password) {
accounts = [
{
"user": username,
"pass": password
}
];
}
return {
listen: addr || "0.0.0.0",
port: port,
protocol: "http",
tag: tag,
settings: {
accounts: accounts,
allowTransparent: false
}
};
};
export function socks_inbound(addr, port, tag, username, password) {
let auth = "noauth";
let accounts = null;
if (username && password) {
auth = "password";
accounts = [
{
"user": username,
"pass": password
}
];
}
return {
listen: addr || "0.0.0.0",
port: port,
protocol: "socks",
tag: tag,
settings: {
auth: auth,
accounts: accounts,
udp: true
}
};
};
export function https_inbound(proxy, config) {
if (proxy["web_server_protocol"] == "vless") {
return https_vless_inbound(proxy, config);
}
if (proxy["web_server_protocol"] == "trojan") {
return https_trojan_inbound(proxy, config);
}
return null;
};
export function extra_inbounds(proxy, extra_inbound) {
let result = [];
for (let v in extra_inbound) {
const tag = `extra_inbound:${v[".name"]}`;
if (v["inbound_type"] == "http") {
push(result, http_inbound(v["inbound_addr"] || "0.0.0.0", v["inbound_port"], tag, v["inbound_username"], v["inbound_password"]));
} else if (v["inbound_type"] == "socks5") {
push(result, socks_inbound(v["inbound_addr"] || "0.0.0.0", v["inbound_port"], tag, v["inbound_username"], v["inbound_password"]));
} else if (v["inbound_type"] == "tproxy_tcp") {
push(result, dokodemo_inbound(v["inbound_addr"] || "0.0.0.0", v["inbound_port"], tag, proxy["tproxy_sniffing"], proxy["route_only"], ["http", "tls"], "0", "tcp", "tproxy"));
} else if (v["inbound_type"] == "tproxy_udp") {
push(result, dokodemo_inbound(v["inbound_addr"] || "0.0.0.0", v["inbound_port"], tag, proxy["tproxy_sniffing"], proxy["route_only"], ["quic"], "0", "udp", "tproxy"));
} else {
die(`unknown inbound type ${v["inbound_type"]}`);
}
}
return result;
};
export function extra_inbound_rules(extra_inbound) {
let result = [];
for (let v in extra_inbound) {
if (v["specify_outbound"] == "1") {
push(result, {
type: "field",
inboundTag: [`extra_inbound:${v[".name"]}`],
balancerTag: `extra_inbound_outbound:${v[".name"]}`
});
}
}
return result;
};
export function extra_inbound_balancers(extra_inbound) {
let result = [];
for (let e in extra_inbound) {
if (e["specify_outbound"] == "1") {
push(result, {
"tag": `extra_inbound_outbound:${e[".name"]}`,
"selector": balancer(e, "destination", `extra_inbound:${e[".name"]}`),
"strategy": {
"type": e["balancer_strategy"] || "random"
}
});
}
}
return result;
};
export function extra_inbound_global(extra_inbound) {
const global_tags = filter(extra_inbound, v => v["specify_outbound"] != "1");
return {
"tproxy_tcp": map(filter(global_tags, v => v["inbound_type"] == "tproxy_tcp"), v => `extra_inbound_${v[".name"]}`),
"tproxy_udp": map(filter(global_tags, v => v["inbound_type"] == "tproxy_udp"), v => `extra_inbound_${v[".name"]}`),
"http": map(filter(global_tags, v => v["inbound_type"] == "http"), v => `extra_inbound_${v[".name"]}`),
"socks5": map(filter(global_tags, v => v["inbound_type"] == "socks5"), v => `extra_inbound_${v[".name"]}`),
};
};
@@ -0,0 +1,83 @@
"use strict";
import { server_outbound } from "./outbound.mjs";
export function manual_tproxy_outbounds(config, manual_tproxy) {
let result = [];
for (let v in manual_tproxy) {
let tcp_tag = "direct";
if (v["force_forward_tcp"] === "1") {
if (v["force_forward_server_tcp"] != null) {
tcp_tag = `manual_tproxy:${v[".name"]}@tcp_outbound@force_forward:${v["force_forward_server_tcp"]}`;
const force_forward_server_tcp = config[v["force_forward_server_tcp"]];
push(result, ...server_outbound(force_forward_server_tcp, tcp_tag, config));
}
}
push(result, {
protocol: "freedom",
tag: sprintf("manual_tproxy:%s@tcp_outbound", v[".name"]),
settings: {
redirect: sprintf("%s:%d", v["dest_addr"] || "", v["dest_port"] || 0),
domainStrategy: "AsIs"
},
proxySettings: {
tag: tcp_tag
}
});
let udp_tag = "direct";
if (v["force_forward_udp"] === "1") {
if (v["force_forward_server_udp"] != null) {
udp_tag = `manual_tproxy:${v[".name"]}@udp_outbound@force_forward:${v["force_forward_server_udp"]}`;
const force_forward_server_udp = config[v["force_forward_server_udp"]];
push(result, ...server_outbound(force_forward_server_udp, udp_tag, config));
}
}
push(result, {
protocol: "freedom",
tag: sprintf("manual_tproxy:%s@udp_outbound", v[".name"]),
settings: {
redirect: sprintf("%s:%d", v["dest_addr"] || "", v["dest_port"] || 0),
domainStrategy: "AsIs"
},
proxySettings: {
tag: udp_tag
}
});
}
return result;
};
export function manual_tproxy_outbound_tags(manual_tproxy) {
let result = [];
for (let v in manual_tproxy) {
if (v["force_forward_tcp"] === "1") {
push(result, `manual_tproxy:${v[".name"]}@tcp_outbound@force_forward:${v["force_forward_server_tcp"]}`);
}
if (v["force_forward_udp"] === "1") {
push(result, `manual_tproxy:${v[".name"]}@udp_outbound@force_forward:${v["force_forward_server_udp"]}`);
}
}
return result;
};
export function manual_tproxy_rules(manual_tproxy) {
let result = [];
for (let v in manual_tproxy) {
splice(result, 0, 0, {
type: "field",
inboundTag: ["tproxy_tcp_inbound_v4", "tproxy_tcp_inbound_v6", "socks_inbound", "https_inbound", "http_inbound"],
ip: [v["source_addr"]],
port: v["source_port"],
outboundTag: sprintf("manual_tproxy:%s@tcp_outbound", v[".name"])
});
splice(result, 0, 0, {
type: "field",
inboundTag: ["tproxy_udp_inbound_v4", "tproxy_udp_inbound_v6"],
ip: [v["source_addr"]],
port: v["source_port"],
outboundTag: sprintf("manual_tproxy:%s@udp_outbound", v[".name"])
});
}
return result;
};
@@ -0,0 +1,100 @@
"use strict";
import { http_outbound } from "../protocol/http.mjs";
import { shadowsocks_outbound } from "../protocol/shadowsocks.mjs";
import { socks_outbound } from "../protocol/socks.mjs";
import { trojan_outbound } from "../protocol/trojan.mjs";
import { vless_outbound } from "../protocol/vless.mjs";
import { vmess_outbound } from "../protocol/vmess.mjs";
import { hysteria_outbound } from "../protocol/hysteria.mjs";
const direct_mark = 252;
const outbound_mark = 253;
function override_custom_config_recursive(x, y) {
if (type(x) != "object" || type(y) != "object") {
return y;
}
for (let k in y) {
x[k] = override_custom_config_recursive(x[k], y[k]);
}
return x;
}
function server_outbound_recursive(t, server, tag, config) {
let outbound_result = null;
if (server["protocol"] == "vmess") {
outbound_result = vmess_outbound(server, tag);
} else if (server["protocol"] == "vless") {
outbound_result = vless_outbound(server, tag);
} else if (server["protocol"] == "shadowsocks") {
outbound_result = shadowsocks_outbound(server, tag);
} else if (server["protocol"] == "trojan") {
outbound_result = trojan_outbound(server, tag);
} else if (server["protocol"] == "http") {
outbound_result = http_outbound(server, tag);
} else if (server["protocol"] == "socks") {
outbound_result = socks_outbound(server, tag);
} else if (server["protocol"] == "hysteria") {
outbound_result = hysteria_outbound(server, tag);
}
if (outbound_result == null) {
die(`unknown outbound server protocol ${server["protocol"]}`);
}
let outbound = outbound_result["outbound"];
const custom_config_outbound_string = server["custom_config"];
if (custom_config_outbound_string != null && custom_config_outbound_string != "") {
const custom_config_outbound = json(custom_config_outbound_string);
for (let k in custom_config_outbound) {
outbound[k] = override_custom_config_recursive(outbound[k], custom_config_outbound[k]);
}
}
outbound["tag"] = tag;
if (type(outbound["streamSettings"]) != "object") {
outbound["streamSettings"] = {};
}
if (type(outbound["streamSettings"]["sockopt"]) != "object") {
outbound["streamSettings"]["sockopt"] = {};
}
outbound["streamSettings"]["sockopt"]["mark"] = outbound_mark;
const dialer_proxy = outbound_result["dialer_proxy"];
const result = [...t, outbound];
if (dialer_proxy != null) {
const dialer_proxy_section = config[dialer_proxy];
return server_outbound_recursive(result, dialer_proxy_section, `${tag}@dialer_proxy:${dialer_proxy}`, config);
}
return result;
}
export function direct_outbound(tag, redirect, enable_dynamic_direct) {
return {
protocol: "freedom",
tag: tag,
settings: {
domainStrategy: "UseIPv4",
redirect: redirect || ""
},
streamSettings: {
sockopt: {
mark: enable_dynamic_direct ? direct_mark : outbound_mark,
}
}
};
};
export function blackhole_outbound() {
return {
tag: "blackhole_outbound",
protocol: "blackhole"
};
};
export function server_outbound(server, tag, config) {
if (server == null) {
return [direct_outbound(tag, null, false)];
}
return server_outbound_recursive([], server, tag, config);
};
@@ -0,0 +1,82 @@
"use strict";
export function balancer(ref, x, prefix) {
const v = ref[x] || [];
if (length(v) == 0) {
return ["direct"];
}
return map(v, (k) => `${prefix}@balancer_outbound:${k}`);
};
export function api_conf(proxy) {
if (proxy["xray_api"] == '1') {
return {
tag: "api",
services: [
"HandlerService",
"LoggerService",
"StatsService"
]
};
}
return null;
};
export function metrics_conf(proxy) {
if (proxy["metrics_server_enable"] == "1") {
return {
tag: "metrics"
};
}
return null;
};
export function policy(proxy) {
const stats = proxy["stats"] == "1";
return {
levels: {
"0": {
handshake: int(proxy["handshake"] || 4),
connIdle: int(proxy["conn_idle"] || 300),
uplinkOnly: int(proxy["uplink_only"] || 2),
downlinkOnly: int(proxy["downlink_only"] || 5),
bufferSize: int(proxy["buffer_size"] || 4),
statsUserUplink: stats,
statsUserDownlink: stats,
}
},
system: {
statsInboundUplink: stats,
statsInboundDownlink: stats,
statsOutboundUplink: stats,
statsOutboundDownlink: stats
}
};
};
export function logging(proxy) {
return {
access: proxy["access_log"] == "1" ? "" : "none",
loglevel: proxy["loglevel"] || "warning",
dnsLog: proxy["dns_log"] == "1"
};
};
export function system_route_rules(proxy) {
let result = [];
if (proxy["xray_api"] == '1') {
push(result, {
type: "field",
inboundTag: ["api"],
outboundTag: "api"
});
}
if (proxy["metrics_server_enable"] == "1") {
push(result, {
type: "field",
inboundTag: ["metrics"],
outboundTag: "metrics"
});
}
return result;
};
@@ -0,0 +1,618 @@
#!/usr/bin/utpl
{%
"use strict";
import { stat } from "fs";
import { load_config } from "./common/config.mjs";
import { dns_direct_servers } from "./feature/dns.mjs";
const ignore_tp_spec_def_gw = stat("/usr/share/xray/ignore_tp_spec_def_gw");
const config = load_config();
const general = config[filter(keys(config), k => config[k][".type"] == "general")[0]];
const general_mark = general.mark || 255;
const tcp4_enabled = length(general.tcp_balancer_v4 || []) > 0;
const udp4_enabled = length(general.udp_balancer_v4 || []) > 0;
const tcp6_enabled = length(general.tcp_balancer_v6 || []) > 0;
const udp6_enabled = length(general.udp_balancer_v6 || []) > 0;
const uids_direct = uniq(general.uids_direct || []);
const gids_direct = uniq(general.gids_direct || []);
let wan_bp_ips = uniq(general.wan_bp_ips || []);
let wan_fw_ips = uniq(general.wan_fw_ips || []);
let wan_bp_dns = dns_direct_servers(config);
const wan_bp_ips_v4 = filter(wan_bp_ips, v => index(v, ":") == -1);
const wan_bp_ips_v6 = filter(wan_bp_ips, v => index(v, ":") != -1);
const wan_fw_ips_v4 = filter(wan_fw_ips, v => index(v, ":") == -1);
const wan_fw_ips_v6 = filter(wan_fw_ips, v => index(v, ":") != -1);
const wan_bp_dns_v4 = filter(wan_bp_dns, v => index(v.ip, ":") == -1);
const wan_bp_dns_v6 = filter(wan_bp_dns, v => index(v.ip, ":") != -1);
const transparent_default_port_policy = general.transparent_default_port_policy || "forwarded";
const wan_fw_tcp_ports = general.wan_fw_tcp_ports || [];
const wan_fw_udp_ports = general.wan_fw_udp_ports || [];
const wan_bp_tcp_ports = general.wan_bp_tcp_ports || [];
const wan_bp_udp_ports = general.wan_bp_udp_ports || [];
const counter = function () {
if (general.fw4_counter == "1") {
return "counter packets 0 bytes 0";
}
return "";
}();
const firewall_priority = function () {
if (general.firewall_priority == null) {
return "+10";
}
if (int(general.firewall_priority) > 0) {
return sprintf("+%s", general.firewall_priority);
};
return sprintf("%s", general.firewall_priority);
}();
const fakedns = map(filter(keys(config), k => config[k][".type"] == "fakedns") || [], k => config[k]);
const manual_tproxy = filter(keys(config), k => config[k][".type"] == "manual_tproxy") || [];
const manual_tproxy_source_ips = filter(map(manual_tproxy, k => config[k]["source_addr"]) || [], v => index(v, ":") == -1);
const tp_spec_sm4_tp = uniq(map(filter(keys(config), k => config[k][".type"] == "lan_hosts" && config[k].access_control_strategy_v4 == "tproxy"), k => config[k].macaddr) || []);
const tp_spec_sm6_tp = uniq(map(filter(keys(config), k => config[k][".type"] == "lan_hosts" && config[k].access_control_strategy_v6 == "tproxy"), k => config[k].macaddr) || []);
const tp_spec_sm4_bp = uniq(map(filter(keys(config), k => config[k][".type"] == "lan_hosts" && config[k].access_control_strategy_v4 == "bypass"), k => config[k].macaddr) || []);
const tp_spec_sm6_bp = uniq(map(filter(keys(config), k => config[k][".type"] == "lan_hosts" && config[k].access_control_strategy_v6 == "bypass"), k => config[k].macaddr) || []);
const tp_spec_sm4_fw = map(filter(keys(config), k => config[k][".type"] == "lan_hosts" && config[k].access_control_strategy_v4 == "forward"), k => config[k]);
const tp_spec_sm6_fw = map(filter(keys(config), k => config[k][".type"] == "lan_hosts" && config[k].access_control_strategy_v6 == "forward"), k => config[k]);
const used_extra_inbound = uniq([
...map(tp_spec_sm4_fw, k => k["access_control_forward_tcp_v4"]),
...map(tp_spec_sm4_fw, k => k["access_control_forward_udp_v4"]),
...map(tp_spec_sm6_fw, k => k["access_control_forward_tcp_v6"]),
...map(tp_spec_sm6_fw, k => k["access_control_forward_udp_v6"]),
]);
let extra_inbound_tcp_v4_map = {};
let extra_inbound_udp_v4_map = {};
let extra_inbound_tcp_v6_map = {};
let extra_inbound_udp_v6_map = {};
for (let i in used_extra_inbound) {
let tcp_v4_items = [];
let udp_v4_items = [];
let tcp_v6_items = [];
let udp_v6_items = [];
for (let j in tp_spec_sm4_fw) {
if (j["access_control_forward_tcp_v4"] == i) {
push(tcp_v4_items, j["macaddr"]);
}
if (j["access_control_forward_udp_v4"] == i) {
push(udp_v4_items, j["macaddr"]);
}
}
if (length(tcp_v4_items) > 0) {
extra_inbound_tcp_v4_map[i] = tcp_v4_items;
}
if (length(udp_v4_items) > 0) {
extra_inbound_udp_v4_map[i] = udp_v4_items;
}
for (let j in tp_spec_sm6_fw) {
if (j["access_control_forward_tcp_v6"] == i) {
push(tcp_v6_items, j["macaddr"]);
}
if (j["access_control_forward_udp_v6"] == i) {
push(udp_v6_items, j["macaddr"]);
}
}
if (length(tcp_v6_items) > 0) {
extra_inbound_tcp_v6_map[i] = tcp_v6_items;
}
if (length(udp_v6_items) > 0) {
extra_inbound_udp_v6_map[i] = udp_v6_items;
}
}
const ttl_override = int(general.ttl_override);
const hop_limit_override = int(general.hop_limit_override);
const ttl_hop_limit_match = int(general.ttl_hop_limit_match);
const dynamic_direct_timeout = general.dynamic_direct_timeout || 2233;
const dynamic_direct_tcp4 = function () {
if (general.dynamic_direct_tcp4 == "1") {
return `update @tp_spec_dv4_dd { ip daddr timeout ${dynamic_direct_timeout}s } update @tp_spec_dv4_dd { ip saddr timeout ${dynamic_direct_timeout}s }`;
}
return "";
}();
const dynamic_direct_tcp6 = function () {
if (general.dynamic_direct_tcp6 == "1") {
return `update @tp_spec_dv6_dd { ip6 daddr timeout ${dynamic_direct_timeout}s } update @tp_spec_dv6_dd { ip6 saddr timeout ${dynamic_direct_timeout}s }`;
}
return "";
}();
const dynamic_direct_udp4 = function () {
if (general.dynamic_direct_udp4 == "1") {
return `update @tp_spec_dv4_dd { ip daddr timeout ${dynamic_direct_timeout}s } update @tp_spec_dv4_dd { ip saddr timeout ${dynamic_direct_timeout}s }`;
}
return "";
}();
const dynamic_direct_udp6 = function () {
if (general.dynamic_direct_udp6 == "1") {
return `update @tp_spec_dv6_dd { ip6 daddr timeout ${dynamic_direct_timeout}s } update @tp_spec_dv6_dd { ip6 saddr timeout ${dynamic_direct_timeout}s }`;
}
return "";
}();
const dynamic_direct_conntrack_tcp = function () {
if (general.dynamic_direct_tcp4 == "1" || general.dynamic_direct_tcp6 == "1") {
return "update @tp_spec_ctt_dd { meta nfproto . tcp sport timeout 30s }";
}
return "";
}();
const dynamic_direct_conntrack_udp = function () {
if (general.dynamic_direct_udp4 == "1" || general.dynamic_direct_udp6 == "1") {
return "update @tp_spec_ctu_dd { meta nfproto . udp sport timeout 30s }";
}
return "";
}();
%}
{% if (length(wan_bp_dns_v4) > 0): %}
set tp_spec_dp4_bp {
typeof ip daddr . udp dport
size {{ length(wan_bp_dns_v4) * 2 + 1 }}
flags interval
elements = {
{{ join(", ", uniq(map(wan_bp_dns_v4, v => `${v.ip} . ${v.port}`))) }}
}
}
{% endif %}
{% if (length(wan_bp_dns_v6) > 0): %}
set tp_spec_dp6_bp {
typeof ip6 daddr . udp dport
size {{ length(wan_bp_dns_v6) * 2 + 1 }}
flags interval
elements = {
{{ join(", ", uniq(map(wan_bp_dns_v6, v => `${v.ip} . ${v.port}`))) }}
}
}
{% endif %}
set tp_spec_dv4_sp {
type ipv4_addr
size 32
flags interval
elements = { 0.0.0.0/8, 10.0.0.0/8,
100.64.0.0/10, 127.0.0.0/8,
169.254.0.0/16, 172.16.0.0/12,
192.0.0.0/24, 192.52.193.0/24,
192.168.0.0/16, 224.0.0.0/3 }
}
set tp_spec_dv6_sp {
type ipv6_addr
size 32
flags interval
elements = { ::,
::1,
::ffff:0.0.0.0/96,
::ffff:0:0:0/96,
64:ff9b::/96,
100::/64,
2001::/32,
2001:20::/28,
2001:db8::/32,
2002::/16,
fc00::/7,
fe80::/10,
ff00::/8 }
}
{% if (length(tp_spec_sm4_bp) > 0): %}
set tp_spec_sm4_bp {
type ether_addr
size {{ length(tp_spec_sm4_bp) * 2 + 1 }}
elements = { {{ join(", ", tp_spec_sm4_bp) }} }
}
{% endif %}
{% if (length(tp_spec_sm6_bp) > 0): %}
set tp_spec_sm6_bp {
type ether_addr
size {{ length(tp_spec_sm6_bp) * 2 + 1 }}
elements = { {{ join(", ", tp_spec_sm6_bp) }} }
}
{% endif %}
{% if (length(tp_spec_sm4_tp) > 0): %}
set tp_spec_sm4_tp {
type ether_addr
size {{ length(tp_spec_sm4_tp) * 2 + 1 }}
elements = { {{ join(", ", tp_spec_sm4_tp) }} }
}
{% endif %}
{% if (length(tp_spec_sm6_tp) > 0): %}
set tp_spec_sm6_tp {
type ether_addr
size {{ length(tp_spec_sm6_tp) * 2 + 1 }}
elements = { {{ join(", ", tp_spec_sm6_tp) }} }
}
{% endif %}
{% for (let i in extra_inbound_tcp_v4_map): %}
set tp_spec_sm4_ft_{{ i }} {
type ether_addr
size {{ length(extra_inbound_tcp_v4_map) * 2 + 1 }}
elements = { {{ join(", ", extra_inbound_tcp_v4_map[i]) }} }
}
{% endfor %}
{% for (let i in extra_inbound_udp_v4_map): %}
set tp_spec_sm4_fu_{{ i }} {
type ether_addr
size {{ length(extra_inbound_udp_v4_map) * 2 + 1 }}
elements = { {{ join(", ", extra_inbound_udp_v4_map[i]) }} }
}
{% endfor %}
{% for (let i in extra_inbound_tcp_v6_map): %}
set tp_spec_sm6_ft_{{ i }} {
type ether_addr
size {{ length(extra_inbound_tcp_v6_map) * 2 + 1 }}
elements = { {{ join(", ", extra_inbound_tcp_v6_map[i]) }} }
}
{% endfor %}
{% for (let i in extra_inbound_udp_v6_map): %}
set tp_spec_sm6_fu_{{ i }} {
type ether_addr
size {{ length(extra_inbound_udp_v6_map) * 2 + 1 }}
elements = { {{ join(", ", extra_inbound_udp_v6_map[i]) }} }
}
{% endfor %}
{% if (length(manual_tproxy_source_ips) > 0): %}
set tp_spec_dv4_mt {
type ipv4_addr
size {{ length(manual_tproxy_source_ips) * 2 + 1 }}
elements = { {{ join(", ", manual_tproxy_source_ips) }} }
}
{% endif %}
{% if (length(wan_bp_ips_v4) > 0): %}
set tp_spec_dv4_bp {
type ipv4_addr
size {{ length(wan_bp_ips_v4) * 2 + 1 }}
flags interval
elements = { {{ join(", ", wan_bp_ips_v4)}} }
}
{% endif %}
{% if (length(wan_bp_ips_v6) > 0): %}
set tp_spec_dv6_bp {
type ipv6_addr
size {{ length(wan_bp_ips_v6) * 2 + 1 }}
flags interval
elements = { {{ join(", ", wan_bp_ips_v6)}} }
}
{% endif %}
{% if (length(wan_fw_ips_v4) > 0): %}
set tp_spec_dv4_fw {
type ipv4_addr
size {{ length(wan_fw_ips_v4) * 2 + 1 }}
flags interval
elements = { {{ join(", ", wan_fw_ips_v4)}} }
}
{% endif %}
{% if (length(wan_fw_ips_v6) > 0): %}
set tp_spec_dv6_fw {
type ipv6_addr
size {{ length(wan_fw_ips_v6) * 2 + 1 }}
flags interval
elements = { {{ join(", ", wan_fw_ips_v6)}} }
}
{% endif %}
{% if (ignore_tp_spec_def_gw == null): %}
set tp_spec_dv4_dg {
type ipv4_addr
size 16
flags interval
}
{% endif %}
set tp_spec_dv6_dg {
type ipv6_addr
size 16
flags interval
}
{% if (general.dynamic_direct_tcp4 == "1" || general.dynamic_direct_udp4 == "1"): %}
set tp_spec_dv4_dd {
type ipv4_addr
size 65536
flags timeout
}
{% endif %}
{% if (general.dynamic_direct_tcp6 == "1" || general.dynamic_direct_udp6 == "1"): %}
set tp_spec_dv6_dd {
type ipv6_addr
size 65536
flags timeout
}
{% endif %}
{% if (dynamic_direct_conntrack_tcp != ""): %}
set tp_spec_ctt_dd {
typeof meta nfproto . tcp sport
size 65536
flags timeout
}
{% endif %}
{% if (dynamic_direct_conntrack_udp != ""): %}
set tp_spec_ctu_dd {
typeof meta nfproto . udp sport
size 65536
flags timeout
}
{% endif %}
chain xray_transparent_proxy {
type filter hook prerouting priority filter {{ firewall_priority }}; policy accept;
mark 0x000000fb {{ counter }} goto tp_spec_wan_fw
ip protocol tcp {{ counter }} accept
ip protocol udp {{ counter }} accept
ip6 nexthdr tcp {{ counter }} accept
ip6 nexthdr udp {{ counter }} accept
{{ counter }} accept
}
chain tp_spec_wan_fw {
{% if (length(fakedns) > 0): %}
ip protocol tcp ip daddr {{ general.pool_v4 || "198.18.0.0/15" }} {{ counter }} tproxy ip to :{{ general.tproxy_port_tcp_f4 || 1086 }} accept
ip protocol udp ip daddr {{ general.pool_v4 || "198.18.0.0/15" }} {{ counter }} tproxy ip to :{{ general.tproxy_port_udp_f4 || 1088 }} accept
ip6 nexthdr tcp ip6 daddr {{ general.pool_v6 || "fc00::/18" }} {{ counter }} tproxy ip6 to :{{ general.tproxy_port_tcp_f6 || 1087 }} accept
ip6 nexthdr udp ip6 daddr {{ general.pool_v6 || "fc00::/18" }} {{ counter }} tproxy ip6 to :{{ general.tproxy_port_udp_f6 || 1089 }} accept
{% endif %}
{% if (length(manual_tproxy_source_ips) > 0): %}
ip protocol tcp ip daddr @tp_spec_dv4_mt {{ counter }} tproxy ip to :{{ general.tproxy_port_tcp_v4 || 1082 }} accept
ip protocol udp ip daddr @tp_spec_dv4_mt {{ counter }} tproxy ip to :{{ general.tproxy_port_udp_v4 || 1084 }} accept
{% endif %}
{% for (let i in extra_inbound_tcp_v4_map): %}
ip protocol tcp ether saddr @tp_spec_sm4_ft_{{ i }} {{ counter }} tproxy ip to :{{ config[i].inbound_port }} accept
{% endfor %}
{% for (let i in extra_inbound_udp_v4_map): %}
ip protocol udp ether saddr @tp_spec_sm4_fu_{{ i }} {{ counter }} tproxy ip to :{{ config[i].inbound_port }} accept
{% endfor %}
{% for (let i in extra_inbound_tcp_v6_map): %}
ip6 nexthdr tcp ether saddr @tp_spec_sm6_ft_{{ i }} {{ counter }} tproxy ip6 to :{{ config[i].inbound_port }} accept
{% endfor %}
{% for (let i in extra_inbound_udp_v6_map): %}
ip6 nexthdr udp ether saddr @tp_spec_sm6_fu_{{ i }} {{ counter }} tproxy ip6 to :{{ config[i].inbound_port }} accept
{% endfor %}
{% if (tcp4_enabled): %}
ip protocol tcp {{ counter }} tproxy ip to :{{ general.tproxy_port_tcp_v4 || 1082 }} accept
{% else %}
ip protocol tcp {{ counter }} meta mark set {{ sprintf("0x%08x", general_mark) }} accept
{% endif %}
{% if (udp4_enabled): %}
ip protocol udp {{ counter }} tproxy ip to :{{ general.tproxy_port_udp_v4 || 1084 }} accept
{% else %}
ip protocol udp {{ counter }} meta mark set {{ sprintf("0x%08x", general_mark) }} accept
{% endif %}
{% if (tcp6_enabled): %}
ip6 nexthdr tcp {{ counter }} tproxy ip6 to :{{ general.tproxy_port_tcp_v6 || 1083 }} accept
{% else %}
ip6 nexthdr tcp {{ counter }} meta mark set {{ sprintf("0x%08x", general_mark) }} accept
{% endif %}
{% if (udp6_enabled): %}
ip6 nexthdr udp {{ counter }} tproxy ip6 to :{{ general.tproxy_port_udp_v6 || 1085 }} accept
{% else %}
ip6 nexthdr udp {{ counter }} meta mark set {{ sprintf("0x%08x", general_mark) }} accept
{% endif %}
{{ counter }} accept
}
chain xray_prerouting {
type filter hook prerouting priority mangle {{ firewall_priority }}; policy accept;
{% if (length(general.ttl_override_bypass_ports) > 0): %}
tcp dport { {{ join(', ', general.ttl_override_bypass_ports) }} } {{ counter }} meta l4proto tcp accept
udp dport { {{ join(', ', general.ttl_override_bypass_ports) }} } {{ counter }} meta l4proto udp accept
{% endif %}
{% if (ttl_override > 0): %}
ip ttl {{ ttl_hop_limit_match }} {{ counter }} ip ttl set {{ ttl_override }}
{% endif %}
{% if (hop_limit_override > 0): %}
ip6 hoplimit {{ ttl_hop_limit_match }} {{ counter }} ip6 hoplimit set {{ hop_limit_override }}
{% endif %}
{% if (general.dynamic_direct_tcp4 == "1"): %}
ip protocol tcp ct mark 0x000000fa {{ counter }} {{ dynamic_direct_tcp4 }} accept comment "Xray dynamic direct TCP4"
{% endif %}
{% if (general.dynamic_direct_udp4 == "1"): %}
ip protocol udp ct mark 0x000000fa {{ counter }} {{ dynamic_direct_udp4 }} accept comment "Xray dynamic direct UDP4"
{% endif %}
{% if (general.dynamic_direct_tcp6 == "1"): %}
ip6 nexthdr tcp ct mark 0x000000fa {{ counter }} {{ dynamic_direct_tcp6 }} accept comment "Xray dynamic direct TCP6"
{% endif %}
{% if (general.dynamic_direct_udp6 == "1"): %}
ip6 nexthdr udp ct mark 0x000000fa {{ counter }} {{ dynamic_direct_udp6 }} accept comment "Xray dynamic direct UDP6"
{% endif %}
mark 0x000000fb {{ counter }} accept comment "Xray remarked from output"
{{ counter }} jump tp_spec_lan_mf comment "Xray FakeDNS / manual transparent proxy"
{% if (length(general.bypass_ifaces_v4 || []) > 0): %}
ip protocol tcp iifname { "{{ join('", "', general.bypass_ifaces_v4) }}" } {{ counter }} accept
ip protocol udp iifname { "{{ join('", "', general.bypass_ifaces_v4) }}" } {{ counter }} accept
{% endif %}
{% if (length(general.bypass_ifaces_v6 || []) > 0): %}
ip6 nexthdr tcp iifname { "{{ join('", "', general.bypass_ifaces_v6) }}" } {{ counter }} accept
ip6 nexthdr udp iifname { "{{ join('", "', general.bypass_ifaces_v6) }}" } {{ counter }} accept
{% endif %}
{% if (length(tp_spec_sm4_bp) > 0): %}
ip protocol tcp ether saddr @tp_spec_sm4_bp {{ counter }} accept
ip protocol udp ether saddr @tp_spec_sm4_bp {{ counter }} accept
{% endif %}
{% if (length(tp_spec_sm6_bp) > 0): %}
ip6 nexthdr tcp ether saddr @tp_spec_sm6_bp {{ counter }} accept
ip6 nexthdr udp ether saddr @tp_spec_sm6_bp {{ counter }} accept
{% endif %}
{% for (let i in extra_inbound_tcp_v4_map): %}
ip protocol tcp ether saddr @tp_spec_sm4_ft_{{ i }} {{ counter }} goto tp_spec_lan_ac
{% endfor %}
{% for (let i in extra_inbound_udp_v4_map): %}
ip protocol udp ether saddr @tp_spec_sm4_fu_{{ i }} {{ counter }} goto tp_spec_lan_ac
{% endfor %}
{% for (let i in extra_inbound_tcp_v6_map): %}
ip6 nexthdr tcp ether saddr @tp_spec_sm6_ft_{{ i }} {{ counter }} goto tp_spec_lan_ac
{% endfor %}
{% for (let i in extra_inbound_udp_v6_map): %}
ip6 nexthdr udp ether saddr @tp_spec_sm6_fu_{{ i }} {{ counter }} goto tp_spec_lan_ac
{% endfor %}
{% if (length(tp_spec_sm4_tp) > 0): %}
ip protocol tcp ether saddr @tp_spec_sm4_tp {{ counter }} goto tp_spec_lan_ac
ip protocol udp ether saddr @tp_spec_sm4_tp {{ counter }} goto tp_spec_lan_ac
{% endif %}
{% if (length(tp_spec_sm6_tp) > 0): %}
ip6 nexthdr tcp ether saddr @tp_spec_sm6_tp {{ counter }} goto tp_spec_lan_ac
ip6 nexthdr udp ether saddr @tp_spec_sm6_tp {{ counter }} goto tp_spec_lan_ac
{% endif %}
{% if (length(general.tproxy_ifaces_v4 || []) > 0): %}
ip protocol tcp iifname { "{{ join('", "', general.tproxy_ifaces_v4) }}" } {{ counter }} goto tp_spec_lan_ac
ip protocol udp iifname { "{{ join('", "', general.tproxy_ifaces_v4) }}" } {{ counter }} goto tp_spec_lan_ac
{% endif %}
{% if (length(general.tproxy_ifaces_v6 || []) > 0): %}
ip6 nexthdr tcp iifname { "{{ join('", "', general.tproxy_ifaces_v6) }}" } {{ counter }} goto tp_spec_lan_ac
ip6 nexthdr udp iifname { "{{ join('", "', general.tproxy_ifaces_v6) }}" } {{ counter }} goto tp_spec_lan_ac
{% endif %}
ip protocol tcp {{ counter }} accept
ip protocol udp {{ counter }} accept
ip6 nexthdr tcp {{ counter }} accept
ip6 nexthdr udp {{ counter }} accept
{{ counter }} accept
}
chain xray_output {
type route hook output priority mangle {{ firewall_priority }}; policy accept;
{% if (length(uids_direct) > 0): %}
meta skuid { {{ join(", ", uids_direct) }} } {{ counter }} accept
{% endif %}
{% if (length(gids_direct) > 0): %}
meta skgid { {{ join(", ", gids_direct) }} } {{ counter }} accept
{% endif %}
ip protocol tcp {{ counter }} goto tp_spec_wan_ac
ip protocol udp {{ counter }} goto tp_spec_wan_ac
ip6 nexthdr tcp {{ counter }} goto tp_spec_wan_ac
ip6 nexthdr udp {{ counter }} goto tp_spec_wan_ac
{{ counter }} accept
}
chain tp_spec_wan_ac {
ip protocol tcp mark 0x000000fc {{ counter }} {{ dynamic_direct_tcp4 }} accept comment "Xray direct outbound TCP4"
ip protocol udp mark 0x000000fc {{ counter }} {{ dynamic_direct_udp4 }} accept comment "Xray direct outbound UDP4"
ip6 nexthdr tcp mark 0x000000fc {{ counter }} {{ dynamic_direct_tcp6 }} accept comment "Xray direct outbound TCP6"
ip6 nexthdr udp mark 0x000000fc {{ counter }} {{ dynamic_direct_udp6 }} accept comment "Xray direct outbound UDP6"
meta mark 0x000000fd {{ counter }} accept comment "Xray transparent proxy outbound"
meta mark 0x000000fe {{ counter }} accept comment "Xray non-IP DNS query outbound"
meta mark {{ sprintf("0x%08x", general_mark) }} {{ counter }} accept comment "Xray specified mark {{ general_mark }} outbound"
{{ counter }} jump tp_spec_lan_mf
{% if (!tcp4_enabled): %}
ip protocol tcp {{ counter }} accept
{% endif %}
{% if (!udp4_enabled): %}
ip protocol udp {{ counter }} accept
{% endif %}
{% if (!tcp6_enabled): %}
ip6 nexthdr tcp {{ counter }} accept
{% endif %}
{% if (!udp6_enabled): %}
ip6 nexthdr udp {{ counter }} accept
{% endif %}
{{ counter }} goto tp_spec_lan_ac
}
chain tp_spec_lan_mf {
{% if (length(fakedns) > 0): %}
ip protocol tcp ip daddr {{ general.pool_v4 || "198.18.0.0/15" }} {{ counter }} goto tp_spec_lan_fw comment "Xray FakeDNS IPv4 Pool TCP"
ip protocol udp ip daddr {{ general.pool_v4 || "198.18.0.0/15" }} {{ counter }} goto tp_spec_lan_fw comment "Xray FakeDNS IPv4 Pool UDP"
ip6 nexthdr tcp ip6 daddr {{ general.pool_v6 || "fc00::/18" }} {{ counter }} goto tp_spec_lan_fw comment "Xray FakeDNS IPv6 Pool TCP"
ip6 nexthdr udp ip6 daddr {{ general.pool_v6 || "fc00::/18" }} {{ counter }} goto tp_spec_lan_fw comment "Xray FakeDNS IPv6 Pool UDP"
{% endif %}
{% if (length(manual_tproxy_source_ips) > 0): %}
ip protocol tcp ip daddr @tp_spec_dv4_mt {{ counter }} goto tp_spec_lan_fw comment "Xray manual transparent proxy TCP"
ip protocol udp ip daddr @tp_spec_dv4_mt {{ counter }} goto tp_spec_lan_fw comment "Xray manual transparent proxy UDP"
{% endif %}
{% if (length(wan_bp_dns_v4) > 0): %}
ip daddr . tcp dport @tp_spec_dp4_bp {{ counter }} accept
ip daddr . udp dport @tp_spec_dp4_bp {{ counter }} accept
{% endif %}
{% if (length(wan_bp_dns_v6) > 0): %}
ip6 daddr . tcp dport @tp_spec_dp6_bp {{ counter }} accept
ip6 daddr . udp dport @tp_spec_dp6_bp {{ counter }} accept
{% endif %}
{{ counter }} return
}
chain tp_spec_lan_ac {
{% if (length(wan_fw_ips_v4) > 0): %}
ip daddr @tp_spec_dv4_fw {{ counter }} goto tp_spec_lan_fw
{% endif %}
{% if (length(wan_fw_ips_v6) > 0): %}
ip6 daddr @tp_spec_dv6_fw {{ counter }} goto tp_spec_lan_fw
{% endif %}
{% if (ignore_tp_spec_def_gw == null): %}
ip daddr @tp_spec_dv4_dg {{ counter }} accept
{% endif %}
ip6 daddr @tp_spec_dv6_dg {{ counter }} accept
{% if (length(wan_bp_ips_v4) > 0): %}
ip daddr @tp_spec_dv4_bp {{ counter }} accept
{% endif %}
{% if (length(wan_bp_ips_v6) > 0): %}
ip6 daddr @tp_spec_dv6_bp {{ counter }} accept
{% endif %}
ip daddr @tp_spec_dv4_sp {{ counter }} accept
ip6 daddr @tp_spec_dv6_sp {{ counter }} accept
{{ counter }} goto tp_spec_lan_re
}
chain tp_spec_lan_re {
{% if (transparent_default_port_policy == "bypassed"): %}
{% if (length(wan_fw_tcp_ports) > 0): %}
tcp dport { {{ join(", ", wan_fw_tcp_ports) }} } {{ counter }} meta l4proto tcp goto tp_spec_lan_fw
{% endif %}
{% if (length(wan_fw_udp_ports) > 0): %}
udp dport { {{ join(", ", wan_fw_udp_ports) }} } {{ counter }} meta l4proto udp goto tp_spec_lan_fw
{% endif %}
{% else %}
{% if (length(wan_bp_tcp_ports) > 0): %}
tcp dport { {{ join(", ", wan_bp_tcp_ports) }} } {{ counter }} meta l4proto tcp accept
{% endif %}
{% if (length(wan_bp_udp_ports) > 0): %}
udp dport { {{ join(", ", wan_bp_udp_ports) }} } {{ counter }} meta l4proto udp accept
{% endif %}
meta l4proto tcp {{ counter }} goto tp_spec_lan_dd
meta l4proto udp {{ counter }} goto tp_spec_lan_dd
{% endif %}
{{ counter }} accept
}
chain tp_spec_lan_dd {
{% if (general.dynamic_direct_tcp4 == "1"): %}
ip protocol tcp ip daddr @tp_spec_dv4_dd {{ counter }} goto tp_spec_lan_ct comment "Xray dynamic direct TCP4"
{% endif %}
{% if (general.dynamic_direct_udp4 == "1"): %}
ip protocol udp ip daddr @tp_spec_dv4_dd {{ counter }} goto tp_spec_lan_ct comment "Xray dynamic direct UDP4"
{% endif %}
{% if (general.dynamic_direct_tcp6 == "1"): %}
ip6 nexthdr tcp ip6 daddr @tp_spec_dv6_dd {{ counter }} goto tp_spec_lan_ct comment "Xray dynamic direct TCP6"
{% endif %}
{% if (general.dynamic_direct_udp6 == "1"): %}
ip6 nexthdr udp ip6 daddr @tp_spec_dv6_dd {{ counter }} goto tp_spec_lan_ct comment "Xray dynamic direct UDP6"
{% endif %}
meta l4proto tcp {{ counter }} {{ dynamic_direct_conntrack_tcp }} goto tp_spec_lan_fw
meta l4proto udp {{ counter }} {{ dynamic_direct_conntrack_udp }} goto tp_spec_lan_fw
{{ counter }} accept
}
{% if (dynamic_direct_conntrack_tcp != "" || dynamic_direct_conntrack_udp != ""): %}
chain tp_spec_lan_ct {
{% if (dynamic_direct_conntrack_tcp != ""): %}
meta nfproto . tcp sport @tp_spec_ctt_dd {{ counter }} meta l4proto tcp goto tp_spec_lan_fw
{% endif %}
{% if (dynamic_direct_conntrack_udp != ""): %}
meta nfproto . udp sport @tp_spec_ctu_dd {{ counter }} meta l4proto udp goto tp_spec_lan_fw
{% endif %}
{{ counter }} ct mark set 0x000000fa accept comment "Xray dynamic direct successful registration"
}
{% endif %}
chain tp_spec_lan_fw {
{{ counter }} mark set 0x000000fb accept
}
@@ -0,0 +1,288 @@
#!/usr/bin/ucode
"use strict";
import { access } from "fs";
import { load_config } from "./common/config.mjs";
import { bridge_outbounds, bridge_rules, bridges } from "./feature/bridge.mjs";
import { blocked_domain_rules, dns_conf, dns_rules, dns_server_inbounds, dns_server_outbounds, fast_domain_rules, secure_domain_rules } from "./feature/dns.mjs";
import { fake_dns_balancers, fake_dns_conf, fake_dns_rules } from "./feature/fake_dns.mjs";
import { dokodemo_inbound, extra_inbound_balancers, extra_inbound_global, extra_inbound_rules, extra_inbounds, http_inbound, https_inbound, socks_inbound } from "./feature/inbound.mjs";
import { manual_tproxy_outbound_tags, manual_tproxy_outbounds, manual_tproxy_rules } from "./feature/manual_tproxy.mjs";
import { blackhole_outbound, direct_outbound, server_outbound } from "./feature/outbound.mjs";
import { api_conf, balancer, logging, metrics_conf, policy, system_route_rules } from "./feature/system.mjs";
function inbounds(proxy, config, extra_inbound) {
const tproxy_sniffing = proxy["tproxy_sniffing"];
const route_only = proxy["route_only"];
const conn_idle = proxy["conn_idle"];
let i = [
socks_inbound("0.0.0.0", proxy["socks_port"] || 1080, "socks_inbound"),
http_inbound("0.0.0.0", proxy["http_port"] || 1081, "http_inbound"),
dokodemo_inbound("0.0.0.0", proxy["tproxy_port_tcp_v4"] || 1082, "tproxy_tcp_inbound_v4", tproxy_sniffing, route_only, ["http", "tls"], "0", "tcp", "tproxy", conn_idle),
dokodemo_inbound("0.0.0.0", proxy["tproxy_port_tcp_v6"] || 1083, "tproxy_tcp_inbound_v6", tproxy_sniffing, route_only, ["http", "tls"], "0", "tcp", "tproxy", conn_idle),
dokodemo_inbound("0.0.0.0", proxy["tproxy_port_udp_v4"] || 1084, "tproxy_udp_inbound_v4", tproxy_sniffing, route_only, ["quic"], "0", "udp", "tproxy", conn_idle),
dokodemo_inbound("0.0.0.0", proxy["tproxy_port_udp_v6"] || 1085, "tproxy_udp_inbound_v6", tproxy_sniffing, route_only, ["quic"], "0", "udp", "tproxy", conn_idle),
...extra_inbounds(proxy, extra_inbound),
...dns_server_inbounds(proxy),
dokodemo_inbound("0.0.0.0", proxy["tproxy_port_tcp_f4"] || 1086, "tproxy_tcp_inbound_f4", "1", "0", ["fakedns"], "1", "tcp", "tproxy", conn_idle),
dokodemo_inbound("0.0.0.0", proxy["tproxy_port_tcp_f6"] || 1087, "tproxy_tcp_inbound_f6", "1", "0", ["fakedns"], "1", "tcp", "tproxy", conn_idle),
dokodemo_inbound("0.0.0.0", proxy["tproxy_port_udp_f4"] || 1088, "tproxy_udp_inbound_f4", "1", "0", ["fakedns"], "1", "udp", "tproxy", conn_idle),
dokodemo_inbound("0.0.0.0", proxy["tproxy_port_udp_f6"] || 1089, "tproxy_udp_inbound_f6", "1", "0", ["fakedns"], "1", "udp", "tproxy", conn_idle),
];
if (proxy["web_server_enable"] == "1") {
push(i, https_inbound(proxy, config));
}
if (proxy["metrics_server_enable"] == '1') {
push(i, {
listen: "0.0.0.0",
port: int(proxy["metrics_server_port"]) || 18888,
protocol: "dokodemo-door",
settings: {
address: "127.0.0.1"
},
tag: "metrics"
});
}
if (proxy["xray_api"] == '1') {
push(i, {
listen: "127.0.0.1",
port: 8080,
protocol: "dokodemo-door",
settings: {
address: "127.0.0.1"
},
tag: "api"
});
}
return i;
}
function outbounds(proxy, config, manual_tproxy, bridge, extra_inbound, fakedns) {
let result = [
blackhole_outbound(),
direct_outbound("direct", null, false),
direct_outbound("dynamic_direct", null, true),
...dns_server_outbounds(proxy),
...manual_tproxy_outbounds(config, manual_tproxy),
...bridge_outbounds(config, bridge)
];
let outbound_balancers_all = {};
for (let b in ["tcp_balancer_v4", "udp_balancer_v4", "tcp_balancer_v6", "udp_balancer_v6"]) {
for (let i in balancer(proxy, b, b)) {
if (i != "direct") {
outbound_balancers_all[i] = true;
}
}
}
for (let e in extra_inbound) {
if (e["specify_outbound"] == "1") {
for (let i in balancer(e, "destination", `extra_inbound:${e[".name"]}`)) {
if (i != "direct") {
outbound_balancers_all[i] = true;
}
}
}
}
for (let f in fakedns) {
for (let i in balancer(f, "fake_dns_forward_server_tcp", `fake_dns_tcp:${f[".name"]}`)) {
if (i != "direct") {
outbound_balancers_all[i] = true;
}
}
for (let i in balancer(f, "fake_dns_forward_server_udp", `fake_dns_udp:${f[".name"]}`)) {
if (i != "direct") {
outbound_balancers_all[i] = true;
}
}
}
for (let i in keys(outbound_balancers_all)) {
push(result, ...server_outbound(config[substr(i, -9)], i, config));
}
return result;
}
function rules(proxy, bridge, manual_tproxy, extra_inbound, fakedns) {
const geoip_existence = access("/usr/share/xray/geoip.dat") || false;
const tproxy_tcp_inbound_v4_tags = ["tproxy_tcp_inbound_v4"];
const tproxy_udp_inbound_v4_tags = ["tproxy_udp_inbound_v4"];
const tproxy_tcp_inbound_v6_tags = ["tproxy_tcp_inbound_v6"];
const tproxy_udp_inbound_v6_tags = ["tproxy_udp_inbound_v6"];
const extra_inbound_global_tags = extra_inbound_global();
const extra_inbound_global_tcp_tags = extra_inbound_global_tags["tproxy_tcp"] || [];
const extra_inbound_global_udp_tags = extra_inbound_global_tags["tproxy_udp"] || [];
const extra_inbound_global_http_tags = extra_inbound_global_tags["http"] || [];
const extra_inbound_global_socks5_tags = extra_inbound_global_tags["socks5"] || [];
const built_in_tcp_inbounds = [...tproxy_tcp_inbound_v4_tags, ...extra_inbound_global_tcp_tags, ...extra_inbound_global_http_tags, ...extra_inbound_global_socks5_tags, "socks_inbound", "https_inbound", "http_inbound"];
const built_in_udp_inbounds = [...tproxy_udp_inbound_v4_tags, ...extra_inbound_global_udp_tags, "dns_conf_inbound"];
let result = [
...fake_dns_rules(fakedns),
...manual_tproxy_rules(manual_tproxy),
...extra_inbound_rules(extra_inbound),
...system_route_rules(proxy),
...bridge_rules(bridge),
...dns_rules(proxy, [...tproxy_tcp_inbound_v6_tags, ...tproxy_tcp_inbound_v4_tags, ...extra_inbound_global_tcp_tags], [...tproxy_udp_inbound_v6_tags, ...tproxy_udp_inbound_v4_tags, ...extra_inbound_global_udp_tags]),
...function () {
let direct_rules = [];
if (geoip_existence) {
const geoip_direct_code_list = map(proxy["geoip_direct_code_list"] || [], v => index(v, ":") > 0 ? v : `geoip:${v}`);
if (length(geoip_direct_code_list) > 0) {
push(direct_rules, {
type: "field",
inboundTag: [...built_in_tcp_inbounds, ...built_in_udp_inbounds],
outboundTag: "dynamic_direct",
ip: geoip_direct_code_list
});
}
const geoip_direct_code_list_v6 = map(proxy["geoip_direct_code_list_v6"] || [], v => index(v, ":") > 0 ? v : `geoip:${v}`);
if (length(geoip_direct_code_list_v6) > 0) {
push(direct_rules, {
type: "field",
inboundTag: [...tproxy_tcp_inbound_v6_tags, ...tproxy_udp_inbound_v6_tags],
outboundTag: "dynamic_direct",
ip: geoip_direct_code_list_v6
});
}
push(direct_rules, {
type: "field",
inboundTag: [...tproxy_tcp_inbound_v6_tags, ...tproxy_udp_inbound_v6_tags, ...built_in_tcp_inbounds, ...built_in_udp_inbounds],
outboundTag: "direct",
ip: ["geoip:private"]
});
}
return direct_rules;
}(),
{
type: "field",
inboundTag: tproxy_tcp_inbound_v6_tags,
balancerTag: "tcp_outbound_v6"
},
{
type: "field",
inboundTag: tproxy_udp_inbound_v6_tags,
balancerTag: "udp_outbound_v6"
},
{
type: "field",
inboundTag: built_in_tcp_inbounds,
balancerTag: "tcp_outbound_v4"
},
{
type: "field",
inboundTag: built_in_udp_inbounds,
balancerTag: "udp_outbound_v4"
},
];
if (proxy["tproxy_sniffing"] == "1") {
if (length(secure_domain_rules(proxy)) > 0) {
splice(result, 0, 0, {
type: "field",
inboundTag: [...tproxy_tcp_inbound_v4_tags, ...extra_inbound_global_tcp_tags],
balancerTag: "tcp_outbound_v4",
domain: secure_domain_rules(proxy),
}, {
type: "field",
inboundTag: [...tproxy_udp_inbound_v4_tags, ...extra_inbound_global_udp_tags],
balancerTag: "udp_outbound_v4",
domain: secure_domain_rules(proxy),
}, {
type: "field",
inboundTag: [...tproxy_tcp_inbound_v6_tags],
balancerTag: "tcp_outbound_v6",
domain: secure_domain_rules(proxy),
}, {
type: "field",
inboundTag: [...tproxy_udp_inbound_v6_tags],
balancerTag: "udp_outbound_v6",
domain: secure_domain_rules(proxy),
});
}
if (length(blocked_domain_rules(proxy)) > 0) {
splice(result, 0, 0, {
type: "field",
inboundTag: [...tproxy_tcp_inbound_v4_tags, ...tproxy_udp_inbound_v4_tags, ...tproxy_tcp_inbound_v6_tags, ...tproxy_udp_inbound_v6_tags, ...extra_inbound_global_tcp_tags, ...extra_inbound_global_udp_tags],
outboundTag: "blackhole_outbound",
domain: blocked_domain_rules(proxy),
});
}
splice(result, 0, 0, {
type: "field",
inboundTag: [...tproxy_tcp_inbound_v4_tags, ...tproxy_udp_inbound_v4_tags, ...tproxy_tcp_inbound_v6_tags, ...tproxy_udp_inbound_v6_tags, ...extra_inbound_global_tcp_tags, ...extra_inbound_global_udp_tags],
outboundTag: "direct",
domain: fast_domain_rules(proxy)
});
if (proxy["direct_bittorrent"] == "1") {
splice(result, 0, 0, {
type: "field",
outboundTag: "direct",
protocol: ["bittorrent"]
});
}
}
return result;
}
function balancers(proxy, extra_inbound, fakedns) {
const general_balancer_strategy = proxy["general_balancer_strategy"] || "random";
const built_in_outbounds = ["tcp_outbound_v4", "udp_outbound_v4", "tcp_outbound_v6", "udp_outbound_v6"];
const built_in_balancers = ["tcp_balancer_v4", "udp_balancer_v4", "tcp_balancer_v6", "udp_balancer_v6"];
return [
...map(built_in_balancers, function (balancer_tag, index) {
return {
"tag": built_in_outbounds[index],
"selector": balancer(proxy, balancer_tag, balancer_tag),
"strategy": {
"type": general_balancer_strategy
}
};
}),
...extra_inbound_balancers(extra_inbound),
...fake_dns_balancers(fakedns),
];
};
function observatory(proxy, manual_tproxy) {
if (proxy["observatory"] == "1") {
return {
subjectSelector: ["tcp_balancer_v4@balancer_outbound", "udp_balancer_v4@balancer_outbound", "tcp_balancer_v6@balancer_outbound", "udp_balancer_v6@balancer_outbound", "extra_inbound", "fake_dns", "direct", ...manual_tproxy_outbound_tags(manual_tproxy)],
probeInterval: "100ms",
probeUrl: "http://www.apple.com/library/test/success.html"
};
}
return null;
}
function gen_config() {
const config = load_config();
const bridge = filter(values(config), v => v[".type"] == "bridge") || [];
const fakedns = filter(values(config), v => v[".type"] == "fakedns") || [];
const extra_inbound = filter(values(config), v => v[".type"] == "extra_inbound") || [];
const manual_tproxy = filter(values(config), v => v[".type"] == "manual_tproxy") || [];
const general = filter(values(config), k => k[".type"] == "general")[0] || {};
const custom_configuration_hook = loadstring(general["custom_configuration_hook"] || "return i => i;")();
return custom_configuration_hook({
inbounds: inbounds(general, config, extra_inbound),
outbounds: outbounds(general, config, manual_tproxy, bridge, extra_inbound, fakedns),
dns: dns_conf(general, config, manual_tproxy, fakedns),
fakedns: fake_dns_conf(general),
api: api_conf(general),
metrics: metrics_conf(general),
policy: policy(general),
log: logging(general),
stats: general["stats"] == "1" ? {
place: "holder"
} : null,
observatory: observatory(general, manual_tproxy),
reverse: {
bridges: bridges(bridge)
},
routing: {
domainStrategy: general["routing_domain_strategy"] || "AsIs",
rules: rules(general, bridge, manual_tproxy, extra_inbound, fakedns),
balancers: balancers(general, extra_inbound, fakedns)
}
});
}
printf("%.4J", gen_config());
@@ -0,0 +1 @@
Ignore TP_SPEC_DEF_GW (nftables only). TP_SPEC_DEF_GW is only useful when you have publicly routable IPv4 address on your router.
@@ -0,0 +1,35 @@
"use strict";
import { port_array, stream_settings } from "../common/stream.mjs";
export function http_outbound(server, tag) {
const stream_settings_object = stream_settings(server, "http", tag);
const stream_settings_result = stream_settings_object["stream_settings"];
const dialer_proxy = stream_settings_object["dialer_proxy"];
let users = null;
if (server["username"] && server["password"]) {
users = [
{
user: server["username"],
pass: server["password"],
}
];
}
return {
outbound: {
protocol: "http",
tag: tag,
settings: {
servers: map(port_array(server["server_port"]), function (v) {
return {
address: server["server"],
port: v,
users: users
};
})
},
streamSettings: stream_settings_result
},
dialer_proxy: dialer_proxy
};
};
@@ -0,0 +1,22 @@
"use strict";
import { port_array, stream_settings } from "../common/stream.mjs";
export function hysteria_outbound(server, tag) {
const stream_settings_object = stream_settings(server, "hysteria", tag);
const stream_settings_result = stream_settings_object["stream_settings"];
const dialer_proxy = stream_settings_object["dialer_proxy"];
return {
outbound: {
protocol: "hysteria",
tag: tag,
settings: {
version: 2,
address: server["server"],
port: port_array(server["server_port"])[0],
},
streamSettings: stream_settings_result
},
dialer_proxy: dialer_proxy
};
};
@@ -0,0 +1,29 @@
"use strict";
import { port_array, stream_settings } from "../common/stream.mjs";
export function shadowsocks_outbound(server, tag) {
const stream_settings_object = stream_settings(server, "shadowsocks", tag);
const stream_settings_result = stream_settings_object["stream_settings"];
const dialer_proxy = stream_settings_object["dialer_proxy"];
return {
outbound: {
protocol: "shadowsocks",
tag: tag,
settings: {
servers: map(port_array(server["server_port"]), function (v) {
return {
address: server["server"],
port: v,
email: server["username"],
password: server["password"],
method: server["shadowsocks_security"],
uot: server["shadowsocks_udp_over_tcp"] == '1'
};
})
},
streamSettings: stream_settings_result
},
dialer_proxy: dialer_proxy
};
};
@@ -0,0 +1,35 @@
"use strict";
import { port_array, stream_settings } from "../common/stream.mjs";
export function socks_outbound(server, tag) {
const stream_settings_object = stream_settings(server, "socks", tag);
const stream_settings_result = stream_settings_object["stream_settings"];
const dialer_proxy = stream_settings_object["dialer_proxy"];
let users = null;
if (server["username"] && server["password"]) {
users = [
{
user: server["username"],
pass: server["password"],
}
];
}
return {
outbound: {
protocol: "socks",
tag: tag,
settings: {
servers: map(port_array(server["server_port"]), function (v) {
return {
address: server["server"],
port: v,
users: users
};
})
},
streamSettings: stream_settings_result
},
dialer_proxy: dialer_proxy
};
};
@@ -0,0 +1,52 @@
"use strict";
import { port_array, stream_settings } from "../common/stream.mjs";
import { fallbacks, tls_inbound_settings } from "../common/tls.mjs";
function trojan_inbound_user(k) {
return {
email: k,
password: k,
};
}
export function trojan_outbound(server, tag) {
const stream_settings_object = stream_settings(server, "trojan", tag);
const stream_settings_result = stream_settings_object["stream_settings"];
const dialer_proxy = stream_settings_object["dialer_proxy"];
return {
outbound: {
protocol: "trojan",
tag: tag,
settings: {
servers: map(port_array(server["server_port"]), function (v) {
return {
address: server["server"],
port: v,
email: server["username"],
password: server["password"]
};
})
},
streamSettings: stream_settings_result
},
dialer_proxy: dialer_proxy
};
};
export function https_trojan_inbound(proxy, config) {
return {
port: proxy["web_server_port"] || 443,
protocol: "trojan",
tag: "https_inbound",
settings: {
clients: map(proxy["web_server_password"], trojan_inbound_user),
fallbacks: fallbacks(proxy, config)
},
streamSettings: {
network: "tcp",
security: proxy["trojan_tls"],
tlsSettings: proxy["trojan_tls"] == "tls" ? tls_inbound_settings(proxy, "trojan") : null
}
};
};
@@ -0,0 +1,79 @@
"use strict";
import { port_array, stream_settings } from "../common/stream.mjs";
import { fallbacks, reality_inbound_settings, tls_inbound_settings } from "../common/tls.mjs";
function vless_inbound_user(k, flow) {
return {
id: k,
email: k,
flow: flow,
};
}
export function vless_outbound(server, tag) {
let flow = null;
if (server["vless_tls"] == "tls") {
flow = server["vless_flow_tls"];
} else if (server["vless_tls"] == "reality") {
flow = server["vless_flow_reality"];
}
if (flow == "none") {
flow = null;
}
const stream_settings_object = stream_settings(server, "vless", tag);
const stream_settings_result = stream_settings_object["stream_settings"];
const dialer_proxy = stream_settings_object["dialer_proxy"];
return {
outbound: {
protocol: "vless",
tag: tag,
settings: {
vnext: map(port_array(server["server_port"]), function (v) {
return {
address: server["server"],
port: v,
users: [
{
email: server["username"],
id: server["password"],
flow: flow,
encryption: server["vless_encryption"] || "none"
}
]
};
})
},
streamSettings: stream_settings_result
},
dialer_proxy: dialer_proxy
};
};
export function https_vless_inbound(proxy, config) {
let flow = null;
if (proxy["vless_tls"] == "tls") {
flow = proxy["vless_flow_tls"];
} else if (proxy["vless_tls"] == "reality") {
flow = proxy["vless_flow_reality"];
}
if (flow == "none") {
flow = null;
}
return {
port: proxy["web_server_port"] || 443,
protocol: "vless",
tag: "https_inbound",
settings: {
clients: map(proxy["web_server_password"], k => vless_inbound_user(k, flow)),
decryption: proxy["vless_decryption"] || "none",
fallbacks: fallbacks(proxy, config)
},
streamSettings: {
network: "tcp",
security: proxy["vless_tls"],
tlsSettings: proxy["vless_tls"] == "tls" ? tls_inbound_settings(proxy, "vless") : null,
realitySettings: proxy["vless_tls"] == "reality" ? reality_inbound_settings(proxy, "vless") : null,
}
};
};
@@ -0,0 +1,33 @@
"use strict";
import { port_array, stream_settings } from "../common/stream.mjs";
export function vmess_outbound(server, tag) {
const stream_settings_object = stream_settings(server, "vmess", tag);
const stream_settings_result = stream_settings_object["stream_settings"];
const dialer_proxy = stream_settings_object["dialer_proxy"];
return {
outbound: {
protocol: "vmess",
tag: tag,
settings: {
vnext: map(port_array(server["server_port"]), function (v) {
return {
address: server["server"],
port: v,
users: [
{
email: server["username"],
id: server["password"],
alterId: int(server["alter_id"]),
security: server["vmess_security"]
}
]
};
})
},
streamSettings: stream_settings_result
},
dialer_proxy: dialer_proxy
};
};
@@ -0,0 +1,2 @@
If you are using dnsmasq v2.87, keep this file.
See https://thekelleys.org.uk/dnsmasq/CHANGELOG for fixes in version 2.88 about the bug introduced in version 2.87, which could result in DNS servers being removed from the configuration when reloading server configuration.
@@ -0,0 +1 @@
300000000 333333333
@@ -0,0 +1 @@
44444444 55555555
@@ -0,0 +1 @@
8192 16384