networktools.ps1
function Get-VLSMBreakdown { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.Net.IPNetwork]$Network, [Parameter(Mandatory = $true, ParameterSetName = "SubnetSize")] [ValidateNotNullOrEmpty()] [array]$SubnetSize, [Parameter(Mandatory = $true, ParameterSetName = "SubnetSizeCidr")] [ValidateNotNullOrEmpty()] [array]$SubnetSizeCidr ) function processRecord($net, $cidr, $type) { try { #try breaking down a network block by CIDR in a form of length $subnets = @([System.Net.IPNetwork]::Subnet($net, $cidr)) #if success push the first resulting subnet to output stack and mark them with associated type $outStack.Push(($subnets[0] | add-member -MemberType NoteProperty -Name type -Value $type -PassThru -Force)) #if there are more subnets generated by "subnet" operation #returning the rest subnets to the Ip blocks queue fur further processing if ($subnets.count -gt 1) { for ($i = 1; $i -le $subnets.count - 1; $i++) { $vlsmStack.Enqueue($subnets[$i]) } } # return true if "subnetting was successfull" $true } catch { # put the network back to the IP blocks queue $vlsmStack.Enqueue($net) # return true if "subnetting failed" $false } } # Check for correct param usage if ("cidr" -in $SubnetSize.Keys) { Throw "You cannot use Cidr notation with SubnetSize param. Please use SubnetSizeCidr param instead." } elseif ("size" -in $SubnetSizeCidr.Keys) { Throw "You cannot use Size notation with SubnetSizeCidr param. Please use SubnetSize param instead." } # Hashtable to map CIDR from $SubnetSize Parameter values to usable IPs. # Needed for calculations. $subnetCidrMap = @{ 16 = 65534 17 = 32766 18 = 16382 19 = 8190 20 = 4094 21 = 2046 22 = 1022 23 = 510 24 = 254 25 = 126 26 = 62 27 = 30 28 = 14 29 = 6 } # If Cidr is being used, we have to convert Cidr to 'usableIPs' to map calucaltion logic in the script below. if ($SubnetSizeCidr) { # Map CIDR from $SubnetSize Parameter values to $subnetCidrMap values. $SubnetAddressMap = @() foreach ($sub in $SubnetSizeCidr) { $subnetDef = @{ type = $sub.type size = $subnetCidrMap.($sub.cidr) } $SubnetAddressMap += $subnetDef } $SubnetSize = $SubnetAddressMap } # Check if summarized $SubnetSize fits into network address range if (($SubnetSize | ForEach-Object { [PSCustomObject]$_ } | Measure-Object -Property size -Sum).Sum -le $Network.Usable) { #queue of masks we wnat to use to break our network $vlsmMasks = [System.Collections.Queue]::new() #calculating mask length based on subnet sizes we get as an input #as an output we get list of objects (type; length) sorted by length #list is stored in $vlsmMasks variable $SubnetSize | ForEach-Object { $length = 32 - [math]::Ceiling([math]::Log($_.size + 2, 2)) if ($length -lt $Network.Cidr) { throw "The subnet $($_.type) is of wrong size" } [PSCustomObject]@{ type = $_.type; length = $length } } | Sort-Object -Property length | ForEach-Object { $vlsmMasks.Enqueue($_) } #queue of networks to break down $vlsmStack = [System.Collections.Queue]::new() #stack of subnets we going to break the network to $outStack = [System.Collections.Stack]::new() #put the very first network to the queue $vlsmStack.Enqueue($Network) #at this point we got two queues: #masks queue and networks queue #masks hosls lengths of all subnets we need and netwoks contains our VNET range (network block) do { $failureCount = 0 #pick a msk form a queue $v = $vlsmMasks.Dequeue() try { #reordering the queue to keep longest masks up top $t = [System.Collections.Queue]::new() $vlsmStack.ToArray() | Sort-Object -Property CIDR -Descending | % { $t.Enqueue($_) } $vlsmStack = $t #pick a network block form a queue $current = $vlsmStack.Dequeue() } catch { write-verbose -Message "No networks in the queue to process" } #processing the block against the mask and assosiated type #if no success - return mask back to tail of a queue and increase failue count if (! (processRecord $current $v.length $v.type) ) { $vlsmMasks.Enqueue($v); $failureCount++ } write-verbose ("VLSM MASKS`: " + $vlsmMasks.Count) write-verbose ("FAILURES`: " + $failureCount) if ($vlsmMasks.Count -eq $failureCount) { break } # break when no records processed during a loop cycle } while ($vlsmMasks.Count -ne 0) #leave when we have masks queue emty write-verbose ("OUT STACK`: " + $outStack.Count) write-verbose ("SubnetSuize`: " + $SubnetSize.count) if ($outStack.count -lt $SubnetSize.count) { write-warning -message "subnetting failed" } #in case we have more subnets than requested store them $reserved = @([System.Net.IPNetwork]::Supernet($vlsmStack.ToArray())) $outStack #and mark them as 'reserved' $reserved | add-member -MemberType NoteProperty -Name type -Value "reserved" -PassThru -Force } else { Throw "The specified address space of $($Network.Network.IPAddressToString)/$($Network.cidr) is too small for the subnets." } } function Get-IPRanges { [cmdletbinding()] param( [Parameter(Mandatory = $true, ParameterSetName = "ByBaseNet")] $Networks, [Parameter(Mandatory = $true, ParameterSetName = "ByBaseNet")] [System.Net.IPNetwork] $BaseNet, [Parameter(Mandatory = $true, ParameterSetName = "ByBaseNet")] [ValidateRange(8, 30)] [int] $CIDR ) function NextSubnet ($network, $newCIDR ) { $net = [System.Net.IPNetwork]::Parse($network) $netmask = [System.Net.IPNetwork]::ToNetmask($newCIDR, $net.AddressFamily) [bigint] $nextAddr = [System.Net.IPNetwork]::ToBigInteger($net.Broadcast) + 1 [bigint] $mask = [System.Net.IPNetwork]::ToBigInteger($netmask) [bigint] $masked = $nextAddr -band $mask if ($masked -eq $nextAddr) { Write-Verbose "Masked" $next = $masked } else { Write-Verbose "Magic!" $next = $masked + [bigint]::Pow(2, 32 - $newCIDR) } $nextNetwork = [System.Net.IPNetwork]::ToIPAddress($next, [System.Net.Sockets.AddressFamily]::InterNetwork) $nextSubnet = [System.Net.IPNetwork]::Parse($nextNetwork, $netmask) $nextSubnet } function getIPRanges { for ($i = 0; $i -le ($outNets.Count - 1); $i++ ) { $n = $outNets[$i] $ns = NextSubnet $n $CIDR Write-Verbose "subnet is`: $n; next subnet is $ns" # test if 'next subnet' is a part of a base range if (-not [System.Net.IPNetwork]::Contains($BaseNet, $ns)) { Write-Verbose "$ns is not in a $BaseNet" # skip the network if it is not in a base range continue; } else { Write-Verbose "$ns is in a $BaseNet" } # test if 'next subnet' overlaps any of the existing below it in a sorted list $k = $i; $isoverlap = $false do { if ([System.Net.IPNetwork]::Overlap($outNets[$k], $ns)) { Write-Verbose "$ns overlaps $($outNets[$k])" # break this loop if there is an overlap $isoverlap = $true break } $k++ } while ($k -lt ($outNets.Count)) # when there were no overlaps -> output if (! $isoverlap) { Write-Verbose "$ns did not overlap" $ns } } } $calcNets = { # put all networks we've found to the out collection, sort them # for some reason sort cmdlet does not work well, so use Lists and internal comparer. $outNets = [System.Collections.Generic.List[System.Net.IPNetwork]]::new() $Networks | ForEach-Object { if ($_) { $outNets.add($_) } } $outNets.Sort() # create a collection of unused nets $freeNets = [System.Collections.Generic.List[System.Net.IPNetwork]]::new() # mark all existing as 'used' $outNets | Add-Member -MemberType NoteProperty -Name IsFree -Value $false -Force # search for free ranges in between the items of the 'used' nets list and after the last one $n = getIPRanges Write-Verbose "ip ranges we've got`: $n" # if there is something - add them to 'unused' list # mark them as 'unused' if ($n.count -gt 0) { $n | ForEach-Object { $net = $_ | Add-Member -MemberType NoteProperty -Name IsFree -Value $true -Force -PassThru; $freeNets.add($net) } } # TODO: search networks in front of the provided set of used networks $firstFree = [System.Net.IPNetwork]::ToBigInteger($BaseNet.FirstUsable) if ($outNets.Count -gt 0) { $lastFree = [System.Net.IPNetwork]::ToBigInteger($outNets[0].FirstUsable) } else { $lastFree = [System.Net.IPNetwork]::ToBigInteger($BaseNet.FirstUsable) } $diff = $lastFree - $firstFree if ($diff -gt 0) { Write-Verbose "there are addresses in front of occupied blocks, number is`: $diff" $frontNets = [System.Collections.Generic.List[System.Net.IPNetwork]]::new() $firstNetToTest = "$($BaseNet.FirstUsable.ToString())/$CIDR" $netToTest = [System.Net.IPNetwork]::Parse($firstNetToTest) while (-not [System.Net.IPNetwork]::Overlap($outNets[0], $netToTest)) { $netToTest | Add-Member -MemberType NoteProperty -Name IsFree -Value $true -Force $frontNets.Add($netToTest) $netToTest = NextSubnet $netToTest $CIDR } Write-Verbose "$frontNets" $freeNets.AddRange( $frontNets ) $freeNets.Sort() } # join used and unused together to output them properly $outNets.AddRange( $freeNets ) $outNets.Sort() # sometimes two consequent subnets may end up with the same next free subnet. selecting unique values to avoid confusion $outNets | Select-Object -Unique } & $calcNets } |