From b5e43939a3281992a690632e2a58f696f635d17f Mon Sep 17 00:00:00 2001 From: yichya QC Date: Mon, 19 Feb 2024 10:31:36 +0800 Subject: [PATCH] fix: domain match priority; stricter resolve options; socks / http auth --- README.md | 1 + core/root/usr/share/xray/feature/dns.mjs | 77 +++++++++++++++---- core/root/usr/share/xray/firewall_include.ut | 4 + core/root/usr/share/xray/protocol/http.mjs | 16 ++-- core/root/usr/share/xray/protocol/socks.mjs | 16 ++-- .../luci-static/resources/view/xray/core.js | 54 ++++++++----- 6 files changed, 119 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 1456259d..2ea2ccf7 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ Fork this repository and: * 2024-02-18 chore: optimize code style; bump version * 2024-02-19 fix: several DNS related validation +* 2024-02-20 fix: domain match priority; stricter resolve options; socks / http auth ## Changelog since 3.3.0 diff --git a/core/root/usr/share/xray/feature/dns.mjs b/core/root/usr/share/xray/feature/dns.mjs index 9f6bb408..7375a0a1 100644 --- a/core/root/usr/share/xray/feature/dns.mjs +++ b/core/root/usr/share/xray/feature/dns.mjs @@ -9,18 +9,42 @@ const fallback_secure_dns = "8.8.8.8:53"; const fallback_default_dns = "1.1.1.1:53"; const geosite_existence = access("/usr/share/xray/geosite.dat") || false; -function split_ipv4_host_port(val, port_default) { - const result = match(val, /^([0-9\.]+):([0-9]+)$/); - if (result == null) { +function parse_ip_port(val, port_default) { + const split_dot = split(val, "."); + if (length(split_dot) > 1) { + const split_ipv4 = split(val, ":"); return { - address: val, - port: int(port_default) + 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: result[1], - port: int(result[2]) + address: `${method}://${val}${url_suffix}` }; } @@ -52,7 +76,7 @@ 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 = split_ipv4_host_port(proxy["default_dns"] || fallback_default_dns, 53); + 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, @@ -126,8 +150,8 @@ export function dns_server_outbounds(proxy) { }; export function dns_conf(proxy, config, manual_tproxy, fakedns) { - const fast_dns_object = split_ipv4_host_port(proxy["fast_dns"] || fallback_fast_dns, 53); - const default_dns_object = split_ipv4_host_port(proxy["default_dns"] || fallback_default_dns, 53); + 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 = {}; @@ -137,7 +161,7 @@ export function dns_conf(proxy, config, manual_tproxy, fakedns) { continue; } if (server["domain_resolve_dns"]) { - domain_extra_options[server["server"]] = server["domain_resolve_dns"]; + domain_extra_options[server["server"]] = `${server["domain_resolve_dns_method"] || "udp"};${server["domain_resolve_dns"]}`; } else { domain_names_set[`domain:${server["server"]}`] = true; } @@ -147,17 +171,21 @@ export function dns_conf(proxy, config, manual_tproxy, fakedns) { for (let k in keys(domain_extra_options)) { const v = domain_extra_options[k]; let original = resolve_merged[v] || []; - push(original, k); + push(original, `domain:${k}`); resolve_merged[v] = original; } let servers = [ ...fake_dns_domains(fakedns), ...map(keys(resolve_merged), function (k) { - let i = split_ipv4_host_port(k); - i["domains"] = uniq(resolve_merged[k]); - i["skipFallback"] = true; - return i; + const dns_split = split(k, ";"); + const resolve_dns_object = format_dns(dns_split[0], dns_split[1]); + return { + address: resolve_dns_object["address"], + port: resolve_dns_object["port"], + domains: uniq(resolve_merged[k]), + skipFallback: true, + }; }), default_dns_object, { @@ -169,7 +197,7 @@ export function dns_conf(proxy, config, manual_tproxy, fakedns) { ]; if (length(secure_domain_rules(proxy)) > 0) { - const secure_dns_object = split_ipv4_host_port(proxy["secure_dns"] || fallback_secure_dns, 53); + 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"], @@ -199,3 +227,18 @@ export function dns_conf(proxy, config, manual_tproxy, fakedns) { 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"])["ip"]); + } + } + } + return result; +}; diff --git a/core/root/usr/share/xray/firewall_include.ut b/core/root/usr/share/xray/firewall_include.ut index 8361a540..a03d4057 100644 --- a/core/root/usr/share/xray/firewall_include.ut +++ b/core/root/usr/share/xray/firewall_include.ut @@ -3,6 +3,7 @@ "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]]; @@ -16,6 +17,9 @@ let wan_bp_ips_no_dns = general.wan_bp_ips || []; let wan_fw_ips_no_dns = general.wan_fw_ips || []; push(wan_bp_ips_no_dns, split(general.fast_dns || "223.5.5.5:53", ":")[0]); + for (let i in dns_direct_servers(config)) { + push(wan_bp_ips_no_dns, i); + } push(wan_fw_ips_no_dns, split(general.secure_dns || "8.8.8.8:53", ":")[0]); const wan_bp_ips_v4 = filter(uniq(wan_bp_ips_no_dns), v => index(v, ":") == -1); const wan_bp_ips_v6 = filter(uniq(wan_bp_ips_no_dns), v => index(v, ":") != -1); diff --git a/core/root/usr/share/xray/protocol/http.mjs b/core/root/usr/share/xray/protocol/http.mjs index 8c80c65c..b340243e 100644 --- a/core/root/usr/share/xray/protocol/http.mjs +++ b/core/root/usr/share/xray/protocol/http.mjs @@ -6,6 +6,15 @@ 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", @@ -15,12 +24,7 @@ export function http_outbound(server, tag) { { address: server["server"], port: int(server["server_port"]), - users: [ - { - user: server["username"], - pass: server["password"], - } - ] + users: users } ] }, diff --git a/core/root/usr/share/xray/protocol/socks.mjs b/core/root/usr/share/xray/protocol/socks.mjs index 95c8619c..4d11a696 100644 --- a/core/root/usr/share/xray/protocol/socks.mjs +++ b/core/root/usr/share/xray/protocol/socks.mjs @@ -6,6 +6,15 @@ 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", @@ -15,12 +24,7 @@ export function socks_outbound(server, tag) { { address: server["server"], port: int(server["server_port"]), - users: [ - { - user: server["username"], - pass: server["password"], - } - ] + users: users } ] }, diff --git a/core/root/www/luci-static/resources/view/xray/core.js b/core/root/www/luci-static/resources/view/xray/core.js index 9b0b408b..59ecde18 100644 --- a/core/root/www/luci-static/resources/view/xray/core.js +++ b/core/root/www/luci-static/resources/view/xray/core.js @@ -42,14 +42,15 @@ function list_folded_format(config_data, k, noun, max_chars, mapping, empty) { }; } -function destination_format(config_data, k, max_chars, null_itatic) { - const null_placeholder = function () { - if (null_itatic) { - return `${_("direct")}`; +function destination_format(config_data, k, e, max_chars) { + return function (s) { + if (e) { + if (!uci.get(config_data, s, e)) { + return `${_("use global settings")}`; + } } - return _("direct"); - }(); - return list_folded_format(config_data, k, "outbounds", max_chars, v => uci.get(config_data, v, "alias"), null_placeholder); + return list_folded_format(config_data, k, "outbounds", max_chars, v => uci.get(config_data, v, "alias"), `${_("direct")}`)(s); + }; } function extra_outbound_format(config_data, s, select_item) { @@ -191,16 +192,6 @@ return view.extend({ o.datatype = 'host'; o.rmempty = false; - o = ss.taboption('general', form.ListValue, 'domain_strategy', _('Domain Strategy'), _("Whether to use IPv4 or IPv6 address if Server Hostname is a domain.")); - o.value("UseIP"); - o.value("UseIPv4"); - o.value("UseIPv6"); - o.default = "UseIP"; - o.modalonly = true; - - o = ss.taboption('general', form.Value, 'domain_resolve_dns', _('Resolve Domain via DNS'), _("Specify a DNS to resolve server hostname. Be careful of possible recursion.")); - o.modalonly = true; - o = ss.taboption('general', form.Value, 'server_port', _('Server Port')); o.datatype = 'port'; o.rmempty = false; @@ -212,6 +203,29 @@ return view.extend({ o.modalonly = true; o.rmempty = false; + ss.tab('resolving', _("Server Hostname Resolving")); + + o = ss.taboption('resolving', form.ListValue, 'domain_strategy', _('Domain Strategy'), _("Whether to use IPv4 or IPv6 address if Server Hostname is a domain.")); + o.value("UseIP"); + o.value("UseIPv4"); + o.value("UseIPv6"); + o.default = "UseIP"; + o.modalonly = true; + + o = ss.taboption('resolving', form.Value, 'domain_resolve_dns', _('Resolve Domain via DNS'), _("Specify a DNS to resolve server hostname. Be careful of possible recursion.")); + o.datatype = "or(ipaddr, ipaddrport(1))"; + o.modalonly = true; + + o = ss.taboption('resolving', form.ListValue, 'domain_resolve_dns_method', _('Resolve Domain DNS Method'), _("Effective when DNS above is set. Direct methods will bypass Xray completely so it may get blocked.")); + o.value("udp", _("UDP")); + o.value("quic+local", _("DNS over QUIC (direct)")); + o.value("tcp", _("TCP")); + o.value("tcp+local", _("TCP (direct)")); + o.value("https", _("DNS over HTTPS")); + o.value("https+local", _("DNS over HTTPS (direct)")); + o.default = "UseIP"; + o.modalonly = true; + ss.tab('protocol', _('Protocol Settings')); o = ss.taboption('protocol', form.ListValue, "protocol", _("Protocol")); @@ -299,7 +313,7 @@ return view.extend({ let destination = extra_inbounds.option(form.MultiValue, 'destination', _('Destination'), _("Select multiple outbounds for load balancing. If none selected, requests will be sent via direct outbound.")); destination.depends("specify_outbound", "1"); destination.datatype = "uciname"; - destination.textvalue = destination_format(config_data, "destination", 60, true); + destination.textvalue = destination_format(config_data, "destination", "specify_outbound", 60); let balancer_strategy = extra_inbounds.option(form.Value, 'balancer_strategy', _('Balancer Strategy'), _('Strategy leastPing requires observatory (see "Extra Options" tab) to be enabled.')); balancer_strategy.depends("specify_outbound", "1"); @@ -499,11 +513,11 @@ return view.extend({ let fake_dns_forward_server_tcp = fs.option(form.MultiValue, 'fake_dns_forward_server_tcp', _('Force Forward server (TCP)')); fake_dns_forward_server_tcp.datatype = "uciname"; - fake_dns_forward_server_tcp.textvalue = destination_format(config_data, "fake_dns_forward_server_tcp", 40, true); + fake_dns_forward_server_tcp.textvalue = destination_format(config_data, "fake_dns_forward_server_tcp", null, 40); let fake_dns_forward_server_udp = fs.option(form.MultiValue, 'fake_dns_forward_server_udp', _('Force Forward server (UDP)')); fake_dns_forward_server_udp.datatype = "uciname"; - fake_dns_forward_server_udp.textvalue = destination_format(config_data, "fake_dns_forward_server_udp", 40, true); + fake_dns_forward_server_udp.textvalue = destination_format(config_data, "fake_dns_forward_server_udp", null, 40); let fake_dns_balancer_strategy = fs.option(form.Value, 'fake_dns_balancer_strategy', _('Balancer Strategy'), _('Strategy leastPing requires observatory (see "Extra Options" tab) to be enabled.')); fake_dns_balancer_strategy.value("random");