mirror of
https://gitee.com/milvus-io/milvus.git
synced 2026-02-02 01:06:41 +08:00
fix: set the right priority for ipv4 and ipv6 network (#45595)
related to #45382 Signed-off-by: xiaofanluan <xiaofan.luan@zilliz.com>
This commit is contained in:
parent
925d5875fd
commit
f166dd817a
@ -1035,6 +1035,9 @@ common:
|
||||
useVectorAsClusteringKey: false # if true, do clustering compaction and segment prune on vector field
|
||||
enableVectorClusteringKey: false # if true, enable vector clustering key and vector clustering compaction
|
||||
localRPCEnabled: false # enable local rpc for internal communication when mix or standalone mode.
|
||||
# Prefer IPv6 addresses when automatically selecting the local IP.
|
||||
# If enabled, IPv6 ULA/global addresses will be prioritized ahead of IPv4.
|
||||
preferIPv6: false
|
||||
sync:
|
||||
taskPoolReleaseTimeoutSeconds: 60 # The maximum time to wait for the task to finish and release resources in the pool
|
||||
enabledOptimizeExpr: true # Indicates whether to enable optimize expr
|
||||
|
||||
@ -30,6 +30,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"go.uber.org/atomic"
|
||||
"go.uber.org/zap"
|
||||
"google.golang.org/grpc/codes"
|
||||
grpcStatus "google.golang.org/grpc/status"
|
||||
@ -46,6 +47,9 @@ const (
|
||||
ControlChannelSuffix = "vcchan" // is the suffix of the virtual control channel
|
||||
)
|
||||
|
||||
// PreferIPv6LocalIP controls whether IPv6 addresses are preferred when selecting local IPs.
|
||||
var PreferIPv6LocalIP atomic.Bool
|
||||
|
||||
// CheckGrpcReady wait for context timeout, or wait 100ms then send nil to targetCh
|
||||
func CheckGrpcReady(ctx context.Context, targetCh chan error) {
|
||||
timer := time.NewTimer(100 * time.Millisecond)
|
||||
@ -98,34 +102,114 @@ func GetIP(ip string) string {
|
||||
// GetLocalIP return the local ip address
|
||||
func GetLocalIP() string {
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err == nil {
|
||||
ip := GetValidLocalIP(addrs)
|
||||
if len(ip) != 0 {
|
||||
return ip
|
||||
}
|
||||
if err != nil {
|
||||
log.Warn("Failed to get interface addresses", zap.Error(err))
|
||||
return "127.0.0.1"
|
||||
}
|
||||
|
||||
preferIPv6 := PreferIPv6LocalIP.Load()
|
||||
|
||||
ip := getValidLocalIP(addrs, preferIPv6)
|
||||
if len(ip) != 0 {
|
||||
return ip
|
||||
}
|
||||
|
||||
log.Warn("No valid local IP found, falling back to loopback")
|
||||
return "127.0.0.1"
|
||||
}
|
||||
|
||||
// GetValidLocalIP return the first valid local ip address
|
||||
func GetValidLocalIP(addrs []net.Addr) string {
|
||||
// Search for valid ipv4 addresses
|
||||
return getValidLocalIP(addrs, PreferIPv6LocalIP.Load())
|
||||
}
|
||||
|
||||
type ipCategory int
|
||||
|
||||
const (
|
||||
ipCategoryIPv4Public ipCategory = iota
|
||||
ipCategoryIPv4Private
|
||||
ipCategoryIPv6Public
|
||||
ipCategoryIPv6Private
|
||||
ipCategoryIPv6LinkLocal
|
||||
)
|
||||
|
||||
var (
|
||||
// Default priority: private first, IPv4 first
|
||||
defaultIPPriority = []ipCategory{
|
||||
ipCategoryIPv4Private,
|
||||
ipCategoryIPv4Public,
|
||||
ipCategoryIPv6Private,
|
||||
ipCategoryIPv6Public,
|
||||
ipCategoryIPv6LinkLocal,
|
||||
}
|
||||
// When IPv6 is preferred: private first, IPv6 first
|
||||
preferIPv6Priority = []ipCategory{
|
||||
ipCategoryIPv6Private,
|
||||
ipCategoryIPv6Public,
|
||||
ipCategoryIPv4Private,
|
||||
ipCategoryIPv4Public,
|
||||
ipCategoryIPv6LinkLocal,
|
||||
}
|
||||
)
|
||||
|
||||
func getValidLocalIP(addrs []net.Addr, preferIPv6 bool) string {
|
||||
candidates := make(map[ipCategory]net.IP, 5)
|
||||
|
||||
for _, addr := range addrs {
|
||||
ipaddr, ok := addr.(*net.IPNet)
|
||||
if ok && ipaddr.IP.IsGlobalUnicast() && ipaddr.IP.To4() != nil {
|
||||
return ipaddr.IP.String()
|
||||
ipNet, ok := addr.(*net.IPNet)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
category, valid := categorizeLocalIP(ipNet.IP)
|
||||
if !valid {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, exists := candidates[category]; !exists {
|
||||
ipCopy := make(net.IP, len(ipNet.IP))
|
||||
copy(ipCopy, ipNet.IP)
|
||||
candidates[category] = ipCopy
|
||||
}
|
||||
}
|
||||
// Search for valid ipv6 addresses
|
||||
for _, addr := range addrs {
|
||||
ipaddr, ok := addr.(*net.IPNet)
|
||||
if ok && ipaddr.IP.IsGlobalUnicast() && ipaddr.IP.To16() != nil && ipaddr.IP.To4() == nil {
|
||||
return "[" + ipaddr.IP.String() + "]"
|
||||
|
||||
priorities := defaultIPPriority
|
||||
if preferIPv6 {
|
||||
priorities = preferIPv6Priority
|
||||
}
|
||||
|
||||
for _, category := range priorities {
|
||||
if ip, exists := candidates[category]; exists {
|
||||
result := formatLocalIP(ip)
|
||||
log.Debug("Selected IP by priority",
|
||||
zap.String("ip", result),
|
||||
zap.String("categoryName", getCategoryName(category)))
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
log.Warn("No valid IP found in candidates")
|
||||
return ""
|
||||
}
|
||||
|
||||
// getCategoryName returns human-readable name for IP category (for debugging)
|
||||
func getCategoryName(category ipCategory) string {
|
||||
switch category {
|
||||
case ipCategoryIPv4Private:
|
||||
return "IPv4Private"
|
||||
case ipCategoryIPv4Public:
|
||||
return "IPv4Public"
|
||||
case ipCategoryIPv6Private:
|
||||
return "IPv6Private"
|
||||
case ipCategoryIPv6Public:
|
||||
return "IPv6Public"
|
||||
case ipCategoryIPv6LinkLocal:
|
||||
return "IPv6LinkLocal"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// JSONToMap parse the jsonic index parameters to map
|
||||
func JSONToMap(mStr string) (map[string]string, error) {
|
||||
buffer := make(map[string]any)
|
||||
@ -659,3 +743,73 @@ func DecodeUserRoleCache(cache string) (string, string, error) {
|
||||
role := cache[index+1:]
|
||||
return user, role, nil
|
||||
}
|
||||
|
||||
// isIPv4Private checks if an IPv4 address is in RFC 1918 private ranges
|
||||
func isIPv4Private(ip net.IP) bool {
|
||||
ipv4 := ip.To4()
|
||||
if ipv4 == nil {
|
||||
return false
|
||||
}
|
||||
// RFC 1918 private address ranges:
|
||||
// 10.0.0.0/8 (10.0.0.0 to 10.255.255.255)
|
||||
// 172.16.0.0/12 (172.16.0.0 to 172.31.255.255)
|
||||
// 192.168.0.0/16 (192.168.0.0 to 192.168.255.255)
|
||||
return ipv4[0] == 10 ||
|
||||
(ipv4[0] == 172 && ipv4[1] >= 16 && ipv4[1] <= 31) ||
|
||||
(ipv4[0] == 192 && ipv4[1] == 168)
|
||||
}
|
||||
|
||||
func categorizeLocalIP(ip net.IP) (ipCategory, bool) {
|
||||
if ip == nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
if ip.IsLoopback() || ip.IsInterfaceLocalMulticast() || ip.IsLinkLocalMulticast() || ip.IsMulticast() || ip.IsUnspecified() {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
if ipv4 := ip.To4(); ipv4 != nil {
|
||||
if !ip.IsGlobalUnicast() {
|
||||
return 0, false
|
||||
}
|
||||
if isIPv4Private(ipv4) {
|
||||
return ipCategoryIPv4Private, true
|
||||
}
|
||||
return ipCategoryIPv4Public, true
|
||||
}
|
||||
|
||||
ipv6 := ip.To16()
|
||||
if ipv6 == nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
if ip.IsLinkLocalUnicast() {
|
||||
return ipCategoryIPv6LinkLocal, true
|
||||
}
|
||||
if isIPv6Private(ipv6) {
|
||||
return ipCategoryIPv6Private, true
|
||||
}
|
||||
if ip.IsGlobalUnicast() {
|
||||
return ipCategoryIPv6Public, true
|
||||
}
|
||||
|
||||
log.Debug("IP categorization: uncategorized IPv6", zap.String("ip", ip.String()))
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// isIPv6Private checks if an IPv6 address is in private ranges
|
||||
func isIPv6Private(ip net.IP) bool {
|
||||
ip = ip.To16()
|
||||
if len(ip) != net.IPv6len {
|
||||
return false
|
||||
}
|
||||
// RFC 4193 Unique Local Addresses (ULA): fc00::/7
|
||||
return ip[0]&0xfe == 0xfc
|
||||
}
|
||||
|
||||
func formatLocalIP(ip net.IP) string {
|
||||
if ip.To4() != nil {
|
||||
return ip.String()
|
||||
}
|
||||
return "[" + ip.String() + "]"
|
||||
}
|
||||
|
||||
@ -40,6 +40,14 @@ import (
|
||||
"github.com/milvus-io/milvus/pkg/v2/util/typeutil"
|
||||
)
|
||||
|
||||
func setPreferIPv6ForTest(t *testing.T, prefer bool) {
|
||||
original := PreferIPv6LocalIP.Load()
|
||||
PreferIPv6LocalIP.Store(prefer)
|
||||
t.Cleanup(func() {
|
||||
PreferIPv6LocalIP.Store(original)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_CheckGrpcReady(t *testing.T) {
|
||||
errChan := make(chan error)
|
||||
|
||||
@ -56,6 +64,7 @@ func Test_CheckGrpcReady(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_GetValidLocalIPNoValid(t *testing.T) {
|
||||
setPreferIPv6ForTest(t, false)
|
||||
addrs := make([]net.Addr, 0, 1)
|
||||
addrs = append(addrs, &net.IPNet{IP: net.IPv4(127, 1, 1, 1), Mask: net.IPv4Mask(255, 255, 255, 255)})
|
||||
ip := GetValidLocalIP(addrs)
|
||||
@ -63,6 +72,7 @@ func Test_GetValidLocalIPNoValid(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_GetValidLocalIPIPv4(t *testing.T) {
|
||||
setPreferIPv6ForTest(t, false)
|
||||
addrs := make([]net.Addr, 0, 1)
|
||||
addrs = append(addrs, &net.IPNet{IP: net.IPv4(100, 1, 1, 1), Mask: net.IPv4Mask(255, 255, 255, 255)})
|
||||
ip := GetValidLocalIP(addrs)
|
||||
@ -70,13 +80,33 @@ func Test_GetValidLocalIPIPv4(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_GetValidLocalIPIPv6(t *testing.T) {
|
||||
setPreferIPv6ForTest(t, false)
|
||||
addrs := make([]net.Addr, 0, 1)
|
||||
addrs = append(addrs, &net.IPNet{IP: net.IP{8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Mask: net.IPMask{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}})
|
||||
ip := GetValidLocalIP(addrs)
|
||||
assert.Equal(t, "[800::]", ip)
|
||||
}
|
||||
|
||||
func Test_GetValidLocalIPIPv6ULA(t *testing.T) {
|
||||
setPreferIPv6ForTest(t, false)
|
||||
addrs := []net.Addr{
|
||||
&net.IPNet{IP: net.ParseIP("fd12::1"), Mask: net.CIDRMask(64, 128)},
|
||||
}
|
||||
ip := GetValidLocalIP(addrs)
|
||||
assert.Equal(t, "[fd12::1]", ip)
|
||||
}
|
||||
|
||||
func Test_GetValidLocalIPIPv6LinkLocal(t *testing.T) {
|
||||
setPreferIPv6ForTest(t, false)
|
||||
addrs := []net.Addr{
|
||||
&net.IPNet{IP: net.ParseIP("fe80::1"), Mask: net.CIDRMask(64, 128)},
|
||||
}
|
||||
ip := GetValidLocalIP(addrs)
|
||||
assert.Equal(t, "[fe80::1]", ip)
|
||||
}
|
||||
|
||||
func Test_GetValidLocalIPIPv4Priority(t *testing.T) {
|
||||
setPreferIPv6ForTest(t, false)
|
||||
addrs := make([]net.Addr, 0, 1)
|
||||
addrs = append(addrs, &net.IPNet{IP: net.IP{8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Mask: net.IPMask{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}})
|
||||
addrs = append(addrs, &net.IPNet{IP: net.IPv4(100, 1, 1, 1), Mask: net.IPv4Mask(255, 255, 255, 255)})
|
||||
@ -84,7 +114,18 @@ func Test_GetValidLocalIPIPv4Priority(t *testing.T) {
|
||||
assert.Equal(t, "100.1.1.1", ip)
|
||||
}
|
||||
|
||||
func Test_GetValidLocalIPPreferIPv6(t *testing.T) {
|
||||
setPreferIPv6ForTest(t, true)
|
||||
addrs := []net.Addr{
|
||||
&net.IPNet{IP: net.IPv4(100, 1, 1, 1), Mask: net.IPv4Mask(255, 255, 255, 255)},
|
||||
&net.IPNet{IP: net.ParseIP("fd12::1"), Mask: net.CIDRMask(64, 128)},
|
||||
}
|
||||
ip := GetValidLocalIP(addrs)
|
||||
assert.Equal(t, "[fd12::1]", ip)
|
||||
}
|
||||
|
||||
func Test_GetLocalIP(t *testing.T) {
|
||||
setPreferIPv6ForTest(t, false)
|
||||
ip := GetLocalIP()
|
||||
assert.NotNil(t, ip)
|
||||
assert.NotZero(t, len(ip))
|
||||
@ -1002,3 +1043,288 @@ func TestString2KeyValuePair(t *testing.T) {
|
||||
assert.Len(t, kvs, 0)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_PrivatePublicAddresses(t *testing.T) {
|
||||
t.Run("isIPv4Private", func(t *testing.T) {
|
||||
// Test private IPv4 addresses
|
||||
assert.True(t, isIPv4Private(net.IPv4(10, 0, 0, 1)))
|
||||
assert.True(t, isIPv4Private(net.IPv4(10, 255, 255, 255)))
|
||||
assert.True(t, isIPv4Private(net.IPv4(172, 16, 0, 1)))
|
||||
assert.True(t, isIPv4Private(net.IPv4(172, 31, 255, 255)))
|
||||
assert.True(t, isIPv4Private(net.IPv4(192, 168, 1, 1)))
|
||||
assert.True(t, isIPv4Private(net.IPv4(192, 168, 255, 255)))
|
||||
|
||||
// Test public IPv4 addresses
|
||||
assert.False(t, isIPv4Private(net.IPv4(8, 8, 8, 8)))
|
||||
assert.False(t, isIPv4Private(net.IPv4(1, 1, 1, 1)))
|
||||
assert.False(t, isIPv4Private(net.IPv4(172, 15, 0, 1))) // Just outside private range
|
||||
assert.False(t, isIPv4Private(net.IPv4(172, 32, 0, 1))) // Just outside private range
|
||||
assert.False(t, isIPv4Private(net.IPv4(192, 167, 1, 1))) // Just outside private range
|
||||
assert.False(t, isIPv4Private(net.IPv4(192, 169, 1, 1))) // Just outside private range
|
||||
|
||||
// Test IPv6 addresses (should return false)
|
||||
assert.False(t, isIPv4Private(net.ParseIP("2001:db8::1")))
|
||||
assert.False(t, isIPv4Private(net.ParseIP("fd12::1")))
|
||||
})
|
||||
|
||||
t.Run("isIPv6Private", func(t *testing.T) {
|
||||
// Test IPv6 private addresses (ULA)
|
||||
assert.True(t, isIPv6Private(net.ParseIP("fd12::1")))
|
||||
assert.True(t, isIPv6Private(net.ParseIP("fc00::1")))
|
||||
assert.True(t, isIPv6Private(net.ParseIP("fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")))
|
||||
|
||||
// Test IPv6 public addresses
|
||||
assert.False(t, isIPv6Private(net.ParseIP("2001:db8::1")))
|
||||
assert.False(t, isIPv6Private(net.ParseIP("2001:4860:4860::8888"))) // Google DNS
|
||||
assert.False(t, isIPv6Private(net.ParseIP("2606:4700:4700::1111"))) // Cloudflare DNS
|
||||
|
||||
// Test IPv6 link-local (not considered private in our definition)
|
||||
assert.False(t, isIPv6Private(net.ParseIP("fe80::1")))
|
||||
|
||||
// Test IPv4 addresses (should return false)
|
||||
assert.False(t, isIPv6Private(net.IPv4(192, 168, 1, 1)))
|
||||
assert.False(t, isIPv6Private(net.IPv4(8, 8, 8, 8)))
|
||||
})
|
||||
|
||||
t.Run("categorizeLocalIP_IPv4", func(t *testing.T) {
|
||||
// Test IPv4 private address categorization
|
||||
category, valid := categorizeLocalIP(net.IPv4(192, 168, 1, 1))
|
||||
assert.True(t, valid)
|
||||
assert.Equal(t, ipCategoryIPv4Private, category)
|
||||
|
||||
// Test IPv4 public address categorization
|
||||
category, valid = categorizeLocalIP(net.IPv4(8, 8, 8, 8))
|
||||
assert.True(t, valid)
|
||||
assert.Equal(t, ipCategoryIPv4Public, category)
|
||||
|
||||
// Test invalid addresses
|
||||
_, valid = categorizeLocalIP(net.IPv4(127, 0, 0, 1)) // loopback
|
||||
assert.False(t, valid)
|
||||
|
||||
_, valid = categorizeLocalIP(net.IPv4(224, 0, 0, 1)) // multicast
|
||||
assert.False(t, valid)
|
||||
})
|
||||
|
||||
t.Run("categorizeLocalIP_IPv6", func(t *testing.T) {
|
||||
// Test IPv6 private address categorization (ULA)
|
||||
category, valid := categorizeLocalIP(net.ParseIP("fd12::1"))
|
||||
assert.True(t, valid)
|
||||
assert.Equal(t, ipCategoryIPv6Private, category)
|
||||
|
||||
// Test IPv6 public address categorization
|
||||
category, valid = categorizeLocalIP(net.ParseIP("2001:db8::1"))
|
||||
assert.True(t, valid)
|
||||
assert.Equal(t, ipCategoryIPv6Public, category)
|
||||
|
||||
// Test IPv6 link-local address categorization
|
||||
category, valid = categorizeLocalIP(net.ParseIP("fe80::1"))
|
||||
assert.True(t, valid)
|
||||
assert.Equal(t, ipCategoryIPv6LinkLocal, category)
|
||||
|
||||
// Test invalid IPv6 addresses
|
||||
_, valid = categorizeLocalIP(net.ParseIP("::1")) // loopback
|
||||
assert.False(t, valid)
|
||||
|
||||
_, valid = categorizeLocalIP(net.ParseIP("ff02::1")) // multicast
|
||||
assert.False(t, valid)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_GetValidLocalIPSimplified(t *testing.T) {
|
||||
t.Run("default_behavior_private_first_ipv4_first", func(t *testing.T) {
|
||||
setPreferIPv6ForTest(t, false)
|
||||
|
||||
addrs := []net.Addr{
|
||||
&net.IPNet{IP: net.IPv4(8, 8, 8, 8), Mask: net.IPv4Mask(255, 255, 255, 0)}, // Public IPv4
|
||||
&net.IPNet{IP: net.IPv4(192, 168, 1, 1), Mask: net.IPv4Mask(255, 255, 255, 0)}, // Private IPv4
|
||||
&net.IPNet{IP: net.ParseIP("2001:db8::1"), Mask: net.CIDRMask(64, 128)}, // Public IPv6
|
||||
&net.IPNet{IP: net.ParseIP("fd12::1"), Mask: net.CIDRMask(64, 128)}, // Private IPv6
|
||||
}
|
||||
ip := GetValidLocalIP(addrs)
|
||||
// Should prefer private IPv4 first
|
||||
assert.Equal(t, "192.168.1.1", ip)
|
||||
})
|
||||
|
||||
t.Run("ipv6_preferred_private_first", func(t *testing.T) {
|
||||
setPreferIPv6ForTest(t, true)
|
||||
|
||||
addrs := []net.Addr{
|
||||
&net.IPNet{IP: net.IPv4(8, 8, 8, 8), Mask: net.IPv4Mask(255, 255, 255, 0)}, // Public IPv4
|
||||
&net.IPNet{IP: net.IPv4(192, 168, 1, 1), Mask: net.IPv4Mask(255, 255, 255, 0)}, // Private IPv4
|
||||
&net.IPNet{IP: net.ParseIP("2001:db8::1"), Mask: net.CIDRMask(64, 128)}, // Public IPv6
|
||||
&net.IPNet{IP: net.ParseIP("fd12::1"), Mask: net.CIDRMask(64, 128)}, // Private IPv6
|
||||
}
|
||||
ip := GetValidLocalIP(addrs)
|
||||
// Should prefer private IPv6 first when IPv6 is preferred
|
||||
assert.Equal(t, "[fd12::1]", ip)
|
||||
})
|
||||
|
||||
t.Run("only_public_addresses_available", func(t *testing.T) {
|
||||
setPreferIPv6ForTest(t, false)
|
||||
|
||||
addrs := []net.Addr{
|
||||
&net.IPNet{IP: net.IPv4(8, 8, 8, 8), Mask: net.IPv4Mask(255, 255, 255, 0)}, // Public IPv4
|
||||
&net.IPNet{IP: net.ParseIP("2001:db8::1"), Mask: net.CIDRMask(64, 128)}, // Public IPv6
|
||||
}
|
||||
ip := GetValidLocalIP(addrs)
|
||||
// Should fallback to public IPv4 when no private available
|
||||
assert.Equal(t, "8.8.8.8", ip)
|
||||
})
|
||||
|
||||
t.Run("only_ipv6_addresses_available", func(t *testing.T) {
|
||||
setPreferIPv6ForTest(t, false)
|
||||
|
||||
addrs := []net.Addr{
|
||||
&net.IPNet{IP: net.ParseIP("fd12::1"), Mask: net.CIDRMask(64, 128)}, // Private IPv6
|
||||
&net.IPNet{IP: net.ParseIP("2001:db8::1"), Mask: net.CIDRMask(64, 128)}, // Public IPv6
|
||||
}
|
||||
ip := GetValidLocalIP(addrs)
|
||||
// Should prefer private IPv6 even when IPv4 is preferred but not available
|
||||
assert.Equal(t, "[fd12::1]", ip)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_GetValidLocalIPEdgeCases(t *testing.T) {
|
||||
t.Run("empty_address_list", func(t *testing.T) {
|
||||
setPreferIPv6ForTest(t, false)
|
||||
|
||||
addrs := []net.Addr{}
|
||||
ip := GetValidLocalIP(addrs)
|
||||
assert.Equal(t, "", ip)
|
||||
})
|
||||
|
||||
t.Run("nil_address_list", func(t *testing.T) {
|
||||
setPreferIPv6ForTest(t, false)
|
||||
|
||||
ip := GetValidLocalIP(nil)
|
||||
assert.Equal(t, "", ip)
|
||||
})
|
||||
|
||||
t.Run("only_loopback_ipv4", func(t *testing.T) {
|
||||
setPreferIPv6ForTest(t, false)
|
||||
|
||||
addrs := []net.Addr{
|
||||
&net.IPNet{IP: net.IPv4(127, 0, 0, 1), Mask: net.IPv4Mask(255, 0, 0, 0)},
|
||||
}
|
||||
ip := GetValidLocalIP(addrs)
|
||||
// Loopback should be filtered out
|
||||
assert.Equal(t, "", ip)
|
||||
})
|
||||
|
||||
t.Run("only_loopback_ipv6", func(t *testing.T) {
|
||||
setPreferIPv6ForTest(t, false)
|
||||
|
||||
addrs := []net.Addr{
|
||||
&net.IPNet{IP: net.ParseIP("::1"), Mask: net.CIDRMask(128, 128)},
|
||||
}
|
||||
ip := GetValidLocalIP(addrs)
|
||||
// Loopback should be filtered out
|
||||
assert.Equal(t, "", ip)
|
||||
})
|
||||
|
||||
t.Run("only_multicast", func(t *testing.T) {
|
||||
setPreferIPv6ForTest(t, false)
|
||||
|
||||
addrs := []net.Addr{
|
||||
&net.IPNet{IP: net.ParseIP("224.0.0.1"), Mask: net.IPv4Mask(240, 0, 0, 0)},
|
||||
&net.IPNet{IP: net.ParseIP("ff02::1"), Mask: net.CIDRMask(8, 128)},
|
||||
}
|
||||
ip := GetValidLocalIP(addrs)
|
||||
// Multicast should be filtered out
|
||||
assert.Equal(t, "", ip)
|
||||
})
|
||||
|
||||
t.Run("ipv4_mapped_ipv6_treated_as_ipv4", func(t *testing.T) {
|
||||
setPreferIPv6ForTest(t, false)
|
||||
|
||||
// IPv4-mapped IPv6 address (::ffff:192.168.1.1)
|
||||
ipv4Mapped := net.ParseIP("::ffff:192.168.1.1")
|
||||
addrs := []net.Addr{
|
||||
&net.IPNet{IP: ipv4Mapped, Mask: net.CIDRMask(128, 128)},
|
||||
}
|
||||
ip := GetValidLocalIP(addrs)
|
||||
// IPv4-mapped IPv6 should be treated as IPv4 (no brackets)
|
||||
assert.Equal(t, "192.168.1.1", ip)
|
||||
})
|
||||
|
||||
t.Run("mixed_invalid_and_valid", func(t *testing.T) {
|
||||
setPreferIPv6ForTest(t, false)
|
||||
|
||||
addrs := []net.Addr{
|
||||
&net.IPNet{IP: net.IPv4(127, 0, 0, 1), Mask: net.IPv4Mask(255, 0, 0, 0)}, // Loopback - filtered
|
||||
&net.IPNet{IP: net.ParseIP("::1"), Mask: net.CIDRMask(128, 128)}, // Loopback - filtered
|
||||
&net.IPNet{IP: net.ParseIP("224.0.0.1"), Mask: net.IPv4Mask(240, 0, 0, 0)}, // Multicast - filtered
|
||||
&net.IPNet{IP: net.IPv4(192, 168, 1, 100), Mask: net.IPv4Mask(255, 255, 255, 0)}, // Valid private IPv4
|
||||
}
|
||||
ip := GetValidLocalIP(addrs)
|
||||
assert.Equal(t, "192.168.1.100", ip)
|
||||
})
|
||||
|
||||
t.Run("non_ipnet_addr_type", func(t *testing.T) {
|
||||
setPreferIPv6ForTest(t, false)
|
||||
|
||||
// Use a different Addr type that's not *net.IPNet
|
||||
addrs := []net.Addr{
|
||||
&net.TCPAddr{IP: net.IPv4(192, 168, 1, 1), Port: 8080},
|
||||
}
|
||||
ip := GetValidLocalIP(addrs)
|
||||
// Non-IPNet types should be skipped
|
||||
assert.Equal(t, "", ip)
|
||||
})
|
||||
|
||||
t.Run("link_local_ipv6_lowest_priority", func(t *testing.T) {
|
||||
setPreferIPv6ForTest(t, false)
|
||||
|
||||
addrs := []net.Addr{
|
||||
&net.IPNet{IP: net.ParseIP("fe80::1"), Mask: net.CIDRMask(64, 128)}, // Link-local IPv6
|
||||
}
|
||||
ip := GetValidLocalIP(addrs)
|
||||
// Link-local should be selected when it's the only option
|
||||
assert.Equal(t, "[fe80::1]", ip)
|
||||
})
|
||||
|
||||
t.Run("link_local_vs_public_ipv6", func(t *testing.T) {
|
||||
setPreferIPv6ForTest(t, true)
|
||||
|
||||
addrs := []net.Addr{
|
||||
&net.IPNet{IP: net.ParseIP("fe80::1"), Mask: net.CIDRMask(64, 128)}, // Link-local IPv6
|
||||
&net.IPNet{IP: net.ParseIP("2001:db8::1"), Mask: net.CIDRMask(64, 128)}, // Public IPv6
|
||||
}
|
||||
ip := GetValidLocalIP(addrs)
|
||||
// Public IPv6 should be preferred over link-local
|
||||
assert.Equal(t, "[2001:db8::1]", ip)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_FormatLocalIP(t *testing.T) {
|
||||
t.Run("ipv4_no_brackets", func(t *testing.T) {
|
||||
ip := net.IPv4(192, 168, 1, 1)
|
||||
result := formatLocalIP(ip)
|
||||
assert.Equal(t, "192.168.1.1", result)
|
||||
})
|
||||
|
||||
t.Run("ipv6_with_brackets", func(t *testing.T) {
|
||||
ip := net.ParseIP("2001:db8::1")
|
||||
result := formatLocalIP(ip)
|
||||
assert.Equal(t, "[2001:db8::1]", result)
|
||||
})
|
||||
|
||||
t.Run("ipv6_ula_with_brackets", func(t *testing.T) {
|
||||
ip := net.ParseIP("fd12::1")
|
||||
result := formatLocalIP(ip)
|
||||
assert.Equal(t, "[fd12::1]", result)
|
||||
})
|
||||
|
||||
t.Run("ipv6_link_local_with_brackets", func(t *testing.T) {
|
||||
ip := net.ParseIP("fe80::1")
|
||||
result := formatLocalIP(ip)
|
||||
assert.Equal(t, "[fe80::1]", result)
|
||||
})
|
||||
|
||||
t.Run("ipv4_mapped_ipv6_no_brackets", func(t *testing.T) {
|
||||
// IPv4-mapped IPv6 should be formatted as IPv4 (no brackets)
|
||||
ip := net.ParseIP("::ffff:192.168.1.1")
|
||||
result := formatLocalIP(ip)
|
||||
assert.Equal(t, "192.168.1.1", result)
|
||||
})
|
||||
}
|
||||
|
||||
@ -32,6 +32,7 @@ import (
|
||||
|
||||
"github.com/milvus-io/milvus/pkg/v2/config"
|
||||
"github.com/milvus-io/milvus/pkg/v2/log"
|
||||
"github.com/milvus-io/milvus/pkg/v2/util/funcutil"
|
||||
"github.com/milvus-io/milvus/pkg/v2/util/hardware"
|
||||
"github.com/milvus-io/milvus/pkg/v2/util/metricsinfo"
|
||||
"github.com/milvus-io/milvus/pkg/v2/util/typeutil"
|
||||
@ -323,6 +324,8 @@ type commonConfig struct {
|
||||
// Local RPC enabled for milvus internal communication when mix or standalone mode.
|
||||
LocalRPCEnabled ParamItem `refreshable:"false"`
|
||||
|
||||
PreferIPv6LocalIP ParamItem `refreshable:"false"`
|
||||
|
||||
SyncTaskPoolReleaseTimeoutSeconds ParamItem `refreshable:"true"`
|
||||
|
||||
EnabledOptimizeExpr ParamItem `refreshable:"true"`
|
||||
@ -1219,6 +1222,17 @@ The default value is 1, which is enough for most cases.`,
|
||||
}
|
||||
p.LocalRPCEnabled.Init(base.mgr)
|
||||
|
||||
p.PreferIPv6LocalIP = ParamItem{
|
||||
Key: "common.preferIPv6",
|
||||
Version: "2.5.10",
|
||||
DefaultValue: "false",
|
||||
Doc: `Prefer IPv6 addresses when automatically selecting the local IP.
|
||||
If enabled, IPv6 ULA/global addresses will be prioritized ahead of IPv4.`,
|
||||
Export: true,
|
||||
}
|
||||
p.PreferIPv6LocalIP.Init(base.mgr)
|
||||
funcutil.PreferIPv6LocalIP.Store(p.PreferIPv6LocalIP.GetAsBool())
|
||||
|
||||
p.SyncTaskPoolReleaseTimeoutSeconds = ParamItem{
|
||||
Key: "common.sync.taskPoolReleaseTimeoutSeconds",
|
||||
DefaultValue: "60",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user