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:
Xiaofan 2026-01-19 21:41:30 +08:00 committed by GitHub
parent 925d5875fd
commit f166dd817a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 511 additions and 14 deletions

View File

@ -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

View File

@ -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() + "]"
}

View File

@ -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)
})
}

View File

@ -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",