feat: 实现第三层 TUN VPN 服务,支持后台 IP 分配
- 新增 TUN 设备层(water 库),分 linux/darwin 平台配置 IP/路由/MTU - 实现 IP 分配管理:动态池自动分配 + 按用户静态预留,支持热更新 - 实现 PacketSwitch 共享 TUN 包转发:源 IP 防伪、按目的 IP 查表转发、allow-c2c - 重写隧道:自研简化 WS 协议(文本帧 JSON 控制 init/ready,二进制帧=原始 IP 包) - VpnService 单例管理 TUN 生命周期,子网变更踢线重建,预留增删热更新 - 新增 vpn_settings/vpn_reservations 表,AutoMigrate + 默认设置 seed - 新增 Admin API:settings 读写、status、clients、reservations CRUD - 前端新增 VpnView(/admin/vpn):状态面板/设置表单/在线客户端/静态预留 - main.go 启动时按 DB 设置初始化 VPN 服务
This commit is contained in:
+137
-9
@@ -1,8 +1,11 @@
|
||||
package vpn
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"lmvpn/internal/model"
|
||||
@@ -13,6 +16,7 @@ import (
|
||||
const (
|
||||
readTimeout = 60 * time.Second
|
||||
writeTimeout = 10 * time.Second
|
||||
readyTimeout = 30 * time.Second
|
||||
pingPeriod = 30 * time.Second
|
||||
maxMessageSize = 1 << 20
|
||||
maxConnsPerUser = 3
|
||||
@@ -23,13 +27,71 @@ var (
|
||||
activeConnsMu sync.Mutex
|
||||
)
|
||||
|
||||
type tunnelConn struct {
|
||||
conn *websocket.Conn
|
||||
user *model.User
|
||||
svc *VpnService
|
||||
assignedIP net.IP
|
||||
connectedAt time.Time
|
||||
writeMu sync.Mutex
|
||||
ready atomic.Bool
|
||||
rxBytes atomic.Int64
|
||||
txBytes atomic.Int64
|
||||
}
|
||||
|
||||
func (c *tunnelConn) AssignedIP() net.IP { return c.assignedIP }
|
||||
|
||||
func (c *tunnelConn) WritePacket(data []byte) error {
|
||||
if !c.ready.Load() || len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
c.writeMu.Lock()
|
||||
defer c.writeMu.Unlock()
|
||||
c.conn.SetWriteDeadline(time.Now().Add(writeTimeout))
|
||||
if err := c.conn.WriteMessage(websocket.BinaryMessage, data); err != nil {
|
||||
return err
|
||||
}
|
||||
c.txBytes.Add(int64(len(data)))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *tunnelConn) writeControl(v interface{}) error {
|
||||
data, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.writeMu.Lock()
|
||||
defer c.writeMu.Unlock()
|
||||
c.conn.SetWriteDeadline(time.Now().Add(writeTimeout))
|
||||
return c.conn.WriteMessage(websocket.TextMessage, data)
|
||||
}
|
||||
|
||||
func (c *tunnelConn) close() {
|
||||
c.writeMu.Lock()
|
||||
_ = c.conn.Close()
|
||||
c.writeMu.Unlock()
|
||||
}
|
||||
|
||||
func (c *tunnelConn) info() ClientInfo {
|
||||
return ClientInfo{
|
||||
Username: c.user.Username,
|
||||
IP: c.assignedIP.String(),
|
||||
ConnectedAt: c.connectedAt.Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
}
|
||||
|
||||
func runTunnel(conn *websocket.Conn, user *model.User) {
|
||||
defer conn.Close()
|
||||
|
||||
if VPN == nil || !VPN.Running() {
|
||||
_ = sendJSON(conn, controlMessage{Type: "error", Message: "VPN 服务未启用"})
|
||||
return
|
||||
}
|
||||
|
||||
activeConnsMu.Lock()
|
||||
if activeConns[user.ID] >= maxConnsPerUser {
|
||||
activeConnsMu.Unlock()
|
||||
sendJSON(conn, authResponse{Type: "auth_err", Message: "连接数已达上限"})
|
||||
_ = sendJSON(conn, controlMessage{Type: "error", Message: "连接数已达上限"})
|
||||
return
|
||||
}
|
||||
activeConns[user.ID]++
|
||||
@@ -44,18 +106,52 @@ func runTunnel(conn *websocket.Conn, user *model.User) {
|
||||
activeConnsMu.Unlock()
|
||||
}()
|
||||
|
||||
log.Printf("用户 %s 已连接", user.Username)
|
||||
ip, err := VPN.Allocate(user)
|
||||
if err != nil {
|
||||
_ = sendJSON(conn, controlMessage{Type: "error", Message: "分配 IP 失败: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
tc := &tunnelConn{
|
||||
conn: conn,
|
||||
user: user,
|
||||
svc: VPN,
|
||||
assignedIP: ip,
|
||||
connectedAt: time.Now(),
|
||||
}
|
||||
|
||||
VPN.registerClient(tc)
|
||||
defer VPN.unregisterClient(tc)
|
||||
|
||||
settings := VPN.Settings()
|
||||
initMsg := initMessage{
|
||||
Type: "init",
|
||||
IP: ip.String(),
|
||||
Prefix: VPN.Prefix(),
|
||||
MTU: settings.MTU,
|
||||
ServerIP: VPN.ServerIP().String(),
|
||||
}
|
||||
if err := tc.writeControl(initMsg); err != nil {
|
||||
log.Printf("用户 %s 发送 init 失败: %v", user.Username, err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("用户 %s 已连接,分配 IP %s", user.Username, ip.String())
|
||||
|
||||
conn.SetReadLimit(maxMessageSize)
|
||||
conn.SetReadDeadline(time.Now().Add(readyTimeout))
|
||||
readyDeadline := time.Now().Add(readyTimeout)
|
||||
|
||||
go func() {
|
||||
ticker := time.NewTicker(pingPeriod)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
conn.SetWriteDeadline(time.Now().Add(writeTimeout))
|
||||
if err := conn.WriteMessage(websocket.PingMessage, nil); err != nil {
|
||||
tc.writeMu.Lock()
|
||||
if err := conn.WriteControl(websocket.PingMessage, nil, time.Now().Add(writeTimeout)); err != nil {
|
||||
tc.writeMu.Unlock()
|
||||
return
|
||||
}
|
||||
tc.writeMu.Unlock()
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -65,17 +161,49 @@ func runTunnel(conn *websocket.Conn, user *model.User) {
|
||||
})
|
||||
|
||||
for {
|
||||
conn.SetReadDeadline(time.Now().Add(readTimeout))
|
||||
messageType, data, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
log.Printf("用户 %s 断开连接: %v", user.Username, err)
|
||||
return
|
||||
}
|
||||
|
||||
conn.SetWriteDeadline(time.Now().Add(writeTimeout))
|
||||
if err := conn.WriteMessage(messageType, data); err != nil {
|
||||
log.Printf("用户 %s 发送失败: %v", user.Username, err)
|
||||
return
|
||||
if messageType == websocket.TextMessage {
|
||||
var msg controlMessage
|
||||
if err := json.Unmarshal(data, &msg); err != nil {
|
||||
continue
|
||||
}
|
||||
if msg.Type == "ready" && !tc.ready.Load() {
|
||||
tc.ready.Store(true)
|
||||
conn.SetReadDeadline(time.Now().Add(readTimeout))
|
||||
log.Printf("用户 %s 就绪 (IP %s)", user.Username, ip.String())
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if messageType != websocket.BinaryMessage {
|
||||
continue
|
||||
}
|
||||
|
||||
if !tc.ready.Load() {
|
||||
if time.Now().After(readyDeadline) {
|
||||
log.Printf("用户 %s 等待 ready 超时", user.Username)
|
||||
return
|
||||
}
|
||||
conn.SetReadDeadline(readyDeadline)
|
||||
continue
|
||||
}
|
||||
|
||||
tc.rxBytes.Add(int64(len(data)))
|
||||
|
||||
targets := VPN.RouteFromClient(tc, data)
|
||||
if len(targets) == 0 {
|
||||
if err := VPN.WriteToTUN(data); err != nil {
|
||||
log.Printf("用户 %s 写入 TUN 失败: %v", user.Username, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
for _, t := range targets {
|
||||
_ = t.WritePacket(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user