IP.Tools.psm1

## Running A Build Will Compile This To A Single PSM1 File Containing All Module Code ##

## If Importing Module Source Directly, This Will Dynamically Build Root Module ##

# Get list of private functions and public functions to import, in order.
$Private = @(Get-ChildItem -Path $PSScriptRoot\private -Recurse -Filter "*.ps1") | Sort-Object Name
$Public = @(Get-ChildItem -Path $PSScriptRoot\public -Recurse -Filter "*.ps1") | Sort-Object Name

$AllMask = [Math]::Pow(2, 32)
New-Variable -Name AllMask -Value $AllMask -Scope Script -Force

Class IpNetwork {

  [ipaddress]$IpAddress
  [ipaddress]$IpNetmask

  IpNetwork ([string]$CIDR) {
    ($IP, $MaskLen) = $CIDR.Split('/')
    $IP = Get-IpNetworkBase -CIDR $CIDR
    Write-Verbose "IP: $($IP); Mask: $($MaskLen)"
    $this.IpAddress = [ipaddress]($IP)
    $this.IpNetmask = [ipaddress](Convert-MaskLenToIp($MaskLen))
  }

  IpNetwork ([IpAddress]$IP, [IpAddress] $Mask) {
    Write-Verbose "IP: $($IP); Mask: $($Mask)"
    $IP = Get-IpNetworkBase -IP $IP -Mask $Mask
    $this.IpAddress = $IP
    $this.IpNetmask = $Mask
  }
}

# Dot source the private function files.
foreach ($ImportItem in $Private) {
  try {
    . $ImportItem.FullName
    Write-Verbose -Message ("Imported private function {0}" -f $ImportItem.FullName)
  }
  catch {
    Write-Error -Message ("Failed to import private function {0}: {1}" -f $ImportItem.FullName, $_)
  }
}

# Dot source the public function files.
foreach ($ImportItem in $Public) {
  try {
    . $ImportItem.FullName
    Write-Verbose -Message ("Imported public function {0}" -f $ImportItem.FullName)
  }
  catch {
    Write-Error -Message ("Failed to import public function {0}: {1}" -f $ImportItem.FullName, $_)
  }
}

# Export the public functions.
Export-ModuleMember -Function $Public.BaseName
# Private Function Example - Replace With Your Function
function Add-PrivateFunction {

  [CmdletBinding()]

  Param (
    # Your parameters go here...
  )

  # Your function code goes here...
  Write-Output "Your private function ran!"

}

function Get-IpBogon {
  <#
    .SYNOPSIS
        Return a list of IP Bogons with [ipaddress] objects representing the IP and Mask
    .DESCRIPTION
        Return a list of IP Bogons (Internet non-routable addresses) with [ipaddress] objects representing both IP and Mask.

        IP Bogons are Internet non-routable addresses that are either reserved for Private use (RFC1918), or are otherwise not available.
    .INPUTS
        None
    .OUTPUTS
        Array of [ipaddress] objects representing IP Bogons.
    .EXAMPLE
        PS> $Bogons = Get-IpBogon.ps1
    .LINK
        https://github.com/jberkers42/ip.tools
  #>

  [CmdletBinding()]

  $Bogons = @()
  $Bogons += '0.0.0.0/8'          # RFC1122 "This" network
  $Bogons += '10.0.0.0/8'         # RFC1918 Private-use networks
  $Bogons += '100.64.0.0/10'      # RFC6598 Carrier-grade NAT
  $Bogons += '127.0.0.0/8'        # RFC1122 IPv4 Loopback
  $Bogons += '169.254.0.0/16'     # RFC3927 IPv4 Link local
  $Bogons += '172.16.0.0/12'      # RFC1918 Private-use networks
  $Bogons += '192.0.0.0/24'       # RFC5736 IETF protocol assignments
  $Bogons += '192.0.2.0/24'       # RFC5737 TEST-NET-1
  $Bogons += '192.168.0.0/16'     # RFC1918 Private-use networks
  $Bogons += '198.18.0.0/15'      # RFC2544 Network interconnect device benchmark testing
  $Bogons += '198.51.100.0/24'    # RFC5737 TEST-NET-2
  $Bogons += '203.0.113.0/24'     # RFC5737 TEST-NET-3
  $Bogons += '224.0.0.0/4'        # RFC1112 Multicast
  $Bogons += '240.0.0.0/4'        # Reserved for future use

  $BogonIps = @()

  foreach ($Bogon in $Bogons) {

      $BogonIp = [IpNetwork]::new($Bogon)

      $BogonIPs += $BogonIp
  }

  Write-Output $BogonIps

}

function Remove-IpBogon {
  #
  <#
    .SYNOPSIS
      Remove any Bogon IPs from the list of provided IPs.
    .DESCRIPTION
      Remove any Bogon IPs from the list of provided IPs. Each IP is expected to be either a dotted quad notation string representation of the address, or an [ipaddress] object.
    .INPUTS
      [IpAddress] or [string] values to filter
    .OUTPUTS
      Array of [ipaddress] objects that have had IP Bogons removed.
    .EXAMPLE
      PS> $IPs = Get-IpFromSomeSource
      PS> $IPs | Remove-IpBogon

      Obtain a list of IPs from some source (like a SIEM), and filter out the Bogon addresses
    .LINK
        https://github.com/jberkers42/ip.tools
  #>

[cmdletbinding(SupportsShouldProcess)]
  param (
      [Parameter(Mandatory=$true,
          ValueFromPipeLine=$true)]
      [ipaddress] $Address
  )

  Begin {
      $Me = $MyInvocation.MyCommand.Name

      Write-Verbose $Me
  }

  Process {

    if ($PSCmdlet.ShouldProcess($Address, "Remove IP if Bogon")) {
      if (!(Test-IpBogon -Address $Address)) {
        Write-Output $Address
    }
  }

  }

  End {

  }

}

function Test-IpBogon {

  [cmdletbinding()]
  param (
      [Parameter(Mandatory=$true,
          ValueFromPipeLine=$true)]
      [ipaddress] $Address
  )

  Begin {
      $Me = $MyInvocation.MyCommand.Name

      Write-Verbose $Me

      $IpBogons = Get-IpBogon
  }

  Process {

      $IsBogon = $false

      foreach ($Bogon in $IpBogons) {
          if (Test-IpInNetwork -Address $Address -Network $Bogon) {
              $IsBogon = $true
          }
      }

      Write-Output $IsBogon

  }

  End {
      Clear-Variable 'IpBogons'
  }

}

function Convert-MaskLenToIp {
  <#
    .SYNOPSIS
      Convert MaskLen from CIDR Notation (IP/MaskLen) to an IP Address object
    .DESCRIPTION
      Convert MaskLen from CIDR Notation (IP/MaskLen) to an IP Address object
    .PARAMETER MaskLen
      Integer value representing the mask length
    .INPUTS
      Integer Mask Lenth
    .OUTPUTS
      [ipaddress] object representing the Netmask
    .EXAMPLE
      Pass a single MaskLen as a parameter

      PS> Convert-MaskLenToIp -MaskLen 24

      AddressFamily : InterNetwork
      ScopeId :
      IsIPv6Multicast : False
      IsIPv6LinkLocal : False
      IsIPv6SiteLocal : False
      IsIPv6Teredo : False
      IsIPv6UniqueLocal : False
      IsIPv4MappedToIPv6 : False
      Address : 16777215
      IPAddressToString : 255.255.255.0

    .EXAMPLE
      Use Pipeline to convert MaskLen to IP Address

      PS> 27 | Convert-MaskLenToIp

      AddressFamily : InterNetwork
      ScopeId :
      IsIPv6Multicast : False
      IsIPv6LinkLocal : False
      IsIPv6SiteLocal : False
      IsIPv6Teredo : False
      IsIPv6UniqueLocal : False
      IsIPv4MappedToIPv6 : False
      Address : 3774873599
      IPAddressToString : 255.255.255.224

    .LINK
      https://github.com/jberkers42/ip.tools
  #>

  [CmdletBinding()]
  [OutputType([ipaddress])]

  param(
    [Parameter(Mandatory=$true,
      ValueFromPipeLine=$true)]
    [ValidateRange([int]0, [int]32)]
    [int]$MaskLen
  )

  Begin {
    $Me = $MyInvocation.MyCommand.Name

    Write-Verbose $Me
  }

  Process {

    [ipaddress]($AllMask - ($AllMask -shr $MaskLen))

  }

  End {

  }

}

function Get-IpNetworkBase {
  <#
    .SYNOPSIS
      Get the Base Network address for the supplied IP/Mask
    .DESCRIPTION
      Get the Base Network address for the supplied IP/Mask
    .PARAMETER CIDR
      [string] containing CIDR notation for network address
    .PARAMETER IP
      [IPAddress] object or Dotted Quad notation string representing the IP address
    .PARAMETER Mask
      [IpNetwork] Object or Dotted Quad notation string representing the Netmask
    .INPUTS
      CIDR Strings
    .OUTPUTS
      [IpAddress] Object
    .EXAMPLE
      PS> (Get-IpNetworkBase -CIDR 192.168.100.20/24).IPAddressToString
      192.168.100.0


    .EXAMPLE
      Use Pipeline to convert MaskLen to IP Address

      PS> 27 | Convert-MaskLenToIp


    .LINK
      https://github.com/jberkers42/ip.tools
  #>

  [CmdletBinding(DefaultParameterSetName = 'CIDR')]
  [OutputType([IpAddress])]
  param(
    [Parameter(ParameterSetName='CIDR',
      Mandatory=$true,
      Position = 0,
      ValueFromPipeline=$true)]
    [string] $CIDR,

    [Parameter(ParameterSetName='IpMask',
      Mandatory=$true,
      Position = 0)]
    [IpAddress] $IP,

    [Parameter(ParameterSetName='IpMask',
      Mandatory=$true,
      Position = 1)]
    [IpAddress] $Mask
  )

  Begin {
    $Me = $MyInvocation.MyCommand.Name

    Write-Verbose $Me
  }

  Process {
    If ($PSCmdlet.ParameterSetName -eq "CIDR") {
      ([IpAddress]$IP, $MaskLen) = $CIDR -split '/'
      [IpAddress]$Mask = Convert-MaskLenToIp $MaskLen
    }
    Write-Verbose "IP: $($IP); Mask: $($Mask);"
    [IpAddress]$Net = ($IP.Address -band $Mask.Address)
    return $Net
  }

  End {

  }

}

function New-IpNetwork {
  <#
    .SYNOPSIS
      Create a new IpNetwork object with the specified CIDR or IP and Mask parameters
    .DESCRIPTION
      Create a new IpNetwork object with the specified CIDR or IP and Mask parameters
    .PARAMETER CIDR
      [string] containing CIDR notation for network
    .PARAMETER IP
      [IPAddress] object or Dotted Quad notation string representing the IP address
    .PARAMETER Mask
      [IpNetwork] Object or Dotted Quad notation string representing the Netmask
    .INPUTS
      CIDR Strings
    .OUTPUTS
      [IpNetwork] Object
    .EXAMPLE
      PS> $Network =
      PS> Test-IpInNetwork -Address 192.168.100.20 -Network


    .EXAMPLE
      Use Pipeline to convert MaskLen to IP Address

      PS> 27 | Convert-MaskLenToIp


    .LINK
      https://github.com/jberkers42/ip.tools
  #>

  [CmdletBinding(DefaultParameterSetName = 'CIDR', SupportsShouldProcess)]
  param(
    [Parameter(ParameterSetName='CIDR',
      Mandatory=$true,
      Position = 0,
      ValueFromPipeline=$true)]
    [string] $CIDR,

    [Parameter(ParameterSetName='IpMask',
      Mandatory=$true,
      Position = 0)]
    [string] $IP,

    [Parameter(ParameterSetName='IpMask',
      Mandatory=$true,
      Position = 1)]
    [string] $Mask
  )

  Begin {
    $Me = $MyInvocation.MyCommand.Name

    Write-Verbose $Me
  }

  Process {
    if ($PSCmdlet.ShouldProcess("IpNetwork", "Return new IP Network from CIDR or IP and Mask")) {
      Switch ($PSCmdlet.ParameterSetName) {
        "CIDR" {
          return [IpNetwork]::new($CIDR)
        }
        "IpMask" {
          return [IpNetwork]::new($IP, $Mask)
        }
      }
    }
  }

  End {

  }

}

function Test-IpInNetwork {
  <#
    .SYNOPSIS
      Test if an IP address exists in the specified network
    .DESCRIPTION
      Test if an IP address exists in the specified network
    .PARAMETER Address
      [IPAddress] object or Dotted Quad notation string representing the IP address to test
    .PARAMETER Network
      [IpNetwork] Object consisting of IpAddress and IpNetmask to check if IP belongs. If supplied as string, will be converted to IpNetwork before testing.
    .INPUTS
      IP Addresses and networks.
    .OUTPUTS
      [bool] result of test
    .EXAMPLE
      PS> $Network = New-IpNetwork "192.168.100.0/24"
      PS> Test-IpInNetwork -Address 192.168.100.20 -Network $Network


    .EXAMPLE
      Use Pipeline to convert MaskLen to IP Address

      PS> 27 | Convert-MaskLenToIp


    .LINK
      https://github.com/jberkers42/ip.tools
  #>

  [cmdletbinding()]
  param (
      [Parameter(Mandatory=$true,
          ValueFromPipeLine=$true)]
      [ipaddress] $Address,

      [Parameter(Mandatory=$true,
          ValueFromPipeline=$true)]
      [ValidateScript({
        $TypeName = $_ | Get-Member | Select-Object -ExpandProperty TypeName -Unique
        if ($TypeName -eq 'System.String' -and $_ -match "(\d{1,3}\.){3}\d{1,3}\/\d{1,2}") {
          Write-Verbose "Convert From String: $_"
          New-IpNetwork $_
        } elseif ($TypeName -eq 'IpNetwork') {
          Write-Verbose "Taken as IpNetwork Object $_"
          $_
        }
      })]
      $Network
  )

  Begin {
      $Me = $MyInvocation.MyCommand.Name

      Write-Verbose $Me

  }

  Process {

    Write-Verbose "Network To Test: $Network"

    if ($Network.GetType().Name -eq 'String') {
      $Network = New-IpNetwork -CIDR $Network
    }

    if ($Network.IpAddress.Address -eq ($Address.Address -band $Network.IpNetmask.Address)) {
        Write-Output $true
    } else {
        Write-Output $false
    }
  }

  End {

  }
}

Export-ModuleMember -Function Get-IpBogon, Remove-IpBogon, Test-IpBogon, Convert-MaskLenToIp, Get-IpNetworkBase, New-IpNetwork, Test-IpInNetwork