Skip to content

Commit

Permalink
Add IPv6 support and caching
Browse files Browse the repository at this point in the history
  • Loading branch information
jazuntee committed Nov 7, 2022
1 parent 8d604fb commit ee2d365
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 40 deletions.
65 changes: 44 additions & 21 deletions src/Get-MsIdAzureIpRange.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ function Get-MsIdAzureIpRange {
[string] $ServiceTag = '' # Default ServiceTag parameter value
if ($fakeBoundParameters.ContainsKey('ServiceTag')) { $ServiceTag = $fakeBoundParameters.ServiceTag }

[array] $AllServiceTagsAndRegions = Get-MSIDAzureIpRange -Cloud $Cloud -AllServiceTagsAndRegions -Verbose:$false
#$StartPosition = $host.UI.RawUI.CursorPosition
#Write-Host '...' -NoNewline

[array] $AllServiceTagsAndRegions = Get-MsIdAzureIpRange -Cloud $Cloud -AllServiceTagsAndRegions -Verbose:$false
#$AllServiceTagsAndRegions.values.properties.region | Select-Object -Unique | Where-Object { $_ }

$listRegions = New-Object System.Collections.Generic.List[string]
Expand All @@ -59,6 +62,9 @@ function Get-MsIdAzureIpRange {
if ($listRegions) {
$listRegions #| ForEach-Object {$_}
}

#$host.UI.RawUI.CursorPosition = $StartPosition
#Write-Host (' ') -NoNewline
})]
[string] $Region,

Expand All @@ -72,7 +78,8 @@ function Get-MsIdAzureIpRange {
[string] $Region = '' # Default Region parameter value
if ($fakeBoundParameters.ContainsKey('Region')) { $Region = $fakeBoundParameters.Region }

[array] $AllServiceTagsAndRegions = Get-MSIDAzureIpRange -Cloud $Cloud -AllServiceTagsAndRegions -Verbose:$false
#Write-Host '...' -NoNewline
[array] $AllServiceTagsAndRegions = Get-MsIdAzureIpRange -Cloud $Cloud -AllServiceTagsAndRegions -Verbose:$false
#$AllServiceTagsAndRegions.values.properties.region | Select-Object -Unique | Where-Object { $_ }

$listServiceTags = New-Object System.Collections.Generic.List[string]
Expand All @@ -91,30 +98,46 @@ function Get-MsIdAzureIpRange {

# List all IP ranges catagorized by Service Tag and Region.
[Parameter(Mandatory = $false, ParameterSetName = 'AllServiceTagsAndRegions')]
[switch] $AllServiceTagsAndRegions
[switch] $AllServiceTagsAndRegions,

# Bypass cache and download data again.
[Parameter(Mandatory = $false)]
[switch] $ForceRefresh
)

[hashtable] $MdcIdCloudMapping = @{
Public = 56519
Government = 57063
Germany = 57064
China = 57062
}
## Get data cache
if (!(Get-Variable cacheAzureIPRangesAndServiceTags -ErrorAction SilentlyContinue)) { New-Variable -Name cacheAzureIPRangesAndServiceTags -Scope Script -Value (New-Object hashtable) }

## Download data and update cache
if ($ForceRefresh -or !$cacheAzureIPRangesAndServiceTags.ContainsKey($Cloud)) {
Write-Verbose ('Downloading data for Cloud [{0}].' -f $Cloud)
[hashtable] $MdcIdCloudMapping = @{
Public = 56519
Government = 57063
Germany = 57064
China = 57062
}

[uri] $MdcUri = 'https://www.microsoft.com/en-us/download/confirmation.aspx?id={0}' -f $MdcIdCloudMapping[$Cloud]
[uri] $MdcDirectUri = $null # Example: https://download.microsoft.com/download/7/1/D/71D86715-5596-4529-9B13-DA13A5DE5B63/ServiceTags_Public_20191111.json
[uri] $MdcUri = 'https://www.microsoft.com/en-us/download/confirmation.aspx?id={0}' -f $MdcIdCloudMapping[$Cloud]
[uri] $MdcDirectUri = $null # Example: https://download.microsoft.com/download/7/1/D/71D86715-5596-4529-9B13-DA13A5DE5B63/ServiceTags_Public_20191111.json

$MdcResponse = Invoke-WebRequest -UseBasicParsing -Uri $MdcUri
if ($MdcResponse -match 'https://download\.microsoft\.com/download/.+?/ServiceTags_.+?_[0-9]{6,8}\.json') {
$MdcDirectUri = $Matches[0]
}
$MdcResponse = Invoke-WebRequest -UseBasicParsing -Uri $MdcUri
if ($MdcResponse -match 'https://download\.microsoft\.com/download/.+?/ServiceTags_.+?_[0-9]{6,8}\.json') {
$MdcDirectUri = $Matches[0]
}

if ($MdcDirectUri) {
$AzureIPs = Invoke-RestMethod -UseBasicParsing -Uri $MdcDirectUri -ErrorAction Stop
if ($MdcDirectUri) {
$cacheAzureIPRangesAndServiceTags[$Cloud] = Invoke-RestMethod -UseBasicParsing -Uri $MdcDirectUri -ErrorAction Stop
}
}
else {
Write-Verbose ('Using cached data for Cloud [{0}]. Use -ForceRefresh parameter to bypass cache.' -f $Cloud)
}
$AzureServiceTagsAndRegions = $cacheAzureIPRangesAndServiceTags[$Cloud]

## Return the data
if ($AllServiceTagsAndRegions) {
return $AzureIPs
return $AzureServiceTagsAndRegions
}
else {
[string] $Id = 'AzureCloud'
Expand All @@ -125,9 +148,9 @@ function Get-MsIdAzureIpRange {
$Id += '.{0}' -f $Region
}

$OutputIPs = $AzureIPs.values | Where-Object id -EQ $Id
if ($OutputIPs) {
return $OutputIPs.properties.addressPrefixes
$FilteredServiceTagsAndRegions = $AzureServiceTagsAndRegions.values | Where-Object id -EQ $Id
if ($FilteredServiceTagsAndRegions) {
return $FilteredServiceTagsAndRegions.properties.addressPrefixes
}
}
}
23 changes: 18 additions & 5 deletions src/Resolve-MsIdAzureIpAddress.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,27 @@ function Resolve-MsIdAzureIpAddress {
[object[]] $InputObjects,
# IP Address of Azure Service
[Parameter(Mandatory = $true, ParameterSetName = 'IpAddress', Position = 1)]
[ipaddress[]] $IpAddresses
[ipaddress[]] $IpAddresses,
# Name of Azure Cloud. Valid values are: Public, Government, Germany, China
[Parameter(Mandatory = $false)]
[ValidateSet('Public', 'Government', 'Germany', 'China')]
[string[]] $Clouds = @('Public', 'Government', 'Germany', 'China'),
# Bypass cache and download data again.
[Parameter(Mandatory = $false)]
[switch] $ForceRefresh
)

begin {
[string[]] $Clouds = 'Public', 'Government', 'Germany', 'China'
#[string[]] $Clouds = 'Public', 'Government', 'Germany', 'China'
[hashtable] $ServiceTagAndRegions = @{}
$PreviousProgressPreference = $ProgressPreference
$ProgressPreference = 'SilentlyContinue'
Write-Verbose 'Getting Azure IP Ranges and Service Tag Data...'
foreach ($Cloud in $Clouds) {
$ServiceTagAndRegions.Add($Cloud, (Get-MsIdAzureIpRange -Cloud $Cloud -AllServiceTagsAndRegions -Verbose:$false))
$ServiceTagAndRegions.Add($Cloud, (Get-MsIdAzureIpRange -Cloud $Cloud -AllServiceTagsAndRegions -ForceRefresh:$ForceRefresh -Verbose:$false))
}
$ProgressPreference = $PreviousProgressPreference
Write-Verbose 'Resolving IP Address to Azure Service Tags...'
}

process {
Expand All @@ -50,8 +59,12 @@ function Resolve-MsIdAzureIpAddress {
$listIpAddresses.Add($InputObject)
}
elseif ($InputObject -is [string]) {
if ($InputObject -match '^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$') {
$listIpAddresses.Add($InputObject)
if ($InputObject -match '^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$' -or $InputObject -match '^(?:(?::{1,2})?[0-9a-fA-F]{1,4}(?::{1,2})?){1,8}$') {
try {
[ipaddress] $IpAddress = $InputObject
$listIpAddresses.Add($IpAddress)
}
catch { throw }
}
else {
$DnsNames = Resolve-DnsName $InputObject -Type A -ErrorAction Stop | Where-Object QueryType -EQ A
Expand Down
80 changes: 66 additions & 14 deletions src/internal/Test-IpAddressInSubnet.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
Determine if an IP address exists in the specified subnet.
.EXAMPLE
PS C:\>Test-IpAddressInSubnet 192.168.1.10 -Subnet '192.168.1.1/32','192.168.1.0/24'
Determine if the IP address exists in the specified subnet.
Determine if the IPv4 address exists in the specified subnet.
.EXAMPLE
PS C:\>Test-IpAddressInSubnet 2001:db8:1234::1 -Subnet '2001:db8:a::123/64','2001:db8:1234::/48'
Determine if the IPv6 address exists in the specified subnet.
.INPUTS
System.Net.IPAddress
.LINK
Expand All @@ -16,36 +19,85 @@ function Test-IpAddressInSubnet {
# IP Address to test against provided subnets.
[Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 1)]
[ipaddress[]] $IpAddresses,
# List of subnets.
# List of subnets in CIDR notation. For example, "192.168.1.0/24" or "2001:db8:1234::/48".
[Parameter(Mandatory = $true)]
[string[]] $Subnets,
# Return list of matching subnets rather than a boolean result.
[Parameter(Mandatory = $false)]
[switch] $ReturnMatchingSubnets
)

begin {
function ConvertBitArrayToByteArray([System.Collections.BitArray] $BitArray) {
[byte[]] $ByteArray = New-Object byte[] ([System.Math]::Ceiling($BitArray.Length / 8))
$BitArray.CopyTo($ByteArray, 0)
return $ByteArray
}

function ConvertBitArrayToBigInt([System.Collections.BitArray] $BitArray) {
return [bigint][byte[]](ConvertBitArrayToByteArray $BitArray)
}
}

process {
foreach ($IpAddress in $IpAddresses) {
if ($IpAddress.AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetwork) {
[int32] $bitIpAddress = [BitConverter]::ToInt32($IpAddress.GetAddressBytes(), 0)
}
else {
[System.Collections.BitArray] $bitIpAddress = $IpAddress.GetAddressBytes()
}

[System.Collections.Generic.List[string]] $listSubnets = New-Object System.Collections.Generic.List[string]
[bool] $Result = $false
foreach ($Subnet in $Subnets) {
[string[]] $SubnetComponents = $Subnet.Split('/')
[ipaddress] $SubnetAddress = $SubnetComponents[0]
[int] $SubnetMaskLength = $SubnetComponents[1]

[int] $bitIpAddress = [BitConverter]::ToInt32($IpAddress.GetAddressBytes(), 0)
[int] $bitSubnetAddress = [BitConverter]::ToInt32(([ipaddress]$SubnetComponents[0]).GetAddressBytes(), 0)
[int] $bitSubnetMaskHostOrder = 0
if ($SubnetComponents[1] -gt 0) {
$bitSubnetMaskHostOrder = -1 -shl (32 - [int]$SubnetComponents[1])
}
[int] $bitSubnetMask = [ipaddress]::HostToNetworkOrder($bitSubnetMaskHostOrder)
if ($IpAddress.AddressFamily -eq $SubnetAddress.AddressFamily) {
if ($IpAddress.AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetwork) {
## Supports IPv4 (32 bit) only but more performant than BitArray?
#[int32] $bitIpAddress = [BitConverter]::ToInt32($IpAddress.GetAddressBytes(), 0)
[int32] $bitSubnetAddress = [BitConverter]::ToInt32($SubnetAddress.GetAddressBytes(), 0)
[int32] $bitSubnetMaskHostOrder = 0
if ($SubnetMaskLength -gt 0) {
$bitSubnetMaskHostOrder = -1 -shl (32 - $SubnetMaskLength)
}
[int32] $bitSubnetMask = [ipaddress]::HostToNetworkOrder($bitSubnetMaskHostOrder)

if (($bitIpAddress -band $bitSubnetMask) -eq ($bitSubnetAddress -band $bitSubnetMask)) {
if ($ReturnMatchingSubnets) {
$listSubnets.Add($Subnet)
## Check IP
if (($bitIpAddress -band $bitSubnetMask) -eq ($bitSubnetAddress -band $bitSubnetMask)) {
if ($ReturnMatchingSubnets) {
$listSubnets.Add($Subnet)
}
else {
$Result = $true
continue
}
}
}
else {
$Result = $true
continue
## BitArray supports IPv4 (32 bits) and IPv6 (128 bits). Would Int128 type in .NET 7 improve performance?
#[System.Collections.BitArray] $bitIpAddress = $IpAddress.GetAddressBytes()
[System.Collections.BitArray] $bitSubnetAddress = $SubnetAddress.GetAddressBytes()
[System.Collections.BitArray] $bitSubnetMask = New-Object System.Collections.BitArray -ArgumentList ($bitSubnetAddress.Length - $SubnetMaskLength), $true
$bitSubnetMask.Length = $bitSubnetAddress.Length
[void]$bitSubnetMask.Not()
[byte[]] $ByteArray = ConvertBitArrayToByteArray $bitSubnetMask
[array]::Reverse($ByteArray) # Convert to Network byte order
[System.Collections.BitArray] $bitSubnetMask = $ByteArray

## Check IP
if ((ConvertBitArrayToBigInt $bitIpAddress.And($bitSubnetMask)) -eq (ConvertBitArrayToBigInt $bitSubnetAddress.And($bitSubnetMask))) {
if ($ReturnMatchingSubnets) {
$listSubnets.Add($Subnet)
}
else {
$Result = $true
continue
}
}
}
}
}
Expand Down

0 comments on commit ee2d365

Please sign in to comment.