Skip to content

Working with IP addresses

qingxiao_ren edited this page Sep 5, 2018 · 1 revision

Working with IP addresses (IpAddress class)

IP address is the standard IP version 4 address used to uniquely identify individual hosts connected to the Internet (or private networks). In the Snmp#Net library, IP address operations are performed using IpAddress class.

There has been a lot of questions about IPv6 and I would like to state here, IpAddress data type is an IPv4 ONLY data type. It cannot represent an IPv6 address. There is an InetAddress variable type defined in RFC-4001 but it is not represented by a dedicated/distinct data type. It is encoded as an OctetString with length of 0 to 255 bytes. That means there is no way for the library to detect and decode the value correctly so it will be up to the user to handle this data type.

IpAddress class is used both as part of SNMP request/reply communication and the supporting framework that handles packet operations. As such, it contains a lot of functionality that is not strictly needed by the SNMP but can be handy when writing real world applications.

IpAddress class is derived from the OctetString class and contains exactly 4 bytes of data (representing 4 octets of the IP address value). Internally, IP address value is stored in bit-endian order as is required for encoding and transmission between hosts. Where value is returned as numeric UInt32 value (4 byte unsigned integer value representing the IP address), byte array is converted into a little endian ordered numeric value suitable for mathematical operations.

First, what is an IP address? As mentioned earlier, IP address is a 4 byte value usually represented in dotted decimal format. For example, 192.168.20.11 is a valid IP address.

Now that IP address format is explained, creating an IpAddress class instance can be done with a default IP address of 0.0.0.0 by calling a constructor without arguments:

IpAddress ipaddr = new IpAddress();
Console.WriteLine("IpAddress: {0}", ipaddr.ToString());
// Prints: IpAddress: 0.0.0.0

It is usually easier if you initialize the class during initialization. One of the ways to do that is to pass the 4 byte array to the constructor:

IpAddress ipaddr = new IpAddress(new byte[] { 192, 168, 20, 11 });
Console.WriteLine("IpAddress: {0}", ipaddr.ToString());
// Prints: IpAddress: 192.168.20.11

Another way to initialize the class is to pass the initial IP address value as a dotted decimal formatted string:

IpAddress ipaddr = new IpAddress("192.168.20.11");
Console.WriteLine("IpAddress: {0}", ipaddr.ToString());
// Prints: IpAddress: 192.168.20.11

Problem with dotted decimal IP address representation is that nobody (or nobody you want to know) can remember IP addresses of all the hosts they need to work with. For that reason, a smart egg somewhere out there in the Internet land came up with the Domain Name Service (DNS) concept. DNS maps difficult to remember IP addresses to somewhat easier to remember host names.

Host name can be resolved to an IP address using DNS name resolution. IpAddress constructor that accepts a dotted decimal string representation of an IP address will also attempt domain name resolution if parsing of dotted decimal format fails. Be careful with this feature. Domain name resolution is performed using blocking DNS calls that can result in thread blocking for a while (if DNS query response is slow) and make your application unresponsive. It is a good idea to resolve IP addresses prior to passing them to the IpAddress class to improve application responsiveness.

Here is DNS resolution in the IpAddress constructor example:

IpAddress ipaddr = new IpAddress("yahoo.com");
Console.WriteLine("IpAddress: {0}", ipaddr.ToString());
// Prints: IpAddress: 69.147.125.65

Be careful with the name resolution in the constructor. If the name you specified in the argument fails to resolve, constructor will throw an exception:

IpAddress ipaddr;
try {
  ipaddr = new IpAddress ("nonexistenthost.com");
} catch (Exception ex) {
  Console.WriteLine ("Exception {0}: {1}", ex.GetType ().ToString (), ex.Message);
  ipaddr = null;
}
if( ipaddr != null )
  Console.WriteLine ("IpAddress: {0}", ipaddr.ToString ());
// Prints: 
//  Exception System.ArgumentException: Unable to parse or resolve supplied value to an IP address.
//  Parameter name: value

Another way to initialize the IpAddress class is to pass an instance of the System.Net.IPAddress:

IPAddress iptmp = IPAddress.Parse ("192.168.20.11");
IpAddress ipaddr = new IpAddress (iptmp);
Console.WriteLine ("IpAddress: {0}", ipaddr.ToString ());
// Prints: IpAddress: 192.168.20.11

Being able to initialize IpAddress class from System.Net.IPAddress can be handy if you are initializing it with a value returned by Socket.ReceiveFrom method for example.

Since IP address is a 4 byte value, it is possible to represent it as an unsigned 32-bit integer. If you can represent the value with it then you should be able to initialize the class with it:

IpAddress ipaddr = new IpAddress (185903296);
Console.WriteLine ("IpAddress: {0}", ipaddr.ToString ());
// Prints: IpAddress: 192.168.20.11

Now that IpAddress class is constructed, you can check validity of the value it represents by calling IpAddress.Valid property. Property value will be true if internal value is 4 bytes in length and at least one of the 4 values is not 0 (zero). While all zero IP address (0.0.0.0) is valid it doesn't have a useful purpose so it is considered invalid for the validity purpose.

You can change the value pre-initialized IpAddress class represents by using IpAddress.Set(..) methods. There is a set method matching the syntax of every constructor described above:

IpAddress ipaddr = new IpAddress ();
Console.WriteLine ("IpAddress: {0}", ipaddr.ToString ());
// Prints: IpAddress: 0.0.0.0
ipaddr.Set (new byte[] { 192, 168, 20, 11 });
Console.WriteLine ("IpAddress: {0}", ipaddr.ToString ());
// Prints: IpAddress: 192.168.20.11
ipaddr.Set ("192.168.20.12");
Console.WriteLine ("IpAddress: {0}", ipaddr.ToString ());
// Prints: IpAddress: 192.168.20.12
ipaddr.Set ("yahoo.com");
Console.WriteLine ("IpAddress: {0}", ipaddr.ToString ());
// Prints: IpAddress: 69.147.125.65
 
/*********************
* On the TODO list: *
*********************
IPAddress iptmp = IPAddress.Parse ("192.168.20.11");
ipaddr.Set(iptmp);
Console.WriteLine ("IpAddress: {0}", ipaddr.ToString ());
*/
ipaddr.Set(185903282);
Console.WriteLine ("IpAddress: {0}", ipaddr.ToString ());
// Prints: IpAddress: 178.168.20.11

As you can see from the above examples, IpAddress value can be retrieved as a string in dotted decimal format using IpAddress.ToString() method.

Additionally, you can get the IpAddress value as a UInt32 using IpAddress.GetUInt32() method or as a byte array, using IpAddress.ToArray() method inherited from OctetString class.

IpAddress ipaddr = new IpAddress (new byte[] { 192, 168, 20, 11 });
Console.WriteLine ("IpAddress UInt32: {0} ({1})", ipaddr.ToUInt32 (), ipaddr.ToString ());
// Prints: IpAddress UInt32: 185903296 (192.168.20.11)
byte[] ipbuf = ipaddr.ToArray ();
SnmpConstants.DumpHex (ipbuf);
// Prints: 0000 c0 a8 14 0b

Value returned by IpAddress.ToUInt32() method is not suitable for performing increment operations on the IP address because it is formatted in the big-endian byte order. See helper methods section for increment, decrement, etc. operation methods.

Access to individual byte values comprising the IP address can be made using indexed accessor:

IpAddress ipaddr = new IpAddress (new byte[] { 192, 168, 20, 11 });
for (int i = 0; i < ipaddr.Length; i++) {
  Console.Write ("{0}:{1} ", i, ipaddr[i]);
}
Console.WriteLine ("");
// Prints: 0:192 1:168 2:20 3:11

You can compare two IpAddress value using IpAddress.CompareTo() method:

/* Example comparison with second value greater then first */
IpAddress ipaddr1 = new IpAddress (new byte[] { 192, 168, 20, 11 });
IpAddress ipaddr2 = new IpAddress (new byte[] { 192, 168, 20, 12 });
int compResult = ipaddr1.CompareTo (ipaddr2);
if (compResult == -1)
  Console.WriteLine ("IpAddress {0} is less then {1}", ipaddr1.ToString (), ipaddr2.ToString ());
else if (compResult == 0)
  Console.WriteLine ("IpAddress {0} is equal to {1}", ipaddr1.ToString (), ipaddr2.ToString ());
else if (compResult == 1)
  Console.WriteLine ("IpAddress {0} is greater then {1}", ipaddr1.ToString (), ipaddr2.ToString ());
// Prints: IpAddress 192.168.20.11 is less then 192.168.20.12
 
/* Example comparison with first value greater then second */
 
ipaddr1.Set (new byte[] { 192, 168, 20, 12 });
ipaddr2.Set (new byte[] { 192, 168, 20, 11 });
compResult = ipaddr1.CompareTo (ipaddr2);
if (compResult == -1)
  Console.WriteLine ("IpAddress {0} is less then {1}", ipaddr1.ToString (), ipaddr2.ToString ());
else if (compResult == 0)
  Console.WriteLine ("IpAddress {0} is equal to {1}", ipaddr1.ToString (), ipaddr2.ToString ());
else if (compResult == 1)
  Console.WriteLine ("IpAddress {0} is greater then {1}", ipaddr1.ToString (), ipaddr2.ToString ());
// Prints: IpAddress 192.168.20.12 is greater then 192.168.20.11
 
/* Example comparison of equal values */
 
ipaddr2.Set (new byte[] { 192, 168, 20, 12 });
compResult = ipaddr1.CompareTo (ipaddr2);
if (compResult == -1)
  Console.WriteLine ("IpAddress {0} is less then {1}", ipaddr1.ToString (), ipaddr2.ToString ());
else if (compResult == 0)
  Console.WriteLine ("IpAddress {0} is equal to {1}", ipaddr1.ToString (), ipaddr2.ToString ());
else if (compResult == 1)
  Console.WriteLine ("IpAddress {0} is greater then {1}", ipaddr1.ToString (), ipaddr2.ToString ());
// Prints: IpAddress 192.168.20.12 is equal to 192.168.20.12

You can do the same comparison of IpAddress class value against an instance of System.Net.IPAddress class. Results will be the same as in the above example.

IpAddress.Equals() method only compares the class value against another IpAddress class. This will be expanded in the next revision of the library (current version is 0.7.8) to include capability to compare with IPAddress, byte array, and UInt32 values.

Helper Methods

I frequently have to increment IP addresses in the process of scanning subnetworks for hosts of interest. It seamed handy to be able to do that directly in the IpAddress class. It contains the right value, it can perform basic DNS resolution, and I'm using it anyway for my SNMP requests.

As discussed earlier, IP address is a 4 byte value in big-endian byte order that can be represented as an unsigned integer. It was also mentioned that this value is not suitable for incrementing and decrementing IP address value because it is in big-endian byte order. What this means in real terms is this:

IpAddress ipaddr = new IpAddress("192.168.11.23");
Console.WriteLine("IpAddress: {0}", ipaddr.ToString());
// Prints: IpAddress: 192.168.11.23
UInt32 ipNum = ipaddr.ToUInt32();
ipNum += 30;
ipaddr.Set(ipNum);
Console.WriteLine("Incremented IpAddress: {0}", ipaddr.ToString());

As you can see from the example, wrong part of the IP address is incremented and that is because byte order of the integer is wrong. We can fix that with the static helper method IpAddress.ReverseByteOrder(UInt32) method:

IpAddress ipaddr = new IpAddress("192.168.11.21");
Console.WriteLine("IpAddress: {0}", ipaddr.ToString());
// Prints: IpAddress: 192.168.11.21
UInt32 ipNum = ipaddr.ToUInt32();
UInt32 revIpNum = IpAddress.ReverseByteOrder(ipNum);
revIpNum += 25;
ipNum = IpAddress.ReverseByteOrder(revIpNum);
ipaddr.Set(ipNum);
Console.WriteLine("Incremented IpAddress: {0}", ipaddr.ToString());
// Prints: Incremented IpAddress: 192.168.11.46

That's better. Reversing the byte order within the unsigned integer has converted the IP address value into a representative value that you can perform any mathematical operation on, reverse the byte order again and apply to the IpAddress class and presto, you've got the IP address you where looking for.

While above example is handy to demonstrate how to get the IpAddress value that you can mathematically process, it is a lot of work to just increment an IP address. It's considerably easier to just use IpAddress.Increment(UInt32) method:

IpAddress ipaddr = new IpAddress("192.168.11.21");
Console.WriteLine("IpAddress: {0}", ipaddr);
// Prints: IpAddress: 192.168.11.21
IpAddress procAddr = ipaddr.Increment(1);
Console.WriteLine("IpAddress + 1: {0}", procAddr);
// Prints: IpAddress + 1: 192.168.11.22
procAddr = ipaddr.Increment(5);
Console.WriteLine("IpAddress + 5: {0}", procAddr);
// Prints: IpAddress + 5: 192.168.11.26

With the basic math on IpAddress values is possible, you need something to do the math on. For that you will need to be able to get subnet information so you can get your range of addresses to scan.

Let's look at a real example. If you access a router and retrieve the list of IP addresses in the ipAddrTable (RFC1213-MIB). The value you get are ipAdEntAddr holding the IP address and ipAdEntNetMask holding the subnet mask for the interface. Using these two values you can calculate the subnetwork address and begin a walk operation:

IpAddress ipaddr = new IpAddress("192.168.11.21");
Console.WriteLine("IpAddress: {0}", ipaddr);
IpAddress submask = new IpAddress("255.255.255.192");
Console.WriteLine("SubnetMask: {0}", submask);
/* Verify subnet mask is a valid value: */
if (submask.IsValidMask())
{
  IpAddress subAddr = ipaddr.GetSubnetAddress(submask);
  Console.WriteLine("Subnet address: {0}", subAddr);
  IpAddress bcastAddr = ipaddr.GetBroadcastAddress(submask);
  Console.WriteLine("Broadcast address: {0}", bcastAddr);
  Console.WriteLine("************");
  IpAddress incrIP = (IpAddress)subAddr.Clone();
  while (incrIP != bcastAddr)
  {
    if (incrIP == subAddr)
      Console.WriteLine("Subnet address: {0}", incrIP);
    else
      Console.WriteLine("Host address: {0}", incrIP);
    incrIP = incrIP.Increment(1);
 
    if( incrIP == bcastAddr )
      Console.WriteLine("Broadcast address: {0}", incrIP);
  }
}
else
{
  Console.WriteLine("SubnetMask {0} is not valid.");
}

With the following result:

IpAddress: 192.168.11.21
SubnetMask: 255.255.255.192
Subnet address: 192.168.11.0
Broadcast address: 192.168.11.63
************
Subnet address: 192.168.11.0
Host address: 192.168.11.1
Host address: 192.168.11.2
Host address: 192.168.11.3
Host address: 192.168.11.4
[...]
Host address: 192.168.11.60
Host address: 192.168.11.61
Host address: 192.168.11.62
Broadcast address: 192.168.11.63

In the course of developing net management applications I have often had to build code to verify string content for a valid IP address. To help with that IpAddress class includes static method IpAddress.IsIP(string). This method will verify that string contains 3 full stops (.) separating 4 numbers with values in the range 0 to 255.

See class documentation for all methods available in the IpAddress class.