feat: 新增 GRE 隧道协议,管理端口调整为 18080

- backend: 新增 GRE 隧道实现(ip tunnel 点对点互联),binmgr 支持 iproute2
- backend: api/identity 调整,各协议 service(gre/ikev2/ipsec/l2tp/openvpn)统一改动
- backend: OpenVPN 认证辅助与用户口令同步逻辑完善
- frontend: 协议图标/向导/编辑器支持 GRE,VPN 用户页重构(234 行)
- frontend: 新增 RouteListInput 路由列表输入组件,Dashboard/Layout 优化
- i18n: 中英文文案同步更新
- test: 新增 binmgr/common/gre 单测,openvpn 测试补充
- docs: README 补充 GRE 说明,默认监听端口 8080 → 18080
- chore: .gitignore 增加 opsx-vpn.previous(构建备份二进制)
This commit is contained in:
opsx committed 2026-10-20 20:58:58 +08:00
1 parent c7f59f2cbf
commit ef7eb378d2
28 files changed
+1363 -439

No files matched your search

+18 -18
View File
@@ -2,6 +2,8 @@
package l2tp
import (
"crypto/rand"
"encoding/hex"
"fmt"
"net/netip"
"os"
@@ -103,20 +105,17 @@ func (s *Service) Create(t *model.Tunnel) error {
if psk := service.Str(cfg, "psk"); psk != "" {
secrets["psk"] = psk
} else if service.Str(secrets, "psk") == "" {
return fmt.Errorf("缺少 IPSec 预共享密钥 psk")
// A per-tunnel key avoids accidentally sharing a weak UI default.
buf := make([]byte, 24)
if _, err := rand.Read(buf); err != nil {
return fmt.Errorf("生成 IPSec 预共享密钥失败: %w", err)
}
secrets["psk"] = hex.EncodeToString(buf)
}
username := service.StrOr(cfg, "username", "vpnuser")
if pw := service.Str(cfg, "password"); pw != "" {
secrets["password"] = pw
}
// 允许仅依赖拨号用户表(VPNUser)而无隧道级默认密码。
dialUsers, err := service.LoadDialUsers(t.ID)
if err != nil {
return err
}
if service.Str(secrets, "password") == "" && len(dialUsers) == 0 {
return fmt.Errorf("缺少拨号密码:请填写隧道默认 password,或在「VPN 拨号用户」中添加本隧道用户")
}
// 1) IPSec 层(transport 模式,L2TP over IPSec)
swanConf := fmt.Sprintf(`connections {
@@ -180,7 +179,7 @@ mru 1400
require-mschap-v2
`, service.Str(cfg, "dns"))
chapSecrets := buildChapSecrets(username, service.Str(secrets, "password"), dialUsers)
chapSecrets := buildChapSecrets("", "", dialUsers)
for name, content := range map[string]string{
"swanctl-l2tp.conf": swanConf,
@@ -192,7 +191,7 @@ require-mschap-v2
return err
}
}
public, sec := service.MergeSecrets(cfg, "psk", "password")
public, sec := service.MergeSecrets(cfg, "psk")
for k, v := range secrets {
sec[k] = v
}
@@ -204,8 +203,6 @@ func (s *Service) RefreshAuth(t *model.Tunnel) error {
return s.Create(t)
}
func buildChapSecrets(defaultUser, defaultPass string, users []service.DialUser) string {
var b strings.Builder
b.WriteString("# client server secret IP addresses\n")
@@ -236,19 +233,17 @@ func (s *Service) GenerateClientConfig(t *model.Tunnel) (string, error) {
服务器地址 : %s
VPN 类型 : L2TP/IPSec PSK(预共享密钥)
IPSec PSK : %s
用户名 : %s
密码 : %s
客户端配置指引:
- iOS/macOS : 设置 → VPN → 添加 L2TP,填入服务器/账号/密码/密钥
- Windows : 设置 → VPN → L2TP/IPsec 预共享密钥,需注册表开启 NAT-T
- Android : 部分新机型已移除 L2TP,建议改用 WireGuard
- 路由器 : 选择 L2TP 拨号,填入上述参数
账号密码请在「VPN 拨号用户」中创建或导入后使用。
`,
service.StrOr(cfg, "public_endpoint", "<服务器公网 IP>"),
secrets["psk"],
service.StrOr(cfg, "username", "vpnuser"),
secrets["password"],
), nil
}
@@ -262,6 +257,9 @@ func (s *Service) Start(t *model.Tunnel) error {
if err := service.RequireRoot("L2TP/IPSec"); err != nil {
return err
}
if err := service.EnsureStrongSwanVICI(swanctl); err != nil {
return fmt.Errorf("无法启动 L2TP/IPSec:%w", err)
}
if err := service.RequireIPv4Forwarding("L2TP/IPSec"); err != nil {
return err
}
@@ -286,7 +284,9 @@ func (s *Service) Start(t *model.Tunnel) error {
_ = s.removeFirewall(t, cfg)
return err
}
if err := service.StartDaemon(t, xl2tpd, "-c", dir+"/xl2tpd.conf", "-p", dir+"/xl2tpd.pid", "-D"); err != nil {
// Source-built xl2tpd defaults its control socket to /var/run/xl2tpd,
// which is absent on minimal TencentOS images. Keep it instance-scoped.
if err := service.StartDaemon(t, xl2tpd, "-c", dir+"/xl2tpd.conf", "-p", dir+"/xl2tpd.pid", "-C", dir+"/l2tp-control", "-D"); err != nil {
_ = s.unload(swanctl, dir+"/swanctl-l2tp.conf")
_ = s.removeFirewall(t, cfg)
return err