mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-06 15:46:37 +08:00
[优化] 更新节点类型支持,添加隧道客户端,重构相关路由和配置
This commit is contained in:
@@ -356,7 +356,6 @@ func PublishConfigVersion(createdBy string, force bool) (*ReleaseResult, error)
|
||||
Checksum: record.Checksum,
|
||||
}
|
||||
BroadcastAgentWSActiveConfig(activeConfig)
|
||||
BroadcastFlaredWSActiveConfig(activeConfig)
|
||||
return &ReleaseResult{
|
||||
Version: record,
|
||||
Routes: bundle.Routes,
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
var DefaultFlaredWSHub = NewWSHub("flared")
|
||||
|
||||
func RegisterFlaredWSClient(tunnelID string) *WSClient {
|
||||
return DefaultFlaredWSHub.Register(tunnelID)
|
||||
}
|
||||
|
||||
func UnregisterFlaredWSClient(client *WSClient) {
|
||||
DefaultFlaredWSHub.Unregister(client)
|
||||
}
|
||||
|
||||
func IsFlaredWSConnected(tunnelID string) bool {
|
||||
return DefaultFlaredWSHub.IsConnected(tunnelID)
|
||||
}
|
||||
|
||||
func SendFlaredWSPong(tunnelID string) bool {
|
||||
return DefaultFlaredWSHub.SendMessage(tunnelID, WSMessage{
|
||||
Type: "pong",
|
||||
})
|
||||
}
|
||||
|
||||
func SendFlaredWSActiveConfig(tunnelID string, activeConfig *ActiveConfigMeta) bool {
|
||||
if activeConfig == nil {
|
||||
return false
|
||||
}
|
||||
return DefaultFlaredWSHub.SendMessage(tunnelID, WSMessage{
|
||||
Type: "active_config",
|
||||
Payload: activeConfig,
|
||||
})
|
||||
}
|
||||
|
||||
func BroadcastFlaredWSActiveConfig(activeConfig *ActiveConfigMeta) WSBroadcastResult {
|
||||
if activeConfig == nil {
|
||||
slog.Debug("flared ws broadcast skipped because active config is nil")
|
||||
return WSBroadcastResult{}
|
||||
}
|
||||
res := DefaultFlaredWSHub.Broadcast(WSMessage{
|
||||
Type: "active_config",
|
||||
Payload: activeConfig,
|
||||
})
|
||||
slog.Debug("flared ws broadcast active config",
|
||||
"version", activeConfig.Version,
|
||||
"checksum", activeConfig.Checksum,
|
||||
"client_count", res.ClientCount,
|
||||
"success_count", res.SuccessCount,
|
||||
"failed_tunnels", res.FailedIDs,
|
||||
)
|
||||
return res
|
||||
}
|
||||
@@ -634,6 +634,8 @@ func normalizeNodeType(raw string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(raw)) {
|
||||
case "tunnel_relay":
|
||||
return "tunnel_relay"
|
||||
case "tunnel_client":
|
||||
return "tunnel_client"
|
||||
default:
|
||||
return "edge_node"
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ type ProxyRouteInput struct {
|
||||
BasicAuthPassword string `json:"basic_auth_password"`
|
||||
Remark string `json:"remark"`
|
||||
UpstreamType string `json:"upstream_type"`
|
||||
TunnelID *uint `json:"tunnel_id"`
|
||||
TunnelNodeID *uint `json:"tunnel_node_id"`
|
||||
TunnelTargetAddr string `json:"tunnel_target_addr"`
|
||||
TunnelTargetProtocol string `json:"tunnel_target_protocol"`
|
||||
}
|
||||
@@ -101,7 +101,7 @@ type ProxyRouteView struct {
|
||||
BasicAuthPassword string `json:"basic_auth_password"`
|
||||
Remark string `json:"remark"`
|
||||
UpstreamType string `json:"upstream_type"`
|
||||
TunnelID *uint `json:"tunnel_id"`
|
||||
TunnelNodeID *uint `json:"tunnel_node_id"`
|
||||
TunnelTargetAddr string `json:"tunnel_target_addr"`
|
||||
TunnelTargetProtocol string `json:"tunnel_target_protocol"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
@@ -327,11 +327,11 @@ func buildProxyRoute(route *model.ProxyRoute, input ProxyRouteInput) (*model.Pro
|
||||
route.Remark = remark
|
||||
route.UpstreamType = upstreamType
|
||||
if upstreamType == "tunnel" {
|
||||
route.TunnelID = input.TunnelID
|
||||
route.TunnelNodeID = input.TunnelNodeID
|
||||
route.TunnelTargetAddr = strings.TrimSpace(input.TunnelTargetAddr)
|
||||
route.TunnelTargetProtocol = normalizeTunnelTargetProtocol(input.TunnelTargetProtocol)
|
||||
} else {
|
||||
route.TunnelID = nil
|
||||
route.TunnelNodeID = nil
|
||||
route.TunnelTargetAddr = ""
|
||||
route.TunnelTargetProtocol = ""
|
||||
}
|
||||
@@ -421,7 +421,7 @@ func buildProxyRouteView(route *model.ProxyRoute) (*ProxyRouteView, error) {
|
||||
BasicAuthPassword: route.BasicAuthPassword,
|
||||
Remark: route.Remark,
|
||||
UpstreamType: route.UpstreamType,
|
||||
TunnelID: route.TunnelID,
|
||||
TunnelNodeID: route.TunnelNodeID,
|
||||
TunnelTargetAddr: route.TunnelTargetAddr,
|
||||
TunnelTargetProtocol: route.TunnelTargetProtocol,
|
||||
CreatedAt: route.CreatedAt,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"openflare/common"
|
||||
@@ -162,42 +161,10 @@ type FlaredProxyEntry struct {
|
||||
CustomDomains []string `json:"custom_domains"`
|
||||
}
|
||||
|
||||
// HeartbeatFlared processes an OpenFlared client heartbeat.
|
||||
func HeartbeatFlared(tunnel *model.Tunnel, payload FlaredHeartbeatPayload) (*FlaredHeartbeatResponse, error) {
|
||||
if tunnel == nil {
|
||||
return nil, fmt.Errorf("tunnel is nil")
|
||||
}
|
||||
slog.Debug("flared heartbeat received", "tunnel_id", tunnel.TunnelID)
|
||||
|
||||
now := time.Now()
|
||||
tunnel.ClientVersion = strings.TrimSpace(payload.ClientVersion)
|
||||
tunnel.FrpVersion = strings.TrimSpace(payload.FrpVersion)
|
||||
tunnel.Status = "online"
|
||||
tunnel.LastSeenAt = now
|
||||
tunnel.CurrentVersion = strings.TrimSpace(payload.CurrentVersion)
|
||||
tunnel.CurrentChecksum = strings.TrimSpace(payload.CurrentChecksum)
|
||||
|
||||
relaysJSON, err := json.Marshal(payload.ConnectedRelays)
|
||||
if err == nil {
|
||||
tunnel.ConnectedRelays = string(relaysJSON)
|
||||
}
|
||||
|
||||
if err := tunnel.Update(); err != nil {
|
||||
return nil, fmt.Errorf("update tunnel heartbeat: %w", err)
|
||||
}
|
||||
|
||||
activeConfig, _ := GetActiveConfigMetaForAgent()
|
||||
|
||||
return &FlaredHeartbeatResponse{
|
||||
ActiveConfig: activeConfig,
|
||||
TunnelSettings: buildRelaySettings(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetFlaredTunnelConfig builds the full tunnel routing config for an OpenFlared client.
|
||||
func GetFlaredTunnelConfig(tunnel *model.Tunnel) (*FlaredTunnelConfigResponse, error) {
|
||||
if tunnel == nil {
|
||||
return nil, fmt.Errorf("tunnel is nil")
|
||||
func GetFlaredTunnelConfig(node *model.Node) (*FlaredTunnelConfigResponse, error) {
|
||||
if node == nil {
|
||||
return nil, fmt.Errorf("node is nil")
|
||||
}
|
||||
|
||||
activeVersion, err := model.GetActiveConfigVersion()
|
||||
@@ -237,7 +204,7 @@ func GetFlaredTunnelConfig(tunnel *model.Tunnel) (*FlaredTunnelConfigResponse, e
|
||||
// Build proxy entries from routes
|
||||
proxies := make([]FlaredProxyEntry, 0)
|
||||
for _, route := range routes {
|
||||
if route.UpstreamType != "tunnel" || route.TunnelID == nil || *route.TunnelID != tunnel.ID {
|
||||
if route.UpstreamType != "tunnel" || route.TunnelNodeID == nil || *route.TunnelNodeID != node.ID {
|
||||
continue
|
||||
}
|
||||
if !route.Enabled {
|
||||
@@ -249,7 +216,7 @@ func GetFlaredTunnelConfig(tunnel *model.Tunnel) (*FlaredTunnelConfigResponse, e
|
||||
}
|
||||
localAddr, localPort := parseTunnelTargetAddr(route.TunnelTargetAddr)
|
||||
proxies = append(proxies, FlaredProxyEntry{
|
||||
Name: fmt.Sprintf("%s-%s", tunnel.TunnelID, sanitizeProxyName(domains[0])),
|
||||
Name: fmt.Sprintf("%s-%s", node.NodeID, sanitizeProxyName(domains[0])),
|
||||
Type: "http",
|
||||
LocalAddr: localAddr,
|
||||
LocalPort: localPort,
|
||||
|
||||
@@ -1,183 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"openflare/model"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TunnelInput struct {
|
||||
Name string `json:"name"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
type TunnelView struct {
|
||||
ID uint `json:"id"`
|
||||
TunnelID string `json:"tunnel_id"`
|
||||
Name string `json:"name"`
|
||||
TunnelToken string `json:"tunnel_token"`
|
||||
Status string `json:"status"`
|
||||
ClientVersion string `json:"client_version"`
|
||||
FrpVersion string `json:"frp_version"`
|
||||
LastSeenAt time.Time `json:"last_seen_at"`
|
||||
LastError string `json:"last_error"`
|
||||
CurrentVersion string `json:"current_version"`
|
||||
CurrentChecksum string `json:"current_checksum"`
|
||||
ConnectedRelays []string `json:"connected_relays"`
|
||||
Remark string `json:"remark"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
func ListTunnels() ([]*TunnelView, error) {
|
||||
tunnels, err := model.ListTunnels()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
views := make([]*TunnelView, 0, len(tunnels))
|
||||
for _, tunnel := range tunnels {
|
||||
views = append(views, buildTunnelView(tunnel))
|
||||
}
|
||||
return views, nil
|
||||
}
|
||||
|
||||
func GetTunnel(id uint) (*TunnelView, error) {
|
||||
tunnel, err := model.GetTunnelByID(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buildTunnelView(tunnel), nil
|
||||
}
|
||||
|
||||
func CreateTunnel(input TunnelInput) (*TunnelView, error) {
|
||||
name := strings.TrimSpace(input.Name)
|
||||
if name == "" {
|
||||
return nil, errors.New("Tunnel 名称不能为空")
|
||||
}
|
||||
tunnelID, err := newTunnelID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tunnelToken, err := newRandomToken()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tunnel := &model.Tunnel{
|
||||
TunnelID: tunnelID,
|
||||
Name: name,
|
||||
TunnelToken: tunnelToken,
|
||||
Status: "offline",
|
||||
Remark: strings.TrimSpace(input.Remark),
|
||||
}
|
||||
if err := tunnel.Insert(); err != nil {
|
||||
if model.IsUniqueConstraintError(err) {
|
||||
return nil, errors.New("Tunnel 标识生成冲突,请重试")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
slog.Info("tunnel created", "name", tunnel.Name, "tunnel_id", tunnel.TunnelID)
|
||||
return buildTunnelView(tunnel), nil
|
||||
}
|
||||
|
||||
func UpdateTunnel(id uint, input TunnelInput) (*TunnelView, error) {
|
||||
tunnel, err := model.GetTunnelByID(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
name := strings.TrimSpace(input.Name)
|
||||
if name == "" {
|
||||
return nil, errors.New("Tunnel 名称不能为空")
|
||||
}
|
||||
tunnel.Name = name
|
||||
tunnel.Remark = strings.TrimSpace(input.Remark)
|
||||
if err := tunnel.Update(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
slog.Info("tunnel updated", "name", tunnel.Name, "tunnel_id", tunnel.TunnelID)
|
||||
return buildTunnelView(tunnel), nil
|
||||
}
|
||||
|
||||
func DeleteTunnel(id uint) error {
|
||||
tunnel, err := model.GetTunnelByID(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
slog.Info("tunnel deleted", "name", tunnel.Name, "tunnel_id", tunnel.TunnelID)
|
||||
return tunnel.Delete()
|
||||
}
|
||||
|
||||
func RotateTunnelToken(id uint) (*TunnelView, error) {
|
||||
tunnel, err := model.GetTunnelByID(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newToken, err := newRandomToken()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tunnel.TunnelToken = newToken
|
||||
if err := tunnel.Update(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
slog.Info("tunnel token rotated", "tunnel_id", tunnel.TunnelID)
|
||||
return buildTunnelView(tunnel), nil
|
||||
}
|
||||
|
||||
func AuthenticateTunnelToken(token string) (*model.Tunnel, error) {
|
||||
token = strings.TrimSpace(token)
|
||||
if token == "" {
|
||||
return nil, errors.New("缺少 Tunnel Token")
|
||||
}
|
||||
tunnel, err := model.GetTunnelByTunnelToken(token)
|
||||
if err != nil {
|
||||
return nil, errors.New("Tunnel Token 无效")
|
||||
}
|
||||
return tunnel, nil
|
||||
}
|
||||
|
||||
func buildTunnelView(tunnel *model.Tunnel) *TunnelView {
|
||||
if tunnel == nil {
|
||||
return nil
|
||||
}
|
||||
relays := decodeTunnelConnectedRelays(tunnel.ConnectedRelays)
|
||||
return &TunnelView{
|
||||
ID: tunnel.ID,
|
||||
TunnelID: tunnel.TunnelID,
|
||||
Name: tunnel.Name,
|
||||
TunnelToken: tunnel.TunnelToken,
|
||||
Status: tunnel.Status,
|
||||
ClientVersion: tunnel.ClientVersion,
|
||||
FrpVersion: tunnel.FrpVersion,
|
||||
LastSeenAt: tunnel.LastSeenAt,
|
||||
LastError: tunnel.LastError,
|
||||
CurrentVersion: tunnel.CurrentVersion,
|
||||
CurrentChecksum: tunnel.CurrentChecksum,
|
||||
ConnectedRelays: relays,
|
||||
Remark: tunnel.Remark,
|
||||
CreatedAt: tunnel.CreatedAt,
|
||||
UpdatedAt: tunnel.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func decodeTunnelConnectedRelays(raw string) []string {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" || raw == "[]" {
|
||||
return make([]string, 0)
|
||||
}
|
||||
var relays []string
|
||||
if err := json.Unmarshal([]byte(raw), &relays); err != nil {
|
||||
return make([]string, 0)
|
||||
}
|
||||
return relays
|
||||
}
|
||||
|
||||
func newTunnelID() (string, error) {
|
||||
token, err := newRandomToken()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "tun-" + token, nil
|
||||
}
|
||||
Reference in New Issue
Block a user