mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-08 16:46:37 +08:00
[功能] 优化节点 IP 解析逻辑,优先使用公网地址并添加相关测试
This commit is contained in:
+2
-1
@@ -131,7 +131,7 @@ Agent 当前支持:
|
|||||||
| `agent_token` | 节点专属认证 Token | 与 `discovery_token` 二选一 | 空 |
|
| `agent_token` | 节点专属认证 Token | 与 `discovery_token` 二选一 | 空 |
|
||||||
| `discovery_token` | 首次自动注册使用的全局 Token | 与 `agent_token` 二选一 | 空 |
|
| `discovery_token` | 首次自动注册使用的全局 Token | 与 `agent_token` 二选一 | 空 |
|
||||||
| `node_name` | 节点名称 | 否 | 自动使用主机名 |
|
| `node_name` | 节点名称 | 否 | 自动使用主机名 |
|
||||||
| `node_ip` | 节点 IP | 否 | 自动探测 |
|
| `node_ip` | 节点 IP | 否 | 自动探测,优先选择公网 IPv4;仅无公网地址时退回可用内网地址 |
|
||||||
| `openresty_path` | 本机 OpenResty 路径 | 否 | 空,未设置时走 Docker 模式 |
|
| `openresty_path` | 本机 OpenResty 路径 | 否 | 空,未设置时走 Docker 模式 |
|
||||||
| `openresty_container_name` | Docker 模式下的容器名 | 否 | `openflare-openresty` |
|
| `openresty_container_name` | Docker 模式下的容器名 | 否 | `openflare-openresty` |
|
||||||
| `openresty_docker_image` | Docker 模式下的镜像 | 否 | `openresty/openresty:alpine` |
|
| `openresty_docker_image` | Docker 模式下的镜像 | 否 | `openresty/openresty:alpine` |
|
||||||
@@ -155,6 +155,7 @@ Agent 当前支持:
|
|||||||
* `agent_token` 与 `discovery_token` 不能同时为空
|
* `agent_token` 与 `discovery_token` 不能同时为空
|
||||||
* `heartbeat_interval` 与 `request_timeout` 支持毫秒整数或 Go duration 字符串
|
* `heartbeat_interval` 与 `request_timeout` 支持毫秒整数或 Go duration 字符串
|
||||||
* 未配置 `openresty_path` 时默认使用 Docker OpenResty 模式
|
* 未配置 `openresty_path` 时默认使用 Docker OpenResty 模式
|
||||||
|
* Agent 自动探测到私网 `node_ip` 时,Server 会在注册/心跳阶段优先保留 Agent 直连来源的公网地址,避免 NAT/多网卡场景误登记内网网卡地址
|
||||||
|
|
||||||
## 3. 维护要求
|
## 3. 维护要求
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
### 1.2 Agent
|
### 1.2 Agent
|
||||||
|
|
||||||
* Go 1.23+
|
* Go 1.24+
|
||||||
* 对 Agent 数据目录有写权限
|
* 对 Agent 数据目录有写权限
|
||||||
* 本机模式下可执行 `openresty -t` 与 `openresty -s reload`
|
* 本机模式下可执行 `openresty -t` 与 `openresty -s reload`
|
||||||
* Docker 模式下具备 Docker 执行权限
|
* Docker 模式下具备 Docker 执行权限
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
`openflare_agent` 继续作为 Go 单体程序:
|
`openflare_agent` 继续作为 Go 单体程序:
|
||||||
|
|
||||||
* Go 1.23+
|
* Go 1.24+
|
||||||
* 单二进制
|
* 单二进制
|
||||||
* 节点本地执行
|
* 节点本地执行
|
||||||
* `openresty_path` 优先
|
* `openresty_path` 优先
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
module openflare-agent
|
module openflare-agent
|
||||||
|
|
||||||
go 1.23.0
|
go 1.24.0
|
||||||
|
|
||||||
|
require openflare v0.0.0
|
||||||
|
|
||||||
|
replace openflare => ../openflare_server
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
|
"openflare/utils/geoip/iputil"
|
||||||
"os"
|
"os"
|
||||||
pathpkg "path"
|
pathpkg "path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -331,6 +332,8 @@ func detectNodeIP() string {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
bestIP := ""
|
||||||
|
bestPriority := -1
|
||||||
for _, iface := range interfaces {
|
for _, iface := range interfaces {
|
||||||
if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 {
|
if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 {
|
||||||
continue
|
continue
|
||||||
@@ -344,11 +347,27 @@ func detectNodeIP() string {
|
|||||||
if !ok || ipNet.IP == nil || ipNet.IP.IsLoopback() {
|
if !ok || ipNet.IP == nil || ipNet.IP.IsLoopback() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ipv4 := ipNet.IP.To4()
|
ipv4 := normalizeIPv4(ipNet.IP)
|
||||||
if ipv4 != nil {
|
priority := nodeIPPriority(ipv4)
|
||||||
return ipv4.String()
|
if priority > bestPriority {
|
||||||
|
bestIP = ipv4.String()
|
||||||
|
bestPriority = priority
|
||||||
|
}
|
||||||
|
if bestPriority == 2 {
|
||||||
|
return bestIP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ""
|
return bestIP
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeIPv4(ip net.IP) net.IP {
|
||||||
|
if ip == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ip.To4()
|
||||||
|
}
|
||||||
|
|
||||||
|
func nodeIPPriority(ip net.IP) int {
|
||||||
|
return iputil.Score(ip)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package config
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -326,3 +327,49 @@ func TestInitialAuthToken(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNodeIPPriority(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
ip string
|
||||||
|
expected int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "public ipv4 preferred",
|
||||||
|
ip: "8.8.8.8",
|
||||||
|
expected: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "private ipv4 fallback",
|
||||||
|
ip: "10.0.0.8",
|
||||||
|
expected: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "link local ignored",
|
||||||
|
ip: "169.254.1.10",
|
||||||
|
expected: -1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "loopback ignored",
|
||||||
|
ip: "127.0.0.1",
|
||||||
|
expected: -1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil ignored",
|
||||||
|
ip: "",
|
||||||
|
expected: -1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var parsed net.IP
|
||||||
|
if tt.ip != "" {
|
||||||
|
parsed = net.ParseIP(tt.ip)
|
||||||
|
}
|
||||||
|
if got := nodeIPPriority(parsed); got != tt.expected {
|
||||||
|
t.Fatalf("unexpected priority for %q: got %d want %d", tt.ip, got, tt.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ func AgentRegister(c *gin.Context) {
|
|||||||
respondBadRequest(c, "")
|
respondBadRequest(c, "")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
payload.IP = service.ResolveReportedNodeIP(payload.IP, c.Request.RemoteAddr)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
result *service.AgentRegistrationResponse
|
result *service.AgentRegistrationResponse
|
||||||
@@ -57,6 +58,7 @@ func AgentHeartbeat(c *gin.Context) {
|
|||||||
respondBadRequest(c, "")
|
respondBadRequest(c, "")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
payload.IP = service.ResolveReportedNodeIP(payload.IP, c.Request.RemoteAddr)
|
||||||
|
|
||||||
authNode, ok := c.Get("agent_node")
|
authNode, ok := c.Get("agent_node")
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
module openflare
|
module openflare
|
||||||
|
|
||||||
// +heroku goVersion go1.18
|
// +heroku goVersion go1.24
|
||||||
go 1.24.0
|
go 1.24.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|||||||
@@ -212,10 +212,10 @@ func TestPhase2AgentLifecycle(t *testing.T) {
|
|||||||
"current_version": "",
|
"current_version": "",
|
||||||
"last_error": "",
|
"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
|
var registeredNode model.Node
|
||||||
decodeResponseData(t, resp, ®isteredNode)
|
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")
|
t.Fatal("expected heartbeat to update node metadata")
|
||||||
}
|
}
|
||||||
if registeredNode.OpenrestyStatus != service.OpenrestyStatusUnhealthy {
|
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 := httptest.NewRequest(http.MethodPost, "/api/agent/nodes/heartbeat", bytes.NewReader(rawHeartbeatPayload))
|
||||||
restartHeartbeatReq.Header.Set("Content-Type", "application/json")
|
restartHeartbeatReq.Header.Set("Content-Type", "application/json")
|
||||||
restartHeartbeatReq.Header.Set("X-Agent-Token", createdNode.AgentToken)
|
restartHeartbeatReq.Header.Set("X-Agent-Token", createdNode.AgentToken)
|
||||||
|
restartHeartbeatReq.RemoteAddr = "198.51.100.10:1234"
|
||||||
restartHeartbeatRecorder := httptest.NewRecorder()
|
restartHeartbeatRecorder := httptest.NewRecorder()
|
||||||
engine.ServeHTTP(restartHeartbeatRecorder, restartHeartbeatReq)
|
engine.ServeHTTP(restartHeartbeatRecorder, restartHeartbeatReq)
|
||||||
if restartHeartbeatRecorder.Code != http.StatusOK {
|
if restartHeartbeatRecorder.Code != http.StatusOK {
|
||||||
@@ -533,7 +534,7 @@ func TestPhase2GlobalDiscoveryRegistration(t *testing.T) {
|
|||||||
t.Fatal("expected global discovery token to be available")
|
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",
|
"node_id": "local-node-id",
|
||||||
"name": "bulk-edge-1",
|
"name": "bulk-edge-1",
|
||||||
"ip": "10.0.0.18",
|
"ip": "10.0.0.18",
|
||||||
@@ -541,7 +542,7 @@ func TestPhase2GlobalDiscoveryRegistration(t *testing.T) {
|
|||||||
"nginx_version": "1.25.5",
|
"nginx_version": "1.25.5",
|
||||||
"current_version": "",
|
"current_version": "",
|
||||||
"last_error": "",
|
"last_error": "",
|
||||||
})
|
}, "203.0.113.18:4321")
|
||||||
var registration service.AgentRegistrationResponse
|
var registration service.AgentRegistrationResponse
|
||||||
decodeResponseData(t, resp, ®istration)
|
decodeResponseData(t, resp, ®istration)
|
||||||
if registration.AgentToken == "" || registration.NodeID == "" {
|
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 {
|
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")
|
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 {
|
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()
|
t.Helper()
|
||||||
var payload []byte
|
var payload []byte
|
||||||
var err error
|
var err error
|
||||||
@@ -573,6 +581,9 @@ func performAgentJSONRequestWithToken(t *testing.T, engine http.Handler, token s
|
|||||||
if body != nil {
|
if body != nil {
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
}
|
}
|
||||||
|
if remoteAddr != "" {
|
||||||
|
req.RemoteAddr = remoteAddr
|
||||||
|
}
|
||||||
req.Header.Set("X-Agent-Token", token)
|
req.Header.Set("X-Agent-Token", token)
|
||||||
recorder := httptest.NewRecorder()
|
recorder := httptest.NewRecorder()
|
||||||
engine.ServeHTTP(recorder, req)
|
engine.ServeHTTP(recorder, req)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"openflare/common"
|
"openflare/common"
|
||||||
"openflare/model"
|
"openflare/model"
|
||||||
"openflare/utils/geoip"
|
"openflare/utils/geoip"
|
||||||
|
"openflare/utils/geoip/iputil"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -332,6 +333,29 @@ func cloneCoordinate(value *float64) *float64 {
|
|||||||
return &cloned
|
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 {
|
func buildNodeAgentReleaseView(node *model.Node, release *githubReleaseResponse, channel ReleaseChannel) *NodeAgentReleaseInfo {
|
||||||
currentVersion := strings.TrimSpace(node.AgentVersion)
|
currentVersion := strings.TrimSpace(node.AgentVersion)
|
||||||
view := &NodeAgentReleaseInfo{
|
view := &NodeAgentReleaseInfo{
|
||||||
@@ -428,6 +452,9 @@ func validateAgentNodePayload(payload AgentNodePayload) error {
|
|||||||
if payload.IP == "" {
|
if payload.IP == "" {
|
||||||
return errors.New("ip 不能为空")
|
return errors.New("ip 不能为空")
|
||||||
}
|
}
|
||||||
|
if net.ParseIP(payload.IP) == nil {
|
||||||
|
return errors.New("ip 格式无效")
|
||||||
|
}
|
||||||
if payload.AgentVersion == "" {
|
if payload.AgentVersion == "" {
|
||||||
return errors.New("agent_version 不能为空")
|
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) {
|
func TestHeartbeatNodeResolvesGeoMetadataFromIPWhenNotManuallyOverridden(t *testing.T) {
|
||||||
setupServiceTestDB(t)
|
setupServiceTestDB(t)
|
||||||
withFakeGeoIPProvider(t, &geoip.GeoInfo{
|
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