Skip to content

Instantly share code, notes, and snippets.

@purplemonkeymad
Created March 5, 2020 16:16
Show Gist options
  • Save purplemonkeymad/ea2d9fa5832797fa5dc2159db5016822 to your computer and use it in GitHub Desktop.
Save purplemonkeymad/ea2d9fa5832797fa5dc2159db5016822 to your computer and use it in GitHub Desktop.
Powershell class for subnet calculation.
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