diff --git a/README.md b/README.md
index 56d499e4..0c6257a1 100644
--- a/README.md
+++ b/README.md
@@ -4,9 +4,13 @@ Focus on making the most of Xray (HTTP/HTTPS/Socks/TProxy inbounds, multiple pro
## Warnings
+* For security concerns, global SOCKS / HTTP inbound (listen on 0.0.0.0, port 1080 / 1081 by default) is deprecated and will be removed in next major version (4.0.0).
+ * These settings are moved out of main luci app. Select "Preview or Deprecated" in "Extra Settings" tab and reboot to let those settings show again in preview app.
+ * Use Extra Inbound to manually add ports (avoid using common ports like 1080, also set listen addresses carefully) and adjust related workloads to use that.
* Since version 3.2.0 sniffing and global custom settings are deprecated.
- * These features are moved out of main luci app. Select "Preview or Deprecated" in "Extra Settings" tab and reboot to let those settings show again in preview app.
- * These likely to be removed in version 4.0.0. Use FakeDNS instead of sniffing and use "Custom Configuration Hook" for global custom settings.
+ * These features are moved out of main luci app (into preview app).
+ * Global custom settings will be removed in version 4.0.0. Use "Custom Configuration Hook" for global custom settings.
+ * Sniffing might get completely reimplemented later. Use FakeDNS instead of sniffing to avoid incompatibilities.
* This project **DOES NOT SUPPORT** the following versions of OpenWrt because of the requirements of firewall4 and cilent-side rendering LuCI:
* LEDE / OpenWrt prior to 22.03
* [Lean's OpenWrt Source](https://github.com/coolsnowwolf/lede) (which uses a variant of LuCI shipped with OpenWrt 18.06)
@@ -41,6 +45,7 @@ Fork this repository and:
* 2024-01-19 chore: bump version
* 2024-01-24 feat: add alias to LAN Hosts Access Control
* 2024-02-04 fix: avoid firewall restart failure & some minor adjustments
+* 2024-02-17 feat: add DNS Hijacking (preview)
## Changelog since 3.2.0
diff --git a/core/root/usr/share/xray/common/stream.mjs b/core/root/usr/share/xray/common/stream.mjs
index f65558d1..043dcaec 100644
--- a/core/root/usr/share/xray/common/stream.mjs
+++ b/core/root/usr/share/xray/common/stream.mjs
@@ -1,6 +1,6 @@
"use strict";
-import { tls_outbound_settings, reality_outbound_settings } from "./tls.mjs";
+import { reality_outbound_settings, tls_outbound_settings } from "./tls.mjs";
function stream_tcp_fake_http_request(server) {
if (server["tcp_guise"] == "http") {
diff --git a/core/root/usr/share/xray/feature/dns.mjs b/core/root/usr/share/xray/feature/dns.mjs
index 53180dc4..75966a51 100644
--- a/core/root/usr/share/xray/feature/dns.mjs
+++ b/core/root/usr/share/xray/feature/dns.mjs
@@ -2,6 +2,7 @@
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";
@@ -67,29 +68,51 @@ export function dns_server_inbounds(proxy) {
return result;
};
-export function dns_server_tags(proxy) {
- let 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);
+ let dns_server_tags = [];
for (let i = dns_port; i <= dns_port + dns_count; i++) {
- push(result, sprintf("dns_server_inbound:%d", i));
+ push(dns_server_tags, sprintf("dns_server_inbound:%d", i));
}
- return result;
-};
-
-export function dns_server_outbound() {
- return {
- protocol: "dns",
- settings: {
- nonIPQuery: "skip"
+ return [
+ {
+ type: "field",
+ port: "53",
+ inboundTag: tcp_hijack_inbound_tags,
+ outboundTag: "dns_tcp_hijack_outbound"
},
- streamSettings: {
- sockopt: {
- mark: 254
- }
+ {
+ type: "field",
+ port: "53",
+ inboundTag: udp_hijack_inbound_tags,
+ outboundTag: "dns_udp_hijack_outbound"
},
- tag: "dns_server_outbound"
- };
+ {
+ type: "field",
+ inboundTag: dns_server_tags,
+ outboundTag: "dns_server_outbound"
+ },
+ ];
+};
+
+export function dns_server_outbounds(proxy) {
+ return [
+ direct_outbound("dns_tcp_hijack_outbound", proxy.dns_tcp_hijack || ""),
+ direct_outbound("dns_udp_hijack_outbound", proxy.dns_udp_hijack || ""),
+ {
+ protocol: "dns",
+ settings: {
+ nonIPQuery: "skip"
+ },
+ streamSettings: {
+ sockopt: {
+ mark: 254
+ }
+ },
+ tag: "dns_server_outbound"
+ }
+ ];
};
export function dns_conf(proxy, config, manual_tproxy, fakedns) {
diff --git a/core/root/usr/share/xray/feature/extra_inbound.mjs b/core/root/usr/share/xray/feature/extra_inbound.mjs
index 8ee5f3b5..bd52abb0 100644
--- a/core/root/usr/share/xray/feature/extra_inbound.mjs
+++ b/core/root/usr/share/xray/feature/extra_inbound.mjs
@@ -52,10 +52,18 @@ export function extra_inbound_balancers(extra_inbound) {
return result;
};
-export function extra_inbound_global_tcp_tags(extra_inbound) {
- return map(filter(extra_inbound, v => v["specify_outbound"] != "1" && v["inbound_type"] != "tproxy_udp"), v => `extra_inbound_${v[".name"]}`);
+export function extra_inbound_global_tcp(extra_inbound) {
+ return map(filter(extra_inbound, v => v["specify_outbound"] != "1" && v["inbound_type"] == "tproxy_tcp"), v => `extra_inbound_${v[".name"]}`);
};
-export function extra_inbound_global_udp_tags(extra_inbound) {
+export function extra_inbound_global_udp(extra_inbound) {
return map(filter(extra_inbound, v => v["specify_outbound"] != "1" && v["inbound_type"] == "tproxy_udp"), v => `extra_inbound_${v[".name"]}`);
};
+
+export function extra_inbound_global_http(extra_inbound) {
+ return map(filter(extra_inbound, v => v["specify_outbound"] != "1" && v["inbound_type"] == "http"), v => `extra_inbound_${v[".name"]}`);
+};
+
+export function extra_inbound_global_socks5(extra_inbound) {
+ return map(filter(extra_inbound, v => v["specify_outbound"] != "1" && v["inbound_type"] == "socks5"), v => `extra_inbound_${v[".name"]}`);
+};
diff --git a/core/root/usr/share/xray/feature/outbound.mjs b/core/root/usr/share/xray/feature/outbound.mjs
index 34ee50e6..32577bc2 100644
--- a/core/root/usr/share/xray/feature/outbound.mjs
+++ b/core/root/usr/share/xray/feature/outbound.mjs
@@ -1,11 +1,11 @@
"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 { http_outbound } from "../protocol/http.mjs";
-import { socks_outbound } from "../protocol/socks.mjs";
function override_custom_config_recursive(x, y) {
if (type(x) != "object" || type(y) != "object") {
@@ -58,12 +58,13 @@ function server_outbound_recursive(t, server, tag, config) {
return result;
}
-export function direct_outbound(tag) {
+export function direct_outbound(tag, redirect) {
return {
protocol: "freedom",
tag: tag,
settings: {
- domainStrategy: "UseIPv4"
+ domainStrategy: "UseIPv4",
+ redirect: redirect || ""
},
streamSettings: {
sockopt: {
@@ -82,7 +83,7 @@ export function blackhole_outbound() {
export function server_outbound(server, tag, config) {
if (server == null) {
- return [direct_outbound(tag)];
+ return [direct_outbound(tag, null)];
}
return server_outbound_recursive([], server, tag, config);
};
diff --git a/core/root/usr/share/xray/gen_config.uc b/core/root/usr/share/xray/gen_config.uc
index 9cbc0cc3..4f1d144d 100644
--- a/core/root/usr/share/xray/gen_config.uc
+++ b/core/root/usr/share/xray/gen_config.uc
@@ -4,8 +4,8 @@
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_server_inbounds, dns_server_outbound, dns_server_tags, fast_domain_rules, secure_domain_rules } from "./feature/dns.mjs";
-import { extra_inbound_balancers, extra_inbound_global_tcp_tags, extra_inbound_global_udp_tags, extra_inbound_rules, extra_inbounds } from "./feature/extra_inbound.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 { extra_inbound_balancers, extra_inbound_global_http, extra_inbound_global_socks5, extra_inbound_global_tcp, extra_inbound_global_udp, extra_inbound_rules, extra_inbounds } from "./feature/extra_inbound.mjs";
import { fake_dns_balancers, fake_dns_conf, fake_dns_rules } from "./feature/fake_dns.mjs";
import { dokodemo_inbound, 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";
@@ -61,9 +61,9 @@ function inbounds(proxy, config, extra_inbound) {
function outbounds(proxy, config, manual_tproxy, bridge, extra_inbound, fakedns) {
let result = [
- direct_outbound("direct"),
+ direct_outbound("direct", null),
blackhole_outbound(),
- dns_server_outbound(),
+ ...dns_server_outbounds(proxy),
...manual_tproxy_outbounds(config, manual_tproxy),
...bridge_outbounds(config, bridge)
];
@@ -108,16 +108,19 @@ function rules(proxy, bridge, manual_tproxy, extra_inbound, fakedns) {
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 built_in_tcp_inbounds = [...tproxy_tcp_inbound_v4_tags, "socks_inbound", "https_inbound", "http_inbound"];
- const built_in_udp_inbounds = [...tproxy_udp_inbound_v4_tags, "dns_conf_inbound"];
- const extra_inbound_global_tcp = extra_inbound_global_tcp_tags() || [];
- const extra_inbound_global_udp = extra_inbound_global_udp_tags() || [];
+ const extra_inbound_global_tcp_tags = extra_inbound_global_tcp() || [];
+ const extra_inbound_global_udp_tags = extra_inbound_global_udp() || [];
+ const extra_inbound_global_http_tags = extra_inbound_global_http() || [];
+ const extra_inbound_global_socks5_tags = extra_inbound_global_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) {
@@ -126,7 +129,7 @@ function rules(proxy, bridge, manual_tproxy, extra_inbound, fakedns) {
if (length(geoip_direct_code_list) > 0) {
push(direct_rules, {
type: "field",
- inboundTag: [...built_in_tcp_inbounds, ...built_in_udp_inbounds, ...extra_inbound_global_tcp, ...extra_inbound_global_udp],
+ inboundTag: [...built_in_tcp_inbounds, ...built_in_udp_inbounds],
outboundTag: "direct",
ip: geoip_direct_code_list
});
@@ -143,7 +146,7 @@ function rules(proxy, bridge, manual_tproxy, extra_inbound, fakedns) {
}
push(direct_rules, {
type: "field",
- inboundTag: [...tproxy_tcp_inbound_v6_tags, ...tproxy_udp_inbound_v6_tags, ...built_in_tcp_inbounds, ...built_in_udp_inbounds, ...extra_inbound_global_tcp, ...extra_inbound_global_udp],
+ inboundTag: [...tproxy_tcp_inbound_v6_tags, ...tproxy_udp_inbound_v6_tags, ...built_in_tcp_inbounds, ...built_in_udp_inbounds],
outboundTag: "direct",
ip: ["geoip:private"]
});
@@ -152,40 +155,35 @@ function rules(proxy, bridge, manual_tproxy, extra_inbound, fakedns) {
}(),
{
type: "field",
- inboundTag: [...tproxy_tcp_inbound_v6_tags],
+ inboundTag: tproxy_tcp_inbound_v6_tags,
balancerTag: "tcp_outbound_v6"
},
{
type: "field",
- inboundTag: [...tproxy_udp_inbound_v6_tags],
+ inboundTag: tproxy_udp_inbound_v6_tags,
balancerTag: "udp_outbound_v6"
},
{
type: "field",
- inboundTag: [...built_in_tcp_inbounds, ...extra_inbound_global_tcp],
+ inboundTag: built_in_tcp_inbounds,
balancerTag: "tcp_outbound_v4"
},
{
type: "field",
- inboundTag: [...built_in_udp_inbounds, ...extra_inbound_global_udp],
+ inboundTag: built_in_udp_inbounds,
balancerTag: "udp_outbound_v4"
},
- {
- type: "field",
- inboundTag: dns_server_tags(proxy),
- outboundTag: "dns_server_outbound"
- },
];
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],
+ 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],
+ inboundTag: [...tproxy_udp_inbound_v4_tags, ...extra_inbound_global_udp_tags],
balancerTag: "udp_outbound_v4",
domain: secure_domain_rules(proxy),
}, {
@@ -203,14 +201,14 @@ function rules(proxy, bridge, manual_tproxy, extra_inbound, fakedns) {
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, ...extra_inbound_global_udp],
+ 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, ...extra_inbound_global_udp],
+ 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)
});
diff --git a/core/root/usr/share/xray/protocol/trojan.mjs b/core/root/usr/share/xray/protocol/trojan.mjs
index b533e96e..9fa99572 100644
--- a/core/root/usr/share/xray/protocol/trojan.mjs
+++ b/core/root/usr/share/xray/protocol/trojan.mjs
@@ -1,7 +1,7 @@
"use strict";
import { stream_settings } from "../common/stream.mjs";
-import { tls_inbound_settings, fallbacks } from "../common/tls.mjs";
+import { fallbacks, tls_inbound_settings } from "../common/tls.mjs";
function trojan_inbound_user(k) {
return {
diff --git a/core/root/usr/share/xray/protocol/vless.mjs b/core/root/usr/share/xray/protocol/vless.mjs
index 35c727e8..e551bb01 100644
--- a/core/root/usr/share/xray/protocol/vless.mjs
+++ b/core/root/usr/share/xray/protocol/vless.mjs
@@ -1,7 +1,7 @@
"use strict";
import { stream_settings } from "../common/stream.mjs";
-import { tls_inbound_settings, reality_inbound_settings, fallbacks } from "../common/tls.mjs";
+import { fallbacks, reality_inbound_settings, tls_inbound_settings } from "../common/tls.mjs";
function vless_inbound_user(k, flow) {
return {
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 251d38ee..f5b120d7 100644
--- a/core/root/www/luci-static/resources/view/xray/core.js
+++ b/core/root/www/luci-static/resources/view/xray/core.js
@@ -243,14 +243,6 @@ return view.extend({
s.tab('inbounds', _('Inbounds'));
- o = s.taboption('inbounds', form.Value, 'socks_port', _('Socks5 proxy port'));
- o.datatype = 'port';
- o.placeholder = 1080;
-
- o = s.taboption('inbounds', form.Value, 'http_port', _('HTTP proxy port'));
- o.datatype = 'port';
- o.placeholder = 1081;
-
o = s.taboption('inbounds', form.Value, 'tproxy_port_tcp_v4', _('Transparent proxy port (TCP4)'));
o.datatype = 'port';
o.placeholder = 1082;
@@ -301,6 +293,7 @@ return view.extend({
destination.textvalue = destination_format(config_data, "destination", 60, true);
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");
balancer_strategy.value("random");
balancer_strategy.value("leastPing");
balancer_strategy.value("roundRobin");
diff --git a/core/root/www/luci-static/resources/view/xray/preview.js b/core/root/www/luci-static/resources/view/xray/preview.js
index 4110d0e8..ed2710ac 100644
--- a/core/root/www/luci-static/resources/view/xray/preview.js
+++ b/core/root/www/luci-static/resources/view/xray/preview.js
@@ -11,6 +11,14 @@ return view.extend({
s.addremove = false;
s.anonymous = true;
+ s.tab("dns_hijack", _("DNS Hijacking"));
+
+ let dns_tcp_hijack = s.taboption('dns_hijack', form.Value, 'dns_tcp_hijack', _('Hijack TCP DNS Requests'), _("Redirect all outgoing TCP requests with destination port 53 to the address specified. In most cases not necessary."));
+ dns_tcp_hijack.datatype = 'or(ip4addr, ip4addrport)';
+
+ let dns_udp_hijack = s.taboption('dns_hijack', form.Value, 'dns_udp_hijack', _('Hijack UDP DNS Requests'), _("Redirect all outgoing UDP requests with destination port 53 to the address specified. Recommended to use 127.0.0.1:53
."));
+ dns_udp_hijack.datatype = 'or(ip4addr, ip4addrport)';
+
s.tab("firewall", _("Extra Firewall Options"));
let mark = s.taboption('firewall', form.Value, 'mark', _('Socket Mark Number'), _('Avoid proxy loopback problems with local (gateway) traffic'));
@@ -30,7 +38,15 @@ return view.extend({
let ttl_hop_limit_match = s.taboption('firewall', form.Value, 'ttl_hop_limit_match', _('TTL / Hop Limit Match'), _("Only override TTL / hop limit for packets with specific TTL / hop limit."));
ttl_hop_limit_match.datatype = 'uinteger';
- s.tab("sniffing", _("Sniffing"));
+ s.tab("sniffing", _("Legacy Inbounds and Sniffing"));
+
+ let socks_port = s.taboption('sniffing', form.Value, 'socks_port', _('Socks5 proxy port'), _("Deprecated for security concerns. Use Extra Inbound instead."));
+ socks_port.datatype = 'port';
+ socks_port.placeholder = 1080;
+
+ let http_port = s.taboption('sniffing', form.Value, 'http_port', _('HTTP proxy port'), _("Deprecated for security concerns. Use Extra Inbound instead."));
+ http_port.datatype = 'port';
+ http_port.placeholder = 1081;
s.taboption('sniffing', form.Flag, 'tproxy_sniffing', _('Enable Sniffing'), _('Route requests according to domain settings in "DNS Settings" tab in core settings. Deprecated; use FakeDNS instead.'));