🎈 Sync 2026-10-04 14:38:18

This commit is contained in:
github-actions[bot] committed 2026-10-04 14:38:18 +08:00
1 parent 0e6611f767
commit aba12667c5
88 files changed
+4600 -1458

No files matched your search

+2 -2
View File
@@ -6,11 +6,11 @@
include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI support for OpenVPN Server
LUCI_DEPENDS:=+openvpn-openssl +openvpn-easy-rsa
LUCI_DEPENDS:=+openvpn-openssl +openvpn-easy-rsa +ip-full +resolveip
LUCI_PKGARCH:=all
PKG_NAME:=luci-app-openvpn-server-client
PKG_VERSION:=6.0
PKG_RELEASE:=1
PKG_RELEASE:=2
MAINTAINER:=lunatickochiya <125438787@qq.com>
include $(TOPDIR)/feeds/luci/luci.mk
@@ -27,9 +27,57 @@ function index()
entry({"admin", "vpn", "openvpn-server","status"},call("act_status")).leaf=true
end
-- 内核是否提供 DCO (需要 ovpn.ko)
function dco_kernel_supported()
local fs = require "nixio.fs"
local krel = luci.sys.exec("uname -r"):gsub("%s+$", "")
return (fs.access("/sys/module/ovpn") or fs.access("/lib/modules/" .. krel .. "/ovpn.ko")) and true or false
end
-- openvpn 是否编译了 DCO 支持
function dco_build_supported()
local v = luci.sys.exec("openvpn --version 2>/dev/null | head -n1")
return v:find("%[DCO%]") ~= nil
end
-- DCO 是否真的在工作: 优先看内核模块引用计数, 退路是查运行日志
function dco_in_use()
local fs = require "nixio.fs"
local rc = fs.readfile("/sys/module/ovpn/refcnt")
if rc then
-- 注意: gsub 返回两个值, 必须加括号只取第一个
local n = tonumber((rc:gsub("%s+$", "")))
if n and n > 0 then
return true
end
end
local uci = require("luci.model.uci").cursor()
local logf = uci:get("openvpn", "myvpn", "log") or "/tmp/openvpn.log"
local hit = luci.sys.exec("grep -m1 -E 'DCO device .* opened|ovpn-dco device' '" .. logf .. "' 2>/dev/null")
return hit ~= nil and hit ~= ""
end
function act_status()
local e={}
e.running=luci.sys.call("pgrep openvpn >/dev/null")==0
luci.http.prepare_content("application/json")
luci.http.write_json(e)
local e = {}
e.running = luci.sys.call("pgrep openvpn >/dev/null") == 0
e.dco_kernel = dco_kernel_supported()
e.dco_build = dco_build_supported()
e.dco_supported = (e.dco_kernel and e.dco_build) and true or false
e.dco_active = dco_in_use()
local uci = require("luci.model.uci").cursor()
local ac = uci:get("openvpn", "myvpn", "allow_compression")
local lzo = uci:get("openvpn", "myvpn", "comp_lzo")
local dis = uci:get("openvpn", "myvpn", "disable_dco")
e.dco_disabled = (dis == "1")
e.allow_compression = ac or ""
e.comp_lzo = lzo or ""
-- 非法的压缩组合: allow-compression=no 与 comp_lzo 同时存在, openvpn 会拒绝启动
e.conflict = (ac == "no" and lzo ~= nil and lzo ~= "")
luci.http.prepare_content("application/json")
luci.http.write_json(e)
end
@@ -33,6 +33,42 @@ end
local major, minor, patch, lzo = get_openvpn_info()
-- DCO 是否真正可用: 需要内核模块 ovpn.ko (只有 openvpn 编译支持还不够)
local function dco_supported()
local fs = require "nixio.fs"
local krel = luci.sys.exec("uname -r"):gsub("%s+$", "")
return (fs.access("/sys/module/ovpn") or fs.access("/lib/modules/" .. krel .. "/ovpn.ko")) and true or false
end
local dco_ok = dco_supported()
-- 打开页面时顺手修复历史遗留的不一致配置:
-- 1) allow-compression=no 与 comp_lzo 不能共存 (openvpn 会拒绝启动)
-- 2) 启用 DCO 时不能有压缩 (否则 DCO 静默失效, 不加速)
if (major > 2) or (major == 2 and minor >= 5) or dco_ok then
local cur = require("luci.model.uci").cursor()
cur:foreach("openvpn", "openvpn", function(sec)
local name = sec[".name"]
local ac, lzo, dis = sec.allow_compression, sec.comp_lzo, sec.disable_dco
local need_fix = false
if dco_ok and dis ~= "1" then
-- DCO 已启用: 压缩必须关掉
if ac ~= "no" or (lzo ~= nil and lzo ~= "") then
need_fix = true
end
elseif ac == "no" and lzo then
-- 致命冲突: allow-compression=no 与 comp_lzo 共存
need_fix = true
end
if need_fix then
cur:set("openvpn", name, "allow_compression", "no")
cur:delete("openvpn", name, "comp_lzo")
cur:commit("openvpn")
end
end)
end
mp = Map("openvpn", "OpenVPN Server",translate("An easy config OpenVPN Server Web-UI"))
mp:section(SimpleSection).template = "openvpn/openvpn_status"
@@ -72,7 +108,16 @@ proto.default ="tcp4"
if (major > 2) or (major == 2 and minor >= 6) then
disable_dco = s:taboption("basic",Flag,"disable_dco", translate("disable dco"))
disable_dco.description = translate("Disabling DCO provides better compatibility but disables acceleration.")
if dco_ok then
disable_dco.description = translate("Better compatibility but no acceleration. This device supports DCO: uncheck to enable it (compression will be turned off automatically).")
else
disable_dco.description = translate("Better compatibility but no acceleration. This device does NOT support DCO (kernel module ovpn.ko is missing); unchecking has no effect.")
end
-- DCO 与压缩互斥: 启用 DCO 时必须关闭压缩, 否则 openvpn 会静默退回用户态,
-- DCO 等于没开。
-- 注意: Flag 取消勾选时 LuCI 不会调用 write, 所以实际修正放在 on_after_commit
-- (那个钩子每次保存都会执行, 已验证)。
end
if (major > 2) or (major == 2 and minor >= 5) then
@@ -84,6 +129,26 @@ allow_compression:value("no")
allow_compression.default="asym"
allow_compression.description = translate("Allow compression, DCO can only be used when set to NO. asym is compatible mode.")
-- 修复 allow_compression 与 comp_lzo 的冲突:
-- openvpn 2.5+ 在两者同时存在时会直接拒绝启动, 报:
-- "Options error: Compression is not allowed since allow-compression is set to 'stub-only'"
-- CBI 的 depends 只会把 comp_lzo 隐藏, 并不会删除 UCI 里已有的值,
-- 于是把 Allow Compression 改成 no 再保存, openvpn 就起不来了。
-- 这里在写入 allow_compression=no 时同步删除 comp_lzo。
local _allow_compression_write = allow_compression.write
allow_compression.write = function(self, section, value)
local c = require("luci.model.uci").cursor()
-- DCO 已启用时不允许再开压缩 (DCO 与压缩互斥), 直接强制为 no
if dco_ok and c:get("openvpn", section, "disable_dco") ~= "1" then
value = "no"
end
_allow_compression_write(self, section, value)
if value == "no" then
c:delete("openvpn", section, "comp_lzo")
c:commit("openvpn")
end
end
push_peer_info = s:taboption("basic",Flag,"push_peer_info", translate("push peer info"))
push_peer_info.description = translate("This will allow the server to know more info about the client like HWADDR, very useful for managing IoT devices.")
@@ -107,6 +172,17 @@ comp_lzo:depends("comp_lzo", "adaptive")
comp_lzo.default="adaptive"
end
comp_lzo.description = translate("Using LZO compression, it does not support versions above 2.5.X, does not support DCO; if your version number is greater than this version, select NO to disable it.")
-- 反向保护: 只要 allow_compression 已经是 no, 就绝不写入 comp_lzo
if (major > 2) or (major == 2 and minor >= 5) then
local _comp_lzo_write = comp_lzo.write
comp_lzo.write = function(self, section, value)
local ac = require("luci.model.uci").cursor():get("openvpn", section, "allow_compression")
if ac == "no" then
return
end
_comp_lzo_write(self, section, value)
end
end
end
auth_user_pass_verify = s:taboption("basic",Value,"auth_user_pass_verify", translate("user password verify"))
@@ -252,6 +328,20 @@ end
local comp_lzo_val = uci:get("openvpn", "myvpn", "comp_lzo")
function mp.on_after_commit(self)
-- 保存后统一修正 DCO / 压缩 的互斥关系。
-- Flag 取消勾选(启用 DCO)时 LuCI 不会走 write, 所以在这里处理
-- (on_after_commit 每次保存都会执行)。
local c = require("luci.model.uci").cursor()
if dco_ok and c:get("openvpn", "myvpn", "disable_dco") ~= "1" then
local ac = c:get("openvpn", "myvpn", "allow_compression")
local lzo = c:get("openvpn", "myvpn", "comp_lzo")
if ac ~= "no" or (lzo ~= nil and lzo ~= "") then
c:set("openvpn", "myvpn", "allow_compression", "no")
c:delete("openvpn", "myvpn", "comp_lzo")
c:commit("openvpn")
end
end
os.execute("uci set firewall.openvpn.dest_port=$(uci get openvpn.myvpn.port) && uci commit firewall && /etc/init.d/firewall restart")
os.execute("/etc/init.d/openvpn restart")
if comp_lzo_val == "no" then
@@ -48,6 +48,11 @@ o = sl:taboption("base", Flag, "enabled", translate("Enable Instance"))
o.default = o.disabled
o.rmempty = false
+o = sl:taboption("base", Flag, "tcp_cubic", translate("Use CUBIC for outer TCP"))
o.default = o.disabled
o.rmempty = false
o.description = translate("For TCP client profiles only. Uses the current route to each remote endpoint and keeps the system congestion control unchanged.")
o = sl:taboption("base", Value, "config", translate("Config File Path"))
o.readonly = true
o.placeholder = translate("Auto-generated based on instance identifier")
@@ -60,7 +65,7 @@ end
o = sl:taboption("config_file", TextValue, "_config_content")
o.title = translate("OpenVPN Configuration (.ovpn/.conf)")
o.description = translate("Paste the content of the .ovpn or .conf file here. This will be saved to the path shown in 'Base Settings'.")
o.description = translate("Paste the content of the .ovpn or .conf file here. This will be saved to the path shown in 'Base Settings'.") .. " " .. translate("If DCO hangs on this device, add disable-dco on its own line before reconnecting.")
o.rows = 15
o.wrap = "off"
o.rmempty = true
@@ -171,6 +176,12 @@ sp.on_remove = function(self, section)
end
m2.on_commit = function(self)
sys.call("/etc/init.d/openvpn-client-cubic enable >/dev/null 2>&1")
local route_result = sys.call("/etc/init.d/openvpn-client-cubic restart >/dev/null 2>&1")
if route_result ~= 0 then
self.message = translate("Could not prepare the CUBIC route for the OpenVPN client.")
return
end
local result = sys.call("/etc/init.d/openvpn reload")
if result ~= 0 then
self.message = translate("Failed to reload OpenVPN service.")
@@ -2,14 +2,44 @@
XHR.poll(3, '<%=url([[admin]], [[vpn]], [[openvpn-server]], [[status]])%>', null,
function(x, data) {
var tb = document.getElementById('openvpn_status');
if (data && tb) {
if (data.running) {
var links = '<em><b><font color=green>OpenVPN <%:RUNNING%></font></b></em>';
tb.innerHTML = links;
} else {
tb.innerHTML = '<em><b><font color=red>OpenVPN <%:NOT RUNNING%></font></b></em>';
}
if (!(data && tb)) return;
var s = '';
/* OpenVPN 运行状态 */
s += data.running
? '<em><b><font color="#27ae60">OpenVPN <%:RUNNING%></font></b></em>'
: '<em><b><font color="#e74c3c">OpenVPN <%:NOT RUNNING%></font></b></em>';
s += ' &nbsp;·&nbsp; ';
/* DCO 状态 */
if (data.dco_active) {
s += '<em><b><font color="#27ae60"><%:DCO ACTIVE%></font></b></em>';
} else if (!data.dco_supported) {
s += '<em><b><font color="#95a5a6"><%:DCO NOT SUPPORTED%></font></b></em>';
} else if (data.dco_disabled) {
s += '<em><b><font color="#95a5a6"><%:DCO DISABLED%></font></b></em>';
} else {
s += '<em><b><font color="#e67e22"><%:DCO INACTIVE (COMPRESSION CONFLICT)%></font></b></em>';
}
/* 压缩配置冲突 */
if (data.conflict) {
s += ' &nbsp;·&nbsp; <em><b><font color="#e74c3c"><%:COMPRESSION CONFIG CONFLICT%></font></b></em>';
}
/* 明细 */
var d = [];
d.push('<%:DCO kernel module%>: ' + (data.dco_kernel ? '✔' : '✘'));
d.push('<%:OpenVPN built with DCO%>: ' + (data.dco_build ? '✔' : '✘'));
d.push('allow-compression: ' + (data.allow_compression || '-'));
d.push('comp_lzo: ' + (data.comp_lzo || '-'));
if (data.conflict) {
d.push('<font color="#e74c3c"><%:allow-compression=no and comp_lzo cannot coexist, openvpn will refuse to start.%></font>');
}
tb.innerHTML = s + '<br><small style="color:#7f8c8d">' + d.join(' &nbsp;|&nbsp; ') + '</small>';
}
);
//]]>
@@ -19,4 +49,4 @@ XHR.poll(3, '<%=url([[admin]], [[vpn]], [[openvpn-server]], [[status]])%>', null
<p id="openvpn_status">
<em><%:Collecting data...%></em>
</p>
</fieldset>
</fieldset>
@@ -216,3 +216,104 @@ msgstr "这将允许服务器获取更多关于客户端的信息,比如硬件
msgid "Disabling DCO provides better compatibility but disables acceleration."
msgstr "禁用DCO可以提供更好的兼容性,但会关闭加速功能。"
# ---- DCO / compression related (added) ----
msgid "Allow Compression"
msgstr "允许压缩"
msgid "Allow compression, DCO can only be used when set to NO. asym is compatible mode."
msgstr "允许压缩。只有设为 NO 时才能使用 DCO 加速;asym 为兼容模式。"
msgid "Base Settings"
msgstr "基本设置"
msgid "Better compatibility but no acceleration. This device does NOT support DCO (kernel module ovpn.ko is missing); unchecking has no effect."
msgstr "兼容性更好但不走加速。本机不支持 DCO(缺少内核模块 ovpn.ko),取消勾选也不会生效。"
msgid "Better compatibility but no acceleration. This device supports DCO: uncheck to enable it (compression will be turned off automatically)."
msgstr "兼容性更好但不走加速。本机支持 DCO:取消勾选即可启用(会同时自动关闭压缩)。"
msgid "COMPRESSION CONFIG CONFLICT"
msgstr "压缩配置冲突"
msgid "Client"
msgstr "客户端"
msgid "Client Config File Content"
msgstr "客户端配置内容"
msgid "Collecting data..."
msgstr "正在收集数据…"
msgid "Config File Path"
msgstr "配置文件路径"
msgid "Configure authentication files for OpenVPN client instances."
msgstr "为 OpenVPN 客户端实例配置认证文件。"
msgid "DCO ACTIVE"
msgstr "DCO 加速中"
msgid "DCO DISABLED"
msgstr "DCO 已关闭"
msgid "DCO INACTIVE (COMPRESSION CONFLICT)"
msgstr "DCO 未生效(压缩冲突)"
msgid "DCO NOT SUPPORTED"
msgstr "DCO 不支持"
msgid "DCO kernel module"
msgstr "DCO 内核模块"
msgid "Failed to reload OpenVPN service."
msgstr "OpenVPN 服务重载失败。"
msgid "NOT RUNNING"
msgstr "未运行"
msgid "OpenVPN Cert"
msgstr "OpenVPN 证书"
msgid "OpenVPN Client Authentication"
msgstr "OpenVPN 客户端认证"
msgid "OpenVPN built with DCO"
msgstr "openvpn 编译支持 DCO"
msgid "RUNNING"
msgstr "运行中"
msgid "Start"
msgstr "启动"
msgid "Stop"
msgstr "停止"
msgid "allow-compression=no and comp_lzo cannot coexist, openvpn will refuse to start."
msgstr "allow-compression=no 与 comp_lzo 不能共存,openvpn 会拒绝启动。"
msgid "client-to-client"
msgstr "客户端互通(client-to-client)"
msgid "client_cert_not_required"
msgstr "客户端免证书(client_cert_not_required)"
msgid "comp_lzo"
msgstr "LZO 压缩(comp_lzo)"
msgid "disable dco"
msgstr "关闭 DCO"
msgid "duplicate_cn"
msgstr "允许重复 CN(duplicate_cn)"
msgid "proto"
msgstr "传输协议"
msgid "push peer info"
msgstr "推送客户端信息(push_peer_info)"
msgid "username_as_common_name"
msgstr "用户名作为 CN(username_as_common_name)"
@@ -1,41 +0,0 @@
config openvpn 'myvpn'
option enabled '0'
option proto 'tcp4'
option dev 'tun'
option topology 'subnet'
option server '10.8.0.0 255.255.255.0'
option comp_lzo 'adaptive'
option ca '/etc/openvpn/pki/ca.crt'
option dh '/etc/openvpn/pki/dh.pem'
option cert '/etc/openvpn/pki/server.crt'
option key '/etc/openvpn/pki/server.key'
option persist_key '1'
option persist_tun '1'
option max_clients '88'
option keepalive '10 120'
option verb '3'
option status '/var/log/openvpn_status.log'
option log '/tmp/openvpn.log'
option port '1194'
option ddns 'myserver.f3322.net'
option auth_user_pass_verify '/etc/openvpn/server/checkpsw.sh via-env'
option script_security '3'
list push 'route 192.168.9.0 255.255.255.0'
list push 'comp-lzo adaptive'
list push 'redirect-gateway def1 bypass-dhcp'
list push 'dhcp-option DNS 192.168.9.1'
config openvpn 'client1'
option enabled '0'
option config '/etc/openvpn/client1.conf'
config openvpn 'client2'
option enabled '0'
option config '/etc/openvpn/client2.conf'
config openvpnpassword 'pwd1'
option pwdfile '/etc/openvpn/pass.txt'
config openvpnpassword 'pwd2'
option pwdfile '/etc/openvpn/pass2.txt'
@@ -1,10 +0,0 @@
set_var EASYRSA_OPENSSL "openssl"
set_var EASYRSA_KEY_SIZE 2048
set_var EASYRSA_REQ_COUNTRY "lunatickochiya"
set_var EASYRSA_REQ_PROVINCE "github"
set_var EASYRSA_REQ_CITY "github"
set_var EASYRSA_REQ_ORG "github"
set_var EASYRSA_REQ_EMAIL "lunatickochiya@github.com"
set_var EASYRSA_REQ_OU "github"
set_var EASYRSA_CA_EXPIRE 3650
set_var EASYRSA_CERT_EXPIRE 3650
@@ -0,0 +1,5 @@
#!/bin/sh
case "$ACTION" in
ifup|ifupdate) /usr/libexec/openvpn-client-cubic-route update >/dev/null 2>&1 ;;
esac
@@ -0,0 +1,29 @@
#!/bin/sh /etc/rc.common
START=89
STOP=10
USE_PROCD=1
start_service() {
local attempt=0
/usr/libexec/openvpn-client-cubic-route enabled || return 0
# OpenVPN starts at 90. Wait for DNS and the uplink before it connects.
while ! /usr/libexec/openvpn-client-cubic-route update; do
attempt=$((attempt + 1))
[ "$attempt" -lt 12 ] || {
logger -t openvpn-client-cubic 'Could not install CUBIC route before OpenVPN startup'
return 1
}
sleep 2
done
procd_open_instance
procd_set_param command /usr/libexec/openvpn-client-cubic-route watch
procd_set_param respawn 3600 5 -1
procd_close_instance
}
stop_service() {
/usr/libexec/openvpn-client-cubic-route clear
}
@@ -1,34 +1,52 @@
#!/bin/sh
function rand_str() {
rand_str() {
(base64 /dev/urandom | tr -dc 'A-Za-z' | head -c $1) 2>/dev/null
}
function rand_str_upper() {
rand_str_upper() {
(rand_str $1 | tr 'a-z' 'A-Z') 2>/dev/null
}
function rand_str_lower() {
rand_str_lower() {
(rand_str $1 | tr 'A-Z' 'a-z') 2>/dev/null
}
function rand_easy_rsa_vars() {
set_easy_rsa_var() {
local name="$1"
local value="$2"
if grep -q "^[[:space:]]*set_var[[:space:]]\+$name[[:space:]]" /etc/easy-rsa/vars; then
sed -i "s|^[[:space:]]*set_var[[:space:]]\+$name[[:space:]].*$|set_var $name\t$value|" /etc/easy-rsa/vars
else
printf 'set_var %s\t%s\n' "$name" "$value" >> /etc/easy-rsa/vars
fi
}
init_easy_rsa_vars() {
touch /etc/easy-rsa/vars
set_easy_rsa_var EASYRSA_OPENSSL '"openssl"'
set_easy_rsa_var EASYRSA_KEY_SIZE '2048'
set_easy_rsa_var EASYRSA_CA_EXPIRE '3650'
set_easy_rsa_var EASYRSA_CERT_EXPIRE '3650'
}
rand_easy_rsa_vars() {
local KEY_PROVINCE="$(rand_str_upper 6)"
local KEY_CITY="$(rand_str 8)"
local KEY_ORG="$(rand_str 8)"
local KEY_EMAIL="$(rand_str_lower 8)@$(rand_str_lower 4).$(rand_str_lower 3)"
local KEY_OU="$(rand_str 8)"
sed -i \
-e "s/^[[:space:]]*set_var[[:space:]]\+EASYRSA_REQ_COUNTRY[[:space:]]\+\".*\"$/set_var EASYRSA_REQ_COUNTRY\t\"$KEY_PROVINCE\"/" \
-e "s/^[[:space:]]*set_var[[:space:]]\+EASYRSA_REQ_PROVINCE[[:space:]]\+\".*\"$/set_var EASYRSA_REQ_PROVINCE\t\"$KEY_CITY\"/" \
-e "s/^[[:space:]]*set_var[[:space:]]\+EASYRSA_REQ_CITY[[:space:]]\+\".*\"$/set_var EASYRSA_REQ_CITY\t\"$KEY_ORG\"/" \
-e "s/^[[:space:]]*set_var[[:space:]]\+EASYRSA_REQ_ORG[[:space:]]\+\".*\"$/set_var EASYRSA_REQ_ORG\t\"$KEY_ORG\"/" \
-e "s/^[[:space:]]*set_var[[:space:]]\+EASYRSA_REQ_EMAIL[[:space:]]\+\".*\"$/set_var EASYRSA_REQ_EMAIL\t\"$KEY_EMAIL\"/" \
-e "s/^[[:space:]]*set_var[[:space:]]\+EASYRSA_REQ_OU[[:space:]]\+\".*\"$/set_var EASYRSA_REQ_OU\t\"$KEY_OU\"/" \
/etc/easy-rsa/vars
set_easy_rsa_var EASYRSA_REQ_COUNTRY "\"$KEY_PROVINCE\""
set_easy_rsa_var EASYRSA_REQ_PROVINCE "\"$KEY_CITY\""
set_easy_rsa_var EASYRSA_REQ_CITY "\"$KEY_ORG\""
set_easy_rsa_var EASYRSA_REQ_ORG "\"$KEY_ORG\""
set_easy_rsa_var EASYRSA_REQ_EMAIL "\"$KEY_EMAIL\""
set_easy_rsa_var EASYRSA_REQ_OU "\"$KEY_OU\""
}
init_easy_rsa_vars
rand_easy_rsa_vars
@@ -0,0 +1,4 @@
#!/bin/sh
/etc/init.d/openvpn-client-cubic enable
exit 0
@@ -1,6 +1,70 @@
#!/bin/sh
grep -qv "myvpn" /etc/config/openvpn && grep -q "myvpn" /etc/config/openvpn-opkg && cp -f /etc/config/openvpn-opkg /etc/config/openvpn
if ! uci -q get openvpn.myvpn >/dev/null; then
uci -q batch <<-EOF >/dev/null
set openvpn.myvpn=openvpn
set openvpn.myvpn.enabled='0'
set openvpn.myvpn.proto='tcp4'
set openvpn.myvpn.dev='tun'
set openvpn.myvpn.topology='subnet'
set openvpn.myvpn.server='10.8.0.0 255.255.255.0'
set openvpn.myvpn.comp_lzo='adaptive'
set openvpn.myvpn.ca='/etc/openvpn/pki/ca.crt'
set openvpn.myvpn.dh='/etc/openvpn/pki/dh.pem'
set openvpn.myvpn.cert='/etc/openvpn/pki/server.crt'
set openvpn.myvpn.key='/etc/openvpn/pki/server.key'
set openvpn.myvpn.persist_key='1'
set openvpn.myvpn.persist_tun='1'
set openvpn.myvpn.max_clients='88'
set openvpn.myvpn.keepalive='10 120'
set openvpn.myvpn.verb='3'
set openvpn.myvpn.status='/var/log/openvpn_status.log'
set openvpn.myvpn.log='/tmp/openvpn.log'
set openvpn.myvpn.port='1194'
set openvpn.myvpn.ddns='myserver.f3322.net'
set openvpn.myvpn.auth_user_pass_verify='/etc/openvpn/server/checkpsw.sh via-env'
set openvpn.myvpn.script_security='3'
add_list openvpn.myvpn.push='route 192.168.9.0 255.255.255.0'
add_list openvpn.myvpn.push='comp-lzo adaptive'
add_list openvpn.myvpn.push='redirect-gateway def1 bypass-dhcp'
add_list openvpn.myvpn.push='dhcp-option DNS 192.168.9.1'
commit openvpn
EOF
fi
if ! uci -q get openvpn.client1 >/dev/null; then
uci -q batch <<-EOF >/dev/null
set openvpn.client1=openvpn
set openvpn.client1.enabled='0'
set openvpn.client1.config='/etc/openvpn/client1.conf'
commit openvpn
EOF
fi
if ! uci -q get openvpn.client2 >/dev/null; then
uci -q batch <<-EOF >/dev/null
set openvpn.client2=openvpn
set openvpn.client2.enabled='0'
set openvpn.client2.config='/etc/openvpn/client2.conf'
commit openvpn
EOF
fi
if ! uci -q get openvpn.pwd1 >/dev/null; then
uci -q batch <<-EOF >/dev/null
set openvpn.pwd1=openvpnpassword
set openvpn.pwd1.pwdfile='/etc/openvpn/pass.txt'
commit openvpn
EOF
fi
if ! uci -q get openvpn.pwd2 >/dev/null; then
uci -q batch <<-EOF >/dev/null
set openvpn.pwd2=openvpnpassword
set openvpn.pwd2.pwdfile='/etc/openvpn/pass2.txt'
commit openvpn
EOF
fi
openvpn_port="$(uci -q get openvpn.myvpn.port)"
[ -z "$openvpn_port" ] && openvpn_port=1194
@@ -0,0 +1,124 @@
#!/bin/sh
# Set CUBIC only for opted-in OpenVPN clients' outer TCP destinations.
umask 077
state=${OPENVPN_CLIENT_CUBIC_STATE:-/tmp/openvpn-client-cubic-routes}
sections() {
uci -q show openvpn 2>/dev/null |
sed -n 's/^openvpn\.\(client[A-Za-z0-9_-]*\)=openvpn$/\1/p'
}
client_enabled() {
[ "$(uci -q get "openvpn.$1.enabled")" = 1 ] &&
[ "$(uci -q get "openvpn.$1.tcp_cubic")" = 1 ]
}
has_clients() {
for section in $(sections); do
client_enabled "$section" && return 0
done
return 1
}
delete_old_routes() {
[ -f "$state" ] || return 0
while IFS='|' read -r address via device; do
[ -n "$address" ] && [ -n "$device" ] || continue
if [ -n "$via" ]; then
ip -4 route del "$address/32" via "$via" dev "$device" 2>/dev/null
else
ip -4 route del "$address/32" dev "$device" 2>/dev/null
fi
done < "$state"
return 0
}
add_route() {
local address=$1 route via device
route=$(ip -4 route get "$address" 2>/dev/null) || return 1
set -- $route
while [ "$#" -gt 1 ]; do
case "$1" in
via) via=$2; shift 2 ;;
dev) device=$2; shift 2 ;;
*) shift ;;
esac
done
[ -n "$device" ] && [ "$device" != lo ] || return 1
if [ -n "$via" ]; then
ip -4 route replace "$address/32" via "$via" dev "$device" congctl cubic || return 1
else
ip -4 route replace "$address/32" dev "$device" congctl cubic || return 1
fi
printf '%s|%s|%s\n' "$address" "$via" "$device" >> "$new_state"
}
update_routes() {
local section conf host addresses address failed=0 desired new_state
desired=$(mktemp /tmp/openvpn-client-cubic-desired.XXXXXX) || return 1
new_state=$(mktemp /tmp/openvpn-client-cubic-state.XXXXXX) || {
rm -f "$desired"
return 1
}
for section in $(sections); do
client_enabled "$section" || continue
conf=$(uci -q get "openvpn.$section.config")
if [ ! -r "$conf" ]; then
logger -t openvpn-client-cubic "Cannot read config for $section"
failed=1
continue
fi
# An outer TCP congestion controller has no effect on UDP clients.
grep -Eiq '^[[:space:]]*proto[[:space:]]+tcp' "$conf" || continue
for host in $(awk '$1 == "remote" { gsub(/["\047]/, "", $2); print $2 }' "$conf"); do
case "$host" in
''|*[!0-9.]*) addresses=$(resolveip -4 -t 5 "$host" 2>/dev/null) ;;
*) addresses=$host ;;
esac
if [ -z "$addresses" ]; then
logger -t openvpn-client-cubic "Cannot resolve $host for $section"
failed=1
continue
fi
for address in $addresses; do
case "$address" in
*[!0-9.]*|'') continue ;;
esac
printf '%s\n' "$address" >> "$desired"
done
done
done
if [ "$failed" -ne 0 ]; then
rm -f "$desired" "$new_state"
return 1
fi
delete_old_routes
sort -u "$desired" | while IFS= read -r address; do
[ -n "$address" ] || continue
add_route "$address" || exit 1
done
failed=$?
rm -f "$desired"
if [ "$failed" -eq 0 ]; then
mv -f "$new_state" "$state"
else
rm -f "$new_state"
fi
return "$failed"
}
case "$1" in
enabled) has_clients ;;
update) update_routes ;;
clear) delete_old_routes; rm -f "$state" ;;
watch)
while sleep 300; do
update_routes || logger -t openvpn-client-cubic 'Could not refresh CUBIC routes'
done
;;
*) echo "Usage: $0 {enabled|update|clear|watch}" >&2; exit 2 ;;
esac
@@ -1,11 +0,0 @@
{
"luci-app-openvpn-server-client": {
"description": "Grant UCI access for luci-app-openvpn-server-client",
"read": {
"uci": [ "openvpn" ]
},
"write": {
"uci": [ "openvpn" ]
}
}
}