Skip to content

Commit 7f81dc9

Browse files
Merge pull request #1252 from metal3-io-bot/cherry-pick-1250-to-release-1.12
🐛 Fix IPv6 preAllocation canonicalization in IPAM
2 parents 0ebd411 + 59217f8 commit 7f81dc9

File tree

2 files changed

+86
-8
lines changed

2 files changed

+86
-8
lines changed

ipam/ippool_manager.go

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package ipam
1818

1919
import (
2020
"context"
21+
"net"
2122
"reflect"
2223
"strings"
2324

@@ -87,6 +88,27 @@ func (m *IPPoolManager) UnsetFinalizer() {
8788
)
8889
}
8990

91+
// ipEqual compares two IP addresses by their parsed values to handle non-canonical
92+
// IPv6 formats (e.g., ::0005 vs ::5). Returns false if either address fails to parse,
93+
// logging a warning to aid in diagnosing configuration issues.
94+
func (m *IPPoolManager) ipEqual(a, b ipamv1.IPAddressStr) bool {
95+
ipA := net.ParseIP(string(a))
96+
ipB := net.ParseIP(string(b))
97+
if ipA == nil {
98+
if a != "" {
99+
m.Log.Info("Failed to parse IP address", "address", a)
100+
}
101+
return false
102+
}
103+
if ipB == nil {
104+
if b != "" {
105+
m.Log.Info("Failed to parse IP address", "address", b)
106+
}
107+
return false
108+
}
109+
return ipA.Equal(ipB)
110+
}
111+
90112
func (m *IPPoolManager) SetClusterOwnerRef(cluster *clusterv1beta1.Cluster) error {
91113
if cluster == nil {
92114
return errors.New("Missing cluster")
@@ -436,7 +458,7 @@ func (m *IPPoolManager) allocateAddress(addressClaim *ipamv1.IPClaim,
436458
isRequestedIPAllocated := false
437459

438460
// Conflict-case, claim is preAllocated but has requested different IP
439-
if requestedIP != "" && ipPreAllocated && requestedIP != preAllocatedAddress {
461+
if requestedIP != "" && ipPreAllocated && !m.ipEqual(requestedIP, preAllocatedAddress) {
440462
addressClaim.Status.ErrorMessage = ptr.To("PreAllocation and requested ip address are conflicting")
441463
return "", 0, nil, []ipamv1.IPAddressStr{}, errors.New("PreAllocation and requested ip address are conflicting")
442464
}
@@ -453,15 +475,15 @@ func (m *IPPoolManager) allocateAddress(addressClaim *ipamv1.IPClaim,
453475
}
454476
index++
455477
// Check if requestedIP is present and matches the current address
456-
if requestedIP != "" && allocatedAddress != requestedIP {
478+
if requestedIP != "" && !m.ipEqual(allocatedAddress, requestedIP) {
457479
continue
458480
}
459-
if requestedIP != "" && allocatedAddress == requestedIP {
481+
if requestedIP != "" && m.ipEqual(allocatedAddress, requestedIP) {
460482
isRequestedIPAllocated = true
461483
}
462484
// We have a pre-allocated ip, we just need to ensure that it matches the current address
463485
// if it does not, continue and try the next address
464-
if ipPreAllocated && allocatedAddress != preAllocatedAddress {
486+
if ipPreAllocated && !m.ipEqual(allocatedAddress, preAllocatedAddress) {
465487
continue
466488
}
467489
// Here the two addresses match, so we continue with that one
@@ -527,7 +549,7 @@ func (m *IPPoolManager) capiAllocateAddress(addressClaim *capipamv1beta1.IPAddre
527549
isRequestedIPAllocated := false
528550

529551
// Conflict-case, claim is preAllocated but has requested different IP
530-
if requestedIP != "" && ipPreAllocated && requestedIP != preAllocatedAddress {
552+
if requestedIP != "" && ipPreAllocated && !m.ipEqual(requestedIP, preAllocatedAddress) {
531553
conditions := clusterv1beta1.Conditions{}
532554
conditions = append(conditions, clusterv1beta1.Condition{
533555
Type: "ErrorMessage",
@@ -553,15 +575,15 @@ func (m *IPPoolManager) capiAllocateAddress(addressClaim *capipamv1beta1.IPAddre
553575
}
554576
index++
555577
// Check if requestedIP is present and matches the current address
556-
if requestedIP != "" && allocatedAddress != requestedIP {
578+
if requestedIP != "" && !m.ipEqual(allocatedAddress, requestedIP) {
557579
continue
558580
}
559-
if requestedIP != "" && allocatedAddress == requestedIP {
581+
if requestedIP != "" && m.ipEqual(allocatedAddress, requestedIP) {
560582
isRequestedIPAllocated = true
561583
}
562584
// We have a pre-allocated ip, we just need to ensure that it matches the current address
563585
// if it does not, continue and try the next address
564-
if ipPreAllocated && allocatedAddress != preAllocatedAddress {
586+
if ipPreAllocated && !m.ipEqual(allocatedAddress, preAllocatedAddress) {
565587
continue
566588
}
567589
// Here the two addresses match, so we continue with that one

ipam/ippool_manager_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2011,6 +2011,37 @@ var _ = Describe("IPPool manager", func() {
20112011
},
20122012
expectedPrefix: 24,
20132013
}),
2014+
Entry("One pool, IPv6 pre-allocated (non-canonical)", testCaseAllocateAddress{
2015+
ipPool: &ipamv1.IPPool{
2016+
Spec: ipamv1.IPPoolSpec{
2017+
Pools: []ipamv1.Pool{
2018+
{
2019+
Start: (*ipamv1.IPAddressStr)(ptr.To("2001:db8::1")),
2020+
End: (*ipamv1.IPAddressStr)(ptr.To("2001:db8::a")),
2021+
},
2022+
},
2023+
PreAllocations: map[string]ipamv1.IPAddressStr{
2024+
"TestRef": ipamv1.IPAddressStr("2001:db8::0005"),
2025+
},
2026+
Prefix: 64,
2027+
Gateway: (*ipamv1.IPAddressStr)(ptr.To("2001:db8::ffff")),
2028+
DNSServers: []ipamv1.IPAddressStr{
2029+
ipamv1.IPAddressStr("2001:4860:4860::8888"),
2030+
},
2031+
},
2032+
},
2033+
ipClaim: &ipamv1.IPClaim{
2034+
ObjectMeta: metav1.ObjectMeta{
2035+
Name: "TestRef",
2036+
},
2037+
},
2038+
expectedAddress: ipamv1.IPAddressStr("2001:db8::5"),
2039+
expectedGateway: (*ipamv1.IPAddressStr)(ptr.To("2001:db8::ffff")),
2040+
expectedDNSServers: []ipamv1.IPAddressStr{
2041+
ipamv1.IPAddressStr("2001:4860:4860::8888"),
2042+
},
2043+
expectedPrefix: 64,
2044+
}),
20142045
Entry("One pool, pre-allocated, with overrides", testCaseAllocateAddress{
20152046
ipPool: &ipamv1.IPPool{
20162047
Spec: ipamv1.IPPoolSpec{
@@ -2459,6 +2490,31 @@ var _ = Describe("IPPool manager", func() {
24592490
expectedGateway: (*ipamv1.IPAddressStr)(ptr.To("192.168.0.1")),
24602491
expectedPrefix: 24,
24612492
}),
2493+
Entry("One pool, IPv6 pre-allocated (non-canonical)", testCapiCaseAllocateAddress{
2494+
ipPool: &ipamv1.IPPool{
2495+
Spec: ipamv1.IPPoolSpec{
2496+
Pools: []ipamv1.Pool{
2497+
{
2498+
Start: (*ipamv1.IPAddressStr)(ptr.To("2001:db8::1")),
2499+
End: (*ipamv1.IPAddressStr)(ptr.To("2001:db8::a")),
2500+
},
2501+
},
2502+
PreAllocations: map[string]ipamv1.IPAddressStr{
2503+
"TestRef": ipamv1.IPAddressStr("2001:db8::0005"),
2504+
},
2505+
Prefix: 64,
2506+
Gateway: (*ipamv1.IPAddressStr)(ptr.To("2001:db8::ffff")),
2507+
},
2508+
},
2509+
ipAddressClaim: &capipamv1beta1.IPAddressClaim{
2510+
ObjectMeta: metav1.ObjectMeta{
2511+
Name: "TestRef",
2512+
},
2513+
},
2514+
expectedAddress: ipamv1.IPAddressStr("2001:db8::5"),
2515+
expectedGateway: (*ipamv1.IPAddressStr)(ptr.To("2001:db8::ffff")),
2516+
expectedPrefix: 64,
2517+
}),
24622518
Entry("One pool, pre-allocated, with overrides", testCapiCaseAllocateAddress{
24632519
ipPool: &ipamv1.IPPool{
24642520
Spec: ipamv1.IPPoolSpec{

0 commit comments

Comments
 (0)