mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-31 14:25:13 +08:00
🍉 Sync 2026-10-06 20:57:57
This commit is contained in:
1 parent
d42f800eec
commit
6a9b13947b
23 files changed
+991
-507
No files matched your search
@@ -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
|
||||
|
||||
@@ -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'));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 Lienol <lawlienol@gmail.com>
|
||||
# Copyright (C) 2018-2026 Lienol <lawlienol@gmail.com>
|
||||
#
|
||||
# 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 <lawlienol@gmail.com>
|
||||
|
||||
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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"))
|
||||
|
||||
@@ -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
|
||||
@@ -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 '<span class="ipsec-server-status is-loading" role="status" aria-live="polite">' .. translate("Checking...") .. '</span>'
|
||||
return '<font class="ipsec-server_status"></font>'
|
||||
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 '<font class="l2tp_status"></font>'
|
||||
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('<a style="color: red">%s</a>', translate("L2TP/IPSec is not compatible with kernel-libipsec, which will disable this module."))
|
||||
end
|
||||
o:depends("type", "L2TP")
|
||||
end
|
||||
]]--
|
||||
end
|
||||
|
||||
return m
|
||||
@@ -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"))
|
||||
|
||||
@@ -1,61 +1,21 @@
|
||||
<style>
|
||||
.cbi-map {
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
|
||||
.ipsec-server-status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.ipsec-server-status::before {
|
||||
width: .65em;
|
||||
height: .65em;
|
||||
margin-right: .55em;
|
||||
border-radius: 50%;
|
||||
background: #6b7280;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.ipsec-server-status.is-running::before {
|
||||
background: #198754;
|
||||
}
|
||||
|
||||
.ipsec-server-status.is-stopped::before {
|
||||
background: #c0392b;
|
||||
}
|
||||
</style>
|
||||
|
||||
<% include("cbi/map") %>
|
||||
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
(function() {
|
||||
var status = document.getElementsByClassName('ipsec-server-status')[0];
|
||||
if (!status) {
|
||||
return;
|
||||
}
|
||||
|
||||
XHR.poll(5, '<%=luci.dispatcher.build_url("admin", "vpn", "ipsec-server", "status")%>', null,
|
||||
function(x, result) {
|
||||
var className;
|
||||
var label;
|
||||
|
||||
if (!result || typeof result.ipsec_status !== 'boolean') {
|
||||
className = 'ipsec-server-status is-unknown';
|
||||
label = '<%=translate("Status unavailable")%>';
|
||||
}
|
||||
else {
|
||||
className = 'ipsec-server-status ' + (result.ipsec_status ? 'is-running' : 'is-stopped');
|
||||
label = result.ipsec_status ? '<%=translate("RUNNING")%>' : '<%=translate("NOT RUNNING")%>';
|
||||
}
|
||||
|
||||
if (status.className !== className) {
|
||||
status.className = className;
|
||||
status.textContent = label;
|
||||
}
|
||||
XHR.poll(2, '<%=luci.dispatcher.build_url("admin", "vpn", "ipsec-server", "status")%>', null,
|
||||
function(x, result)
|
||||
{
|
||||
var ipsec_status = document.getElementsByClassName('ipsec-server_status')[0];
|
||||
if (ipsec_status) {
|
||||
ipsec_status.setAttribute("style","font-weight:bold;");
|
||||
ipsec_status.setAttribute("color",result.ipsec_status ? "green":"red");
|
||||
ipsec_status.innerHTML = result.ipsec_status?'<%=translate("RUNNING")%>':'<%=translate("NOT RUNNING")%>';
|
||||
}
|
||||
);
|
||||
})();
|
||||
var l2tp_status = document.getElementsByClassName('l2tp_status')[0];
|
||||
if (l2tp_status) {
|
||||
l2tp_status.setAttribute("style","font-weight:bold;");
|
||||
l2tp_status.setAttribute("color",result.l2tp_status ? "green":"red");
|
||||
l2tp_status.innerHTML = result.l2tp_status?'<%=translate("RUNNING")%>':'<%=translate("NOT RUNNING")%>';
|
||||
}
|
||||
}
|
||||
)
|
||||
//]]>
|
||||
</script>
|
||||
@@ -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 域名"
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
Regular → Executable
+7
-17
@@ -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
|
||||
|
||||
+27
@@ -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" "$@"
|
||||
}
|
||||
+58
@@ -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" "$@"
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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, <host>, …`),
|
||||
所以小写**很可能**被接受,但**没有文档明说**。**未实施** —— 这是全协议面的写法,
|
||||
改动面大且依据不足;若日后确认大小写敏感,应改的是 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) 决定保持现状)。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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.<flavor>.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 有独立的协议块
|
||||
|
||||
+1
-1
@@ -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)
|
||||
|
||||
|
||||
Reference in new issue
Block a user