Skip to content

Fixed issue with GenerateNonOverlappingIPv4Subnet not correctly randomizing enough and add better random distribution across multiple private ranges - #95

Merged
jhaynie merged 5 commits into
mainfrom
subnet-dist
Sep 12, 2025
Merged

jhaynie merged 5 commits into
mainfrom
subnet-dist

Conversation

@jhaynie

@jhaynie jhaynie commented Sep 11, 2025 •

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Improvements

    • Randomized private-range selection and deterministic, thread-safe randomness for more even subnet distribution.
  • Bug Fixes

    • Stricter prefix validation (now 9–30) and corrected subnet computation to avoid invalid or overlapping allocations.
  • Tests

    • Extensive deterministic and concurrent tests plus new benchmarks covering distribution, capacity, exhaustion, performance, and edge cases.
  • Chores

    • Added bench target to Makefile and bumped Go toolchain to 1.25.1.

…mizing enough and add better random distribution across multiple private ranges
@coderabbitai

coderabbitai Bot commented Sep 11, 2025 •

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3c7d898 and b56f94b.

📒 Files selected for processing (4)
  • network/concurrent_test.go (1 hunks)
  • network/ipv4.go (3 hunks)
  • network/ipv4_bench_test.go (1 hunks)
  • network/ipv4_test.go (2 hunks)
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch subnet-dist

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
network/ipv4.go (1)

72-74: Fix inconsistent prefix validation: /8 is allowed but never producible

GenerateNonOverlappingIPv4Subnet accepts 8..30, but generateRandomSubnet rejects prefixLen <= parentOnes, making /8 impossible in any private range. Either disallow 8, or allow equal-to-parent in generateRandomSubnet. Easiest: restrict to 9..30 and skip parent ranges that can’t host the requested prefix.

Apply:

- if prefixLen < 8 || prefixLen > 30 {
+ if prefixLen < 9 || prefixLen > 30 {
   return nil, nil, fmt.Errorf("invalid prefix length %d: must be between 8 and 30", prefixLen)
 }

Also applies to: 35-38

🧹 Nitpick comments (5)
network/ipv4.go (2)

50-66: Avoid fmt+ParseCIDR; build IPNet directly to reduce allocs

Minor perf/readability tweak: construct the IP/mask without string formatting and re-parsing.

Apply:

- // Convert back to 4 bytes
- randBytes := []byte{
-   byte(newIP >> 24),
-   byte(newIP >> 16),
-   byte(newIP >> 8),
-   byte(newIP),
- }
-
- newSubnet := fmt.Sprintf("%s/%d", net.IP(randBytes).String(), prefixLen)
- _, result, _ := net.ParseCIDR(newSubnet)
- return result
+ // Convert back to 4 bytes and build IPNet directly
+ ipBytes := net.IP{
+   byte(newIP >> 24),
+   byte(newIP >> 16),
+   byte(newIP >> 8),
+   byte(newIP),
+ }
+ mask := net.CIDRMask(prefixLen, 32)
+ return &net.IPNet{IP: ipBytes.Mask(mask), Mask: mask}

76-82: Optional: inject RNG for testability and distribution control

If you ever need deterministic-but-configurable distribution, accept a *rand.Rand or use a package-level var that tests can seed. Keeps prod behavior while enabling controlled tests.

network/ipv4_test.go (3)

211-250: Reduce flake risk in distribution assertions

Requiring each range count > 50 out of 300 is statistically fine but still a hard threshold. Consider a softer bound (e.g., > 30) or asserting non-zero plus max-min spread < some delta to avoid rare CI flakes if RNG behavior changes.


388-426: Nit: avoid repeated CIDR parsing inside the loop

Parsing the same CIDRs each iteration is avoidable. Move them out of the loop.

Apply:

- // Now generate more - should use other ranges
- for i := 0; i < 100; i++ {
-   subnet, gateway, err := GenerateNonOverlappingIPv4Subnet(existingNetworks, 24)
+ // Pre-parse ranges once
+ _, range192, _ := net.ParseCIDR("192.168.0.0/16")
+ _, range172, _ := net.ParseCIDR("172.16.0.0/12")
+ _, range10, _ := net.ParseCIDR("10.0.0.0/8")
+
+ // Now generate more - should use other ranges
+ for i := 0; i < 100; i++ {
+   subnet, gateway, err := GenerateNonOverlappingIPv4Subnet(existingNetworks, 24)
    assert.NoError(t, err, "Should be able to generate subnet %d after exhausting 192.168.0.0/16", i+1)
    assert.NotNil(t, subnet)
    assert.NotNil(t, gateway)
-   // Should not be in 192.168.0.0/16
-   _, range192, _ := net.ParseCIDR("192.168.0.0/16")
+   // Should not be in 192.168.0.0/16
    assert.False(t, range192.Contains(subnet.IP), "Subnet %s should not be in exhausted 192.168.0.0/16 range", subnet.String())
-   // Should be in 172.16.0.0/12 or 10.0.0.0/8
-   _, range172, _ := net.ParseCIDR("172.16.0.0/12")
-   _, range10, _ := net.ParseCIDR("10.0.0.0/8")
+   // Should be in 172.16.0.0/12 or 10.0.0.0/8
    inOtherRange := range172.Contains(subnet.IP) || range10.Contains(subnet.IP)
    assert.True(t, inOtherRange, "Subnet %s should be in 172.16.0.0/12 or 10.0.0.0/8", subnet.String())
    existingNetworks = append(existingNetworks, subnet)
  }

342-386: Add a test for invalid prefixes aligning with production validation

Once you update prod validation to 9..30, add a quick negative test for 8 and 31 to lock behavior.

I can add a small table-driven test if you want.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0300baf and 68c5455.

📒 Files selected for processing (2)
  • network/ipv4.go (2 hunks)
  • network/ipv4_test.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
network/ipv4_test.go (1)
network/ipv4.go (1)
  • GenerateNonOverlappingIPv4Subnet (70-105)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (go)
🔇 Additional comments (2)
network/ipv4.go (1)

83-103: 'for range ' is OK (go 1.25); add guard to skip parent ranges where prefixLen <= parent mask

go.mod declares "go 1.25.0" — the Go 1.22+ pattern is supported. Still avoid 1000 futile attempts by skipping parent ranges:

 for _, rng := range ranges {
+	ones, _ := rng.network.Mask.Size()
+	if prefixLen <= ones {
+		continue
+	}
 	for range 1000 { // Try 1000 times to find non-overlapping subnet

Likely an incorrect or invalid review comment.

network/ipv4_test.go (1)

120-147: Tests look solid; good coverage and scaling checks

Subnets are accumulated to enforce non-overlap, uniqueness is asserted, and long runs are gated. Looks good.

Also applies to: 149-177, 178-210, 297-341, 342-386

@coderabbitai
coderabbitai Bot requested a review from robindiddams September 12, 2025 13:23

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
network/ipv4.go (2)

44-50: Consider overflow protection for subnet calculations.

While unlikely with typical prefix lengths, the bit shift operations could theoretically overflow for edge cases.

Add bounds checking:

 	// Calculate how many subnets of the requested size can fit in the parent
 	subnetBits := prefixLen - parentOnes
+	if subnetBits > 32 {
+		return nil
+	}
 	maxSubnets := uint32(1) << subnetBits

85-105: LGTM with a minor suggestion on gateway IP construction.

The logic correctly generates non-overlapping subnets and properly constructs the gateway IP. The retry mechanism with 1000 attempts per range provides good coverage.

One minor optimization - you could avoid the copy allocation by directly constructing the gateway IP:

-	network := candidate.IP.To4()
-	ip := make(net.IP, 4)
-	copy(ip, network)
-	ip[3] = 0x1 // make the gateway the first ip and copy since we have a shared copy we need to change
+	ip := make(net.IP, 4)
+	copy(ip, candidate.IP.To4())
+	ip[3] = 0x1 // make the gateway the first ip
network/ipv4_bench_test.go (1)

57-80: Consider the computational intensity of this benchmark.

Generating 1000 subnets per benchmark iteration (b.N times) could be extremely time-consuming and may not provide meaningful performance metrics due to the high variance.

Consider reducing the inner loop count or restructuring the benchmark:

 func BenchmarkHighVolumeGeneration(b *testing.B) {
 	// Save and restore original RNG
 	originalRNG := rng
 	defer func() { rng = originalRNG }()
 
 	// Use a seeded RNG for consistent benchmarking
 	rng = rand.New(rand.NewSource(42))
 
+	// Pre-generate base set outside of benchmark timing
+	var baseNetworks []*net.IPNet
+	for j := 0; j < 900; j++ {
+		subnet, _, _ := GenerateNonOverlappingIPv4Subnet(baseNetworks, 24)
+		baseNetworks = append(baseNetworks, subnet)
+	}
+
 	b.ResetTimer()
 
 	for i := 0; i < b.N; i++ {
-		var existingNetworks []*net.IPNet
-
-		// Generate 1000 subnets in each benchmark iteration
-		for j := 0; j < 1000; j++ {
-			subnet, _, err := GenerateNonOverlappingIPv4Subnet(existingNetworks, 24)
+		// Copy base networks for isolation
+		existingNetworks := make([]*net.IPNet, len(baseNetworks))
+		copy(existingNetworks, baseNetworks)
+		
+		// Benchmark generating the next 100 subnets
+		for j := 0; j < 100; j++ {
+			subnet, _, err := GenerateNonOverlappingIPv4Subnet(existingNetworks, 24)
 			if err != nil {
 				b.Fatal(err)
 			}
 			existingNetworks = append(existingNetworks, subnet)
 		}
 	}
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 68c5455 and 3c7d898.

📒 Files selected for processing (5)
  • Makefile (3 hunks)
  • go.mod (1 hunks)
  • network/ipv4.go (2 hunks)
  • network/ipv4_bench_test.go (1 hunks)
  • network/ipv4_test.go (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • go.mod
🧰 Additional context used
🧬 Code graph analysis (2)
network/ipv4_bench_test.go (1)
network/ipv4.go (1)
  • GenerateNonOverlappingIPv4Subnet (72-107)
network/ipv4_test.go (1)
network/ipv4.go (1)
  • GenerateNonOverlappingIPv4Subnet (72-107)
🪛 checkmake (0.2.2)
Makefile

[warning] 1-1: Missing required phony target "clean"

(minphony)

🔇 Additional comments (22)
Makefile (3)

1-1: LGTM!

The addition of the bench target to the PHONY declaration is correct and follows best practices for Makefile organization.


25-25: LGTM!

The integration of the bench target into the test workflow makes sense for continuous performance monitoring during the test cycle.


37-40: LGTM!

The bench target is properly implemented with clear output formatting and runs all benchmarks in the project.

network/ipv4.go (4)

39-43: LGTM!

The validation logic correctly ensures that the requested prefix length is larger than the parent network's prefix to allow proper subnet creation.


54-69: LGTM!

The subnet calculation and IP construction logic is correct and efficient. The direct construction of the IPNet avoids unnecessary operations.


74-76: LGTM!

The tightened validation range from 8-30 to 9-30 is appropriate, as a /8 prefix would be impossible to generate within any of the private ranges (the largest being /8 itself).


78-84: Good randomization implementation!

The shuffling of private ranges before attempting generation provides better distribution across the available IP ranges, addressing the core issue mentioned in the PR objectives.

network/ipv4_bench_test.go (2)

10-26: LGTM!

The benchmark properly saves and restores the original RNG to ensure test isolation and uses a seeded RNG for deterministic results.


28-55: LGTM!

Good benchmark for testing performance with existing subnets. The pre-population of 100 subnets provides a realistic scenario for collision detection performance.

network/ipv4_test.go (13)

121-148: LGTM!

Comprehensive test for generating 100 non-overlapping subnets with proper uniqueness validation and overlap checking.


150-177: LGTM!

Good progressive test with 500 subnets and helpful progress logging for debugging long-running tests.


179-210: LGTM!

Excellent use of testing.Short() to skip this computationally intensive test in CI/quick test runs.


212-273: Well-designed distribution test!

This test effectively validates that the randomization changes achieve their goal of better distribution across private ranges. The lenient thresholds (>30 subnets per range) appropriately account for randomness while still ensuring reasonable distribution.


275-319: LGTM!

Good test to verify that range selection is properly randomized. The threshold of at least 2 ranges being selected first in 30 runs is reasonable to avoid flaky tests while still validating randomization.


321-358: Comprehensive prefix length validation!

Excellent coverage of edge cases and boundary conditions for prefix length validation.


360-385: LGTM!

Good focused test on the specific 9-30 boundary validation with clear error message verification.


387-440: Excellent compatibility matrix test!

This test effectively validates which prefix lengths can fit in which private ranges, ensuring the subnet generation logic correctly handles range limitations.


442-473: LGTM!

Good test for verifying deterministic behavior when using a seeded RNG, which is crucial for reproducible testing and debugging.


475-503: LGTM!

Thorough validation of the optimized subnet construction logic, ensuring all generated subnets have correct structure and proper gateway configuration.


505-548: LGTM!

Good integration test with pre-existing networks across all three private ranges.


550-594: LGTM!

Well-structured capacity tests with progressive sizes to validate the algorithm can handle various scales of subnet generation.


596-635: Excellent exhaustion test!

This test effectively validates that the algorithm properly falls back to other ranges when one range is exhausted. The pre-parsing of CIDR ranges is a good optimization for the repeated checks in the loop.

Comment thread network/ipv4.go
@jhaynie
jhaynie merged commit aa3ee42 into main Sep 12, 2025
4 of 5 checks passed
@jhaynie
jhaynie deleted the subnet-dist branch September 12, 2025 13:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant