mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-08 00:26:37 +08:00
[优化] 增加 IP 处理逻辑并优化心跳负载
This commit is contained in:
@@ -191,6 +191,7 @@ func normalizeRelayStatus(status string) string {
|
|||||||
type FlaredHeartbeatPayload struct {
|
type FlaredHeartbeatPayload struct {
|
||||||
ClientVersion string `json:"client_version"`
|
ClientVersion string `json:"client_version"`
|
||||||
FrpVersion string `json:"frp_version"`
|
FrpVersion string `json:"frp_version"`
|
||||||
|
IP string `json:"ip"`
|
||||||
TunnelStatus string `json:"tunnel_status"`
|
TunnelStatus string `json:"tunnel_status"`
|
||||||
ConnectedRelays []FlaredConnectedRelay `json:"connected_relays"`
|
ConnectedRelays []FlaredConnectedRelay `json:"connected_relays"`
|
||||||
CurrentVersion string `json:"current_version"`
|
CurrentVersion string `json:"current_version"`
|
||||||
@@ -200,6 +201,7 @@ type FlaredHeartbeatPayload struct {
|
|||||||
func normalizeFlaredHeartbeatPayload(payload FlaredHeartbeatPayload) FlaredHeartbeatPayload {
|
func normalizeFlaredHeartbeatPayload(payload FlaredHeartbeatPayload) FlaredHeartbeatPayload {
|
||||||
payload.ClientVersion = strings.TrimSpace(payload.ClientVersion)
|
payload.ClientVersion = strings.TrimSpace(payload.ClientVersion)
|
||||||
payload.FrpVersion = strings.TrimSpace(payload.FrpVersion)
|
payload.FrpVersion = strings.TrimSpace(payload.FrpVersion)
|
||||||
|
payload.IP = strings.TrimSpace(payload.IP)
|
||||||
payload.TunnelStatus = strings.ToLower(strings.TrimSpace(payload.TunnelStatus))
|
payload.TunnelStatus = strings.ToLower(strings.TrimSpace(payload.TunnelStatus))
|
||||||
payload.CurrentVersion = strings.TrimSpace(payload.CurrentVersion)
|
payload.CurrentVersion = strings.TrimSpace(payload.CurrentVersion)
|
||||||
payload.CurrentChecksum = strings.TrimSpace(payload.CurrentChecksum)
|
payload.CurrentChecksum = strings.TrimSpace(payload.CurrentChecksum)
|
||||||
@@ -255,8 +257,23 @@ func HeartbeatFlared(node *model.Node, payload FlaredHeartbeatPayload) (*FlaredH
|
|||||||
node.CurrentVersion = payload.CurrentVersion
|
node.CurrentVersion = payload.CurrentVersion
|
||||||
node.LastSeenAt = now
|
node.LastSeenAt = now
|
||||||
node.Status = NodeStatusOnline
|
node.Status = NodeStatusOnline
|
||||||
|
|
||||||
|
if !node.IPManualOverride && payload.IP != "" && previous.IP != payload.IP {
|
||||||
|
changes["ip"] = payload.IP
|
||||||
|
node.IP = payload.IP
|
||||||
|
}
|
||||||
|
|
||||||
if !node.GeoManualOverride {
|
if !node.GeoManualOverride {
|
||||||
applyGeoInfoFromIP(node, node.IP)
|
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 {
|
if len(changes) > 0 {
|
||||||
|
|||||||
@@ -150,6 +150,7 @@ func TestHeartbeatFlaredPersistsRuntime(t *testing.T) {
|
|||||||
resp, err := HeartbeatFlared(node, FlaredHeartbeatPayload{
|
resp, err := HeartbeatFlared(node, FlaredHeartbeatPayload{
|
||||||
ClientVersion: " v0.2.0 ",
|
ClientVersion: " v0.2.0 ",
|
||||||
FrpVersion: " 0.61.1 ",
|
FrpVersion: " 0.61.1 ",
|
||||||
|
IP: " 192.168.1.10 ",
|
||||||
TunnelStatus: " RUNNING ",
|
TunnelStatus: " RUNNING ",
|
||||||
ConnectedRelays: []FlaredConnectedRelay{
|
ConnectedRelays: []FlaredConnectedRelay{
|
||||||
{RelayNodeID: " node-relay-1 ", Status: " HEALTHY ", ProxyCount: 3},
|
{RelayNodeID: " node-relay-1 ", Status: " HEALTHY ", ProxyCount: 3},
|
||||||
@@ -185,12 +186,39 @@ func TestHeartbeatFlaredPersistsRuntime(t *testing.T) {
|
|||||||
if updated.ExtVersion != "0.61.1" {
|
if updated.ExtVersion != "0.61.1" {
|
||||||
t.Fatalf("expected frp_version to be trimmed and stored, got %q", updated.ExtVersion)
|
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" {
|
if updated.CurrentVersion != "v1" {
|
||||||
t.Fatalf("expected current_version to be stored, got %q", updated.CurrentVersion)
|
t.Fatalf("expected current_version to be stored, got %q", updated.CurrentVersion)
|
||||||
}
|
}
|
||||||
if updated.LastSeenAt.IsZero() {
|
if updated.LastSeenAt.IsZero() {
|
||||||
t.Fatal("expected last_seen_at to be updated")
|
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) {
|
func TestHeartbeatFlaredTrimsAndFiltersRelays(t *testing.T) {
|
||||||
|
|||||||
@@ -3,12 +3,20 @@ package heartbeat
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"openflare-flared/internal/config"
|
"openflare-flared/internal/config"
|
||||||
"openflare-flared/internal/frpc"
|
"openflare-flared/internal/frpc"
|
||||||
"openflare-flared/internal/httpclient"
|
"openflare-flared/internal/httpclient"
|
||||||
"openflare/service"
|
"openflare/service"
|
||||||
|
"openflare/utils/geoip"
|
||||||
|
"openflare/utils/geoip/iputil"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
lookupOutboundIP = geoip.GetOutboundIP
|
||||||
|
lookupLocalIP = detectLocalNodeIP
|
||||||
)
|
)
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
@@ -45,9 +53,12 @@ func (s *Service) Run(ctx context.Context) {
|
|||||||
func (s *Service) doHeartbeat(ctx context.Context) {
|
func (s *Service) doHeartbeat(ctx context.Context) {
|
||||||
slog.Debug("sending flared heartbeat")
|
slog.Debug("sending flared heartbeat")
|
||||||
|
|
||||||
|
ip := detectNodeIP()
|
||||||
|
|
||||||
payload := service.FlaredHeartbeatPayload{
|
payload := service.FlaredHeartbeatPayload{
|
||||||
ClientVersion: config.Version,
|
ClientVersion: config.Version,
|
||||||
FrpVersion: s.frpcManager.GetVersion(),
|
FrpVersion: s.frpcManager.GetVersion(),
|
||||||
|
IP: ip,
|
||||||
TunnelStatus: "running", // TODO implement proper status tracking
|
TunnelStatus: "running", // TODO implement proper status tracking
|
||||||
ConnectedRelays: s.frpcManager.GetConnectedRelays(),
|
ConnectedRelays: s.frpcManager.GetConnectedRelays(),
|
||||||
CurrentVersion: s.frpcManager.GetCurrentConfigVersion(),
|
CurrentVersion: s.frpcManager.GetCurrentConfigVersion(),
|
||||||
@@ -61,3 +72,57 @@ func (s *Service) doHeartbeat(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
slog.Debug("flared heartbeat succeeded")
|
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
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user