Created
March 5, 2020 16:16
-
-
Save purplemonkeymad/ea2d9fa5832797fa5dc2159db5016822 to your computer and use it in GitHub Desktop.
Powershell class for subnet calculation.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class IPSubnet { | |
[ipaddress]$InitialAddress | |
[ValidateRange(0,128)] | |
[int]$SubnetLength | |
# basic constructor | |
IPSubnet([ipaddress]$InitialAddress,[int]$SubnetLength){ | |
$this.InitialAddress = $InitialAddress | |
$this.SubnetLength = $SubnetLength | |
$this.__addScriptProperties() | |
} | |
# no subnet | |
IPSubnet([ipaddress]$InitialAddress){ | |
$this.InitialAddress = $InitialAddress | |
$this.SubnetLength = $this.InitialAddress.GetAddressBytes().length*8 # by default we go for a single ip subnet. | |
$this.__addScriptProperties() | |
} | |
# from string | |
IPSubnet([string]$SubnetString){ | |
if ($SubnetString.Contains([char]'/')){ | |
$Address,$SubNet = $SubnetString -split '/' | |
if ($SubNet.count -gt 1){ | |
$SubNet = $SubNet[0] | |
} | |
$this.InitialAddress = $Address | |
$this.SubnetLength = $SubNet | |
} else { | |
$this.InitialAddress = $SubnetString | |
$this.SubnetLength = $this.InitialAddress.GetAddressBytes().length*8 # by default we go for a single ip subnet. | |
} | |
$this.__addScriptProperties() | |
} | |
## add script properties to this object, | |
## ps 5.1 does not support modified getters and setters for properties | |
hidden [void]__addScriptProperties(){ | |
# address family | |
Add-Member -InputObject $this -MemberType ScriptProperty -Name AddressFamily -Value { | |
return $this.InitialAddress.AddressFamily | |
} | |
# network address | |
Add-Member -InputObject $this -MemberType ScriptProperty -Name NetworkAddress -Value { | |
return $this.GetNetworkAddress() | |
} | |
# b cast address | |
Add-Member -InputObject $this -MemberType ScriptProperty -Name BroadcastAddress -Value { | |
return $this.GetBroadcastAddress() | |
} | |
} | |
# contains method for ip addresses, return true if the address is in the subnet. | |
[bool]contains([ipaddress]$ReferenceAddress){ | |
if ($ReferenceAddress.AddressFamily -ne $this.InitialAddress.AddressFamily){ | |
throw "Reference Address is from a different family." | |
} | |
# i don't like using loops for this, but it appears to be the best | |
# way to deal with variable length protocol addresses (ip4/ip6 etc) | |
$subnetBytes = $this.LengthToSubnetBytes($this.SubnetLength) | |
$baseBytes = $this.InitialAddress.GetAddressBytes() | |
$SubNetBaseAddress = for ($byteIndex = 0; $byteIndex -lt $subnetBytes.Length; $byteIndex++){ | |
$baseBytes[$byteIndex] -band $subnetBytes[$byteIndex] | |
} | |
$referenceBytes = $ReferenceAddress.GetAddressBytes() | |
$ReferenceAddressBytes = for ($byteIndex = 0; $byteIndex -lt $subnetBytes.Length; $byteIndex++){ | |
$referenceBytes[$byteIndex] -band $subnetBytes[$byteIndex] | |
} | |
# if all bytes are the same, then we have the same network address and a match | |
for ($byteIndex = 0; $byteIndex -lt $ReferenceAddressBytes.Length; $byteIndex++){ | |
if ($ReferenceAddressBytes[$byteIndex] -eq $SubNetBaseAddress[$byteIndex]){ | |
# do nothing | |
} else { | |
# must be different | |
return $false | |
} | |
} | |
# must be good if we are here | |
return $true | |
} | |
# helper for converting subnet lengths into a set of bytes. | |
hidden [byte[]]LengthToSubnetBytes([int]$length){ | |
$targetLength = $this.InitialAddress.GetAddressBytes().Length | |
if ($length -gt ($targetLength*8)){ | |
throw "Subnet length too long, got $Length, must be equal or less than $($targetLength*8)" | |
} | |
$bytes = $( | |
while ($length -ge 8){ # whole octets | |
[byte]255 | |
$length = $length - 8 | |
} | |
# final octet | |
if ($length -gt 0){ | |
# we take all ones, then shift right to introduce $length bits in the upper bit | |
# then xor with all ones to do a bitwize not | |
[byte](([byte]255 -shr $length) -bxor [byte]255) | |
} | |
) | |
if ($bytes.count -gt $targetLength){ | |
throw "Calculated too many bytes, probably my fault." | |
} | |
if ($bytes.count -lt $targetLength){ | |
# array addition but still works around ps array unroll of $bytes | |
$bytes = $( | |
$bytes | |
@([byte]0) * ($targetLength - $bytes.count) | |
) | |
} | |
return $bytes | |
} | |
# help to find first 0th ip in subnet | |
hidden [ipaddress]GetNetworkAddress(){ | |
$subnetBytes = $this.LengthToSubnetBytes($this.SubnetLength) | |
$networkipBytes = $this.InitialAddress.GetAddressBytes() | |
$BaseNetAddress = for ($byteIndex = 0; $byteIndex -lt $subnetBytes.Length; $byteIndex++){ | |
$networkipBytes[$byteIndex] -band $subnetBytes[$byteIndex] | |
} | |
return ([ipaddress]::new($BaseNetAddress)) | |
} | |
# mask bytes | |
hidden [byte[]]LengthToMaskBytes([int]$length){ | |
$subnetBytes = $this.LengthToSubnetBytes($length) | |
# the mask is basically the inversion of the subnet | |
$maskBytes = for ($byteIndex = 0; $byteIndex -lt $subnetBytes.Length; $byteIndex++){ | |
$subnetBytes[$byteIndex] -bxor [byte]255 | |
} | |
return $maskBytes | |
} | |
# broadcast address | |
hidden [ipaddress]GetBroadcastAddress(){ | |
$addressBytes = $this.InitialAddress.GetAddressBytes() | |
$maskBytes = $this.LengthToMaskBytes($this.SubnetLength) | |
# bcast is every bit on subnet set to 1 or any ip bitwise or with the mask | |
$bcastBytes = for ($byteIndex = 0; $byteIndex -lt $addressBytes.Length; $byteIndex++){ | |
$addressBytes[$byteIndex] -bor $maskBytes[$byteIndex] | |
} | |
return ([ipaddress]::new($bcastBytes)) | |
} | |
# turn into a string with format ipaddress/subnetbits | |
[string]ToString() { | |
return "$($this.InitialAddress)/$($this.SubnetLength)" | |
} | |
# test if equal to another subnet | |
[bool]Equals([object]$Subnet) { | |
if ($Subnet -isnot [IPSubnet]){ | |
return $false | |
} | |
# need to be the same address type | |
if ($Subnet.InitialAddress.AddressFamily -ne $this.InitialAddress.AddressFamily){ | |
return $false | |
} | |
# need to have the same mask length | |
if ($Subnet.SubnetLength -ne $this.SubnetLength){ | |
return $false | |
} | |
# if one contains the other base address then they are in the same subnet | |
return ($this.contains($Subnet.InitialAddress)) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment