diff --git a/luci-app-passwall2/Makefile b/luci-app-passwall2/Makefile index 9c503f189..7d64fba3a 100644 --- a/luci-app-passwall2/Makefile +++ b/luci-app-passwall2/Makefile @@ -5,7 +5,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-passwall2 -PKG_VERSION:=1.19-6 +PKG_VERSION:=1.20-4 PKG_RELEASE:= PKG_CONFIG_DEPENDS:= \ @@ -23,8 +23,8 @@ PKG_CONFIG_DEPENDS:= \ CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Client \ CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Server \ CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Simple_Obfs \ + CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_SingBox \ CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_tuic_client \ - CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_V2ray \ CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_V2ray_Plugin LUCI_TITLE:=LuCI support for PassWall 2 @@ -47,8 +47,8 @@ LUCI_DEPENDS:=+coreutils +coreutils-base64 +coreutils-nohup +curl \ +PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Client:shadowsocksr-libev-ssr-redir \ +PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Server:shadowsocksr-libev-ssr-server \ +PACKAGE_$(PKG_NAME)_INCLUDE_Simple_Obfs:simple-obfs \ + +PACKAGE_$(PKG_NAME)_INCLUDE_SingBox:sing-box \ +PACKAGE_$(PKG_NAME)_INCLUDE_tuic_client:tuic-client \ - +PACKAGE_$(PKG_NAME)_INCLUDE_V2ray:v2ray-core \ +PACKAGE_$(PKG_NAME)_INCLUDE_V2ray_Plugin:v2ray-plugin define Package/$(PKG_NAME)/config @@ -133,15 +133,15 @@ config PACKAGE_$(PKG_NAME)_INCLUDE_Simple_Obfs bool "Include Simple-Obfs (Shadowsocks Plugin)" default y +config PACKAGE_$(PKG_NAME)_INCLUDE_SingBox + bool "Include Sing-Box" + default y if aarch64||arm||i386||x86_64 + config PACKAGE_$(PKG_NAME)_INCLUDE_tuic_client bool "Include tuic-client" depends on aarch64||arm||i386||x86_64 default n -config PACKAGE_$(PKG_NAME)_INCLUDE_V2ray - bool "Include V2ray" - default n - config PACKAGE_$(PKG_NAME)_INCLUDE_V2ray_Plugin bool "Include V2ray-Plugin (Shadowsocks Plugin)" default y if aarch64||arm||i386||x86_64 diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/global.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/global.lua index 68cced9e2..da57fc056 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/global.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/global.lua @@ -2,7 +2,7 @@ local api = require "luci.passwall2.api" local appname = api.appname local uci = api.uci local datatypes = api.datatypes -local has_v2ray = api.is_finded("v2ray") +local has_singbox = api.is_finded("sing-box") local has_xray = api.is_finded("xray") m = Map(appname) @@ -36,6 +36,8 @@ end m:append(Template(appname .. "/global/status")) +local global_cfgid = uci:get_all(appname, "@global[0]")[".name"] + s = m:section(TypedSection, "global") s.anonymous = true s.addremove = false @@ -51,7 +53,7 @@ node = s:taboption("Main", ListValue, "node", "" .. transl node:value("nil", translate("Close")) -- 分流 -if (has_v2ray or has_xray) and #nodes_table > 0 then +if (has_singbox or has_xray) and #nodes_table > 0 then local normal_list = {} local balancing_list = {} local shunt_list = {} @@ -84,10 +86,10 @@ if (has_v2ray or has_xray) and #nodes_table > 0 then if #normal_list > 0 then for k, v in pairs(shunt_list) do local vid = v.id - -- shunt node type, V2ray or Xray + -- shunt node type, Sing-Box or Xray local type = s:taboption("Main", ListValue, vid .. "-type", translate("Type")) - if has_v2ray then - type:value("V2ray", translate("V2ray")) + if has_singbox then + type:value("sing-box", translate("Sing-Box")) end if has_xray then type:value("Xray", translate("Xray")) @@ -119,7 +121,7 @@ if (has_v2ray or has_xray) and #nodes_table > 0 then o.cfgvalue = get_cfgvalue(v.id, "main_node") o.write = get_write(v.id, "main_node") - if (has_v2ray and has_xray) or (v.type == "V2ray" and not has_v2ray) or (v.type == "Xray" and not has_xray) then + if (has_singbox and has_xray) or (v.type == "sing-box" and not has_singbox) or (v.type == "Xray" and not has_xray) then type:depends("node", v.id) else type:depends("node", "hide") --不存在的依赖,即始终隐藏 @@ -212,14 +214,6 @@ node_socks_port = s:taboption("Main", Value, "node_socks_port", translate("Node" node_socks_port.default = 1070 node_socks_port.datatype = "port" ---[[ -if has_v2ray or has_xray then - node_http_port = s:taboption("Main", Value, "node_http_port", translate("Node") .. " HTTP " .. translate("Listen Port") .. " " .. translate("0 is not use")) - node_http_port.default = 0 - node_http_port.datatype = "port" -end -]]-- - s:tab("DNS", translate("DNS")) o = s:taboption("DNS", ListValue, "remote_dns_protocol", translate("Remote DNS Protocol")) @@ -260,8 +254,6 @@ o = s:taboption("DNS", Value, "remote_dns_client_ip", translate("Remote DNS EDNS o.description = translate("Notify the DNS server when the DNS query is notified, the location of the client (cannot be a private IP address).") .. "
" .. translate("This feature requires the DNS server to support the Edns Client Subnet (RFC7871).") o.datatype = "ipaddr" -o:depends("remote_dns_protocol", "tcp") -o:depends("remote_dns_protocol", "doh") o = s:taboption("DNS", Flag, "remote_fakedns", "FakeDNS", translate("Use FakeDNS work in the shunt domain that proxy.")) o.default = "0" @@ -273,9 +265,16 @@ o:value("UseIP") o:value("UseIPv4") o:value("UseIPv6") -hosts = s:taboption("DNS", TextValue, "dns_hosts", translate("Domain Override")) -hosts.rows = 5 -hosts.wrap = "off" +o = s:taboption("DNS", TextValue, "dns_hosts", translate("Domain Override")) +o.rows = 5 +o.wrap = "off" +o.remove = function(self, section) + local node_value = node:formvalue(global_cfgid) + local node_t = m:get(node_value) + if node_t.type == "Xray" then + AbstractValue.remove(self, section) + end +end o = s:taboption("DNS", Button, "clear_ipset", translate("Clear IPSET"), translate("Try this feature if the rule modification does not take effect.")) o.inputstyle = "remove" @@ -284,6 +283,14 @@ function o.write(e, e) luci.http.redirect(api.url("log")) end +for k, v in pairs(nodes_table) do + if v.type == "Xray" then + s.fields["remote_dns_client_ip"]:depends({ node = v.id, remote_dns_protocol = "tcp" }) + s.fields["remote_dns_client_ip"]:depends({ node = v.id, remote_dns_protocol = "doh" }) + s.fields["dns_hosts"]:depends({ node = v.id }) + end +end + s:tab("log", translate("Log")) o = s:taboption("log", Flag, "close_log", translate("Close Node Log")) o.rmempty = false @@ -342,7 +349,7 @@ o.default = n + 1080 o.datatype = "port" o.rmempty = false -if has_v2ray or has_xray then +if has_singbox or has_xray then o = s:option(Value, "http_port", "HTTP " .. translate("Listen Port") .. " " .. translate("0 is not use")) o.default = 0 o.datatype = "port" @@ -351,7 +358,7 @@ end for k, v in pairs(nodes_table) do node:value(v.id, v["remark"]) if v.type == "Socks" then - if has_v2ray or has_xray then + if has_singbox or has_xray then socks_node:value(v.id, v["remark"]) end else diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_list.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_list.lua index af6d32533..83584fe2c 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_list.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_list.lua @@ -83,7 +83,7 @@ o.cfgvalue = function(t, n) local remarks = m:get(n, "remarks") or "" local type = m:get(n, "type") or "" str = str .. string.format("", appname, n, type) - if type == "V2ray" or type == "Xray" then + if type == "sing-box" or type == "Xray" then local protocol = m:get(n, "protocol") if protocol == "_balancing" then protocol = translate("Balancing") diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe.lua index 044d3eb8c..824ef1e28 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe.lua @@ -2,7 +2,7 @@ local api = require "luci.passwall2.api" local appname = api.appname local has_ss = api.is_finded("ss-redir") local has_ss_rust = api.is_finded("sslocal") -local has_v2ray = api.is_finded("v2ray") +local has_singbox = api.is_finded("sing-box") local has_xray = api.is_finded("xray") local ss_aead_type = {} if has_ss then @@ -11,8 +11,8 @@ end if has_ss_rust then ss_aead_type[#ss_aead_type + 1] = "shadowsocks-rust" end -if has_v2ray then - ss_aead_type[#ss_aead_type + 1] = "v2ray" +if has_singbox then + ss_aead_type[#ss_aead_type + 1] = "sing-box" end if has_xray then ss_aead_type[#ss_aead_type + 1] = "xray" diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe_config.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe_config.lua index 1a23f9f78..a833c186b 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe_config.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe_config.lua @@ -3,7 +3,7 @@ local appname = api.appname local sys = api.sys local has_ss = api.is_finded("ss-redir") local has_ss_rust = api.is_finded("sslocal") -local has_v2ray = api.is_finded("v2ray") +local has_singbox = api.is_finded("sing-box") local has_xray = api.is_finded("xray") local ss_aead_type = {} if has_ss then @@ -12,8 +12,8 @@ end if has_ss_rust then ss_aead_type[#ss_aead_type + 1] = "shadowsocks-rust" end -if has_v2ray then - ss_aead_type[#ss_aead_type + 1] = "v2ray" +if has_singbox then + ss_aead_type[#ss_aead_type + 1] = "sing-box" end if has_xray then ss_aead_type[#ss_aead_type + 1] = "xray" diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/other.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/other.lua index df29e2871..41a4e4817 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/other.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/other.lua @@ -1,7 +1,8 @@ local api = require "luci.passwall2.api" local appname = api.appname local fs = api.fs -local has_v2ray = api.is_finded("v2ray") +local uci = api.uci +local has_singbox = api.is_finded("sing-box") local has_xray = api.is_finded("xray") local has_fw3 = api.is_finded("fw3") local has_fw4 = api.is_finded("fw4") @@ -126,8 +127,8 @@ o = s:option(Flag, "accept_icmpv6", translate("Hijacking ICMPv6 (IPv6 PING)")) o:depends("ipv6_tproxy", true) o.default = 0 -if has_v2ray or has_xray then - s = m:section(TypedSection, "global_xray", "V2Ray/Xray " .. translate("Settings")) +if has_xray then + s = m:section(TypedSection, "global_xray", "Xray " .. translate("Settings")) s.anonymous = true s.addremove = false @@ -158,4 +159,33 @@ if has_v2ray or has_xray then end end +if has_singbox then + s = m:section(TypedSection, "global_singbox", "Sing-Box " .. translate("Settings")) + s.anonymous = true + s.addremove = false + + o = s:option(Flag, "sniff_override_destination", translate("Override the connection destination address"), translate("Override the connection destination address with the sniffed domain.")) + o.default = 1 + o.rmempty = false + + o = s:option(Value, "geoip_path", translate("Custom geoip Path")) + o.default = "/tmp/singbox/geoip.db" + o.rmempty = false + + o = s:option(Value, "geoip_url", translate("Custom geoip URL")) + o.default = "https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db" + o:value("https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db") + o.rmempty = false + + o = s:option(Value, "geosite_path", translate("Custom geosite Path")) + o.default = "/tmp/singbox/geosite.db" + o.rmempty = false + + o = s:option(Value, "geosite_url", translate("Custom geosite URL")) + o.default = "https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db" + o:value("https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db") + o.rmempty = false + +end + return m diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/rule.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/rule.lua index 56ce0fcf9..706ac26e3 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/rule.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/rule.lua @@ -41,7 +41,7 @@ for e = 0, 23 do o:value(e, e .. translate("oclock")) end o.default = 0 o:depends("auto_update", true) -s = m:section(TypedSection, "shunt_rules", "V2ray/Xray " .. translate("Shunt Rule"), "
" .. translate("Please note attention to the priority, the higher the order, the higher the priority.") .. "") +s = m:section(TypedSection, "shunt_rules", "Xray " .. translate("Shunt Rule"), "" .. translate("Please note attention to the priority, the higher the order, the higher the priority.") .. "") s.template = "cbi/tblsection" s.anonymous = false s.addremove = true diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/shunt_rules.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/shunt_rules.lua index 9fb2f9aca..77f111151 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/shunt_rules.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/shunt_rules.lua @@ -2,7 +2,7 @@ local api = require "luci.passwall2.api" local appname = api.appname local datatypes = api.datatypes -m = Map(appname, "V2ray/Xray " .. translate("Shunt Rule")) +m = Map(appname, "Xray " .. translate("Shunt Rule")) m.redirect = api.url() s = m:section(NamedSection, arg[1], "shunt_rules", "") diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/socks_config.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/socks_config.lua index e0f321fde..bb4d2763e 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/socks_config.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/socks_config.lua @@ -1,7 +1,7 @@ local api = require "luci.passwall2.api" local appname = api.appname local uci = api.uci -local has_v2ray = api.is_finded("v2ray") +local has_singbox = api.is_finded("sing-box") local has_xray = api.is_finded("xray") m = Map(appname) @@ -54,7 +54,7 @@ o.default = n + 1080 o.datatype = "port" o.rmempty = false -if has_v2ray or has_xray then +if has_singbox or has_xray then o = s:option(Value, "http_port", "HTTP " .. translate("Listen Port") .. " " .. translate("0 is not use")) o.default = 0 o.datatype = "port" diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ray.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ray.lua index c8e082b23..ebbd61e0f 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ray.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ray.lua @@ -2,13 +2,15 @@ local m, s = ... local api = require "luci.passwall2.api" -if not api.is_finded("xray") and not api.is_finded("v2ray") then +if not api.is_finded("xray") then return end local appname = api.appname local uci = api.uci +local type_name = "Xray" + local option_prefix = "xray_" local function option_name(name) @@ -21,52 +23,20 @@ local function rm_prefix_cfgvalue(self, section) end end local function rm_prefix_write(self, section, value) - if s.fields["type"]:formvalue(arg[1]) == "Xray" or s.fields["type"]:formvalue(arg[1]) == "V2ray" then + if s.fields["type"]:formvalue(arg[1]) == type_name then if self.option:find(option_prefix) == 1 then m:set(section, self.option:sub(1 + #option_prefix), value) end end end local function rm_prefix_remove(self, section, value) - if s.fields["type"]:formvalue(arg[1]) == "Xray" or s.fields["type"]:formvalue(arg[1]) == "V2ray" then + if s.fields["type"]:formvalue(arg[1]) == type_name then if self.option:find(option_prefix) == 1 then m:del(section, self.option:sub(1 + #option_prefix)) end end end -local function add_xray_depends(o, field, value) - local deps = { type = "Xray" } - if field then - if type(field) == "string" then - deps[field] = value - else - for key, value in pairs(field) do - deps[key] = value - end - end - end - o:depends(deps) -end - -local function add_v2ray_depends(o, field, value) - local deps = { type = "V2ray" } - if field then - if type(field) == "string" then - deps[field] = value - else - for key, value in pairs(field) do - deps[key] = value - end - end - end - o:depends(deps) -end - -local v_ss_encrypt_method_list = { - "aes-128-gcm", "aes-256-gcm", "chacha20-poly1305" -} - local x_ss_encrypt_method_list = { "aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "xchacha20-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305" } @@ -79,12 +49,7 @@ local header_type_list = { -- [[ Xray ]] -if api.is_finded("xray") then - s.fields["type"]:value("Xray", translate("Xray")) -end -if api.is_finded("v2ray") then - s.fields["type"]:value("V2ray", translate("V2ray")) -end +s.fields["type"]:value(type_name, "Xray") o = s:option(ListValue, option_name("protocol"), translate("Protocol")) o:value("vmess", translate("Vmess")) @@ -97,8 +62,6 @@ o:value("wireguard", translate("WireGuard")) o:value("_balancing", translate("Balancing")) o:value("_shunt", translate("Shunt")) o:value("_iface", translate("Custom Interface") .. " (Only Support Xray)") -add_xray_depends(o) -add_v2ray_depends(o) o = s:option(Value, option_name("iface"), translate("Interface")) o.default = "eth1" @@ -131,31 +94,26 @@ end -- 负载均衡列表 local o = s:option(DynamicList, option_name("balancing_node"), translate("Load balancing node list"), translate("Load balancing node list, document")) o:depends({ [option_name("protocol")] = "_balancing" }) -add_v2ray_depends(o, { [option_name("protocol")] = "_balancing" }) for k, v in pairs(nodes_table) do o:value(v.id, v.remarks) end local o = s:option(ListValue, option_name("balancingStrategy"), translate("Balancing Strategy")) -add_xray_depends(o, { [option_name("protocol")] = "_balancing" }) -add_v2ray_depends(o, { [option_name("protocol")] = "_balancing" }) +o:depends({ [option_name("protocol")] = "_balancing" }) o:value("random") o:value("leastPing") o.default = "random" -- 探测地址 local o = s:option(Flag, option_name("useCustomProbeUrl"), translate("Use Custome Probe URL"), translate("By default the built-in probe URL will be used, enable this option to use a custom probe URL.")) -add_xray_depends(o, { [option_name("balancingStrategy")] = "leastPing" }) -add_v2ray_depends(o, { [option_name("balancingStrategy")] = "leastPing" }) +o:depends({ [option_name("balancingStrategy")] = "leastPing" }) local o = s:option(Value, option_name("probeUrl"), translate("Probe URL")) -add_xray_depends(o, { [option_name("useCustomProbeUrl")] = true }) -add_v2ray_depends(o, { [option_name("useCustomProbeUrl")] = true }) +o:depends({ [option_name("useCustomProbeUrl")] = true }) o.default = "https://www.google.com/generate_204" o.description = translate("The URL used to detect the connection status.") -- 探测间隔 local o = s:option(Value, option_name("probeInterval"), translate("Probe Interval")) -add_xray_depends(o, { [option_name("balancingStrategy")] = "leastPing" }) -add_v2ray_depends(o, { [option_name("balancingStrategy")] = "leastPing" }) +o:depends({ [option_name("balancingStrategy")] = "leastPing" }) o.default = "1m" o.description = translate("The interval between initiating probes. Every time this time elapses, a server status check is performed on a server. The time format is numbers + units, such as '10s', '2h45m', and the supported time units are ns, us, ms, s, m, h, which correspond to nanoseconds, microseconds, milliseconds, seconds, minutes, and hours, respectively.") @@ -163,11 +121,9 @@ o.description = translate("The interval between initiating probes. Every time th if #nodes_table > 0 then o = s:option(Flag, option_name("preproxy_enabled"), translate("Preproxy")) o:depends({ [option_name("protocol")] = "_shunt" }) - add_v2ray_depends(o, { [option_name("protocol")] = "_shunt" }) o = s:option(Value, option_name("main_node"), string.format('%s', translate("Preproxy Node")), translate("Set the node to be used as a pre-proxy. Each rule (including Default) has a separate switch that controls whether this rule uses the pre-proxy or not.")) o:depends({ [option_name("protocol")] = "_shunt", [option_name("preproxy_enabled")] = true }) - add_v2ray_depends(o, { [option_name("protocol")] = "_shunt", [option_name("preproxy_enabled")] = true }) for k, v in pairs(balancers_table) do o:value(v.id, v.remarks) end @@ -187,7 +143,6 @@ uci:foreach(appname, "shunt_rules", function(e) o:value("_direct", translate("Direct Connection")) o:value("_blackhole", translate("Blackhole")) o:depends({ [option_name("protocol")] = "_shunt" }) - add_v2ray_depends(o, { [option_name("protocol")] = "_shunt" }) if #nodes_table > 0 then for k, v in pairs(balancers_table) do @@ -203,7 +158,6 @@ uci:foreach(appname, "shunt_rules", function(e) for k, v in pairs(nodes_table) do o:value(v.id, v.remarks) pt:depends({ [option_name("protocol")] = "_shunt", [option_name("preproxy_enabled")] = true, [option_name(e[".name"])] = v.id }) - add_v2ray_depends(o, { [option_name("protocol")] = "_shunt", [option_name("preproxy_enabled")] = true, [option_name(e[".name"])] = v.id }) end end end @@ -258,134 +212,88 @@ o:depends({ [option_name("protocol")] = "_shunt" }) -- [[ 分流模块 End ]] o = s:option(Value, option_name("address"), translate("Address (Support Domain Name)")) -add_xray_depends(o, { [option_name("protocol")] = "vmess" }) -add_xray_depends(o, { [option_name("protocol")] = "vless" }) -add_xray_depends(o, { [option_name("protocol")] = "http" }) -add_xray_depends(o, { [option_name("protocol")] = "socks" }) -add_xray_depends(o, { [option_name("protocol")] = "shadowsocks" }) -add_xray_depends(o, { [option_name("protocol")] = "trojan" }) -add_xray_depends(o, { [option_name("protocol")] = "wireguard" }) -add_v2ray_depends(o, { [option_name("protocol")] = "vmess" }) -add_v2ray_depends(o, { [option_name("protocol")] = "vless" }) -add_v2ray_depends(o, { [option_name("protocol")] = "http" }) -add_v2ray_depends(o, { [option_name("protocol")] = "socks" }) -add_v2ray_depends(o, { [option_name("protocol")] = "shadowsocks" }) -add_v2ray_depends(o, { [option_name("protocol")] = "trojan" }) o = s:option(Value, option_name("port"), translate("Port")) o.datatype = "port" -add_xray_depends(o, { [option_name("protocol")] = "vmess" }) -add_xray_depends(o, { [option_name("protocol")] = "vless" }) -add_xray_depends(o, { [option_name("protocol")] = "http" }) -add_xray_depends(o, { [option_name("protocol")] = "socks" }) -add_xray_depends(o, { [option_name("protocol")] = "shadowsocks" }) -add_xray_depends(o, { [option_name("protocol")] = "trojan" }) -add_xray_depends(o, { [option_name("protocol")] = "wireguard" }) -add_v2ray_depends(o, { [option_name("protocol")] = "vmess" }) -add_v2ray_depends(o, { [option_name("protocol")] = "vless" }) -add_v2ray_depends(o, { [option_name("protocol")] = "http" }) -add_v2ray_depends(o, { [option_name("protocol")] = "socks" }) -add_v2ray_depends(o, { [option_name("protocol")] = "shadowsocks" }) -add_v2ray_depends(o, { [option_name("protocol")] = "trojan" }) + +local protocols = s.fields[option_name("protocol")].keylist +if #protocols > 0 then + for index, value in ipairs(protocols) do + if not value:find("_") then + s.fields[option_name("address")]:depends({ [option_name("protocol")] = value }) + s.fields[option_name("port")]:depends({ [option_name("protocol")] = value }) + end + end +end o = s:option(Value, option_name("username"), translate("Username")) -add_xray_depends(o, { [option_name("protocol")] = "http" }) -add_xray_depends(o, { [option_name("protocol")] = "socks" }) -add_v2ray_depends(o, { [option_name("protocol")] = "http" }) -add_v2ray_depends(o, { [option_name("protocol")] = "socks" }) +o:depends({ [option_name("protocol")] = "http" }) +o:depends({ [option_name("protocol")] = "socks" }) o = s:option(Value, option_name("password"), translate("Password")) o.password = true -add_xray_depends(o, { [option_name("protocol")] = "http" }) -add_xray_depends(o, { [option_name("protocol")] = "socks" }) -add_xray_depends(o, { [option_name("protocol")] = "shadowsocks" }) -add_xray_depends(o, { [option_name("protocol")] = "trojan" }) -add_v2ray_depends(o, { [option_name("protocol")] = "http" }) -add_v2ray_depends(o, { [option_name("protocol")] = "socks" }) -add_v2ray_depends(o, { [option_name("protocol")] = "shadowsocks" }) -add_v2ray_depends(o, { [option_name("protocol")] = "trojan" }) +o:depends({ [option_name("protocol")] = "http" }) +o:depends({ [option_name("protocol")] = "socks" }) +o:depends({ [option_name("protocol")] = "shadowsocks" }) +o:depends({ [option_name("protocol")] = "trojan" }) o = s:option(ListValue, option_name("security"), translate("Encrypt Method")) for a, t in ipairs(security_list) do o:value(t) end -add_xray_depends(o, { [option_name("protocol")] = "vmess" }) -add_v2ray_depends(o, { [option_name("protocol")] = "vmess" }) +o:depends({ [option_name("protocol")] = "vmess" }) o = s:option(Value, option_name("encryption"), translate("Encrypt Method")) o.default = "none" o:value("none") -add_xray_depends(o, { [option_name("protocol")] = "vless" }) -add_v2ray_depends(o, { [option_name("protocol")] = "vless" }) - -o = s:option(ListValue, "v_ss_encrypt_method", translate("Encrypt Method")) -o.not_rewrite = true -for a, t in ipairs(v_ss_encrypt_method_list) do o:value(t) end -add_v2ray_depends(o, { [option_name("protocol")] = "shadowsocks" }) -function o.cfgvalue(self, section) - return m:get(section, "method") -end -function o.write(self, section, value) - if s.fields["type"]:formvalue(arg[1]) == "Xray" or s.fields["type"]:formvalue(arg[1]) == "V2ray" then - m:set(section, "method", value) - end -end +o:depends({ [option_name("protocol")] = "vless" }) o = s:option(ListValue, option_name("x_ss_encrypt_method"), translate("Encrypt Method")) o.not_rewrite = true for a, t in ipairs(x_ss_encrypt_method_list) do o:value(t) end -add_xray_depends(o, { [option_name("protocol")] = "shadowsocks" }) +o:depends({ [option_name("protocol")] = "shadowsocks" }) function o.cfgvalue(self, section) return m:get(section, "method") end function o.write(self, section, value) - if s.fields["type"]:formvalue(arg[1]) == "Xray" or s.fields["type"]:formvalue(arg[1]) == "V2ray" then + if s.fields["type"]:formvalue(arg[1]) == type_name then m:set(section, "method", value) end end o = s:option(Flag, option_name("iv_check"), translate("IV Check")) -add_v2ray_depends(o, { [option_name("protocol")] = "shadowsocks" }) -add_xray_depends(o, { [option_name("protocol")] = "shadowsocks", [option_name("x_ss_encrypt_method")] = "aes-128-gcm" }) -add_xray_depends(o, { [option_name("protocol")] = "shadowsocks", [option_name("x_ss_encrypt_method")] = "aes-256-gcm" }) -add_xray_depends(o, { [option_name("protocol")] = "shadowsocks", [option_name("x_ss_encrypt_method")] = "chacha20-poly1305" }) -add_xray_depends(o, { [option_name("protocol")] = "shadowsocks", [option_name("x_ss_encrypt_method")] = "xchacha20-poly1305" }) +o:depends({ [option_name("protocol")] = "shadowsocks", [option_name("x_ss_encrypt_method")] = "aes-128-gcm" }) +o:depends({ [option_name("protocol")] = "shadowsocks", [option_name("x_ss_encrypt_method")] = "aes-256-gcm" }) +o:depends({ [option_name("protocol")] = "shadowsocks", [option_name("x_ss_encrypt_method")] = "chacha20-poly1305" }) +o:depends({ [option_name("protocol")] = "shadowsocks", [option_name("x_ss_encrypt_method")] = "xchacha20-poly1305" }) o = s:option(Flag, option_name("uot"), translate("UDP over TCP"), translate("Need Xray-core or sing-box as server side.")) -add_xray_depends(o, { [option_name("protocol")] = "shadowsocks", [option_name("x_ss_encrypt_method")] = "2022-blake3-aes-128-gcm" }) -add_xray_depends(o, { [option_name("protocol")] = "shadowsocks", [option_name("x_ss_encrypt_method")] = "2022-blake3-aes-256-gcm" }) -add_xray_depends(o, { [option_name("protocol")] = "shadowsocks", [option_name("x_ss_encrypt_method")] = "2022-blake3-chacha20-poly1305" }) +o:depends({ [option_name("protocol")] = "shadowsocks", [option_name("x_ss_encrypt_method")] = "2022-blake3-aes-128-gcm" }) +o:depends({ [option_name("protocol")] = "shadowsocks", [option_name("x_ss_encrypt_method")] = "2022-blake3-aes-256-gcm" }) +o:depends({ [option_name("protocol")] = "shadowsocks", [option_name("x_ss_encrypt_method")] = "2022-blake3-chacha20-poly1305" }) o = s:option(Value, option_name("uuid"), translate("ID")) o.password = true -add_xray_depends(o, { [option_name("protocol")] = "vmess" }) -add_xray_depends(o, { [option_name("protocol")] = "vless" }) -add_v2ray_depends(o, { [option_name("protocol")] = "vmess" }) -add_v2ray_depends(o, { [option_name("protocol")] = "vless" }) +o:depends({ [option_name("protocol")] = "vmess" }) +o:depends({ [option_name("protocol")] = "vless" }) -o = s:option(Flag, option_name("tls"), translate("TLS")) -o.default = 0 -add_xray_depends(o, { [option_name("protocol")] = "vmess" }) -add_xray_depends(o, { [option_name("protocol")] = "vless" }) -add_xray_depends(o, { [option_name("protocol")] = "socks" }) -add_xray_depends(o, { [option_name("protocol")] = "trojan" }) -add_xray_depends(o, { [option_name("protocol")] = "shadowsocks" }) -add_v2ray_depends(o, { [option_name("protocol")] = "vmess" }) -add_v2ray_depends(o, { [option_name("protocol")] = "vless" }) -add_v2ray_depends(o, { [option_name("protocol")] = "socks" }) -add_v2ray_depends(o, { [option_name("protocol")] = "trojan" }) -add_v2ray_depends(o, { [option_name("protocol")] = "shadowsocks" }) - -o = s:option(Value, option_name("tlsflow"), translate("flow")) +o = s:option(ListValue, option_name("flow"), translate("flow")) o.default = "" o:value("", translate("Disable")) o:value("xtls-rprx-vision") -o:value("xtls-rprx-vision-udp443") -add_xray_depends(o, { [option_name("protocol")] = "vless", [option_name("tls")] = true, [option_name("transport")] = "tcp" }) +o:depends({ [option_name("protocol")] = "vless" }) -o = s:option(Flag, option_name("reality"), translate("REALITY"), translate("Only recommend to use with VLESS-TCP-XTLS-Vision.")) +o = s:option(Flag, option_name("tls"), translate("TLS")) o.default = 0 -add_xray_depends(o, { [option_name("tls")] = true, [option_name("transport")] = "tcp" }) -add_xray_depends(o, { [option_name("tls")] = true, [option_name("transport")] = "h2" }) -add_xray_depends(o, { [option_name("tls")] = true, [option_name("transport")] = "grpc" }) +o:depends({ [option_name("protocol")] = "vmess" }) +o:depends({ [option_name("protocol")] = "vless" }) +o:depends({ [option_name("protocol")] = "socks" }) +o:depends({ [option_name("protocol")] = "trojan" }) +o:depends({ [option_name("protocol")] = "shadowsocks" }) + +o = s:option(Flag, option_name("reality"), translate("REALITY")) +o.default = 0 +o:depends({ [option_name("tls")] = true, [option_name("transport")] = "tcp" }) +o:depends({ [option_name("tls")] = true, [option_name("transport")] = "h2" }) +o:depends({ [option_name("tls")] = true, [option_name("transport")] = "grpc" }) o = s:option(ListValue, option_name("alpn"), translate("alpn")) o.default = "default" @@ -393,72 +301,49 @@ o:value("default", translate("Default")) o:value("h2,http/1.1") o:value("h2") o:value("http/1.1") -add_xray_depends(o, { [option_name("tls")] = true, [option_name("reality")] = false }) -add_v2ray_depends(o, { [option_name("tls")] = true }) +o:depends({ [option_name("tls")] = true, [option_name("reality")] = false }) -- o = s:option(Value, option_name("minversion"), translate("minversion")) -- o.default = "1.3" -- o:value("1.3") --- add_xray_depends(o, { [option_name("tls")] = true }) --- add_v2ray_depends(o, { [option_name("tls")] = true }) +-- o:depends({ [option_name("tls")] = true }) o = s:option(Value, option_name("tls_serverName"), translate("Domain")) -add_xray_depends(o, { [option_name("tls")] = true }) -add_v2ray_depends(o, { [option_name("tls")] = true }) +o:depends({ [option_name("tls")] = true }) o = s:option(Flag, option_name("tls_allowInsecure"), translate("allowInsecure"), translate("Whether unsafe connections are allowed. When checked, Certificate validation will be skipped.")) o.default = "0" -add_xray_depends(o, { [option_name("tls")] = true, [option_name("reality")] = false }) -add_v2ray_depends(o, { [option_name("tls")] = true }) - -o = s:option(Value, option_name("fingerprint"), translate("Finger Print"), translate("Avoid using randomized, unless you have to.")) -o:value("", translate("Disable")) -o:value("chrome") -o:value("firefox") -o:value("safari") -o:value("ios") --- o:value("android") -o:value("edge") --- o:value("360") -o:value("qq") -o:value("random") -o:value("randomized") -o.default = "" -add_xray_depends(o, { [option_name("tls")] = true, [option_name("reality")] = false }) +o:depends({ [option_name("tls")] = true, [option_name("reality")] = false }) -- [[ REALITY部分 ]] -- o = s:option(Value, option_name("reality_publicKey"), translate("Public Key")) -add_xray_depends(o, { [option_name("tls")] = true, [option_name("reality")] = true }) +o:depends({ [option_name("tls")] = true, [option_name("reality")] = true }) o = s:option(Value, option_name("reality_shortId"), translate("Short Id")) -add_xray_depends(o, { [option_name("tls")] = true, [option_name("reality")] = true }) +o:depends({ [option_name("tls")] = true, [option_name("reality")] = true }) o = s:option(Value, option_name("reality_spiderX"), translate("Spider X")) o.placeholder = "/" -add_xray_depends(o, { [option_name("tls")] = true, [option_name("reality")] = true }) +o:depends({ [option_name("tls")] = true, [option_name("reality")] = true }) -o = s:option(Value, option_name("reality_fingerprint"), translate("Finger Print"), translate("Avoid using randomized, unless you have to.")) -o.not_rewrite = true +o = s:option(Flag, option_name("utls"), translate("uTLS")) +o.default = "0" +o:depends({ [option_name("tls")] = true, [option_name("reality")] = false }) + +o = s:option(ListValue, option_name("fingerprint"), translate("Finger Print")) o:value("chrome") o:value("firefox") -o:value("safari") -o:value("ios") --- o:value("android") o:value("edge") --- o:value("360") +o:value("safari") +o:value("360") o:value("qq") +o:value("ios") +o:value("android") o:value("random") o:value("randomized") o.default = "chrome" -add_xray_depends(o, { [option_name("tls")] = true, [option_name("reality")] = true }) -function o.cfgvalue(self, section) - return m:get(section, "fingerprint") -end -function o.write(self, section, value) - if s.fields["type"]:formvalue(arg[1]) == "Xray" or s.fields["type"]:formvalue(arg[1]) == "V2ray" then - m:set(section, "fingerprint", value) - end -end +o:depends({ [option_name("tls")] = true, [option_name("utls")] = true }) +o:depends({ [option_name("tls")] = true, [option_name("reality")] = true }) o = s:option(ListValue, option_name("transport"), translate("Transport")) o:value("tcp", "TCP") @@ -468,50 +353,44 @@ o:value("h2", "HTTP/2") o:value("ds", "DomainSocket") o:value("quic", "QUIC") o:value("grpc", "gRPC") -add_xray_depends(o, { [option_name("protocol")] = "vmess" }) -add_xray_depends(o, { [option_name("protocol")] = "vless" }) -add_xray_depends(o, { [option_name("protocol")] = "socks" }) -add_xray_depends(o, { [option_name("protocol")] = "shadowsocks" }) -add_xray_depends(o, { [option_name("protocol")] = "trojan" }) -add_v2ray_depends(o, { [option_name("protocol")] = "vmess" }) -add_v2ray_depends(o, { [option_name("protocol")] = "vless" }) -add_v2ray_depends(o, { [option_name("protocol")] = "socks" }) -add_v2ray_depends(o, { [option_name("protocol")] = "shadowsocks" }) -add_v2ray_depends(o, { [option_name("protocol")] = "trojan" }) +o:depends({ [option_name("protocol")] = "vmess" }) +o:depends({ [option_name("protocol")] = "vless" }) +o:depends({ [option_name("protocol")] = "socks" }) +o:depends({ [option_name("protocol")] = "shadowsocks" }) +o:depends({ [option_name("protocol")] = "trojan" }) --[[ o = s:option(ListValue, option_name("ss_transport"), translate("Transport")) o:value("ws", "WebSocket") o:value("h2", "HTTP/2") o:value("h2+ws", "HTTP/2 & WebSocket") -add_xray_depends(o, { [option_name("protocol")] = "shadowsocks" }) -add_v2ray_depends(o, { [option_name("protocol")] = "shadowsocks" }) +o:depends({ [option_name("protocol")] = "shadowsocks" }) ]]-- o = s:option(Value, option_name("wireguard_public_key"), translate("Public Key")) -add_xray_depends(o, { [option_name("protocol")] = "wireguard" }) +o:depends({ [option_name("protocol")] = "wireguard" }) o = s:option(Value, option_name("wireguard_secret_key"), translate("Private Key")) -add_xray_depends(o, { [option_name("protocol")] = "wireguard" }) +o:depends({ [option_name("protocol")] = "wireguard" }) o = s:option(Value, option_name("wireguard_preSharedKey"), translate("Pre shared key")) -add_xray_depends(o, { [option_name("protocol")] = "wireguard" }) +o:depends({ [option_name("protocol")] = "wireguard" }) o = s:option(DynamicList, option_name("wireguard_local_address"), translate("Local Address")) -add_xray_depends(o, { [option_name("protocol")] = "wireguard" }) +o:depends({ [option_name("protocol")] = "wireguard" }) o = s:option(Value, option_name("wireguard_mtu"), translate("MTU")) o.default = "1420" -add_xray_depends(o, { [option_name("protocol")] = "wireguard" }) +o:depends({ [option_name("protocol")] = "wireguard" }) if api.compare_versions(api.get_app_version("xray"), ">=", "1.8.0") then o = s:option(Value, option_name("wireguard_reserved"), translate("Reserved"), translate("Decimal numbers separated by \",\" or Base64-encoded strings.")) - add_xray_depends(o, { [option_name("protocol")] = "wireguard" }) + o:depends({ [option_name("protocol")] = "wireguard" }) end o = s:option(Value, option_name("wireguard_keepAlive"), translate("Keep Alive")) o.default = "0" -add_xray_depends(o, { [option_name("protocol")] = "wireguard" }) +o:depends({ [option_name("protocol")] = "wireguard" }) -- [[ TCP部分 ]]-- @@ -519,194 +398,152 @@ add_xray_depends(o, { [option_name("protocol")] = "wireguard" }) o = s:option(ListValue, option_name("tcp_guise"), translate("Camouflage Type")) o:value("none", "none") o:value("http", "http") -add_xray_depends(o, { [option_name("transport")] = "tcp" }) -add_v2ray_depends(o, { [option_name("transport")] = "tcp" }) +o:depends({ [option_name("transport")] = "tcp" }) -- HTTP域名 o = s:option(DynamicList, option_name("tcp_guise_http_host"), translate("HTTP Host")) -add_xray_depends(o, { [option_name("tcp_guise")] = "http" }) -add_v2ray_depends(o, { [option_name("tcp_guise")] = "http" }) +o:depends({ [option_name("tcp_guise")] = "http" }) -- HTTP路径 o = s:option(DynamicList, option_name("tcp_guise_http_path"), translate("HTTP Path")) o.placeholder = "/" -add_xray_depends(o, { [option_name("tcp_guise")] = "http" }) -add_v2ray_depends(o, { [option_name("tcp_guise")] = "http" }) +o:depends({ [option_name("tcp_guise")] = "http" }) -- [[ mKCP部分 ]]-- o = s:option(ListValue, option_name("mkcp_guise"), translate("Camouflage Type"), translate('
none: default, no masquerade, data sent is packets with no characteristics.
srtp: disguised as an SRTP packet, it will be recognized as video call data (such as FaceTime).
utp: packets disguised as uTP will be recognized as bittorrent downloaded data.
wechat-video: packets disguised as WeChat video calls.
dtls: disguised as DTLS 1.2 packet.
wireguard: disguised as a WireGuard packet. (not really WireGuard protocol)')) for a, t in ipairs(header_type_list) do o:value(t) end -add_xray_depends(o, { [option_name("transport")] = "mkcp" }) -add_v2ray_depends(o, { [option_name("transport")] = "mkcp" }) +o:depends({ [option_name("transport")] = "mkcp" }) o = s:option(Value, option_name("mkcp_mtu"), translate("KCP MTU")) o.default = "1350" -add_xray_depends(o, { [option_name("transport")] = "mkcp" }) -add_v2ray_depends(o, { [option_name("transport")] = "mkcp" }) +o:depends({ [option_name("transport")] = "mkcp" }) o = s:option(Value, option_name("mkcp_tti"), translate("KCP TTI")) o.default = "20" -add_xray_depends(o, { [option_name("transport")] = "mkcp" }) -add_v2ray_depends(o, { [option_name("transport")] = "mkcp" }) +o:depends({ [option_name("transport")] = "mkcp" }) o = s:option(Value, option_name("mkcp_uplinkCapacity"), translate("KCP uplinkCapacity")) o.default = "5" -add_xray_depends(o, { [option_name("transport")] = "mkcp" }) -add_v2ray_depends(o, { [option_name("transport")] = "mkcp" }) +o:depends({ [option_name("transport")] = "mkcp" }) o = s:option(Value, option_name("mkcp_downlinkCapacity"), translate("KCP downlinkCapacity")) o.default = "20" -add_xray_depends(o, { [option_name("transport")] = "mkcp" }) -add_v2ray_depends(o, { [option_name("transport")] = "mkcp" }) +o:depends({ [option_name("transport")] = "mkcp" }) o = s:option(Flag, option_name("mkcp_congestion"), translate("KCP Congestion")) -add_xray_depends(o, { [option_name("transport")] = "mkcp" }) -add_v2ray_depends(o, { [option_name("transport")] = "mkcp" }) +o:depends({ [option_name("transport")] = "mkcp" }) o = s:option(Value, option_name("mkcp_readBufferSize"), translate("KCP readBufferSize")) o.default = "1" -add_xray_depends(o, { [option_name("transport")] = "mkcp" }) -add_v2ray_depends(o, { [option_name("transport")] = "mkcp" }) +o:depends({ [option_name("transport")] = "mkcp" }) o = s:option(Value, option_name("mkcp_writeBufferSize"), translate("KCP writeBufferSize")) o.default = "1" -add_xray_depends(o, { [option_name("transport")] = "mkcp" }) -add_v2ray_depends(o, { [option_name("transport")] = "mkcp" }) +o:depends({ [option_name("transport")] = "mkcp" }) o = s:option(Value, option_name("mkcp_seed"), translate("KCP Seed")) -add_xray_depends(o, { [option_name("transport")] = "mkcp" }) -add_v2ray_depends(o, { [option_name("transport")] = "mkcp" }) +o:depends({ [option_name("transport")] = "mkcp" }) -- [[ WebSocket部分 ]]-- o = s:option(Value, option_name("ws_host"), translate("WebSocket Host")) -add_xray_depends(o, { [option_name("transport")] = "ws" }) -add_xray_depends(o, { [option_name("ss_transport")] = "ws" }) -add_v2ray_depends(o, { [option_name("transport")] = "ws" }) -add_v2ray_depends(o, { [option_name("ss_transport")] = "ws" }) +o:depends({ [option_name("transport")] = "ws" }) +o:depends({ [option_name("ss_transport")] = "ws" }) o = s:option(Value, option_name("ws_path"), translate("WebSocket Path")) o.placeholder = "/" -add_xray_depends(o, { [option_name("transport")] = "ws" }) -add_xray_depends(o, { [option_name("ss_transport")] = "ws" }) -add_v2ray_depends(o, { [option_name("transport")] = "ws" }) -add_v2ray_depends(o, { [option_name("ss_transport")] = "ws" }) - -o = s:option(Flag, "v2ray_ws_enableEarlyData", translate("Enable early data")) -add_v2ray_depends(o, { [option_name("transport")] = "ws" }) - -o = s:option(Value, "v2ray_ws_maxEarlyData", translate("Early data length")) -o.default = "1024" -add_v2ray_depends(o, { v2ray_ws_enableEarlyData = true }) - -o = s:option(Value, "v2ray_ws_earlyDataHeaderName", translate("Early data header name"), translate("Recommended value: Sec-WebSocket-Protocol")) -add_v2ray_depends(o, { v2ray_ws_enableEarlyData = true }) +o:depends({ [option_name("transport")] = "ws" }) +o:depends({ [option_name("ss_transport")] = "ws" }) -- [[ HTTP/2部分 ]]-- o = s:option(Value, option_name("h2_host"), translate("HTTP/2 Host")) -add_xray_depends(o, { [option_name("transport")] = "h2" }) -add_xray_depends(o, { [option_name("ss_transport")] = "h2" }) -add_v2ray_depends(o, { [option_name("transport")] = "h2" }) -add_v2ray_depends(o, { [option_name("ss_transport")] = "h2" }) +o:depends({ [option_name("transport")] = "h2" }) +o:depends({ [option_name("ss_transport")] = "h2" }) o = s:option(Value, option_name("h2_path"), translate("HTTP/2 Path")) o.placeholder = "/" -add_xray_depends(o, { [option_name("transport")] = "h2" }) -add_xray_depends(o, { [option_name("ss_transport")] = "h2" }) -add_v2ray_depends(o, { [option_name("transport")] = "h2" }) -add_v2ray_depends(o, { [option_name("ss_transport")] = "h2" }) +o:depends({ [option_name("transport")] = "h2" }) +o:depends({ [option_name("ss_transport")] = "h2" }) o = s:option(Flag, option_name("h2_health_check"), translate("Health check")) -add_xray_depends(o, { [option_name("transport")] = "h2" }) +o:depends({ [option_name("transport")] = "h2" }) o = s:option(Value, option_name("h2_read_idle_timeout"), translate("Idle timeout")) o.default = "10" -add_xray_depends(o, { [option_name("h2_health_check")] = true }) +o:depends({ [option_name("h2_health_check")] = true }) o = s:option(Value, option_name("h2_health_check_timeout"), translate("Health check timeout")) o.default = "15" -add_xray_depends(o, { [option_name("h2_health_check")] = true }) +o:depends({ [option_name("h2_health_check")] = true }) -- [[ DomainSocket部分 ]]-- o = s:option(Value, option_name("ds_path"), "Path", translate("A legal file path. This file must not exist before running.")) -add_xray_depends(o, { [option_name("transport")] = "ds" }) -add_v2ray_depends(o, { [option_name("transport")] = "ds" }) +o:depends({ [option_name("transport")] = "ds" }) -- [[ QUIC部分 ]]-- o = s:option(ListValue, option_name("quic_security"), translate("Encrypt Method")) o:value("none") o:value("aes-128-gcm") o:value("chacha20-poly1305") -add_xray_depends(o, { [option_name("transport")] = "quic" }) -add_v2ray_depends(o, { [option_name("transport")] = "quic" }) +o:depends({ [option_name("transport")] = "quic" }) o = s:option(Value, option_name("quic_key"), translate("Encrypt Method") .. translate("Key")) -add_xray_depends(o, { [option_name("transport")] = "quic" }) -add_v2ray_depends(o, { [option_name("transport")] = "quic" }) +o:depends({ [option_name("transport")] = "quic" }) o = s:option(ListValue, option_name("quic_guise"), translate("Camouflage Type")) for a, t in ipairs(header_type_list) do o:value(t) end -add_xray_depends(o, { [option_name("transport")] = "quic" }) -add_v2ray_depends(o, { [option_name("transport")] = "quic" }) +o:depends({ [option_name("transport")] = "quic" }) -- [[ gRPC部分 ]]-- o = s:option(Value, option_name("grpc_serviceName"), "ServiceName") -add_xray_depends(o, { [option_name("transport")] = "grpc" }) -add_v2ray_depends(o, { [option_name("transport")] = "grpc" }) +o:depends({ [option_name("transport")] = "grpc" }) o = s:option(ListValue, option_name("grpc_mode"), "gRPC " .. translate("Transfer mode")) o:value("gun") o:value("multi") -add_xray_depends(o, { [option_name("transport")] = "grpc" }) +o:depends({ [option_name("transport")] = "grpc" }) o = s:option(Flag, option_name("grpc_health_check"), translate("Health check")) -add_xray_depends(o, { [option_name("transport")] = "grpc" }) +o:depends({ [option_name("transport")] = "grpc" }) o = s:option(Value, option_name("grpc_idle_timeout"), translate("Idle timeout")) o.default = "10" -add_xray_depends(o, { [option_name("grpc_health_check")] = true }) +o:depends({ [option_name("grpc_health_check")] = true }) o = s:option(Value, option_name("grpc_health_check_timeout"), translate("Health check timeout")) o.default = "20" -add_xray_depends(o, { [option_name("grpc_health_check")] = true }) +o:depends({ [option_name("grpc_health_check")] = true }) o = s:option(Flag, option_name("grpc_permit_without_stream"), translate("Permit without stream")) o.default = "0" -add_xray_depends(o, { [option_name("grpc_health_check")] = true }) +o:depends({ [option_name("grpc_health_check")] = true }) o = s:option(Value, option_name("grpc_initial_windows_size"), translate("Initial Windows Size")) o.default = "0" -add_xray_depends(o, { [option_name("transport")] = "grpc" }) +o:depends({ [option_name("transport")] = "grpc" }) -- [[ Mux ]]-- o = s:option(Flag, option_name("mux"), translate("Mux")) -add_v2ray_depends(o, { [option_name("protocol")] = "vmess" }) -add_v2ray_depends(o, { [option_name("protocol")] = "vless" }) -add_v2ray_depends(o, { [option_name("protocol")] = "http" }) -add_v2ray_depends(o, { [option_name("protocol")] = "socks" }) -add_v2ray_depends(o, { [option_name("protocol")] = "shadowsocks" }) -add_v2ray_depends(o, { [option_name("protocol")] = "trojan" }) -add_xray_depends(o, { [option_name("protocol")] = "vmess" }) -add_xray_depends(o, { [option_name("protocol")] = "vless", [option_name("tlsflow")] = "" }) -add_xray_depends(o, { [option_name("protocol")] = "http" }) -add_xray_depends(o, { [option_name("protocol")] = "socks" }) -add_xray_depends(o, { [option_name("protocol")] = "shadowsocks" }) -add_xray_depends(o, { [option_name("protocol")] = "trojan" }) +o:depends({ [option_name("protocol")] = "vmess" }) +o:depends({ [option_name("protocol")] = "vless", [option_name("flow")] = "" }) +o:depends({ [option_name("protocol")] = "http" }) +o:depends({ [option_name("protocol")] = "socks" }) +o:depends({ [option_name("protocol")] = "shadowsocks" }) +o:depends({ [option_name("protocol")] = "trojan" }) o = s:option(Value, option_name("mux_concurrency"), translate("Mux concurrency")) o.default = 8 -add_xray_depends(o, { [option_name("mux")] = true }) -add_v2ray_depends(o, { [option_name("mux")] = true }) +o:depends({ [option_name("mux")] = true }) -- [[ XUDP Mux ]]-- o = s:option(Flag, option_name("xmux"), translate("xMux")) o.default = 1 -add_xray_depends(o, { [option_name("protocol")] = "vless", [option_name("tlsflow")] = "xtls-rprx-vision" }) -add_xray_depends(o, { [option_name("protocol")] = "vless", [option_name("tlsflow")] = "xtls-rprx-vision-udp443" }) +o:depends({ [option_name("protocol")] = "vless", [option_name("flow")] = "xtls-rprx-vision" }) +o:depends({ [option_name("protocol")] = "vless", [option_name("flow")] = "xtls-rprx-vision-udp443" }) o = s:option(Value, option_name("xudp_concurrency"), translate("XUDP Mux concurrency")) o.default = 8 -add_xray_depends(o, { [option_name("xmux")] = true }) +o:depends({ [option_name("xmux")] = true }) for key, value in pairs(s.fields) do if key:find(option_prefix) == 1 then @@ -715,5 +552,14 @@ for key, value in pairs(s.fields) do s.fields[key].write = rm_prefix_write s.fields[key].remove = rm_prefix_remove end + + local deps = s.fields[key].deps + if #deps > 0 then + for index, value in ipairs(deps) do + deps[index]["type"] = type_name + end + else + s.fields[key]:depends({ type = type_name }) + end end end diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/sing-box.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/sing-box.lua new file mode 100644 index 000000000..d460a639f --- /dev/null +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/sing-box.lua @@ -0,0 +1,567 @@ +local m, s = ... + +local api = require "luci.passwall2.api" + +if not api.is_finded("sing-box") then + return +end + +local singbox_tags = luci.sys.exec(api.finded("sing-box") .. " version | grep 'Tags:' | awk '{print $2}'") + +local appname = api.appname +local uci = api.uci + +local type_name = "sing-box" + +local option_prefix = "singbox_" + +local function option_name(name) + return option_prefix .. name +end + +local function rm_prefix_cfgvalue(self, section) + if self.option:find(option_prefix) == 1 then + return m:get(section, self.option:sub(1 + #option_prefix)) + end +end +local function rm_prefix_write(self, section, value) + if s.fields["type"]:formvalue(arg[1]) == type_name then + if self.option:find(option_prefix) == 1 then + m:set(section, self.option:sub(1 + #option_prefix), value) + end + end +end +local function rm_prefix_remove(self, section, value) + if s.fields["type"]:formvalue(arg[1]) == type_name then + if self.option:find(option_prefix) == 1 then + m:del(section, self.option:sub(1 + #option_prefix)) + end + end +end + +local ss_method_new_list = { + "none", "aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "chacha20-ietf-poly1305", "xchacha20-ietf-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305" +} + +local ss_method_old_list = { + "aes-128-ctr", "aes-192-ctr", "aes-256-ctr", "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", "rc4-md5", "chacha20-ietf", "xchacha20", +} + +local security_list = { "none", "auto", "aes-128-gcm", "chacha20-poly1305", "zero" } + +-- [[ sing-box ]] + +s.fields["type"]:value(type_name, translate("Sing-Box")) + +o = s:option(ListValue, option_name("protocol"), translate("Protocol")) +o:value("socks", "Socks") +o:value("http", "HTTP") +o:value("shadowsocks", "Shadowsocks") +if singbox_tags:find("with_shadowsocksr") then + o:value("shadowsocksr", "ShadowsocksR") +end +o:value("vmess", "Vmess") +o:value("trojan", "Trojan") +if singbox_tags:find("with_wireguard") then + o:value("wireguard", "WireGuard") +end +if singbox_tags:find("with_quic") then + o:value("hysteria", "Hysteria") +end +o:value("shadowtls", "ShadowTLS") +o:value("vless", "VLESS") +if singbox_tags:find("with_quic") then + o:value("tuic", "TUIC") +end +if singbox_tags:find("with_quic") then + o:value("hysteria2", "Hysteria2") +end +o:value("_shunt", translate("Shunt")) +o:value("_iface", translate("Custom Interface") .. " (Only Support Xray)") + +o = s:option(Value, option_name("iface"), translate("Interface")) +o.default = "eth1" +o:depends({ [option_name("protocol")] = "_iface" }) + +local nodes_table = {} +local balancers_table = {} +local iface_table = {} +for k, e in ipairs(api.get_valid_nodes()) do + if e.node_type == "normal" then + nodes_table[#nodes_table + 1] = { + id = e[".name"], + remarks = e["remark"] + } + end + if e.protocol == "_iface" then + iface_table[#iface_table + 1] = { + id = e[".name"], + remarks = e["remark"] + } + end +end + +-- [[ 分流模块 ]] +if #nodes_table > 0 then + o = s:option(Flag, option_name("preproxy_enabled"), translate("Preproxy")) + o:depends({ [option_name("protocol")] = "_shunt" }) + + o = s:option(Value, option_name("main_node"), string.format('%s', translate("Preproxy Node")), translate("Set the node to be used as a pre-proxy. Each rule (including Default) has a separate switch that controls whether this rule uses the pre-proxy or not.")) + o:depends({ [option_name("protocol")] = "_shunt", [option_name("preproxy_enabled")] = true }) + for k, v in pairs(balancers_table) do + o:value(v.id, v.remarks) + end + for k, v in pairs(iface_table) do + o:value(v.id, v.remarks) + end + for k, v in pairs(nodes_table) do + o:value(v.id, v.remarks) + end + o.default = "nil" +end +uci:foreach(appname, "shunt_rules", function(e) + if e[".name"] and e.remarks then + o = s:option(Value, option_name(e[".name"]), string.format('* %s', api.url("shunt_rules", e[".name"]), e.remarks)) + o:value("nil", translate("Close")) + o:value("_default", translate("Default")) + o:value("_direct", translate("Direct Connection")) + o:value("_blackhole", translate("Blackhole")) + o:depends({ [option_name("protocol")] = "_shunt" }) + + if #nodes_table > 0 then + for k, v in pairs(balancers_table) do + o:value(v.id, v.remarks) + end + for k, v in pairs(iface_table) do + o:value(v.id, v.remarks) + end + local pt = s:option(ListValue, option_name(e[".name"] .. "_proxy_tag"), string.format('* %s', e.remarks .. " " .. translate("Preproxy"))) + pt:value("nil", translate("Close")) + pt:value("main", translate("Preproxy Node")) + pt.default = "nil" + for k, v in pairs(nodes_table) do + o:value(v.id, v.remarks) + pt:depends({ [option_name("protocol")] = "_shunt", [option_name("preproxy_enabled")] = true, [option_name(e[".name"])] = v.id }) + end + end + end +end) + +o = s:option(DummyValue, option_name("shunt_tips"), " ") +o.not_rewrite = true +o.rawhtml = true +o.cfgvalue = function(t, n) + return string.format('%s', translate("No shunt rules? Click me to go to add.")) +end +o:depends({ [option_name("protocol")] = "_shunt" }) + +local o = s:option(Value, option_name("default_node"), string.format('* %s', translate("Default"))) +o:depends({ [option_name("protocol")] = "_shunt" }) +o:value("_direct", translate("Direct Connection")) +o:value("_blackhole", translate("Blackhole")) + +if #nodes_table > 0 then + for k, v in pairs(balancers_table) do + o:value(v.id, v.remarks) + end + for k, v in pairs(iface_table) do + o:value(v.id, v.remarks) + end + local dpt = s:option(ListValue, option_name("default_proxy_tag"), string.format('* %s', translate("Default Preproxy")), translate("When using, localhost will connect this node first and then use this node to connect the default node.")) + dpt:value("nil", translate("Close")) + dpt:value("main", translate("Preproxy Node")) + dpt.default = "nil" + for k, v in pairs(nodes_table) do + o:value(v.id, v.remarks) + dpt:depends({ [option_name("protocol")] = "_shunt", [option_name("preproxy_enabled")] = true, [option_name("default_node")] = v.id }) + end +end + +-- [[ 分流模块 End ]] + +o = s:option(Value, option_name("address"), translate("Address (Support Domain Name)")) + +o = s:option(Value, option_name("port"), translate("Port")) +o.datatype = "port" + +local protocols = s.fields[option_name("protocol")].keylist +if #protocols > 0 then + for index, value in ipairs(protocols) do + if not value:find("_") then + s.fields[option_name("address")]:depends({ [option_name("protocol")] = value }) + s.fields[option_name("port")]:depends({ [option_name("protocol")] = value }) + end + end +end + +o = s:option(ListValue, option_name("shadowtls_version"), translate("Version")) +o.default = "1" +o:value("1", "ShadowTLS v1") +o:value("2", "ShadowTLS v2") +o:value("3", "ShadowTLS v3") +o:depends({ [option_name("protocol")] = "shadowtls" }) + +o = s:option(Value, option_name("username"), translate("Username")) +o:depends({ [option_name("protocol")] = "http" }) +o:depends({ [option_name("protocol")] = "socks" }) + +o = s:option(Value, option_name("password"), translate("Password")) +o.password = true +o:depends({ [option_name("protocol")] = "http" }) +o:depends({ [option_name("protocol")] = "socks" }) +o:depends({ [option_name("protocol")] = "shadowsocks" }) +o:depends({ [option_name("protocol")] = "shadowsocksr" }) +o:depends({ [option_name("protocol")] = "trojan" }) +o:depends({ [option_name("protocol")] = "shadowtls", [option_name("shadowtls_version")] = "2" }) +o:depends({ [option_name("protocol")] = "shadowtls", [option_name("shadowtls_version")] = "3" }) +o:depends({ [option_name("protocol")] = "tuic" }) + +o = s:option(ListValue, option_name("security"), translate("Encrypt Method")) +for a, t in ipairs(security_list) do o:value(t) end +o:depends({ [option_name("protocol")] = "vmess" }) + +o = s:option(ListValue, option_name("ss_method"), translate("Encrypt Method")) +o.not_rewrite = true +for a, t in ipairs(ss_method_new_list) do o:value(t) end +for a, t in ipairs(ss_method_old_list) do o:value(t) end +o:depends({ [option_name("protocol")] = "shadowsocks" }) +function o.cfgvalue(self, section) + return m:get(section, "method") +end +function o.write(self, section, value) + if s.fields["type"]:formvalue(arg[1]) == type_name then + m:set(section, "method", value) + end +end + +if singbox_tags:find("with_shadowsocksr") then + o = s:option(ListValue, option_name("ssr_method"), translate("Encrypt Method")) + o.not_rewrite = true + for a, t in ipairs(ss_method_old_list) do o:value(t) end + o:depends({ [option_name("protocol")] = "shadowsocksr" }) + function o.cfgvalue(self, section) + return m:get(section, "method") + end + function o.write(self, section, value) + if s.fields["type"]:formvalue(arg[1]) == type_name then + m:set(section, "method", value) + end + end + + local ssr_protocol_list = { + "origin", "verify_simple", "verify_deflate", "verify_sha1", "auth_simple", + "auth_sha1", "auth_sha1_v2", "auth_sha1_v4", "auth_aes128_md5", + "auth_aes128_sha1", "auth_chain_a", "auth_chain_b", "auth_chain_c", + "auth_chain_d", "auth_chain_e", "auth_chain_f" + } + + o = s:option(ListValue, option_name("ssr_protocol"), translate("Protocol")) + for a, t in ipairs(ssr_protocol_list) do o:value(t) end + o:depends({ [option_name("protocol")] = "shadowsocksr" }) + + o = s:option(Value, option_name("ssr_protocol_param"), translate("Protocol_param")) + o:depends({ [option_name("protocol")] = "shadowsocksr" }) + + local ssr_obfs_list = { + "plain", "http_simple", "http_post", "random_head", "tls_simple", + "tls1.0_session_auth", "tls1.2_ticket_auth" + } + + o = s:option(ListValue, option_name("ssr_obfs"), translate("Obfs")) + for a, t in ipairs(ssr_obfs_list) do o:value(t) end + o:depends({ [option_name("protocol")] = "shadowsocksr" }) + + o = s:option(Value, option_name("ssr_obfs_param"), translate("Obfs_param")) + o:depends({ [option_name("protocol")] = "shadowsocksr" }) +end + +o = s:option(Flag, option_name("uot"), translate("UDP over TCP"), translate("Need Xray-core or sing-box as server side.")) +o:depends({ [option_name("protocol")] = "shadowsocks", [option_name("ss_method")] = "2022-blake3-aes-128-gcm" }) +o:depends({ [option_name("protocol")] = "shadowsocks", [option_name("ss_method")] = "2022-blake3-aes-256-gcm" }) +o:depends({ [option_name("protocol")] = "shadowsocks", [option_name("ss_method")] = "2022-blake3-chacha20-poly1305" }) + +o = s:option(Value, option_name("uuid"), translate("ID")) +o.password = true +o:depends({ [option_name("protocol")] = "vmess" }) +o:depends({ [option_name("protocol")] = "vless" }) +o:depends({ [option_name("protocol")] = "tuic" }) + +o = s:option(ListValue, option_name("flow"), translate("flow")) +o.default = "" +o:value("", translate("Disable")) +o:value("xtls-rprx-vision") +o:depends({ [option_name("protocol")] = "vless", [option_name("tls")] = true }) + +if singbox_tags:find("with_quic") then + o = s:option(Value, option_name("hysteria_obfs"), translate("Obfs Password")) + o:depends({ [option_name("protocol")] = "hysteria" }) + + o = s:option(ListValue, option_name("hysteria_auth_type"), translate("Auth Type")) + o:value("disable", translate("Disable")) + o:value("string", translate("STRING")) + o:value("base64", translate("BASE64")) + o:depends({ [option_name("protocol")] = "hysteria" }) + + o = s:option(Value, option_name("hysteria_auth_password"), translate("Auth Password")) + o.password = true + o:depends({ [option_name("protocol")] = "hysteria", [option_name("hysteria_auth_type")] = "string"}) + o:depends({ [option_name("protocol")] = "hysteria", [option_name("hysteria_auth_type")] = "base64"}) + + o = s:option(Value, option_name("hysteria_up_mbps"), translate("Max upload Mbps")) + o.default = "10" + o:depends({ [option_name("protocol")] = "hysteria" }) + + o = s:option(Value, option_name("hysteria_down_mbps"), translate("Max download Mbps")) + o.default = "50" + o:depends({ [option_name("protocol")] = "hysteria" }) + + o = s:option(Value, option_name("hysteria_recv_window_conn"), translate("QUIC stream receive window")) + o:depends({ [option_name("protocol")] = "hysteria" }) + + o = s:option(Value, option_name("hysteria_recv_window"), translate("QUIC connection receive window")) + o:depends({ [option_name("protocol")] = "hysteria" }) + + o = s:option(Flag, option_name("hysteria_disable_mtu_discovery"), translate("Disable MTU detection")) + o:depends({ [option_name("protocol")] = "hysteria" }) +end + +if singbox_tags:find("with_quic") then + o = s:option(ListValue, option_name("tuic_congestion_control"), translate("Congestion control algorithm")) + o.default = "cubic" + o:value("bbr", translate("BBR")) + o:value("cubic", translate("CUBIC")) + o:value("new_reno", translate("New Reno")) + o:depends({ [option_name("protocol")] = "tuic" }) + + o = s:option(ListValue, option_name("tuic_udp_relay_mode"), translate("UDP relay mode")) + o.default = "native" + o:value("native", translate("native")) + o:value("quic", translate("QUIC")) + o:depends({ [option_name("protocol")] = "tuic" }) + + --[[ + o = s:option(Flag, option_name("tuic_udp_over_stream"), translate("UDP over stream")) + o:depends({ [option_name("protocol")] = "tuic" }) + ]]-- + + o = s:option(Flag, option_name("tuic_zero_rtt_handshake"), translate("Enable 0-RTT QUIC handshake")) + o.default = 0 + o:depends({ [option_name("protocol")] = "tuic" }) + + o = s:option(Value, option_name("tuic_heartbeat"), translate("Heartbeat interval(second)")) + o.datatype = "uinteger" + o.default = "3" + o:depends({ [option_name("protocol")] = "tuic" }) +end + +if singbox_tags:find("with_quic") then + o = s:option(Value, option_name("hysteria2_up_mbps"), translate("Max upload Mbps")) + o:depends({ [option_name("protocol")] = "hysteria2" }) + + o = s:option(Value, option_name("hysteria2_down_mbps"), translate("Max download Mbps")) + o:depends({ [option_name("protocol")] = "hysteria2" }) + + o = s:option(ListValue, option_name("hysteria2_obfs_type"), translate("Obfs Type")) + o:value("", translate("Disable")) + o:value("salamander") + o:depends({ [option_name("protocol")] = "hysteria2" }) + + o = s:option(Value, option_name("hysteria2_obfs_password"), translate("Obfs Password")) + o:depends({ [option_name("protocol")] = "hysteria2" }) + + o = s:option(Value, option_name("hysteria2_auth_password"), translate("Auth Password")) + o.password = true + o:depends({ [option_name("protocol")] = "hysteria2"}) +end + +o = s:option(Flag, option_name("tls"), translate("TLS")) +o.default = 0 +o:depends({ [option_name("protocol")] = "vmess" }) +o:depends({ [option_name("protocol")] = "vless" }) +o:depends({ [option_name("protocol")] = "socks" }) +o:depends({ [option_name("protocol")] = "trojan" }) +o:depends({ [option_name("protocol")] = "shadowsocks" }) +o:depends({ [option_name("protocol")] = "shadowtls" }) + +if singbox_tags:find("with_reality") then + o = s:option(Flag, option_name("reality"), translate("REALITY")) + o.default = 0 + o:depends({ [option_name("protocol")] = "vless", [option_name("tls")] = true }) + + -- [[ REALITY部分 ]] -- + o = s:option(Value, option_name("reality_publicKey"), translate("Public Key")) + o:depends({ [option_name("tls")] = true, [option_name("reality")] = true }) + + o = s:option(Value, option_name("reality_shortId"), translate("Short Id")) + o:depends({ [option_name("tls")] = true, [option_name("reality")] = true }) +end + +o = s:option(ListValue, option_name("alpn"), translate("alpn")) +o.default = "default" +o:value("default", translate("Default")) +o:value("h2,http/1.1") +o:value("h2") +o:value("http/1.1") +o:depends({ [option_name("tls")] = true }) + +o = s:option(Value, option_name("tls_serverName"), translate("Domain")) +o:depends({ [option_name("tls")] = true }) + +o = s:option(Flag, option_name("tls_allowInsecure"), translate("allowInsecure"), translate("Whether unsafe connections are allowed. When checked, Certificate validation will be skipped.")) +o.default = "0" +o:depends({ [option_name("tls")] = true }) + +if singbox_tags:find("with_utls") then + o = s:option(Flag, option_name("utls"), translate("uTLS")) + o.default = "0" + o:depends({ [option_name("tls")] = true, [option_name("reality")] = false }) + + o = s:option(ListValue, option_name("fingerprint"), translate("Finger Print")) + o:value("chrome") + o:value("firefox") + o:value("edge") + o:value("safari") + o:value("360") + o:value("qq") + o:value("ios") + o:value("android") + o:value("random") + o:value("randomized") + o.default = "chrome" + o:depends({ [option_name("tls")] = true, [option_name("utls")] = true }) + o:depends({ [option_name("tls")] = true, [option_name("reality")] = true }) +end + +o = s:option(ListValue, option_name("transport"), translate("Transport")) +o:value("tcp", "TCP") +o:value("http", "HTTP") +o:value("ws", "WebSocket") +if singbox_tags:find("with_quic") then + o:value("quic", "QUIC") +end +if singbox_tags:find("with_grpc") then + o:value("grpc", "gRPC") +end +o:depends({ [option_name("protocol")] = "vmess" }) +o:depends({ [option_name("protocol")] = "vless" }) +o:depends({ [option_name("protocol")] = "socks" }) +o:depends({ [option_name("protocol")] = "shadowsocks" }) +o:depends({ [option_name("protocol")] = "trojan" }) + +if singbox_tags:find("with_wireguard") then + o = s:option(Value, option_name("wireguard_public_key"), translate("Public Key")) + o:depends({ [option_name("protocol")] = "wireguard" }) + + o = s:option(Value, option_name("wireguard_secret_key"), translate("Private Key")) + o:depends({ [option_name("protocol")] = "wireguard" }) + + o = s:option(Value, option_name("wireguard_preSharedKey"), translate("Pre shared key")) + o:depends({ [option_name("protocol")] = "wireguard" }) + + o = s:option(DynamicList, option_name("wireguard_local_address"), translate("Local Address")) + o:depends({ [option_name("protocol")] = "wireguard" }) + + o = s:option(Value, option_name("wireguard_mtu"), translate("MTU")) + o.default = "1420" + o:depends({ [option_name("protocol")] = "wireguard" }) + + o = s:option(Value, option_name("wireguard_reserved"), translate("Reserved"), translate("Decimal numbers separated by \",\" or Base64-encoded strings.")) + o:depends({ [option_name("protocol")] = "wireguard" }) +end + +-- [[ HTTP部分 ]]-- +o = s:option(Value, option_name("http_host"), translate("HTTP Host")) +o:depends({ [option_name("transport")] = "http" }) + +o = s:option(Value, option_name("http_path"), translate("HTTP Path")) +o.placeholder = "/" +o:depends({ [option_name("transport")] = "http" }) + +o = s:option(Flag, option_name("http_h2_health_check"), translate("Health check")) +o:depends({ [option_name("tls")] = true, [option_name("transport")] = "http" }) + +o = s:option(Value, option_name("http_h2_read_idle_timeout"), translate("Idle timeout")) +o.default = "10" +o:depends({ [option_name("tls")] = true, [option_name("transport")] = "http", [option_name("http_h2_health_check")] = true }) + +o = s:option(Value, option_name("http_h2_health_check_timeout"), translate("Health check timeout")) +o.default = "15" +o:depends({ [option_name("tls")] = true, [option_name("transport")] = "http", [option_name("http_h2_health_check")] = true }) + +-- [[ WebSocket部分 ]]-- +o = s:option(Value, option_name("ws_host"), translate("WebSocket Host")) +o:depends({ [option_name("transport")] = "ws" }) + +o = s:option(Value, option_name("ws_path"), translate("WebSocket Path")) +o.placeholder = "/" +o:depends({ [option_name("transport")] = "ws" }) + +o = s:option(Flag, option_name("ws_enableEarlyData"), translate("Enable early data")) +o:depends({ [option_name("transport")] = "ws" }) + +o = s:option(Value, option_name("ws_maxEarlyData"), translate("Early data length")) +o.default = "1024" +o:depends({ [option_name("ws_enableEarlyData")] = true }) + +o = s:option(Value, option_name("ws_earlyDataHeaderName"), translate("Early data header name"), translate("Recommended value: Sec-WebSocket-Protocol")) +o:depends({ [option_name("ws_enableEarlyData")] = true }) + +-- [[ gRPC部分 ]]-- +if singbox_tags:find("with_grpc") then + o = s:option(Value, option_name("grpc_serviceName"), "ServiceName") + o:depends({ [option_name("transport")] = "grpc" }) + + o = s:option(Flag, option_name("grpc_health_check"), translate("Health check")) + o:depends({ [option_name("transport")] = "grpc" }) + + o = s:option(Value, option_name("grpc_idle_timeout"), translate("Idle timeout")) + o.default = "10" + o:depends({ [option_name("grpc_health_check")] = true }) + + o = s:option(Value, option_name("grpc_health_check_timeout"), translate("Health check timeout")) + o.default = "20" + o:depends({ [option_name("grpc_health_check")] = true }) + + o = s:option(Flag, option_name("grpc_permit_without_stream"), translate("Permit without stream")) + o.default = "0" + o:depends({ [option_name("grpc_health_check")] = true }) +end + +-- [[ Mux ]]-- +o = s:option(Flag, option_name("mux"), translate("Mux")) +o.rmempty = false +o:depends({ [option_name("protocol")] = "vmess" }) +o:depends({ [option_name("protocol")] = "vless", [option_name("flow")] = "" }) +o:depends({ [option_name("protocol")] = "http" }) +o:depends({ [option_name("protocol")] = "socks" }) +o:depends({ [option_name("protocol")] = "shadowsocks" }) +o:depends({ [option_name("protocol")] = "trojan" }) + +o = s:option(ListValue, option_name("mux_type"), translate("Mux")) +o:value("smux") +o:value("yamux") +o:value("h2mux") +o:depends({ [option_name("mux")] = true }) + +o = s:option(Value, option_name("mux_concurrency"), translate("Mux concurrency")) +o.default = 8 +o:depends({ [option_name("mux")] = true }) + +for key, value in pairs(s.fields) do + if key:find(option_prefix) == 1 then + if not s.fields[key].not_rewrite then + s.fields[key].cfgvalue = rm_prefix_cfgvalue + s.fields[key].write = rm_prefix_write + s.fields[key].remove = rm_prefix_remove + end + + local deps = s.fields[key].deps + if #deps > 0 then + for index, value in ipairs(deps) do + deps[index]["type"] = type_name + end + else + s.fields[key]:depends({ type = type_name }) + end + end +end diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/index.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/index.lua index fc6d2e5cc..46753e365 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/index.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/index.lua @@ -45,15 +45,8 @@ e = t:option(DummyValue, "type", translate("Type")) e.cfgvalue = function(t, n) local v = Value.cfgvalue(t, n) if v then - if v == "V2ray" or v == "Xray" then + if v == "sing-box" or v == "Xray" then local protocol = m:get(n, "protocol") - if protocol == "vmess" then - protocol = "VMess" - elseif protocol == "vless" then - protocol = "VLESS" - else - protocol = protocol:gsub("^%l",string.upper) - end return v .. " -> " .. protocol end return v diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/ray.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/ray.lua index 77daaf368..5b82782b9 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/ray.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/ray.lua @@ -2,10 +2,12 @@ local m, s = ... local api = require "luci.passwall2.api" -if not api.is_finded("xray") and not api.is_finded("v2ray")then +if not api.is_finded("xray") then return end +local type_name = "Xray" + local option_prefix = "xray_" local function option_name(name) @@ -18,52 +20,20 @@ local function rm_prefix_cfgvalue(self, section) end end local function rm_prefix_write(self, section, value) - if s.fields["type"]:formvalue(arg[1]) == "Xray" or s.fields["type"]:formvalue(arg[1]) == "V2ray" then + if s.fields["type"]:formvalue(arg[1]) == type_name then if self.option:find(option_prefix) == 1 then m:set(section, self.option:sub(1 + #option_prefix), value) end end end local function rm_prefix_remove(self, section, value) - if s.fields["type"]:formvalue(arg[1]) == "Xray" or s.fields["type"]:formvalue(arg[1]) == "V2ray" then + if s.fields["type"]:formvalue(arg[1]) == type_name then if self.option:find(option_prefix) == 1 then m:del(section, self.option:sub(1 + #option_prefix)) end end end -local function add_xray_depends(o, field, value) - local deps = { type = "Xray" } - if field then - if type(field) == "string" then - deps[field] = value - else - for key, value in pairs(field) do - deps[key] = value - end - end - end - o:depends(deps) -end - -local function add_v2ray_depends(o, field, value) - local deps = { type = "V2ray" } - if field then - if type(field) == "string" then - deps[field] = value - else - for key, value in pairs(field) do - deps[key] = value - end - end - end - o:depends(deps) -end - -local v_ss_method_list = { - "aes-128-gcm", "aes-256-gcm", "chacha20-poly1305" -} - local x_ss_method_list = { "aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "xchacha20-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305" } @@ -74,12 +44,7 @@ local header_type_list = { -- [[ Xray ]] -if api.is_finded("v2ray") then - s.fields["type"]:value("V2ray", translate("V2ray")) -end -if api.is_finded("xray") then - s.fields["type"]:value("Xray", translate("Xray")) -end +s.fields["type"]:value(type_name, "Xray") o = s:option(ListValue, option_name("protocol"), translate("Protocol")) o:value("vmess", "Vmess") @@ -89,13 +54,9 @@ o:value("socks", "Socks") o:value("shadowsocks", "Shadowsocks") o:value("trojan", "Trojan") o:value("dokodemo-door", "dokodemo-door") -add_xray_depends(o) -add_v2ray_depends(o) o = s:option(Value, option_name("port"), translate("Listen Port")) o.datatype = "port" -add_xray_depends(o) -add_v2ray_depends(o) o = s:option(Flag, option_name("auth"), translate("Auth")) o.validate = function(self, value, t) @@ -108,60 +69,38 @@ o.validate = function(self, value, t) end return value end -add_xray_depends(o, { [option_name("protocol")] = "socks" }) -add_xray_depends(o, { [option_name("protocol")] = "http" }) -add_v2ray_depends(o, { [option_name("protocol")] = "socks" }) -add_v2ray_depends(o, { [option_name("protocol")] = "http" }) +o:depends({ [option_name("protocol")] = "socks" }) +o:depends({ [option_name("protocol")] = "http" }) o = s:option(Value, option_name("username"), translate("Username")) -add_xray_depends(o, { [option_name("auth")] = true }) -add_v2ray_depends(o, { [option_name("auth")] = true }) +o:depends({ [option_name("auth")] = true }) o = s:option(Value, option_name("password"), translate("Password")) o.password = true -add_xray_depends(o, { [option_name("auth")] = true }) -add_v2ray_depends(o, { [option_name("auth")] = true }) -add_xray_depends(o, { [option_name("protocol")] = "shadowsocks" }) -add_v2ray_depends(o, { [option_name("protocol")] = "shadowsocks" }) +o:depends({ [option_name("auth")] = true }) +o:depends({ [option_name("protocol")] = "shadowsocks" }) o = s:option(ListValue, option_name("d_protocol"), translate("Destination protocol")) o:value("tcp", "TCP") o:value("udp", "UDP") o:value("tcp,udp", "TCP,UDP") -add_v2ray_depends(o, { [option_name("protocol")] = "dokodemo-door" }) -add_xray_depends(o, { [option_name("protocol")] = "dokodemo-door" }) +o:depends({ [option_name("protocol")] = "dokodemo-door" }) o = s:option(Value, option_name("d_address"), translate("Destination address")) -add_v2ray_depends(o, { [option_name("protocol")] = "dokodemo-door" }) -add_xray_depends(o, { [option_name("protocol")] = "dokodemo-door" }) +o:depends({ [option_name("protocol")] = "dokodemo-door" }) o = s:option(Value, option_name("d_port"), translate("Destination port")) o.datatype = "port" -add_v2ray_depends(o, { [option_name("protocol")] = "dokodemo-door" }) -add_xray_depends(o, { [option_name("protocol")] = "dokodemo-door" }) +o:depends({ [option_name("protocol")] = "dokodemo-door" }) o = s:option(Value, option_name("decryption"), translate("Encrypt Method")) o.default = "none" -add_v2ray_depends(o, { [option_name("protocol")] = "vless" }) -add_xray_depends(o, { [option_name("protocol")] = "vless" }) - -o = s:option(ListValue, option_name("v_ss_method"), translate("Encrypt Method")) -o.not_rewrite = true -for a, t in ipairs(v_ss_method_list) do o:value(t) end -add_v2ray_depends(o, { [option_name("protocol")] = "shadowsocks" }) -function o.cfgvalue(self, section) - return m:get(section, "method") -end -function o.write(self, section, value) - if s.fields["type"]:formvalue(arg[1]) == "V2ray" then - m:set(section, "method", value) - end -end +o:depends({ [option_name("protocol")] = "vless" }) o = s:option(ListValue, option_name("x_ss_method"), translate("Encrypt Method")) o.not_rewrite = true for a, t in ipairs(x_ss_method_list) do o:value(t) end -add_xray_depends(o, { [option_name("protocol")] = "shadowsocks" }) +o:depends({ [option_name("protocol")] = "shadowsocks" }) function o.cfgvalue(self, section) return m:get(section, "method") end @@ -172,33 +111,33 @@ function o.write(self, section, value) end o = s:option(Flag, option_name("iv_check"), translate("IV Check")) -add_v2ray_depends(o, { [option_name("protocol")] = "shadowsocks" }) -add_xray_depends(o, { [option_name("protocol")] = "shadowsocks" }) +o:depends({ [option_name("protocol")] = "shadowsocks" }) o = s:option(ListValue, option_name("ss_network"), translate("Transport")) o.default = "tcp,udp" o:value("tcp", "TCP") o:value("udp", "UDP") o:value("tcp,udp", "TCP,UDP") -add_v2ray_depends(o, { [option_name("protocol")] = "shadowsocks" }) -add_xray_depends(o, { [option_name("protocol")] = "shadowsocks" }) +o:depends({ [option_name("protocol")] = "shadowsocks" }) o = s:option(Flag, option_name("udp_forward"), translate("UDP Forward")) o.default = "1" o.rmempty = false -add_v2ray_depends(o, { [option_name("protocol")] = "socks" }) -add_xray_depends(o, { [option_name("protocol")] = "socks" }) +o:depends({ [option_name("protocol")] = "socks" }) o = s:option(DynamicList, option_name("uuid"), translate("ID") .. "/" .. translate("Password")) for i = 1, 3 do o:value(api.gen_uuid(1)) end -add_v2ray_depends(o, { [option_name("protocol")] = "vmess" }) -add_v2ray_depends(o, { [option_name("protocol")] = "vless" }) -add_v2ray_depends(o, { [option_name("protocol")] = "trojan" }) -add_xray_depends(o, { [option_name("protocol")] = "vmess" }) -add_xray_depends(o, { [option_name("protocol")] = "vless" }) -add_xray_depends(o, { [option_name("protocol")] = "trojan" }) +o:depends({ [option_name("protocol")] = "vmess" }) +o:depends({ [option_name("protocol")] = "vless" }) +o:depends({ [option_name("protocol")] = "trojan" }) + +o = s:option(ListValue, option_name("flow"), translate("flow")) +o.default = "" +o:value("", translate("Disable")) +o:value("xtls-rprx-vision") +o:depends({ [option_name("protocol")] = "vless" }) o = s:option(Flag, option_name("tls"), translate("TLS")) o.default = 0 @@ -214,44 +153,29 @@ o.validate = function(self, value, t) return value end end -add_v2ray_depends(o, { [option_name("protocol")] = "vmess" }) -add_v2ray_depends(o, { [option_name("protocol")] = "vless" }) -add_v2ray_depends(o, { [option_name("protocol")] = "socks" }) -add_v2ray_depends(o, { [option_name("protocol")] = "shadowsocks" }) -add_v2ray_depends(o, { [option_name("protocol")] = "trojan" }) -add_xray_depends(o, { [option_name("protocol")] = "vmess" }) -add_xray_depends(o, { [option_name("protocol")] = "vless" }) -add_xray_depends(o, { [option_name("protocol")] = "socks" }) -add_xray_depends(o, { [option_name("protocol")] = "shadowsocks" }) -add_xray_depends(o, { [option_name("protocol")] = "trojan" }) - -o = s:option(Value, option_name("tlsflow"), translate("flow")) -o.default = "" -o:value("", translate("Disable")) -o:value("xtls-rprx-vision") -o:value("xtls-rprx-vision-udp443") -add_xray_depends(o, { [option_name("protocol")] = "vless", [option_name("tls")] = true }) +o:depends({ [option_name("protocol")] = "vmess" }) +o:depends({ [option_name("protocol")] = "vless" }) +o:depends({ [option_name("protocol")] = "socks" }) +o:depends({ [option_name("protocol")] = "shadowsocks" }) +o:depends({ [option_name("protocol")] = "trojan" }) o = s:option(ListValue, option_name("alpn"), translate("alpn")) o.default = "h2,http/1.1" o:value("h2,http/1.1") o:value("h2") o:value("http/1.1") -add_v2ray_depends(o, { [option_name("tls")] = true }) -add_xray_depends(o, { [option_name("tls")] = true }) +o:depends({ [option_name("tls")] = true }) -- o = s:option(Value, option_name("minversion"), translate("minversion")) -- o.default = "1.3" -- o:value("1.3") ---add_v2ray_depends(o, { [option_name("tls")] = true }) ---add_xray_depends(o, { [option_name("tls")] = true }) +--o:depends({ [option_name("tls")] = true }) -- [[ TLS部分 ]] -- o = s:option(FileUpload, option_name("tls_certificateFile"), translate("Public key absolute path"), translate("as:") .. "/etc/ssl/fullchain.pem") o.default = m:get(s.section, "tls_certificateFile") or "/etc/config/ssl/" .. arg[1] .. ".pem" -add_v2ray_depends(o, { [option_name("tls")] = true }) -add_xray_depends(o, { [option_name("tls")] = true }) +o:depends({ [option_name("tls")] = true }) o.validate = function(self, value, t) if value and value ~= "" then if not nixio.fs.access(value) then @@ -265,8 +189,7 @@ end o = s:option(FileUpload, option_name("tls_keyFile"), translate("Private key absolute path"), translate("as:") .. "/etc/ssl/private.key") o.default = m:get(s.section, "tls_keyFile") or "/etc/config/ssl/" .. arg[1] .. ".key" -add_v2ray_depends(o, { [option_name("tls")] = true }) -add_xray_depends(o, { [option_name("tls")] = true }) +o:depends({ [option_name("tls")] = true }) o.validate = function(self, value, t) if value and value ~= "" then if not nixio.fs.access(value) then @@ -286,36 +209,27 @@ o:value("h2", "HTTP/2") o:value("ds", "DomainSocket") o:value("quic", "QUIC") o:value("grpc", "gRPC") -add_v2ray_depends(o, { [option_name("protocol")] = "vmess" }) -add_v2ray_depends(o, { [option_name("protocol")] = "vless" }) -add_v2ray_depends(o, { [option_name("protocol")] = "socks" }) -add_v2ray_depends(o, { [option_name("protocol")] = "shadowsocks" }) -add_v2ray_depends(o, { [option_name("protocol")] = "trojan" }) -add_xray_depends(o, { [option_name("protocol")] = "vmess" }) -add_xray_depends(o, { [option_name("protocol")] = "vless" }) -add_xray_depends(o, { [option_name("protocol")] = "socks" }) -add_xray_depends(o, { [option_name("protocol")] = "shadowsocks" }) -add_xray_depends(o, { [option_name("protocol")] = "trojan" }) +o:depends({ [option_name("protocol")] = "vmess" }) +o:depends({ [option_name("protocol")] = "vless" }) +o:depends({ [option_name("protocol")] = "socks" }) +o:depends({ [option_name("protocol")] = "shadowsocks" }) +o:depends({ [option_name("protocol")] = "trojan" }) -- [[ WebSocket部分 ]]-- o = s:option(Value, option_name("ws_host"), translate("WebSocket Host")) -add_v2ray_depends(o, { [option_name("transport")] = "ws" }) -add_xray_depends(o, { [option_name("transport")] = "ws" }) +o:depends({ [option_name("transport")] = "ws" }) o = s:option(Value, option_name("ws_path"), translate("WebSocket Path")) -add_v2ray_depends(o, { [option_name("transport")] = "ws" }) -add_xray_depends(o, { [option_name("transport")] = "ws" }) +o:depends({ [option_name("transport")] = "ws" }) -- [[ HTTP/2部分 ]]-- o = s:option(Value, option_name("h2_host"), translate("HTTP/2 Host")) -add_v2ray_depends(o, { [option_name("transport")] = "h2" }) -add_xray_depends(o, { [option_name("transport")] = "h2" }) +o:depends({ [option_name("transport")] = "h2" }) o = s:option(Value, option_name("h2_path"), translate("HTTP/2 Path")) -add_v2ray_depends(o, { [option_name("transport")] = "h2" }) -add_xray_depends(o, { [option_name("transport")] = "h2" }) +o:depends({ [option_name("transport")] = "h2" }) -- [[ TCP部分 ]]-- @@ -323,141 +237,111 @@ add_xray_depends(o, { [option_name("transport")] = "h2" }) o = s:option(ListValue, option_name("tcp_guise"), translate("Camouflage Type")) o:value("none", "none") o:value("http", "http") -add_v2ray_depends(o, { [option_name("transport")] = "tcp" }) -add_xray_depends(o, { [option_name("transport")] = "tcp" }) +o:depends({ [option_name("transport")] = "tcp" }) -- HTTP域名 o = s:option(DynamicList, option_name("tcp_guise_http_host"), translate("HTTP Host")) -add_v2ray_depends(o, { [option_name("tcp_guise")] = "http" }) -add_xray_depends(o, { [option_name("tcp_guise")] = "http" }) +o:depends({ [option_name("tcp_guise")] = "http" }) -- HTTP路径 o = s:option(DynamicList, option_name("tcp_guise_http_path"), translate("HTTP Path")) -add_v2ray_depends(o, { [option_name("tcp_guise")] = "http" }) -add_xray_depends(o, { [option_name("tcp_guise")] = "http" }) +o:depends({ [option_name("tcp_guise")] = "http" }) -- [[ mKCP部分 ]]-- o = s:option(ListValue, option_name("mkcp_guise"), translate("Camouflage Type"), translate('
none: default, no masquerade, data sent is packets with no characteristics.
srtp: disguised as an SRTP packet, it will be recognized as video call data (such as FaceTime).
utp: packets disguised as uTP will be recognized as bittorrent downloaded data.
wechat-video: packets disguised as WeChat video calls.
dtls: disguised as DTLS 1.2 packet.
wireguard: disguised as a WireGuard packet. (not really WireGuard protocol)')) for a, t in ipairs(header_type_list) do o:value(t) end -add_v2ray_depends(o, { [option_name("transport")] = "mkcp" }) -add_xray_depends(o, { [option_name("transport")] = "mkcp" }) +o:depends({ [option_name("transport")] = "mkcp" }) o = s:option(Value, option_name("mkcp_mtu"), translate("KCP MTU")) o.default = "1350" -add_v2ray_depends(o, { [option_name("transport")] = "mkcp" }) -add_xray_depends(o, { [option_name("transport")] = "mkcp" }) +o:depends({ [option_name("transport")] = "mkcp" }) o = s:option(Value, option_name("mkcp_tti"), translate("KCP TTI")) o.default = "20" -add_v2ray_depends(o, { [option_name("transport")] = "mkcp" }) -add_xray_depends(o, { [option_name("transport")] = "mkcp" }) +o:depends({ [option_name("transport")] = "mkcp" }) o = s:option(Value, option_name("mkcp_uplinkCapacity"), translate("KCP uplinkCapacity")) o.default = "5" -add_v2ray_depends(o, { [option_name("transport")] = "mkcp" }) -add_xray_depends(o, { [option_name("transport")] = "mkcp" }) +o:depends({ [option_name("transport")] = "mkcp" }) o = s:option(Value, option_name("mkcp_downlinkCapacity"), translate("KCP downlinkCapacity")) o.default = "20" -add_v2ray_depends(o, { [option_name("transport")] = "mkcp" }) -add_xray_depends(o, { [option_name("transport")] = "mkcp" }) +o:depends({ [option_name("transport")] = "mkcp" }) o = s:option(Flag, option_name("mkcp_congestion"), translate("KCP Congestion")) -add_v2ray_depends(o, { [option_name("transport")] = "mkcp" }) -add_xray_depends(o, { [option_name("transport")] = "mkcp" }) +o:depends({ [option_name("transport")] = "mkcp" }) o = s:option(Value, option_name("mkcp_readBufferSize"), translate("KCP readBufferSize")) o.default = "1" -add_v2ray_depends(o, { [option_name("transport")] = "mkcp" }) -add_xray_depends(o, { [option_name("transport")] = "mkcp" }) +o:depends({ [option_name("transport")] = "mkcp" }) o = s:option(Value, option_name("mkcp_writeBufferSize"), translate("KCP writeBufferSize")) o.default = "1" -add_v2ray_depends(o, { [option_name("transport")] = "mkcp" }) -add_xray_depends(o, { [option_name("transport")] = "mkcp" }) +o:depends({ [option_name("transport")] = "mkcp" }) o = s:option(Value, option_name("mkcp_seed"), translate("KCP Seed")) -add_v2ray_depends(o, { [option_name("transport")] = "mkcp" }) -add_xray_depends(o, { [option_name("transport")] = "mkcp" }) +o:depends({ [option_name("transport")] = "mkcp" }) -- [[ DomainSocket部分 ]]-- o = s:option(Value, option_name("ds_path"), "Path", translate("A legal file path. This file must not exist before running.")) -add_v2ray_depends(o, { [option_name("transport")] = "ds" }) -add_xray_depends(o, { [option_name("transport")] = "ds" }) +o:depends({ [option_name("transport")] = "ds" }) -- [[ QUIC部分 ]]-- o = s:option(ListValue, option_name("quic_security"), translate("Encrypt Method")) o:value("none") o:value("aes-128-gcm") o:value("chacha20-poly1305") -add_v2ray_depends(o, { [option_name("transport")] = "quic" }) -add_xray_depends(o, { [option_name("transport")] = "quic" }) +o:depends({ [option_name("transport")] = "quic" }) o = s:option(Value, option_name("quic_key"), translate("Encrypt Method") .. translate("Key")) -add_v2ray_depends(o, { [option_name("transport")] = "quic" }) -add_xray_depends(o, { [option_name("transport")] = "quic" }) +o:depends({ [option_name("transport")] = "quic" }) o = s:option(ListValue, option_name("quic_guise"), translate("Camouflage Type")) for a, t in ipairs(header_type_list) do o:value(t) end -add_v2ray_depends(o, { [option_name("transport")] = "quic" }) -add_xray_depends(o, { [option_name("transport")] = "quic" }) +o:depends({ [option_name("transport")] = "quic" }) -- [[ gRPC部分 ]]-- o = s:option(Value, option_name("grpc_serviceName"), "ServiceName") -add_v2ray_depends(o, { [option_name("transport")] = "grpc" }) -add_xray_depends(o, { [option_name("transport")] = "grpc" }) +o:depends({ [option_name("transport")] = "grpc" }) o = s:option(Flag, option_name("acceptProxyProtocol"), translate("acceptProxyProtocol"), translate("Whether to receive PROXY protocol, when this node want to be fallback or forwarded by proxy, it must be enable, otherwise it cannot be used.")) -add_v2ray_depends(o, { [option_name("transport")] = "tcp" }) -add_v2ray_depends(o, { [option_name("transport")] = "ws" }) -add_xray_depends(o, { [option_name("transport")] = "tcp" }) -add_xray_depends(o, { [option_name("transport")] = "ws" }) +o:depends({ [option_name("transport")] = "tcp" }) +o:depends({ [option_name("transport")] = "ws" }) -- [[ Fallback部分 ]]-- o = s:option(Flag, option_name("fallback"), translate("Fallback")) -add_v2ray_depends(o, { [option_name("protocol")] = "vless", [option_name("transport")] = "tcp" }) -add_v2ray_depends(o, { [option_name("protocol")] = "trojan", [option_name("transport")] = "tcp" }) -add_xray_depends(o, { [option_name("protocol")] = "vless", [option_name("transport")] = "tcp" }) -add_xray_depends(o, { [option_name("protocol")] = "trojan", [option_name("transport")] = "tcp" }) +o:depends({ [option_name("protocol")] = "vless", [option_name("transport")] = "tcp" }) +o:depends({ [option_name("protocol")] = "trojan", [option_name("transport")] = "tcp" }) --[[ o = s:option(Value, option_name("fallback_alpn"), "Fallback alpn") -add_v2ray_depends(o, { [option_name("fallback")] = true }) -add_xray_depends(o, { [option_name("fallback")] = true }) +o:depends({ [option_name("fallback")] = true }) o = s:option(Value, option_name("fallback_path"), "Fallback path") -add_v2ray_depends(o, { [option_name("fallback")] = true }) -add_xray_depends(o, { [option_name("fallback")] = true }) +o:depends({ [option_name("fallback")] = true }) o = s:option(Value, option_name("fallback_dest"), "Fallback dest") -add_v2ray_depends(o, { [option_name("fallback")] = true }) -add_xray_depends(o, { [option_name("fallback")] = true }) +o:depends({ [option_name("fallback")] = true }) o = s:option(Value, option_name("fallback_xver"), "Fallback xver") o.default = 0 -add_v2ray_depends(o, { [option_name("fallback")] = true }) -add_xray_depends(o, { [option_name("fallback")] = true }) +o:depends({ [option_name("fallback")] = true }) ]]-- o = s:option(DynamicList, option_name("fallback_list"), "Fallback", translate("dest,path")) -add_v2ray_depends(o, { [option_name("fallback")] = true }) -add_xray_depends(o, { [option_name("fallback")] = true }) +o:depends({ [option_name("fallback")] = true }) o = s:option(Flag, option_name("bind_local"), translate("Bind Local"), translate("When selected, it can only be accessed locally, It is recommended to turn on when using reverse proxies or be fallback.")) o.default = "0" -add_v2ray_depends(o) -add_xray_depends(o) o = s:option(Flag, option_name("accept_lan"), translate("Accept LAN Access"), translate("When selected, it can accessed lan , this will not be safe!")) o.default = "0" -add_v2ray_depends(o) -add_xray_depends(o) local nodes_table = {} for k, e in ipairs(api.get_valid_nodes()) do - if e.node_type == "normal" and (e.type == "V2ray" or e.type == "Xray") then + if e.node_type == "normal" and e.type == type_name then nodes_table[#nodes_table + 1] = { id = e[".name"], remarks = e["remark"] @@ -472,45 +356,32 @@ o:value("_http", translate("Custom HTTP")) o:value("_iface", translate("Custom Interface") .. " (Only Support Xray)") for k, v in pairs(nodes_table) do o:value(v.id, v.remarks) end o.default = "nil" -add_v2ray_depends(o) -add_xray_depends(o) o = s:option(Value, option_name("outbound_node_address"), translate("Address (Support Domain Name)")) -add_v2ray_depends(o, { [option_name("outbound_node")] = "_socks"}) -add_v2ray_depends(o, { [option_name("outbound_node")] = "_http"}) -add_xray_depends(o, { [option_name("outbound_node")] = "_socks"}) -add_xray_depends(o, { [option_name("outbound_node")] = "_http"}) +o:depends({ [option_name("outbound_node")] = "_socks"}) +o:depends({ [option_name("outbound_node")] = "_http"}) o = s:option(Value, option_name("outbound_node_port"), translate("Port")) o.datatype = "port" -add_v2ray_depends(o, { [option_name("outbound_node")] = "_socks"}) -add_v2ray_depends(o, { [option_name("outbound_node")] = "_http"}) -add_xray_depends(o, { [option_name("outbound_node")] = "_socks"}) -add_xray_depends(o, { [option_name("outbound_node")] = "_http"}) +o:depends({ [option_name("outbound_node")] = "_socks"}) +o:depends({ [option_name("outbound_node")] = "_http"}) o = s:option(Value, option_name("outbound_node_username"), translate("Username")) -add_v2ray_depends(o, { [option_name("outbound_node")] = "_socks"}) -add_v2ray_depends(o, { [option_name("outbound_node")] = "_http"}) -add_xray_depends(o, { [option_name("outbound_node")] = "_socks"}) -add_xray_depends(o, { [option_name("outbound_node")] = "_http"}) +o:depends({ [option_name("outbound_node")] = "_socks"}) +o:depends({ [option_name("outbound_node")] = "_http"}) o = s:option(Value, option_name("outbound_node_password"), translate("Password")) o.password = true -add_v2ray_depends(o, { [option_name("outbound_node")] = "_socks"}) -add_v2ray_depends(o, { [option_name("outbound_node")] = "_http"}) -add_xray_depends(o, { [option_name("outbound_node")] = "_socks"}) -add_xray_depends(o, { [option_name("outbound_node")] = "_http"}) +o:depends({ [option_name("outbound_node")] = "_socks"}) +o:depends({ [option_name("outbound_node")] = "_http"}) o = s:option(Value, option_name("outbound_node_iface"), translate("Interface")) o.default = "eth1" -add_v2ray_depends(o, { [option_name("outbound_node")] = "_iface"}) -add_xray_depends(o, { [option_name("outbound_node")] = "_iface"}) +o:depends({ [option_name("outbound_node")] = "_iface"}) o = s:option(Flag, option_name("log"), translate("Log")) o.default = "1" o.rmempty = false -add_v2ray_depends(o) -add_xray_depends(o) o = s:option(ListValue, option_name("loglevel"), translate("Log Level")) o.default = "warning" @@ -518,8 +389,7 @@ o:value("debug") o:value("info") o:value("warning") o:value("error") -add_v2ray_depends(o, { [option_name("log")] = true }) -add_xray_depends(o, { [option_name("log")] = true }) +o:depends({ [option_name("log")] = true }) for key, value in pairs(s.fields) do if key:find(option_prefix) == 1 then @@ -529,4 +399,13 @@ for key, value in pairs(s.fields) do s.fields[key].remove = rm_prefix_remove end end + + local deps = s.fields[key].deps + if #deps > 0 then + for index, value in ipairs(deps) do + deps[index]["type"] = type_name + end + else + s.fields[key]:depends({ type = type_name }) + end end diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/sing-box.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/sing-box.lua new file mode 100644 index 000000000..766ea780b --- /dev/null +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/server/type/sing-box.lua @@ -0,0 +1,377 @@ +local m, s = ... + +local api = require "luci.passwall2.api" + +if not api.is_finded("sing-box")then + return +end + +local singbox_tags = luci.sys.exec(api.finded("sing-box") .. " version | grep 'Tags:' | awk '{print $2}'") + +local type_name = "sing-box" + +local option_prefix = "singbox_" + +local function option_name(name) + return option_prefix .. name +end + +local function rm_prefix_cfgvalue(self, section) + if self.option:find(option_prefix) == 1 then + return m:get(section, self.option:sub(1 + #option_prefix)) + end +end +local function rm_prefix_write(self, section, value) + if s.fields["type"]:formvalue(arg[1]) == type_name then + if self.option:find(option_prefix) == 1 then + m:set(section, self.option:sub(1 + #option_prefix), value) + end + end +end +local function rm_prefix_remove(self, section, value) + if s.fields["type"]:formvalue(arg[1]) == type_name then + if self.option:find(option_prefix) == 1 then + m:del(section, self.option:sub(1 + #option_prefix)) + end + end +end + +local ss_method_list = { + "none", "aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "chacha20-ietf-poly1305", "xchacha20-ietf-poly1305", + "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305" +} + +-- [[ Sing-Box ]] + +s.fields["type"]:value(type_name, "Sing-Box") + +o = s:option(ListValue, option_name("protocol"), translate("Protocol")) +o:value("mixed", "Mixed") +o:value("socks", "Socks") +o:value("http", "HTTP") +o:value("shadowsocks", "Shadowsocks") +o:value("vmess", "Vmess") +o:value("vless", "VLESS") +o:value("trojan", "Trojan") +o:value("naive", "Naive") +if singbox_tags:find("with_quic") then + o:value("hysteria", "Hysteria") +end +if singbox_tags:find("with_quic") then + o:value("tuic", "TUIC") +end +if singbox_tags:find("with_quic") then + o:value("hysteria2", "Hysteria2") +end +o:value("direct", "Direct") + +o = s:option(Value, option_name("port"), translate("Listen Port")) +o.datatype = "port" + +o = s:option(Flag, option_name("auth"), translate("Auth")) +o.validate = function(self, value, t) + if value and value == "1" then + local user_v = s.fields[option_name("username")]:formvalue(t) or "" + local pass_v = s.fields[option_name("password")]:formvalue(t) or "" + if user_v == "" or pass_v == "" then + return nil, translate("Username and Password must be used together!") + end + end + return value +end +o:depends({ [option_name("protocol")] = "mixed" }) +o:depends({ [option_name("protocol")] = "socks" }) +o:depends({ [option_name("protocol")] = "http" }) + +o = s:option(Value, option_name("username"), translate("Username")) +o:depends({ [option_name("auth")] = true }) +o:depends({ [option_name("protocol")] = "naive" }) + +o = s:option(Value, option_name("password"), translate("Password")) +o.password = true +o:depends({ [option_name("auth")] = true }) +o:depends({ [option_name("protocol")] = "shadowsocks" }) +o:depends({ [option_name("protocol")] = "naive" }) +o:depends({ [option_name("protocol")] = "tuic" }) + +if singbox_tags:find("with_quic") then + o = s:option(Value, option_name("hysteria_up_mbps"), translate("Max upload Mbps")) + o.default = "100" + o:depends({ [option_name("protocol")] = "hysteria" }) + + o = s:option(Value, option_name("hysteria_down_mbps"), translate("Max download Mbps")) + o.default = "100" + o:depends({ [option_name("protocol")] = "hysteria" }) + + o = s:option(Value, option_name("hysteria_obfs"), translate("Obfs Password")) + o:depends({ [option_name("protocol")] = "hysteria" }) + + o = s:option(ListValue, option_name("hysteria_auth_type"), translate("Auth Type")) + o:value("disable", translate("Disable")) + o:value("string", translate("STRING")) + o:value("base64", translate("BASE64")) + o:depends({ [option_name("protocol")] = "hysteria" }) + + o = s:option(Value, option_name("hysteria_auth_password"), translate("Auth Password")) + o.password = true + o:depends({ [option_name("protocol")] = "hysteria", [option_name("hysteria_auth_type")] = "string"}) + o:depends({ [option_name("protocol")] = "hysteria", [option_name("hysteria_auth_type")] = "base64"}) + + o = s:option(Value, option_name("hysteria_recv_window_conn"), translate("QUIC stream receive window")) + o:depends({ [option_name("protocol")] = "hysteria" }) + + o = s:option(Value, option_name("hysteria_recv_window_client"), translate("QUIC connection receive window")) + o:depends({ [option_name("protocol")] = "hysteria" }) + + o = s:option(Value, option_name("hysteria_max_conn_client"), translate("QUIC concurrent bidirectional streams")) + o.default = "1024" + o:depends({ [option_name("protocol")] = "hysteria" }) + + o = s:option(Flag, option_name("hysteria_disable_mtu_discovery"), translate("Disable MTU detection")) + o:depends({ [option_name("protocol")] = "hysteria" }) +end + +if singbox_tags:find("with_quic") then + o = s:option(ListValue, option_name("tuic_congestion_control"), translate("Congestion control algorithm")) + o.default = "cubic" + o:value("bbr", translate("BBR")) + o:value("cubic", translate("CUBIC")) + o:value("new_reno", translate("New Reno")) + o:depends({ [option_name("protocol")] = "tuic" }) + + o = s:option(Flag, option_name("tuic_zero_rtt_handshake"), translate("Enable 0-RTT QUIC handshake")) + o.default = 0 + o:depends({ [option_name("protocol")] = "tuic" }) + + o = s:option(Value, option_name("tuic_heartbeat"), translate("Heartbeat interval(second)")) + o.datatype = "uinteger" + o.default = "3" + o:depends({ [option_name("protocol")] = "tuic" }) +end + +if singbox_tags:find("with_quic") then + o = s:option(Flag, option_name("hysteria2_ignore_client_bandwidth"), translate("Commands the client to use the BBR flow control algorithm")) + o.default = 0 + o:depends({ [option_name("protocol")] = "hysteria2" }) + + o = s:option(Value, option_name("hysteria2_up_mbps"), translate("Max upload Mbps")) + o:depends({ [option_name("protocol")] = "hysteria2", [option_name("hysteria2_ignore_client_bandwidth")] = false }) + + o = s:option(Value, option_name("hysteria2_down_mbps"), translate("Max download Mbps")) + o:depends({ [option_name("protocol")] = "hysteria2", [option_name("hysteria2_ignore_client_bandwidth")] = false }) + + o = s:option(ListValue, option_name("hysteria2_obfs_type"), translate("Obfs Type")) + o:value("", translate("Disable")) + o:value("salamander") + o:depends({ [option_name("protocol")] = "hysteria2" }) + + o = s:option(Value, option_name("hysteria2_obfs_password"), translate("Obfs Password")) + o:depends({ [option_name("protocol")] = "hysteria2" }) + + o = s:option(Value, option_name("hysteria2_auth_password"), translate("Auth Password")) + o.password = true + o:depends({ [option_name("protocol")] = "hysteria2"}) +end + +o = s:option(ListValue, option_name("d_protocol"), translate("Destination protocol")) +o:value("tcp", "TCP") +o:value("udp", "UDP") +o:value("tcp,udp", "TCP,UDP") +o:depends({ [option_name("protocol")] = "direct" }) + +o = s:option(Value, option_name("d_address"), translate("Destination address")) +o:depends({ [option_name("protocol")] = "direct" }) + +o = s:option(Value, option_name("d_port"), translate("Destination port")) +o.datatype = "port" +o:depends({ [option_name("protocol")] = "direct" }) + +o = s:option(Value, option_name("decryption"), translate("Encrypt Method")) +o.default = "none" +o:depends({ [option_name("protocol")] = "vless" }) + +o = s:option(ListValue, option_name("ss_method"), translate("Encrypt Method")) +o.not_rewrite = true +for a, t in ipairs(ss_method_list) do o:value(t) end +o:depends({ [option_name("protocol")] = "shadowsocks" }) +function o.cfgvalue(self, section) + return m:get(section, "method") +end +function o.write(self, section, value) + if s.fields["type"]:formvalue(arg[1]) == type_name then + m:set(section, "method", value) + end +end + +o = s:option(DynamicList, option_name("uuid"), translate("ID") .. "/" .. translate("Password")) +for i = 1, 3 do + o:value(api.gen_uuid(1)) +end +o:depends({ [option_name("protocol")] = "vmess" }) +o:depends({ [option_name("protocol")] = "vless" }) +o:depends({ [option_name("protocol")] = "trojan" }) +o:depends({ [option_name("protocol")] = "tuic" }) + +o = s:option(ListValue, option_name("flow"), translate("flow")) +o.default = "" +o:value("", translate("Disable")) +o:value("xtls-rprx-vision") +o:depends({ [option_name("protocol")] = "vless" }) + +o = s:option(Flag, option_name("tls"), translate("TLS")) +o.default = 0 +o.validate = function(self, value, t) + if value then + if value == "1" then + local ca = s.fields[option_name("tls_certificateFile")]:formvalue(t) or "" + local key = s.fields[option_name("tls_keyFile")]:formvalue(t) or "" + if ca == "" or key == "" then + return nil, translate("Public key and Private key path can not be empty!") + end + end + return value + end +end +o:depends({ [option_name("protocol")] = "http" }) +o:depends({ [option_name("protocol")] = "shadowsocks" }) +o:depends({ [option_name("protocol")] = "vmess" }) +o:depends({ [option_name("protocol")] = "vless" }) +o:depends({ [option_name("protocol")] = "trojan" }) + +-- [[ TLS部分 ]] -- + +o = s:option(FileUpload, option_name("tls_certificateFile"), translate("Public key absolute path"), translate("as:") .. "/etc/ssl/fullchain.pem") +o.default = m:get(s.section, "tls_certificateFile") or "/etc/config/ssl/" .. arg[1] .. ".pem" +o:depends({ [option_name("tls")] = true }) +o.validate = function(self, value, t) + if value and value ~= "" then + if not nixio.fs.access(value) then + return nil, translate("Can't find this file!") + else + return value + end + end + return nil +end + +o = s:option(FileUpload, option_name("tls_keyFile"), translate("Private key absolute path"), translate("as:") .. "/etc/ssl/private.key") +o.default = m:get(s.section, "tls_keyFile") or "/etc/config/ssl/" .. arg[1] .. ".key" +o:depends({ [option_name("tls")] = true }) +o.validate = function(self, value, t) + if value and value ~= "" then + if not nixio.fs.access(value) then + return nil, translate("Can't find this file!") + else + return value + end + end + return nil +end + +o = s:option(ListValue, option_name("transport"), translate("Transport")) +o:value("tcp", "TCP") +o:value("http", "HTTP") +o:value("ws", "WebSocket") +o:value("quic", "QUIC") +o:value("grpc", "gRPC") +o:depends({ [option_name("protocol")] = "shadowsocks" }) +o:depends({ [option_name("protocol")] = "vmess" }) +o:depends({ [option_name("protocol")] = "vless" }) +o:depends({ [option_name("protocol")] = "trojan" }) + +-- [[ HTTP部分 ]]-- + +o = s:option(Value, option_name("http_host"), translate("HTTP Host")) +o:depends({ [option_name("transport")] = "http" }) + +o = s:option(Value, option_name("http_path"), translate("HTTP Path")) +o:depends({ [option_name("transport")] = "http" }) + +-- [[ WebSocket部分 ]]-- + +o = s:option(Value, option_name("ws_host"), translate("WebSocket Host")) +o:depends({ [option_name("transport")] = "ws" }) + +o = s:option(Value, option_name("ws_path"), translate("WebSocket Path")) +o:depends({ [option_name("transport")] = "ws" }) + +-- [[ gRPC部分 ]]-- +o = s:option(Value, option_name("grpc_serviceName"), "ServiceName") +o:depends({ [option_name("transport")] = "grpc" }) + +o = s:option(Flag, option_name("bind_local"), translate("Bind Local"), translate("When selected, it can only be accessed locally, It is recommended to turn on when using reverse proxies or be fallback.")) +o.default = "0" + +o = s:option(Flag, option_name("accept_lan"), translate("Accept LAN Access"), translate("When selected, it can accessed lan , this will not be safe!")) +o.default = "0" + +local nodes_table = {} +for k, e in ipairs(api.get_valid_nodes()) do + if e.node_type == "normal" and e.type == type_name then + nodes_table[#nodes_table + 1] = { + id = e[".name"], + remarks = e["remark"] + } + end +end + +o = s:option(ListValue, option_name("outbound_node"), translate("outbound node")) +o:value("nil", translate("Close")) +o:value("_socks", translate("Custom Socks")) +o:value("_http", translate("Custom HTTP")) +o:value("_iface", translate("Custom Interface")) +for k, v in pairs(nodes_table) do o:value(v.id, v.remarks) end +o.default = "nil" + +o = s:option(Value, option_name("outbound_node_address"), translate("Address (Support Domain Name)")) +o:depends({ [option_name("outbound_node")] = "_socks" }) +o:depends({ [option_name("outbound_node")] = "_http" }) + +o = s:option(Value, option_name("outbound_node_port"), translate("Port")) +o.datatype = "port" +o:depends({ [option_name("outbound_node")] = "_socks" }) +o:depends({ [option_name("outbound_node")] = "_http" }) + +o = s:option(Value, option_name("outbound_node_username"), translate("Username")) +o:depends({ [option_name("outbound_node")] = "_socks" }) +o:depends({ [option_name("outbound_node")] = "_http" }) + +o = s:option(Value, option_name("outbound_node_password"), translate("Password")) +o.password = true +o:depends({ [option_name("outbound_node")] = "_socks" }) +o:depends({ [option_name("outbound_node")] = "_http" }) + +o = s:option(Value, option_name("outbound_node_iface"), translate("Interface")) +o.default = "eth1" +o:depends({ [option_name("outbound_node")] = "_iface" }) + +o = s:option(Flag, option_name("log"), translate("Log")) +o.default = "1" +o.rmempty = false + +o = s:option(ListValue, option_name("loglevel"), translate("Log Level")) +o.default = "info" +o:value("debug") +o:value("info") +o:value("warn") +o:value("error") +o:depends({ [option_name("log")] = true }) + +for key, value in pairs(s.fields) do + if key:find(option_prefix) == 1 then + if not s.fields[key].not_rewrite then + s.fields[key].cfgvalue = rm_prefix_cfgvalue + s.fields[key].write = rm_prefix_write + s.fields[key].remove = rm_prefix_remove + end + + local deps = s.fields[key].deps + if #deps > 0 then + for index, value in ipairs(deps) do + deps[index]["type"] = type_name + end + else + s.fields[key]:depends({ type = type_name }) + end + end +end diff --git a/luci-app-passwall2/luasrc/passwall2/api.lua b/luci-app-passwall2/luasrc/passwall2/api.lua index cba51e2a5..113e9d4be 100644 --- a/luci-app-passwall2/luasrc/passwall2/api.lua +++ b/luci-app-passwall2/luasrc/passwall2/api.lua @@ -282,7 +282,7 @@ function get_valid_nodes() local address = e.address if is_ip(address) or datatypes.hostname(address) then local type = e.type - if (type == "V2ray" or type == "Xray") and e.protocol then + if (type == "sing-box" or type == "Xray") and e.protocol then local protocol = e.protocol if protocol == "vmess" then protocol = "VMess" @@ -314,7 +314,7 @@ function get_node_remarks(n) remarks = "%s:[%s] " % {n.type .. " " .. i18n.translatef(n.protocol), n.remarks} else local type2 = n.type - if (n.type == "V2ray" or n.type == "Xray") and n.protocol then + if (n.type == "sing-box" or n.type == "Xray") and n.protocol then local protocol = n.protocol if protocol == "vmess" then protocol = "VMess" @@ -381,8 +381,12 @@ function get_customed_path(e) return uci_get_type("global_app", e .. "_file") end +function finded(e) + return luci.sys.exec('echo -n $(type -t -p "/bin/%s" -p "/usr/bin/%s" -p "%s" "%s" | head -n1)' % {e, e, get_customed_path(e), e}) +end + function is_finded(e) - return luci.sys.exec('type -t -p "/bin/%s" -p "/usr/bin/%s" -p "%s" "%s"' % {e, e, get_customed_path(e), e}) ~= "" and true or false + return finded(e) ~= "" and true or false end function clone(org) diff --git a/luci-app-passwall2/luasrc/passwall2/com.lua b/luci-app-passwall2/luasrc/passwall2/com.lua index dfdf8321f..8da871622 100644 --- a/luci-app-passwall2/luasrc/passwall2/com.lua +++ b/luci-app-passwall2/luasrc/passwall2/com.lua @@ -33,13 +33,26 @@ _M.hysteria = { } } -_M.v2ray = { - name = "V2ray", - repo = "v2fly/v2ray-core", +_M.singbox = { + name = "Sing-Box", + repo = "SagerNet/sing-box", + get_url = gh_pre_release_url, + cmd_version = "version | awk '{print $3}' | sed -n 1P", + zipped = true, + default_path = "/usr/bin/sing-box", + match_fmt_str = "linux%%-%s", + file_tree = { + x86_64 = "amd64" + } +} + +_M.xray = { + name = "Xray", + repo = "XTLS/Xray-core", get_url = gh_pre_release_url, cmd_version = "version | awk '{print $2}' | sed -n 1P", zipped = true, - default_path = "/usr/bin/v2ray", + default_path = "/usr/bin/xray", match_fmt_str = "linux%%-%s", file_tree = { x86_64 = "64", @@ -49,15 +62,4 @@ _M.v2ray = { } } -_M.xray = { - name = "Xray", - repo = "XTLS/Xray-core", - get_url = gh_pre_release_url, - cmd_version = _M.v2ray.cmd_version, - zipped = true, - default_path = "/usr/bin/xray", - match_fmt_str = _M.v2ray.match_fmt_str, - file_tree = _M.v2ray.file_tree -} - return _M diff --git a/luci-app-passwall2/luasrc/passwall2/server_app.lua b/luci-app-passwall2/luasrc/passwall2/server_app.lua index aa3dca543..476cb1bad 100644 --- a/luci-app-passwall2/luasrc/passwall2/server_app.lua +++ b/luci-app-passwall2/luasrc/passwall2/server_app.lua @@ -144,12 +144,12 @@ local function start() elseif type == "SS-Rust" then config = require(require_dir .. "util_shadowsocks").gen_config_server(user) bin = ln_run("/usr/bin/ssserver", "ssserver", "-c " .. config_file, log_path) - elseif type == "V2ray" then - config = require(require_dir .. "util_xray").gen_config_server(user) - bin = ln_run(api.get_app_path("v2ray"), "v2ray", "run -c " .. config_file, log_path) elseif type == "Xray" then config = require(require_dir .. "util_xray").gen_config_server(user) bin = ln_run(api.get_app_path("xray"), "xray", "run -c " .. config_file, log_path) + elseif type == "sing-box" then + config = require(require_dir .. "util_sing-box").gen_config_server(user) + bin = ln_run(api.get_app_path("singbox"), "sing-box", "run -c " .. config_file, log_path) elseif type == "Brook" then local brook_protocol = user.protocol local brook_password = user.password diff --git a/luci-app-passwall2/luasrc/passwall2/util_sing-box.lua b/luci-app-passwall2/luasrc/passwall2/util_sing-box.lua new file mode 100644 index 000000000..55c04d2e2 --- /dev/null +++ b/luci-app-passwall2/luasrc/passwall2/util_sing-box.lua @@ -0,0 +1,1533 @@ +module("luci.passwall2.util_sing-box", package.seeall) +local api = require "luci.passwall2.api" +local uci = api.uci +local sys = api.sys +local jsonc = api.jsonc +local appname = api.appname +local fs = api.fs + +local new_port + +local function get_new_port() + if new_port then + new_port = tonumber(sys.exec(string.format("echo -n $(/usr/share/%s/app.sh get_new_port %s tcp)", appname, new_port + 1))) + else + new_port = tonumber(sys.exec(string.format("echo -n $(/usr/share/%s/app.sh get_new_port auto tcp)", appname))) + end + return new_port +end + +function gen_outbound(flag, node, tag, proxy_table) + local result = nil + if node and node ~= "nil" then + local node_id = node[".name"] + if tag == nil then + tag = node_id + end + + local proxy = 0 + local proxy_tag = "nil" + if proxy_table ~= nil and type(proxy_table) == "table" then + proxy = proxy_table.proxy or 0 + proxy_tag = proxy_table.tag or "nil" + end + + if node.type == "sing-box" then + proxy = 0 + if proxy_tag ~= "nil" then + node.detour = proxy_tag + end + end + + if node.type ~= "sing-box" then + local relay_port = node.port + new_port = get_new_port() + local config_file = string.format("%s_%s_%s.json", flag, tag, new_port) + if tag and node_id and tag ~= node_id then + config_file = string.format("%s_%s_%s_%s.json", flag, tag, node_id, new_port) + end + sys.call(string.format('/usr/share/%s/app.sh run_socks "%s"> /dev/null', + appname, + string.format("flag=%s node=%s bind=%s socks_port=%s config_file=%s relay_port=%s", + new_port, --flag + node_id, --node + "127.0.0.1", --bind + new_port, --socks port + config_file, --config file + (proxy == 1 and relay_port) and tostring(relay_port) or "" --relay port + ) + ) + ) + node = { + protocol = "socks", + address = "127.0.0.1", + port = new_port + } + end + + result = { + _flag_tag = node_id, + _flag_proxy = proxy, + _flag_proxy_tag = proxy_tag, + tag = tag, + type = node.protocol, + server = node.address, + server_port = tonumber(node.port), + detour = node.detour, + } + + local tls = nil + if node.tls == "1" then + local alpn = nil + if node.alpn and node.alpn ~= "default" then + alpn = {} + string.gsub(node.alpn, '[^' .. "," .. ']+', function(w) + table.insert(alpn, w) + end) + end + tls = { + enabled = true, + disable_sni = false, --不要在 ClientHello 中发送服务器名称. + server_name = node.tls_serverName, --用于验证返回证书上的主机名,除非设置不安全。它还包含在 ClientHello 中以支持虚拟主机,除非它是 IP 地址。 + insecure = (node.tls_allowInsecure == "1") and true or false, --接受任何服务器证书。 + alpn = alpn, --支持的应用层协议协商列表,按优先顺序排列。如果两个对等点都支持 ALPN,则选择的协议将是此列表中的一个,如果没有相互支持的协议则连接将失败。 + --min_version = "1.2", + --max_version = "1.3", + utls = { + enabled = (node.utls == "1" or node.reality == "1") and true or false, + fingerprint = node.fingerprint or "chrome" + }, + reality = { + enabled = (node.reality == "1") and true or false, + public_key = node.reality_publicKey, + short_id = node.reality_shortId + } + } + end + + local mux = nil + if node.mux == "1" then + mux = { + enabled = true, + protocol = node.mux_type or "h2mux", + max_connections = tonumber(node.mux_concurrency) or 4, + --min_streams = 4, + --max_streams = 0, + } + end + + local v2ray_transport = nil + + if node.transport == "http" then + v2ray_transport = { + type = "http", + host = { node.http_host }, + path = node.http_path or "/", + idle_timeout = (node.http_h2_health_check == "1") and node.http_h2_read_idle_timeout or nil, + ping_timeout = (node.http_h2_health_check == "1") and node.http_h2_health_check_timeout or nil, + } + --不强制执行 TLS。如果未配置 TLS,将使用纯 HTTP 1.1。 + end + + if node.transport == "ws" then + v2ray_transport = { + type = "ws", + path = node.ws_path or "/", + headers = (node.ws_host ~= nil) and { Host = node.ws_host } or nil, + max_early_data = tonumber(node.ws_maxEarlyData) or nil, + early_data_header_name = (node.ws_earlyDataHeaderName) and node.ws_earlyDataHeaderName or nil --要与 Xray-core 兼容,请将其设置为 Sec-WebSocket-Protocol。它需要与服务器保持一致。 + } + end + + if node.transport == "quic" then + v2ray_transport = { + type = "quic" + } + --没有额外的加密支持: 它基本上是重复加密。 并且 Xray-core 在这里与 v2ray-core 不兼容。 + end + + if node.transport == "grpc" then + v2ray_transport = { + type = "grpc", + serviceName = node.grpc_serviceName, + idle_timeout = tonumber(node.grpc_idle_timeout) or nil, + ping_timeout = tonumber(node.grpc_health_check_timeout) or nil, + permit_without_stream = (node.grpc_permit_without_stream == "1") and true or nil, + } + end + + local protocol_table = nil + + if node.protocol == "socks" then + protocol_table = { + version = "5", + username = (node.username and node.password) and node.username or nil, + password = (node.username and node.password) and node.password or nil, + udp_over_tcp = false, + } + end + + if node.protocol == "http" then + protocol_table = { + username = (node.username and node.password) and node.username or nil, + password = (node.username and node.password) and node.password or nil, + path = nil, + headers = nil, + tls = tls + } + end + + if node.protocol == "shadowsocks" then + protocol_table = { + method = node.method or nil, + password = node.password or "", + plugin = node.plugin and nil, + plugin_opts = node.plugin_opts and nil, + udp_over_tcp = node.uot == "1" and { + enabled = true, + version = 2 + } or nil, + multiplex = mux, + } + end + + if node.protocol == "shadowsocksr" then + protocol_table = { + method = node.method or nil, + password = node.password or "", + obfs = node.ssr_obfs, + obfs_param = node.ssr_obfs_param, + protocol = node.ssr_protocol, + protocol_param = node.ssr_protocol_param, + } + end + + if node.protocol == "trojan" then + protocol_table = { + password = node.password, + tls = tls, + multiplex = mux, + transport = v2ray_transport + } + end + + if node.protocol == "vmess" then + protocol_table = { + uuid = node.uuid, + security = node.security, + alter_id = 0, + global_padding = false, + authenticated_length = true, + tls = tls, + packet_encoding = "", --UDP 包编码。(空):禁用 packetaddr:由 v2ray 5+ 支持 xudp:由 xray 支持 + multiplex = mux, + transport = v2ray_transport, + } + end + + if node.protocol == "vless" then + protocol_table = { + uuid = node.uuid, + flow = (node.tls == '1' and node.flow) and node.flow or nil, + tls = tls, + packet_encoding = "xudp", --UDP 包编码。(空):禁用 packetaddr:由 v2ray 5+ 支持 xudp:由 xray 支持 + transport = v2ray_transport, + } + end + + if node.protocol == "wireguard" then + if node.wireguard_reserved then + local bytes = {} + if not node.wireguard_reserved:match("[^%d,]+") then + node.wireguard_reserved:gsub("%d+", function(b) + bytes[#bytes + 1] = tonumber(b) + end) + else + local result = api.bin.b64decode(node.wireguard_reserved) + for i = 1, #result do + bytes[i] = result:byte(i) + end + end + node.wireguard_reserved = #bytes > 0 and bytes or nil + end + protocol_table = { + system_interface = nil, + interface_name = nil, + local_address = node.wireguard_local_address, + private_key = node.wireguard_secret_key, + peer_public_key = node.wireguard_public_key, + pre_shared_key = node.wireguard_preSharedKey, + reserved = node.wireguard_reserved, + mtu = tonumber(node.wireguard_mtu), + } + end + + if node.protocol == "hysteria" then + protocol_table = { + up = node.hysteria_up_mbps .. " Mbps", + down = node.hysteria_down_mbps .. " Mbps", + up_mbps = tonumber(node.hysteria_up_mbps), + down_mbps = tonumber(node.hysteria_down_mbps), + obfs = node.hysteria_obfs, + auth = (node.hysteria_auth_type == "base64") and node.hysteria_auth_password or nil, + auth_str = (node.hysteria_auth_type == "string") and node.hysteria_auth_password or nil, + recv_window_conn = tonumber(node.hysteria_recv_window_conn), + recv_window = tonumber(node.hysteria_recv_window), + disable_mtu_discovery = (node.hysteria_disable_mtu_discovery == "1") and true or false, + tls = tls, + } + end + + if node.protocol == "shadowtls" then + protocol_table = { + version = tonumber(node.shadowtls_version), + password = (node.shadowtls_version == "2" or node.shadowtls_version == "3") and node.password or nil, + tls = tls, + } + end + + if node.protocol == "tuic" then + protocol_table = { + uuid = node.uuid, + password = node.password, + congestion_control = node.tuic_congestion_control or "cubic", + udp_relay_mode = node.tuic_udp_relay_mode or "native", + udp_over_stream = false, + zero_rtt_handshake = (node.tuic_zero_rtt_handshake == "1") and true or false, + heartbeat = tonumber(node.tuic_heartbeat), + tls = tls, + } + end + + if node.protocol == "hysteria2" then + protocol_table = { + up_mbps = (node.hysteria2_up_mbps and tonumber(node.hysteria2_up_mbps)) and tonumber(node.hysteria2_up_mbps) or nil, + down_mbps = (node.hysteria2_down_mbps and tonumber(node.hysteria2_down_mbps)) and tonumber(node.hysteria2_down_mbps) or nil, + obfs = { + type = node.hysteria2_obfs_type, + password = node.hysteria2_obfs_password + }, + password = node.hysteria2_auth_password or nil, + tls = tls, + } + end + + if protocol_table then + for key, value in pairs(protocol_table) do + result[key] = value + end + end + end + return result +end + +function gen_config_server(node) + local outbounds = { + { type = "direct", tag = "direct" }, + { type = "block", tag = "block" } + } + + local tls = nil + + if node.tls == "1" then + tls = { + enabled = true, + certificate_path = node.tls_certificateFile, + key_path = node.tls_keyFile, + } + end + + local v2ray_transport = nil + + if node.transport == "http" then + v2ray_transport = { + type = "http", + host = node.http_host, + path = node.http_path or "/", + } + end + + if node.transport == "ws" then + v2ray_transport = { + type = "ws", + path = node.ws_path or "/", + headers = (node.ws_host ~= nil) and { Host = node.ws_host } or nil, + early_data_header_name = (node.ws_earlyDataHeaderName) and node.ws_earlyDataHeaderName or nil --要与 Xray-core 兼容,请将其设置为 Sec-WebSocket-Protocol。它需要与服务器保持一致。 + } + end + + if node.transport == "quic" then + v2ray_transport = { + type = "quic" + } + --没有额外的加密支持: 它基本上是重复加密。 并且 Xray-core 在这里与 v2ray-core 不兼容。 + end + + if node.transport == "grpc" then + v2ray_transport = { + type = "grpc", + serviceName = node.grpc_serviceName, + } + end + + local inbound = { + type = node.protocol, + tag = "inbound", + listen = (node.bind_local == "1") and "127.0.0.1" or "::", + listen_port = tonumber(node.port), + } + + local protocol_table = nil + + if node.protocol == "mixed" then + protocol_table = { + users = (node.auth == "1") and { + { + username = node.username, + password = node.password + } + } or nil, + set_system_proxy = false + } + end + + if node.protocol == "socks" then + protocol_table = { + users = (node.auth == "1") and { + { + username = node.username, + password = node.password + } + } or nil + } + end + + if node.protocol == "http" then + protocol_table = { + users = (node.auth == "1") and { + { + username = node.username, + password = node.password + } + } or nil, + tls = tls, + } + end + + if node.protocol == "shadowsocks" then + protocol_table = { + method = node.method, + password = node.password, + } + end + + if node.protocol == "vmess" then + if node.uuid then + local users = {} + for i = 1, #node.uuid do + users[i] = { + name = node.uuid[i], + uuid = node.uuid[i], + alterId = 0, + } + end + protocol_table = { + users = users, + tls = tls, + transport = v2ray_transport, + } + end + end + + if node.protocol == "vless" then + if node.uuid then + local users = {} + for i = 1, #node.uuid do + users[i] = { + name = node.uuid[i], + uuid = node.uuid[i], + flow = node.flow, + } + end + protocol_table = { + users = users, + tls = tls, + transport = v2ray_transport, + } + end + end + + if node.protocol == "trojan" then + if node.uuid then + local users = {} + for i = 1, #node.uuid do + users[i] = { + name = node.uuid[i], + uuid = node.uuid[i], + } + end + protocol_table = { + users = users, + tls = tls, + fallback = nil, + fallback_for_alpn = nil, + transport = v2ray_transport, + } + end + end + + if node.protocol == "naive" then + protocol_table = { + users = { + { + username = node.username, + password = node.password + } + }, + tls = tls, + } + end + + if node.protocol == "hysteria" then + protocol_table = { + up = node.hysteria_up_mbps .. " Mbps", + down = node.hysteria_down_mbps .. " Mbps", + up_mbps = tonumber(node.hysteria_up_mbps), + down_mbps = tonumber(node.hysteria_down_mbps), + obfs = node.hysteria_obfs, + users = { + { + name = "user1", + auth = (node.hysteria_auth_type == "base64") and node.hysteria_auth_password or nil, + auth_str = (node.hysteria_auth_type == "string") and node.hysteria_auth_password or nil, + } + }, + recv_window_conn = node.hysteria_recv_window_conn and tonumber(node.hysteria_recv_window_conn) or nil, + recv_window_client = node.hysteria_recv_window_client and tonumber(node.hysteria_recv_window_client) or nil, + max_conn_client = node.hysteria_max_conn_client and tonumber(node.hysteria_max_conn_client) or nil, + disable_mtu_discovery = (node.hysteria_disable_mtu_discovery == "1") and true or false, + tls = tls, + } + end + + if node.protocol == "tuic" then + protocol_table = { + users = { + { + name = "user1", + uuid = node.uuid, + password = node.password + } + }, + congestion_control = node.tuic_congestion_control or "cubic", + zero_rtt_handshake = (node.tuic_zero_rtt_handshake == "1") and true or false, + heartbeat = node.tuic_heartbeat .. "s", + tls = tls, + } + end + + if node.protocol == "hysteria2" then + protocol_table = { + up_mbps = (node.hysteria2_ignore_client_bandwidth ~= "1" and node.hysteria2_up_mbps and tonumber(node.hysteria2_up_mbps)) and tonumber(node.hysteria2_up_mbps) or nil, + down_mbps = (node.hysteria2_ignore_client_bandwidth ~= "1" and node.hysteria2_down_mbps and tonumber(node.hysteria2_down_mbps)) and tonumber(node.hysteria2_down_mbps) or nil, + obfs = { + type = node.hysteria2_obfs_type, + password = node.hysteria2_obfs_password + }, + users = { + { + name = "user1", + password = node.hysteria2_auth_password or nil, + } + }, + ignore_client_bandwidth = (node.hysteria2_ignore_client_bandwidth == "1") and true or false, + tls = tls, + } + end + + if node.protocol == "direct" then + protocol_table = { + network = (node.d_protocol ~= "TCP,UDP") and node.d_protocol or nil, + override_address = node.d_address, + override_port = tonumber(node.d_port) + } + end + + if protocol_table then + for key, value in pairs(protocol_table) do + inbound[key] = value + end + end + + local route = { + rules = { + { + ip_cidr = { "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16" }, + outbound = (node.accept_lan == nil or node.accept_lan == "0") and "block" or "direct" + } + } + } + + if node.outbound_node and node.outbound_node ~= "nil" then + local outbound = nil + if node.outbound_node == "_iface" and node.outbound_node_iface then + outbound = { + type = "direct", + tag = "outbound", + bind_interface = node.outbound_node_iface, + routing_mark = 255, + } + sys.call("mkdir -p /tmp/etc/passwall2/iface && touch /tmp/etc/passwall2/iface/" .. node.outbound_node_iface) + else + local outbound_node_t = uci:get_all("passwall2", node.outbound_node) + if node.outbound_node == "_socks" or node.outbound_node == "_http" then + outbound_node_t = { + type = node.type, + protocol = node.outbound_node:gsub("_", ""), + address = node.outbound_node_address, + port = tonumber(node.outbound_node_port), + username = (node.outbound_node_username and node.outbound_node_username ~= "") and node.outbound_node_username or nil, + password = (node.outbound_node_password and node.outbound_node_password ~= "") and node.outbound_node_password or nil, + } + end + outbound = require("luci.passwall2.util_sing-box").gen_outbound(nil, outbound_node_t, "outbound") + end + if outbound then + route.final = "outbound" + table.insert(outbounds, 1, outbound) + end + end + + local config = { + log = { + disabled = (not node or node.log == "0") and true or false, + level = node.loglevel or "info", + timestamp = true, + --output = logfile, + }, + inbounds = { inbound }, + outbounds = outbounds, + route = route + } + + for index, value in ipairs(config.outbounds) do + for k, v in pairs(config.outbounds[index]) do + if k:find("_") == 1 then + config.outbounds[index][k] = nil + end + end + end + + return config +end + +function gen_config(var) + local flag = var["-flag"] + local log = var["-log"] or "0" + local loglevel = var["-loglevel"] or "warn" + local logfile = var["-logfile"] or "/dev/null" + local node_id = var["-node"] + local tcp_proxy_way = var["-tcp_proxy_way"] + local redir_port = var["-redir_port"] + local local_socks_address = var["-local_socks_address"] or "0.0.0.0" + local local_socks_port = var["-local_socks_port"] + local local_socks_username = var["-local_socks_username"] + local local_socks_password = var["-local_socks_password"] + local local_http_address = var["-local_http_address"] or "0.0.0.0" + local local_http_port = var["-local_http_port"] + local local_http_username = var["-local_http_username"] + local local_http_password = var["-local_http_password"] + local dns_listen_port = var["-dns_listen_port"] + local direct_dns_port = var["-direct_dns_port"] + local direct_dns_udp_server = var["-direct_dns_udp_server"] + local direct_dns_query_strategy = var["-direct_dns_query_strategy"] + local remote_dns_port = var["-remote_dns_port"] + local remote_dns_udp_server = var["-remote_dns_udp_server"] + local remote_dns_tcp_server = var["-remote_dns_tcp_server"] + local remote_dns_doh_url = var["-remote_dns_doh_url"] + local remote_dns_doh_host = var["-remote_dns_doh_host"] + local remote_dns_query_strategy = var["-remote_dns_query_strategy"] + local remote_dns_fake = var["-remote_dns_fake"] + local dns_cache = var["-dns_cache"] + local tags = var["-tags"] + + local dns_direct_domains = {} + local dns_remote_domains = {} + local dns = nil + local inbounds = {} + local outbounds = {} + + local singbox_settings = uci:get_all(appname, "@global_singbox[0]") or {} + + local route = { + rules = {}, + geoip = { + path = singbox_settings.geoip_path or "/tmp/singbox/geoip.db", + download_url = singbox_settings.geoip_url or nil, + download_detour = nil, + }, + geosite = { + path = singbox_settings.geosite_path or "/tmp/singbox/geosite.db", + download_url = singbox_settings.geosite_url or nil, + download_detour = nil, + }, + } + + local experimental = nil + + local nodes = {} + if node_id then + local node = uci:get_all(appname, node_id) + if node then + nodes[node_id] = node + end + end + + if local_socks_port then + local inbound = { + type = "socks", + tag = "socks-in", + listen = local_socks_address, + listen_port = tonumber(local_socks_port), + sniff = true + } + if local_socks_username and local_socks_password and local_socks_username ~= "" and local_socks_password ~= "" then + inbound.users = { + { + username = local_socks_username, + password = local_socks_password + } + } + end + table.insert(inbounds, inbound) + end + + if local_http_port then + local inbound = { + type = "http", + tag = "http-in", + listen = local_http_address, + listen_port = tonumber(local_http_port) + } + if local_http_username and local_http_password and local_http_username ~= "" and local_http_password ~= "" then + inbound.users = { + { + username = local_http_username, + password = local_http_password + } + } + end + table.insert(inbounds, inbound) + end + + if redir_port then + local inbound_tproxy = { + type = "tproxy", + tag = "tproxy", + listen = "::", + listen_port = tonumber(redir_port), + sniff = true, + sniff_override_destination = (singbox_settings.sniff_override_destination == "1") and true or false + } + if tcp_proxy_way ~= "tproxy" then + local inbound = { + type = "redirect", + tag = "redirect_tcp", + listen = "::", + listen_port = tonumber(redir_port), + sniff = true, + sniff_override_destination = (singbox_settings.sniff_override_destination == "1") and true or false, + } + table.insert(inbounds, inbound) + + inbound_tproxy.tag = "tproxy_udp" + inbound_tproxy.network = "udp" + end + + table.insert(inbounds, inbound_tproxy) + end + + local dns_outTag = nil + + for k, v in pairs(nodes) do + local node = v + if node.protocol == "_shunt" then + local rules = {} + + local preproxy_enabled = node.preproxy_enabled == "1" + local preproxy_tag = "main" + local preproxy_node_id = node["main_node"] + local preproxy_node = preproxy_enabled and preproxy_node_id and uci:get_all(appname, preproxy_node_id) or nil + + if not preproxy_node and preproxy_node_id and api.parseURL(preproxy_node_id) then + local parsed1 = api.parseURL(preproxy_node_id) + local _node = { + type = "sing-box", + protocol = parsed1.protocol, + username = parsed1.username, + password = parsed1.password, + address = parsed1.host, + port = parsed1.port, + } + local preproxy_outbound = gen_outbound(flag, _node, preproxy_tag) + if preproxy_outbound then + table.insert(outbounds, preproxy_outbound) + else + preproxy_enabled = false + end + elseif preproxy_node and api.is_normal_node(preproxy_node) then + local preproxy_outbound = gen_outbound(flag, preproxy_node, preproxy_tag) + if preproxy_outbound then + table.insert(outbounds, preproxy_outbound) + else + preproxy_enabled = false + end + end + + local function gen_shunt_node(rule_name, _node_id, as_proxy) + if not rule_name then return nil, nil end + if not _node_id then _node_id = node[rule_name] or "nil" end + local rule_outboundTag + if _node_id == "_direct" then + rule_outboundTag = "direct" + elseif _node_id == "_blackhole" then + rule_outboundTag = "block" + elseif _node_id == "_default" and rule_name ~= "default" then + rule_outboundTag = "default" + elseif api.parseURL(_node_id) then + local parsed1 = api.parseURL(_node_id) + local _node = { + type = "sing-box", + protocol = parsed1.protocol, + username = parsed1.username, + password = parsed1.password, + address = parsed1.host, + port = parsed1.port, + } + local _outbound = gen_outbound(flag, _node, rule_name) + if _outbound then + table.insert(outbounds, _outbound) + rule_outboundTag = rule_name + end + elseif _node_id ~= "nil" then + local _node = uci:get_all(appname, _node_id) + if not _node then return nil, nil end + + if api.is_normal_node(_node) then + local proxy = preproxy_enabled and node[rule_name .. "_proxy_tag"] == preproxy_tag and _node_id ~= preproxy_node_id + local copied_outbound + for index, value in ipairs(outbounds) do + if value["_flag_tag"] == _node_id and value["_flag_proxy_tag"] == preproxy_tag then + copied_outbound = api.clone(value) + break + end + end + if copied_outbound then + copied_outbound.tag = rule_name + table.insert(outbounds, copied_outbound) + rule_outboundTag = rule_name + else + if proxy then + local pre_proxy = nil + if _node.type ~= "sing-box" then + pre_proxy = true + else + if _node.flow == "xtls-rprx-vision" then + pre_proxy = true + end + end + if pre_proxy then + new_port = get_new_port() + table.insert(inbounds, { + type = "direct", + tag = "proxy_" .. rule_name, + listen = "127.0.0.1", + listen_port = new_port, + override_address = _node.address, + override_port = tonumber(_node.port), + }) + if _node.tls_serverName == nil then + _node.tls_serverName = _node.address + end + _node.address = "127.0.0.1" + _node.port = new_port + table.insert(rules, 1, { + inbound = {"proxy_" .. rule_name}, + outbound = preproxy_tag, + }) + end + end + local _outbound = gen_outbound(flag, _node, rule_name, { proxy = proxy and 1 or 0, tag = proxy and preproxy_tag or nil }) + if _outbound then + table.insert(outbounds, _outbound) + if proxy then preproxy_used = true end + rule_outboundTag = rule_name + end + end + elseif _node.protocol == "_iface" then + if _node.iface then + local _outbound = { + type = "direct", + tag = rule_name, + bind_interface = _node.iface, + routing_mark = 255, + } + table.insert(outbounds, _outbound) + rule_outboundTag = rule_name + sys.call("touch /tmp/etc/passwall2/iface/" .. _node.iface) + end + end + end + return rule_outboundTag + end + --default_node + local default_node_id = node.default_node or "_direct" + local default_outboundTag = gen_shunt_node("default", default_node_id) + --shunt rule + uci:foreach(appname, "shunt_rules", function(e) + local outboundTag = gen_shunt_node(e[".name"]) + if outboundTag and e.remarks then + if outboundTag == "default" then + outboundTag = default_outboundTag + end + local protocols = nil + if e["protocol"] and e["protocol"] ~= "" then + protocols = {} + string.gsub(e["protocol"], '[^' .. " " .. ']+', function(w) + table.insert(protocols, w) + end) + end + + local rule = { + outbound = outboundTag, + invert = false, --匹配反选 + protocol = protocols + } + + if e.network then + local network = {} + string.gsub(e.network, '[^' .. "," .. ']+', function(w) + table.insert(network, w) + end) + rule.network = network + end + + if e.source then + local source_geoip = {} + local source_ip_cidr = {} + string.gsub(e.source, '[^' .. " " .. ']+', function(w) + if w:find("geoip") == 1 then + table.insert(source_geoip, w) + else + table.insert(source_ip_cidr, w) + end + end) + rule.source_geoip = #source_geoip > 0 and source_geoip or nil + rule.source_ip_cidr = #source_ip_cidr > 0 and source_ip_cidr or nil + end + + if e.sourcePort then + local source_port = {} + local source_port_range = {} + string.gsub(e.sourcePort, '[^' .. "," .. ']+', function(w) + if tonumber(w) and tonumber(w) >= 1 and tonumber(w) <= 65535 then + table.insert(source_port, tonumber(w)) + else + table.insert(source_port_range, w) + end + end) + rule.source_port = #source_port > 0 and source_port or nil + rule.source_port_range = #source_port_range > 0 and source_port_range or nil + end + + if e.port then + local port = {} + local port_range = {} + string.gsub(e.port, '[^' .. "," .. ']+', function(w) + if tonumber(w) and tonumber(w) >= 1 and tonumber(w) <= 65535 then + table.insert(port, tonumber(w)) + else + table.insert(port_range, w) + end + end) + rule.port = #port > 0 and port or nil + rule.port_range = #port_range > 0 and port_range or nil + end + + if e.domain_list then + local domain = {} + local domain_suffix = {} + local domain_keyword = {} + local domain_regex = {} + local geosite = {} + string.gsub(e.domain_list, '[^' .. "\r\n" .. ']+', function(w) + if w:find("geosite:") == 1 then + table.insert(geosite, w:sub(1 + #"geosite:")) + elseif w:find("regexp:") == 1 then + table.insert(domain_regex, w:sub(1 + #"regexp:")) + elseif w:find("full:") == 1 then + table.insert(domain, w:sub(1 + #"full:")) + elseif w:find("domain:") == 1 then + table.insert(domain_keyword, w:sub(1 + #"domain:")) + else + table.insert(domain, w) + end + + if outboundTag == "direct" then + table.insert(dns_direct_domains, w) + else + if outboundTag ~= "nil" then + table.insert(dns_remote_domains, w) + end + end + end) + + rule.domain = #domain > 0 and domain or nil + rule.domain_suffix = #domain_suffix > 0 and domain_suffix or nil + rule.domain_keyword = #domain_keyword > 0 and domain_keyword or nil + rule.domain_regex = #domain_regex > 0 and domain_regex or nil + rule.geosite = #geosite > 0 and geosite or nil + end + + if e.ip_list then + local ip_cidr = {} + local geoip = {} + string.gsub(e.ip_list, '[^' .. "\r\n" .. ']+', function(w) + if w:find("geoip:") == 1 then + table.insert(geoip, w:sub(1 + #"geoip:")) + else + table.insert(ip_cidr, w) + end + end) + + rule.ip_cidr = #ip_cidr > 0 and ip_cidr or nil + rule.geoip = #geoip > 0 and geoip or nil + end + + table.insert(rules, rule) + end + end) + + if default_outboundTag then + route.final = default_outboundTag + dns_outTag = default_outboundTag + end + + for index, value in ipairs(rules) do + table.insert(route.rules, rules[index]) + end + else + local outbound = nil + if node.protocol == "_iface" then + if node.iface then + outbound = { + type = "direct", + tag = "outbound", + bind_interface = node.iface, + routing_mark = 255, + } + sys.call("touch /tmp/etc/passwall2/iface/" .. node.iface) + end + else + outbound = gen_outbound(flag, node) + end + if outbound then + dns_outTag = outbound.tag + table.insert(outbounds, outbound) + end + + route.final = node_id + end + end + + if dns_listen_port then + dns = { + servers = {}, + rules = {}, + disable_cache = (dns_cache and dns_cache == "0") and true or false, + disable_expire = false, --禁用 DNS 缓存过期。 + independent_cache = false, --使每个 DNS 服务器的缓存独立,以满足特殊目的。如果启用,将轻微降低性能。 + reverse_mapping = true, --在响应 DNS 查询后存储 IP 地址的反向映射以为路由目的提供域名。 + fakeip = nil, + } + + if true then + local dns_tag = "remote" + + local domain = {} + local domain_suffix = {} + local domain_keyword = {} + local domain_regex = {} + local geosite = {} + for index, value in ipairs(dns_remote_domains) do + if value:find("geosite:") == 1 then + table.insert(geosite, value:sub(1 + #"geosite:")) + elseif value:find("regexp:") == 1 then + table.insert(domain_regex, value:sub(1 + #"regexp:")) + elseif value:find("full:") == 1 then + table.insert(domain, value:sub(1 + #"full:")) + elseif value:find("domain:") == 1 then + table.insert(domain_keyword, value:sub(1 + #"domain:")) + else + table.insert(domain, value) + end + end + local remote_rule = { + server = dns_tag, + domain = #domain > 0 and domain or nil, + domain_suffix = #domain_suffix > 0 and domain_suffix or nil, + domain_keyword = #domain_keyword > 0 and domain_keyword or nil, + domain_regex = #domain_regex > 0 and domain_regex or nil, + geosite = #geosite > 0 and geosite or nil, + disable_cache = true, + } + + local remote_strategy = "prefer_ipv6" + if remote_dns_query_strategy == "UseIPv4" then + remote_strategy = "ipv4_only" + elseif remote_dns_query_strategy == "UseIPv6" then + remote_strategy = "ipv6_only" + end + + local server = { + tag = dns_tag, + address_strategy = "prefer_ipv4", + strategy = remote_strategy, + detour = dns_outTag, + } + + local rule_server = dns_tag + + if remote_dns_udp_server then + local server_port = tonumber(remote_dns_port) or 53 + server.address = "udp://" .. remote_dns_udp_server .. ":" .. server_port + end + + if remote_dns_tcp_server then + server.address = remote_dns_tcp_server + end + + if remote_dns_doh_url and remote_dns_doh_host then + server.address = remote_dns_doh_url + end + + table.insert(dns.servers, server) + + if remote_dns_fake then + dns.fakeip = { + enabled = true, + inet4_range = "198.18.0.0/16", + inet6_range = "fc00::/18", + } + + table.insert(dns.servers, { + tag = dns_tag .. "_fakeip", + address = "fakeip", + strategy = remote_strategy, + }) + + rule_server = dns_tag .. "_fakeip" + + if tags and tags:find("with_clash_api") then + if not experimental then + experimental = {} + end + experimental.clash_api = { + store_fakeip = true, + cache_file = "/tmp/singbox/passwall2_" .. flag .. ".db" + } + end + end + + if remote_rule.domain or remote_rule.domain_suffix or remote_rule.domain_keyword or remote_rule.domain_regex or remote_rule.geosite then + local rule = api.clone(remote_rule) + rule.server = rule_server + table.insert(dns.rules, rule) + end + end + + if direct_dns_udp_server then + local nodes_domain_text = sys.exec('uci show passwall2 | grep ".address=" | cut -d "\'" -f 2 | grep "[a-zA-Z]$" | sort -u') + string.gsub(nodes_domain_text, '[^' .. "\r\n" .. ']+', function(w) + table.insert(dns_direct_domains, "full:" .. w) + end) + + local dns_tag = "direct" + + local domain = {} + local domain_suffix = {} + local domain_keyword = {} + local domain_regex = {} + local geosite = {} + for index, value in ipairs(dns_direct_domains) do + if value:find("geosite:") == 1 then + table.insert(geosite, value:sub(1 + #"geosite:")) + elseif value:find("regexp:") == 1 then + table.insert(domain_regex, value:sub(1 + #"regexp:")) + elseif value:find("full:") == 1 then + table.insert(domain, value:sub(1 + #"full:")) + elseif value:find("domain:") == 1 then + table.insert(domain_keyword, value:sub(1 + #"domain:")) + else + table.insert(domain, value) + end + end + local direct_rule = { + server = dns_tag, + domain = domain, + domain_suffix = #domain_suffix > 0 and domain_suffix or nil, + domain_keyword = #domain_keyword > 0 and domain_keyword or nil, + domain_regex = #domain_regex > 0 and domain_regex or nil, + geosite = #geosite > 0 and geosite or nil, + disable_cache = false, + } + table.insert(dns.rules, direct_rule) + + local direct_strategy = "prefer_ipv6" + if direct_dns_query_strategy == "UseIPv4" then + direct_strategy = "ipv4_only" + elseif direct_dns_query_strategy == "UseIPv6" then + direct_strategy = "ipv6_only" + end + + local port = tonumber(direct_dns_port) or 53 + + table.insert(dns.servers, { + tag = dns_tag, + address = "udp://" .. direct_dns_udp_server .. ":" .. port, + address_strategy = "prefer_ipv6", + strategy = direct_strategy, + detour = "direct", + }) + end + + table.insert(dns.servers, { + tag = "block", + address = "rcode://refused", + }) + + table.insert(inbounds, { + type = "direct", + tag = "dns-in", + listen = "127.0.0.1", + listen_port = tonumber(dns_listen_port), + sniff = true, + }) + table.insert(outbounds, { + type = "dns", + tag = "dns-out", + }) + table.insert(route.rules, 1, { + protocol = "dns", + inbound = { + "dns-in" + }, + outbound = "dns-out" + }) + + local default_dns_flag = "remote" + if node_id and redir_port then + local node = uci:get_all(appname, node_id) + if node.protocol == "_shunt" then + if node.default_node == "_direct" then + default_dns_flag = "direct" + end + end + end + dns.final = default_dns_flag + end + + if inbounds or outbounds then + local config = { + log = { + disabled = log == "0" and true or false, + level = loglevel, + timestamp = true, + output = logfile, + }, + -- DNS + dns = dns, + -- 传入连接 + inbounds = inbounds, + -- 传出连接 + outbounds = outbounds, + -- 路由 + route = route, + --实验性 + experimental = experimental, + } + table.insert(outbounds, { + type = "direct", + tag = "direct", + routing_mark = 255, + domain_strategy = "prefer_ipv6", + }) + table.insert(outbounds, { + type = "block", + tag = "block" + }) + for index, value in ipairs(config.outbounds) do + for k, v in pairs(config.outbounds[index]) do + if k:find("_") == 1 then + config.outbounds[index][k] = nil + end + end + end + return jsonc.stringify(config, 1) + end +end + +function gen_proto_config(var) + local local_socks_address = var["-local_socks_address"] or "0.0.0.0" + local local_socks_port = var["-local_socks_port"] + local local_socks_username = var["-local_socks_username"] + local local_socks_password = var["-local_socks_password"] + local local_http_address = var["-local_http_address"] or "0.0.0.0" + local local_http_port = var["-local_http_port"] + local local_http_username = var["-local_http_username"] + local local_http_password = var["-local_http_password"] + local server_proto = var["-server_proto"] + local server_address = var["-server_address"] + local server_port = var["-server_port"] + local server_username = var["-server_username"] + local server_password = var["-server_password"] + + local inbounds = {} + local outbounds = {} + + if local_socks_address and local_socks_port then + local inbound = { + type = "socks", + tag = "socks-in", + listen = local_socks_address, + listen_port = tonumber(local_socks_port), + } + if local_socks_username and local_socks_password and local_socks_username ~= "" and local_socks_password ~= "" then + inbound.users = { + username = local_socks_username, + password = local_socks_password + } + end + table.insert(inbounds, inbound) + end + + if local_http_address and local_http_port then + local inbound = { + type = "http", + tag = "http-in", + tls = nil, + listen = local_http_address, + listen_port = tonumber(local_http_port), + } + if local_http_username and local_http_password and local_http_username ~= "" and local_http_password ~= "" then + inbound.users = { + { + username = local_http_username, + password = local_http_password + } + } + end + table.insert(inbounds, inbound) + end + + if server_proto ~= "nil" and server_address ~= "nil" and server_port ~= "nil" then + local outbound = { + type = server_proto, + tag = "out", + server = server_address, + server_port = tonumber(server_port), + username = (server_username and server_password) and server_username or nil, + password = (server_username and server_password) and server_password or nil, + } + if outbound then table.insert(outbounds, outbound) end + end + + local config = { + log = { + disabled = true, + level = "warn", + timestamp = true, + }, + -- 传入连接 + inbounds = inbounds, + -- 传出连接 + outbounds = outbounds, + } + return jsonc.stringify(config, 1) +end + +function gen_dns_config(var) + local dns_listen_port = var["-dns_listen_port"] + local dns_query_strategy = var["-dns_query_strategy"] + local dns_out_tag = var["-dns_out_tag"] + local dns_client_ip = var["-dns_client_ip"] + local direct_dns_server = var["-direct_dns_server"] + local direct_dns_port = var["-direct_dns_port"] + local direct_dns_udp_server = var["-direct_dns_udp_server"] + local direct_dns_tcp_server = var["-direct_dns_tcp_server"] + local direct_dns_doh_url = var["-direct_dns_doh_url"] + local direct_dns_doh_host = var["-direct_dns_doh_host"] + local remote_dns_server = var["-remote_dns_server"] + local remote_dns_port = var["-remote_dns_port"] + local remote_dns_udp_server = var["-remote_dns_udp_server"] + local remote_dns_tcp_server = var["-remote_dns_tcp_server"] + local remote_dns_doh_url = var["-remote_dns_doh_url"] + local remote_dns_doh_host = var["-remote_dns_doh_host"] + local remote_dns_outbound_socks_address = var["-remote_dns_outbound_socks_address"] + local remote_dns_outbound_socks_port = var["-remote_dns_outbound_socks_port"] + local remote_dns_fake = var["-remote_dns_fake"] + local dns_cache = var["-dns_cache"] + local log = var["-log"] or "0" + local loglevel = var["-loglevel"] or "warn" + local logfile = var["-logfile"] or "/dev/null" + + local inbounds = {} + local outbounds = {} + local dns = nil + local route = nil + + if dns_listen_port then + route = { + rules = {} + } + + dns = { + servers = {}, + rules = {}, + disable_cache = (dns_cache and dns_cache == "0") and true or false, + disable_expire = false, --禁用 DNS 缓存过期。 + independent_cache = false, --使每个 DNS 服务器的缓存独立,以满足特殊目的。如果启用,将轻微降低性能。 + reverse_mapping = true, --在响应 DNS 查询后存储 IP 地址的反向映射以为路由目的提供域名。 + fakeip = nil, + } + + if dns_out_tag == "remote" then + local server = { + tag = dns_out_tag, + address_strategy = "prefer_ipv4", + strategy = (dns_query_strategy and dns_query_strategy ~= "UseIP") and "ipv4_only" or "prefer_ipv6", + detour = "remote-out", + } + + if remote_dns_fake then + server.address = "fakeip" + dns.fakeip = { + enabled = true, + inet4_range = "198.18.0.0/16", + inet6_range = "fc00::/18", + } + end + + if remote_dns_udp_server then + local server_port = tonumber(remote_dns_port) or 53 + server.address = "udp://" .. remote_dns_udp_server .. ":" .. server_port + end + + if remote_dns_tcp_server then + server.address = remote_dns_tcp_server + end + + if remote_dns_doh_url and remote_dns_doh_host then + server.address = remote_dns_doh_url + end + + table.insert(dns.servers, server) + + table.insert(outbounds, 1, { + type = "socks", + tag = "remote-out", + server = remote_dns_outbound_socks_address, + server_port = tonumber(remote_dns_outbound_socks_port), + }) + + table.insert(route.rules, { + network = {"tcp", "udp"}, + outbound = "remote-out" + }) + elseif dns_out_tag == "direct" then + local server = { + tag = dns_out_tag, + address_strategy = "prefer_ipv6", + strategy = (dns_query_strategy and dns_query_strategy ~= "UseIP") and "ipv4_only" or "prefer_ipv6", + detour = "direct-out", + } + + if direct_dns_udp_server then + local server_port = tonumber(direct_dns_port) or 53 + server.address = "udp://" .. direct_dns_udp_server .. ":" .. server_port + end + + if direct_dns_tcp_server then + local server_port = tonumber(direct_dns_port) or 53 + server.address = direct_dns_tcp_server .. ":" .. server_port + end + + if direct_dns_doh_url and direct_dns_doh_host then + local server_port = tonumber(direct_dns_port) or 443 + server.address = direct_dns_doh_url + end + + table.insert(dns.servers, server) + + table.insert(outbounds, 1, { + type = "direct", + tag = "direct-out", + routing_mark = 255, + domain_strategy = (dns_query_strategy and dns_query_strategy ~= "UseIP") and "ipv4_only" or "prefer_ipv6", + }) + end + + table.insert(inbounds, { + type = "direct", + tag = "dns-in", + listen = "127.0.0.1", + listen_port = tonumber(dns_listen_port), + sniff = true, + }) + + table.insert(outbounds, { + type = "dns", + tag = "dns-out", + }) + + table.insert(route.rules, 1, { + protocol = "dns", + inbound = { + "dns-in" + }, + outbound = "dns-out" + }) + end + + if inbounds or outbounds then + local config = { + log = { + disabled = log == "0" and true or false, + level = loglevel, + timestamp = true, + output = logfile, + }, + -- DNS + dns = dns, + -- 传入连接 + inbounds = inbounds, + -- 传出连接 + outbounds = outbounds, + -- 路由 + route = route + } + return jsonc.stringify(config, 1) + end + +end + +_G.gen_config = gen_config +_G.gen_proto_config = gen_proto_config +_G.gen_dns_config = gen_dns_config + +if arg[1] then + local func =_G[arg[1]] + if func then + print(func(api.get_function_args(arg))) + end +end diff --git a/luci-app-passwall2/luasrc/passwall2/util_xray.lua b/luci-app-passwall2/luasrc/passwall2/util_xray.lua index dc84ad82c..6c7bd04e7 100644 --- a/luci-app-passwall2/luasrc/passwall2/util_xray.lua +++ b/luci-app-passwall2/luasrc/passwall2/util_xray.lua @@ -47,8 +47,8 @@ function gen_outbound(flag, node, tag, proxy_table) proxy_tag = proxy_table.tag or "nil" end - if node.type == "V2ray" or node.type == "Xray" then - if node.type == "Xray" and node.tlsflow == "xtls-rprx-vision" then + if node.type == "Xray" then + if node.flow == "xtls-rprx-vision" then else proxy = 0 if proxy_tag ~= "nil" then @@ -60,7 +60,7 @@ function gen_outbound(flag, node, tag, proxy_table) end end - if node.type ~= "V2ray" and node.type ~= "Xray" then + if node.type ~= "Xray" then local relay_port = node.port new_port = get_new_port() local config_file = string.format("%s_%s_%s.json", flag, tag, new_port) @@ -87,10 +87,10 @@ function gen_outbound(flag, node, tag, proxy_table) node.stream_security = "none" end - if node.type == "V2ray" or node.type == "Xray" then + if node.type == "Xray" then if node.tls and node.tls == "1" then node.stream_security = "tls" - if node.type == "Xray" and node.reality and node.reality == "1" then + if node.reality and node.reality == "1" then node.stream_security = "reality" end end @@ -133,7 +133,7 @@ function gen_outbound(flag, node, tag, proxy_table) tlsSettings = (node.stream_security == "tls") and { serverName = node.tls_serverName, allowInsecure = (node.tls_allowInsecure == "1") and true or false, - fingerprint = (node.type == "Xray" and node.fingerprint and node.fingerprint ~= "") and node.fingerprint or nil + fingerprint = (node.type == "Xray" and node.utls == "1" and node.fingerprint and node.fingerprint ~= "") and node.fingerprint or nil } or nil, realitySettings = (node.stream_security == "reality") and { serverName = node.tls_serverName, @@ -204,7 +204,7 @@ function gen_outbound(flag, node, tag, proxy_table) level = 0, security = (node.protocol == "vmess") and node.security or nil, encryption = node.encryption or "none", - flow = (node.protocol == "vless" and node.tls == '1' and node.tlsflow) and node.tlsflow or nil + flow = (node.protocol == "vless" and node.tls == '1' and node.flow) and node.flow or nil } } } @@ -267,7 +267,7 @@ function gen_config_server(node) for i = 1, #node.uuid do clients[i] = { id = node.uuid[i], - flow = ("vless" == node.protocol and "1" == node.tls and node.tlsflow) and node.tlsflow or nil + flow = ("vless" == node.protocol and "1" == node.tls and node.flow) and node.flow or nil } end settings = { @@ -760,10 +760,10 @@ function gen_config(var) else if proxy then local pre_proxy = nil - if _node.type ~= "V2ray" and _node.type ~= "Xray" then + if _node.type ~= "Xray" then pre_proxy = true end - if _node.type == "Xray" and _node.tlsflow == "xtls-rprx-vision" then + if _node.type == "Xray" and _node.flow == "xtls-rprx-vision" then pre_proxy = true end if pre_proxy then diff --git a/luci-app-passwall2/luasrc/view/passwall2/global/status.htm b/luci-app-passwall2/luasrc/view/passwall2/global/status.htm index 45205c471..3ce29f2bb 100644 --- a/luci-app-passwall2/luasrc/view/passwall2/global/status.htm +++ b/luci-app-passwall2/luasrc/view/passwall2/global/status.htm @@ -91,7 +91,7 @@ https://github.com/pure-css/pure/blob/master/LICENSE.md
-

V2ray
<%:NOT RUNNING%>

+

Xray
<%:NOT RUNNING%>

@@ -145,7 +145,7 @@ https://github.com/pure-css/pure/blob/master/LICENSE.md if (true) { var status_node = document.getElementById('status_node'); if (status_node) { - var text = 'V2ray
'; + var text = 'Xray
'; if (data["global_status"]) text += '<%:RUNNING%>'; else diff --git a/luci-app-passwall2/luasrc/view/passwall2/node_list/link_share_man.htm b/luci-app-passwall2/luasrc/view/passwall2/node_list/link_share_man.htm index dccf2429a..9c9880e23 100644 --- a/luci-app-passwall2/luasrc/view/passwall2/node_list/link_share_man.htm +++ b/luci-app-passwall2/luasrc/view/passwall2/node_list/link_share_man.htm @@ -1,7 +1,6 @@ <%+cbi/valueheader%> <% local api = require "luci.passwall2.api" -local has_v2ray = api.is_finded("v2ray") local has_xray = api.is_finded("xray") -%>