Private/ConvertTo-AACCidrRange.ps1
|
function ConvertTo-AACCidrRange { <# .SYNOPSIS IPv4 CIDR arithmetic for the virtual network assessment: a prefix's range and size, and the free blocks left in an address space. .DESCRIPTION -Prefix '10.0.0.0/16' returns @{ Prefix; Version = 4; Start; End; Size; Length; Valid } - Start and End as integers (so ranges can be compared), Size the number of addresses (65,536). An IPv6 prefix returns Version 6 with no range (Azure's IPv6 subnets are /64: their size isn't a useful number), and anything else Valid = $false. -Space with -Used returns the free blocks of the -Space prefixes that no -Used prefix covers, each as the largest aligned CIDR blocks that fit ('10.0.4.0/22', '10.0.8.0/21'...), largest first per gap - the subnets that could still be added. -Format turns a start and length back into 'a.b.c.d/n'. #> [CmdletBinding(DefaultParameterSetName = 'Prefix')] [OutputType([hashtable], [object[]], [string])] param( [Parameter(Mandatory, ParameterSetName = 'Prefix', Position = 0)] [AllowEmptyString()] [string] $Prefix, [Parameter(Mandatory, ParameterSetName = 'Free')] [AllowEmptyCollection()] [string[]] $Space, [Parameter(ParameterSetName = 'Free')] [AllowEmptyCollection()] [string[]] $Used = @(), [Parameter(Mandatory, ParameterSetName = 'Format')] [long] $Start, [Parameter(Mandatory, ParameterSetName = 'Format')] [int] $Length ) $toText = { param([long] $Value) '{0}.{1}.{2}.{3}' -f (($Value -shr 24) -band 255), (($Value -shr 16) -band 255), (($Value -shr 8) -band 255), ($Value -band 255) } if ($PSCmdlet.ParameterSetName -eq 'Format') { return "$(& $toText $Start)/$Length" } $parse = { param([string] $Text) $text = $Text.Trim() if ($text -match '^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})(/(\d{1,2}))?$') { $octets = @([int]$Matches[1], [int]$Matches[2], [int]$Matches[3], [int]$Matches[4]) $bits = if ($Matches[6]) { [int]$Matches[6] } else { 32 } if (@($octets | Where-Object { $_ -gt 255 }).Count -or $bits -gt 32) { return @{ Prefix = $text; Version = 4; Valid = $false } } $value = ([long]$octets[0] -shl 24) + ([long]$octets[1] -shl 16) + ([long]$octets[2] -shl 8) + [long]$octets[3] $size = [long][Math]::Pow(2, 32 - $bits) $first = $value - ($value % $size) return @{ Prefix = $text; Version = 4; Start = $first; End = $first + $size - 1; Size = $size; Length = $bits; Valid = $true } } if ($text -match ':') { return @{ Prefix = $text; Version = 6; Start = $null; End = $null; Size = $null; Length = $(if ($text -match '/(\d+)$') { [int]$Matches[1] } else { 128 }); Valid = $true } } @{ Prefix = $text; Version = 0; Valid = $false } } if ($PSCmdlet.ParameterSetName -eq 'Prefix') { return & $parse $Prefix } # --- Free blocks: each space minus what is used, as aligned CIDRs ------------------------------------------ $taken = @($Used | ForEach-Object { & $parse $_ } | Where-Object { $_.Valid -and $_.Version -eq 4 } | Sort-Object { $_.Start }) $free = [System.Collections.Generic.List[object]]::new() foreach ($range in @($Space | ForEach-Object { & $parse $_ } | Where-Object { $_.Valid -and $_.Version -eq 4 })) { $cursor = $range.Start $gaps = [System.Collections.Generic.List[object]]::new() foreach ($item in $taken) { if ($item.End -lt $range.Start -or $item.Start -gt $range.End) { continue } if ($item.Start -gt $cursor) { $gaps.Add(@($cursor, [Math]::Min($item.Start - 1, $range.End))) } $cursor = [Math]::Max($cursor, $item.End + 1) } if ($cursor -le $range.End) { $gaps.Add(@($cursor, $range.End)) } foreach ($gap in $gaps) { # The largest aligned block at the gap's start, again and again. $at = [long]$gap[0] $blocks = [System.Collections.Generic.List[object]]::new() while ($at -le $gap[1]) { $bits = 32 while ($bits -gt 0) { $size = [long][Math]::Pow(2, 32 - ($bits - 1)) if (($at % $size) -ne 0 -or ($at + $size - 1) -gt $gap[1]) { break } $bits-- } $size = [long][Math]::Pow(2, 32 - $bits) $blocks.Add([pscustomobject]@{ Space = $range.Prefix; Prefix = "$(& $toText $at)/$bits"; Length = $bits; Size = $size; Start = $at }) $at += $size } foreach ($block in $blocks | Sort-Object Size -Descending) { $free.Add($block) } } } $free.ToArray() } |