diff --git a/applications/luci-app-passwall/Makefile b/applications/luci-app-passwall/Makefile index c4b74a5d3b..44ae942705 100644 --- a/applications/luci-app-passwall/Makefile +++ b/applications/luci-app-passwall/Makefile @@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-passwall -PKG_VERSION:=26.1.17 +PKG_VERSION:=26.1.25 PKG_RELEASE:=1 PKG_CONFIG_DEPENDS:= \ diff --git a/applications/luci-app-passwall/luasrc/controller/passwall.lua b/applications/luci-app-passwall/luasrc/controller/passwall.lua index a052e29635..cc50ae8c9e 100644 --- a/applications/luci-app-passwall/luasrc/controller/passwall.lua +++ b/applications/luci-app-passwall/luasrc/controller/passwall.lua @@ -11,6 +11,7 @@ local http = require "luci.http" local util = require "luci.util" local i18n = require "luci.i18n" local jsonStringify = luci.jsonc.stringify +local jsonParse = luci.jsonc.parse function index() if not nixio.fs.access("/etc/config/passwall") then @@ -79,6 +80,7 @@ function index() entry({"admin", "services", appname, "ping_node"}, call("ping_node")).leaf = true entry({"admin", "services", appname, "urltest_node"}, call("urltest_node")).leaf = true entry({"admin", "services", appname, "add_node"}, call("add_node")).leaf = true + entry({"admin", "services", appname, "update_node"}, call("update_node")).leaf = true entry({"admin", "services", appname, "set_node"}, call("set_node")).leaf = true entry({"admin", "services", appname, "copy_node"}, call("copy_node")).leaf = true entry({"admin", "services", appname, "clear_all_nodes"}, call("clear_all_nodes")).leaf = true @@ -119,6 +121,16 @@ local function http_write_json(content) http.write(jsonStringify(content or {code = 1})) end +local function http_write_json_ok(data) + http.prepare_content("application/json") + http.write(jsonStringify({code = 1, data = data})) +end + +local function http_write_json_error(data) + http.prepare_content("application/json") + http.write(jsonStringify({code = 0, data = data})) +end + function reset_config() luci.sys.call('/etc/init.d/passwall stop') luci.sys.call('[ -f "/usr/share/passwall/0_default_config" ] && cp -f /usr/share/passwall/0_default_config /etc/config/passwall') @@ -412,6 +424,8 @@ function add_node() uci:set(appname, uuid, "group", group) end + uci:set(appname, uuid, "type", "Xray") + if redirect == "1" then api.uci_save(uci, appname) http.redirect(api.url("node_config", uuid)) @@ -421,6 +435,23 @@ function add_node() end end +function update_node() + local id = http.formvalue("id") -- Node id + local data = http.formvalue("data") -- json new Data + if id and data then + local data_t = jsonParse(data) or {} + if next(data_t) then + for k, v in pairs(data_t) do + uci:set(appname, id, k, v) + end + api.uci_save(uci, appname) + http_write_json_ok() + return + end + end + http_write_json_error() +end + function set_node() local protocol = http.formvalue("protocol") local section = http.formvalue("section") diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/acl_config.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/acl_config.lua index 8cffe54e57..435390ce04 100644 --- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/acl_config.lua +++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/acl_config.lua @@ -464,11 +464,19 @@ o:depends({xray_dns_mode = "tcp+doh"}) o:depends({singbox_dns_mode = "doh"}) o = s:option(Value, "remote_dns_client_ip", translate("EDNS Client Subnet")) +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({dns_mode = "sing-box"}) o:depends({dns_mode = "xray"}) o:depends({_node_sel_shunt = "1"}) +o = s:option(Flag, "remote_fakedns", "FakeDNS", translate("Use FakeDNS work in the shunt domain that proxy.")) +o.default = "0" +o.rmempty = false +o:depends({dns_mode = "sing-box"}) +o:depends({dns_mode = "xray"}) + o = s:option(ListValue, "chinadns_ng_default_tag", translate("Default DNS")) o.default = "none" o:value("gfw", translate("Remote DNS")) diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua index 4cd6b0ea8e..181688f720 100644 --- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua +++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua @@ -95,6 +95,8 @@ end m:append(Template(appname .. "/global/status")) +local global_cfgid = m:get("@global[0]")[".name"] + s = m:section(TypedSection, "global") s.anonymous = true s.addremove = false @@ -118,29 +120,34 @@ o:value("", translate("Close")) o:value("tcp", translate("Same as the tcp node")) o.group = {"",""} +local tcp_node_id = m.uci:get(appname, global_cfgid, "tcp_node") +local tcp_node = tcp_node_id and m.uci:get_all(appname, tcp_node_id) or {} + -- 分流 if (has_singbox or has_xray) and #nodes_table > 0 then - local function get_cfgvalue(shunt_node_id, option) - return function(self, section) - return m:get(shunt_node_id, option) - end - end - local function get_write(shunt_node_id, option) - return function(self, section, value) - if s.fields["tcp_node"]:formvalue(section) == shunt_node_id then - m:set(shunt_node_id, option, value) + if #normal_list > 0 and tcp_node.protocol == "_shunt" then + local v = tcp_node + if v then + local function get_cfgvalue(shunt_node_id, option) + return function(self, section) + return m:get(shunt_node_id, option) + end end - end - end - local function get_remove(shunt_node_id, option) - return function(self, section) - if s.fields["tcp_node"]:formvalue(section) == shunt_node_id then - m:del(shunt_node_id, option) + local function get_write(shunt_node_id, option) + return function(self, section, value) + if s.fields["tcp_node"]:formvalue(section) == shunt_node_id then + m:set(shunt_node_id, option, value) + end + end end - end - end - if #normal_list > 0 then - for k, v in pairs(shunt_list) do + local function get_remove(shunt_node_id, option) + return function(self, section) + if s.fields["tcp_node"]:formvalue(section) == shunt_node_id then + m:del(shunt_node_id, option) + end + end + end + v.id = v[".name"] local vid = v.id -- shunt node type, Sing-Box or Xray o = s:taboption("Main", ListValue, vid .. "-type", translate("Type")) @@ -161,7 +168,7 @@ if (has_singbox or has_xray) and #nodes_table > 0 then o.cfgvalue = get_cfgvalue(v.id, "preproxy_enabled") o.write = get_write(v.id, "preproxy_enabled") - o = s:taboption("Main", ListValue, vid .. "-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 = s:taboption("Main", ListValue, vid .. "-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(vid .. "-preproxy_enabled", "1") o.template = appname .. "/cbi/nodes_listvalue" o.group = {} @@ -188,6 +195,12 @@ if (has_singbox or has_xray) and #nodes_table > 0 then o.cfgvalue = get_cfgvalue(v.id, "main_node") o.write = get_write(v.id, "main_node") + o = s:taboption("Main", Flag, vid .. "-fakedns", "FakeDNS", translate("Use FakeDNS work in the shunt domain that proxy.")) + o:depends("tcp_node", v.id) + o.cfgvalue = get_cfgvalue(v.id, "fakedns") + o.write = get_write(v.id, "fakedns") + o.remove = get_remove(v.id, "fakedns") + m.uci:foreach(appname, "shunt_rules", function(e) local id = e[".name"] local node_option = vid .. "-" .. id .. "_node" @@ -204,16 +217,23 @@ if (has_singbox or has_xray) and #nodes_table > 0 then o.template = appname .. "/cbi/nodes_listvalue" o.group = {"","","",""} - local pt = s:taboption("Main", ListValue, vid .. "-".. id .. "_proxy_tag", string.format('* %s', e.remarks .. " " .. translate("Preproxy"))) + local pt = s:taboption("Main", ListValue, vid .. "-".. id .. "_proxy_tag", string.format('* %s', e.remarks .. " " .. translate("Preproxy"))) pt.cfgvalue = get_cfgvalue(v.id, id .. "_proxy_tag") pt.write = get_write(v.id, id .. "_proxy_tag") pt.remove = get_remove(v.id, id .. "_proxy_tag") pt:value("", translate("Close")) pt:value("main", translate("Preproxy Node")) pt:depends("__hide__", "1") + + local fakedns_tag = s:taboption("Main", Flag, vid .. "-".. id .. "_fakedns", string.format('* %s', e.remarks .. " " .. "FakeDNS")) + fakedns_tag.cfgvalue = get_cfgvalue(v.id, id .. "_fakedns") + fakedns_tag.write = get_write(v.id, id .. "_fakedns") + fakedns_tag.remove = get_remove(v.id, id .. "_fakedns") + for k1, v1 in pairs(socks_list) do o:value(v1.id, v1.remark) o.group[#o.group+1] = (v1.group and v1.group ~= "") and v1.group or translate("default") + fakedns_tag:depends({ [node_option] = v1.id, [vid .. "-fakedns"] = "1" }) end for k1, v1 in pairs(balancing_list) do o:value(v1.id, v1.remark) @@ -233,6 +253,10 @@ if (has_singbox or has_xray) and #nodes_table > 0 then if not api.is_local_ip(v1.address) then --本地节点禁止使用前置 pt:depends({ [node_option] = v1.id, [vid .. "-preproxy_enabled"] = "1" }) end + fakedns_tag:depends({ [node_option] = v1.id, [vid .. "-fakedns"] = "1" }) + end + if v.default_node ~= "_direct" or v.default_node ~= "_blackhole" then + fakedns_tag:depends({ [node_option] = "_default", [vid .. "-fakedns"] = "1" }) end end end) @@ -269,7 +293,7 @@ if (has_singbox or has_xray) and #nodes_table > 0 then end local id = "default_proxy_tag" - o = s:taboption("Main", ListValue, vid .. "-" .. id, 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.")) + o = s:taboption("Main", ListValue, vid .. "-" .. id, 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.")) o.cfgvalue = get_cfgvalue(v.id, id) o.write = get_write(v.id, id) o.remove = get_remove(v.id, id) @@ -335,7 +359,7 @@ if api.is_finded("smartdns") then o = s:taboption("DNS", Value, "group_domestic", translate("Domestic group name")) o.placeholder = "local" o:depends("dns_shunt", "smartdns") - o.description = translate("You only need to configure domestic DNS packets in SmartDNS and set it redirect or as Dnsmasq upstream, and fill in the domestic DNS group name here.") + o.description = translate("You only need to configure domestic DNS packets in SmartDNS, and fill in the domestic DNS group name here.") end o = s:taboption("DNS", ListValue, "direct_dns_mode", translate("Direct DNS") .. " " .. translate("Request protocol")) @@ -559,7 +583,7 @@ o:depends({singbox_dns_mode = "doh"}) o = s:taboption("DNS", Value, "remote_dns_client_ip", translate("EDNS Client Subnet")) 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).") + translate("This feature requires the DNS server to support the Edns Client Subnet (RFC7871).") o.datatype = "ipaddr" o:depends({dns_mode = "sing-box"}) o:depends({dns_mode = "xray"}) @@ -574,7 +598,7 @@ o:depends({smartdns_dns_mode = "sing-box", dns_shunt = "smartdns"}) o:depends({dns_mode = "xray", dns_shunt = "dnsmasq"}) o:depends({dns_mode = "xray", dns_shunt = "chinadns-ng"}) o:depends({smartdns_dns_mode = "xray", dns_shunt = "smartdns"}) -o:depends("_node_sel_shunt", "1") +--o:depends("_node_sel_shunt", "1") o.validate = function(self, value, t) if value and value == "1" then local _dns_mode = s.fields["dns_mode"]:formvalue(t) @@ -924,6 +948,10 @@ for k, v in pairs(nodes_table) do end end -m:append(Template(appname .. "/global/footer")) +local footer = Template(appname .. "/global/footer") +footer.api = api +footer.global_cfgid = global_cfgid +footer.shunt_list = api.jsonc.stringify(shunt_list) +m:append(footer) return m diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/node_config.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/node_config.lua index a6201bebe0..8f6bb76046 100644 --- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/node_config.lua +++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/node_config.lua @@ -9,6 +9,11 @@ if not arg[1] or not m:get(arg[1]) then luci.http.redirect(m.redirect) end +local header = Template(appname .. "/node_config/header") +header.api = api +header.section = arg[1] +m:append(header) + m:append(Template(appname .. "/cbi/nodes_multivalue_com")) m:append(Template(appname .. "/cbi/nodes_listvalue_com")) @@ -18,7 +23,7 @@ s.dynamic = false o = s:option(DummyValue, "passwall", " ") o.rawhtml = true -o.template = "passwall/node_list/link_share_man" +o.template = "passwall/node_config/link_share_man" o.value = arg[1] o = s:option(Value, "remarks", translate("Node Remarks")) @@ -55,35 +60,40 @@ o.write = function(self, section, value) m:set(section, self.option, value) end +local fs = api.fs +local types_dir = "/usr/lib/lua/luci/model/cbi/passwall/client/type/" +s.val = {} +s.val["type"] = m.uci:get(appname, arg[1], "type") +s.val["protocol"] = m.uci:get(appname, arg[1], "protocol") + o = s:option(ListValue, "type", translate("Type")) if api.is_finded("ipt2socks") then - local function _n(name) - return "socks_" .. name - end - s.fields["type"]:value("Socks", translate("Socks")) - o = s:option(ListValue, _n("del_protocol"), " ") --始终隐藏,用于删除 protocol - o:depends({ [_n("__hide")] = "1" }) - o.rewrite_option = "protocol" + if s.val["type"] == "Socks" then + local function _n(name) + return "socks_" .. name + end + o = s:option(ListValue, _n("del_protocol"), " ") --始终隐藏,用于删除 protocol + o:depends({ [_n("__hide")] = "1" }) + o.rewrite_option = "protocol" + + o = s:option(Value, _n("address"), translate("Address (Support Domain Name)")) - o = s:option(Value, _n("address"), translate("Address (Support Domain Name)")) + o = s:option(Value, _n("port"), translate("Port")) + o.datatype = "port" - o = s:option(Value, _n("port"), translate("Port")) - o.datatype = "port" + o = s:option(Value, _n("username"), translate("Username")) - o = s:option(Value, _n("username"), translate("Username")) + o = s:option(Value, _n("password"), translate("Password")) + o.password = true - o = s:option(Value, _n("password"), translate("Password")) - o.password = true + api.luci_types(arg[1], m, s, "Socks", "socks_") + end - api.luci_types(arg[1], m, s, "Socks", "socks_") end -local fs = api.fs -local types_dir = "/usr/lib/lua/luci/model/cbi/passwall/client/type/" - local type_table = {} for filename in fs.dir(types_dir) do table.insert(type_table, filename) @@ -95,4 +105,10 @@ for index, value in ipairs(type_table) do setfenv(p_func, getfenv(1))(m, s) end +local footer = Template(appname .. "/node_config/footer") +footer.api = api +footer.section = arg[1] + +m:append(footer) + return m diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/shunt_rules.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/shunt_rules.lua index 764e0020a3..77b7275a9f 100644 --- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/shunt_rules.lua +++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/shunt_rules.lua @@ -138,7 +138,10 @@ end source.write = dynamicList_write +--[[ +-- Too low usage rate, hidden sourcePort = s:option(Value, "sourcePort", translate("Source port")) +]]-- port = s:option(Value, "port", translate("port")) @@ -163,6 +166,11 @@ domain_list.validate = function(self, value) flag = 0 elseif host:find("ext:") and host:find("ext:") == 1 then flag = 0 + elseif host:find("rule-set:", 1, true) == 1 or host:find("rs:") == 1 then + local w = host:sub(host:find(":") + 1, #host) + if w:find("local:") == 1 or w:find("remote:") == 1 then + flag = 0 + end elseif host:find("#") and host:find("#") == 1 then flag = 0 end @@ -174,13 +182,21 @@ domain_list.validate = function(self, value) end return value end -domain_list.description = "
" +domain_list.description = "
" ip_list = s:option(TextValue, "ip_list", "IP") ip_list.rows = 10 ip_list.wrap = "off" @@ -191,6 +207,11 @@ ip_list.validate = function(self, value) for index, ipmask in ipairs(ipmasks) do if ipmask:find("geoip:") and ipmask:find("geoip:") == 1 and not ipmask:find("%s") then elseif ipmask:find("ext:") and ipmask:find("ext:") == 1 and not ipmask:find("%s") then + elseif ipmask:find("rule-set:", 1, true) == 1 or ipmask:find("rs:") == 1 then + local w = ipmask:sub(ipmask:find(":") + 1, #ipmask) + if w:find("local:") == 1 or w:find("remote:") == 1 then + flag = 0 + end elseif ipmask:find("#") and ipmask:find("#") == 1 then else if not (datatypes.ipmask4(ipmask) or datatypes.ipmask6(ipmask)) then @@ -200,10 +221,20 @@ ip_list.validate = function(self, value) end return value end -ip_list.description = "
" +ip_list.description = "
" + +o = s:option(Flag, "invert", "Invert", translate("Invert match result.") .. " " .. translate("Only support Sing-Box.")) return m diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/hysteria2.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/hysteria2.lua index 30f6b762fe..928748461d 100644 --- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/hysteria2.lua +++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/hysteria2.lua @@ -8,16 +8,20 @@ end local type_name = "Hysteria2" +-- [[ Hysteria2 ]] + +s.fields["type"]:value(type_name, "Hysteria2") + +if s.val["type"] ~= type_name then + return +end + local option_prefix = "hysteria2_" local function _n(name) return option_prefix .. name end --- [[ Hysteria2 ]] - -s.fields["type"]:value(type_name, "Hysteria2") - o = s:option(ListValue, _n("protocol"), translate("Protocol")) o:value("udp", "UDP") diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/naive.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/naive.lua index 601ac6a262..2dbaa30054 100644 --- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/naive.lua +++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/naive.lua @@ -8,16 +8,20 @@ end local type_name = "Naiveproxy" +-- [[ Naive ]] + +s.fields["type"]:value(type_name, "NaiveProxy") + +if s.val["type"] ~= type_name then + return +end + local option_prefix = "naive_" local function _n(name) return option_prefix .. name end --- [[ Naive ]] - -s.fields["type"]:value(type_name, translate("NaiveProxy")) - o = s:option(ListValue, _n("protocol"), translate("Protocol")) o:value("https", translate("HTTPS")) o:value("quic", translate("QUIC")) diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua index a7342a51f9..dad012f772 100644 --- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua +++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ray.lua @@ -11,6 +11,17 @@ local jsonc = api.jsonc local type_name = "Xray" +-- [[ Xray ]] + +s.fields["type"]:value(type_name, "Xray") +if not s.fields["type"].default then + s.fields["type"].default = type_name +end + +if s.val["type"] ~= type_name then + return +end + local option_prefix = "xray_" local function _n(name) @@ -28,9 +39,6 @@ local header_type_list = { } local xray_version = api.get_app_version("xray") --- [[ Xray ]] - -s.fields["type"]:value(type_name, "Xray") o = s:option(ListValue, _n("protocol"), translate("Protocol")) o:value("vmess", translate("Vmess")) @@ -49,10 +57,6 @@ end o:value("_shunt", translate("Shunt")) o:value("_iface", translate("Custom Interface")) -o = s:option(Value, _n("iface"), translate("Interface")) -o.default = "eth1" -o:depends({ [_n("protocol")] = "_iface" }) - local nodes_table = {} local balancers_table = {} local fallback_table = {} @@ -106,134 +110,76 @@ m.uci:foreach(appname, "socks", function(s) end end) --- 负载均衡列表 -o = s:option(MultiValue, _n("balancing_node"), translate("Load balancing node list"), translate("Load balancing node list, document")) -o:depends({ [_n("protocol")] = "_balancing" }) -o.widget = "checkbox" -o.template = appname .. "/cbi/nodes_multivalue" -o.group = {} -for k, v in pairs(socks_list) do - o:value(v.id, v.remark) - o.group[#o.group+1] = v.group or "" -end -for i, v in pairs(nodes_table) do - o:value(v.id, v.remark) - o.group[#o.group+1] = v.group or "" -end --- 读取旧 DynamicList -function o.cfgvalue(self, section) - return m.uci:get_list(appname, section, "balancing_node") or {} -end --- 写入保持 DynamicList -function o.custom_write(self, section, value) - local old = m.uci:get_list(appname, section, "balancing_node") or {} - local new, set = {}, {} - for v in value:gmatch("%S+") do - new[#new + 1] = v - set[v] = 1 +if s.val["protocol"] == "_balancing" then -- [[ 负载均衡 Start ]] + o = s:option(MultiValue, _n("balancing_node"), translate("Load balancing node list"), translate("Load balancing node list, document")) + o:depends({ [_n("protocol")] = "_balancing" }) + o.widget = "checkbox" + o.template = appname .. "/cbi/nodes_multivalue" + o.group = {} + for k, v in pairs(socks_list) do + o:value(v.id, v.remark) + o.group[#o.group+1] = v.group or "" + end + for i, v in pairs(nodes_table) do + o:value(v.id, v.remark) + o.group[#o.group+1] = v.group or "" end - for _, v in ipairs(old) do - if not set[v] then + -- 读取旧 DynamicList + function o.cfgvalue(self, section) + return m.uci:get_list(appname, section, "balancing_node") or {} + end + -- 写入保持 DynamicList + function o.custom_write(self, section, value) + local old = m.uci:get_list(appname, section, "balancing_node") or {} + local new, set = {}, {} + for v in value:gmatch("%S+") do + new[#new + 1] = v + set[v] = 1 + end + for _, v in ipairs(old) do + if not set[v] then + m.uci:set_list(appname, section, "balancing_node", new) + return + end + set[v] = nil + end + for _ in pairs(set) do m.uci:set_list(appname, section, "balancing_node", new) return end - set[v] = nil - end - for _ in pairs(set) do - m.uci:set_list(appname, section, "balancing_node", new) - return end -end -o = s:option(ListValue, _n("balancingStrategy"), translate("Balancing Strategy")) -o:depends({ [_n("protocol")] = "_balancing" }) -o:value("random") -o:value("roundRobin") -o:value("leastPing") -o:value("leastLoad") -o.default = "random" - --- Fallback Node -o = s:option(ListValue, _n("fallback_node"), translate("Fallback Node")) -o:value("", translate("Close(Not use)")) -o:depends({ [_n("protocol")] = "_balancing" }) -o.template = appname .. "/cbi/nodes_listvalue" -o.group = {""} -local function check_fallback_chain(fb) - for k, v in pairs(fallback_table) do - if v.fallback == fb then - fallback_table[k] = nil - check_fallback_chain(v.id) + o = s:option(ListValue, _n("balancingStrategy"), translate("Balancing Strategy")) + o:depends({ [_n("protocol")] = "_balancing" }) + o:value("random") + o:value("roundRobin") + o:value("leastPing") + o:value("leastLoad") + o.default = "random" + + -- Fallback Node + o = s:option(ListValue, _n("fallback_node"), translate("Fallback Node")) + o:value("", translate("Close(Not use)")) + o:depends({ [_n("protocol")] = "_balancing" }) + o.template = appname .. "/cbi/nodes_listvalue" + o.group = {""} + local function check_fallback_chain(fb) + for k, v in pairs(fallback_table) do + if v.fallback == fb then + fallback_table[k] = nil + check_fallback_chain(v.id) + end end end -end --- 检查fallback链,去掉会形成闭环的balancer节点 -if is_balancer then - check_fallback_chain(arg[1]) -end -for k, v in pairs(socks_list) do - o:value(v.id, v.remark) - o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") -end -for k, v in pairs(fallback_table) do - o:value(v.id, v.remark) - o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") -end -for k, v in pairs(nodes_table) do - o:value(v.id, v.remark) - o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") -end - --- 探测地址 -o = s:option(Flag, _n("useCustomProbeUrl"), translate("Use Custom Probe URL"), translate("By default the built-in probe URL will be used, enable this option to use a custom probe URL.")) -o:depends({ [_n("protocol")] = "_balancing" }) - -o = s:option(Value, _n("probeUrl"), translate("Probe URL")) -o:depends({ [_n("useCustomProbeUrl")] = true }) -o:value("https://cp.cloudflare.com/", "Cloudflare") -o:value("https://www.gstatic.com/generate_204", "Gstatic") -o:value("https://www.google.com/generate_204", "Google") -o:value("https://www.youtube.com/generate_204", "YouTube") -o:value("https://connect.rom.miui.com/generate_204", "MIUI (CN)") -o:value("https://connectivitycheck.platform.hicloud.com/generate_204", "HiCloud (CN)") -o.default = o.keylist[3] -o.description = translate("The URL used to detect the connection status.") - --- 探测间隔 -o = s:option(Value, _n("probeInterval"), translate("Probe Interval")) -o:depends({ [_n("protocol")] = "_balancing" }) -o.default = "1m" -o.placeholder = "1m" -o.description = translate("The interval between initiating probes.") .. "
" .. - translate("The time format is numbers + units, such as '10s', '2h45m', and the supported time units are s, m, h, which correspond to seconds, minutes, and hours, respectively.") .. "
" .. - translate("When the unit is not filled in, it defaults to seconds.") - -o = s:option(Value, _n("expected"), translate("Preferred Node Count")) -o:depends({ [_n("balancingStrategy")] = "leastLoad" }) -o.datatype = "uinteger" -o.default = "2" -o.placeholder = "2" -o.description = translate("The load balancer selects the optimal number of nodes, and traffic is randomly distributed among them.") - - --- [[ 分流模块 ]] -if #nodes_table > 0 then - o = s:option(Flag, _n("preproxy_enabled"), translate("Preproxy")) - o:depends({ [_n("protocol")] = "_shunt" }) - - o = s:option(ListValue, _n("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({ [_n("protocol")] = "_shunt", [_n("preproxy_enabled")] = true }) - o.template = appname .. "/cbi/nodes_listvalue" - o.group = {} - for k, v in pairs(socks_list) do - o:value(v.id, v.remark) - o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") + -- 检查fallback链,去掉会形成闭环的balancer节点 + if is_balancer then + check_fallback_chain(arg[1]) end - for k, v in pairs(balancers_table) do + for k, v in pairs(socks_list) do o:value(v.id, v.remark) o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") end - for k, v in pairs(iface_table) do + for k, v in pairs(fallback_table) do o:value(v.id, v.remark) o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") end @@ -241,104 +187,179 @@ if #nodes_table > 0 then o:value(v.id, v.remark) o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") end -end -m.uci:foreach(appname, "shunt_rules", function(e) - if e[".name"] and e.remarks then - o = s:option(ListValue, _n(e[".name"]), string.format('* %s', api.url("shunt_rules", e[".name"]), e.remarks)) - o:value("", translate("Close")) - o:value("_default", translate("Default")) - o:value("_direct", translate("Direct Connection")) - o:value("_blackhole", translate("Blackhole")) + + -- 探测地址 + o = s:option(Flag, _n("useCustomProbeUrl"), translate("Use Custom Probe URL"), translate("By default the built-in probe URL will be used, enable this option to use a custom probe URL.")) + o:depends({ [_n("protocol")] = "_balancing" }) + + o = s:option(Value, _n("probeUrl"), translate("Probe URL")) + o:depends({ [_n("useCustomProbeUrl")] = true }) + o:value("https://cp.cloudflare.com/", "Cloudflare") + o:value("https://www.gstatic.com/generate_204", "Gstatic") + o:value("https://www.google.com/generate_204", "Google") + o:value("https://www.youtube.com/generate_204", "YouTube") + o:value("https://connect.rom.miui.com/generate_204", "MIUI (CN)") + o:value("https://connectivitycheck.platform.hicloud.com/generate_204", "HiCloud (CN)") + o.default = o.keylist[3] + o.description = translate("The URL used to detect the connection status.") + + -- 探测间隔 + o = s:option(Value, _n("probeInterval"), translate("Probe Interval")) + o:depends({ [_n("protocol")] = "_balancing" }) + o.default = "1m" + o.placeholder = "1m" + o.description = translate("The interval between initiating probes.") .. "
" .. + translate("The time format is numbers + units, such as '10s', '2h45m', and the supported time units are s, m, h, which correspond to seconds, minutes, and hours, respectively.") .. "
" .. + translate("When the unit is not filled in, it defaults to seconds.") + + o = s:option(Value, _n("expected"), translate("Preferred Node Count")) + o:depends({ [_n("balancingStrategy")] = "leastLoad" }) + o.datatype = "uinteger" + o.default = "2" + o.placeholder = "2" + o.description = translate("The load balancer selects the optimal number of nodes, and traffic is randomly distributed among them.") +end -- [[ 负载均衡 End ]] + +if s.val["protocol"] == "_shunt" then -- [[ 分流模块 Start ]] + local default_node = m.uci:get(appname, arg[1], "default_node") or "_direct" + if #nodes_table > 0 then + o = s:option(Flag, _n("preproxy_enabled"), translate("Preproxy")) o:depends({ [_n("protocol")] = "_shunt" }) + + o = s:option(ListValue, _n("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({ [_n("protocol")] = "_shunt", [_n("preproxy_enabled")] = true }) o.template = appname .. "/cbi/nodes_listvalue" - o.group = {"","","",""} + o.group = {} + for k, v in pairs(socks_list) do + o:value(v.id, v.remark) + o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") + end + for k, v in pairs(balancers_table) do + o:value(v.id, v.remark) + o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") + end + for k, v in pairs(iface_table) do + o:value(v.id, v.remark) + o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") + end + for k, v in pairs(nodes_table) do + o:value(v.id, v.remark) + o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") + end - if #nodes_table > 0 then - for k, v in pairs(socks_list) do - o:value(v.id, v.remark) - o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - end - for k, v in pairs(balancers_table) do - o:value(v.id, v.remark) - o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - end - for k, v in pairs(iface_table) do - o:value(v.id, v.remark) - o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - end - local pt = s:option(ListValue, _n(e[".name"] .. "_proxy_tag"), string.format('* %s', e.remarks .. " " .. translate("Preproxy"))) - pt:value("", translate("Close")) - pt:value("main", translate("Preproxy Node")) - pt:depends("__hide__", "1") - for k, v in pairs(nodes_table) do - o:value(v.id, v.remark) - o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - if not api.is_local_ip(v.address) then --本地节点禁止使用前置 - pt:depends({ [_n("protocol")] = "_shunt", [_n("preproxy_enabled")] = true, [_n(e[".name"])] = v.id }) + o = s:option(Flag, _n("fakedns"), "FakeDNS", translate("Use FakeDNS work in the shunt domain that proxy.")) + o:depends({ [_n("protocol")] = "_shunt" }) + end + m.uci:foreach(appname, "shunt_rules", function(e) + if e[".name"] and e.remarks then + o = s:option(ListValue, _n(e[".name"]), string.format('* %s', api.url("shunt_rules", e[".name"]), e.remarks)) + o:value("", translate("Close")) + o:value("_default", translate("Default")) + o:value("_direct", translate("Direct Connection")) + o:value("_blackhole", translate("Blackhole")) + o:depends({ [_n("protocol")] = "_shunt" }) + o.template = appname .. "/cbi/nodes_listvalue" + o.group = {"","","",""} + + if #nodes_table > 0 then + local pt = s:option(ListValue, _n(e[".name"] .. "_proxy_tag"), string.format('* %s', e.remarks .. " " .. translate("Preproxy"))) + pt:value("", translate("Close")) + pt:value("main", translate("Preproxy Node")) + pt:depends("__hide__", "1") + + local fakedns_tag = s:option(Flag, _n(e[".name"] .. "_fakedns"), string.format('* %s', e.remarks .. " " .. "FakeDNS")) + + for k, v in pairs(socks_list) do + o:value(v.id, v.remark) + o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") + fakedns_tag:depends({ [_n("protocol")] = "_shunt", [_n("fakedns")] = true, [_n(e[".name"])] = v.id }) + end + for k, v in pairs(balancers_table) do + o:value(v.id, v.remark) + o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") + end + for k, v in pairs(iface_table) do + o:value(v.id, v.remark) + o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") + end + for k, v in pairs(nodes_table) do + o:value(v.id, v.remark) + o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") + if not api.is_local_ip(v.address) then --本地节点禁止使用前置 + pt:depends({ [_n("protocol")] = "_shunt", [_n("preproxy_enabled")] = true, [_n(e[".name"])] = v.id }) + end + fakedns_tag:depends({ [_n("protocol")] = "_shunt", [_n("fakedns")] = true, [_n(e[".name"])] = v.id }) + end + if default_node ~= "_direct" or default_node ~= "_blackhole" then + fakedns_tag:depends({ [_n("protocol")] = "_shunt", [_n("fakedns")] = true, [_n(e[".name"])] = "_default" }) end end end - end -end) + end) -o = s:option(DummyValue, _n("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({ [_n("protocol")] = "_shunt" }) + o = s:option(DummyValue, _n("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({ [_n("protocol")] = "_shunt" }) -local o = s:option(ListValue, _n("default_node"), string.format('* %s', translate("Default"))) -o:depends({ [_n("protocol")] = "_shunt" }) -o:value("_direct", translate("Direct Connection")) -o:value("_blackhole", translate("Blackhole")) -o.template = appname .. "/cbi/nodes_listvalue" -o.group = {"",""} + local o = s:option(ListValue, _n("default_node"), string.format('* %s', translate("Default"))) + o:depends({ [_n("protocol")] = "_shunt" }) + o:value("_direct", translate("Direct Connection")) + o:value("_blackhole", translate("Blackhole")) + o.template = appname .. "/cbi/nodes_listvalue" + o.group = {"",""} -if #nodes_table > 0 then - for k, v in pairs(socks_list) do - o:value(v.id, v.remark) - o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - end - for k, v in pairs(balancers_table) do - o:value(v.id, v.remark) - o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - end - for k, v in pairs(iface_table) do - o:value(v.id, v.remark) - o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - end - local dpt = s:option(ListValue, _n("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("", translate("Close")) - dpt:value("main", translate("Preproxy Node")) - dpt:depends("__hide__", "1") - for k, v in pairs(nodes_table) do - o:value(v.id, v.remark) - o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - if not api.is_local_ip(v.address) then - dpt:depends({ [_n("protocol")] = "_shunt", [_n("preproxy_enabled")] = true, [_n("default_node")] = v.id }) + if #nodes_table > 0 then + for k, v in pairs(socks_list) do + o:value(v.id, v.remark) + o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") + end + for k, v in pairs(balancers_table) do + o:value(v.id, v.remark) + o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") + end + for k, v in pairs(iface_table) do + o:value(v.id, v.remark) + o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") + end + local dpt = s:option(ListValue, _n("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("", translate("Close")) + dpt:value("main", translate("Preproxy Node")) + dpt:depends("__hide__", "1") + for k, v in pairs(nodes_table) do + o:value(v.id, v.remark) + o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") + if not api.is_local_ip(v.address) then + dpt:depends({ [_n("protocol")] = "_shunt", [_n("preproxy_enabled")] = true, [_n("default_node")] = v.id }) + end end end -end -o = s:option(ListValue, _n("domainStrategy"), translate("Domain Strategy")) -o:value("AsIs") -o:value("IPIfNonMatch") -o:value("IPOnDemand") -o.default = "IPOnDemand" -o.description = "
" -o:depends({ [_n("protocol")] = "_shunt" }) - -o = s:option(ListValue, _n("domainMatcher"), translate("Domain matcher")) -o:value("hybrid") -o:value("linear") -o:depends({ [_n("protocol")] = "_shunt" }) - --- [[ 分流模块 End ]] + o = s:option(ListValue, _n("domainStrategy"), translate("Domain Strategy")) + o:value("AsIs") + o:value("IPIfNonMatch") + o:value("IPOnDemand") + o.default = "IPOnDemand" + o.description = "
" + o:depends({ [_n("protocol")] = "_shunt" }) + + o = s:option(ListValue, _n("domainMatcher"), translate("Domain matcher")) + o:value("hybrid") + o:value("linear") + o:depends({ [_n("protocol")] = "_shunt" }) +end -- [[ 分流模块 End ]] + +if s.val["protocol"] == "_iface" then -- [[ 自定义接口 Start ]] +o = s:option(Value, _n("iface"), translate("Interface")) +o.default = "eth1" +o:depends({ [_n("protocol")] = "_iface" }) +end -- [[ 自定义接口 End ]] o = s:option(Value, _n("address"), translate("Address (Support Domain Name)")) diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/sing-box.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/sing-box.lua index 1568573132..312cced5c3 100644 --- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/sing-box.lua +++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/sing-box.lua @@ -8,15 +8,21 @@ if not singbox_bin then return end -local local_version = api.get_app_version("sing-box") -local version_ge_1_12_0 = api.compare_versions(local_version:match("[^v]+"), ">=", "1.12.0") - -local singbox_tags = luci.sys.exec(singbox_bin .. " version | grep 'Tags:' | awk '{print $2}'") - local appname = "passwall" local type_name = "sing-box" +-- [[ sing-box ]] + +s.fields["type"]:value(type_name, "Sing-Box") +if not s.fields["type"].default then + s.fields["type"].default = type_name +end + +if s.val["type"] ~= type_name then + return +end + local option_prefix = "singbox_" local function _n(name) @@ -33,9 +39,10 @@ local ss_method_old_list = { local security_list = { "none", "auto", "aes-128-gcm", "chacha20-poly1305", "zero" } --- [[ sing-box ]] +local local_version = api.get_app_version("sing-box") +local version_ge_1_12_0 = api.compare_versions(local_version:match("[^v]+"), ">=", "1.12.0") -s.fields["type"]:value(type_name, "Sing-Box") +local singbox_tags = luci.sys.exec(singbox_bin .. " version | grep 'Tags:' | awk '{print $2}'") o = s:option(ListValue, _n("protocol"), translate("Protocol")) o:value("socks", "Socks") @@ -64,10 +71,6 @@ o:value("_urltest", translate("URLTest")) o:value("_shunt", translate("Shunt")) o:value("_iface", translate("Custom Interface")) -o = s:option(Value, _n("iface"), translate("Interface")) -o.default = "eth1" -o:depends({ [_n("protocol")] = "_iface" }) - local nodes_table = {} local iface_table = {} local urltest_table = {} @@ -109,191 +112,209 @@ m.uci:foreach(appname, "socks", function(s) end end) ---[[ URLTest ]] -o = s:option(MultiValue, _n("urltest_node"), translate("URLTest node list"), translate("List of nodes to test, document")) -o:depends({ [_n("protocol")] = "_urltest" }) -o.widget = "checkbox" -o.template = appname .. "/cbi/nodes_multivalue" -o.group = {} -for k, v in pairs(socks_list) do - o:value(v.id, v.remark) - o.group[#o.group+1] = v.group or "" -end -for i, v in pairs(nodes_table) do - o:value(v.id, v.remark) - o.group[#o.group+1] = v.group or "" -end --- 读取旧 DynamicList -function o.cfgvalue(self, section) - return m.uci:get_list(appname, section, "urltest_node") or {} -end --- 写入保持 DynamicList -function o.custom_write(self, section, value) - local old = m.uci:get_list(appname, section, "urltest_node") or {} - local new, set = {}, {} - for v in value:gmatch("%S+") do - new[#new + 1] = v - set[v] = 1 +if s.val["protocol"] == "_urltest" then -- [[ URLTest Start ]] + o = s:option(MultiValue, _n("urltest_node"), translate("URLTest node list"), translate("List of nodes to test, document")) + o:depends({ [_n("protocol")] = "_urltest" }) + o.widget = "checkbox" + o.template = appname .. "/cbi/nodes_multivalue" + o.group = {} + for k, v in pairs(socks_list) do + o:value(v.id, v.remark) + o.group[#o.group+1] = v.group or "" + end + for i, v in pairs(nodes_table) do + o:value(v.id, v.remark) + o.group[#o.group+1] = v.group or "" + end + -- 读取旧 DynamicList + function o.cfgvalue(self, section) + return m.uci:get_list(appname, section, "urltest_node") or {} end - for _, v in ipairs(old) do - if not set[v] then + -- 写入保持 DynamicList + function o.custom_write(self, section, value) + local old = m.uci:get_list(appname, section, "urltest_node") or {} + local new, set = {}, {} + for v in value:gmatch("%S+") do + new[#new + 1] = v + set[v] = 1 + end + for _, v in ipairs(old) do + if not set[v] then + m.uci:set_list(appname, section, "urltest_node", new) + return + end + set[v] = nil + end + for _ in pairs(set) do m.uci:set_list(appname, section, "urltest_node", new) return end - set[v] = nil end - for _ in pairs(set) do - m.uci:set_list(appname, section, "urltest_node", new) - return - end -end -o = s:option(Value, _n("urltest_url"), translate("Probe URL")) -o:depends({ [_n("protocol")] = "_urltest" }) -o:value("https://cp.cloudflare.com/", "Cloudflare") -o:value("https://www.gstatic.com/generate_204", "Gstatic") -o:value("https://www.google.com/generate_204", "Google") -o:value("https://www.youtube.com/generate_204", "YouTube") -o:value("https://connect.rom.miui.com/generate_204", "MIUI (CN)") -o:value("https://connectivitycheck.platform.hicloud.com/generate_204", "HiCloud (CN)") -o.default = o.keylist[3] -o.description = translate("The URL used to detect the connection status.") - -o = s:option(Value, _n("urltest_interval"), translate("Test interval")) -o:depends({ [_n("protocol")] = "_urltest" }) -o.default = "3m" -o.placeholder = "3m" -o.description = translate("The interval between initiating probes.") .. "
" .. - translate("The time format is numbers + units, such as '10s', '2h45m', and the supported time units are s, m, h, which correspond to seconds, minutes, and hours, respectively.") .. "
" .. - translate("When the unit is not filled in, it defaults to seconds.") .. "
" .. - translate("Test interval must be less or equal than idle timeout.") - -o = s:option(Value, _n("urltest_tolerance"), translate("Test tolerance"), translate("The test tolerance in milliseconds.")) -o:depends({ [_n("protocol")] = "_urltest" }) -o.datatype = "uinteger" -o.placeholder = "50" -o.default = "50" - -o = s:option(Value, _n("urltest_idle_timeout"), translate("Idle timeout")) -o:depends({ [_n("protocol")] = "_urltest" }) -o.placeholder = "30m" -o.default = "30m" -o.description = translate("The idle timeout.") .. "
" .. - translate("The time format is numbers + units, such as '10s', '2h45m', and the supported time units are s, m, h, which correspond to seconds, minutes, and hours, respectively.") .. "
" .. - translate("When the unit is not filled in, it defaults to seconds.") + o = s:option(Value, _n("urltest_url"), translate("Probe URL")) + o:depends({ [_n("protocol")] = "_urltest" }) + o:value("https://cp.cloudflare.com/", "Cloudflare") + o:value("https://www.gstatic.com/generate_204", "Gstatic") + o:value("https://www.google.com/generate_204", "Google") + o:value("https://www.youtube.com/generate_204", "YouTube") + o:value("https://connect.rom.miui.com/generate_204", "MIUI (CN)") + o:value("https://connectivitycheck.platform.hicloud.com/generate_204", "HiCloud (CN)") + o.default = o.keylist[3] + o.description = translate("The URL used to detect the connection status.") + + o = s:option(Value, _n("urltest_interval"), translate("Test interval")) + o:depends({ [_n("protocol")] = "_urltest" }) + o.default = "3m" + o.placeholder = "3m" + o.description = translate("The interval between initiating probes.") .. "
" .. + translate("The time format is numbers + units, such as '10s', '2h45m', and the supported time units are s, m, h, which correspond to seconds, minutes, and hours, respectively.") .. "
" .. + translate("When the unit is not filled in, it defaults to seconds.") .. "
" .. + translate("Test interval must be less or equal than idle timeout.") + + o = s:option(Value, _n("urltest_tolerance"), translate("Test tolerance"), translate("The test tolerance in milliseconds.")) + o:depends({ [_n("protocol")] = "_urltest" }) + o.datatype = "uinteger" + o.placeholder = "50" + o.default = "50" -o = s:option(Flag, _n("urltest_interrupt_exist_connections"), translate("Interrupt existing connections")) -o:depends({ [_n("protocol")] = "_urltest" }) -o.default = "0" -o.description = translate("Interrupt existing connections when the selected outbound has changed.") + o = s:option(Value, _n("urltest_idle_timeout"), translate("Idle timeout")) + o:depends({ [_n("protocol")] = "_urltest" }) + o.placeholder = "30m" + o.default = "30m" + o.description = translate("The idle timeout.") .. "
" .. + translate("The time format is numbers + units, such as '10s', '2h45m', and the supported time units are s, m, h, which correspond to seconds, minutes, and hours, respectively.") .. "
" .. + translate("When the unit is not filled in, it defaults to seconds.") --- [[ 分流模块 ]] -if #nodes_table > 0 then - o = s:option(Flag, _n("preproxy_enabled"), translate("Preproxy")) - o:depends({ [_n("protocol")] = "_shunt" }) + o = s:option(Flag, _n("urltest_interrupt_exist_connections"), translate("Interrupt existing connections")) + o:depends({ [_n("protocol")] = "_urltest" }) + o.default = "0" + o.description = translate("Interrupt existing connections when the selected outbound has changed.") +end -- [[ URLTest End ]] - o = s:option(ListValue, _n("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({ [_n("protocol")] = "_shunt", [_n("preproxy_enabled")] = true }) - o.template = appname .. "/cbi/nodes_listvalue" - o.group = {} - for k, v in pairs(socks_list) do - o:value(v.id, v.remark) - o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - end - for k, v in pairs(urltest_table) do - o:value(v.id, v.remark) - o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - end - for k, v in pairs(iface_table) do - o:value(v.id, v.remark) - o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - end - for k, v in pairs(nodes_table) do - o:value(v.id, v.remark) - o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - end -end -m.uci:foreach(appname, "shunt_rules", function(e) - if e[".name"] and e.remarks then - o = s:option(ListValue, _n(e[".name"]), string.format('* %s', api.url("shunt_rules", e[".name"]), e.remarks)) - o:value("", translate("Close")) - o:value("_default", translate("Default")) - o:value("_direct", translate("Direct Connection")) - o:value("_blackhole", translate("Blackhole")) +if s.val["protocol"] == "_shunt" then -- [[ 分流模块 Start ]] + local default_node = m.uci:get(appname, arg[1], "default_node") or "_direct" + if #nodes_table > 0 then + o = s:option(Flag, _n("preproxy_enabled"), translate("Preproxy")) o:depends({ [_n("protocol")] = "_shunt" }) + + o = s:option(ListValue, _n("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({ [_n("protocol")] = "_shunt", [_n("preproxy_enabled")] = true }) o.template = appname .. "/cbi/nodes_listvalue" - o.group = {"","","",""} + o.group = {} + for k, v in pairs(socks_list) do + o:value(v.id, v.remark) + o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") + end + for k, v in pairs(urltest_table) do + o:value(v.id, v.remark) + o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") + end + for k, v in pairs(iface_table) do + o:value(v.id, v.remark) + o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") + end + for k, v in pairs(nodes_table) do + o:value(v.id, v.remark) + o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") + end - if #nodes_table > 0 then - for k, v in pairs(socks_list) do - o:value(v.id, v.remark) - o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - end - for k, v in pairs(urltest_table) do - o:value(v.id, v.remark) - o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - end - for k, v in pairs(iface_table) do - o:value(v.id, v.remark) - o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - end - local pt = s:option(ListValue, _n(e[".name"] .. "_proxy_tag"), string.format('* %s', e.remarks .. " " .. translate("Preproxy"))) - pt:value("", translate("Close")) - pt:value("main", translate("Preproxy Node")) - pt:depends("__hide__", "1") - for k, v in pairs(nodes_table) do - o:value(v.id, v.remark) - o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - if not api.is_local_ip(v.address) then --本地节点禁止使用前置 - pt:depends({ [_n("protocol")] = "_shunt", [_n("preproxy_enabled")] = true, [_n(e[".name"])] = v.id }) + o = s:option(Flag, _n("fakedns"), "FakeDNS", translate("Use FakeDNS work in the shunt domain that proxy.")) + o:depends({ [_n("protocol")] = "_shunt" }) + end + m.uci:foreach(appname, "shunt_rules", function(e) + if e[".name"] and e.remarks then + o = s:option(ListValue, _n(e[".name"]), string.format('* %s', api.url("shunt_rules", e[".name"]), e.remarks)) + o:value("", translate("Close")) + o:value("_default", translate("Default")) + o:value("_direct", translate("Direct Connection")) + o:value("_blackhole", translate("Blackhole")) + o:depends({ [_n("protocol")] = "_shunt" }) + o.template = appname .. "/cbi/nodes_listvalue" + o.group = {"","","",""} + + if #nodes_table > 0 then + local pt = s:option(ListValue, _n(e[".name"] .. "_proxy_tag"), string.format('* %s', e.remarks .. " " .. translate("Preproxy"))) + pt:value("", translate("Close")) + pt:value("main", translate("Preproxy Node")) + pt:depends("__hide__", "1") + + local fakedns_tag = s:option(Flag, _n(e[".name"] .. "_fakedns"), string.format('* %s', e.remarks .. " " .. "FakeDNS")) + + for k, v in pairs(socks_list) do + o:value(v.id, v.remark) + o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") + fakedns_tag:depends({ [_n("protocol")] = "_shunt", [_n("fakedns")] = true, [_n(e[".name"])] = v.id }) + end + for k, v in pairs(urltest_table) do + o:value(v.id, v.remark) + o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") + end + for k, v in pairs(iface_table) do + o:value(v.id, v.remark) + o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") + end + for k, v in pairs(nodes_table) do + o:value(v.id, v.remark) + o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") + if not api.is_local_ip(v.address) then --本地节点禁止使用前置 + pt:depends({ [_n("protocol")] = "_shunt", [_n("preproxy_enabled")] = true, [_n(e[".name"])] = v.id }) + end + fakedns_tag:depends({ [_n("protocol")] = "_shunt", [_n("fakedns")] = true, [_n(e[".name"])] = v.id }) + end + if default_node ~= "_direct" or default_node ~= "_blackhole" then + fakedns_tag:depends({ [_n("protocol")] = "_shunt", [_n("fakedns")] = true, [_n(e[".name"])] = "_default" }) end end end - end -end) + end) -o = s:option(DummyValue, _n("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({ [_n("protocol")] = "_shunt" }) + o = s:option(DummyValue, _n("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({ [_n("protocol")] = "_shunt" }) -local o = s:option(ListValue, _n("default_node"), string.format('* %s', translate("Default"))) -o:depends({ [_n("protocol")] = "_shunt" }) -o:value("_direct", translate("Direct Connection")) -o:value("_blackhole", translate("Blackhole")) -o.template = appname .. "/cbi/nodes_listvalue" -o.group = {"",""} + local o = s:option(ListValue, _n("default_node"), string.format('* %s', translate("Default"))) + o:depends({ [_n("protocol")] = "_shunt" }) + o:value("_direct", translate("Direct Connection")) + o:value("_blackhole", translate("Blackhole")) + o.template = appname .. "/cbi/nodes_listvalue" + o.group = {"",""} -if #nodes_table > 0 then - for k, v in pairs(socks_list) do - o:value(v.id, v.remark) - o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - end - for k, v in pairs(urltest_table) do - o:value(v.id, v.remark) - o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - end - for k, v in pairs(iface_table) do - o:value(v.id, v.remark) - o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - end - local dpt = s:option(ListValue, _n("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("", translate("Close")) - dpt:value("main", translate("Preproxy Node")) - dpt:depends("__hide__", "1") - for k, v in pairs(nodes_table) do - o:value(v.id, v.remark) - o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - if not api.is_local_ip(v.address) then - dpt:depends({ [_n("protocol")] = "_shunt", [_n("preproxy_enabled")] = true, [_n("default_node")] = v.id }) + if #nodes_table > 0 then + for k, v in pairs(socks_list) do + o:value(v.id, v.remark) + o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") + end + for k, v in pairs(urltest_table) do + o:value(v.id, v.remark) + o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") + end + for k, v in pairs(iface_table) do + o:value(v.id, v.remark) + o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") + end + local dpt = s:option(ListValue, _n("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("", translate("Close")) + dpt:value("main", translate("Preproxy Node")) + dpt:depends("__hide__", "1") + for k, v in pairs(nodes_table) do + o:value(v.id, v.remark) + o.group[#o.group+1] = (v.group and v.group ~= "") and v.group or translate("default") + if not api.is_local_ip(v.address) then + dpt:depends({ [_n("protocol")] = "_shunt", [_n("preproxy_enabled")] = true, [_n("default_node")] = v.id }) + end end end -end +end -- [[ 分流模块 End ]] --- [[ 分流模块 End ]] +if s.val["protocol"] == "_iface" then -- [[ 自定义接口 Start ]] + o = s:option(Value, _n("iface"), translate("Interface")) + o.default = "eth1" + o:depends({ [_n("protocol")] = "_iface" }) +end o = s:option(Value, _n("address"), translate("Address (Support Domain Name)")) diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ss-rust.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ss-rust.lua index 04b8686ce5..feb15c029b 100644 --- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ss-rust.lua +++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ss-rust.lua @@ -8,6 +8,14 @@ end local type_name = "SS-Rust" +-- [[ Shadowsocks Rust ]] + +s.fields["type"]:value(type_name, "Shadowsocks Rust") + +if s.val["type"] ~= type_name then + return +end + local option_prefix = "ssrust_" local function _n(name) @@ -20,10 +28,6 @@ local ssrust_encrypt_method_list = { "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305" } --- [[ Shadowsocks Rust ]] - -s.fields["type"]:value(type_name, translate("Shadowsocks Rust")) - o = s:option(ListValue, _n("del_protocol")) --始终隐藏,用于删除 protocol o:depends({ [_n("__hide")] = "1" }) o.rewrite_option = "protocol" diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ss.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ss.lua index fba5cd564c..1b28c488bc 100644 --- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ss.lua +++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ss.lua @@ -8,6 +8,14 @@ end local type_name = "SS" +-- [[ Shadowsocks Libev ]] + +s.fields["type"]:value(type_name, "Shadowsocks Libev") + +if s.val["type"] ~= type_name then + return +end + local option_prefix = "ss_" local function _n(name) @@ -21,10 +29,6 @@ local ss_encrypt_method_list = { "xchacha20-ietf-poly1305" } --- [[ Shadowsocks Libev ]] - -s.fields["type"]:value(type_name, translate("Shadowsocks Libev")) - o = s:option(ListValue, _n("del_protocol")) --始终隐藏,用于删除 protocol o:depends({ [_n("__hide")] = "1" }) o.rewrite_option = "protocol" diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ssr.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ssr.lua index 67629e6334..c8f5ac6f3c 100644 --- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ssr.lua +++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/ssr.lua @@ -8,6 +8,14 @@ end local type_name = "SSR" +-- [[ ShadowsocksR Libev ]] + +s.fields["type"]:value(type_name, "ShadowsocksR Libev") + +if s.val["type"] ~= type_name then + return +end + local option_prefix = "ssr_" local function _n(name) @@ -33,10 +41,6 @@ local ssr_obfs_list = { "tls1.0_session_auth", "tls1.2_ticket_auth" } --- [[ ShadowsocksR Libev ]] - -s.fields["type"]:value(type_name, translate("ShadowsocksR Libev")) - o = s:option(ListValue, _n("del_protocol")) --始终隐藏,用于删除 protocol o:depends({ [_n("__hide")] = "1" }) o.rewrite_option = "protocol" diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/trojan-plus.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/trojan-plus.lua index 016ae34ad4..22dd8e24ae 100644 --- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/trojan-plus.lua +++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/trojan-plus.lua @@ -8,16 +8,20 @@ end local type_name = "Trojan-Plus" +-- [[ Trojan Plus ]] + +s.fields["type"]:value(type_name, "Trojan-Plus") + +if s.val["type"] ~= type_name then + return +end + local option_prefix = "trojan_plus_" local function _n(name) return option_prefix .. name end --- [[ Trojan Plus ]] - -s.fields["type"]:value(type_name, "Trojan-Plus") - o = s:option(ListValue, _n("del_protocol")) --始终隐藏,用于删除 protocol o:depends({ [_n("__hide")] = "1" }) o.rewrite_option = "protocol" diff --git a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/tuic.lua b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/tuic.lua index cf748a3d4f..0d882fb1e6 100644 --- a/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/tuic.lua +++ b/applications/luci-app-passwall/luasrc/model/cbi/passwall/client/type/tuic.lua @@ -8,16 +8,20 @@ end local type_name = "TUIC" +-- [[ TUIC ]] + +s.fields["type"]:value(type_name, "TUIC") + +if s.val["type"] ~= type_name then + return +end + local option_prefix = "tuic_" local function _n(name) return option_prefix .. name end --- [[ TUIC ]] - -s.fields["type"]:value(type_name, translate("TUIC")) - o = s:option(ListValue, _n("del_protocol")) --始终隐藏,用于删除 protocol o:depends({ [_n("__hide")] = "1" }) o.rewrite_option = "protocol" diff --git a/applications/luci-app-passwall/luasrc/passwall/api.lua b/applications/luci-app-passwall/luasrc/passwall/api.lua index a878d8f892..05c39fa611 100644 --- a/applications/luci-app-passwall/luasrc/passwall/api.lua +++ b/applications/luci-app-passwall/luasrc/passwall/api.lua @@ -21,6 +21,8 @@ LOG_FILE = "/tmp/log/" .. appname .. ".log" TMP_PATH = "/tmp/etc/" .. appname TMP_IFACE_PATH = TMP_PATH .. "/iface" +NEW_PORT = nil + function log(...) local result = os.date("%Y-%m-%d %H:%M:%S: ") .. table.concat({...}, " ") local f, err = io.open(LOG_FILE, "a") @@ -94,6 +96,16 @@ function get_cache_var(key) return val end +function get_new_port() + local cmd_format = ". /usr/share/passwall/utils.sh ; echo -n $(get_new_port %s tcp,udp)" + local set_port = 0 + if NEW_PORT and tonumber(NEW_PORT) then + set_port = tonumber(NEW_PORT) + 1 + end + NEW_PORT = tonumber(sys.exec(string.format(cmd_format, set_port == 0 and "auto" or set_port))) + return NEW_PORT +end + function exec_call(cmd) local process = io.popen(cmd .. '; echo -e "\n$?"') local lines = {} diff --git a/applications/luci-app-passwall/luasrc/passwall/util_sing-box.lua b/applications/luci-app-passwall/luasrc/passwall/util_sing-box.lua index 8cdac56960..9f18299e8c 100644 --- a/applications/luci-app-passwall/luasrc/passwall/util_sing-box.lua +++ b/applications/luci-app-passwall/luasrc/passwall/util_sing-box.lua @@ -11,66 +11,74 @@ local local_version = api.get_app_version("sing-box"):match("[^v]+") local version_ge_1_11_0 = api.compare_versions(local_version, ">=", "1.11.0") local version_ge_1_12_0 = api.compare_versions(local_version, ">=", "1.12.0") -local geosite_all_tag = {} -local geoip_all_tag = {} -local srss_path = "/tmp/etc/" .. appname .."_tmp/srss/" +local GEO_VAR = { + OK = nil, + DIR = nil, + SITE_PATH = nil, + IP_PATH = nil, + SITE_TAGS = {}, + IP_TAGS = {}, + TO_SRS_PATH = "/tmp/etc/" .. appname .."_tmp/singbox_srss/" +} + +function check_geoview() + if not GEO_VAR.OK then + -- Only get once + GEO_VAR.OK = (api.finded_com("geoview") and api.compare_versions(api.get_app_version("geoview"), ">=", "0.1.10")) and 1 or 0 + end + if GEO_VAR.OK == 0 then + api.log("!!!注意:缺少 Geoview 组件或版本过低,Sing-Box 分流无法启用!") + else + GEO_VAR.DIR = GEO_VAR.DIR or (uci:get(appname, "@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"):match("^(.*)/") + GEO_VAR.SITE_PATH = GEO_VAR.SITE_PATH or (GEO_VAR.DIR .. "/geosite.dat") + GEO_VAR.IP_PATH = GEO_VAR.IP_PATH or (GEO_VAR.DIR .. "/geoip.dat") + if not fs.access(GEO_VAR.TO_SRS_PATH) then + fs.mkdir(GEO_VAR.TO_SRS_PATH) + end + end + return GEO_VAR.OK +end -local function convert_geofile() - if api.compare_versions(local_version, "<", "1.8.0") then - api.log("!!!注意:Sing-Box 版本低,Sing-Box 分流无法启用!请在[组件更新]中更新。") +function geo_convert_srs(var) + if check_geoview() ~= 1 then return end - local geo_dir = (uci:get(appname, "@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"):match("^(.*)/") - local geosite_path = geo_dir .. "/geosite.dat" - local geoip_path = geo_dir .. "/geoip.dat" - if not api.finded_com("geoview") then - api.log("!!!注意:缺少 Geoview 组件,Sing-Box 分流无法启用!请在[组件更新]中更新。") - return - else - if api.compare_versions(api.get_app_version("geoview"), "<", "0.1.10") then - api.log("!!!注意:Geoview 组件版本低,Sing-Box 分流无法启用!请在[组件更新]中更新。") - return + local geo_path = var["-geo_path"] + local prefix = var["-prefix"] + local rule_name = var["-rule_name"] + local output_srs_file = GEO_VAR.TO_SRS_PATH .. prefix .. "-" .. rule_name .. ".srs" + if not fs.access(output_srs_file) then + local cmd = string.format("geoview -type %s -action convert -input '%s' -list '%s' -output '%s' -lowmem=true", + prefix, geo_path, rule_name, output_srs_file) + sys.call(cmd) + local status = fs.access(output_srs_file) and "success." or "failed!" + if status == "failed!" then + api.log(string.format(" - %s:%s 转换为srs格式:%s", prefix, rule_name, status)) end end - if not fs.access(srss_path) then - fs.mkdir(srss_path) +end + +local function convert_geofile() + if check_geoview() ~= 1 then + return end local function convert(file_path, prefix, tags) if next(tags) and fs.access(file_path) then - local md5_file = srss_path .. prefix .. ".dat.md5" + local md5_file = GEO_VAR.TO_SRS_PATH .. prefix .. ".dat.md5" local new_md5 = sys.exec("md5sum " .. file_path .. " 2>/dev/null | awk '{print $1}'"):gsub("\n", "") local old_md5 = sys.exec("[ -f " .. md5_file .. " ] && head -n 1 " .. md5_file .. " | tr -d ' \t\n' || echo ''") if new_md5 ~= "" and new_md5 ~= old_md5 then sys.call("printf '%s' " .. new_md5 .. " > " .. md5_file) - sys.call("rm -rf " .. srss_path .. prefix .. "-*.srs" ) + sys.call("rm -rf " .. GEO_VAR.TO_SRS_PATH .. prefix .. "-*.srs" ) end for k in pairs(tags) do - local srs_file = srss_path .. prefix .. "-" .. k .. ".srs" - if not fs.access(srs_file) then - local cmd = string.format("geoview -type %s -action convert -input '%s' -list '%s' -output '%s' -lowmem=true", - prefix, file_path, k, srs_file) - sys.exec(cmd) - --local status = fs.access(srs_file) and "成功。" or "失败!" - --api.log(string.format(" - 转换 %s:%s ... %s", prefix, k, status)) - end + geo_convert_srs({["-geo_path"] = file_path, ["-prefix"] = prefix, ["-rule_name"] = k}) end end end --api.log("Sing-Box 规则集转换:") - convert(geosite_path, "geosite", geosite_all_tag) - convert(geoip_path, "geoip", geoip_all_tag) -end - -local new_port - -local function get_new_port() - local cmd_format = ". /usr/share/passwall/utils.sh ; echo -n $(get_new_port %s tcp)" - local set_port = 0 - if new_port and tonumber(new_port) then - set_port = tonumber(new_port) + 1 - end - new_port = tonumber(sys.exec(string.format(cmd_format, set_port == 0 and "auto" or set_port))) - return new_port + convert(GEO_VAR.SITE_PATH, "geosite", GEO_VAR.SITE_TAGS) + convert(GEO_VAR.IP_PATH, "geoip", GEO_VAR.IP_TAGS) end function gen_outbound(flag, node, tag, proxy_table) @@ -94,7 +102,7 @@ function gen_outbound(flag, node, tag, proxy_table) if node.type ~= "sing-box" then local relay_port = node.port - new_port = get_new_port() + local new_port = api.get_new_port() local config_file = string.format("%s_%s_%s.json", flag, tag, new_port) if tag and node_id and not tag:find(node_id) then config_file = string.format("%s_%s_%s_%s.json", flag, tag, node_id, new_port) @@ -925,6 +933,7 @@ function gen_config(var) local dns = nil local inbounds = {} local outbounds = {} + local rule_set_table = {} local COMMON = {} local singbox_settings = uci:get_all(appname, "@global_singbox[0]") or {} @@ -935,6 +944,59 @@ function gen_config(var) local experimental = nil + function add_rule_set(tab) + if tab and next(tab) and tab.tag and not rule_set_table[tab.tag]then + rule_set_table[tab.tag] = tab + end + end + + function parse_rule_set(w, rs) + -- Format: remote:https://raw.githubusercontent.com/lyc8503/sing-box-rules/rule-set-geosite/geosite-netflix.srs' + -- Format: local:/usr/share/sing-box/geosite-netflix.srs' + local result = nil + if w and #w > 0 then + if w:find("local:") == 1 or w:find("remote:") == 1 then + local _type = w:sub(1, w:find(":") - 1) -- "local" or "remote" + w = w:sub(w:find(":") + 1, #w) + local format = nil + local filename = w:sub(-w:reverse():find("/") + 1) -- geosite-netflix.srs + local suffix = "" + local find_doc = filename:reverse():find("%.") + if find_doc then + suffix = filename:sub(-find_doc + 1) -- "srs" or "json" + end + if suffix == "srs" then + format = "binary" + elseif suffix == "json" then + format = "source" + end + if format then + local rule_set_tag = filename:sub(1, filename:find("%.") - 1) --geosite-netflix + if rule_set_tag and #rule_set_tag > 0 then + if rs then + rule_set_tag = "rs_" .. rule_set_tag + end + result = { + type = _type, + tag = rule_set_tag, + format = format, + path = _type == "local" and w or nil, + url = _type == "remote" and w or nil, + --download_detour = _type == "remote" and "", + --update_interval = _type == "remote" and "", + } + end + end + end + end + return result + end + + function geo_rule_set(prefix, rule_name) + local output_srs_file = "local:" .. GEO_VAR.TO_SRS_PATH .. prefix .. "-" .. rule_name .. ".srs" + return parse_rule_set(output_srs_file) + end + if node_id then local node = uci:get_all(appname, node_id) if node then @@ -1154,6 +1216,8 @@ function gen_config(var) local preproxy_tag = preproxy_rule_name local preproxy_node_id = preproxy_rule_name and node["main_node"] or nil + inner_fakedns = node.fakedns or "0" + local function gen_shunt_node(rule_name, _node_id) if not rule_name then return nil, nil end if not _node_id then _node_id = node[rule_name] end @@ -1205,7 +1269,7 @@ function gen_config(var) pre_proxy = true end if pre_proxy then - new_port = get_new_port() + local new_port = api.get_new_port() table.insert(inbounds, { type = "direct", tag = "proxy_" .. rule_name, @@ -1344,6 +1408,8 @@ function gen_config(var) if is_private or #source_ip_cidr > 0 then rule.rule_set_ip_cidr_match_source = true end end + --[[ + -- Too low usage rate, hidden if e.sourcePort then local source_port = {} local source_port_range = {} @@ -1357,6 +1423,7 @@ function gen_config(var) 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 = {} @@ -1372,7 +1439,7 @@ function gen_config(var) rule.port_range = #port_range > 0 and port_range or nil end - local rule_set_tag = {} + local rule_set = {} if e.domain_list then local domain_table = { @@ -1382,20 +1449,34 @@ function gen_config(var) domain_keyword = {}, domain_regex = {}, rule_set = {}, + fakedns = nil, + invert = e.invert == "1" and true or nil } string.gsub(e.domain_list, '[^' .. "\r\n" .. ']+', function(w) if w:find("#") == 1 then return end if w:find("geosite:") == 1 then local _geosite = w:sub(1 + #"geosite:") --适配srs - geosite_all_tag[_geosite] = true - table.insert(rule_set_tag, "geosite-" .. _geosite) - table.insert(domain_table.rule_set, "geosite-" .. _geosite) + local t = geo_rule_set("geosite", _geosite) + if t then + GEO_VAR.SITE_TAGS[_geosite] = true + add_rule_set(t) + table.insert(rule_set, t.tag) + table.insert(domain_table.rule_set, t.tag) + end elseif w:find("regexp:") == 1 then table.insert(domain_table.domain_regex, w:sub(1 + #"regexp:")) elseif w:find("full:") == 1 then table.insert(domain_table.domain, w:sub(1 + #"full:")) elseif w:find("domain:") == 1 then table.insert(domain_table.domain_suffix, w:sub(1 + #"domain:")) + elseif w:find("rule-set:", 1, true) == 1 or w:find("rs:") == 1 then + w = w:sub(w:find(":") + 1, #w) + local t = parse_rule_set(w, true) + if t then + add_rule_set(t) + table.insert(rule_set, t.tag) + table.insert(domain_table.rule_set, t.tag) + end else table.insert(domain_table.domain_keyword, w) end @@ -1404,6 +1485,10 @@ function gen_config(var) rule.domain_suffix = #domain_table.domain_suffix > 0 and domain_table.domain_suffix or nil rule.domain_keyword = #domain_table.domain_keyword > 0 and domain_table.domain_keyword or nil rule.domain_regex = #domain_table.domain_regex > 0 and domain_table.domain_regex or nil + rule.rule_set = #domain_table.rule_set > 0 and domain_table.rule_set or nil + if inner_fakedns == "1" and node[e[".name"] .. "_fakedns"] == "1" then + domain_table.fakedns = true + end if outboundTag then table.insert(dns_domain_rules, api.clone(domain_table)) @@ -1420,8 +1505,19 @@ function gen_config(var) if _geoip == "private" then is_private = true else - geoip_all_tag[_geoip] = true - table.insert(rule_set_tag, "geoip-" .. _geoip) + local t = geo_rule_set("geoip", _geoip) + if t then + GEO_VAR.IP_TAGS[_geoip] = true + add_rule_set(t) + table.insert(rule_set, t.tag) + end + end + elseif w:find("rule-set:", 1, true) == 1 or w:find("rs:") == 1 then + w = w:sub(w:find(":") + 1, #w) + local t = parse_rule_set(w, true) + if t then + add_rule_set(t) + table.insert(rule_set, t.tag) end else table.insert(ip_cidr, w) @@ -1432,7 +1528,8 @@ function gen_config(var) rule.ip_cidr = #ip_cidr > 0 and ip_cidr or nil end - rule.rule_set = #rule_set_tag > 0 and rule_set_tag or nil --适配srs + rule.rule_set = #rule_set > 0 and rule_set or nil --适配srs + rule.invert = e.invert == "1" and true or nil table.insert(rules, rule) end @@ -1441,34 +1538,6 @@ function gen_config(var) for index, value in ipairs(rules) do table.insert(route.rules, rules[index]) end - - local rule_set = {} --适配srs - if next(geosite_all_tag) then - for k,v in pairs(geosite_all_tag) do - local srs_file = srss_path .. "geosite-" .. k ..".srs" - local _rule_set = { - tag = "geosite-" .. k, - type = "local", - format = "binary", - path = srs_file - } - table.insert(rule_set, _rule_set) - end - end - if next(geoip_all_tag) then - for k,v in pairs(geoip_all_tag) do - local srs_file = srss_path .. "geoip-" .. k ..".srs" - local _rule_set = { - tag = "geoip-" .. k, - type = "local", - format = "binary", - path = srs_file - } - table.insert(rule_set, _rule_set) - end - end - route.rule_set = #rule_set >0 and rule_set or nil - elseif node.protocol == "_urltest" then if node.urltest_node then COMMON.default_outbound_tag = gen_urltest(node) @@ -1571,7 +1640,7 @@ function gen_config(var) table.insert(dns.servers, remote_server) end - if remote_dns_fake then + if remote_dns_fake or inner_fakedns == "1" then dns.fakeip = { enabled = true, inet4_range = "198.18.0.0/15", @@ -1637,7 +1706,7 @@ function gen_config(var) table.insert(dns.servers, remote_server) end - if remote_dns_fake then + if remote_dns_fake or inner_fakedns == "1" then table.insert(dns.servers, { tag = fakedns_tag, type = "fakeip", @@ -1755,8 +1824,9 @@ function gen_config(var) domain_suffix = (value.domain_suffix and #value.domain_suffix > 0) and value.domain_suffix or nil, domain_keyword = (value.domain_keyword and #value.domain_keyword > 0) and value.domain_keyword or nil, domain_regex = (value.domain_regex and #value.domain_regex > 0) and value.domain_regex or nil, - rule_set = (value.rule_set and #value.rule_set > 0) and value.rule_set or nil, --适配srs + rule_set = (value.rule_set and #value.rule_set > 0) and value.rule_set or nil, --适配srs disable_cache = false, + invert = value.invert, strategy = (version_ge_1_12_0 and value.outboundTag == "direct") and direct_strategy or nil --Migrate to 1.12 DNS } if version_ge_1_12_0 and value.outboundTag == "block" then --Migrate to 1.12 DNS @@ -1778,7 +1848,7 @@ function gen_config(var) table.insert(dns.servers, remote_shunt_server) dns_rule.server = remote_shunt_server.tag end - if remote_dns_fake then + if value.fakedns then local fakedns_dns_rule = api.clone(dns_rule) fakedns_dns_rule.query_type = { "A", "AAAA" @@ -1813,6 +1883,13 @@ function gen_config(var) }) end + if next(rule_set_table) then + route.rule_set = {} + for k, v in pairs(rule_set_table) do + table.insert(route.rule_set, v) + end + end + if inbounds or outbounds then local config = { log = { @@ -2053,12 +2130,13 @@ end _G.gen_config = gen_config _G.gen_proto_config = gen_proto_config +_G.geo_convert_srs = geo_convert_srs if arg[1] then local func =_G[arg[1]] if func then print(func(api.get_function_args(arg))) - if (next(geosite_all_tag) or next(geoip_all_tag)) and not no_run then + if (next(GEO_VAR.SITE_TAGS) or next(GEO_VAR.IP_TAGS)) and not no_run then convert_geofile() end end diff --git a/applications/luci-app-passwall/luasrc/passwall/util_xray.lua b/applications/luci-app-passwall/luasrc/passwall/util_xray.lua index 7c9ae0ad59..a09af5d945 100644 --- a/applications/luci-app-passwall/luasrc/passwall/util_xray.lua +++ b/applications/luci-app-passwall/luasrc/passwall/util_xray.lua @@ -6,18 +6,6 @@ local jsonc = api.jsonc local appname = "passwall" local fs = api.fs -local new_port - -local function get_new_port() - local cmd_format = ". /usr/share/passwall/utils.sh ; echo -n $(get_new_port %s tcp)" - local set_port = 0 - if new_port and tonumber(new_port) then - set_port = tonumber(new_port) + 1 - end - new_port = tonumber(sys.exec(string.format(cmd_format, set_port == 0 and "auto" or set_port))) - return new_port -end - local function get_noise_packets() local noises = {} uci:foreach(appname, "xray_noise_packets", function(n) @@ -73,7 +61,7 @@ function gen_outbound(flag, node, tag, proxy_table) node.transport = "tcp" else local relay_port = node.port - new_port = get_new_port() + local new_port = api.get_new_port() local config_file = string.format("%s_%s_%s.json", flag, tag, new_port) if tag and node_id and not tag:find(node_id) then config_file = string.format("%s_%s_%s_%s.json", flag, tag, node_id, new_port) @@ -273,7 +261,7 @@ function gen_outbound(flag, node, tag, proxy_table) port = string.gsub(node.hysteria2_hop, ":", "-"), interval = (function() local v = tonumber((node.hysteria2_hop_interval or "30s"):match("^%d+")) - return (v and v >= 5) and (v .. "s") or "30s" + return (v and v >= 5) and v or 30 end)() } or nil, maxIdleTimeout = (function() @@ -731,50 +719,6 @@ function gen_config(var) table.insert(inbounds, inbound) end - if tcp_redir_port or udp_redir_port then - local inbound = { - protocol = "dokodemo-door", - settings = {network = "tcp,udp", followRedirect = true}, - streamSettings = {sockopt = {tproxy = "tproxy"}}, - sniffing = { - enabled = xray_settings.sniffing_override_dest == "1" or node.protocol == "_shunt" - } - } - if inbound.sniffing.enabled == true then - inbound.sniffing.destOverride = {"http", "tls", "quic"} - inbound.sniffing.metadataOnly = false - inbound.sniffing.routeOnly = xray_settings.sniffing_override_dest ~= "1" or nil - inbound.sniffing.domainsExcluded = xray_settings.sniffing_override_dest == "1" and get_domain_excluded() or nil - end - if remote_dns_fake then - inbound.sniffing.enabled = true - if not inbound.sniffing.destOverride then - inbound.sniffing.destOverride = {"fakedns"} - inbound.sniffing.metadataOnly = true - else - table.insert(inbound.sniffing.destOverride, "fakedns") - inbound.sniffing.metadataOnly = false - end - end - - if tcp_redir_port then - local tcp_inbound = api.clone(inbound) - tcp_inbound.tag = "tcp_redir" - tcp_inbound.settings.network = "tcp" - tcp_inbound.port = tonumber(tcp_redir_port) - tcp_inbound.streamSettings.sockopt.tproxy = tcp_proxy_way - table.insert(inbounds, tcp_inbound) - end - - if udp_redir_port then - local udp_inbound = api.clone(inbound) - udp_inbound.tag = "udp_redir" - udp_inbound.settings.network = "udp" - udp_inbound.port = tonumber(udp_redir_port) - table.insert(inbounds, udp_inbound) - end - end - local function gen_loopback(outbound_tag, loopback_dst) if not outbound_tag or outbound_tag == "" then return nil end local inbound_tag = loopback_dst and "lo-to-" .. loopback_dst or outbound_tag .. "-lo" @@ -993,6 +937,8 @@ function gen_config(var) local preproxy_outbound_tag, preproxy_balancer_tag local preproxy_nodes + inner_fakedns = node.fakedns or "0" + local function gen_shunt_node(rule_name, _node_id) if not rule_name then return nil, nil end if not _node_id then @@ -1047,7 +993,7 @@ function gen_config(var) end --new outbound if use_proxy and _node.type ~= "Xray" then - new_port = get_new_port() + local new_port = api.get_new_port() table.insert(inbounds, { tag = "proxy_" .. rule_name, listen = "127.0.0.1", @@ -1193,13 +1139,18 @@ function gen_config(var) outboundTag = outbound_tag, balancerTag = balancer_tag, domain = {}, + fakedns = nil, } domains = {} string.gsub(e.domain_list, '[^' .. "\r\n" .. ']+', function(w) if w:find("#") == 1 then return end + if w:find("rule-set:", 1, true) == 1 or w:find("rs:") == 1 then return end table.insert(domains, w) table.insert(domain_table.domain, w) end) + if inner_fakedns == "1" and node[e[".name"] .. "_fakedns"] == "1" and #domains > 0 then + domain_table.fakedns = true + end if outbound_tag or balancer_tag then table.insert(dns_domain_rules, api.clone(domain_table)) end @@ -1210,6 +1161,7 @@ function gen_config(var) ip = {} string.gsub(e.ip_list, '[^' .. "\r\n" .. ']+', function(w) if w:find("#") == 1 then return end + if w:find("rule-set:", 1, true) == 1 or w:find("rs:") == 1 then return end table.insert(ip, w) end) if #ip == 0 then ip = nil end @@ -1228,7 +1180,7 @@ function gen_config(var) balancerTag = balancer_tag, network = e["network"] or "tcp,udp", source = source, - sourcePort = e["sourcePort"] ~= "" and e["sourcePort"] or nil, + --sourcePort = e["sourcePort"] ~= "" and e["sourcePort"] or nil, port = e["port"] ~= "" and e["port"] or nil, protocol = protocols } @@ -1320,6 +1272,50 @@ function gen_config(var) network = "tcp,udp" }) end + + if tcp_redir_port or udp_redir_port then + local inbound = { + protocol = "dokodemo-door", + settings = {network = "tcp,udp", followRedirect = true}, + streamSettings = {sockopt = {tproxy = "tproxy"}}, + sniffing = { + enabled = xray_settings.sniffing_override_dest == "1" or node.protocol == "_shunt" + } + } + if inbound.sniffing.enabled == true then + inbound.sniffing.destOverride = {"http", "tls", "quic"} + inbound.sniffing.metadataOnly = false + inbound.sniffing.routeOnly = xray_settings.sniffing_override_dest ~= "1" or nil + inbound.sniffing.domainsExcluded = xray_settings.sniffing_override_dest == "1" and get_domain_excluded() or nil + end + if remote_dns_fake or inner_fakedns == "1" then + inbound.sniffing.enabled = true + if not inbound.sniffing.destOverride then + inbound.sniffing.destOverride = {"fakedns"} + inbound.sniffing.metadataOnly = true + else + table.insert(inbound.sniffing.destOverride, "fakedns") + inbound.sniffing.metadataOnly = false + end + end + + if tcp_redir_port then + local tcp_inbound = api.clone(inbound) + tcp_inbound.tag = "tcp_redir" + tcp_inbound.settings.network = "tcp" + tcp_inbound.port = tonumber(tcp_redir_port) + tcp_inbound.streamSettings.sockopt.tproxy = tcp_proxy_way + table.insert(inbounds, tcp_inbound) + end + + if udp_redir_port then + local udp_inbound = api.clone(inbound) + udp_inbound.tag = "udp_redir" + udp_inbound.settings.network = "udp" + udp_inbound.port = tonumber(udp_redir_port) + table.insert(inbounds, udp_inbound) + end + end end if (remote_dns_udp_server and remote_dns_udp_port) or (remote_dns_tcp_server and remote_dns_tcp_port) then @@ -1402,7 +1398,7 @@ function gen_config(var) address = "fakedns", } - if remote_dns_fake then + if remote_dns_fake or inner_fakedns == "1" then fakedns = {} local fakedns4 = { ipPool = "198.18.0.0/15", @@ -1420,7 +1416,9 @@ function gen_config(var) elseif remote_dns_query_strategy == "UseIPv6" then table.insert(fakedns, fakedns6) end - table.insert(dns.servers, 1, _remote_fakedns) + if remote_dns_fake and inner_fakedns == "0" then + table.insert(dns.servers, 1, _remote_fakedns) + end end local dns_outbound_tag = "direct" @@ -1510,7 +1508,7 @@ function gen_config(var) if value.outboundTag == "direct" and _direct_dns.address then dns_server = api.clone(_direct_dns) else - if remote_dns_fake then + if value.fakedns then dns_server = api.clone(_remote_fakedns) else dns_server = api.clone(_remote_dns) diff --git a/applications/luci-app-passwall/luasrc/view/passwall/global/footer.htm b/applications/luci-app-passwall/luasrc/view/passwall/global/footer.htm index 221949738f..ddf3b139c8 100644 --- a/applications/luci-app-passwall/luasrc/view/passwall/global/footer.htm +++ b/applications/luci-app-passwall/luasrc/view/passwall/global/footer.htm @@ -1,5 +1,5 @@ <% -local api = require "luci.passwall.api" +local api = self.api -%> diff --git a/applications/luci-app-passwall/luasrc/view/passwall/node_config/header.htm b/applications/luci-app-passwall/luasrc/view/passwall/node_config/header.htm new file mode 100644 index 0000000000..7824b1a763 --- /dev/null +++ b/applications/luci-app-passwall/luasrc/view/passwall/node_config/header.htm @@ -0,0 +1,53 @@ +<% +local api = self.api +-%> + diff --git a/applications/luci-app-passwall/luasrc/view/passwall/node_list/link_share_man.htm b/applications/luci-app-passwall/luasrc/view/passwall/node_config/link_share_man.htm similarity index 98% rename from applications/luci-app-passwall/luasrc/view/passwall/node_list/link_share_man.htm rename to applications/luci-app-passwall/luasrc/view/passwall/node_config/link_share_man.htm index 46440b1388..a7ad9e9e31 100644 --- a/applications/luci-app-passwall/luasrc/view/passwall/node_list/link_share_man.htm +++ b/applications/luci-app-passwall/luasrc/view/passwall/node_config/link_share_man.htm @@ -692,7 +692,7 @@ return server.replace(/^\[/, '').replace(/\]$/, ''); } - function fromUrl(btn, urlname, sid) { + function fromUrl(btn, urlname, sid, cacheData) { var opt = { base: 'cbid.passwall', client: true, @@ -800,9 +800,21 @@ } opt.base = "cbid." + urlname + "." + sid; opt.client = urlname.indexOf("server") === -1; - var ssrurl = prompt('<%:Paste Share URL Here%>', ''); - if (ssrurl === null || ssrurl === "") { - return false; + var ssrurl = null; + if (cacheData) { + ssrurl = cacheData.ssrurl + } else { + ssrurl = prompt('<%:Paste Share URL Here%>', ''); + if (ssrurl === null || ssrurl === "") { + return false; + } + sessionStorage.setItem("fromUrl", JSON.stringify({ + timestamp: Date.now(), + savetime: 60 * 1000, + urlname: urlname, + sid: sid, + ssrurl: ssrurl + })); } ssrurl = ssrurl.replace(/&/gi, '&').replace(/\s*#\s*/, '#').trim(); //一些奇葩的链接用"&"当做"&","#"前后带空格 s.innerHTML = ""; @@ -1658,13 +1670,23 @@ return false; } s.innerHTML = "<%:Import Finished %>"; - return false; + sessionStorage.removeItem("fromUrl"); + return true; } function exportConfigFile(btn, sid) { window.open('<%=api.url("gen_client_config")%>?id=' + sid, "_blank") } + document.addEventListener("DOMContentLoaded", function () { + const fromUrlCache = JSON.parse(sessionStorage.getItem("fromUrl")); + if (fromUrlCache && fromUrlCache.savetime && (Date.now() - fromUrlCache.timestamp) < fromUrlCache.savetime) { + fromUrl(null, fromUrlCache.urlname, fromUrlCache.sid, fromUrlCache) + } else { + sessionStorage.removeItem("fromUrl"); + } + }) + //]]>