[优化] 增加 IP 处理逻辑并优化心跳负载

This commit is contained in:
ryan
2026-06-01 21:16:14 +08:00
parent 92df7d5c84
commit e43312d4c6
3 changed files with 110 additions and 0 deletions
+17
View File
@@ -191,6 +191,7 @@ func normalizeRelayStatus(status string) string {
type FlaredHeartbeatPayload struct {
ClientVersion string `json:"client_version"`
FrpVersion string `json:"frp_version"`
IP string `json:"ip"`
TunnelStatus string `json:"tunnel_status"`
ConnectedRelays []FlaredConnectedRelay `json:"connected_relays"`
CurrentVersion string `json:"current_version"`
@@ -200,6 +201,7 @@ type FlaredHeartbeatPayload struct {
func normalizeFlaredHeartbeatPayload(payload FlaredHeartbeatPayload) FlaredHeartbeatPayload {
payload.ClientVersion = strings.TrimSpace(payload.ClientVersion)
payload.FrpVersion = strings.TrimSpace(payload.FrpVersion)
payload.IP = strings.TrimSpace(payload.IP)
payload.TunnelStatus = strings.ToLower(strings.TrimSpace(payload.TunnelStatus))
payload.CurrentVersion = strings.TrimSpace(payload.CurrentVersion)
payload.CurrentChecksum = strings.TrimSpace(payload.CurrentChecksum)
@@ -255,8 +257,23 @@ func HeartbeatFlared(node *model.Node, payload FlaredHeartbeatPayload) (*FlaredH
node.CurrentVersion = payload.CurrentVersion
node.LastSeenAt = now
node.Status = NodeStatusOnline
if !node.IPManualOverride && payload.IP != "" && previous.IP != payload.IP {
changes["ip"] = payload.IP
node.IP = payload.IP
}
if !node.GeoManualOverride {
applyGeoInfoFromIP(node, node.IP)
if previous.GeoName != node.GeoName {
changes["geo_name"] = node.GeoName
}
if !coordinatesEqual(previous.GeoLatitude, node.GeoLatitude) {
changes["geo_latitude"] = node.GeoLatitude
}
if !coordinatesEqual(previous.GeoLongitude, node.GeoLongitude) {
changes["geo_longitude"] = node.GeoLongitude
}
}
if len(changes) > 0 {
+28
View File
@@ -150,6 +150,7 @@ func TestHeartbeatFlaredPersistsRuntime(t *testing.T) {
resp, err := HeartbeatFlared(node, FlaredHeartbeatPayload{
ClientVersion: " v0.2.0 ",
FrpVersion: " 0.61.1 ",
IP: " 192.168.1.10 ",
TunnelStatus: " RUNNING ",
ConnectedRelays: []FlaredConnectedRelay{
{RelayNodeID: " node-relay-1 ", Status: " HEALTHY ", ProxyCount: 3},
@@ -185,12 +186,39 @@ func TestHeartbeatFlaredPersistsRuntime(t *testing.T) {
if updated.ExtVersion != "0.61.1" {
t.Fatalf("expected frp_version to be trimmed and stored, got %q", updated.ExtVersion)
}
if updated.IP != "192.168.1.10" {
t.Fatalf("expected IP to be trimmed and stored, got %q", updated.IP)
}
if updated.CurrentVersion != "v1" {
t.Fatalf("expected current_version to be stored, got %q", updated.CurrentVersion)
}
if updated.LastSeenAt.IsZero() {
t.Fatal("expected last_seen_at to be updated")
}
// Test IPManualOverride
updated.IPManualOverride = true
if err := updated.Update(); err != nil {
t.Fatalf("failed to lock IP: %v", err)
}
_, err = HeartbeatFlared(updated, FlaredHeartbeatPayload{
ClientVersion: "v0.2.0",
FrpVersion: "0.61.1",
IP: "10.0.0.99",
TunnelStatus: "running",
})
if err != nil {
t.Fatalf("second HeartbeatFlared failed: %v", err)
}
lockedNode, err := model.GetNodeByNodeID(node.NodeID)
if err != nil {
t.Fatalf("failed to reload locked node: %v", err)
}
if lockedNode.IP != "192.168.1.10" {
t.Fatalf("expected IP to stay locked at 192.168.1.10, but got %q", lockedNode.IP)
}
}
func TestHeartbeatFlaredTrimsAndFiltersRelays(t *testing.T) {
+65
View File
@@ -3,12 +3,20 @@ package heartbeat
import (
"context"
"log/slog"
"net"
"time"
"openflare-flared/internal/config"
"openflare-flared/internal/frpc"
"openflare-flared/internal/httpclient"
"openflare/service"
"openflare/utils/geoip"
"openflare/utils/geoip/iputil"
)
var (
lookupOutboundIP = geoip.GetOutboundIP
lookupLocalIP = detectLocalNodeIP
)
type Service struct {
@@ -45,9 +53,12 @@ func (s *Service) Run(ctx context.Context) {
func (s *Service) doHeartbeat(ctx context.Context) {
slog.Debug("sending flared heartbeat")
ip := detectNodeIP()
payload := service.FlaredHeartbeatPayload{
ClientVersion: config.Version,
FrpVersion: s.frpcManager.GetVersion(),
IP: ip,
TunnelStatus: "running", // TODO implement proper status tracking
ConnectedRelays: s.frpcManager.GetConnectedRelays(),
CurrentVersion: s.frpcManager.GetCurrentConfigVersion(),
@@ -61,3 +72,57 @@ func (s *Service) doHeartbeat(ctx context.Context) {
}
slog.Debug("flared heartbeat succeeded")
}
func detectNodeIP() string {
if ip := detectOutboundNodeIP(); ip != "" {
return ip
}
return lookupLocalIP()
}
func detectOutboundNodeIP() string {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
ip, err := lookupOutboundIP(ctx)
if err != nil || ip == nil {
return ""
}
return ip.String()
}
func detectLocalNodeIP() string {
interfaces, err := net.Interfaces()
if err != nil {
return ""
}
bestIP := ""
bestPriority := -1
for _, iface := range interfaces {
if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 {
continue
}
addrs, err := iface.Addrs()
if err != nil {
continue
}
for _, addr := range addrs {
ipNet, ok := addr.(*net.IPNet)
if !ok || ipNet.IP == nil || ipNet.IP.IsLoopback() {
continue
}
ipv4 := ipNet.IP.To4()
if ipv4 == nil {
continue
}
priority := iputil.Score(ipv4)
if priority > bestPriority {
bestIP = ipv4.String()
bestPriority = priority
}
if bestPriority == 2 {
return bestIP
}
}
}
return bestIP
}