During the preparation of the SMS/SCCM Webservice for MDT/OSD Deployments I came across a problem to get the Active Directory Site for a given IP Address. There were a couple of examples available on the Internet but most of them just called the DsGetDcName API. As this is possible within the .Net Framework I personally try to avoid using a com API if using .Net.
Actually the .Net Framework 2.0 contains a namespace called "System.DirectoryServices.ActiveDirectory". So I thought it must be possible to get this information. The first thing we would need is a list of all Active Directory sites with their corresponding Subnets. How do get all sites? The Forest Object will do the trick. Forest.GetCurrentForest.Sites will return a list of all sites. Each site object will then return a list of all subnets. Now we only need to check if the given IP Address is part of a single subnet and if we have a positive match we have the name of the corresponding site. So a function to loop through all of them could look like this:
Public Shared Function GetSite(ByVal IPAddress As String) As String
Dim Location As String = ""
For Each site As ActiveDirectorySite In Forest.GetCurrentForest.Sites
For Each subnet As ActiveDirectorySubnet In site.Subnets
If IPInSubnet(IPAddress, subnet.Name) Then
Location = site.Name
End If
Next
Next
Return Location
End Function
So far so good. Now we need to check if a single IP Address is within a subnet. One way could be to get the first and last IP Address of the subnet and then see if the IP Address can be found between them.
Public Shared Function IPInSubnet(ByVal IPAddress As String, ByVal subnet As String) As Boolean
Dim Result As Boolean = False
Dim SplittedSubnetName As String() = subnet.Split("/")
If SplittedSubnetName.Length = 2 Then
Dim IPAddressInDecimal As Long = IPToDecimal(IPAddress)
Dim SubnetmaskBits As Integer = Integer.Parse(SplittedSubnetName(1))
Dim NoOfAddresses As Integer = Convert.ToInt32(Math.Pow(2, (32 - SubnetmaskBits)) - 1)
Dim LowIPAddress As Long = IPToDecimal(SplittedSubnetName(0))
Dim HighIPAddress As Long = LowIPAddress + NoOfAddresses
Dim TotalIPAddressCount As Long = (Convert.ToInt64(Math.Pow(2, 31))) - 1
If LowIPAddress <= IPAddressInDecimal AndAlso IPAddressInDecimal <= HighIPAddress _
AndAlso NoOfAddresses <= TotalIPAddressCount Then
Result = True
End If
End If
Return Result
End Function
To ease the calculations, we just convert the IP Address to a Long (64 Bit Integer) Value
Public Shared Function IPToDecimal(ByVal IPAddress As String) As Long
Dim IPAddresses As String() = IPAddress.Split(".")
Return Convert.ToInt64(((Int32.Parse(IPAddresses(0)) * Math.Pow(2, 24) + _
(Int32.Parse(IPAddresses(1)) * Math.Pow(2, 16) + _
(Int32.Parse(IPAddresses(2)) * Math.Pow(2, 8) + _
(Int32.Parse(IPAddresses(3))))))))
End Function
So far so good. Now we could use this function within our webservice to get the ADSite for any IP Address. For sure only if we have the appropiate subnets configured :-)
Finally as a small goody. Have you ever tried to check all your AD Site subnets if they maybe overlap or collide with any other? Using the same functions we have just seen this task is quite easy. Add the following two functions to your class and it will return a list of all Site-Subnet combinations which overlap with another subnet.
Public Shared Function IPInSubnet(ByVal IPAddress As String, ByVal subnet As String) As Boolean
Dim Result As Boolean = False
Dim SplittedSubnetName As String() = subnet.Split("/")
If SplittedSubnetName.Length = 2 Then
Dim IPAddressInDecimal As Long = IPToDecimal(IPAddress)
Dim SubnetmaskBits As Integer = Integer.Parse(SplittedSubnetName(1))
Dim NoOfAddresses As Integer = Convert.ToInt32(Math.Pow(2, (32 - SubnetmaskBits)) - 1)
Dim LowIPAddress As Long = IPToDecimal(SplittedSubnetName(0))
Dim HighIPAddress As Long = LowIPAddress + NoOfAddresses
Dim TotalIPAddressCount As Long = (Convert.ToInt64(Math.Pow(2, 31))) - 1
If LowIPAddress <= IPAddressInDecimal AndAlso IPAddressInDecimal <= HighIPAddress _
AndAlso NoOfAddresses <= TotalIPAddressCount Then
Result = True
End If
End If
Return Result
End Function
Public Shared Function SubnetOverlapsSubnet(ByVal Subnet1 As String, ByVal Subnet2 As String)
Dim result As Boolean
Dim splittedsubnetname1 As String() = Subnet1.Split("/")
Dim splittedsubnetname2 As String() = Subnet2.Split("/")
Dim SubnetmaskBits1 As Integer = Integer.Parse(splittedsubnetname1(1))
Dim SubnetmaskBits2 As Integer = Integer.Parse(splittedsubnetname2(1))
Dim NoOfAddresses1 As Integer = Convert.ToInt32(Math.Pow(2, (32 - SubnetmaskBits1)) - 1)
Dim NoOfAddresses2 As Integer = Convert.ToInt32(Math.Pow(2, (32 - SubnetmaskBits2)) - 1)
Dim LowIPAddress1 As Long = IPToDecimal(splittedsubnetname1(0))
Dim LowIPAddress2 As Long = IPToDecimal(splittedsubnetname2(0))
Dim HighIPAddress1 As Long = LowIPAddress1 + NoOfAddresses1
Dim HighIPAddress2 As Long = LowIPAddress2 + NoOfAddresses2
If (LowIPAddress1 < LowIPAddress2 AndAlso HighIPAddress1 < LowIPAddress2) _
OrElse (LowIPAddress1 > HighIPAddress2 AndAlso HighIPAddress1 > HighIPAddress2) Then
result = False
Else
result = True
End If
Return result
End Function
The subnet value needs to in the Format of 192.168.20.0/24 to get a proper result. You might wanna add code to parse 192.168.20.0/255.255.255.0 by yourself :-).
As always you can download the full source code.