Skip to content

Commit 1645a83

Browse files
Add prefix length validation for IPv6 gateway and IP range defaults
The code previously assumed it could always use network+1 for gateway and network+2 for start IP, but this fails for very small prefixes: - /128 (1 address): Cannot accommodate gateway - /127 (2 addresses): Can accommodate gateway but not start/end IP range Added validation to ensure: - When specifyiprange is false: minimum /127 (2 addresses for gateway) - When specifyiprange is true: minimum /126 (4 addresses for gateway + range) Added comprehensive unit tests for edge cases: - TestParseCIDRv6_Prefix128_NoIPRange: Rejects /128 (too small) - TestParseCIDRv6_Prefix127_NoIPRange: Accepts /127 without IP range - TestParseCIDRv6_Prefix127_WithIPRange: Rejects /127 with IP range - TestParseCIDRv6_Prefix126_WithIPRange: Accepts /126 with IP range
1 parent 745c190 commit 1645a83

2 files changed

Lines changed: 104 additions & 0 deletions

File tree

cloudstack/resource_cloudstack_network.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,25 @@ func parseCIDRv6(d *schema.ResourceData, specifyiprange bool) (map[string]string
554554
return nil, fmt.Errorf("ip6cidr must be an IPv6 CIDR with 16-byte mask, got %d bytes: %s", len(ipnet.Mask), cidr)
555555
}
556556

557+
// Validate prefix length to ensure we have enough addresses for gateway/start/end
558+
ones, _ := ipnet.Mask.Size()
559+
if specifyiprange {
560+
// When specifyiprange is true, we need at least 3 addresses:
561+
// - gateway (network + 1)
562+
// - start IP (network + 2)
563+
// - end IP (network + 3 or more)
564+
// This requires a /126 or larger prefix (4 addresses minimum)
565+
if ones > 126 {
566+
return nil, fmt.Errorf("ip6cidr prefix /%d is too small for automatic IP range generation; minimum is /126 (4 addresses)", ones)
567+
}
568+
} else {
569+
// When specifyiprange is false, we only need the gateway (network + 1)
570+
// This requires a /127 or larger prefix (2 addresses minimum)
571+
if ones > 127 {
572+
return nil, fmt.Errorf("ip6cidr prefix /%d is too small for automatic gateway generation; minimum is /127 (2 addresses)", ones)
573+
}
574+
}
575+
557576
if gateway, ok := d.GetOk("ip6gateway"); ok {
558577
m["ip6gateway"] = gateway.(string)
559578
} else {

cloudstack/resource_cloudstack_network_unit_test.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,3 +149,88 @@ func TestParseCIDRv6_RejectsIPv4(t *testing.T) {
149149
t.Errorf("Expected error message to start with '%s', got '%s'", expectedError, err.Error())
150150
}
151151
}
152+
153+
func TestParseCIDRv6_Prefix128_NoIPRange(t *testing.T) {
154+
// /128 is a single address - should fail even without IP range
155+
d := schema.TestResourceDataRaw(t, resourceCloudStackNetwork().Schema, map[string]interface{}{
156+
"ip6cidr": "2001:db8::1/128",
157+
})
158+
159+
_, err := parseCIDRv6(d, false)
160+
if err == nil {
161+
t.Fatal("parseCIDRv6 should reject /128 prefix (single address)")
162+
}
163+
164+
expectedError := "ip6cidr prefix /128 is too small"
165+
if err.Error()[:len(expectedError)] != expectedError {
166+
t.Errorf("Expected error message to start with '%s', got '%s'", expectedError, err.Error())
167+
}
168+
}
169+
170+
func TestParseCIDRv6_Prefix127_NoIPRange(t *testing.T) {
171+
// /127 has 2 addresses - should work without IP range (only needs gateway)
172+
d := schema.TestResourceDataRaw(t, resourceCloudStackNetwork().Schema, map[string]interface{}{
173+
"ip6cidr": "2001:db8::/127",
174+
})
175+
176+
result, err := parseCIDRv6(d, false)
177+
if err != nil {
178+
t.Fatalf("parseCIDRv6 should accept /127 prefix without IP range: %v", err)
179+
}
180+
181+
// Should have gateway
182+
if _, ok := result["ip6gateway"]; !ok {
183+
t.Error("Expected ip6gateway to be set")
184+
}
185+
186+
// Should not have start/end IP
187+
if _, ok := result["startipv6"]; ok {
188+
t.Error("startipv6 should not be set when specifyiprange is false")
189+
}
190+
}
191+
192+
func TestParseCIDRv6_Prefix127_WithIPRange(t *testing.T) {
193+
// /127 has only 2 addresses - should fail with IP range (needs 3+ addresses)
194+
d := schema.TestResourceDataRaw(t, resourceCloudStackNetwork().Schema, map[string]interface{}{
195+
"ip6cidr": "2001:db8::/127",
196+
})
197+
198+
_, err := parseCIDRv6(d, true)
199+
if err == nil {
200+
t.Fatal("parseCIDRv6 should reject /127 prefix with IP range (only 2 addresses)")
201+
}
202+
203+
expectedError := "ip6cidr prefix /127 is too small for automatic IP range generation"
204+
if err.Error()[:len(expectedError)] != expectedError {
205+
t.Errorf("Expected error message to start with '%s', got '%s'", expectedError, err.Error())
206+
}
207+
}
208+
209+
func TestParseCIDRv6_Prefix126_WithIPRange(t *testing.T) {
210+
// /126 has 4 addresses - should work with IP range
211+
d := schema.TestResourceDataRaw(t, resourceCloudStackNetwork().Schema, map[string]interface{}{
212+
"ip6cidr": "2001:db8::/126",
213+
})
214+
215+
result, err := parseCIDRv6(d, true)
216+
if err != nil {
217+
t.Fatalf("parseCIDRv6 should accept /126 prefix with IP range: %v", err)
218+
}
219+
220+
// Should have gateway, start, and end
221+
if _, ok := result["ip6gateway"]; !ok {
222+
t.Error("Expected ip6gateway to be set")
223+
}
224+
if _, ok := result["startipv6"]; !ok {
225+
t.Error("Expected startipv6 to be set")
226+
}
227+
if _, ok := result["endipv6"]; !ok {
228+
t.Error("Expected endipv6 to be set")
229+
}
230+
231+
// Verify the end IP is correct for /126 (last 2 bits set to 1)
232+
expectedEndIP := "2001:db8::3"
233+
if result["endipv6"] != expectedEndIP {
234+
t.Errorf("Expected end IP %s for /126, got %s", expectedEndIP, result["endipv6"])
235+
}
236+
}

0 commit comments

Comments
 (0)