From 6a9b13947b5d93037e20853cd03d89361c6a8a87 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 6 Oct 2026 20:57:57 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=8D=89=20Sync=202026-10-06=2020:57:57?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- luci-app-flowproxy/Makefile | 2 +- .../resources/view/flowproxy/flowproxy.js | 103 ++--- luci-app-ipsec-server/Makefile | 12 +- luci-app-ipsec-server/example.config | 26 -- .../luasrc/controller/ipsec-server.lua | 4 +- .../model/cbi/ipsec-server/l2tp_user.lua | 5 +- .../luasrc/model/cbi/ipsec-server/online.lua | 148 +++++++ .../model/cbi/ipsec-server/settings.lua | 97 ++-- .../luasrc/model/cbi/ipsec-server/users.lua | 56 ++- .../view/ipsec-server/ipsec-server_status.htm | 72 +-- .../po/zh-cn/ipsec-server.po | 91 ++-- .../root/etc/config/luci-app-ipsec-server | 8 +- .../root/etc/init.d/luci-app-ipsec-server | 417 +++++++++--------- .../etc/uci-defaults/luci-app-ipsec-server | 24 +- .../root/usr/share/xl2tpd/ip-down | 27 ++ .../root/usr/share/xl2tpd/ip-up | 58 +++ luci-app-substore/CHANGELOG.md | 70 +++ luci-app-substore/Makefile | 2 +- luci-app-substore/docs/LEGACY_ISSUES.md | 179 +++++++- .../usr/share/substore/output_formats.lua | 46 +- .../tests/output_formats_test.lua | 39 ++ .../tests/protocol_registry_test.lua | 10 +- naiveproxy/Makefile | 2 +- 23 files changed, 991 insertions(+), 507 deletions(-) delete mode 100644 luci-app-ipsec-server/example.config create mode 100644 luci-app-ipsec-server/luasrc/model/cbi/ipsec-server/online.lua mode change 100644 => 100755 luci-app-ipsec-server/root/etc/uci-defaults/luci-app-ipsec-server create mode 100755 luci-app-ipsec-server/root/usr/share/xl2tpd/ip-down create mode 100755 luci-app-ipsec-server/root/usr/share/xl2tpd/ip-up diff --git a/luci-app-flowproxy/Makefile b/luci-app-flowproxy/Makefile index 34d46ca70..424633c55 100644 --- a/luci-app-flowproxy/Makefile +++ b/luci-app-flowproxy/Makefile @@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-flowproxy PKG_VERSION:=1.0.3 -PKG_RELEASE:=5 +PKG_RELEASE:=6 LUCI_TITLE:=LuCI Support for FlowProxy Traffic Diversion LUCI_DEPENDS:=+luci-base +nftables +ip-full diff --git a/luci-app-flowproxy/htdocs/luci-static/resources/view/flowproxy/flowproxy.js b/luci-app-flowproxy/htdocs/luci-static/resources/view/flowproxy/flowproxy.js index eb1449868..72dfb5687 100644 --- a/luci-app-flowproxy/htdocs/luci-static/resources/view/flowproxy/flowproxy.js +++ b/luci-app-flowproxy/htdocs/luci-static/resources/view/flowproxy/flowproxy.js @@ -189,63 +189,64 @@ return L.view.extend({ s.taboption('settings', form.Flag, 'dns_proxy_enabled', _('Force upstream DNS to proxy server')).rmempty = false; o = s.taboption('settings', form.Value, 'proxy_server_ip_addr', _('Proxy server IP address')); - o.datatype = 'ip4addr'; o.rmempty = false; + o.datatype = 'ip4addr'; o.rmempty = true; o.placeholder = '192.168.1.254'; - o.validate = function(section_id, value) { - var ipInCidr = function(ipStr, cidrStr) { - if (!cidrStr || cidrStr.indexOf(':') !== -1 || !ipStr || ipStr.indexOf(':') !== -1) return false; - var parts = cidrStr.split('/'); - var cidrIp = parts[0]; - var maskBits = parseInt(parts[1] || '32', 10); - - var ipToNum = function(s) { - var p = s.split('.'); - if (p.length !== 4) return 0; - return (((parseInt(p[0], 10) << 24) | - (parseInt(p[1], 10) << 16) | - (parseInt(p[2], 10) << 8) | - parseInt(p[3], 10)) >>> 0); - }; - var ipNum = ipToNum(ipStr); - var cidrNum = ipToNum(cidrIp); - - if (maskBits === 0) return true; - - var mask = (0xffffffff << (32 - maskBits)) >>> 0; - return (ipNum & mask) === (cidrNum & mask); - }; + m.save = function() { + var enabled = s.formvalue('global', 'enabled') || uci.get('flowproxy', 'global', 'enabled'); + if (enabled === '1') { + var ipVal = s.formvalue('global', 'proxy_server_ip_addr') || uci.get('flowproxy', 'global', 'proxy_server_ip_addr'); + var ifaceVal = s.formvalue('global', 'interface') || uci.get('flowproxy', 'global', 'interface'); - var selected_interface = s.formvalue(section_id, 'interface'); - if (!value || !selected_interface) return true; + if (ipVal && ifaceVal) { + var ipInCidr = function(ipStr, cidrStr) { + if (!cidrStr || cidrStr.indexOf(':') !== -1 || !ipStr || ipStr.indexOf(':') !== -1) return false; + var parts = cidrStr.split('/'); + var cidrIp = parts[0]; + var maskBits = parseInt(parts[1] || '32', 10); + var ipToNum = function(s) { + var p = s.split('.'); + if (p.length !== 4) return 0; + return (((parseInt(p[0], 10) << 24) | + (parseInt(p[1], 10) << 16) | + (parseInt(p[2], 10) << 8) | + parseInt(p[3], 10)) >>> 0); + }; + var ipNum = ipToNum(ipStr); + var cidrNum = ipToNum(cidrIp); + if (maskBits === 0) return true; + var mask = (0xffffffff << (32 - maskBits)) >>> 0; + return (ipNum & mask) === (cidrNum & mask); + }; - var target_iface = null; - for (var i = 0; i < ifaces.length; i++) { - var dev = ifaces[i].getL3Device() || ifaces[i].getDevice(); - if ((dev && dev.getName() === selected_interface) || ifaces[i].getName() === selected_interface) { - target_iface = ifaces[i]; - break; + var target_iface = null; + for (var i = 0; i < ifaces.length; i++) { + var dev = ifaces[i].getL3Device() || ifaces[i].getDevice(); + if ((dev && dev.getName() === ifaceVal) || ifaces[i].getName() === ifaceVal) { + target_iface = ifaces[i]; + break; + } + } + + if (target_iface) { + var ips = target_iface.getIPAddrs(); + if (ips && ips.length > 0) { + var inSubnet = false; + for (var j = 0; j < ips.length; j++) { + if (ipInCidr(ipVal, ips[j])) { + inSubnet = true; + break; + } + } + if (!inSubnet) { + ui.addNotification(null, E('p', _('The proxy server IP address must be within the subnet of the selected interface (%s).').format(ips.join(', '))), 'danger'); + return Promise.reject(new Error('Proxy IP subnet mismatch')); + } + } + } } } - - if (!target_iface) return true; - - var ips = target_iface.getIPAddrs(); - if (!ips || ips.length === 0) return true; - - var inSubnet = false; - for (var j = 0; j < ips.length; j++) { - if (ipInCidr(value, ips[j])) { - inSubnet = true; - break; - } - } - - if (!inSubnet) { - return _('The proxy server IP address must be within the subnet of the selected interface (%s).').format(ips.join(', ')); - } - - return true; + return form.Map.prototype.save.apply(m, arguments); }; o = s.taboption('settings', form.Value, 'proxy_server_dns_port', _('Proxy server DNS port')); diff --git a/luci-app-ipsec-server/Makefile b/luci-app-ipsec-server/Makefile index beacbf141..e7c367c87 100644 --- a/luci-app-ipsec-server/Makefile +++ b/luci-app-ipsec-server/Makefile @@ -1,4 +1,4 @@ -# Copyright (C) 2018-2021 Lienol +# Copyright (C) 2018-2026 Lienol # # This is free software, licensed under the Apache License, Version 2.0 . # @@ -6,13 +6,17 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-ipsec-server -PKG_VERSION:=20260815 -PKG_RELEASE:=2 +PKG_VERSION:=20260807 +PKG_RELEASE:=3 PKG_MAINTAINER:=Lienol LUCI_TITLE:=LuCI support for IPSec VPN Server -LUCI_DEPENDS:=+kmod-tun +luci-lib-jsonc +strongswan +strongswan-minimal +strongswan-mod-kernel-libipsec +strongswan-mod-openssl +strongswan-mod-xauth-generic +strongswan-mod-eap-mschapv2 +strongswan-mod-eap-identity +strongswan-mod-pem +strongswan-mod-pkcs1 +strongswan-mod-pkcs8 +strongswan-mod-dhcp +strongswan-mod-farp +LUCI_DEPENDS:=+kmod-tun +luci-lib-jsonc +xl2tpd +strongswan \ + +PACKAGE_strongswan:strongswan-mod-kernel-libipsec \ + +PACKAGE_strongswan:strongswan-mod-openssl \ + +PACKAGE_strongswan:strongswan-mod-xauth-generic \ + +(PACKAGE_strongswan-mod-kdf||PACKAGE_strongswan-mod-openssl||PACKAGE_strongswan-mod-wolfssl):strongswan-minimal LUCI_PKGARCH:=all define Package/$(PKG_NAME)/conffiles diff --git a/luci-app-ipsec-server/example.config b/luci-app-ipsec-server/example.config deleted file mode 100644 index 8525d32ad..000000000 --- a/luci-app-ipsec-server/example.config +++ /dev/null @@ -1,26 +0,0 @@ -conn ikev2-EAP - keyexchange=ikev2 - auto=add - fragmentation=yes - forceencaps=yes - - left=%defaultroute - leftid=@ns.miaogongzi.cc - leftauth=pubkey - leftcert=full.pem - leftsendcert=always - leftsubnet=0.0.0.0/0 - - right=%any - rightid=%any - rightauth=eap-mschapv2 - eap_identity=%identity - rightsourceip=10.0.20.0/24 - rightdns=10.0.0.1 - - ike=aes256-sha256-modp1024,aes256-sha1-modp1024,aes128-sha256-modp1024,aes128-sha1-modp1024,aes256-sha256-modp2048,aes256-sha1-modp2048 - esp=aes256-sha256,aes128-sha256,aes256-sha1,aes128-sha1 - dpdaction=clear - dpddelay=30s - rekey=no - compress=no \ No newline at end of file diff --git a/luci-app-ipsec-server/luasrc/controller/ipsec-server.lua b/luci-app-ipsec-server/luasrc/controller/ipsec-server.lua index 67906fdda..8bd5670f0 100644 --- a/luci-app-ipsec-server/luasrc/controller/ipsec-server.lua +++ b/luci-app-ipsec-server/luasrc/controller/ipsec-server.lua @@ -11,12 +11,14 @@ function index() entry({"admin", "vpn", "ipsec-server", "settings"}, cbi("ipsec-server/settings"), _("General Settings"), 10).leaf = true entry({"admin", "vpn", "ipsec-server", "users"}, cbi("ipsec-server/users"), _("Users Manager"), 20).leaf = true entry({"admin", "vpn", "ipsec-server", "l2tp_user"}, cbi("ipsec-server/l2tp_user")).leaf = true + entry({"admin", "vpn", "ipsec-server", "online"}, cbi("ipsec-server/online"), _("Online Users"), 30).leaf = true entry({"admin", "vpn", "ipsec-server", "status"}, call("act_status")).leaf = true end function act_status() local e = {} - e["ipsec_status"] = luci.sys.call("pidof charon >/dev/null 2>&1") == 0 + e["ipsec_status"] = luci.sys.call("/usr/bin/pgrep ipsec >/dev/null") == 0 + e["l2tp_status"] = luci.sys.call("top -bn1 | grep -v grep | grep '/var/etc/xl2tpd' >/dev/null") == 0 luci.http.prepare_content("application/json") luci.http.write_json(e) end diff --git a/luci-app-ipsec-server/luasrc/model/cbi/ipsec-server/l2tp_user.lua b/luci-app-ipsec-server/luasrc/model/cbi/ipsec-server/l2tp_user.lua index e21a9043e..3b8460c65 100644 --- a/luci-app-ipsec-server/luasrc/model/cbi/ipsec-server/l2tp_user.lua +++ b/luci-app-ipsec-server/luasrc/model/cbi/ipsec-server/l2tp_user.lua @@ -1,10 +1,10 @@ local d = require "luci.dispatcher" -local fs = require "nixio.fs" +local sys = require "luci.sys" m = Map("luci-app-ipsec-server", "L2TP/IPSec PSK " .. translate("Users Manager")) m.redirect = d.build_url("admin", "vpn", "ipsec-server", "users") -if fs.access("/usr/sbin/xl2tpd") then +if sys.call("command -v xl2tpd > /dev/null") == 0 then s = m:section(NamedSection, arg[1], "l2tp_users", "") s.addremove = false s.anonymous = true @@ -19,7 +19,6 @@ if fs.access("/usr/sbin/xl2tpd") then o = s:option(Value, "password", translate("Password")) o.placeholder = translate("Password") - o.password = true o.rmempty = false o = s:option(Value, "ipaddress", translate("IP address")) diff --git a/luci-app-ipsec-server/luasrc/model/cbi/ipsec-server/online.lua b/luci-app-ipsec-server/luasrc/model/cbi/ipsec-server/online.lua new file mode 100644 index 000000000..c4e0d65c3 --- /dev/null +++ b/luci-app-ipsec-server/luasrc/model/cbi/ipsec-server/online.lua @@ -0,0 +1,148 @@ +local d = require "luci.dispatcher" +local fs = require "nixio.fs" +local jsonc = require "luci.jsonc" +local sys = require "luci.sys" +local util = require "luci.util" +local uci = require "luci.model.uci".cursor() + +f = SimpleForm("processes") +f.reset = false +f.submit = false + +local firewall_user_path = "/etc/firewall.user" + +if uci:get("luci-app-ipsec-server", "ipsec", "type") ~= "L2TP" then + local sessions = {} + local status_dump = io.popen('ipsec status 2>&1 | grep "ESTABLISHED"') + if status_dump then + local line + for line in status_dump:lines() do + local line = string.split(line, ",") + local s1 = string.split(line[1], " ") + local type_count = s1[1] + local user = line[2] + local user_split = string.split(user, "...") + if #user_split == 2 then + local leftid = string.match(user_split[1], "%[(.-)%]") + local username = string.match(user_split[2], "%[(.-)%]") + local remote_ip = string.split(user_split[2], "[")[1] + local a = {} + for i, v in ipairs(s1) do + if i > 2 then + a[#a + 1] = v + end + end + local login_time = table.concat(a, " ") + sessions[#sessions + 1] = { + username = username, + remote_ip = remote_ip, + login_time = login_time + } + end + end + end + for i, v in ipairs(sessions) do + local ip = util.exec("ipsec leases 2>/dev/null | grep 'online' | grep '%s' | awk '{print $1}'" % { v.username }) + v.ip = ip + end + t = f:section(Table, sessions, translate("Online Users")) + t:option(DummyValue, "username", translate("Username") .. "/" .. translate("Identifier")) + t:option(DummyValue, "ip", translate("Client IP")) + t:option(DummyValue, "login_time", translate("Login Time")) + t:option(DummyValue, "remote_ip", translate("IP address")) + add_blacklist = t:option(Button, "add_blacklist", translate("Blacklist")) + function add_blacklist.render(e, t, a) + e.title = translate("Add to Blacklist") + e.inputstyle = "remove" + Button.render(e, t, a) + end + function add_blacklist.write(t, s) + local e = t.map:get(s, "remote_ip") + util.execi("echo 'iptables -I INPUT -s %s -p udp -m multiport --dports 500,4500 -j DROP ## ipsec-blacklist-%s' >> %s" % {e, e, firewall_user_path}) + util.execi("iptables -I INPUT -s %s -p udp -m multiport --dports 500,4500 -j DROP" % {e}) + luci.http.redirect(d.build_url("admin/vpn/ipsec-server/online")) + end +else + local l2tp_sessions = {} + local l2tp_session_path = "/var/etc/xl2tpd/session" + if fs.access(l2tp_session_path) then + for filename in fs.dir(l2tp_session_path) do + local session_file = l2tp_session_path .. "/" .. filename + local file = io.open(session_file, "r") + local t = jsonc.parse(file:read("*a")) + if t then + t.session_file = session_file + l2tp_sessions[#l2tp_sessions + 1] = t + end + file:close() + end + end + + t = f:section(Table, l2tp_sessions, translate("L2TP Online Users")) + t:option(DummyValue, "username", translate("Username")) + t:option(DummyValue, "interface", translate("Interface")) + t:option(DummyValue, "ip", translate("Client IP")) + t:option(DummyValue, "remote_ip", translate("IP address")) + t:option(DummyValue, "login_time", translate("Login Time")) + + _blacklist = t:option(Button, "_blacklist", translate("Blacklist")) + function _blacklist.render(e, t, a) + e.title = translate("Add to Blacklist") + e.inputstyle = "remove" + Button.render(e, t, a) + end + function _blacklist.write(t, s) + local e = t.map:get(s, "remote_ip") + util.execi("echo 'iptables -I INPUT -s %s -p udp -m multiport --dports 500,4500,1701 -j DROP ## xl2tpd-blacklist-%s' >> %s" % {e, e, firewall_user_path}) + util.execi("iptables -I INPUT -s %s -p udp -m multiport --dports 500,4500,1701 -j DROP" % {e}) + util.execi("rm -f " .. t.map:get(s, "session_file")) + null, t.tag_error[s] = sys.process.signal(t.map:get(s, "pid"), 9) + luci.http.redirect(d.build_url("admin/vpn/ipsec-server/online")) + end + + _kill = t:option(Button, "_kill", translate("Forced offline")) + _kill.inputstyle = "remove" + function _kill.write(t, s) + util.execi("rm -f " .. t.map:get(s, "session_file")) + null, t.tag_error[t] = sys.process.signal(t.map:get(s, "pid"), 9) + luci.http.redirect(d.build_url("admin/vpn/ipsec-server/online")) + end +end + +local blacklist = {} +if fs.access(firewall_user_path) then + for line in io.lines(firewall_user_path) do + local m = line:match('ipsec%-blacklist%-([^\n]+)') + if m then + local t = {} + t.ip = m + blacklist[#blacklist + 1] = t + end + local m = line:match('xl2tpd%-blacklist%-([^\n]+)') + if m then + local t = {} + t.ip = m + blacklist[#blacklist + 1] = t + end + end +end + +t = f:section(Table, blacklist, translate("Blacklist")) +t:option(DummyValue, "ip", translate("IP address")) + +remove_blacklist = t:option(Button, "remove_blacklist", translate("Blacklist")) +function remove_blacklist.render(e, t, a) + e.title = translate("Remove from Blacklist") + e.inputstyle = "apply" + Button.render(e, t, a) +end +function remove_blacklist.write(t, s) + local e = t.map:get(s, "ip") + util.execi("sed -i -e '/## ipsec-blacklist-%s/d' %s" % {e, firewall_user_path}) + util.execi("sed -i -e '/## xl2tpd-blacklist-%s/d' %s" % {e, firewall_user_path}) + util.execi("iptables -D INPUT -s %s -p udp -m multiport --dports 500,4500 -j DROP 2>/dev/null" % {e}) + util.execi("iptables -D INPUT -s %s -p udp -m multiport --dports 500,4500,1701 -j DROP 2>/dev/null" % {e}) + luci.http.redirect(d.build_url("admin/vpn/ipsec-server/online")) +end + +return f diff --git a/luci-app-ipsec-server/luasrc/model/cbi/ipsec-server/settings.lua b/luci-app-ipsec-server/luasrc/model/cbi/ipsec-server/settings.lua index 52d533af5..cde365bee 100644 --- a/luci-app-ipsec-server/luasrc/model/cbi/ipsec-server/settings.lua +++ b/luci-app-ipsec-server/luasrc/model/cbi/ipsec-server/settings.lua @@ -1,3 +1,5 @@ +local sys = require "luci.sys" + m = Map("luci-app-ipsec-server", translate("IPSec VPN Server")) m.template = "ipsec-server/ipsec-server_status" @@ -7,53 +9,64 @@ s.anonymous = true o = s:option(DummyValue, "ipsec-server_status", translate("Current Condition")) o.rawhtml = true o.cfgvalue = function(t, n) - return '' .. translate("Checking...") .. '' + return '' end -enabled = s:option(Flag, "enabled", translate("Enable")) -enabled.description = translate("Enable IPsec access for IKEv2 PSK, IKEv1 XAuth PSK, and IKEv2 EAP clients.") -enabled.default = 0 -enabled.rmempty = false +o = s:option(Flag, "enabled", translate("Enable")) +o.default = 0 +o.rmempty = false -clientip = s:option(Value, "clientip", translate("VPN Client IP")) -clientip.description = translate("VPN client address pool in CIDR notation, such as: 10.0.10.0/24") -clientip.datatype = "ip4addr" -clientip.optional = false -clientip.rmempty = false -clientip.placeholder = "10.0.10.0/24" +type = s:option(ListValue, "type", translate("Type")) +type:value("IKEv2", "IKEv2/IPSec PSK") +type:value("IKEv1", "IPSec Xauth PSK") +type.default = "IKEv2" -serverip = s:option(Value, "serverip", translate("VPN Server IP")) -serverip.description = translate("VPN server address and subnet in CIDR notation, such as: 10.0.0.1/24") -serverip.datatype = "ip4addr" -serverip.optional = false -serverip.rmempty = false -serverip.placeholder = "10.0.0.1/24" +o = s:option(Value, "clientip", translate("VPN Client IP")) +o.description = translate("VPN Client reserved started IP addresses with the same subnet mask, such as: 192.168.100.10/24") +o.datatype = "ip4addr" +o.optional = false +o:depends("type", "IKEv2") +o:depends("type", "IKEv1") -domain = s:option(Value, "domain", translate("VPN Domain")) -domain.description = translate("Domain name used for IKEv2 certificate and Remote ID, such as: ns.miaogongzi.cc") -domain.datatype = "host" -domain.optional = false -domain.rmempty = false -domain.placeholder = "ns.miaogongzi.cc" -domain.default = "ns.miaogongzi.cc" - -certs_name = s:option(Value, "certs_name", translate("Certificate File Name")) -certs_name.description = translate("Server certificate file name under /etc/ipsec.d/certs/, such as: server.pem") -certs_name.optional = false -certs_name.rmempty = false -certs_name.placeholder = "server.pem" -certs_name.default = "server.pem" - -private_key = s:option(Value, "private_key", translate("Private Key File Name")) -private_key.description = translate("Server private key file name under /etc/ipsec.d/private/, such as: server_key.pem") -private_key.optional = false -private_key.rmempty = false -private_key.placeholder = "server_key.pem" -private_key.default = "server_key.pem" - -secret = s:option(Value, "secret", translate("Secret Pre-Shared Key")) -secret.description = translate("Used by IKEv2 PSK and IKEv1 XAuth PSK clients.") +secret = s:option(Value, "secret", translate("Preshared Key")) +secret.default = "ipsec" secret.password = true -secret.rmempty = false +secret:depends("type", "IKEv1") + +if sys.call("command -v xl2tpd > /dev/null") == 0 then + type:value("L2TP", "L2TP/IPSec PSK") + secret:depends("type", "L2TP") + + o = s:option(DummyValue, "l2tp_status", "L2TP " .. translate("Current Condition")) + o.rawhtml = true + o.cfgvalue = function(t, n) + return '' + end + o:depends("type", "L2TP") + + o = s:option(Value, "l2tp_localip", "L2TP " .. translate("Server IP")) + o.description = translate("VPN Server IP address, such as: 192.168.101.1") + o.datatype = "ip4addr" + o.default = "192.168.101.1" + o.placeholder = o.default + o:depends("type", "L2TP") + + o = s:option(Value, "l2tp_remoteip", "L2TP " .. translate("Client IP")) + o.description = translate("VPN Client IP address range, such as: 192.168.101.10-20") + o.default = "192.168.101.10-20" + o.placeholder = o.default + o:depends("type", "L2TP") + +--[[ + if sys.call("ls -L /usr/lib/ipsec/libipsec* 2>/dev/null >/dev/null") == 0 then + o = s:option(DummyValue, "_o", " ") + o.rawhtml = true + o.cfgvalue = function(t, n) + return string.format('%s', translate("L2TP/IPSec is not compatible with kernel-libipsec, which will disable this module.")) + end + o:depends("type", "L2TP") + end +]]-- +end return m diff --git a/luci-app-ipsec-server/luasrc/model/cbi/ipsec-server/users.lua b/luci-app-ipsec-server/luasrc/model/cbi/ipsec-server/users.lua index 8f2e07315..adf7c9af6 100644 --- a/luci-app-ipsec-server/luasrc/model/cbi/ipsec-server/users.lua +++ b/luci-app-ipsec-server/luasrc/model/cbi/ipsec-server/users.lua @@ -1,28 +1,49 @@ local d = require "luci.dispatcher" -local fs = require "nixio.fs" +local sys = require "luci.sys" m = Map("luci-app-ipsec-server") -s = m:section(TypedSection, "ipsec_users", "IPSec Xauth PSK/EAP " .. translate("Users Manager")) -s.description = translate("Use a client that supports IPSec Xauth PSK or IPSec EAP (iOS, Android, macOS, or Windows) to connect to this server.") -s.addremove = true -s.anonymous = true -s.template = "cbi/tblsection" +local type = m:get("ipsec", "type") -o = s:option(Flag, "enabled", translate("Enabled")) -o.default = 1 -o.rmempty = false +if type == "IKEv2" then + s = m:section(TypedSection, "ikev2_users", "IKEv2/IPSec PSK " .. translate("Users Manager")) + s.description = translate("Use a client that supports IKEv2/IPSec PSK (iOS or Android) to connect to this server.") + s.addremove = true + s.anonymous = true + s.template = "cbi/tblsection" -o = s:option(Value, "username", translate("Username")) -o.placeholder = translate("Username") -o.rmempty = false + o = s:option(Flag, "enabled", translate("Enabled")) + o.default = 1 + o.rmempty = false -o = s:option(Value, "password", translate("Password")) -o.placeholder = translate("Password") -o.password = true -o.rmempty = false + o = s:option(Value, "identifier", translate("Identifier")) + o.placeholder = translate("Identifier") + o.rmempty = false -if fs.access("/usr/sbin/xl2tpd") then + o = s:option(Value, "secret", translate("Preshared Key")) + o.placeholder = translate("Preshared Key") + o.rmempty = false +end +if type == "IKEv1" then + s = m:section(TypedSection, "ipsec_users", "IPSec Xauth PSK " .. translate("Users Manager")) + s.description = translate("Use a client that supports IPSec Xauth PSK (iOS or Android) to connect to this server.") + s.addremove = true + s.anonymous = true + s.template = "cbi/tblsection" + + o = s:option(Flag, "enabled", translate("Enabled")) + o.default = 1 + o.rmempty = false + + o = s:option(Value, "username", translate("Username")) + o.placeholder = translate("Username") + o.rmempty = false + + o = s:option(Value, "password", translate("Password")) + o.placeholder = translate("Password") + o.rmempty = false +end +if type == "L2TP" and sys.call("command -v xl2tpd > /dev/null") == 0 then s = m:section(TypedSection, "l2tp_users", "L2TP/IPSec PSK " .. translate("Users Manager")) s.description = translate("Use a client that supports L2TP over IPSec PSK to connect to this server.") s.addremove = true @@ -44,7 +65,6 @@ if fs.access("/usr/sbin/xl2tpd") then o = s:option(Value, "password", translate("Password")) o.placeholder = translate("Password") - o.password = true o.rmempty = false o = s:option(Value, "ipaddress", translate("IP address")) diff --git a/luci-app-ipsec-server/luasrc/view/ipsec-server/ipsec-server_status.htm b/luci-app-ipsec-server/luasrc/view/ipsec-server/ipsec-server_status.htm index 251caacce..93e36a405 100644 --- a/luci-app-ipsec-server/luasrc/view/ipsec-server/ipsec-server_status.htm +++ b/luci-app-ipsec-server/luasrc/view/ipsec-server/ipsec-server_status.htm @@ -1,61 +1,21 @@ - - <% include("cbi/map") %> - diff --git a/luci-app-ipsec-server/po/zh-cn/ipsec-server.po b/luci-app-ipsec-server/po/zh-cn/ipsec-server.po index e99b576fa..80b0b499b 100644 --- a/luci-app-ipsec-server/po/zh-cn/ipsec-server.po +++ b/luci-app-ipsec-server/po/zh-cn/ipsec-server.po @@ -1,60 +1,51 @@ msgid "IPSec VPN Server" msgstr "IPSec VPN 服务器" -msgid "Enable IPsec access for IKEv2 PSK, IKEv1 XAuth PSK, and IKEv2 EAP clients." -msgstr "启用 IKEv2 PSK、IKEv1 XAuth PSK 和 IKEv2 EAP 客户端接入。" +msgid "Type" +msgstr "类型" -msgid "Use a client that supports IPSec Xauth PSK or IPSec EAP (iOS, Android, macOS, or Windows) to connect to this server." -msgstr "请使用支持 IPSec Xauth PSK 或 IPSec EAP 的客户端(iOS、Android、macOS、Windows)连接到此服务器。" +msgid "Use a client that supports IKEv2/IPSec PSK (iOS or Android) to connect to this server." +msgstr "使用支持 IKEv2/IPSec PSK(iOS 或 Android)的客户端连接到此服务端。" + +msgid "Use a client that supports IPSec Xauth PSK (iOS or Android) to connect to this server." +msgstr "使用支持 IPSec Xauth PSK(iOS 或 Android)的客户端连接到此服务端。" + +msgid "Use a client that supports L2TP over IPSec PSK to connect to this server." +msgstr "使用支持 L2TP over IPSec PSK 的客户端连接到此服务端。" msgid "Current Condition" msgstr "当前状态" -msgid "Checking..." -msgstr "正在检查..." - -msgid "Status unavailable" -msgstr "状态不可用" - -msgid "General Settings" +msgid "General settings" msgstr "基本设置" -msgid "Enable" -msgstr "启用" - msgid "Enabled" msgstr "启用" msgid "VPN Client IP" -msgstr "VPN 客户端地址池" +msgstr "VPN客户端地址段" -msgid "VPN Server IP" -msgstr "VPN 服务端地址" +msgid "VPN Client reserved started IP addresses with the same subnet mask, such as: 192.168.100.10/24" +msgstr "VPN客户端获取IP的起始地址,例如:192.168.100.10/24" -msgid "VPN client address pool in CIDR notation, such as: 10.0.10.0/24" -msgstr "VPN 客户端地址池(CIDR 格式),例如:10.0.10.0/24" +msgid "Preshared Key" +msgstr "预共享密钥" -msgid "VPN server address and subnet in CIDR notation, such as: 10.0.0.1/24" -msgstr "VPN 服务端地址和子网(CIDR 格式),例如:10.0.0.1/24" +msgid "VPN Server IP address, such as: 192.168.101.1" +msgstr "VPN服务端IP地址,例如:192.168.101.1" -msgid "Domain name used for IKEv2 certificate and Remote ID, such as: ns.miaogongzi.cc" -msgstr "用于 IKEv2 证书和远程 ID 的域名,例如:ns.miaogongzi.cc" +msgid "VPN Client IP address range, such as: 192.168.101.10-20" +msgstr "VPN客户端获取IP范围,例如:192.168.101.10-20" -msgid "Server private key file name under /etc/ipsec.d/private/, such as: server_key.pem" -msgstr "位于 /etc/ipsec.d/private/ 的服务器私钥文件名,例如:server_key.pem" - -msgid "Server certificate file name under /etc/ipsec.d/certs/, such as: server.pem" -msgstr "位于 /etc/ipsec.d/certs/ 的服务器证书文件名,例如:server.pem" - -msgid "Secret Pre-Shared Key" -msgstr "PSK 密钥" - -msgid "Used by IKEv2 PSK and IKEv1 XAuth PSK clients." -msgstr "供 IKEv2 PSK 和 IKEv1 XAuth PSK 客户端使用。" +msgid "L2TP/IPSec is not compatible with kernel-libipsec, which will disable this module." +msgstr "L2TP/IPSec不兼容kernel-libipsec,开启将会禁用此模块。" msgid "Users Manager" msgstr "用户管理" +msgid "Identifier" +msgstr "标识符" + msgid "Username" msgstr "用户名" @@ -67,23 +58,29 @@ msgstr "IP 地址" msgid "Automatically" msgstr "自动分配" -msgid "Static Routes" -msgstr "静态路由" +msgid "Online Users" +msgstr "在线用户" -msgid "Use a client that supports L2TP over IPSec PSK to connect to this server." -msgstr "使用支持 L2TP over IPSec PSK 的客户端连接到此服务器。" +msgid "L2TP Online Users" +msgstr "L2TP 在线用户" + +msgid "Login Time" +msgstr "登录时间" + +msgid "Blacklist" +msgstr "黑名单" + +msgid "Add to Blacklist" +msgstr "加入黑名单" + +msgid "Remove from Blacklist" +msgstr "移出黑名单" + +msgid "Forced offline" +msgstr "强制下线" msgid "NOT RUNNING" msgstr "未运行" msgid "RUNNING" msgstr "运行中" - -msgid "Certificate File Name" -msgstr "证书文件名" - -msgid "Private Key File Name" -msgstr "私钥文件名" - -msgid "VPN Domain" -msgstr "VPN 域名" diff --git a/luci-app-ipsec-server/root/etc/config/luci-app-ipsec-server b/luci-app-ipsec-server/root/etc/config/luci-app-ipsec-server index fcfa85bb5..397f55ee3 100644 --- a/luci-app-ipsec-server/root/etc/config/luci-app-ipsec-server +++ b/luci-app-ipsec-server/root/etc/config/luci-app-ipsec-server @@ -1,10 +1,6 @@ config service 'ipsec' option enabled '0' - option secret 'ipsec' - option clientip '10.0.10.0/24' - option serverip '10.0.0.1/24' - option certs_name 'server.pem' - option private_key 'server_key.pem' - option domain 'ns.miaogongzi.cc' + option type 'IKEv2' + option clientip '192.168.100.10/24' diff --git a/luci-app-ipsec-server/root/etc/init.d/luci-app-ipsec-server b/luci-app-ipsec-server/root/etc/init.d/luci-app-ipsec-server index 5164e99ee..2a3e4a0ac 100755 --- a/luci-app-ipsec-server/root/etc/init.d/luci-app-ipsec-server +++ b/luci-app-ipsec-server/root/etc/init.d/luci-app-ipsec-server @@ -12,15 +12,13 @@ L2TP_CONFIG_FILE=${L2TP_PATH}/xl2tpd.conf L2TP_OPTIONS_FILE=${L2TP_PATH}/options.xl2tpd L2TP_LOG_FILE=${L2TP_PATH}/xl2tpd.log -vt_clientip=$(uci -q get ${CONFIG}.@service[0].clientip) -vt_serverip=$(uci -q get ${CONFIG}.@service[0].serverip) -certs_name=$(uci -q get ${CONFIG}.@service[0].certs_name) -private_key=$(uci -q get ${CONFIG}.@service[0].private_key) -vpn_domain=$(uci -q get ${CONFIG}.@service[0].domain) -l2tp_enabled=$(uci -q get ${CONFIG}.@service[0].l2tp_enable) -l2tp_localip=$(uci -q get ${CONFIG}.@service[0].l2tp_localip) +ipt_comment="IPSec VPN Server" -ipt_flag="IPSec VPN Server" +init_config() { + vt_type=$(uci -q get ${CONFIG}.@service[0].type) + vt_clientip=$(uci -q get ${CONFIG}.@service[0].clientip) + l2tp_localip=$(uci -q get ${CONFIG}.@service[0].l2tp_localip) +} get_enabled_anonymous_secs() { uci -q show "${CONFIG}" | grep "${1}\[.*\.enabled='1'" | cut -d '.' -sf2 @@ -28,29 +26,73 @@ get_enabled_anonymous_secs() { ipt_rule() { if [ "$1" = "add" ]; then - iptables -t nat -I POSTROUTING -s ${vt_serverip} -m comment --comment "${ipt_flag}" -j MASQUERADE 2>/dev/null - iptables -I forwarding_rule -s ${vt_serverip} -m comment --comment "${ipt_flag}" -j ACCEPT 2>/dev/null - iptables -I forwarding_rule -m policy --dir in --pol ipsec --proto esp -m comment --comment "${ipt_flag}" -j ACCEPT 2>/dev/null - iptables -I forwarding_rule -m policy --dir out --pol ipsec --proto esp -m comment --comment "${ipt_flag}" -j ACCEPT 2>/dev/null - iptables -I INPUT -p udp -m multiport --dports 500,4500 -m comment --comment "${ipt_flag}" -j ACCEPT 2>/dev/null - iptables -t mangle -I OUTPUT -p udp -m multiport --sports 500,4500 -m comment --comment "${ipt_flag}" -j RETURN 2>/dev/null - [ "${l2tp_enabled}" = 1 ] && { - iptables -t nat -I POSTROUTING -s ${l2tp_localip%.*}.0/24 -m comment --comment "${ipt_flag}" -j MASQUERADE 2>/dev/null - iptables -I forwarding_rule -s ${l2tp_localip%.*}.0/24 -m comment --comment "${ipt_flag}" -j ACCEPT 2>/dev/null - iptables -I INPUT -p udp --dport 1701 -m comment --comment "${ipt_flag}" -j ACCEPT 2>/dev/null - iptables -t mangle -I OUTPUT -p udp --sport 1701 -m comment --comment "${ipt_flag}" -j RETURN 2>/dev/null - } + iptables -w -t mangle -I OUTPUT -p udp -m multiport --sports 500,4500 -m comment --comment "${ipt_comment}" -j RETURN 2>/dev/null + iptables -w -I INPUT -p udp -m multiport --dports 500,4500 -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null + iptables -w -I FORWARD -m policy --dir in --pol ipsec --proto esp -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null + iptables -w -I FORWARD -m policy --dir out --pol ipsec --proto esp -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null + + ip6tables -w -t mangle -I OUTPUT -p udp -m multiport --sports 500,4500 -m comment --comment "${ipt_comment}" -j RETURN 2>/dev/null + ip6tables -w -I INPUT -p udp -m multiport --dports 500,4500 -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null + ip6tables -w -I FORWARD -m policy --dir in --pol ipsec --proto esp -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null + ip6tables -w -I FORWARD -m policy --dir out --pol ipsec --proto esp -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null + + if [ "${vt_type}" = "L2TP" ]; then + iptables -w -t mangle -I OUTPUT -p udp --sport 1701 -m comment --comment "${ipt_comment}" -j RETURN 2>/dev/null + iptables -w -I INPUT -p udp --dport 1701 -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null + iptables -w -I INPUT -s ${l2tp_localip%.*}.0/24 -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null + iptables -w -I INPUT -d ${l2tp_localip%.*}.0/24 -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null + iptables -w -I FORWARD -s ${l2tp_localip%.*}.0/24 -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null + iptables -w -I FORWARD -d ${l2tp_localip%.*}.0/24 -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null + iptables -w -I OUTPUT -s ${l2tp_localip%.*}.0/24 -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null + iptables -w -I OUTPUT -d ${l2tp_localip%.*}.0/24 -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null + iptables -w -t nat -I POSTROUTING -s ${l2tp_localip%.*}.0/24 -m comment --comment "${ipt_comment}" -j FULLCONENAT 2>/dev/null + [ "$?" != "0" ] && iptables -w -t nat -I POSTROUTING -s ${l2tp_localip%.*}.0/24 -m comment --comment "${ipt_comment}" -j MASQUERADE 2>/dev/null + iptables -w -t nat -I POSTROUTING -d ${l2tp_localip%.*}.0/24 -m comment --comment "${ipt_comment}" -j FULLCONENAT 2>/dev/null + [ "$?" != "0" ] && iptables -w -t nat -I POSTROUTING -d ${l2tp_localip%.*}.0/24 -m comment --comment "${ipt_comment}" -j MASQUERADE 2>/dev/null + else + iptables -w -I INPUT -s ${vt_clientip} -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null + iptables -w -I INPUT -d ${vt_clientip} -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null + iptables -w -I FORWARD -s ${vt_clientip} -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null + iptables -w -I FORWARD -d ${vt_clientip} -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null + iptables -w -I OUTPUT -s ${vt_clientip} -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null + iptables -w -I OUTPUT -d ${vt_clientip} -m comment --comment "${ipt_comment}" -j ACCEPT 2>/dev/null + iptables -w -t nat -I POSTROUTING -s ${vt_clientip} -m comment --comment "${ipt_comment}" -j FULLCONENAT 2>/dev/null + [ "$?" != "0" ] && iptables -w -t nat -I POSTROUTING -s ${vt_clientip} -m comment --comment "${ipt_comment}" -j MASQUERADE 2>/dev/null + iptables -w -t nat -I POSTROUTING -d ${vt_clientip} -m comment --comment "${ipt_comment}" -j FULLCONENAT 2>/dev/null + [ "$?" != "0" ] && iptables -w -t nat -I POSTROUTING -d ${vt_clientip} -m comment --comment "${ipt_comment}" -j MASQUERADE 2>/dev/null + + + uci -q batch <<-EOF >/dev/null + delete network.ipsec_server + set network.ipsec_server=interface + set network.ipsec_server.device="ipsec0" + set network.ipsec_server.device="ipsec0" + set network.ipsec_server.proto="static" + set network.ipsec_server.ipaddr="${vt_clientip%.*}.1" + set network.ipsec_server.netmask="255.255.255.0" + commit network + EOF + fi else ipt_del() { - for i in $(seq 1 $($1 -nL $2 | grep -c "${ipt_flag}")); do - local index=$($1 --line-number -nL $2 | grep "${ipt_flag}" | head -1 | awk '{print $1}') + for i in $(seq 1 $($1 -nL $2 | grep -c "${ipt_comment}")); do + local index=$($1 --line-number -nL $2 | grep "${ipt_comment}" | head -1 | awk '{print $1}') $1 -w -D $2 $index 2>/dev/null done } - ipt_del "iptables" "forwarding_rule" - ipt_del "iptables" "INPUT" - ipt_del "iptables -t nat" "POSTROUTING" - ipt_del "iptables -t mangle" "OUTPUT" + ipt_del "iptables -w" "INPUT" + ipt_del "iptables -w" "FORWARD" + ipt_del "iptables -w" "OUTPUT" + ipt_del "iptables -w -t nat" "POSTROUTING" + ipt_del "iptables -w -t mangle" "OUTPUT" + ipt_del "ip6tables -w" "INPUT" + ipt_del "ip6tables -w" "FORWARD" + ipt_del "ip6tables -w -t mangle" "OUTPUT" + uci -q batch <<-EOF >/dev/null + delete network.ipsec_server + commit network + EOF + ifup ipsec_server > /dev/null 2>&1 fi } @@ -58,50 +100,155 @@ gen_include() { echo '#!/bin/sh' > /var/etc/ipsecvpn.include extract_rules() { echo "*$1" - iptables-save -t $1 | grep "${ipt_flag}" | \ + iptables-save -t $1 | grep "${ipt_comment}" | \ + sed -e "s/^-A \(INPUT\)/-I \1 1/" + echo 'COMMIT' + } + extract_rules6() { + echo "*$1" + ip6tables-save -t $1 | grep "${ipt_comment}" | \ sed -e "s/^-A \(INPUT\)/-I \1 1/" echo 'COMMIT' } cat <<-EOF >> /var/etc/ipsecvpn.include - iptables-save -c | grep -v "${ipt_flag}" | iptables-restore -c + iptables-save -c | grep -v "${ipt_comment}" | iptables-restore -c iptables-restore -n <<-EOT $(extract_rules filter) $(extract_rules nat) + $(extract_rules mangle) EOT + + ip6tables-save -c | grep -v "${ipt_comment}" | ip6tables-restore -c + ip6tables-restore -n <<-EOT + $(extract_rules6 filter) + $(extract_rules6 mangle) + EOT + exit 0 EOF return 0 } start() { + init_config local vt_enabled=$(uci -q get ${CONFIG}.@service[0].enabled) - [ "$vt_enabled" = 1 ] || return 0 - - local vt_gateway="${vt_serverip%.*}.1" - local vt_secret=$(uci -q get ${CONFIG}.@service[0].secret) - - local l2tp_enabled=$(uci -q get ${CONFIG}.@service[0].l2tp_enable) - [ "${l2tp_enabled}" = 1 ] && { - touch ${CHAP_SECRETS} - local vt_remoteip=$(uci -q get ${CONFIG}.@service[0].l2tp_remoteip) - local ipsec_l2tp_config=$(cat <<-EOF - ####################################### - # L2TP Connections - ####################################### + [ "$vt_enabled" = 0 ] && return 1 - conn L2TP-IKEv1-PSK - type=transport - keyexchange=ikev1 - authby=secret - leftprotoport=udp/l2tp - left=%any - right=%any + cat > ${IPSEC_CONN_FILE} <<-EOF + # ipsec.conf - strongSwan IPsec configuration file + + config setup + uniqueids=no + charondebug="cfg 2, dmn 2, ike 2, net 0" + + conn %default + dpdaction=clear + dpddelay=300s rekey=no - forceencaps=yes + left=%any + leftfirewall=yes + right=%any + ikelifetime=60m + keylife=20m + rekeymargin=3m + keyingtries=1 + auto=add ike=aes128-sha1-modp2048,aes128-sha1-modp1024,3des-sha1-modp1024,3des-sha1-modp1536 - esp=aes128-sha1,3des-sha1 + esp=aes128-sha1,aes256-sha256,3des-sha1 + + EOF + + cat > ${IPSEC_SECRETS_FILE} <<-EOF + # /etc/ipsec.secrets - strongSwan IPsec secrets file + EOF + + ([ "${vt_type}" = "IKEv1" ] || [ "${vt_type}" = "IKEv2" ]) && { + local vt_gateway="${vt_clientip%.*}.1" + cat >> ${IPSEC_CONN_FILE} <<-EOF + conn conn-ipsec + leftsubnet=0.0.0.0/0 + rightsubnet=${vt_clientip} + rightsourceip=${vt_clientip} + rightdns=${vt_gateway} + EOF - ) - + } + + [ "${vt_type}" = "IKEv2" ] && { + cat >> ${IPSEC_CONN_FILE} <<-EOF + conn IKEv2-PSK-XAuth + also=conn-ipsec + keyexchange=ikev2 + leftauth=psk + rightauth=psk + + EOF + local ikev2_users=$(get_enabled_anonymous_secs "@ikev2_users") + [ -n "${ikev2_users}" ] && { + for _user in ${ikev2_users}; do + local u_enabled=$(uci -q get ${CONFIG}.${_user}.enabled) + [ "${u_enabled}" -eq 1 ] || continue + + local u_identifier=$(uci -q get ${CONFIG}.${_user}.identifier) + [ -n "${u_identifier}" ] || continue + + local u_secret=$(uci -q get ${CONFIG}.${_user}.secret) + [ -n "${u_secret}" ] || continue + + echo "${u_identifier} : PSK '${u_secret}'" >> ${IPSEC_SECRETS_FILE} + done + } + } + + ([ "${vt_type}" = "IKEv1" ] || [ "${vt_type}" = "L2TP" ]) && { + cat >> ${IPSEC_CONN_FILE} <<-EOF + conn IKEv1-PSK-XAuth + keyexchange=ikev1 + EOF + local vt_secret=$(uci -q get ${CONFIG}.@service[0].secret) + cat >> ${IPSEC_SECRETS_FILE} <<-EOF + : PSK "$vt_secret" + EOF + } + + [ "${vt_type}" = "IKEv1" ] && { + cat >> ${IPSEC_CONN_FILE} <<-EOF + also=conn-ipsec + leftauth=psk + rightauth=psk + rightauth2=xauth + EOF + local ipsec_users=$(get_enabled_anonymous_secs "@ipsec_users") + [ -n "${ipsec_users}" ] && { + for _user in ${ipsec_users}; do + local u_enabled=$(uci -q get ${CONFIG}.${_user}.enabled) + [ "${u_enabled}" -eq 1 ] || continue + + local u_username=$(uci -q get ${CONFIG}.${_user}.username) + [ -n "${u_username}" ] || continue + + local u_password=$(uci -q get ${CONFIG}.${_user}.password) + [ -n "${u_password}" ] || continue + + echo "${u_username} : XAUTH '${u_password}'" >> ${IPSEC_SECRETS_FILE} + done + } + } + + [ "${vt_type}" = "L2TP" ] && { + cat >> ${IPSEC_CONN_FILE} <<-EOF + + conn L2TP-IKEv1-PSK + type=transport + keyexchange=ikev1 + authby=secret + leftprotoport=udp/1701 + left=%any + right=%any + rekey=no + forceencaps=yes + + EOF + mkdir -p ${L2TP_PATH} cat > ${L2TP_OPTIONS_FILE} <<-EOF name "l2tp-server" @@ -117,6 +264,7 @@ start() { lcp-echo-interval 60 connect-delay 5000 EOF + local l2tp_remoteip=$(uci -q get ${CONFIG}.@service[0].l2tp_remoteip) cat > ${L2TP_CONFIG_FILE} <<-EOF [global] port = 1701 @@ -125,7 +273,7 @@ start() { ;debug state = yes ;debug tunnel = yes [lns default] - ip range = ${vt_remoteip} + ip range = ${l2tp_remoteip} local ip = ${l2tp_localip} require chap = yes refuse pap = yes @@ -135,192 +283,49 @@ start() { pppoptfile = ${L2TP_OPTIONS_FILE} length bit = yes EOF - + + touch ${CHAP_SECRETS} local l2tp_users=$(get_enabled_anonymous_secs "@l2tp_users") [ -n "${l2tp_users}" ] && { for _user in ${l2tp_users}; do local u_enabled=$(uci -q get ${CONFIG}.${_user}.enabled) [ "${u_enabled}" -eq 1 ] || continue - + local u_username=$(uci -q get ${CONFIG}.${_user}.username) [ -n "${u_username}" ] || continue - + local u_password=$(uci -q get ${CONFIG}.${_user}.password) [ -n "${u_password}" ] || continue - + local u_ipaddress=$(uci -q get ${CONFIG}.${_user}.ipaddress) [ -n "${u_ipaddress}" ] || u_ipaddress="*" - + echo "${u_username} l2tp-server ${u_password} ${u_ipaddress}" >> ${CHAP_SECRETS} done } - unset user - + echo "ip-up-script /usr/share/xl2tpd/ip-up" >> ${L2TP_OPTIONS_FILE} echo "ip-down-script /usr/share/xl2tpd/ip-down" >> ${L2TP_OPTIONS_FILE} xl2tpd -c ${L2TP_CONFIG_FILE} -C ${L2TP_CONTROL_FILE} -D >${L2TP_LOG_FILE} 2>&1 & rm -f "/usr/lib/ipsec/libipsec.so.0" } - - cat > ${IPSEC_CONN_FILE} <<-EOF - # ipsec.conf - strongSwan IPsec configuration file - config setup - uniqueids=no - charondebug="cfg 2, dmn 2, ike 2, net 0" - - conn %default - dpdaction=clear - dpddelay=300s - rekey=no - left=%defaultroute - leftfirewall=yes - right=%any - ikelifetime=60m - keylife=20m - rekeymargin=3m - keyingtries=1 - auto=add - - ####################################### - # Default non L2TP Connections - ####################################### - - conn Non-L2TP - leftsubnet=0.0.0.0/0 - rightsubnet=${vt_serverip} - rightsourceip=${vt_serverip} - rightdns=${vt_gateway} - ike=aes128-sha1-modp2048,aes128-sha1-modp1024,3des-sha1-modp1024,3des-sha1-modp1536 - esp=aes128-sha1,3des-sha1 - - # Cisco IPSec - conn IKEv1-PSK-XAuth - also=Non-L2TP - keyexchange=ikev1 - leftauth=psk - rightauth=psk - rightauth2=xauth - - conn IKEv2-PSK - keyexchange=ikev2 - auto=add - left=%defaultroute - leftauth=psk - leftsubnet=0.0.0.0/0 - right=%any - rightauth=psk - rightsourceip=${vt_clientip} - - ike=aes128-sha1-modp2048,aes128-sha1-modp1024,3des-sha1-modp1024,3des-sha1-modp1536 - esp=aes128-sha1,3des-sha1 - dpdaction=clear - dpddelay=30s - rekey=no - compress=no - - conn ikev2-EAP - keyexchange=ikev2 - auto=add - fragmentation=yes - forceencaps=yes - - left=%defaultroute - leftid=@$vpn_domain - leftauth=pubkey - leftcert=${certs_name} - leftsendcert=always - leftsubnet=0.0.0.0/0 - - right=%any - rightid=%any - rightauth=eap-mschapv2 - eap_identity=%identity - rightsourceip=${vt_clientip} - rightdns=10.0.0.1 - - ike=aes256-sha256-modp1024,aes256-sha1-modp1024,aes128-sha256-modp1024,aes128-sha1-modp1024,aes256-sha256-modp2048,aes256-sha1-modp2048 - esp=aes256-sha256,aes128-sha256,aes256-sha1,aes128-sha1 - dpdaction=clear - dpddelay=30s - rekey=no - compress=no - - - - $ipsec_l2tp_config - EOF - - cat > /etc/ipsec.secrets <<-EOF - # /etc/ipsec.secrets - strongSwan IPsec secrets file - : PSK "$vt_secret" - : RSA /etc/ipsec.d/private/$private_key - EOF - - local ipsec_users=$(get_enabled_anonymous_secs "@ipsec_users") - [ -n "${ipsec_users}" ] && { - for _user in ${ipsec_users}; do - local u_enabled=$(uci -q get ${CONFIG}.${_user}.enabled) - [ "${u_enabled}" -eq 1 ] || continue - - local u_username=$(uci -q get ${CONFIG}.${_user}.username) - [ -n "${u_username}" ] || continue - - local u_password=$(uci -q get ${CONFIG}.${_user}.password) - [ -n "${u_password}" ] || continue - - echo "${u_username} : XAUTH '${u_password}'" >> ${IPSEC_SECRETS_FILE} - echo "${u_username} : EAP '${u_password}'" >> ${IPSEC_SECRETS_FILE} - done - } - unset user - ipt_rule add - + /usr/lib/ipsec/starter --daemon charon --nofork > /dev/null 2>&1 & gen_include - - uci -q batch <<-EOF >/dev/null - set network.ipsec_server.ipaddr="${vt_serverip%.*}.1" - commit network - EOF ifup ipsec_server > /dev/null 2>&1 } stop() { + init_config ifdown ipsec_server > /dev/null 2>&1 sed -i '/l2tp-server/d' ${CHAP_SECRETS} 2>/dev/null top -bn1 | grep "${L2TP_PATH}" | grep -v "grep" | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 rm -rf ${L2TP_PATH} - ps -w | grep "/usr/lib/ipsec" | grep -v "grep" | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 + busybox ps -w | grep "/usr/lib/ipsec" | grep -v "grep" | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 ipt_rule del rm -rf /var/etc/ipsecvpn.include ln -s "libipsec.so.0.0.0" "/usr/lib/ipsec/libipsec.so.0" >/dev/null 2>&1 } - -gen_iface_and_firewall() { - uci -q batch <<-EOF >/dev/null - delete network.ipsec_server - set network.ipsec_server=interface - set network.ipsec_server.device="ipsec0" - set network.ipsec_server.device="ipsec0" - set network.ipsec_server.proto="static" - set network.ipsec_server.ipaddr="${vt_serverip%.*}.1" - set network.ipsec_server.netmask="255.255.255.0" - commit network - - delete firewall.ipsecserver - set firewall.ipsecserver=zone - set firewall.ipsecserver.name="ipsecserver" - set firewall.ipsecserver.input="ACCEPT" - set firewall.ipsecserver.forward="ACCEPT" - set firewall.ipsecserver.output="ACCEPT" - set firewall.ipsecserver.network="ipsec_server" - commit firewall - EOF -} - -if [ -z "$(uci -q get network.ipsec_server)" ] || [ -z "$(uci -q get firewall.ipsecserver)" ]; then - gen_iface_and_firewall -fi diff --git a/luci-app-ipsec-server/root/etc/uci-defaults/luci-app-ipsec-server b/luci-app-ipsec-server/root/etc/uci-defaults/luci-app-ipsec-server old mode 100644 new mode 100755 index c22e3b498..6e01c4e1d --- a/luci-app-ipsec-server/root/etc/uci-defaults/luci-app-ipsec-server +++ b/luci-app-ipsec-server/root/etc/uci-defaults/luci-app-ipsec-server @@ -1,29 +1,19 @@ #!/bin/sh -[ ! -f "/usr/share/ucitrack/luci-app-luci-app-ipsec-server.json" ] && { - cat > /usr/share/ucitrack/luci-app-luci-app-ipsec-server.json << EEOF -{ - "config": "luci-app-ipsec-server", - "init": "luci-app-ipsec-server" -} -EEOF -} -rm -f /usr/share/ucitrack/luci-app-luci-app-ipsec-server.json uci -q batch <<-EOF >/dev/null delete firewall.luci_app_ipsec_server set firewall.luci_app_ipsec_server=include set firewall.luci_app_ipsec_server.type=script set firewall.luci_app_ipsec_server.path=/var/etc/ipsecvpn.include - set firewall.luci_app_ipsec_server.reload=1 - commit firewall EOF -uci -q batch <<-EOF >/dev/null - delete ucitrack.@luci-app-ipsec-server[-1] - add ucitrack luci-app-ipsec-server - set ucitrack.@luci-app-ipsec-server[-1].init=luci-app-ipsec-server - commit ucitrack -EOF +if [ -x "/sbin/fw3" ] && [ ! -L "/sbin/fw3" ]; then + uci -q set firewall.luci_app_ipsec_server.reload='1' +else + uci -q delete firewall.luci_app_ipsec_server.reload + uci -q delete firewall.luci_app_ipsec_server.fw4_compatible +fi +uci commit firewall /etc/init.d/ipsec disable 2>/dev/null /etc/init.d/ipsec stop 2>/dev/null diff --git a/luci-app-ipsec-server/root/usr/share/xl2tpd/ip-down b/luci-app-ipsec-server/root/usr/share/xl2tpd/ip-down new file mode 100755 index 000000000..9434e7615 --- /dev/null +++ b/luci-app-ipsec-server/root/usr/share/xl2tpd/ip-down @@ -0,0 +1,27 @@ +#!/bin/sh + +_LOGOUT_TIME="$(date "+%Y-%m-%d %H:%M:%S")" +CONFIG="luci-app-ipsec-server" +L2TP_PATH=/var/etc/xl2tpd +L2TP_SESSION_PATH=${L2TP_PATH}/session + +_USERNAME=${PEERNAME} +_IFACE=${1} +_TTY=${2} +_SPEED=${3} +_LOCALIP=${4} +_PEERIP=${5} +_REMOTEIP=${6} +_BYTES_SENT=${BYTES_SENT} +_BYTES_RCVD=${BYTES_RCVD} +_CONNECT_TIME=${CONNECT_TIME} + +rm -f ${L2TP_SESSION_PATH}/${_USERNAME}.${_IFACE} +rm -f /var/run/${_IFACE}.pid + +#可根据退出的账号自定义脚本,如静态路由表,组网等。 +SCRIPT="/usr/share/xl2tpd/ip-down.d/${_USERNAME}" +[ -s "$SCRIPT" ] && { + [ ! -x "$SCRIPT" ] && chmod 0755 "$SCRIPT" + "$SCRIPT" "$@" +} diff --git a/luci-app-ipsec-server/root/usr/share/xl2tpd/ip-up b/luci-app-ipsec-server/root/usr/share/xl2tpd/ip-up new file mode 100755 index 000000000..6109d037e --- /dev/null +++ b/luci-app-ipsec-server/root/usr/share/xl2tpd/ip-up @@ -0,0 +1,58 @@ +#!/bin/sh + +_LOGIN_TIME="$(date "+%Y-%m-%d %H:%M:%S")" +CONFIG="luci-app-ipsec-server" +L2TP_PATH=/var/etc/xl2tpd +L2TP_SESSION_PATH=${L2TP_PATH}/session + +_USERNAME=${PEERNAME} +_IFACE=${1} +_TTY=${2} +_SPEED=${3} +_LOCALIP=${4} +_PEERIP=${5} + +_PID=$(cat /var/run/${_IFACE}.pid 2>/dev/null) +_REMOTEIP=$(cat /var/etc/xl2tpd/xl2tpd.log 2>/dev/null | grep "PID: ${_PID}" | grep -o -E '([0-9]{1,3}[\.]){3}[0-9]{1,3}') + +mkdir -p ${L2TP_SESSION_PATH} + +cat <<-EOF > ${L2TP_SESSION_PATH}/${_USERNAME}.${_IFACE} + { + "username": "${_USERNAME}", + "interface": "${_IFACE}", + "tty": "${_TTY}", + "speed": "${_SPEED}", + "ip": "${_PEERIP}", + "remote_ip": "${_REMOTEIP}", + "pid": "${_PID}", + "login_time": "${_LOGIN_TIME}" + } +EOF + +#只能单用户使用 +cfgid=$(uci show ${CONFIG} | grep "@l2tp_users" | grep "\.username='${_USERNAME}'" | cut -d '.' -sf 2) +[ -n "$cfgid" ] && { + HAS_LOGIN=$(ls ${L2TP_SESSION_PATH} | grep "^${_USERNAME}\.ppp" | grep -v "${_IFACE}") + [ -n "$HAS_LOGIN" ] && { + #踢出之前的用户 + KO_IFACE=$(echo $HAS_LOGIN | awk -F '.' '{print $2}') + KO_PID=$(cat /var/run/${KO_IFACE}.pid 2>/dev/null) + [ -n "$KO_PID" ] && kill -9 ${KO_PID} >/dev/null 2>&1 + rm -f ${L2TP_SESSION_PATH}/${HAS_LOGIN} + rm -f /var/run/${KO_IFACE}.pid + } + routes=$(uci -q get ${CONFIG}.${cfgid}.routes) + [ -n "$routes" ] && { + for router in ${routes}; do + route add -net ${router} dev ${_IFACE} >/dev/null 2>&1 + done + } +} + +#可根据登录的账号自定义脚本,如组网、日志、限速、权限等特殊待遇。 +SCRIPT="/usr/share/xl2tpd/ip-up.d/${_USERNAME}" +[ -s "$SCRIPT" ] && { + [ ! -x "$SCRIPT" ] && chmod 0755 "$SCRIPT" + "$SCRIPT" "$@" +} diff --git a/luci-app-substore/CHANGELOG.md b/luci-app-substore/CHANGELOG.md index 130206f53..9198c14f4 100644 --- a/luci-app-substore/CHANGELOG.md +++ b/luci-app-substore/CHANGELOG.md @@ -2,6 +2,76 @@ All notable changes to this project will be documented in this file. +## [2.7.2-r8] - Surge / SurgeMac 一并丢弃 Hysteria v1(LEGACY_ISSUES 7.9 j) + +第六轮把 Loon 的 Hysteria v1 丢弃了,本轮把同一条证据链延伸到 Surge 家族。 + +### 修复 — 7.9(j):Surge / SurgeMac 丢弃 Hysteria v1 + +第六轮新发现 (j):Surge 家族的手册协议清单里写的也是 **"Hysteria 2"**, +`manual.nssurge.com/policies/hysteria.html` 是 404 而同目录的 `hysteria2.html` +存在 —— 与 Loon 同一情形,而本生成器仍会给这两家输出 `hysteria, ...` 行。 + +`FAMILY_CAPS.surge` / `FAMILY_CAPS.surgemac` 各加 `hysteria = false`,整条丢弃。 +被丢的仍然只有上一代 **v1**:Hysteria 2 三家都支持,不受影响。 + +**Surfboard 不在其中**:`getsurfboard.com` 的 `profile-format/proxy/external-proxy` +一节及其无斜杠形式都返回 404,无法证实它读不懂 v1。既然没有证据,就不替它丢弃 —— +能力表对 Surfboard 保持沉默(`caps[p] == nil` 一律保留)。测试里有一条断言钉住这点, +防止日后有人「顺手统一」。 + +### 测试 + +* `tests/protocol_registry_test.lua`:`hysteria` 的丢弃表加 `surge` / `surgemac` 两条。 +* `tests/output_formats_test.lua`:(j) 新增 5 条断言(surge 丢/留、组不含 v1、 + surgemac 丢、surfboard 留),并删去第六轮那条 `finding j pending` 的占位断言。 +* 反向验证:把 `hysteria = false` 从 surge / surgemac 撤掉,确认测试变红 + (output_formats 3 条 + registry 1 条),且 surfboard 那条**始终绿**(本就无关)。 +* 全套 **59 个测试文件、0 失败**。 + +## [2.7.2-r7] - Loon 丢弃 Hysteria v1、skip-cert-verify 改写 true(LEGACY_ISSUES 7.9 g/h) + +第五轮新发现里经决策要实施的两条。第三条(协议名大小写)决定保持现状。 + +### 修复 — 7.9(g):Loon 丢弃 Hysteria v1 + +Loon 的节点类型清单里只有 **Hysteria2**(`nsloon.app/docs/Node/`),本生成器却会把 +统一模型里的 hysteria v1 节点也输出成一条 Loon 读不懂的行。现在 +`FAMILY_CAPS.loon` 加 `hysteria = false`,整条丢弃 —— 与丢弃 wireguard / ssr +同一约定。**hysteria 2 是各家通用的**,被丢的只有上一代 v1。 + +顺带把 `surge_config` 的过滤逻辑改成数据驱动:此前是逐协议写死的 +`elseif p == "ssr" and not caps.ssr` / `elseif p == "vless" and not caps.vless`, +每加一个维度就要再加一条分支,**漏掉的那条默认是「保留」** —— 这正是本条缺陷的 +成因(`hysteria` 从没进过这张表)。现在是 `elseif caps[p] == false`,标一个键即可。 + +### 修复 — 7.9(h):`skip-cert-verify` 写 `true` 而不是 `1` + +Loon 文档的示例就是 `skip-cert-verify=false`,直接证明 `false` 是合法字面量、 +`true` 同理;而 Surge 手册的 TLS 页只写「Optional, boolean, default: false」, +两家的文档里都找不到 `1` 这个取值。解析端两种都认,回环不受影响。 + +### 保持现状 — 7.9(i):协议名大小写 + +Loon 文档写 `VMess` / `Hysteria2`(首字母大写),本生成器用小写。无证据显示小写会 +失败(Loon 定位是兼容 Surge 配置,Surge 手册的语法行就是小写),改动面却覆盖全部 +协议 —— 不改。 + +### 测试 + +* `tests/protocol_registry_test.lua`:`hysteria` 的丢弃表加 `loon` 一条。 +* `tests/output_formats_test.lua`:新增 (g) 5 条 / (h) 4 条断言(含回环)。 +* 反向验证:两条改动各自改回旧行为,确认测试变红。 +* 全套 **59 个测试文件、0 失败**。 + +### 本轮新发现(未实施,记录在 LEGACY_ISSUES) + +(j) **Surge 家族同样没有 Hysteria v1** —— `manual.nssurge.com/policies/hysteria.html` +返回 404 而 `hysteria2.html` 存在,手册的协议清单写的也是 "Hysteria 2"。也就是 +Surge / SurgeMac 的 `hysteria` 行同样读不懂。本轮**没有**顺手改 —— (g) 的决策只 +点名了 Loon。测试里有一条断言如实钉住当前行为,实施 (j) 时一并翻转。 +Surfboard 未获证据(其 external-proxy 文档页本轮返回 404)。 + ## [2.7.2-r6] - Loon / Surge 家族写法按官方文档对齐(LEGACY_ISSUES 7.9) `docs/LEGACY_ISSUES.md` 第七节 2.7.2 审计新发现的**第五轮**修复:(a)(b)(c)(d) diff --git a/luci-app-substore/Makefile b/luci-app-substore/Makefile index 837baa8ef..fb08ad78a 100644 --- a/luci-app-substore/Makefile +++ b/luci-app-substore/Makefile @@ -10,7 +10,7 @@ PKG_NAME:=luci-app-substore # 版本约定:每次提交 PKG_RELEASE +1(2.1.2-r1 ~ r9); # 达到 r10 时 PKG_VERSION 末位 +1(2.1.2 -> 2.1.3),PKG_RELEASE 重置为 1。 PKG_VERSION:=2.7.2 -PKG_RELEASE:=21 +PKG_RELEASE:=22 LUCI_DEPENDS:=+luci-lua-runtime +luci-compat diff --git a/luci-app-substore/docs/LEGACY_ISSUES.md b/luci-app-substore/docs/LEGACY_ISSUES.md index 5b90b63c0..9e4c66bb7 100644 --- a/luci-app-substore/docs/LEGACY_ISSUES.md +++ b/luci-app-substore/docs/LEGACY_ISSUES.md @@ -658,13 +658,13 @@ crontab 里,`substore-cron.sh` 会拿着已不存在的 id 反复执行,每 | 7.6 | 修复 | **已修复**(第一轮,见下) | | 7.7 | A:后端错误串 msgid 化 | **已实施**(第四轮,见下) | | 7.8 | A:删除 `age.lua` / `age_test.lua` | **已删除**(第一轮) | -| 7.9 | (a)(b)(c)(d) 全部实施;(e) 保持保守(含逗号整条丢弃) | **已实施**(第五轮,见下) | +| 7.9 | (a)(b)(c)(d) 全部实施;(e) 保持保守;(g)(h) 实施;(i) 保持现状;(j) 实施(Surge/SurgeMac) | **已实施**(第五、六、七轮,见下) | 7.5 / 7.6 的修复见本节末尾「7.5 / 7.6 修复记录」, 7.1 / 7.3 / 7.4 的实施见「第二轮修复记录」, 7.2 的实施见「第三轮修复记录」, 7.7 的实施见「第四轮修复记录」, -7.9 的实施见「第五轮修复记录」。 +7.9 的 (a)–(e) 见「第五轮修复记录」,(g)(h) 见「第六轮修复记录」,(j) 见「第七轮修复记录」。 | # | 问题 | 位置 | 依据 | 影响 | |---|---|---|---|---| @@ -676,7 +676,7 @@ crontab 里,`substore-cron.sh` 会拿着已不存在的 id 反复执行,每 | 7.6 | ~~Loon 的 `transport=ws` 未映射到 `net`~~ **已修复** | `parser_surge.parse_surge_line` 的 `transport=` 分支 | 只认 `ws=true`(Surge 旧写法)与 `obfs=ws`(QX);`nsloon.app/docs/Node/` 用 `transport=ws` + `path=` + `host=` | Loon 的 ws 节点导入后 `net=tcp`,`path` / `host` 全丢 → 导出到任何格式都按 tcp 连,握手失败**且不报错** | | 7.7 | ~~后端模块仍有 **113 处**硬编码中文字符串字面量(注释外)~~ **已实施** | `core.lua` 47 / `http.lua` 47 / `parser.lua` 9 / `util.lua` 5 / `output_wireguard_conf.lua` 3 / `node.lua` 2 | 扫描脚本(去注释后提取含 CJK / 全角的字符串字面量),见下 | 控制器文案已接入 i18n,但这些来自后端的失败原因经 `?err=` **原样**显示,英文界面下仍是中文。**已按 A 实施**:全部改为语言中立的英文 msgid,组合消息用 `msg.lua` 的分隔符机制,翻译只在显示边界发生(详见「第四轮修复记录」) | | 7.8 | ~~`root/usr/share/substore/age.lua` 与 `tests/age_test.lua` 未被 git 跟踪,且 `age.lua` 未被任何模块 `require`~~ **已删除** | 仓库根 | `grep -rn require` 无引用 | 未随包发布;留在工作区会被后续审计反复重新评估 | -| 7.9 | ~~Loon 的节点行仍有**多处**与官方文档不一致~~ **已实施** | `output_formats.surge_line` / `parser_surge.parse_surge_line` | `nsloon.app/docs/Node/`;`manual.nssurge.com`;`getsurfboard.com` | 逐条见下「7.9 的明细」。**已按 (a)(b)(c)(d) 全部实施、(e) 保持保守**:Loon 的 TLS 开关改 `over-tls`(Trojan 除外)、UDP 改 `udp`、Surge 家族补 `encrypt-method`、Loon 的 ss/ssr/hysteria2 改位置参数;解析端同步跟进以保住回环(详见「第五轮修复记录」)。实施中新发现三条待决策项,见下「第五轮新发现」 | +| 7.9 | ~~Loon 的节点行仍有**多处**与官方文档不一致~~ **已实施** | `output_formats.surge_line` / `parser_surge.parse_surge_line` | `nsloon.app/docs/Node/`;`manual.nssurge.com`;`getsurfboard.com` | 逐条见下「7.9 的明细」。**已按 (a)(b)(c)(d) 全部实施、(e) 保持保守**:Loon 的 TLS 开关改 `over-tls`(Trojan 除外)、UDP 改 `udp`、Surge 家族补 `encrypt-method`、Loon 的 ss/ssr/hysteria2 改位置参数;解析端同步跟进以保住回环(详见「第五轮修复记录」)。实施中新发现三条待决策项,见下「第五轮新发现」;其中 (g)(h) 已于第六轮实施、(i) 决定保持现状,另发现 (j) Surge 家族同样没有 Hysteria v1,**已于第七轮实施**(Surge / SurgeMac 丢弃,Surfboard 因无证据保留) | ### 7.7 的统计口径与例外(修复前必读) @@ -790,33 +790,76 @@ trojan 页同样写 `username=` / `password=`。所以 Surge / Surfboard / Surge 就整条丢弃」处理,并在 `tests/output_formats_test.lua` 钉了一条断言防止日后被 无意改成半截输出。 -### 第五轮新发现(**均未实施,待决策**) +### 第五轮新发现((g)(h) 第六轮已实施,(i) 保持现状,(j) 第七轮已实施) 第五轮实施 (a)–(d) 时对照 `nsloon.app/docs/Node/` 全文复核,另发现三处不一致。 每条都只陈述**文档写了什么**与**本实现写了什么**,不推测客户端的实际行为。 +第六轮按决策实施了 (g)(h),(i) 决定保持现状,实施过程中又发现 (j)(第七轮已实施)。 **(g) Loon 没有 Hysteria v1,本生成器会把 `hysteria` 行输出给它** —— Loon 文档的 节点类型清单里只有 **Hysteria2**,没有 Hysteria(v1)。而 `FAMILY_CAPS` 只按 vless / ssr 分客户端能力,`hysteria` 不在过滤表里,所以统一模型里的 hysteria v1 -节点会生成一条 Loon 读不懂的行。修法二选一:(i) 给 `FAMILY_CAPS.loon` 加 -`hysteria = false` 整条丢弃(与丢弃 wireguard / ssr 同一约定);(ii) 保持现状。 -**未实施** —— 需要先确认「Loon 是否真的不认 `hysteria`」以及 hysteria v1 节点在 -用户订阅里的实际占比。 +节点会生成一条 Loon 读不懂的行。 + +**→ 实施(第六轮)**:`FAMILY_CAPS.loon` 加 `hysteria = false`,整条丢弃 +(与丢弃 wireguard / ssr 同一约定)。同时把 `surge_config` 的过滤从逐协议写死的 +`elseif p == "ssr" and not caps.ssr` 改成数据驱动的 `elseif caps[p] == false` —— +每加一个维度就要再加一条分支,漏掉的那条默认是「保留」,正是本条缺陷的成因。 +**注意 hysteria 2 是各家通用的**,被丢的只有上一代 v1。 **(h) `skip-cert-verify` 写成 `1`,两家文档都没写这个取值** —— Loon 文档的示例 一律写 `skip-cert-verify=false`;Surge 手册的 TLS 页只写「Optional, boolean, default: false」,**没有**列出可接受的字面量。本生成器一律写 `skip-cert-verify=1`。`1` 是否被接受**无依据**。解析端两种都认(`parser_surge` -把非 `0` / 非 `false` 都当 true),所以回环不受影响,改与不改都不会破坏导入。 -**未实施** —— 改成 `true` 会动到所有 Surge 系输出的既有断言,收益(若 `1` 本就被 -接受则为零)尚不明确。 +把非 `0` / 非 `false` 都当 true),所以回环不受影响。 + +**→ 实施(第六轮)**:改写成 `skip-cert-verify=true`。判据是「Loon 自己的示例写了 +`false`,直接证明 `false` 是合法字面量、`true` 同理;而 `1` 在任何一家文档里都 +找不到」。既有测试里没有任何一条断言过**生成端**的 `skip-cert-verify=1` +(`anytls_reality_test` 里那一处是把 `=1` 当**输入**喂给解析器,仍然有效)。 **(i) 协议名大小写:Loon 文档一律首字母大写,本生成器一律小写** —— Loon 文档写 `Hysteria2` / `VMess` / `VLESS` / `Shadowsocks` / `AnyTLS`;本生成器输出 `hysteria2` / `vmess` / `vless` / `shadowsocks` / `anytls`。Loon 官方定位是 「兼容 Surge 配置」,而 Surge 手册的语法行用的是小写(`Name = vmess, , …`), -所以小写**很可能**被接受,但**没有文档明说**。**未实施** —— 这是全协议面的写法, -改动面大且依据不足;若日后确认大小写敏感,应改的是 Loon 分支的类型名映射。 +所以小写**很可能**被接受,但**没有文档明说**。 + +**→ 决定:保持现状(第六轮)**。没有任何证据显示小写会失败,而改动面覆盖全部 +协议;若日后确认大小写敏感,应改的是 Loon 分支的类型名映射。 + +### 第六轮新发现((j) **第七轮已实施**) + +**(j) Surge 家族同样没有 Hysteria v1,本生成器仍会把 `hysteria` 行输出给它们** —— +第五轮实施 (g) 时顺带复核了 Surge 的协议清单,发现与 Loon 是**同一个缺口**: + +* `manual.nssurge.com` 的 Proxy Protocols 一节列的是 **Hysteria 2**,没有 Hysteria; +* 直接的页面探针:`manual.nssurge.com/policies/hysteria.html` → **HTTP 404**, + 而 `manual.nssurge.com/policies/hysteria2.html` → **存在**(语法行 + `Proxy-Hysteria = hysteria2, 192.168.20.6, 443, password=pwd, download-bandwidth=100`)。 + +也就是说 Surge / SurgeMac 的 `hysteria`(v1)行同样是一条客户端读不懂的行。 +**(g) 的决策只点名了 Loon**,所以第六轮**没有**顺手改 Surge 家族 —— +`tests/output_formats_test.lua` 里有一条断言如实钉住了当前行为 +(`surge still keeps hysteria v1 (finding j pending)`),实施 (j) 时要一并翻转。 + +Surfboard 未获证据:`getsurfboard.com/docs/profile-format/proxy/external-proxy/` +与其无斜杠形式本轮都返回 404,没能取到协议清单。 + +修法:给 `FAMILY_CAPS` 的 surge / surfboard / surgemac 三行都加 `hysteria = false` +(过滤逻辑第六轮已改成数据驱动,加一个键即可),或把 `hysteria` 与 `wireguard` +一样无条件丢弃。 + +**→ 实施(第七轮,2.7.2-r8)**:`FAMILY_CAPS.surge` / `FAMILY_CAPS.surgemac` 各加 +`hysteria = false`,整条丢弃。被丢的仍然只有上一代 v1 —— Hysteria 2 三家都支持, +两者在 `surge_line` 里共用同一个输出分支,靠 `proto` 区分,容易误伤,测试里专门 +断言了「丢 v1、留 v2」。 + +**没有**给 `surfboard` 加这一条:文档 404、证据不足,能力表对它保持沉默 +(`caps[p] == nil` 一律保留)。上面那条「三家都加」的修法**没有**采纳 —— +第七轮的决策是「Surge + SurgeMac 一起丢」,Surfboard 维持现状。 +`tests/output_formats_test.lua` 里留了一条 `surfboard keeps hysteria v1 +(no evidence to drop)` 钉住这点,防止日后有人「顺手统一」。 +第六轮那条 `finding j pending` 的占位断言已按计划翻转成 `surge drops hysteria v1`。 ## 7.1 / 7.2 / 7.3 的实测探针 @@ -1308,10 +1351,116 @@ trojan / vmess / vless 一律用具名参数」—— 7.4-A 之后就不成立 全套 **59 个测试文件、0 失败**。 -### 本轮新发现(未实施,见上文「第五轮新发现」) +### 本轮新发现(见上文「第五轮新发现」) (g) Loon 没有 Hysteria v1;(h) `skip-cert-verify=1` 的取值无文档依据; -(i) 协议名大小写。三条都只记录,不动代码。 +(i) 协议名大小写。三条当时只记录、不动代码;(g)(h) 已在第六轮实施, +(i) 决定保持现状。 + +--- + +## 第六轮修复记录(7.9 的 g / h) + +第五轮新发现里经决策要实施的两条。(i) 决定保持现状,不动代码。 + +### (g) Loon 丢弃 Hysteria v1 + +```lua +loon = { vless = true, ssr = true, hysteria = false }, +``` + +判据:Loon 的节点类型清单里只有 **Hysteria2**(`nsloon.app/docs/Node/`)。 +**hysteria 2 是各家通用的**,被丢的只有上一代 v1 —— 两者共用同一个输出分支 +(`elseif proto == "hysteria2" or proto == "hysteria"`),最容易误伤。 + +**顺带把过滤逻辑改成数据驱动**。此前是逐协议写死的: + +```lua +elseif p == "ssr" and not caps.ssr then -- 旧 +elseif p == "vless" and not caps.vless then -- 旧 +``` + +```lua +elseif caps[p] == false then -- 新 +``` + +每加一个维度就要再加一条分支,**漏掉的那条默认是「保留」** —— 这正是本条缺陷的 +成因(`hysteria` 从没进过这张表)。改成查表后,`FAMILY_CAPS` 里标一个 `false` +即可,不会再有「忘了加分支」这种漏法。`caps[p]` 为 nil(表没表态)一律保留。 + +### (h) `skip-cert-verify` 改写 `true` + +```lua +if n["skip-cert-verify"] then e[#e + 1] = "skip-cert-verify=true" end +``` + +判据:Loon 文档的示例就是 `skip-cert-verify=false`,直接证明 `false` 是合法 +字面量、`true` 同理;而 `1` 在任何一家文档里都找不到(Surge 手册的 TLS 页只写 +「Optional, boolean, default: false」)。解析端两种都认,回环不受影响。 + +**断言面审计**:全仓库只有 `output_formats.lua` 一处写这个串;既有测试里**没有** +任何一条断言过生成端的 `skip-cert-verify=1` —— `anytls_reality_test.lua:151` 那处 +是把 `=1` 当**输入**喂给解析器,改写后仍然有效。 + +### (i) 协议名大小写:保持现状 + +无证据显示小写会失败(Loon 定位是兼容 Surge 配置,Surge 手册的语法行就是小写), +改动面却覆盖全部协议。不改。 + +### 测试 + +* `tests/protocol_registry_test.lua`:`hysteria` 的丢弃表加 `loon` 一条。 +* `tests/output_formats_test.lua`:新增 (g) 5 条 / (h) 4 条断言,含回环。 + 其中一条如实钉住 **Surge 家族当前仍输出 hysteria v1**(发现 (j),待决策), + 实施 (j) 时要一并翻转。 +* 反向验证:`FAMILY_CAPS.loon.hysteria` 改回不设 / `skip-cert-verify` 改回 `=1`, + 确认测试变红。 +* 全套 **59 个测试文件、0 失败**。 + +### 本轮新发现(见上文「第六轮新发现」) + +(j) Surge 家族同样没有 Hysteria v1(`manual.nssurge.com/policies/hysteria.html` +是 404、`hysteria2.html` 存在)。本轮**没有**顺手改 —— (g) 的决策只点名了 Loon。 +**→ 已于第七轮实施。** + +--- + +## 第七轮修复记录(7.9 的 j) + +第六轮新发现 (j):Surge 家族的手册协议清单里写的也是 **"Hysteria 2"**, +`manual.nssurge.com/policies/hysteria.html` 是 404 而同目录的 `hysteria2.html` +存在 —— 与 Loon 同一情形。本轮按决策把丢弃延伸到 Surge / SurgeMac。 + +### 改动 + +* `root/usr/share/substore/output_formats.lua` + * `FAMILY_CAPS.surge` / `FAMILY_CAPS.surgemac` 各加 `hysteria = false`。 + **Surfboard 不加** —— 文档 404、证据不足(决策是「Surge + SurgeMac 一起丢」)。 + * `FAMILY_CAPS` 上方的依据注释同步更新:Surge / SurgeMac 的清单补上「也没有 + Hysteria v1」及其探针证据;Surfboard 一段写明「未获证据,故保留」。 + * `surge_config` 的过滤注释补上 `hysteria` 一行,说明它同样按 `FAMILY_CAPS` + 分客户端、Surfboard 因本表沉默而保留。 + * 过滤**逻辑**本身不动:第六轮已改成数据驱动(`elseif caps[p] == false`), + 本轮只是往表里加键 —— 这正是第六轮那次重构要买的东西。 + +### 测试 + +* `tests/protocol_registry_test.lua`:`hysteria` 的丢弃表加 `surge` / `surgemac` + 两条,并注明 Surfboard 不在其中。 +* `tests/output_formats_test.lua`:(j) 新增 5 条断言 —— surge 丢 v1 / 留 v2 / + 组不含 v1、surgemac 丢 v1、**surfboard 留 v1(无证据丢弃)**;第六轮那条 + `surge still keeps hysteria v1 (finding j pending)` 占位断言已删除(被 + `surge drops hysteria v1` 取代)。 +* 反向验证:把 `hysteria = false` 从 surge / surgemac 两行撤掉 → **4 条 FAIL** + (output_formats 3 条 + registry 1 条),而 `surfboard keeps hysteria v1` + 那条**始终绿**(本轮没碰 Surfboard,它本就不该受影响)→ 说明断言确实指向 + 本轮改动,且没有误伤范围外的行为。还原后全绿。 +* 全套 **59 个测试文件、0 失败**。 + +### 本轮新发现 + +无。这是 7.9 决策链的最后一条;实施完 (j) 后 7.9 的 (a)–(e)、(g)–(j) 全部结清 +((f) 复核确认本来就对,(i) 决定保持现状)。 --- diff --git a/luci-app-substore/root/usr/share/substore/output_formats.lua b/luci-app-substore/root/usr/share/substore/output_formats.lua index 457483177..ada75e72f 100644 --- a/luci-app-substore/root/usr/share/substore/output_formats.lua +++ b/luci-app-substore/root/usr/share/substore/output_formats.lua @@ -74,7 +74,9 @@ local REALITY_FLAVORS = { loon = true } -- 各 Surge 系客户端对「非通用协议」的支持情况。 -- -- 通用协议(shadowsocks / vmess / trojan / socks5 / http / hysteria2 / tuic / --- anytls)各家都认;各家**不一致**的只有 VLESS 与 SSR 两个,所以只记这两个。 +-- anytls)各家都认;各家**不一致**的是 VLESS、SSR 与 Hysteria(v1)三个, +-- 所以只记这三个。注意 Hysteria **2** 是通用的,各家都支持;不一致的是它的 +-- 上一代 Hysteria v1。 -- 用一张表而不是给每个调用点传布尔参数:此前是 `supports_ssr` 一个布尔量, -- 加一个维度就要再加一个参数,调用点一多必然漏传(漏传的默认值还是「支持」, -- 于是导出里多出客户端读不懂的行,且不报错)。 @@ -83,18 +85,32 @@ local REALITY_FLAVORS = { loon = true } -- * Surge / SurgeMac:manual.nssurge.com 的 Proxy Protocols 一节只列 -- HTTP and HTTP/2、SOCKS5、Shadowsocks、Snell、VMess、Trojan、TUIC、 -- Hysteria 2、MASQUE、AnyTLS、Trust Tunnel、SSH、WireGuard、Tailscale --- —— 没有 VLESS,也没有 ShadowsocksR。 +-- —— 没有 VLESS、没有 ShadowsocksR,也没有 Hysteria(v1)。 +-- Hysteria v1 的证据:清单里写的是 "Hysteria 2",且同目录下 +-- manual.nssurge.com/policies/hysteria2.html 存在而 hysteria.html 是 404。 -- * Surfboard:getsurfboard.com 的 external-proxy 清单同样没有 VLESS / SSR。 --- * Loon:nsloon.app/docs/Node/ 有独立的 VLESS 与 ShadowsocksR 两节,都支持。 +-- (Surfboard 是否有 Hysteria v1 未获证据:该站文档的 +-- profile-format/proxy/external-proxy 一节及其无斜杠形式都返回 404。 +-- 既然无法证实「读不懂」,就不替它丢弃 —— 见 LEGACY_ISSUES 的 (j), +-- 该条已实施:Surge / SurgeMac 丢弃 Hysteria v1,Surfboard 按此保留。) +-- * Loon:nsloon.app/docs/Node/ 有独立的 VLESS 与 ShadowsocksR 两节,都支持; +-- 但节点类型清单里**只有 Hysteria2**,没有 Hysteria(v1)—— 与 Surge 家族 +-- 相反的两处之一(另一处是 VLESS / SSR)。 -- -- Egern **不在**这张表里:它的配置是 YAML,已由 output_egern.lua 单独实现 -- (7.2 已实施)。它的协议清单与 Surge 家族本就不同(有 VLESS 与 WireGuard, -- 没有 SSR 与 Hysteria v1),能力判定一并搬到了那个模块里。 +-- +-- 注意 hysteria 指的是**上一代 v1**:Hysteria 2 是各家通用的,被丢的只有 v1 +-- (两者在 surge_line 里共用同一个输出分支,靠 proto 区分)。 local FAMILY_CAPS = { - surge = { vless = false, ssr = false }, + surge = { vless = false, ssr = false, hysteria = false }, surfboard = { vless = false, ssr = false }, - surgemac = { vless = false, ssr = false }, - loon = { vless = true, ssr = true }, + surgemac = { vless = false, ssr = false, hysteria = false }, + -- Loon 支持 VLESS 与 SSR(与 Surge 家族相反),但没有 Hysteria v1 + -- (节点类型清单里只有 Hysteria2)。标 false 即整条丢弃,与丢弃 wireguard + -- 同一约定:不输出客户端读不懂的东西。 + loon = { vless = true, ssr = true, hysteria = false }, } -- 生成 Surge 风格代理行(Surge / Surfboard / SurgeMac / Loon 通用)。 @@ -339,7 +355,12 @@ function M.surge_line(n, flavor) return nil end - if n["skip-cert-verify"] then e[#e + 1] = "skip-cert-verify=1" end + -- 取值写 true/false,不写 1:Loon 文档的示例就是 `skip-cert-verify=false` + -- (nsloon.app/docs/Node/ 的 Hysteria2 一行),直接证明 false 是合法字面量、 + -- true 同理;而 Surge 手册的 TLS 页只写「Optional, boolean, default: false」, + -- 两家的文档里都找不到 `1` 这个取值。解析端两种都认 + -- (parser_surge 把非 "0" / 非 "false" 都当 true),所以回环不受影响。 + if n["skip-cert-verify"] then e[#e + 1] = "skip-cert-verify=true" end -- UDP 转发的参数名同样各家不同:Surge 家族是 udp-relay=true,Loon 是 udp=true -- (nsloon.app/docs/Node/ 的每个示例都写 udp=true,udp-relay 一次都没出现; -- 相关的另外两个开关叫 udp-over-tcp / udp-port)。 @@ -402,16 +423,19 @@ local function surge_config(nodes, group_name, flavor) local list = nodes or {} -- 过滤 Surge 家族无法用单行 [Proxy] 表达的协议: -- vless / ssr:按 FAMILY_CAPS 分客户端(Surge/Surfboard/SurgeMac 两个都不认) + -- hysteria:同样按 FAMILY_CAPS(Surge/SurgeMac 只有 Hysteria 2,没有 v1; + -- Surfboard 未获证据,本表没对它表态,故保留) -- wireguard:Surge 需专用多段 [WireGuard] 配置,单行无法表达,统一丢弃(不输出损坏行) local kept = {} for _, n in ipairs(list) do local p = (n.proto or ""):lower() if p == "wireguard" then -- drop - elseif p == "ssr" and not caps.ssr then - -- drop - elseif p == "vless" and not caps.vless then - -- drop + elseif caps[p] == false then + -- drop:能力表显式标了 false 的协议。写成数据驱动而不是逐个协议写 + -- `elseif p == "ssr" and not caps.ssr`:每加一个维度就要再加一条分支, + -- 漏掉的那条默认是「保留」,于是导出里多出客户端读不懂的行且不报错。 + -- (caps[p] 为 nil 表示「这张表没对它表态」,一律保留。) else kept[#kept + 1] = n end diff --git a/luci-app-substore/tests/output_formats_test.lua b/luci-app-substore/tests/output_formats_test.lua index 0c218cf5d..a590f0b61 100644 --- a/luci-app-substore/tests/output_formats_test.lua +++ b/luci-app-substore/tests/output_formats_test.lua @@ -262,5 +262,44 @@ local rt_tj = loon_roundtrip(tj_node) check("loon rt trojan password", rt_tj and rt_tj.password == "p") check("loon rt trojan security", rt_tj and rt_tj.security == "tls") +-- ---------- 7.9(g)+(j):Loon / Surge / SurgeMac 都没有 Hysteria v1 ---------- +-- Loon 的节点类型清单里只有 Hysteria2(nsloon.app/docs/Node/)。注意 hysteria +-- **2** 是各家通用的,被丢的只有上一代 v1 —— 两者共用同一个输出分支,容易误伤。 +local hy1_node = { proto = "hysteria", name = "H1", server = "1.1.1.1", port = 443, password = "p" } +local hy2_node = { proto = "hysteria2", name = "H2", server = "1.1.1.1", port = 443, password = "p" } +local loon_hy = fmts.to_loon({ hy1_node, hy2_node }, { name = "P" }) +check("loon drops hysteria v1", loon_hy:find("hysteria, 1.1.1.1", 1, true) == nil) +check("loon keeps hysteria2", loon_hy:find('hysteria2, 1.1.1.1, 443, "p"', 1, true) ~= nil) +-- 被丢弃的节点也不能留在 [Proxy Group] 的成员列表里(否则组引用一个不存在的代理) +check("loon group has no hysteria v1", loon_hy:find("H1", 1, true) == nil) +check("loon group keeps hysteria2", loon_hy:find("H2", 1, true) ~= nil) + +-- (j):Surge / SurgeMac 的手册协议清单写的也是 "Hysteria 2", +-- manual.nssurge.com/policies/hysteria.html 是 404 而 hysteria2.html 存在。 +-- Surfboard 未获证据(其文档 404),**不**跟着丢 —— 这条断言防止有人顺手扩大范围。 +local surge_hy = fmts.to_surge({ hy1_node, hy2_node }, { name = "P" }) +check("surge drops hysteria v1", surge_hy:find("hysteria, 1.1.1.1", 1, true) == nil) +check("surge keeps hysteria2", surge_hy:find("hysteria2, 1.1.1.1", 1, true) ~= nil) +check("surge group has no hysteria v1", surge_hy:find("H1", 1, true) == nil) +local mac_hy = fmts.to_surgemac({ hy1_node }, { name = "P" }) +check("surgemac drops hysteria v1", mac_hy:find("hysteria, 1.1.1.1", 1, true) == nil) +local sb_hy = fmts.to_surfboard({ hy1_node }, { name = "P" }) +check("surfboard keeps hysteria v1 (no evidence to drop)", + sb_hy:find("hysteria, 1.1.1.1", 1, true) ~= nil) + +-- ---------- 7.9(h):skip-cert-verify 写 true/false ---------- +-- Loon 文档的示例是 skip-cert-verify=false;Surge 手册只写 "boolean"。 +-- 两家文档里都找不到 `1` 这个取值。 +local scv_node = { proto = "hysteria2", name = "S", server = "1.1.1.1", port = 443, + password = "p", ["skip-cert-verify"] = true } +check("surge skip-cert-verify=true", + fmts.surge_line(scv_node, "surge"):find("skip-cert-verify=true", 1, true) ~= nil) +check("loon skip-cert-verify=true", + fmts.surge_line(scv_node, "loon"):find("skip-cert-verify=true", 1, true) ~= nil) +check("no skip-cert-verify=1", + fmts.surge_line(scv_node, "surge"):find("skip-cert-verify=1", 1, true) == nil) +local rt_scv = loon_roundtrip(scv_node) +check("loon rt skip-cert-verify", rt_scv and rt_scv["skip-cert-verify"] == true) + print(string.format("\n%d passed, %d failed", passed, failed)) os.exit(failed == 0 and 0 or 1) \ No newline at end of file diff --git a/luci-app-substore/tests/protocol_registry_test.lua b/luci-app-substore/tests/protocol_registry_test.lua index 18efb3a23..6a8a38065 100644 --- a/luci-app-substore/tests/protocol_registry_test.lua +++ b/luci-app-substore/tests/protocol_registry_test.lua @@ -103,9 +103,17 @@ local DROPPED = { v2ray = "V2RAY_PROTOS 无 hysteria2", wgconf = "wgconf 只收 wireguard" }, tuic = { clash = "原版 Clash 无 tuic", qx = "to_qx 无 tuic", v2ray = "V2RAY_PROTOS 无 tuic", wgconf = "wgconf 只收 wireguard" }, + -- hysteria v1 的第二、三处:Loon 的节点类型清单里同样只有 Hysteria2 + -- (nsloon.app/docs/Node/),Surge / SurgeMac 的手册协议清单写的也是 + -- "Hysteria 2"(且 policies/hysteria.html 是 404、hysteria2.html 存在)—— + -- 均由 FAMILY_CAPS..hysteria=false 整条丢弃。 + -- Surfboard **不在**此列:其文档 404,无法证实它读不懂 v1,故保留。 hysteria = { clash = "原版 Clash 无 hysteria", qx = "to_qx 无 hysteria", v2ray = "V2RAY_PROTOS 无 hysteria", wgconf = "wgconf 只收 wireguard", - egern = "Egern 只有 Hysteria2,没有 Hysteria v1" }, + egern = "Egern 只有 Hysteria2,没有 Hysteria v1", + loon = "Loon 只有 Hysteria2,没有 Hysteria v1", + surge = "Surge 只有 Hysteria2,没有 Hysteria v1", + surgemac = "SurgeMac 只有 Hysteria2,没有 Hysteria v1" }, -- wireguard:Surge 家族需要专用的多段 [WireGuard] 配置,单行 [Proxy] 表达不了, -- surge_config 统一丢弃;原版 Clash / qx / v2ray 同样不支持。 -- Egern **不在**此列:它的配置是 YAML,WireGuard 有独立的协议块 diff --git a/naiveproxy/Makefile b/naiveproxy/Makefile index e18cf3c11..c7581a690 100644 --- a/naiveproxy/Makefile +++ b/naiveproxy/Makefile @@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=naiveproxy PKG_VERSION:=154.0.8037.49 -PKG_RELEASE:=2 +PKG_RELEASE:=3 ARCH_PREBUILT:=$(ARCH_PACKAGES)