diff --git a/luci-app-ikoolproxy/root/usr/share/koolproxy/kpupdate b/luci-app-ikoolproxy/root/usr/share/koolproxy/kpupdate index 548815bc..90966272 100755 --- a/luci-app-ikoolproxy/root/usr/share/koolproxy/kpupdate +++ b/luci-app-ikoolproxy/root/usr/share/koolproxy/kpupdate @@ -126,7 +126,7 @@ update_rules() { yhosts_rules_local=`cat /usr/share/koolproxy/data/rules/yhosts.txt | sed -n '1p' | cut -d ":" -f2` antiad_rules_local=`cat /usr/share/koolproxy/data/rules/antiad.txt | sed -n '2p' | cut -d "=" -f2` koolproxy_rules_local=`cat /usr/share/koolproxy/data/rules/koolproxy.txt | sed -n '3p'|awk '{print $3,$4}'` - adgk_rules_local=`cat /usr/share/koolproxy/data/rules/adgk.txt | sed -n '1p'|awk '{print $3}'` + adgk_rules_local=`cat /usr/share/koolproxy/data/rules/adgk.txt | sed -n '2p'|awk '{print $3}'` echo $(date "+%F %T"): -------------------AdGuard规则 Version $adg_rules_local >>$LOGFILE echo $(date "+%F %T"): -------------------Steven规则 Version $steven_rules_local >>$LOGFILE echo $(date "+%F %T"): -------------------Yhosts规则 Version $yhosts_rules_local >>$LOGFILE diff --git a/luci-app-passwall2/Makefile b/luci-app-passwall2/Makefile index 2df4d26b..3de58079 100644 --- a/luci-app-passwall2/Makefile +++ b/luci-app-passwall2/Makefile @@ -5,11 +5,12 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-passwall2 -PKG_VERSION:=1.10 -PKG_RELEASE:=6 +PKG_VERSION:=1.11 +PKG_RELEASE:=1 PKG_CONFIG_DEPENDS:= \ CONFIG_PACKAGE_$(PKG_NAME)_Transparent_Proxy \ + CONFIG_PACKAGE_$(PKG_NAME)_Transparent_Proxy_Iptables-nft \ CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Brook \ CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Hysteria \ CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_IPv6_Nat \ @@ -52,6 +53,7 @@ menu "Configuration" config PACKAGE_$(PKG_NAME)_Transparent_Proxy bool "Transparent Proxy" select PACKAGE_dnsmasq-full + select PACKAGE_dnsmasq_full_ipset select PACKAGE_ipset select PACKAGE_iptables select PACKAGE_iptables-zz-legacy @@ -61,7 +63,21 @@ config PACKAGE_$(PKG_NAME)_Transparent_Proxy select PACKAGE_iptables-mod-conntrack-extra select PACKAGE_kmod-ipt-nat depends on PACKAGE_$(PKG_NAME) - default y + default y if ! PACKAGE_firewall4 + +config PACKAGE_$(PKG_NAME)_Transparent_Proxy_Iptables-nft + bool "Transparent Proxy Use Iptables-nft" + select PACKAGE_dnsmasq-full + select PACKAGE_dnsmasq_full_ipset + select PACKAGE_ipset + select PACKAGE_iptables-nft + select PACKAGE_iptables-mod-iprange + select PACKAGE_iptables-mod-socket + select PACKAGE_iptables-mod-tproxy + select PACKAGE_iptables-mod-conntrack-extra + select PACKAGE_kmod-ipt-nat + depends on PACKAGE_$(PKG_NAME) + default y if PACKAGE_firewall4 config PACKAGE_$(PKG_NAME)_INCLUDE_Brook bool "Include Brook" diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_config.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_config.lua index 8db52516..9e4c5eb6 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_config.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_config.lua @@ -129,10 +129,21 @@ for k, e in ipairs(api.get_valid_nodes()) do end -- 负载均衡列表 -balancing_node = s:option(DynamicList, "balancing_node", translate("Load balancing node list"), translate("Load balancing node list, document")) +local balancing_node = s:option(DynamicList, "balancing_node", translate("Load balancing node list"), translate("Load balancing node list, document")) for k, v in pairs(nodes_table) do balancing_node:value(v.id, v.remarks) end balancing_node:depends("protocol", "_balancing") +local balancingStrategy = s:option(ListValue, "balancingStrategy", translate("Balancing Strategy")) +balancingStrategy:depends("protocol", "_balancing") +balancingStrategy:value("random") +balancingStrategy:value("leastPing") +balancingStrategy.default = "random" + +local probeInterval = s:option(Value, "probeInterval", translate("Probe Interval")) +probeInterval:depends("balancingStrategy", "leastPing") +probeInterval.default = "1m" +probeInterval.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.") + -- 分流 uci:foreach(appname, "shunt_rules", function(e) if e[".name"] and e.remarks then @@ -192,13 +203,11 @@ domainStrategy.description = "
" -domainStrategy:depends("protocol", "_balancing") domainStrategy:depends("protocol", "_shunt") domainMatcher = s:option(ListValue, "domainMatcher", translate("Domain matcher")) domainMatcher:value("hybrid") domainMatcher:value("linear") -domainMatcher:depends("protocol", "_balancing") domainMatcher:depends("protocol", "_shunt") @@ -544,9 +553,9 @@ xray_fingerprint:value("chrome") xray_fingerprint:value("firefox") xray_fingerprint:value("safari") xray_fingerprint:value("ios") -xray_fingerprint:value("android") +--xray_fingerprint:value("android") xray_fingerprint:value("edge") -xray_fingerprint:value("360") +--xray_fingerprint:value("360") xray_fingerprint:value("qq") xray_fingerprint:value("random") xray_fingerprint:value("randomized") @@ -579,9 +588,9 @@ reality_fingerprint:value("chrome") reality_fingerprint:value("firefox") reality_fingerprint:value("safari") reality_fingerprint:value("ios") -reality_fingerprint:value("android") +--reality_fingerprint:value("android") reality_fingerprint:value("edge") -reality_fingerprint:value("360") +--reality_fingerprint:value("360") reality_fingerprint:value("qq") reality_fingerprint:value("random") reality_fingerprint:value("randomized") @@ -638,6 +647,11 @@ wireguard_mtu = s:option(Value, "wireguard_mtu", translate("MTU")) wireguard_mtu.default = "1420" wireguard_mtu:depends({ type = "Xray", protocol = "wireguard" }) +if api.compare_versions(api.get_app_version("xray"), ">=", "1.8.0") then + wireguard_reserved = s:option(Value, "wireguard_reserved", translate("Reserved")) + wireguard_reserved:depends({ type = "Xray", protocol = "wireguard" }) +end + wireguard_keepAlive = s:option(Value, "wireguard_keepAlive", translate("Keep Alive")) wireguard_keepAlive.default = "0" wireguard_keepAlive:depends({ type = "Xray", protocol = "wireguard" }) @@ -833,6 +847,9 @@ hysteria_hop_interval:depends("type", "Hysteria") hysteria_disable_mtu_discovery = s:option(Flag, "hysteria_disable_mtu_discovery", translate("Disable MTU detection")) hysteria_disable_mtu_discovery:depends("type", "Hysteria") +hysteria_lazy_start = s:option(Flag, "hysteria_lazy_start", translate("Lazy Start")) +hysteria_lazy_start:depends("type", "Hysteria") + protocol.validate = function(self, value) if value == "_shunt" or value == "_balancing" then address.rmempty = true diff --git a/luci-app-passwall2/luasrc/passwall2/api.lua b/luci-app-passwall2/luasrc/passwall2/api.lua index b2b90872..f42a4a49 100644 --- a/luci-app-passwall2/luasrc/passwall2/api.lua +++ b/luci-app-passwall2/luasrc/passwall2/api.lua @@ -13,6 +13,17 @@ command_timeout = 300 LEDE_BOARD = nil DISTRIB_TARGET = nil +LOG_FILE = "/tmp/log/passwall2.log" + +function log(...) + local result = os.date("%Y-%m-%d %H:%M:%S: ") .. table.concat({...}, " ") + local f, err = io.open(LOG_FILE, "a") + if f and err == nil then + f:write(result .. "\n") + f:close() + end +end + function exec_call(cmd) local process = io.popen(cmd .. '; echo -e "\n$?"') local lines = {} @@ -355,7 +366,7 @@ function get_customed_path(e) end function is_finded(e) - return luci.sys.exec('type -t -p "/bin/%s" -p "%s" "%s"' % {e, get_customed_path(e), e}) ~= "" and true or false + 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 end diff --git a/luci-app-passwall2/luasrc/passwall2/util_hysteria.lua b/luci-app-passwall2/luasrc/passwall2/util_hysteria.lua index 1d7d7140..ee00a0d3 100644 --- a/luci-app-passwall2/luasrc/passwall2/util_hysteria.lua +++ b/luci-app-passwall2/luasrc/passwall2/util_hysteria.lua @@ -74,6 +74,7 @@ function gen_config(var) hop_interval = (node.hysteria_hop_interval) and tonumber(node.hysteria_hop_interval) or nil, disable_mtu_discovery = (node.hysteria_disable_mtu_discovery) and true or false, fast_open = (node.fast_open == "1") and true or false, + lazy_start = (node.hysteria_lazy_start) and true or false, socks5 = (local_socks_address and local_socks_port) and { listen = local_socks_address .. ":" .. local_socks_port, timeout = 300, diff --git a/luci-app-passwall2/luasrc/passwall2/util_xray.lua b/luci-app-passwall2/luasrc/passwall2/util_xray.lua index fdba97ad..c5f93620 100644 --- a/luci-app-passwall2/luasrc/passwall2/util_xray.lua +++ b/luci-app-passwall2/luasrc/passwall2/util_xray.lua @@ -106,6 +106,14 @@ function gen_outbound(flag, node, tag, proxy_table) end end + if node.protocol == "wireguard" and node.wireguard_reserved then + local bytes = {} + node.wireguard_reserved:gsub("[^,]+", function(b) + bytes[#bytes+1] = tonumber(b) + end) + node.wireguard_reserved = #bytes > 0 and bytes or nil + end + result = { _flag_tag = node_id, _flag_proxy = proxy, @@ -230,7 +238,8 @@ function gen_outbound(flag, node, tag, proxy_table) keepAlive = node.wireguard_keepAlive and tonumber(node.wireguard_keepAlive) or nil } } or nil, - mtu = (node.protocol == "wireguard" and node.wireguard_mtu) and tonumber(node.wireguard_mtu) or nil + mtu = (node.protocol == "wireguard" and node.wireguard_mtu) and tonumber(node.wireguard_mtu) or nil, + reserved = (node.protocol == "wireguard" and node.wireguard_reserved) and node.wireguard_reserved or nil } } local alpn = {} @@ -522,6 +531,7 @@ function gen_config(var) local inbounds = {} local outbounds = {} local routing = nil + local observatory = nil if local_socks_port then local inbound = { @@ -817,10 +827,18 @@ function gen_config(var) local outbound = gen_outbound(flag, node) if outbound then table.insert(outbounds, outbound) end end + if node.balancingStrategy == "leastPing" then + observatory = { + subjectSelector = nodes, + probeInterval = node.probeInterval or "1m" + } + end routing = { - domainStrategy = node.domainStrategy or "AsIs", - domainMatcher = node.domainMatcher or "hybrid", - balancers = {{tag = "balancer", selector = nodes}}, + balancers = {{ + tag = "balancer", + selector = nodes, + strategy = {type = node.balancingStrategy or "random"} + }}, rules = { {type = "field", network = "tcp,udp", balancerTag = "balancer"} } @@ -1093,6 +1111,8 @@ function gen_config(var) inbounds = inbounds, -- 传出连接 outbounds = outbounds, + -- 连接观测 + observatory = observatory, -- 路由 routing = routing, -- 本地策略 @@ -1277,7 +1297,7 @@ function gen_dns_config(var) dns = { tag = "dns-in1", hosts = {}, - disableCache = (dns_cache and dns_cache == "0") and true or false, + disableCache = (dns_cache == "1") and false or true, disableFallback = true, disableFallbackIfMatch = true, servers = {}, 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 bb85f8a1..18e93bfb 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 @@ -56,26 +56,18 @@ local has_xray = api.is_finded("xray") return b64decsafe(v); } function parseNodeUrl(url) { - var m = url.match(/^(([^:\/?#]+:)?(?:\/\/((?:([^\/?#:]*)([^\/?#:]*)@)?([^\/?#:]*)(?::([^\/?#:]*))?)))?([^?#]*)(\?[^#]*)?(#.*)?$/), - r = { - hash: m[10] || "", // #asd - host: m[3] || "", // localhost:257 - hostname: m[6] || "", // localhost - href: m[0] || "", // http://username:password@localhost:257/deploy/?asd=asd#asd - origin: m[1] || "", // http://username:password@localhost:257 - pathname: m[8] || (m[1] ? "/" : ""), // /deploy/ - port: m[7] || "", // 257 - protocol: m[2] || "", // http: - search: m[9] || "", // ?asd=asd - passwd: m[4] || "", // username - removed: m[5] || "" // password - }; - if (r.protocol.length === 2) { - r.protocol = "file:///" + r.protocol.toUpperCase(); - r.origin = r.protocol + "//" + r.host; - } - r.href = r.origin + r.pathname + r.search + r.hash; - return m && r; + let protocol = url.substring(0, url.indexOf("://")) + ":" + let str = "http" + url.substring(url.indexOf("://")) + const parsedUrl = new URL(str); + var r = { + hash: parsedUrl.hash, // #asd + host: parsedUrl.host, // localhost:257 + hostname: parsedUrl.hostname, // localhost + port: parsedUrl.port, // 257 + search: parsedUrl.search, // ?asd=asd + passwd: parsedUrl.username || parsedUrl.password // username + }; + return r; } function buildUrl(btn, urlname, sid) { @@ -130,15 +122,25 @@ local has_xray = api.is_finded("xray") opt.client = urlname.indexOf("server") === -1; var v_type = opt.get("type").value; var v_alias = opt.get("remarks"); + var _address = "" + try { + var v_server = opt.get("address"); + const ipv6Regex = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/; + if (ipv6Regex.test(v_server.value)) { + _address = "[" + v_server.value + "]" + } else { + _address = v_server.value + } + } catch(e) { + } var url = null; if (v_type === "SS") { - var v_server = opt.get("address"); var v_port = opt.get("port"); var v_method = opt.get("ss_encrypt_method"); var v_password = opt.get("password"); url = b64encsafe(v_method.value + ":" + v_password.value) + "@" + - v_server.value + ":" + + _address + ":" + v_port.value + "/?"; var params = ""; @@ -160,7 +162,6 @@ local has_xray = api.is_finded("xray") } url += params; } else if (v_type === "SSR") { - var v_server = opt.get("address"); var v_port = opt.get("port"); var v_protocol = opt.get("ssr_protocol"); var v_method = opt.get("ssr_encrypt_method"); @@ -168,7 +169,7 @@ local has_xray = api.is_finded("xray") var v_password = opt.get("password"); var v_obfs_param = opt.get("obfs_param"); var v_protocol_param = opt.get("protocol_param"); - var ssr_str = v_server.value + ":" + + var ssr_str = _address + ":" + v_port.value + ":" + v_protocol.value + ":" + v_method.value + ":" + @@ -184,6 +185,7 @@ local has_xray = api.is_finded("xray") info.v = "2"; info.ps = v_alias.value; info.add = opt.get("address").value; + //info.add = _address; info.port = opt.get("port").value; info.id = opt.get("uuid").value; @@ -196,8 +198,10 @@ local has_xray = api.is_finded("xray") info.path = opt.get("h2_path").value; } else if (v_transport === "tcp") { info.type = opt.get("tcp_guise").value; - info.host = opt.get("tcp_guise_http_host").value; - info.path = opt.get("tcp_guise_http_path").value; + if (info.type === "http") { + info.host = opt.get("tcp_guise_http_host").value; + info.path = opt.get("tcp_guise_http_path").value; + } } else if (v_transport === "mkcp") { v_transport = "kcp"; info.type = opt.get("mkcp_guise").value; @@ -223,10 +227,9 @@ local has_xray = api.is_finded("xray") } else if ((v_type === "V2ray" || v_type === "Xray") && opt.get("protocol").value === "vless") { v_type = "vless"; var v_password = opt.get("uuid"); - var v_server = opt.get("address"); var v_port = opt.get("port"); url = encodeURIComponent(v_password.value) + - "@" + v_server.value + + "@" + _address + ":" + v_port.value + "?"; var params = ""; @@ -235,6 +238,7 @@ local has_xray = api.is_finded("xray") params += opt.query("host", "ws_host"); params += opt.query("path", "ws_path"); } else if (v_transport === "h2") { + v_transport = "http"; params += opt.query("host", "h2_host"); params += opt.query("path", "h2_path"); } else if (v_transport === "tcp") { @@ -252,21 +256,32 @@ local has_xray = api.is_finded("xray") //不知道是用path还是serviceName,这里先这样吧 params += opt.query("path", "grpc_serviceName"); params += opt.query("serviceName", "grpc_serviceName"); + params += opt.query("mode", "grpc_mode"); } params += "&type=" + v_transport; params += opt.query("encryption", "encryption"); if (opt.get("tls").checked) { var v_security = "tls"; - params += "&security=" + v_security; - if (opt.get("tlsflow").value) { + if (opt.get("xray_fingerprint") && opt.get("xray_fingerprint").value != "") { + let v_fp = opt.get("xray_fingerprint").value; + params += "&fp=" + v_fp; + } + if(opt.get("reality") && opt.get("reality").checked) { + v_security = "reality"; + if (opt.get("reality_fingerprint") && opt.get("reality_fingerprint").value != "") { + let v_fp = opt.get("reality_fingerprint").value; + params += "&fp=" + v_fp; + } + params += opt.query("pbk", "reality_publicKey"); + params += opt.query("sid", "reality_shortId"); + params += opt.query("spx", "reality_spiderX"); + } + if (opt.get("tlsflow") && opt.get("tlsflow").value) { let v_flow = opt.get("tlsflow").value; params += "&flow=" + v_flow; } - if (opt.get("xray_fingerprint").value && opt.get("xray_fingerprint").value != "") { - let v_fp = opt.get("xray_fingerprint").value - params += "&fp=" + v_fp; - } + params += "&security=" + v_security; params += opt.query("sni", "tls_serverName"); } @@ -280,10 +295,9 @@ local has_xray = api.is_finded("xray") v_type = "trojan"; } var v_password = opt.get("password"); - var v_server = opt.get("address"); var v_port = opt.get("port"); url = encodeURIComponent(v_password.value) + - "@" + v_server.value + + "@" + _address + ":" + v_port.value + "/?"; var params = ""; if (opt.get("tls").checked) { @@ -300,7 +314,6 @@ local has_xray = api.is_finded("xray") var url = ""; var params = "?"; var v_protocol = opt.get("brook_protocol"); - var v_server = opt.get("address"); var v_port = opt.get("port"); var v_password = opt.get("password"); var b_protocol_value = v_protocol.value.split('client').join('server'); @@ -319,14 +332,13 @@ local has_xray = api.is_finded("xray") if (v_path_value.length > 1 && v_path_value.indexOf('/') < 0) { v_path_value = '/' + v_path_value; } - params += "&" + url_protocol + "=" + encodeURIComponent(prefix + v_server.value + ":" + v_port.value + v_path_value); + params += "&" + url_protocol + "=" + encodeURIComponent(prefix + _address + ":" + v_port.value + v_path_value); } else { - params += "&" + url_protocol + "=" + encodeURIComponent(v_server.value + ":" + v_port.value); + params += "&" + url_protocol + "=" + encodeURIComponent(_address + ":" + v_port.value); } url += url_protocol; url += params; } else if (v_type === "Hysteria") { - var v_server = opt.get("address"); var v_port = opt.get("port"); var params = ""; params += opt.query("protocol", "hysteria_protocol"); @@ -338,7 +350,7 @@ local has_xray = api.is_finded("xray") params += opt.query("alpn", "hysteria_alpn"); params += opt.query("obfsParam", "hysteria_obfs"); var url = - v_server.value + ":" + + _address + ":" + v_port.value + "?" + params + "#" + encodeURI(v_alias.value); @@ -677,6 +689,7 @@ local has_xray = api.is_finded("xray") if (queryParam.security) { if (queryParam.security == "tls") { opt.set('tls', true); + opt.set('reality', false) opt.set('tlsflow', queryParam.flow || ''); opt.set('tls_serverName', queryParam.sni || ''); opt.set('tls_allowInsecure', true); @@ -688,10 +701,25 @@ local has_xray = api.is_finded("xray") } } } - + + if (queryParam.security == "reality") { + opt.set('tls', true); + opt.set('reality', true) + opt.set('tlsflow', queryParam.flow || ''); + opt.set('tls_serverName', queryParam.sni || ''); + if (queryParam.fp && queryParam.fp.trim() != "") { + opt.set('reality_fingerprint', queryParam.fp); + } + opt.set('reality_publicKey', queryParam.pbk || ''); + opt.set('reality_shortId', queryParam.sid || ''); + opt.set('reality_spiderX', queryParam.spx || ''); + } + queryParam.type = queryParam.type.toLowerCase(); if (queryParam.type === "kcp" || queryParam.type === "mkcp") queryParam.type = "mkcp" + if (queryParam.type === "h2" || queryParam.type === "http") + queryParam.type = "h2" opt.set('transport', queryParam.type); if (queryParam.type === "tcp") { opt.set('tcp_guise', queryParam.headerType || "none"); @@ -702,7 +730,7 @@ local has_xray = api.is_finded("xray") } else if (queryParam.type === "ws") { opt.set('ws_host', queryParam.host || ""); opt.set('ws_path', queryParam.path || ""); - } else if (queryParam.type === "h2") { + } else if (queryParam.type === "h2" || queryParam.type === "http") { opt.set('h2_host', queryParam.host || ""); opt.set('h2_path', queryParam.path || ""); } else if (queryParam.type === "quic") { @@ -713,6 +741,7 @@ local has_xray = api.is_finded("xray") opt.set('mkcp_guise', queryParam.headerType || "none"); } else if (queryParam.type === "grpc") { opt.set('grpc_serviceName', (queryParam.serviceName || queryParam.path) || ""); + opt.set('grpc_mode', queryParam.mode); } if (m.hash) { diff --git a/luci-app-passwall2/po/zh-cn/passwall2.po b/luci-app-passwall2/po/zh-cn/passwall2.po index 450c0595..3240692e 100644 --- a/luci-app-passwall2/po/zh-cn/passwall2.po +++ b/luci-app-passwall2/po/zh-cn/passwall2.po @@ -274,6 +274,15 @@ msgstr "Xray 负载均衡" msgid "V2ray_balancing" msgstr "V2ray 负载均衡" +msgid "Balancing Strategy" +msgstr "负载均衡策略" + +msgid "Probe Interval" +msgstr "探测间隔" + +msgid "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." +msgstr "发起探测的间隔。每经过这个时间,就会对一个服务器进行服务器状态检测。时间格式为数字+单位,比如"10s", "2h45m",支持的时间单位有 nsusmssmh,分别对应纳秒、微秒、毫秒、秒、分、时。" + msgid "Shunt" msgstr "分流" @@ -406,6 +415,9 @@ msgstr "QUIC 连接接收窗口" msgid "Disable MTU detection" msgstr "禁用 MTU 检测" +msgid "Lazy Start" +msgstr "延迟启动" + msgid "Encrypt Method" msgstr "加密" diff --git a/luci-app-passwall2/root/usr/share/passwall2/0_default_config b/luci-app-passwall2/root/usr/share/passwall2/0_default_config index 976f7c0a..b4424cd1 100644 --- a/luci-app-passwall2/root/usr/share/passwall2/0_default_config +++ b/luci-app-passwall2/root/usr/share/passwall2/0_default_config @@ -70,6 +70,7 @@ config nodes 'myshunt' option AD 'nil' option BT '_direct' option Netflix 'nil' + option OpenAI 'nil' option TVB 'nil' option Proxy '_default' option China '_direct' @@ -173,6 +174,10 @@ config shunt_rules 'Netflix' option remarks 'Netflix' option network 'tcp,udp' option domain_list 'geosite:netflix' + +config shunt_rules 'OpenAI' + option remarks 'OpenAI' + option domain_list 'geosite:openai' config shunt_rules 'TVB' option remarks 'TVB' diff --git a/luci-app-passwall2/root/usr/share/passwall2/app.sh b/luci-app-passwall2/root/usr/share/passwall2/app.sh index 3fbdb437..ad00b570 100755 --- a/luci-app-passwall2/root/usr/share/passwall2/app.sh +++ b/luci-app-passwall2/root/usr/share/passwall2/app.sh @@ -299,12 +299,12 @@ run_v2ray() { } local buffer_size=$(config_t_get global_forwarding buffer_size) [ -n "${buffer_size}" ] && _extra_param="${_extra_param} -buffer_size ${buffer_size}" - + local protocol=$(config_n_get $node protocol) [ "$protocol" == "_iface" ] && { IFACES="$IFACES $(config_n_get $node iface)" } - + [ -n "$dns_listen_port" ] && { V2RAY_DNS_DIRECT_CONFIG="${TMP_PATH}/${flag}_dns_direct.json" V2RAY_DNS_DIRECT_LOG="${TMP_PATH}/${flag}_dns_direct.log" @@ -345,10 +345,10 @@ run_v2ray() { esac [ -n "$direct_dns_query_strategy" ] && V2RAY_DNS_DIRECT_ARGS="${V2RAY_DNS_DIRECT_ARGS} -dns_query_strategy ${direct_dns_query_strategy}" [ -n "$direct_dns_client_ip" ] && V2RAY_DNS_DIRECT_ARGS="${V2RAY_DNS_DIRECT_ARGS} -dns_client_ip ${direct_dns_client_ip}" - + lua $UTIL_XRAY gen_dns_config ${V2RAY_DNS_DIRECT_ARGS} > $V2RAY_DNS_DIRECT_CONFIG ln_run "$(first_type $(config_t_get global_app ${type}_file) ${type})" ${type} $V2RAY_DNS_DIRECT_LOG run -c "$V2RAY_DNS_DIRECT_CONFIG" - + [ "$remote_dns_protocol" != "fakedns" ] && { V2RAY_DNS_REMOTE_CONFIG="${TMP_PATH}/${flag}_dns_remote.json" V2RAY_DNS_REMOTE_LOG="${TMP_PATH}/${flag}_dns_remote.log" @@ -386,15 +386,15 @@ run_v2ray() { V2RAY_DNS_REMOTE_ARGS="${V2RAY_DNS_REMOTE_ARGS} -remote_dns_fake 1" ;; esac - + [ -n "$remote_dns_query_strategy" ] && V2RAY_DNS_REMOTE_ARGS="${V2RAY_DNS_REMOTE_ARGS} -dns_query_strategy ${remote_dns_query_strategy}" [ -n "$remote_dns_client_ip" ] && V2RAY_DNS_REMOTE_ARGS="${V2RAY_DNS_REMOTE_ARGS} -dns_client_ip ${remote_dns_client_ip}" - + V2RAY_DNS_REMOTE_ARGS="${V2RAY_DNS_REMOTE_ARGS} -remote_dns_outbound_socks_address 127.0.0.1 -remote_dns_outbound_socks_port ${socks_port}" lua $UTIL_XRAY gen_dns_config ${V2RAY_DNS_REMOTE_ARGS} > $V2RAY_DNS_REMOTE_CONFIG ln_run "$(first_type $(config_t_get global_app ${type}_file) ${type})" ${type} $V2RAY_DNS_REMOTE_LOG run -c "$V2RAY_DNS_REMOTE_CONFIG" } - + [ -n "$dns_listen_port" ] && _extra_param="${_extra_param} -dns_listen_port ${dns_listen_port}" [ -n "$dns_cache" ] && _extra_param="${_extra_param} -dns_cache ${dns_cache}" _extra_param="${_extra_param} -dns_query_strategy UseIP" @@ -405,7 +405,7 @@ run_v2ray() { _extra_param="${_extra_param} -remote_dns_port ${dns_remote_listen_port} -remote_dns_udp_server 127.0.0.1" fi } - + lua $UTIL_XRAY gen_config -node $node -redir_port $redir_port -tcp_proxy_way $tcp_proxy_way -loglevel $loglevel ${_extra_param} > $config_file ln_run "$(first_type $(config_t_get global_app ${type}_file) ${type})" ${type} $log_file run -c "$config_file" } @@ -441,7 +441,7 @@ run_socks() { else error_msg="某种原因,此 Socks 服务的相关配置已失联,启动中止!" fi - + if [ "$type" == "v2ray" ] || [ "$type" == "xray" ]; then local protocol=$(config_n_get $node protocol) if [ "$protocol" == "_balancing" ] || [ "$protocol" == "_shunt" ] || [ "$protocol" == "_iface" ]; then @@ -558,7 +558,7 @@ node_switch() { new_script_func=$(echo $script_func | sed "s#${now_node_arg}#node=${new_node}#g") ${new_script_func} echo $new_node > $TMP_ID_PATH/${flag} - + [ "$shunt_logic" != "0" ] && [ "$(config_n_get $new_node protocol nil)" = "_shunt" ] && { echo $(config_n_get $new_node default_node nil) > $TMP_ID_PATH/${flag}_default echo $(config_n_get $new_node main_node nil) > $TMP_ID_PATH/${flag}_main @@ -642,7 +642,7 @@ run_global() { } msg="${msg})" echolog ${msg} - + source $APP_PATH/helper_dnsmasq.sh stretch source $APP_PATH/helper_dnsmasq.sh add TMP_DNSMASQ_PATH=$TMP_DNSMASQ_PATH DNSMASQ_CONF_FILE=/tmp/dnsmasq.d/dnsmasq-passwall2.conf DEFAULT_DNS=$AUTO_DNS LOCAL_DNS=$LOCAL_DNS TUN_DNS=$TUN_DNS @@ -801,7 +801,7 @@ acl_app() { sid=$(uci -q show "${CONFIG}.${item}" | grep "=acl_rule" | awk -F '=' '{print $1}' | awk -F '.' '{print $2}') eval $(uci -q show "${CONFIG}.${item}" | cut -d'.' -sf 3-) [ "$enabled" = "1" ] || continue - + [ -z "${sources}" ] && continue for s in $sources; do is_iprange=$(lua_api "iprange(\"${s}\")") @@ -821,7 +821,7 @@ acl_app() { [ -z "${rule_list}" ] && continue mkdir -p $TMP_ACL_PATH/$sid echo -e "${rule_list}" | sed '/^$/d' > $TMP_ACL_PATH/$sid/rule_list - + tcp_proxy_mode="global" udp_proxy_mode="global" node=${node:-default} @@ -833,7 +833,7 @@ acl_app() { remote_dns=${remote_dns:-1.1.1.1} [ "$remote_dns_protocol" = "doh" ] && remote_dns=${remote_dns_doh:-https://1.1.1.1/dns-query} remote_dns_query_strategy=${remote_dns_query_strategy:-UseIPv4} - + [ "$node" != "nil" ] && { if [ "$node" = "default" ]; then node=$NODE @@ -893,18 +893,18 @@ acl_app() { start() { pgrep -f /tmp/etc/passwall2/bin > /dev/null 2>&1 && { - echolog "程序已启动,无需重复启动!" - return 0 + echolog "程序已启动,先停止再重新启动!" + stop } ulimit -n 65535 start_socks - + local USE_TABLES="iptables" if [ -z "$(command -v iptables-legacy || command -v iptables)" ] || [ -z "$(command -v ipset)" ]; then echolog "系统未安装iptables或ipset,无法透明代理!" fi - + [ "$ENABLED_DEFAULT_ACL" == 1 ] && run_global source $APP_PATH/${USE_TABLES}.sh start [ "$ENABLED_DEFAULT_ACL" == 1 ] && source $APP_PATH/helper_dnsmasq.sh logic_restart diff --git a/luci-app-passwall2/root/usr/share/passwall2/rule_update.lua b/luci-app-passwall2/root/usr/share/passwall2/rule_update.lua index 6bf1b55d..c3aa1fd0 100755 --- a/luci-app-passwall2/root/usr/share/passwall2/rule_update.lua +++ b/luci-app-passwall2/root/usr/share/passwall2/rule_update.lua @@ -21,14 +21,10 @@ local geosite_api = ucic:get_first(name, 'global_rules', "geosite_url", "https:/ local log = function(...) if arg1 then - local result = os.date("%Y-%m-%d %H:%M:%S: ") .. table.concat({...}, " ") if arg1 == "log" then - local f, err = io.open("/tmp/log/passwall2.log", "a") - if f and err == nil then - f:write(result .. "\n") - f:close() - end + api.log(...) elseif arg1 == "print" then + local result = os.date("%Y-%m-%d %H:%M:%S: ") .. table.concat({...}, " ") print(result) end end diff --git a/luci-app-passwall2/root/usr/share/passwall2/subscribe.lua b/luci-app-passwall2/root/usr/share/passwall2/subscribe.lua index 77036d73..13e103b4 100755 --- a/luci-app-passwall2/root/usr/share/passwall2/subscribe.lua +++ b/luci-app-passwall2/root/usr/share/passwall2/subscribe.lua @@ -80,15 +80,11 @@ local nodeResult = {} -- update result local debug = false local log = function(...) - local result = os.date("%Y-%m-%d %H:%M:%S: ") .. table.concat({...}, " ") if debug == true then + local result = os.date("%Y-%m-%d %H:%M:%S: ") .. table.concat({...}, " ") print(result) else - local f, err = io.open("/tmp/log/" .. appname .. ".log", "a") - if f and err == nil then - f:write(result .. "\n") - f:close() - end + api.log(...) end end @@ -328,15 +324,17 @@ local function processData(szType, content, add_mode, add_from) add_mode = add_mode, --0为手动配置,1为导入,2为订阅 add_from = add_from } + --ssr://base64(host:port:protocol:method:obfs:base64pass/?obfsparam=base64param&protoparam=base64param&remarks=base64remarks&group=base64group&udpport=0&uot=0) if szType == 'ssr' then + result.type = "SSR" + local dat = split(content, "/%?") local hostInfo = split(dat[1], ':') - result.type = "SSR" - result.address = "" - for i=1,#hostInfo-5,1 do - result.address = result.address .. hostInfo[i] .. ":" + if dat[1]:match('%[(.*)%]') then + result.address = dat[1]:match('%[(.*)%]') + else + result.address = hostInfo[#hostInfo-5] end - result.address = string.sub(result.address, 0, #result.address-1) result.port = hostInfo[#hostInfo-4] result.protocol = hostInfo[#hostInfo-3] result.method = hostInfo[#hostInfo-2] @@ -410,6 +408,15 @@ local function processData(szType, content, add_mode, add_from) result.tls = "0" end elseif szType == "ss" then + result.type = "SS" + + --SS-URI = "ss://" userinfo "@" hostname ":" port [ "/" ] [ "?" plugin ] [ "#" tag ] + --userinfo = websafe-base64-encode-utf8(method ":" password) + --ss://YWVzLTEyOC1nY206dGVzdA@192.168.100.1:8888#Example1 + --ss://cmM0LW1kNTpwYXNzd2Q@192.168.100.1:8888/?plugin=obfs-local%3Bobfs%3Dhttp#Example2 + --ss://2022-blake3-aes-256-gcm:YctPZ6U7xPPcU%2Bgp3u%2B0tx%2FtRizJN9K8y%2BuKlW2qjlI%3D@192.168.100.1:8888#Example3 + --ss://2022-blake3-aes-256-gcm:YctPZ6U7xPPcU%2Bgp3u%2B0tx%2FtRizJN9K8y%2BuKlW2qjlI%3D@192.168.100.1:8888/?plugin=v2ray-plugin%3Bserver#Example3 + local idx_sp = 0 local alias = "" if content:find("#") then @@ -418,28 +425,9 @@ local function processData(szType, content, add_mode, add_from) end result.remarks = UrlDecode(alias) local info = content:sub(1, idx_sp - 1) - local hostInfo = split(base64Decode(info), "@") - local hostInfoLen = #hostInfo - local host = nil - local userinfo = nil - if hostInfoLen > 2 then - host = split(hostInfo[hostInfoLen], ":") - userinfo = {} - for i = 1, hostInfoLen - 1 do - tinsert(userinfo, hostInfo[i]) - end - userinfo = table.concat(userinfo, '@') - else - host = split(hostInfo[2], ":") - userinfo = base64Decode(hostInfo[1]) - end - local method = userinfo:sub(1, userinfo:find(":") - 1) - local password = userinfo:sub(userinfo:find(":") + 1, #userinfo) - result.type = "SS" - result.address = host[1] - if host[2] and host[2]:find("/%?") then - local query = split(host[2], "/%?") - result.port = query[1] + if info:find("/%?") then + local find_index = info:find("/%?") + local query = split(info, "/%?") local params = {} for _, v in pairs(split(query[2], '&')) do local t = split(v, '=') @@ -459,39 +447,70 @@ local function processData(szType, content, add_mode, add_from) if result.plugin and result.plugin == "simple-obfs" then result.plugin = "obfs-local" end - else - result.port = host[2] + info = info:sub(1, find_index - 1) end - result.method = method - result.password = password - local aead = false - for k, v in ipairs({"aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "chacha20-ietf-poly1305"}) do - if method:lower() == v:lower() then - aead = true + local hostInfo = split(base64Decode(info), "@") + if hostInfo and #hostInfo > 0 then + local host_port = hostInfo[#hostInfo] + -- [2001:4860:4860::8888]:443 + -- 8.8.8.8:443 + if host_port:find(":") then + local sp = split(host_port, ":") + result.port = sp[#sp] + if api.is_ipv6addrport(host_port) then + result.address = api.get_ipv6_only(host_port) + else + result.address = sp[1] + end + else + result.address = host_port end - end - if aead then - if ss_aead_type_default == "shadowsocks-libev" and has_ss then - result.type = "SS" - elseif ss_aead_type_default == "shadowsocks-rust" and has_ss_rust then - result.type = 'SS-Rust' - if method:lower() == "chacha20-poly1305" then - result.method = "chacha20-ietf-poly1305" + + local userinfo = nil + if #hostInfo > 2 then + userinfo = {} + for i = 1, #hostInfo - 1 do + tinsert(userinfo, hostInfo[i]) end - elseif ss_aead_type_default == "v2ray" and has_v2ray and not result.plugin then - result.type = 'V2ray' - result.protocol = 'shadowsocks' - result.transport = 'tcp' - if method:lower() == "chacha20-ietf-poly1305" then - result.method = "chacha20-poly1305" + userinfo = table.concat(userinfo, '@') + else + userinfo = base64Decode(hostInfo[1]) + end + + local method = userinfo:sub(1, userinfo:find(":") - 1) + local password = userinfo:sub(userinfo:find(":") + 1, #userinfo) + result.method = method + result.password = password + + local aead = false + for k, v in ipairs({"aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "chacha20-ietf-poly1305"}) do + if method:lower() == v:lower() then + aead = true end - elseif ss_aead_type_default == "xray" and has_xray and not result.plugin then - result.type = 'Xray' - result.protocol = 'shadowsocks' - result.transport = 'tcp' - if method:lower() == "chacha20-ietf-poly1305" then - result.method = "chacha20-poly1305" + end + if aead then + if ss_aead_type_default == "shadowsocks-libev" and has_ss then + result.type = "SS" + elseif ss_aead_type_default == "shadowsocks-rust" and has_ss_rust then + result.type = 'SS-Rust' + if method:lower() == "chacha20-poly1305" then + result.method = "chacha20-ietf-poly1305" + end + elseif ss_aead_type_default == "v2ray" and has_v2ray and not result.plugin then + result.type = 'V2ray' + result.protocol = 'shadowsocks' + result.transport = 'tcp' + if method:lower() == "chacha20-ietf-poly1305" then + result.method = "chacha20-poly1305" + end + elseif ss_aead_type_default == "xray" and has_xray and not result.plugin then + result.type = 'Xray' + result.protocol = 'shadowsocks' + result.transport = 'tcp' + if method:lower() == "chacha20-ietf-poly1305" then + result.method = "chacha20-poly1305" + end end end end @@ -513,33 +532,28 @@ local function processData(szType, content, add_mode, add_from) result.password = UrlDecode(Info[1]) local port = "443" Info[2] = (Info[2] or ""):gsub("/%?", "?") - local hostInfo = nil - if Info[2]:find(":") then - hostInfo = split(Info[2], ":") - result.address = hostInfo[1] - local idx_port = 2 - if hostInfo[2]:find("?") then - hostInfo = split(hostInfo[2], "?") - idx_port = 1 - end - if hostInfo[idx_port] ~= "" then port = hostInfo[idx_port] end - else - if Info[2]:find("?") then - hostInfo = split(Info[2], "?") - end - result.address = hostInfo and hostInfo[1] or Info[2] - end - local peer, sni = nil, "" - local allowInsecure = allowInsecure_default local query = split(Info[2], "?") + local host_port = query[1] local params = {} for _, v in pairs(split(query[2], '&')) do local t = split(v, '=') params[string.lower(t[1])] = UrlDecode(t[2]) end - if params.allowinsecure then - allowInsecure = params.allowinsecure + -- [2001:4860:4860::8888]:443 + -- 8.8.8.8:443 + if host_port:find(":") then + local sp = split(host_port, ":") + port = sp[#sp] + if api.is_ipv6addrport(host_port) then + result.address = api.get_ipv6_only(host_port) + else + result.address = sp[1] + end + else + result.address = host_port end + + local peer, sni = nil, "" if params.peer then peer = params.peer end sni = params.sni and params.sni or "" if params.ws and params.ws == "1" then @@ -551,7 +565,16 @@ local function processData(szType, content, add_mode, add_from) result.port = port result.tls = '1' result.tls_serverName = peer and peer or sni - result.tls_allowInsecure = allowInsecure and "1" or "0" + if params.allowinsecure then + if params.allowinsecure == "1" or params.allowinsecure == "0" then + result.tls_allowInsecure = params.allowinsecure + else + result.tls_allowInsecure = string.lower(params.allowinsecure) == "true" and "1" or "0" + end + --log(result.remarks .. ' 使用节点AllowInsecure设定: '.. result.tls_allowInsecure) + else + result.tls_allowInsecure = allowInsecure_default and "1" or "0" + end end elseif szType == "ssd" then result.type = "SS" @@ -581,36 +604,34 @@ local function processData(szType, content, add_mode, add_from) result.uuid = UrlDecode(Info[1]) local port = "443" Info[2] = (Info[2] or ""):gsub("/%?", "?") - local hostInfo = nil - if Info[2]:find(":") then - hostInfo = split(Info[2], ":") - result.address = hostInfo[1] - local idx_port = 2 - if hostInfo[2]:find("?") then - hostInfo = split(hostInfo[2], "?") - idx_port = 1 - end - if hostInfo[idx_port] ~= "" then port = hostInfo[idx_port] end - else - if Info[2]:find("?") then - hostInfo = split(Info[2], "?") - end - result.address = hostInfo and hostInfo[1] or Info[2] - end - local query = split(Info[2], "?") + local host_port = query[1] local params = {} for _, v in pairs(split(query[2], '&')) do local t = split(v, '=') params[t[1]] = UrlDecode(t[2]) end + -- [2001:4860:4860::8888]:443 + -- 8.8.8.8:443 + if host_port:find(":") then + local sp = split(host_port, ":") + port = sp[#sp] + if api.is_ipv6addrport(host_port) then + result.address = api.get_ipv6_only(host_port) + else + result.address = sp[1] + end + else + result.address = host_port + end params.type = string.lower(params.type) if params.type == 'ws' then result.ws_host = params.host result.ws_path = params.path end - if params.type == 'h2' then + if params.type == 'h2' or params.type == 'http' then + params.type = "h2" result.h2_host = params.host result.h2_path = params.path end @@ -637,17 +658,24 @@ local function processData(szType, content, add_mode, add_from) if params.type == 'grpc' then if params.path then result.grpc_serviceName = params.path end if params.serviceName then result.grpc_serviceName = params.serviceName end + result.grpc_mode = params.mode end result.transport = params.type result.encryption = params.encryption or "none" result.tls = "0" - if params.security == "tls" then + if params.security == "tls" or params.security == "reality" then result.tls = "1" result.tlsflow = params.flow or nil result.tls_serverName = (params.sni and params.sni ~= "") and params.sni or params.host result.fingerprint = (params.fp and params.fp ~= "") and params.fp or "chrome" + if params.security == "reality" then + result.reality = "1" + result.reality_publicKey = params.pbk or nil + result.reality_shortId = params.sid or nil + result.reality_spiderX = params.spx or nil + end end result.port = port @@ -664,9 +692,7 @@ local function processData(szType, content, add_mode, add_from) result.type = "Hysteria" local dat = split(content, '%?') - local hostInfo = split(dat[1], ':') - result.address = hostInfo[1] - result.port = hostInfo[2] + local host_port = dat[1] local params = {} for _, v in pairs(split(dat[2], '&')) do local t = split(v, '=') @@ -674,6 +700,19 @@ local function processData(szType, content, add_mode, add_from) params[t[1]] = t[2] end end + -- [2001:4860:4860::8888]:443 + -- 8.8.8.8:443 + if host_port:find(":") then + local sp = split(host_port, ":") + result.port = sp[#sp] + if api.is_ipv6addrport(host_port) then + result.address = api.get_ipv6_only(host_port) + else + result.address = sp[1] + end + else + result.address = host_port + end result.protocol = params.protocol result.hysteria_obfs = params.obfsParam result.hysteria_auth_type = "string" @@ -990,8 +1029,8 @@ local function parse_link(raw, add_mode, add_from) if result then if not result.type then log('丢弃节点:' .. result.remarks .. ",找不到可使用二进制.") - elseif (add_mode == "2" and is_filter_keyword(result.remarks)) or not result.address or result.remarks == "NULL" or result.address=="127.0.0.1" or - (not datatypes.hostname(result.address) and not (datatypes.ipmask4(result.address) or datatypes.ipmask6(result.address))) then + elseif (add_mode == "2" and is_filter_keyword(result.remarks)) or not result.address or result.remarks == "NULL" or result.address == "127.0.0.1" or + (not datatypes.hostname(result.address) and not (api.is_ip(result.address))) then log('丢弃过滤节点: ' .. result.type .. ' 节点, ' .. result.remarks) else tinsert(node_list, result)