mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-03 23:06:36 +08:00
[功能] 优化节点 IP 解析逻辑,优先使用公网地址并添加相关测试
This commit is contained in:
@@ -24,6 +24,7 @@ func AgentRegister(c *gin.Context) {
|
||||
respondBadRequest(c, "")
|
||||
return
|
||||
}
|
||||
payload.IP = service.ResolveReportedNodeIP(payload.IP, c.Request.RemoteAddr)
|
||||
|
||||
var (
|
||||
result *service.AgentRegistrationResponse
|
||||
@@ -57,6 +58,7 @@ func AgentHeartbeat(c *gin.Context) {
|
||||
respondBadRequest(c, "")
|
||||
return
|
||||
}
|
||||
payload.IP = service.ResolveReportedNodeIP(payload.IP, c.Request.RemoteAddr)
|
||||
|
||||
authNode, ok := c.Get("agent_node")
|
||||
if !ok {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module openflare
|
||||
|
||||
// +heroku goVersion go1.18
|
||||
// +heroku goVersion go1.24
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
|
||||
@@ -212,10 +212,10 @@ func TestPhase2AgentLifecycle(t *testing.T) {
|
||||
"current_version": "",
|
||||
"last_error": "",
|
||||
}
|
||||
resp := performAgentJSONRequestWithToken(t, engine, createdNode.AgentToken, http.MethodPost, "/api/agent/nodes/heartbeat", heartbeatPayload)
|
||||
resp := performAgentJSONRequestWithTokenAndRemote(t, engine, createdNode.AgentToken, http.MethodPost, "/api/agent/nodes/heartbeat", heartbeatPayload, "198.51.100.10:1234")
|
||||
var registeredNode model.Node
|
||||
decodeResponseData(t, resp, ®isteredNode)
|
||||
if registeredNode.IP != "10.0.0.9" || registeredNode.AgentVersion != "0.1.1" || registeredNode.NodeID != createdNode.NodeID {
|
||||
if registeredNode.IP != "198.51.100.10" || registeredNode.AgentVersion != "0.1.1" || registeredNode.NodeID != createdNode.NodeID {
|
||||
t.Fatal("expected heartbeat to update node metadata")
|
||||
}
|
||||
if registeredNode.OpenrestyStatus != service.OpenrestyStatusUnhealthy {
|
||||
@@ -330,6 +330,7 @@ func TestPhase2AgentLifecycle(t *testing.T) {
|
||||
restartHeartbeatReq := httptest.NewRequest(http.MethodPost, "/api/agent/nodes/heartbeat", bytes.NewReader(rawHeartbeatPayload))
|
||||
restartHeartbeatReq.Header.Set("Content-Type", "application/json")
|
||||
restartHeartbeatReq.Header.Set("X-Agent-Token", createdNode.AgentToken)
|
||||
restartHeartbeatReq.RemoteAddr = "198.51.100.10:1234"
|
||||
restartHeartbeatRecorder := httptest.NewRecorder()
|
||||
engine.ServeHTTP(restartHeartbeatRecorder, restartHeartbeatReq)
|
||||
if restartHeartbeatRecorder.Code != http.StatusOK {
|
||||
@@ -533,7 +534,7 @@ func TestPhase2GlobalDiscoveryRegistration(t *testing.T) {
|
||||
t.Fatal("expected global discovery token to be available")
|
||||
}
|
||||
|
||||
resp := performAgentJSONRequestWithToken(t, engine, bootstrap.DiscoveryToken, http.MethodPost, "/api/agent/nodes/register", map[string]any{
|
||||
resp := performAgentJSONRequestWithTokenAndRemote(t, engine, bootstrap.DiscoveryToken, http.MethodPost, "/api/agent/nodes/register", map[string]any{
|
||||
"node_id": "local-node-id",
|
||||
"name": "bulk-edge-1",
|
||||
"ip": "10.0.0.18",
|
||||
@@ -541,7 +542,7 @@ func TestPhase2GlobalDiscoveryRegistration(t *testing.T) {
|
||||
"nginx_version": "1.25.5",
|
||||
"current_version": "",
|
||||
"last_error": "",
|
||||
})
|
||||
}, "203.0.113.18:4321")
|
||||
var registration service.AgentRegistrationResponse
|
||||
decodeResponseData(t, resp, ®istration)
|
||||
if registration.AgentToken == "" || registration.NodeID == "" {
|
||||
@@ -557,9 +558,16 @@ func TestPhase2GlobalDiscoveryRegistration(t *testing.T) {
|
||||
if nodes[0].Name != "bulk-edge-1" || nodes[0].AgentToken != registration.AgentToken || nodes[0].Status != service.NodeStatusOnline {
|
||||
t.Fatal("expected discovered node to be created online with issued agent token")
|
||||
}
|
||||
if nodes[0].IP != "203.0.113.18" {
|
||||
t.Fatalf("expected discovered node to keep public source ip, got %s", nodes[0].IP)
|
||||
}
|
||||
}
|
||||
|
||||
func performAgentJSONRequestWithToken(t *testing.T, engine http.Handler, token string, method string, path string, body any) apiResponse {
|
||||
return performAgentJSONRequestWithTokenAndRemote(t, engine, token, method, path, body, "")
|
||||
}
|
||||
|
||||
func performAgentJSONRequestWithTokenAndRemote(t *testing.T, engine http.Handler, token string, method string, path string, body any, remoteAddr string) apiResponse {
|
||||
t.Helper()
|
||||
var payload []byte
|
||||
var err error
|
||||
@@ -573,6 +581,9 @@ func performAgentJSONRequestWithToken(t *testing.T, engine http.Handler, token s
|
||||
if body != nil {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
if remoteAddr != "" {
|
||||
req.RemoteAddr = remoteAddr
|
||||
}
|
||||
req.Header.Set("X-Agent-Token", token)
|
||||
recorder := httptest.NewRecorder()
|
||||
engine.ServeHTTP(recorder, req)
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"openflare/common"
|
||||
"openflare/model"
|
||||
"openflare/utils/geoip"
|
||||
"openflare/utils/geoip/iputil"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -332,6 +333,29 @@ func cloneCoordinate(value *float64) *float64 {
|
||||
return &cloned
|
||||
}
|
||||
|
||||
func ResolveReportedNodeIP(reportedIP string, remoteAddr string) string {
|
||||
reported := iputil.NormalizeIP(reportedIP)
|
||||
remote := iputil.NormalizeRemoteAddr(remoteAddr)
|
||||
if reported == "" {
|
||||
return remote
|
||||
}
|
||||
if !shouldPreferRemoteNodeIP(reported) {
|
||||
return reported
|
||||
}
|
||||
if isPublicNodeIP(remote) {
|
||||
return remote
|
||||
}
|
||||
return reported
|
||||
}
|
||||
|
||||
func shouldPreferRemoteNodeIP(ip string) bool {
|
||||
return !isPublicNodeIP(ip)
|
||||
}
|
||||
|
||||
func isPublicNodeIP(raw string) bool {
|
||||
return iputil.IsPublicString(raw)
|
||||
}
|
||||
|
||||
func buildNodeAgentReleaseView(node *model.Node, release *githubReleaseResponse, channel ReleaseChannel) *NodeAgentReleaseInfo {
|
||||
currentVersion := strings.TrimSpace(node.AgentVersion)
|
||||
view := &NodeAgentReleaseInfo{
|
||||
@@ -428,6 +452,9 @@ func validateAgentNodePayload(payload AgentNodePayload) error {
|
||||
if payload.IP == "" {
|
||||
return errors.New("ip 不能为空")
|
||||
}
|
||||
if net.ParseIP(payload.IP) == nil {
|
||||
return errors.New("ip 格式无效")
|
||||
}
|
||||
if payload.AgentVersion == "" {
|
||||
return errors.New("agent_version 不能为空")
|
||||
}
|
||||
|
||||
@@ -328,6 +328,27 @@ func TestUpdateNodeCanChangeIPAndAutoResolveGeo(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveReportedNodeIPPrefersPublicRemoteAddr(t *testing.T) {
|
||||
resolved := ResolveReportedNodeIP("10.0.0.8", "198.51.100.20:9000")
|
||||
if resolved != "198.51.100.20" {
|
||||
t.Fatalf("expected public remote ip to override private reported ip, got %q", resolved)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveReportedNodeIPKeepsPublicReportedAddr(t *testing.T) {
|
||||
resolved := ResolveReportedNodeIP("8.8.8.8", "198.51.100.20:9000")
|
||||
if resolved != "8.8.8.8" {
|
||||
t.Fatalf("expected reported public ip to be preserved, got %q", resolved)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveReportedNodeIPKeepsPrivateReportedAddrWhenRemoteIsPrivate(t *testing.T) {
|
||||
resolved := ResolveReportedNodeIP("10.0.0.8", "172.16.1.10:9000")
|
||||
if resolved != "10.0.0.8" {
|
||||
t.Fatalf("expected private reported ip to be preserved when remote is also private, got %q", resolved)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHeartbeatNodeResolvesGeoMetadataFromIPWhenNotManuallyOverridden(t *testing.T) {
|
||||
setupServiceTestDB(t)
|
||||
withFakeGeoIPProvider(t, &geoip.GeoInfo{
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package iputil
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func NormalizeIP(raw string) string {
|
||||
trimmed := strings.TrimSpace(raw)
|
||||
if trimmed == "" {
|
||||
return ""
|
||||
}
|
||||
ip := net.ParseIP(trimmed)
|
||||
if ip == nil {
|
||||
return ""
|
||||
}
|
||||
if ipv4 := ip.To4(); ipv4 != nil {
|
||||
return ipv4.String()
|
||||
}
|
||||
return ip.String()
|
||||
}
|
||||
|
||||
func NormalizeRemoteAddr(remoteAddr string) string {
|
||||
trimmed := strings.TrimSpace(remoteAddr)
|
||||
if trimmed == "" {
|
||||
return ""
|
||||
}
|
||||
if host, _, err := net.SplitHostPort(trimmed); err == nil {
|
||||
return NormalizeIP(host)
|
||||
}
|
||||
return NormalizeIP(trimmed)
|
||||
}
|
||||
|
||||
func IsPublic(ip net.IP) bool {
|
||||
if ip == nil {
|
||||
return false
|
||||
}
|
||||
if ipv4 := ip.To4(); ipv4 != nil {
|
||||
ip = ipv4
|
||||
}
|
||||
if !ip.IsGlobalUnicast() || ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() || ip.IsMulticast() || ip.IsUnspecified() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func IsPublicString(raw string) bool {
|
||||
ip := net.ParseIP(strings.TrimSpace(raw))
|
||||
return IsPublic(ip)
|
||||
}
|
||||
|
||||
func Score(ip net.IP) int {
|
||||
if ip == nil {
|
||||
return -1
|
||||
}
|
||||
if ipv4 := ip.To4(); ipv4 != nil {
|
||||
ip = ipv4
|
||||
}
|
||||
if !ip.IsGlobalUnicast() || ip.IsLoopback() || ip.IsMulticast() || ip.IsUnspecified() {
|
||||
return -1
|
||||
}
|
||||
if IsPublic(ip) {
|
||||
return 2
|
||||
}
|
||||
return 1
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package iputil
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNormalizeIP(t *testing.T) {
|
||||
if got := NormalizeIP(" 8.8.8.8 "); got != "8.8.8.8" {
|
||||
t.Fatalf("unexpected normalized ipv4: %q", got)
|
||||
}
|
||||
if got := NormalizeIP("[::1]"); got != "" {
|
||||
t.Fatalf("expected invalid bracketed host to be rejected, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeRemoteAddr(t *testing.T) {
|
||||
if got := NormalizeRemoteAddr("203.0.113.10:8443"); got != "203.0.113.10" {
|
||||
t.Fatalf("unexpected remote addr normalization: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsPublic(t *testing.T) {
|
||||
if !IsPublic(net.ParseIP("8.8.8.8")) {
|
||||
t.Fatal("expected public ip to be detected")
|
||||
}
|
||||
if IsPublic(net.ParseIP("10.0.0.8")) {
|
||||
t.Fatal("expected private ip to be rejected")
|
||||
}
|
||||
if IsPublic(net.ParseIP("127.0.0.1")) {
|
||||
t.Fatal("expected loopback ip to be rejected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestScore(t *testing.T) {
|
||||
if got := Score(net.ParseIP("8.8.8.8")); got != 2 {
|
||||
t.Fatalf("unexpected score for public ip: %d", got)
|
||||
}
|
||||
if got := Score(net.ParseIP("10.0.0.8")); got != 1 {
|
||||
t.Fatalf("unexpected score for private ip: %d", got)
|
||||
}
|
||||
if got := Score(net.ParseIP("127.0.0.1")); got != -1 {
|
||||
t.Fatalf("unexpected score for loopback ip: %d", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user