mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-27 02:42:51 +08:00
🚀 Sync 2026-10-06 13:56:25
This commit is contained in:
1 parent
d63f87a572
commit
3b4244f7de
14 files changed
+1034
-410
No files matched your search
@@ -0,0 +1,2 @@
|
||||
*.apk
|
||||
*.ipk
|
||||
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -1,9 +1,18 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Original: ImmortalWrt LuCI applications/luci-app-ipsec-vpnd
|
||||
# Maintained by: szwjp <szwjp@users.noreply.github.com>
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
LUCI_TITLE:=LuCI support for IPSec VPN Server (IKEv1 with PSK and Xauth)
|
||||
LUCI_DEPENDS:=+strongswan +strongswan-minimal +strongswan-mod-xauth-generic +strongswan-mod-kernel-libipsec +kmod-tun
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
PKG_NAME:=luci-app-ipsec-vpnd
|
||||
PKG_VERSION:=27.906.25.12
|
||||
PKG_RELEASE:=2
|
||||
|
||||
define Package/luci-app-ipsec-vpnd/conffiles
|
||||
/etc/config/ipsec-vpnd
|
||||
endef
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
LuCI - Lua Configuration Interface
|
||||
Copyright 2008 Steven Barth <steven@midlink.org>
|
||||
Copyright 2008 Jo-Philipp Wich <jow@openwrt.org>
|
||||
Licensed under the Apache License, Version 2.0.
|
||||
|
||||
Contains code from:
|
||||
coxpcall - Copyright 2005 - Kepler Project (www.keplerproject.org)
|
||||
ltn12/luasocket - Copyright 2004-2007 Diego Nehab
|
||||
@@ -0,0 +1,146 @@
|
||||
# luci-app-ipsec-vpnd
|
||||
|
||||
LuCI 界面,给运行在 OpenWrt / ImmortalWrt 上的 strongSwan IPsec VPN 服务做配置管理。
|
||||
|
||||
IKEv1 + PSK + Xauth(账号密码),目标用户是家用 / 小公司路由器场景——LAN 后面的客户端用用户名密码拨号,拿到一个独立虚拟网段的 IP。
|
||||
|
||||
> **维护者说明**:本项目维护者本人既在自家路由器上跑,也把编译好的 .apk/.ipk 同步发到 community / 朋友分发渠道。下面“构建”与“安装”分别对应这两种使用方式。
|
||||
|
||||
---
|
||||
|
||||
## 功能
|
||||
|
||||
- **VPN 服务启停**(uhttpd → LuCI → procd 守护进程控制 strongSwan 的 `starter`)
|
||||
- **账号 / 密码 / PSK 配置** 写在 `/etc/config/ipsec-vpnd`,运行时被 init 脚本翻译成 strongSwan 的 `ipsec.conf` + `ipsec.secrets`
|
||||
- **首次安装自动初始化**:建 `network.VPN` 设备(绑 `ipsec0`)+ `firewall.ike/ipsec/ah/esp` 入站规则 + `firewall.VPN` zone + 端口转发;只跑一次,`/etc/config/ipsec-vpnd` 里的 `initialized` 标记防止升级时覆盖用户后续自定义
|
||||
- **和系统自带的 `ipsec` 服务冲突处理**:每次启动前都 `stop && disable` 系统那个,防止 charon 抢 UDP/500、4500
|
||||
- **菜单位置**:`VPN → IPSec VPN Server`(order 30)
|
||||
|
||||
## 界面与配置项
|
||||
|
||||
UCI 路径 `ipsec-vpnd.@ipsec[0]`,字段:
|
||||
|
||||
| 字段 | 默认 | 说明 |
|
||||
|---|---|---|
|
||||
| `enabled` | `0` | 总开关 |
|
||||
| `clientip` | `10.9.8.10/24` | 客户端地址池(CIDR),用 strongSwan 的 `rightsourceip` 推给拨号端 |
|
||||
| `clientdns` | `10.9.8.1` | 推给客户端的 DNS(即 VPN 网关 IP) |
|
||||
| `account` | `vpnuser` | Xauth 用户名 |
|
||||
| `password` | `changeme` | Xauth 密码 |
|
||||
| `secret` | `changeme` | IKE PSK 预共享密钥 |
|
||||
|
||||
VPN 客户端拨号后会拿到 `clientip` 池里一个地址,`clientdns` 指向 VPN 网关(路由器上的 `ipsec0` IP,初始化脚本里按 LAN 前两段、第三段 +1 自动算出来)。
|
||||
|
||||
## 依赖
|
||||
|
||||
`Makefile` 里 `LUCI_DEPENDS` 列的运行时依赖:
|
||||
|
||||
```
|
||||
+strongswan-minimal
|
||||
+strongswan-mod-xauth-generic
|
||||
+strongswan-mod-kernel-libipsec
|
||||
+strongswan-mod-des
|
||||
+kmod-tun
|
||||
```
|
||||
|
||||
**这是 IPsec VPN 服务能起来的前提。**安装本包前请确认这些包在固件里都有,否则 LuCI 界面能开,但服务起不来。
|
||||
|
||||
### 关于 ImmortalWrt 25.12 系列(重要)
|
||||
|
||||
ImmortalWrt **25.12.0 / 25.12.1 的官方软件源在全架构上都没带 strongswan**(OpenWrt 上游 25.12 源有,ImmortalWrt 24.10 源也有,唯独 ImmortalWrt 25.12 系列缺)。如果你的固件就是 ImmortalWrt 25.12 系列且只挂着官方源,`opkg install luci-app-ipsec-vpnd` 会卡在依赖解析。
|
||||
|
||||
可行方案(任选):
|
||||
|
||||
- **换 OpenWrt 上游源**:`/etc/opkg/distfeeds.conf` 里加 `src/gz openwrt_core https://downloads.openwrt.org/releases/25.12.1/targets/.../packages/`
|
||||
- **换同版本但带 strongswan 的 ImmortalWrt fork 源**
|
||||
- **用 szwjp/luci 这种社区源**,里头把 strongswan 也带上了
|
||||
|
||||
## 安装
|
||||
|
||||
### 普通用户(装现成的 .apk)
|
||||
|
||||
从本仓库的 `Actions → Build → 最新一次成功 run → Artifacts → ipsec-vpnd-packages` 下载 `luci-app-ipsec-vpnd_*.apk` 和 `luci-i18n-ipsec-vpnd-zh-cn_*.apk`(中文界面,可选),用 scp / WinSCP 推到路由器:
|
||||
|
||||
```sh
|
||||
opkg install luci-app-ipsec-vpnd_*.apk
|
||||
opkg install luci-i18n-ipsec-vpnd-zh-cn_*.apk # 可选
|
||||
```
|
||||
|
||||
第一次安装会跑 `uci-defaults/luci-ipsec-vpnd` 一次性初始化(建 network/firewall/zone + 删系统 ipsec 服务),然后 LuCI 刷新一下就能在 `VPN → IPSec VPN Server` 看到。
|
||||
|
||||
或者从 release 装(GitHub Actions 自动把每次 main 提交 publish 到 `IPSec VPND Latest Build` release):
|
||||
|
||||
```sh
|
||||
opkg install https://github.com/szwjp/luci-app-ipsec-vpnd/releases/download/luci-app-ipsec-vpnd/luci-app-ipsec-vpnd_27.906.25.12-r1_all.apk
|
||||
```
|
||||
|
||||
### 自己构建(社区分发 / 自己做 firmware)
|
||||
|
||||
走仓库的 GitHub Actions **Build** workflow:push 到 `main` 触发(或手动 dispatch),会在 GitHub Actions 里产 `luci-app-ipsec-vpnd-*.apk` + `luci-i18n-ipsec-vpnd-zh-cn-*.apk` 两个 apk 和对应的 ipk,并通过 `softprops/action-gh-release` publish 到 latest release。本地要复现的话:
|
||||
|
||||
```sh
|
||||
# Ubuntu 20.04+,装上 apk-tools / meson / ninja / fakeroot
|
||||
sudo apt-get install -y build-essential meson ninja-build fakeroot
|
||||
|
||||
# 用仓库自带的 .github/build-pkg.sh
|
||||
fakeroot bash .github/build-pkg.sh apk 1 # 出 luci-app-ipsec-vpnd_*.apk + luci-i18n-ipsec-vpnd-zh-cn-*.apk
|
||||
fakeroot bash .github/build-pkg.sh ipk 1 # 出同名 .ipk
|
||||
ls -l *.apk *.ipk
|
||||
```
|
||||
|
||||
`build-pkg.sh` 用了仓库里那个**专为本包定制的最小工具链**(apk-tools 自编译 + po2lmo 自编译 + ipkg-build),不依赖完整 OpenWrt SDK 编译环境;这就是为什么 `build.yml` 跑得很快(~40s)。所有 actions 都用 Node 24 版(`actions/checkout@v5` / `actions/upload-artifact@v6` / `softprops/action-gh-release@v3`)。
|
||||
|
||||
## CI
|
||||
|
||||
`.github/workflows/` 下两个 workflow:
|
||||
|
||||
- **`build.yml`**(生产 workflow):push 到 main / PR / 手动 dispatch 触发。9 步全绿、零 annotation,~40s 出包并 publish 到 release。是这个项目的命脉。
|
||||
- **`cleanup-old-workflow.yml`**(工具 workflow):手动 dispatch,按你给的天数阈值删 `actions/runs` 里的 completed run,防止 Actions 列表太长。文件名带 `.yml` 后缀(之前没有后缀,GitHub Actions 静默忽略——这种坑踩过一次记到 memory 里了)。
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
.
|
||||
├── Makefile # OpenWrt 包定义:LUCI_TITLE / LUCI_DEPENDS / PKG_*
|
||||
├── htdocs/luci-static/resources/view/ipsec-vpnd.js
|
||||
│ # LuCI web UI(cbid 表格 + 启停按钮)
|
||||
├── po/
|
||||
│ ├── templates/ipsec-vpnd.pot # gettext 模板
|
||||
│ └── zh_Hans/ipsec-vpnd.po # 简体中文翻译
|
||||
├── root/
|
||||
│ ├── etc/config/ipsec-vpnd # UCI 默认配置(服务默认 enabled=0)
|
||||
│ ├── etc/init.d/ipsec-vpnd # procd init:写 ipsec.conf/secrets、启 starter
|
||||
│ ├── etc/uci-defaults/luci-ipsec-vpnd
|
||||
│ │ # 首次安装一次性:建 network.VPN + firewall
|
||||
│ ├── usr/share/luci/menu.d/luci-app-ipsec-vpnd.json
|
||||
│ │ # LuCI 菜单注册(VPN → IPSec VPN Server)
|
||||
│ └── usr/share/rpcd/acl.d/luci-app-ipsec-vpnd.json
|
||||
│ # rpcd ACL:放行 luci-app-ipsec-vpnd 读写 /etc/config/ipsec-vpnd
|
||||
├── .github/workflows/
|
||||
│ ├── build.yml
|
||||
│ └── cleanup-old-workflow.yml
|
||||
├── LICENSE # Apache-2.0
|
||||
└── README.md # 本文件
|
||||
```
|
||||
|
||||
## 维护
|
||||
|
||||
- 上游:fork 自 ImmortalWrt LuCI `applications/luci-app-ipsec-vpnd`
|
||||
- 当前 main:自维护分支,修了上游一些配置假设(详见 commit 历史)
|
||||
- 同步:fix 在 main 上验证后通过 PR 回上游
|
||||
- 不要把生成的 `.apk` / `.ipk` 提交到仓库(`.gitignore` 已过滤)
|
||||
|
||||
## 故障排查
|
||||
|
||||
| 现象 | 排查 |
|
||||
|---|---|
|
||||
| LuCI 菜单看不到 IPSec VPN Server | LuCI → 系统 → 软件包,确认 `luci-app-ipsec-vpnd` 已装;刷新 LuCI 缓存(`rm -f /tmp/luci-indexcache`) |
|
||||
| 服务起不来,日志说 "starter: no such file" | `strongswan-minimal` 没装;装上 |
|
||||
| 客户端拨不上,日志说 "no IKE config" | 看 `/etc/config/ipsec-vpnd` 的 `enabled` 是不是 `1`;看 init 日志(`logread \| grep ipsec`) |
|
||||
| 系统自带 ipsec 又被启起来抢端口 | 升级 strongswan 包后可能要再跑一次 `uci-defaults/luci-ipsec-vpnd`——本包 init 脚本每次启动前都会重新 stop+disable 系统那个,但只在服务 enabled 时跑 |
|
||||
| ImmortalWrt 25.12 装不上(缺依赖) | 见上面“关于 ImmortalWrt 25.12 系列” |
|
||||
| GitHub Actions 出包失败 | 看 `Build` workflow 的 step 日志;常见坑在 `actions/cache` 没命中、host 工具链 apt 装失败这些 |
|
||||
|
||||
## 许可证
|
||||
|
||||
Apache-2.0。详见 `LICENSE`。
|
||||
@@ -17,11 +17,34 @@ const callServiceList = rpc.declare({
|
||||
expect: { '': {} }
|
||||
});
|
||||
|
||||
const callServiceInit = rpc.declare({
|
||||
object: 'rc',
|
||||
method: 'init',
|
||||
params: ['name', 'action']
|
||||
});
|
||||
|
||||
const callIpsecSessions = rpc.declare({
|
||||
object: 'ipsec-vpnd',
|
||||
method: 'sessions',
|
||||
expect: { '': {} }
|
||||
});
|
||||
|
||||
const callClearHistory = rpc.declare({
|
||||
object: 'ipsec-vpnd',
|
||||
method: 'clear_history',
|
||||
expect: { '': {} }
|
||||
});
|
||||
|
||||
function getServiceStatus() {
|
||||
return L.resolveDefault(callServiceList('ipsec-vpnd'), {}).then(function(res) {
|
||||
let isRunning = false;
|
||||
try {
|
||||
isRunning = res['ipsec-vpnd']['instances']['instance1']['running'];
|
||||
// procd_open_instance 未指定实例名时,procd 按 init 脚本 basename
|
||||
// 命名实例为 'ipsec-vpnd';遍历全部实例,任一 running 即视为运行
|
||||
let instances = res['ipsec-vpnd']['instances'] || {};
|
||||
for (let name in instances)
|
||||
if (instances[name]['running'])
|
||||
isRunning = true;
|
||||
} catch (e) { }
|
||||
return isRunning;
|
||||
});
|
||||
@@ -43,7 +66,7 @@ return view.extend({
|
||||
let m, s, o;
|
||||
|
||||
m = new form.Map('ipsec-vpnd', _('IPSec VPN Server'),
|
||||
_('IPSec VPN connectivity using the native built-in VPN Client on iOS or Andriod (IKEv1 with PSK and Xauth)'));
|
||||
_('IPSec VPN connectivity using the native built-in VPN Client on iOS or Android (IKEv1 with PSK and Xauth)'));
|
||||
|
||||
s = m.section(form.TypedSection);
|
||||
s.anonymous = true;
|
||||
@@ -67,12 +90,12 @@ return view.extend({
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.option(form.Value, 'clientip', _('VPN Client IP'),
|
||||
_('LAN DHCP reserved started IP addresses with the same subnet mask'));
|
||||
_('Starting IP of the VPN client address pool (CIDR notation). Use a private subnet that does not overlap the LAN, e.g. 10.9.8.10/24'));
|
||||
o.datatype = 'ip4addr';
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.option(form.Value, 'clientdns', _('VPN Client DNS'),
|
||||
_('DNS using in VPN tunnel.Set to the router\'s LAN IP is recommended'));
|
||||
_('DNS server assigned to VPN clients. Use the VPN gateway (ipsec0) address, e.g. 10.9.8.1'));
|
||||
o.datatype = 'ip4addr';
|
||||
o.rmempty = false;
|
||||
|
||||
@@ -87,6 +110,91 @@ return view.extend({
|
||||
o.password = true;
|
||||
o.rmempty = false;
|
||||
|
||||
// 在线客户端列表:页面最底部,5s 轮询刷新。数据来自 rpcd 后端
|
||||
// /usr/libexec/rpcd/ipsec-vpnd(解析 `ipsec statusall` 的 stroke 输出)。
|
||||
s = m.section(form.TypedSection);
|
||||
s.anonymous = true;
|
||||
s.render = function() {
|
||||
// 用显式 <thead>/<tbody> 渲染:只重建表体,表头固定保留。
|
||||
// 不依赖 cbi_update_table 对表头行(tr.table-titles)的处理,更稳。
|
||||
let clientBody = E('tbody', { 'id': 'ipsec_clients_body' });
|
||||
let clientTable = E('table', { 'class': 'table cbi-section-table', 'id': 'ipsec_clients_table' }, [
|
||||
E('thead', {}, [
|
||||
E('tr', { 'class': 'tr table-titles' }, [
|
||||
E('th', { 'class': 'th' }, _('User')),
|
||||
E('th', { 'class': 'th' }, _('Remote Address')),
|
||||
E('th', { 'class': 'th' }, _('Assigned IP')),
|
||||
E('th', { 'class': 'th' }, _('Duration')),
|
||||
E('th', { 'class': 'th' }, _('Status'))
|
||||
])
|
||||
]),
|
||||
clientBody
|
||||
]);
|
||||
let hint = E('p', { 'id': 'ipsec_clients_hint' }, _('Collecting data...'));
|
||||
|
||||
let renderRows = function(sessions) {
|
||||
clientBody.textContent = '';
|
||||
sessions.forEach(function(s) {
|
||||
clientBody.appendChild(E('tr', { 'class': 'tr' }, [
|
||||
E('td', { 'class': 'td' }, s.user || '-'),
|
||||
E('td', { 'class': 'td' }, s.remote || '-'),
|
||||
E('td', { 'class': 'td' }, s.vip || '-'),
|
||||
E('td', { 'class': 'td' }, s.age || '-'),
|
||||
E('td', { 'class': 'td' }, s.online ? _('Connected') : _('Disconnected'))
|
||||
]));
|
||||
});
|
||||
};
|
||||
|
||||
let update = function() {
|
||||
return L.resolveDefault(callIpsecSessions(), {}).then(function(res) {
|
||||
let sessions = (res && Array.isArray(res.sessions)) ? res.sessions : null;
|
||||
let hintEl = document.getElementById('ipsec_clients_hint');
|
||||
if (sessions === null) {
|
||||
if (hintEl) hintEl.textContent = _('Failed to retrieve VPN client information.');
|
||||
clientBody.textContent = '';
|
||||
return;
|
||||
}
|
||||
if (sessions.length === 0) {
|
||||
if (hintEl) hintEl.textContent = _('No VPN clients are connected.');
|
||||
clientBody.textContent = '';
|
||||
return;
|
||||
}
|
||||
if (hintEl) hintEl.textContent = '';
|
||||
renderRows(sessions);
|
||||
});
|
||||
};
|
||||
|
||||
poll.add(update, 5);
|
||||
update();
|
||||
|
||||
let clearBtn = E('button', {
|
||||
'class': 'btn cbi-button-remove',
|
||||
'click': function(ev) {
|
||||
ev.preventDefault();
|
||||
if (!window.confirm(_('Clear all client history records?')))
|
||||
return;
|
||||
L.resolveDefault(callClearHistory(), {}).then(function() {
|
||||
return update();
|
||||
});
|
||||
}
|
||||
}, [ _('Clear history') ]);
|
||||
|
||||
return E('div', { 'class': 'cbi-section cbi-tblsection' }, [
|
||||
E('div', {}, [ E('h3', _('IPSec VPN Clients')), clearBtn ]),
|
||||
hint,
|
||||
clientTable
|
||||
]);
|
||||
};
|
||||
|
||||
// 保存后显式 restart 服务:LuCI 保存走 uci commit,不会触发 procd 的
|
||||
// config.change reload trigger(实测 commit/reload_config 均不重启服务),
|
||||
// 必须显式 rc.init restart 才能让启用/停用即时生效。
|
||||
m.save = function(ev) {
|
||||
return form.Map.prototype.save.call(this).then(function() {
|
||||
return L.resolveDefault(callServiceInit('ipsec-vpnd', 'restart'));
|
||||
});
|
||||
};
|
||||
|
||||
return m.render();
|
||||
}
|
||||
});
|
||||
@@ -1,66 +1,115 @@
|
||||
msgid ""
|
||||
msgstr "Content-Type: text/plain; charset=UTF-8"
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:79
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:96
|
||||
msgid "Account"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:59
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:116
|
||||
msgid "Assigned IP"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:162
|
||||
msgid "Clear all client history records?"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:168
|
||||
msgid "Clear history"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:76
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:121
|
||||
msgid "Collecting data..."
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:75
|
||||
msgid "DNS using in VPN tunnel.Set to the router's LAN IP is recommended"
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:190
|
||||
msgid "Connected"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:65
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:92
|
||||
msgid "DNS server assigned to VPN clients. Use the VPN gateway (ipsec0) address, e.g. 10.9.8.1"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:190
|
||||
msgid "Disconnected"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:117
|
||||
msgid "Duration"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:82
|
||||
msgid "Enable"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:145
|
||||
msgid "Failed to retrieve VPN client information."
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/root/usr/share/rpcd/acl.d/luci-app-ipsec-vpnd.json:3
|
||||
msgid "Grant access to ipsec configuration"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:34
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:36
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:51
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:53
|
||||
msgid "IPSec VPN"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:45
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:171
|
||||
msgid "IPSec VPN Clients"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:62
|
||||
#: applications/luci-app-ipsec-vpnd/root/usr/share/luci/menu.d/luci-app-ipsec-vpnd.json:3
|
||||
msgid "IPSec VPN Server"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:46
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:63
|
||||
msgid ""
|
||||
"IPSec VPN connectivity using the native built-in VPN Client on iOS or "
|
||||
"Andriod (IKEv1 with PSK and Xauth)"
|
||||
"Android (IKEv1 with PSK and Xauth)"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:70
|
||||
msgid "LAN DHCP reserved started IP addresses with the same subnet mask"
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:171
|
||||
msgid "No VPN clients are connected."
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:36
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:53
|
||||
msgid "NOT RUNNING"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:82
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:99
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:34
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:115
|
||||
msgid "Remote Address"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:51
|
||||
msgid "RUNNING"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:86
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:103
|
||||
msgid "Secret Pre-Shared Key"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:74
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:87
|
||||
msgid "Starting IP of the VPN client address pool (CIDR notation). Use a private subnet that does not overlap the LAN, e.g. 10.9.8.10/24"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:118
|
||||
msgid "Status"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:114
|
||||
msgid "User"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:91
|
||||
msgid "VPN Client DNS"
|
||||
msgstr ""
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:69
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:86
|
||||
msgid "VPN Client IP"
|
||||
msgstr ""
|
||||
@@ -8,70 +8,117 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:79
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:96
|
||||
msgid "Account"
|
||||
msgstr "账户"
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:59
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:116
|
||||
msgid "Assigned IP"
|
||||
msgstr "分配的 IP"
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:162
|
||||
msgid "Clear all client history records?"
|
||||
msgstr "确定要清除所有客户端历史记录吗?"
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:168
|
||||
msgid "Clear history"
|
||||
msgstr "清除历史"
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:76
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:121
|
||||
msgid "Collecting data..."
|
||||
msgstr "正在收集数据中..."
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:75
|
||||
msgid "DNS using in VPN tunnel.Set to the router's LAN IP is recommended"
|
||||
msgstr "指定VPN客户端的DNS地址。推荐设置为路由器的LAN IP,例如 192.168.0.1"
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:190
|
||||
msgid "Connected"
|
||||
msgstr "已连接"
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:65
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:92
|
||||
msgid "DNS server assigned to VPN clients. Use the VPN gateway (ipsec0) address, e.g. 10.9.8.1"
|
||||
msgstr "分配给 VPN 客户端的 DNS 服务器。使用 VPN 网关(ipsec0)地址,例如 10.9.8.1"
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:190
|
||||
msgid "Disconnected"
|
||||
msgstr "已断开"
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:117
|
||||
msgid "Duration"
|
||||
msgstr "连接时长"
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:82
|
||||
msgid "Enable"
|
||||
msgstr "启用"
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:145
|
||||
msgid "Failed to retrieve VPN client information."
|
||||
msgstr "获取 VPN 客户端信息失败"
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/root/usr/share/rpcd/acl.d/luci-app-ipsec-vpnd.json:3
|
||||
msgid "Grant access to ipsec configuration"
|
||||
msgstr "授予访问 ipsec 配置的权限"
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:34
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:36
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:51
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:53
|
||||
msgid "IPSec VPN"
|
||||
msgstr ""
|
||||
msgstr "IPSec VPN"
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:45
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:171
|
||||
msgid "IPSec VPN Clients"
|
||||
msgstr "IPSec VPN 在线客户端"
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:62
|
||||
#: applications/luci-app-ipsec-vpnd/root/usr/share/luci/menu.d/luci-app-ipsec-vpnd.json:3
|
||||
msgid "IPSec VPN Server"
|
||||
msgstr "IPSec VPN 服务器"
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:46
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:63
|
||||
msgid ""
|
||||
"IPSec VPN connectivity using the native built-in VPN Client on iOS or "
|
||||
"Andriod (IKEv1 with PSK and Xauth)"
|
||||
"Android (IKEv1 with PSK and Xauth)"
|
||||
msgstr ""
|
||||
"使用iOS 或者 Andriod (IKEv1 with PSK and Xauth) 原生内置 IPSec VPN 客户端进行"
|
||||
"使用iOS 或者 Android (IKEv1 with PSK and Xauth) 原生内置 IPSec VPN 客户端进行"
|
||||
"连接"
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:70
|
||||
msgid "LAN DHCP reserved started IP addresses with the same subnet mask"
|
||||
msgstr ""
|
||||
"VPN客户端使用DHCP保留空余IP的起始地址,必须和路由器LAN同一个子网掩码,例如 "
|
||||
"192.168.0.10/24"
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:171
|
||||
msgid "No VPN clients are connected."
|
||||
msgstr "当前没有 VPN 客户端在线"
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:36
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:53
|
||||
msgid "NOT RUNNING"
|
||||
msgstr "未运行"
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:82
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:99
|
||||
msgid "Password"
|
||||
msgstr "密码"
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:34
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:115
|
||||
msgid "Remote Address"
|
||||
msgstr "来源地址"
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:51
|
||||
msgid "RUNNING"
|
||||
msgstr "运行中"
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:86
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:103
|
||||
msgid "Secret Pre-Shared Key"
|
||||
msgstr "PSK密钥"
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:74
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:87
|
||||
msgid "Starting IP of the VPN client address pool (CIDR notation). Use a private subnet that does not overlap the LAN, e.g. 10.9.8.10/24"
|
||||
msgstr "VPN 客户端地址池的起始地址(CIDR 记法)。使用与 LAN 不重叠的私网网段,例如 10.9.8.10/24"
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:118
|
||||
msgid "Status"
|
||||
msgstr "状态"
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:114
|
||||
msgid "User"
|
||||
msgstr "用户"
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:91
|
||||
msgid "VPN Client DNS"
|
||||
msgstr "VPN客户端DNS服务器"
|
||||
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:69
|
||||
#: applications/luci-app-ipsec-vpnd/htdocs/luci-static/resources/view/ipsec-vpnd.js:86
|
||||
msgid "VPN Client IP"
|
||||
msgstr "VPN客户端地址段"
|
||||
@@ -1,9 +1,12 @@
|
||||
|
||||
config service 'ipsec'
|
||||
option clientdns '192.168.0.1'
|
||||
option account 'lean'
|
||||
option secret 'myopenwrt'
|
||||
# VPN 网段固定为 10.9.8.0/24(首次安装的 uci-defaults 也按此写入,不再按 LAN 推导)。
|
||||
# 若 LAN 恰在 10.9.8.0/24,请手动改 clientip/clientdns。
|
||||
# 下面的账号密码仅为占位值,启用服务前请务必修改。
|
||||
option clientdns '10.9.8.1'
|
||||
option account 'vpnuser'
|
||||
option secret 'changeme'
|
||||
option enabled '0'
|
||||
option password '12345678'
|
||||
option clientip '192.168.0.10/24'
|
||||
option password 'changeme'
|
||||
option clientip '10.9.8.10/24'
|
||||
|
||||
@@ -13,356 +13,26 @@ IPSEC_SECRETS_FILE=/etc/ipsec.secrets
|
||||
IPSEC_CONN_FILE=/etc/ipsec.conf
|
||||
STRONGSWAN_CONF_FILE=/etc/strongswan.conf
|
||||
|
||||
IPSEC_VAR_SECRETS_FILE=/var/ipsec/ipsec.secrets
|
||||
IPSEC_VAR_CONN_FILE=/var/ipsec/ipsec.conf
|
||||
STRONGSWAN_VAR_CONF_FILE=/var/ipsec/strongswan.conf
|
||||
|
||||
WAIT_FOR_INTF=0
|
||||
|
||||
file_reset() {
|
||||
: > "$1"
|
||||
}
|
||||
|
||||
xappend() {
|
||||
local file="$1"
|
||||
shift
|
||||
|
||||
echo "${@}" >> "${file}"
|
||||
}
|
||||
|
||||
remove_include() {
|
||||
local file="$1"
|
||||
local include="$2"
|
||||
|
||||
sed -i "\_${include}_d" "${file}"
|
||||
}
|
||||
|
||||
remove_includes() {
|
||||
remove_include "${IPSEC_CONN_FILE}" "${IPSEC_VAR_CONN_FILE}"
|
||||
remove_include "${IPSEC_SECRETS_FILE}" "${IPSEC_VAR_SECRETS_FILE}"
|
||||
remove_include "${STRONGSWAN_CONF_FILE}" "${STRONGSWAN_VAR_CONF_FILE}"
|
||||
}
|
||||
|
||||
do_include() {
|
||||
local conf="$1"
|
||||
local uciconf="$2"
|
||||
local backup=`mktemp -t -p /tmp/ ipsec-init-XXXXXX`
|
||||
|
||||
[ ! -f "${conf}" ] && rm -rf "${conf}"
|
||||
touch "${conf}"
|
||||
|
||||
cat "${conf}" | grep -v "${uciconf}" > "${backup}"
|
||||
mv "${backup}" "${conf}"
|
||||
xappend "${conf}" "include ${uciconf}"
|
||||
file_reset "${uciconf}"
|
||||
}
|
||||
|
||||
ipsec_reset() {
|
||||
do_include "${IPSEC_CONN_FILE}" "${IPSEC_VAR_CONN_FILE}"
|
||||
}
|
||||
|
||||
ipsec_xappend() {
|
||||
xappend "${IPSEC_VAR_CONN_FILE}" "$@"
|
||||
}
|
||||
|
||||
swan_reset() {
|
||||
do_include "${STRONGSWAN_CONF_FILE}" "${STRONGSWAN_VAR_CONF_FILE}"
|
||||
}
|
||||
|
||||
swan_xappend() {
|
||||
xappend "${STRONGSWAN_VAR_CONF_FILE}" "$@"
|
||||
}
|
||||
|
||||
secret_reset() {
|
||||
do_include "${IPSEC_SECRETS_FILE}" "${IPSEC_VAR_SECRETS_FILE}"
|
||||
}
|
||||
|
||||
secret_xappend() {
|
||||
xappend "${IPSEC_VAR_SECRETS_FILE}" "$@"
|
||||
}
|
||||
|
||||
warning() {
|
||||
echo "WARNING: $@" >&2
|
||||
}
|
||||
|
||||
add_crypto_proposal() {
|
||||
local encryption_algorithm
|
||||
local hash_algorithm
|
||||
local dh_group
|
||||
|
||||
config_get encryption_algorithm "$1" encryption_algorithm
|
||||
config_get hash_algorithm "$1" hash_algorithm
|
||||
config_get dh_group "$1" dh_group
|
||||
|
||||
[ -n "${encryption_algorithm}" ] && \
|
||||
crypto="${crypto:+${crypto},}${encryption_algorithm}${hash_algorithm:+-${hash_algorithm}}${dh_group:+-${dh_group}}"
|
||||
}
|
||||
|
||||
set_crypto_proposal() {
|
||||
local conf="$1"
|
||||
local proposal
|
||||
|
||||
crypto=""
|
||||
|
||||
config_get crypto_proposal "$conf" crypto_proposal ""
|
||||
for proposal in $crypto_proposal; do
|
||||
add_crypto_proposal "$proposal"
|
||||
done
|
||||
|
||||
[ -n "${crypto}" ] && {
|
||||
local force_crypto_proposal
|
||||
|
||||
config_get_bool force_crypto_proposal "$conf" force_crypto_proposal
|
||||
|
||||
[ "${force_crypto_proposal}" = "1" ] && crypto="${crypto}!"
|
||||
}
|
||||
|
||||
crypto_proposal="${crypto}"
|
||||
}
|
||||
|
||||
config_conn() {
|
||||
# Generic ipsec conn section shared by tunnel and transport
|
||||
local mode
|
||||
local local_subnet
|
||||
local local_nat
|
||||
local local_sourceip
|
||||
local local_updown
|
||||
local local_firewall
|
||||
local remote_subnet
|
||||
local remote_sourceip
|
||||
local remote_updown
|
||||
local remote_firewall
|
||||
local ikelifetime
|
||||
local lifetime
|
||||
local margintime
|
||||
local keyingtries
|
||||
local dpdaction
|
||||
local dpddelay
|
||||
local inactivity
|
||||
local keyexchange
|
||||
|
||||
config_get mode "$1" mode "route"
|
||||
config_get local_subnet "$1" local_subnet ""
|
||||
config_get local_nat "$1" local_nat ""
|
||||
config_get local_sourceip "$1" local_sourceip ""
|
||||
config_get local_updown "$1" local_updown ""
|
||||
config_get local_firewall "$1" local_firewall ""
|
||||
config_get remote_subnet "$1" remote_subnet ""
|
||||
config_get remote_sourceip "$1" remote_sourceip ""
|
||||
config_get remote_updown "$1" remote_updown ""
|
||||
config_get remote_firewall "$1" remote_firewall ""
|
||||
config_get ikelifetime "$1" ikelifetime "3h"
|
||||
config_get lifetime "$1" lifetime "1h"
|
||||
config_get margintime "$1" margintime "9m"
|
||||
config_get keyingtries "$1" keyingtries "3"
|
||||
config_get dpdaction "$1" dpdaction "none"
|
||||
config_get dpddelay "$1" dpddelay "30s"
|
||||
config_get inactivity "$1" inactivity
|
||||
config_get keyexchange "$1" keyexchange "ikev2"
|
||||
|
||||
[ -n "$local_nat" ] && local_subnet=$local_nat
|
||||
|
||||
ipsec_xappend "conn $config_name-$1"
|
||||
ipsec_xappend " left=%any"
|
||||
ipsec_xappend " right=$remote_gateway"
|
||||
|
||||
[ -n "$local_sourceip" ] && ipsec_xappend " leftsourceip=$local_sourceip"
|
||||
[ -n "$local_subnet" ] && ipsec_xappend " leftsubnet=$local_subnet"
|
||||
|
||||
[ -n "$local_firewall" ] && ipsec_xappend " leftfirewall=$local_firewall"
|
||||
[ -n "$remote_firewall" ] && ipsec_xappend " rightfirewall=$remote_firewall"
|
||||
|
||||
ipsec_xappend " ikelifetime=$ikelifetime"
|
||||
ipsec_xappend " lifetime=$lifetime"
|
||||
ipsec_xappend " margintime=$margintime"
|
||||
ipsec_xappend " keyingtries=$keyingtries"
|
||||
ipsec_xappend " dpdaction=$dpdaction"
|
||||
ipsec_xappend " dpddelay=$dpddelay"
|
||||
|
||||
[ -n "$inactivity" ] && ipsec_xappend " inactivity=$inactivity"
|
||||
|
||||
if [ "$auth_method" = "psk" ]; then
|
||||
ipsec_xappend " leftauth=psk"
|
||||
ipsec_xappend " rightauth=psk"
|
||||
|
||||
[ "$remote_sourceip" != "" ] && ipsec_xappend " rightsourceip=$remote_sourceip"
|
||||
[ "$remote_subnet" != "" ] && ipsec_xappend " rightsubnet=$remote_subnet"
|
||||
|
||||
ipsec_xappend " auto=$mode"
|
||||
else
|
||||
warning "AuthenticationMethod $auth_method not supported"
|
||||
fi
|
||||
|
||||
[ -n "$local_identifier" ] && ipsec_xappend " leftid=$local_identifier"
|
||||
[ -n "$remote_identifier" ] && ipsec_xappend " rightid=$remote_identifier"
|
||||
[ -n "$local_updown" ] && ipsec_xappend " leftupdown=$local_updown"
|
||||
[ -n "$remote_updown" ] && ipsec_xappend " rightupdown=$remote_updown"
|
||||
ipsec_xappend " keyexchange=$keyexchange"
|
||||
|
||||
set_crypto_proposal "$1"
|
||||
[ -n "${crypto_proposal}" ] && ipsec_xappend " esp=$crypto_proposal"
|
||||
[ -n "${ike_proposal}" ] && ipsec_xappend " ike=$ike_proposal"
|
||||
}
|
||||
|
||||
config_tunnel() {
|
||||
config_conn "$1"
|
||||
|
||||
# Specific for the tunnel part
|
||||
ipsec_xappend " type=tunnel"
|
||||
}
|
||||
|
||||
config_transport() {
|
||||
config_conn "$1"
|
||||
|
||||
# Specific for the transport part
|
||||
ipsec_xappend " type=transport"
|
||||
}
|
||||
|
||||
config_remote() {
|
||||
local enabled
|
||||
local gateway
|
||||
local pre_shared_key
|
||||
local auth_method
|
||||
|
||||
config_name=$1
|
||||
|
||||
config_get_bool enabled "$1" enabled 0
|
||||
[ $enabled -eq 0 ] && return
|
||||
|
||||
config_get gateway "$1" gateway
|
||||
config_get pre_shared_key "$1" pre_shared_key
|
||||
config_get auth_method "$1" authentication_method
|
||||
config_get local_identifier "$1" local_identifier ""
|
||||
config_get remote_identifier "$1" remote_identifier ""
|
||||
|
||||
[ "$gateway" = "any" ] && remote_gateway="%any" || remote_gateway="$gateway"
|
||||
|
||||
[ -z "$local_identifier" ] && {
|
||||
local ipdest
|
||||
|
||||
[ "$remote_gateway" = "%any" ] && ipdest="1.1.1.1" || ipdest="$remote_gateway"
|
||||
local_gateway=`ip route get $ipdest | awk -F"src" '/src/{gsub(/ /,"");print $2}'`
|
||||
}
|
||||
|
||||
[ -n "$local_identifier" ] && secret_xappend -n "$local_identifier " || secret_xappend -n "$local_gateway "
|
||||
[ -n "$remote_identifier" ] && secret_xappend -n "$remote_identifier " || secret_xappend -n "$remote_gateway "
|
||||
|
||||
secret_xappend ": PSK \"$pre_shared_key\""
|
||||
|
||||
set_crypto_proposal "$1"
|
||||
ike_proposal="$crypto_proposal"
|
||||
|
||||
config_list_foreach "$1" tunnel config_tunnel
|
||||
|
||||
config_list_foreach "$1" transport config_transport
|
||||
|
||||
ipsec_xappend ""
|
||||
}
|
||||
|
||||
config_ipsec() {
|
||||
local debug
|
||||
local rtinstall_enabled
|
||||
local routing_tables_ignored
|
||||
local routing_table
|
||||
local routing_table_id
|
||||
local interface
|
||||
local device_list
|
||||
|
||||
ipsec_reset
|
||||
secret_reset
|
||||
swan_reset
|
||||
|
||||
ipsec_xappend "# generated by /etc/init.d/ipsec-vpnd"
|
||||
ipsec_xappend "version 2"
|
||||
ipsec_xappend ""
|
||||
|
||||
secret_xappend "# generated by /etc/init.d/ipsec-vpnd"
|
||||
|
||||
config_get debug "$1" debug 0
|
||||
config_get_bool rtinstall_enabled "$1" rtinstall_enabled 1
|
||||
[ $rtinstall_enabled -eq 1 ] && install_routes=yes || install_routes=no
|
||||
|
||||
# prepare extra charon config option ignore_routing_tables
|
||||
for routing_table in $(config_get "$1" "ignore_routing_tables"); do
|
||||
if [ "$routing_table" -ge 0 ] 2>/dev/null; then
|
||||
routing_table_id=$routing_table
|
||||
else
|
||||
routing_table_id=$(sed -n '/[ \t]*[0-9]\+[ \t]\+'$routing_table'[ \t]*$/s/[ \t]*\([0-9]\+\).*/\1/p' /etc/iproute2/rt_tables)
|
||||
fi
|
||||
|
||||
[ -n "$routing_table_id" ] && append routing_tables_ignored "$routing_table_id"
|
||||
done
|
||||
|
||||
local interface_list=$(config_get "$1" "interface")
|
||||
if [ -z "$interface_list" ]; then
|
||||
WAIT_FOR_INTF=0
|
||||
else
|
||||
for interface in $interface_list; do
|
||||
network_get_device device $interface
|
||||
[ -n "$device" ] && append device_list "$device" ","
|
||||
done
|
||||
[ -n "$device_list" ] && WAIT_FOR_INTF=0 || WAIT_FOR_INTF=1
|
||||
fi
|
||||
|
||||
swan_xappend "# generated by /etc/init.d/ipsec-vpnd"
|
||||
swan_xappend "charon {"
|
||||
swan_xappend " load_modular = yes"
|
||||
swan_xappend " install_routes = $install_routes"
|
||||
[ -n "$routing_tables_ignored" ] && swan_xappend " ignore_routing_tables = $routing_tables_ignored"
|
||||
[ -n "$device_list" ] && swan_xappend " interfaces_use = $device_list"
|
||||
swan_xappend " plugins {"
|
||||
swan_xappend " include /etc/strongswan.d/charon/*.conf"
|
||||
swan_xappend " }"
|
||||
swan_xappend " syslog {"
|
||||
swan_xappend " identifier = ipsec"
|
||||
swan_xappend " daemon {"
|
||||
swan_xappend " default = $debug"
|
||||
swan_xappend " }"
|
||||
swan_xappend " auth {"
|
||||
swan_xappend " default = $debug"
|
||||
swan_xappend " }"
|
||||
swan_xappend " }"
|
||||
swan_xappend "}"
|
||||
}
|
||||
|
||||
prepare_env() {
|
||||
mkdir -p /var/ipsec
|
||||
remove_includes
|
||||
config_load ipsec-vpnd
|
||||
config_foreach config_ipsec ipsec
|
||||
config_foreach config_remote remote
|
||||
}
|
||||
|
||||
service_running() {
|
||||
ipsec status > /dev/null 2>&1
|
||||
}
|
||||
|
||||
reload_service() {
|
||||
local bool vt_enabled=`uci get ipsec-vpnd@service[0].enabled 2>/dev/null`
|
||||
[ "$vt_enabled" = 0 ] && /etc/init.d/ipsec-vpnd stop && return
|
||||
running && {
|
||||
prepare_env
|
||||
[ $WAIT_FOR_INTF -eq 0 ] && {
|
||||
ipsec rereadall
|
||||
ipsec reload
|
||||
return
|
||||
}
|
||||
config_load ipsec-vpnd
|
||||
|
||||
local vt_enabled
|
||||
config_get_bool vt_enabled "ipsec" "enabled" "0"
|
||||
[ "$vt_enabled" -eq "0" ] && {
|
||||
stop
|
||||
return 1
|
||||
}
|
||||
[ "$vt_enabled" = 1 ] && start
|
||||
}
|
||||
|
||||
check_ipsec_interface() {
|
||||
local intf
|
||||
|
||||
for intf in $(config_get "$1" interface); do
|
||||
procd_add_interface_trigger "interface.*" "$intf" /etc/init.d/ipsec-vpnd reload
|
||||
done
|
||||
# 配置变更时重启 procd 实例以重新加载。
|
||||
# 不要使用 strongswan 的 ipsec rereadall/reload 命令:
|
||||
# 它们通过 starter pidfile 操作 charon,会误杀本服务
|
||||
# 自己管理的 starter/charon 实例
|
||||
stop
|
||||
start
|
||||
}
|
||||
|
||||
service_triggers() {
|
||||
procd_add_reload_trigger "ipsec-vpnd"
|
||||
config_load "ipsec-vpnd"
|
||||
config_foreach check_ipsec_interface ipsec
|
||||
}
|
||||
|
||||
start_service() {
|
||||
@@ -370,14 +40,22 @@ start_service() {
|
||||
|
||||
local vt_enabled
|
||||
config_get_bool vt_enabled "ipsec" "enabled" "0"
|
||||
[ "$vt_enabled" -eq "0" ] && stop && return 1
|
||||
[ "$vt_enabled" -eq "0" ] && return 1
|
||||
|
||||
# 系统 strongswan 的 ipsec 服务与本服务共用 charon 及 UDP 500/4500,
|
||||
# 必须停掉并禁用来避免抢端口。每次启动都重新执行,防止系统 ipsec
|
||||
# 被升级或手动重新启用后再次抢占。
|
||||
[ -x /etc/init.d/ipsec ] && {
|
||||
/etc/init.d/ipsec stop >/dev/null 2>&1
|
||||
/etc/init.d/ipsec disable >/dev/null 2>&1
|
||||
}
|
||||
|
||||
local vt_clientip vt_clientdns vt_account vt_password vt_secret
|
||||
config_get vt_clientip "ipsec" "clientip"
|
||||
config_get vt_clientdns "ipsec" "clientdns"
|
||||
config_get vt_account "ipsec" "account"
|
||||
config_get vt_password "ipsec" "password"
|
||||
config_get vt_secret "ipsec" "secret"
|
||||
config_get vt_clientip "ipsec" "clientip" "10.9.8.10/24"
|
||||
config_get vt_clientdns "ipsec" "clientdns" "10.9.8.1"
|
||||
config_get vt_account "ipsec" "account" "vpnuser"
|
||||
config_get vt_password "ipsec" "password" "changeme"
|
||||
config_get vt_secret "ipsec" "secret" "changeme"
|
||||
|
||||
cat > /etc/ipsec.conf <<EOF
|
||||
# ipsec.conf - strongSwan IPsec configuration file
|
||||
@@ -402,18 +80,20 @@ conn xauth_psk
|
||||
rightauth2=xauth
|
||||
rightsourceip=$vt_clientip
|
||||
rightdns=$vt_clientdns
|
||||
leftupdown=/usr/lib/ipsec/luci-ipsec-vpnd-updown
|
||||
auto=add
|
||||
EOF
|
||||
|
||||
# secrets 含 PSK 与 XAUTH 密码,用 177 umask 创建并收紧权限,
|
||||
# 避免本地用户读取
|
||||
umask 177
|
||||
cat > /etc/ipsec.secrets <<EOF
|
||||
# /etc/ipsec.secrets - strongSwan IPsec secrets file
|
||||
: PSK "$vt_secret"
|
||||
$vt_account : XAUTH "$vt_password"
|
||||
EOF
|
||||
|
||||
prepare_env
|
||||
|
||||
[ "$WAIT_FOR_INTF" -eq "1" ] && return 1
|
||||
umask 022
|
||||
chmod 600 /etc/ipsec.secrets
|
||||
|
||||
procd_open_instance
|
||||
|
||||
@@ -428,6 +108,10 @@ EOF
|
||||
procd_set_param respawn
|
||||
|
||||
procd_close_instance
|
||||
}
|
||||
|
||||
echo 1 > /proc/sys/net/ipv4/conf/br-lan/proxy_arp
|
||||
stop_service() {
|
||||
# proxy_arp 由 updown 钩子按 ipsec0 接口单独设置,接口在服务停止时随 charon
|
||||
# 一并移除,无需在此清理。
|
||||
return 0
|
||||
}
|
||||
@@ -1,16 +1,33 @@
|
||||
#!/bin/sh
|
||||
|
||||
# 仅首次安装时执行初始化(停用系统 ipsec、重建 network/firewall、删除
|
||||
# strongswan 占位配置)。升级或重复执行时直接跳过,避免覆盖用户对网络、
|
||||
# 防火墙及 strongswan 的自定义配置。
|
||||
# 初始化标记记录在 /etc/config/ipsec-vpnd 的 initialized 选项里:该文件
|
||||
# 在 conffiles 中,包升级和 sysupgrade 都会保留,标记不会丢失。
|
||||
[ "$(uci -q get ipsec-vpnd.ipsec.initialized)" = "1" ] && exit 0
|
||||
|
||||
# 禁用 strongswan 自带的 ipsec 服务,避免与 ipsec-vpnd 共用 charon 冲突
|
||||
[ -x /etc/init.d/ipsec ] && /etc/init.d/ipsec disable
|
||||
|
||||
uci -q batch <<-EOF >/dev/null
|
||||
delete firewall.ipsecd
|
||||
commit firewall
|
||||
EOF
|
||||
|
||||
# VPN 专用 /24 网段固定为 10.9.8.0/24(与出厂默认配置一致),不再按 LAN 推导。
|
||||
# 若你的 LAN 恰好在 10.9.8.0/24,请在初始化后手动改 network.VPN.ipaddr 与
|
||||
# ipsec-vpnd.ipsec 的 clientip/clientdns,避免与 LAN 重叠。
|
||||
VPN_NET="10.9.8"
|
||||
VPN_GW="$VPN_NET.1"
|
||||
VPN_POOL="$VPN_NET.10/24"
|
||||
|
||||
uci -q batch <<-EOF >/dev/null
|
||||
delete network.VPN
|
||||
set network.VPN=interface
|
||||
set network.VPN.device="ipsec0"
|
||||
set network.VPN.proto="static"
|
||||
set network.VPN.ipaddr="192.168.0.1"
|
||||
set network.VPN.ipaddr="$VPN_GW"
|
||||
set network.VPN.netmask="255.255.255.0"
|
||||
commit network
|
||||
|
||||
@@ -50,7 +67,7 @@ uci -q batch <<-EOF >/dev/null
|
||||
|
||||
delete firewall.VPN
|
||||
set firewall.VPN=zone
|
||||
set firewall.VPN.name="VPN"
|
||||
set firewall.VPN.name="ipsec-v"
|
||||
set firewall.VPN.input="ACCEPT"
|
||||
set firewall.VPN.forward="ACCEPT"
|
||||
set firewall.VPN.output="ACCEPT"
|
||||
@@ -58,14 +75,24 @@ uci -q batch <<-EOF >/dev/null
|
||||
|
||||
delete firewall.vpn
|
||||
set firewall.vpn=forwarding
|
||||
set firewall.vpn.name="vpn"
|
||||
set firewall.vpn.name="ipsec-v"
|
||||
set firewall.vpn.dest="wan"
|
||||
set firewall.vpn.src="VPN"
|
||||
set firewall.vpn.src="ipsec-v"
|
||||
|
||||
commit firewall
|
||||
|
||||
set ipsec-vpnd.ipsec.clientip="$VPN_POOL"
|
||||
set ipsec-vpnd.ipsec.clientdns="$VPN_GW"
|
||||
commit ipsec-vpnd
|
||||
EOF
|
||||
|
||||
[ -f "/etc/config/ipsec" ] && mv "/etc/config/ipsec" "/etc/config/ipsec-vpnd"
|
||||
# strongswan 的 /etc/config/ipsec 只是占位配置,直接删除,
|
||||
# 避免改名后覆盖本包自带配置(默认 enabled=0)
|
||||
[ -f "/etc/config/ipsec" ] && rm -f "/etc/config/ipsec"
|
||||
|
||||
# 记录初始化标记,避免升级时重复执行破坏性操作
|
||||
uci -q set ipsec-vpnd.ipsec.initialized='1'
|
||||
uci -q commit ipsec-vpnd
|
||||
|
||||
rm -f /tmp/luci-indexcache
|
||||
exit 0
|
||||
@@ -0,0 +1,86 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# strongSwan updown hook for luci-app-ipsec-vpnd.
|
||||
# Invoked by strongSwan on every IKEv1 SA up/down event; records the client in
|
||||
# the router-side history file so the LuCI client list shows it even when the
|
||||
# page is never opened. Online state is still determined live from
|
||||
# `ipsec statusall` by the rpcd backend; this hook only guarantees presence.
|
||||
#
|
||||
# Documented strongSwan variables used:
|
||||
# PLUTO_VERB up-client / down-client / up-host / down-host [...]
|
||||
# PLUTO_XAUTH_ID XAuth username
|
||||
# PLUTO_PEER peer (client) IP address
|
||||
# PLUTO_PEER_SOURCEIP virtual IP sent to the client (responder side)
|
||||
# PLUTO_PEER_CLIENT peer traffic selector (may carry the /32 VIP)
|
||||
# PLUTO_MY_SOURCEIP fallback virtual IP
|
||||
#
|
||||
# State file format (matches the rpcd backend):
|
||||
# key\tuser\tremote\tvip\tage\tlast_seen (key = "user|remote")
|
||||
|
||||
STATE_FILE="/etc/luci-app-ipsec-vpnd-sessions.state"
|
||||
# Working copy for this hook; never share this tmp path with the rpcd backend.
|
||||
UP_TMP="/tmp/.luci-app-ipsec-vpnd-updown.tmp"
|
||||
LOCK_FILE="/tmp/.luci-app-ipsec-vpnd-sessions.lock"
|
||||
|
||||
# Best-effort advisory lock (same rationale as the rpcd backend).
|
||||
flock_lock() {
|
||||
command -v flock >/dev/null 2>&1 || return 0
|
||||
exec 9>"$LOCK_FILE" 2>/dev/null || return 0
|
||||
flock -w 5 9 2>/dev/null || return 0
|
||||
}
|
||||
flock_unlock() {
|
||||
command -v flock >/dev/null 2>&1 || return 0
|
||||
flock -u 9 2>/dev/null
|
||||
exec 9>&- 2>/dev/null
|
||||
}
|
||||
|
||||
[ -n "$PLUTO_VERB" ] || exit 0
|
||||
|
||||
# 只对 VPN 的 ipsec0 接口开 proxy_arp,不开到所有接口(避免影响 LAN/WAN)。
|
||||
# 该接口在 strongSwan 建 SA 时才存在,所以放在这里;服务停止时接口随 charon 移除,
|
||||
# proxy_arp 也随之消失,无需在此清理。
|
||||
[ -d /proc/sys/net/ipv4/conf/ipsec0 ] && echo 1 > /proc/sys/net/ipv4/conf/ipsec0/proxy_arp 2>/dev/null
|
||||
|
||||
now="$(date +%s)"
|
||||
|
||||
user="${PLUTO_XAUTH_ID:-}"
|
||||
remote="${PLUTO_PEER:-}"
|
||||
|
||||
# assigned client IP: the responder-sent virtual IP, then the /32 selector
|
||||
vip="${PLUTO_PEER_SOURCEIP%%/*}"
|
||||
[ -n "$vip" ] || vip="${PLUTO_MY_SOURCEIP%%/*}"
|
||||
[ -n "$vip" ] || vip="$(printf '%s' "${PLUTO_PEER_CLIENT%%/*}" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -n1)"
|
||||
[ -n "$vip" ] || vip=""
|
||||
|
||||
[ -z "$remote" ] && exit 0
|
||||
[ -z "$user" ] && user="-"
|
||||
|
||||
key="$user|$remote"
|
||||
|
||||
[ -f "$STATE_FILE" ] || : > "$STATE_FILE"
|
||||
|
||||
# serialize read/merge/write with the rpcd backend
|
||||
flock_lock
|
||||
|
||||
awk -F'\t' -v key="$key" -v user="$user" -v remote="$remote" -v vip="$vip" -v now="$now" '
|
||||
{
|
||||
k = $1
|
||||
if (k == key) {
|
||||
# refresh record, keep old vip/age if none supplied
|
||||
printf "%s\t%s\t%s\t%s\t%s\t%s\n", key, user, remote,
|
||||
(vip != "" ? vip : $4), $5, now
|
||||
done = 1
|
||||
} else {
|
||||
print $0
|
||||
}
|
||||
}
|
||||
END {
|
||||
if (!done)
|
||||
printf "%s\t%s\t%s\t%s\t%s\t%s\n", key, user, remote, vip, "", now
|
||||
}' "$STATE_FILE" > "$UP_TMP"
|
||||
|
||||
[ -s "$UP_TMP" ] && mv "$UP_TMP" "$STATE_FILE"
|
||||
rm -f "$UP_TMP"
|
||||
flock_unlock
|
||||
exit 0
|
||||
+249
@@ -0,0 +1,249 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# rpcd backend for luci-app-ipsec-vpnd.
|
||||
# Exposes one read-only ubus object method "sessions": parse `ipsec statusall`
|
||||
# (stroke), merge the currently-online IKEv1 SA set with a persisted history of
|
||||
# seen clients (router-side, survives browser/reboot), and return the combined
|
||||
# list with an "online" flag per entry.
|
||||
#
|
||||
# rpcd plugin protocol: argv[1] = "list" | "call", argv[2] = method name when
|
||||
# calling; the request JSON arrives on stdin. stdout carries one JSON document.
|
||||
#
|
||||
# Persisted state: one line per known client, TAB separated:
|
||||
# key\tuser\tremote\tvip\tage\tlast_seen (key = "user|remote")
|
||||
|
||||
. /usr/share/libubox/jshn.sh
|
||||
|
||||
STATE_FILE="/etc/luci-app-ipsec-vpnd-sessions.state"
|
||||
# Working copy for sessions(); kept in /tmp so an unchanged 5s poll doesn't keep
|
||||
# rewriting the persistent state file on flash. Never share this path with the
|
||||
# updown hook.
|
||||
SESSIONS_TMP="/tmp/.luci-app-ipsec-vpnd-sessions.tmp"
|
||||
LOCK_FILE="/tmp/.luci-app-ipsec-vpnd-sessions.lock"
|
||||
|
||||
# Best-effort advisory lock: serialises the read/merge/write against the updown
|
||||
# hook. If flock is unavailable each writer still uses its own tmp, so the worst
|
||||
# case is a lost update in a tiny race window, never a corrupted file.
|
||||
flock_lock() {
|
||||
command -v flock >/dev/null 2>&1 || return 0
|
||||
exec 9>"$LOCK_FILE" 2>/dev/null || return 0
|
||||
flock -w 5 9 2>/dev/null || return 0
|
||||
}
|
||||
flock_unlock() {
|
||||
command -v flock >/dev/null 2>&1 || return 0
|
||||
flock -u 9 2>/dev/null
|
||||
exec 9>&- 2>/dev/null
|
||||
}
|
||||
|
||||
list_methods() {
|
||||
printf '%s\n' '{"sessions":{},"clear_history":{}}'
|
||||
}
|
||||
|
||||
json_err() {
|
||||
json_init
|
||||
json_add_int code 1
|
||||
json_add_string error "$1"
|
||||
json_dump
|
||||
}
|
||||
|
||||
# Parse `ipsec statusall` from stdin into TAB-separated online rows:
|
||||
# key\tuser\tremote\tvip\tage
|
||||
parse_statusall() {
|
||||
awk '
|
||||
function getsa(l) {
|
||||
if (match(l, /^[ \t]*[A-Za-z0-9_.-]+\[[0-9]+\]:/)) {
|
||||
s = substr(l, RSTART, RLENGTH - 1)
|
||||
sub(/^[ \t]*/, "", s)
|
||||
return s
|
||||
}
|
||||
return ""
|
||||
}
|
||||
function getts(s) {
|
||||
if (match(s, /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\/[0-9]+/))
|
||||
return substr(s, RSTART, RLENGTH)
|
||||
return ""
|
||||
}
|
||||
BEGIN { n = 0; last = "" }
|
||||
{
|
||||
line = $0
|
||||
|
||||
if (match(line, /^[ \t]*[A-Za-z0-9_.-]+\[[0-9]+\]: ESTABLISHED /)) {
|
||||
sa = getsa(line)
|
||||
if (!(sa in est)) {
|
||||
est[sa] = 1
|
||||
order[++n] = sa
|
||||
last = sa
|
||||
|
||||
s = index(line, "ESTABLISHED ") + 12
|
||||
e = index(line, " ago,")
|
||||
age[sa] = (e > s) ? substr(line, s, e - s) : ""
|
||||
|
||||
rest = ""
|
||||
i = index(line, "...")
|
||||
if (i > 0) rest = substr(line, i + 3)
|
||||
ip = ""
|
||||
if (match(rest, /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/))
|
||||
ip = substr(rest, RSTART, RLENGTH)
|
||||
if (ip == "") {
|
||||
sub(/^[ \t]*/, "", rest)
|
||||
j = index(rest, "[")
|
||||
if (j > 0) rest = substr(rest, 1, j - 1)
|
||||
ip = rest
|
||||
}
|
||||
remote[sa] = ip
|
||||
}
|
||||
next
|
||||
}
|
||||
|
||||
if (line ~ /[Xx][Aa]uth/ && index(line, "identity:") > 0) {
|
||||
sa = getsa(line)
|
||||
if (sa != "" && (sa in est) && !(sa in user)) {
|
||||
v = substr(line, index(line, "identity:") + 9)
|
||||
gsub(/^[ \t"'"'"']+|[ \t"'"'"']+$/, "", v)
|
||||
if (v != "") user[sa] = v
|
||||
}
|
||||
next
|
||||
}
|
||||
|
||||
# CHILD_SA traffic selector carrying the assigned /32 client address;
|
||||
# strongSwan 6.x puts the pool /32 on the RIGHT (remote) side here.
|
||||
if (index(line, "===") > 0 && line ~ /\/[0-9]+/) {
|
||||
i = index(line, "===")
|
||||
l = getts(substr(line, 1, i - 1))
|
||||
r = getts(substr(line, i + 3))
|
||||
ip = ""
|
||||
if (r ~ /\/32$/) ip = substr(r, 1, length(r) - 3)
|
||||
else if (l ~ /\/32$/) ip = substr(l, 1, length(l) - 3)
|
||||
if (ip != "") {
|
||||
sa = getsa(line)
|
||||
if (!(sa in est)) sa = last
|
||||
if ((sa in est) && !(sa in vip)) vip[sa] = ip
|
||||
}
|
||||
next
|
||||
}
|
||||
}
|
||||
END {
|
||||
for (k = 1; k <= n; k++) {
|
||||
sa = order[k]
|
||||
u = user[sa]; r = remote[sa]; v = vip[sa]; a = age[sa]
|
||||
if (u == "") u = "-"
|
||||
if (r == "") r = "-"
|
||||
printf "%s|%s\t%s\t%s\t%s\t%s\n", u, r, u, r, v, a
|
||||
}
|
||||
}'
|
||||
}
|
||||
|
||||
sessions() {
|
||||
local now="$(date +%s)"
|
||||
local online_tmp="/tmp/.luci-app-ipsec-vpnd-online.tsv"
|
||||
|
||||
# ensure the history file exists so awk can always read it as first input
|
||||
[ -f "$STATE_FILE" ] || : > "$STATE_FILE"
|
||||
|
||||
# current online clients as TSV
|
||||
/usr/sbin/ipsec statusall 2>/dev/null | parse_statusall > "$online_tmp"
|
||||
|
||||
# serialize read/merge/write with the updown hook
|
||||
flock_lock
|
||||
|
||||
awk -F'\t' -v now="$now" -v state_tmp="$SESSIONS_TMP" -v state="$STATE_FILE" '
|
||||
function jesc(s) {
|
||||
gsub(/\\/, "\\\\", s)
|
||||
gsub(/"/, "\\\"", s)
|
||||
return s
|
||||
}
|
||||
BEGIN { n = 0 }
|
||||
FILENAME == state {
|
||||
# history rows: key user remote vip age last_seen
|
||||
key = $1
|
||||
if (key == "") next
|
||||
if (!(key in seen)) { seen[key] = 1; order[++n] = key }
|
||||
hu[key] = $2; hr[key] = $3; hv[key] = $4; ha[key] = $5; hl[key] = $6
|
||||
next
|
||||
}
|
||||
{
|
||||
# online rows: key user remote vip age
|
||||
key = $1
|
||||
if (key == "") next
|
||||
active[key] = 1
|
||||
if (!(key in seen)) { seen[key] = 1; order[++n] = key }
|
||||
hu[key] = $2; hr[key] = $3; hv[key] = $4; ha[key] = $5; hl[key] = now
|
||||
}
|
||||
END {
|
||||
# build output order: online first (kept), then offline by last_seen desc
|
||||
nc = 0
|
||||
for (i = 1; i <= n; i++)
|
||||
if (active[order[i]]) out[++nc] = order[i]
|
||||
oc = 0
|
||||
for (i = 1; i <= n; i++)
|
||||
if (!active[order[i]]) off[++oc] = order[i]
|
||||
for (i = 1; i <= oc; i++)
|
||||
for (j = i + 1; j <= oc; j++)
|
||||
if ((hl[off[j]] + 0) > (hl[off[i]] + 0)) {
|
||||
t = off[i]; off[i] = off[j]; off[j] = t
|
||||
}
|
||||
for (i = 1; i <= oc; i++) out[++nc] = off[i]
|
||||
|
||||
# JSON response
|
||||
printf "{\"code\":0,\"sessions\":["
|
||||
first = 1
|
||||
for (i = 1; i <= nc; i++) {
|
||||
k = out[i]
|
||||
if (!first) printf ","
|
||||
first = 0
|
||||
printf "{\"user\":\"%s\",\"remote\":\"%s\",\"vip\":\"%s\",\"age\":\"%s\",\"online\":%s}",
|
||||
jesc(hu[k]), jesc(hr[k]), jesc(hv[k]), jesc(ha[k]),
|
||||
(active[k] ? "true" : "false")
|
||||
}
|
||||
printf "]}\n"
|
||||
|
||||
# rewrite persisted history (drop online flag; recomputed each call)
|
||||
for (i = 1; i <= n; i++) {
|
||||
k = order[i]
|
||||
printf "%s\t%s\t%s\t%s\t%s\t%s\n", k, hu[k], hr[k], hv[k], ha[k], hl[k] > state_tmp
|
||||
}
|
||||
}' "$STATE_FILE" "$online_tmp"
|
||||
|
||||
rc=$?
|
||||
rm -f "$online_tmp"
|
||||
# rewrite the persisted history only when it actually changed, so an idle
|
||||
# 5s poll doesn't keep re-writing the state file on flash.
|
||||
if [ -s "$SESSIONS_TMP" ] && ! cmp -s "$SESSIONS_TMP" "$STATE_FILE"; then
|
||||
mv "$SESSIONS_TMP" "$STATE_FILE"
|
||||
fi
|
||||
rm -f "$SESSIONS_TMP"
|
||||
flock_unlock
|
||||
return $rc
|
||||
}
|
||||
|
||||
clear_history() {
|
||||
flock_lock
|
||||
rm -f "$STATE_FILE" "$SESSIONS_TMP"
|
||||
flock_unlock
|
||||
json_init
|
||||
json_add_int code 0
|
||||
json_add_string message "cleared"
|
||||
json_dump
|
||||
}
|
||||
|
||||
main() {
|
||||
case "$1" in
|
||||
list)
|
||||
list_methods
|
||||
;;
|
||||
call)
|
||||
case "$2" in
|
||||
sessions) sessions ;;
|
||||
clear_history) clear_history ;;
|
||||
*) json_err "unknown method" ;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
printf '%s\n' '{}'
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# source-able for offline parser testing
|
||||
[ -n "$IPSEC_VPND_SKIP_MAIN" ] || main "$@"
|
||||
@@ -3,11 +3,16 @@
|
||||
"description": "Grant access to ipsec configuration",
|
||||
"read": {
|
||||
"ubus": {
|
||||
"ipsec-vpnd": [ "sessions", "clear_history" ],
|
||||
"rc": [ "init" ],
|
||||
"service": [ "list" ]
|
||||
},
|
||||
"uci": [ "ipsec-vpnd" ]
|
||||
},
|
||||
"write": {
|
||||
"ubus": {
|
||||
"rc": [ "init" ]
|
||||
},
|
||||
"uci": [ "ipsec-vpnd" ]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user