ipwithpython

Introduction to IP networking and manipulation using Python APIs.

contents

--

Overview

Basic theory on IP addresses

Manipulating your IPs with Python API

Overview

This article aims to provide a gentle introduction to IP networking using Pythonipaddress module. It is aimed primarily at users that aren’t already familiar with IP networking terminology, but may also be useful to network engineers wanting an overview of how ipaddress represents IP network addressing concepts. This API helps network engineers so often as it provides handy functions and classes that handles various tasks that are related to IP addresses, including checking whether two hosts belong to the same subnet, iterating over all hosts of a subnet, retrieving broadcast addresses of a particular subnet, and much more.

What is IP address ?

IP address stands for internet protocol address; it is an identifying number that is associated with a specific computer or computer network. When connected to the internet, the IP address allows the computers to send and receive information in a very unique way.

IP address versions and categories

There are two versions of IP address. Internet Protocol Version 4 (IPv4) was the initial version, which is still widely used by most of the websites on the Internet. Addresses in IPv4 are 32-bits long. This allows for a maximum of 4,294,967,296 (2^32) unique addresses. The depletion of numbers on IPv4 forced authorities to introduce another Internet Protocol Version 6 (IPv6). The IPv6 is still not widely used due to compatibility with all network devices. Addresses in IPv6 are 128-bits, which allows for 3.4 x 1038 (2^128) unique addresses. The total usable address pool of both versions is reduced by various reserved addresses and other considerations.

IP addresses are binary numbers but are typically expressed in decimal form (IPv4) or hexadecimal form (IPv6) to make reading and using them easier for humans.

Furthermore, there are different categories of IP addresses known as public IP address, private IP address, dynamic IP address and static IP address whereas the two standards for IP addresses are IP Version 4 (IPv4) and IP Version 6 (IPv6). But to let you understand the two main category, thus, dynamic and Static, I would say, your Internet Service Provider (ISP) assigns a dynamic IP address for each of your active Internet session. However, in most cases you will get same IP address since the allocation is fixed for your subscription. Static IP address is created manually through editing the network settings in your computer. In order to do this, you should first get the static IP address from your ISP.

The IPv4 address comprises of 32 bits in total and expressed in dot decimal notation like 1.1.1.1. It has four decimal numbers separated by dots and each number can represent from 0 to 255. For example: 17.172.224.47.

The IPv6 address comprises of 128 bits. It is classified by eight groups of hexadecimal numbers, which are separated by colons. For example, Google’s public DNS server’s IPv6 address is: 2001:4860:4860:0000:0000:8888

How to Find Your IP Address?

In [17]: import socket

In [18]: hostname=socket.gethostname()

In [19]: IPAddr= socket.gethostbyname (hostname)

In [20]: print ( “Your computer’s name is:”, hostname)
Your computer’s name is: DESKTOP-KwesiWelbred

In [21]: print(“Your computer’s IPAddress is:”, IPAddr)
Your computer’s IPAddress is: 127.0.0.1

There are many other tools available on the Internet that interprets your computer’s IP address and find your location. You can simple click here to find your IP address with map. You can also try to find the IP address on your Windows 10, Mac, iPhone and Android phone. For example,

on Windows 10 follow the below instructions:

  • Right click on the Start menu and open Command Prompt.
  • Type ipconfig and hit enter.
  • You will see IP address of your computer as below.

Follow the below instructions on Mac:

  • Press “Command + Spacebar” to open Spotlight Search.
  • Type “network utility” and open Network Utility app.
  • Select your network name under “Info” tab.
  • You can find the IP address of your Mac here.
IP Address in Mac

Sometimes you may notice IP conflict message on your device, when you have same IP address assigned to more than one device. In such case, simply restart your device or disconnect and reconnect the network connection to get a new IP address for your device.

For readers that aren’t particularly familiar with IP addressing, it’s important to know that the Internet Protocol is currently in the process of moving from version 4 of the protocol to version 6. This transition is occurring largely because version 4 of the protocol doesn’t provide enough addresses to handle the needs of the whole world, especially given the increasing number of devices with direct connections to the internet.

IP addresses: Networks and hosts

For a TCP/IP wide area network (WAN) to work efficiently as a collection of networks, the routers that pass packets of data between networks do not know the exact location of a host for which a packet of information is destined. Routers only know what network the host is a member of and use information stored in their route table to determine how to get the packet to the destination host’s network. After the packet is delivered to the destination’s network, the packet is delivered to the appropriate host.

For this process to work, an IP address has two parts. The first part of an IP address is used as a network address, the last part as a host address. If you take the example 192.168.123.132 and divide it into these two parts, you get 192.168.123. Network .132 Host or 192.168.123.0 — network address. 0.0.0.132 — host address.

Subnet mask

The second item, which is required for TCP/IP to work, is the subnet mask. The subnet mask is used by the TCP/IP protocol to determine whether a host is on the local subnet or on a remote network.

In TCP/IP, the parts of the IP address that are used as the network and host addresses are not fixed, so the network and host addresses above (i.e. 192.168.123.132)cannot be determined unless you have more information. This information is supplied in another 32-bit number called a subnet mask. In this example, the subnet mask is 255.255.255.0.

It is not obvious what this number means unless you know that 255 in binary notation equals 11111111; so, the subnet mask is 11111111.11111111.11111111.0000000.

Lining up the IP address and the subnet mask together, the network, and host portions of the address can be separated:

11000000.10101000.01111011.10000100 — IP address (192.168.123.132) 11111111.11111111.11111111.00000000 — Subnet mask (255.255.255.0)

The first 24 bits (the number of ones in the subnet mask) are identified as the network address, with the last 8 bits (the number of remaining zeros in the subnet mask) identified as the host address. This gives you the following:

11000000.10101000.01111011.00000000 — Network address (192.168.123.0)

00000000.00000000.00000000.10000100 — Host address (000.000.000.132)

So now you know, for this example using a 255.255.255.0 subnet mask, that the network ID is 192.168.123.0, and the host address is 0.0.0.132. When a packet arrives on the 192.168.123.0 subnet (from the local subnet or a remote network), and it has a destination address of 192.168.123.132, your computer will receive it from the network and process it.

Almost all decimal subnet masks convert to binary numbers that are all ones on the left and all zeros on the right. Some other common subnet masks are:

Decimal Binary 255.255.255.192 1111111.11111111.1111111.11000000 255.255.255.224 1111111.11111111.1111111.11100000

IP Network classes

Internet addresses are allocated by the InterNIC, the organization that administers the Internet. These IP addresses are divided into classes. The most common of these are classes A, B, and C. Classes D and E exist, but are not used by end users. Each of the address classes has a different default subnet mask. You can identify the class of an IP address by looking at its first octet. Following are the ranges of Class A, B, and C Internet addresses, each with an example address:

  • Class A networks use a default subnet mask of 255.0.0.0 and have 0–127 as their first octet. The address 10.52.36.11 is a class A address. Its first octet is 10, which is between 1 and 126, inclusive.
  • Class B networks use a default subnet mask of 255.255.0.0 and have 128–191 as their first octet. The address 172.16.52.63 is a class B address. Its first octet is 172, which is between 128 and 191, inclusive.
  • Class C networks use a default subnet mask of 255.255.255.0 and have 192–223 as their first octet. The address 192.168.123.132 is a class C address. Its first octet is 192, which is between 192 and 223, inclusive.

In some scenarios, the default subnet mask values do not fit the needs of the organization, because of the physical topology of the network, or because the numbers of networks (or hosts) do not fit within the default subnet mask restrictions. The next section explains how networks can be divided using subnet masks.

Subnetting

A Class A, B, or C TCP/IP network can be further divided, or subnetted, by a system administrator. This becomes necessary as you reconcile the logical address scheme of the Internet (the abstract world of IP addresses and subnets) with the physical networks in use by the real world.

A system administrator who is allocated a block of IP addresses may be administering networks that are not organized in a way that easily fits these addresses. For example, you have a wide area network with 150 hosts on three networks (in different cities) that are connected by a TCP/IP router. Each of these three networks has 50 hosts. You are allocated the class C network 192.168.123.0. (For illustration, this address is actually from a range that is not allocated on the Internet.) This means that you can use the addresses 192.168.123.1 to 192.168.123.254 for your 150 hosts.

Two addresses that cannot be used in your example are 192.168.123.0 and 192.168.123.255 because binary addresses with a host portion of all ones and all zeros are invalid. The zero address is invalid because it is used to specify a network without specifying a host. The 255 address (in binary notation, a host address of all ones) is used to broadcast a message to every host on a network. Just remember that the first and last address in any network or subnet cannot be assigned to any individual host.

You should now be able to give IP addresses to 254 hosts. This works fine if all 150 computers are on a single network. However, your 150 computers are on three separate physical networks. Instead of requesting more address blocks for each network, you divide your network into subnets that enable you to use one block of addresses on multiple physical networks.

In this case, you divide your network into four subnets by using a subnet mask that makes the network address larger and the possible range of host addresses smaller. In other words, you are ‘borrowing’ some of the bits used for the host address, and using them for the network portion of the address. The subnet mask 255.255.255.192 gives you four networks of 62 hosts each. This works because in binary notation, 255.255.255.192 is the same as 1111111.11111111.1111111.11000000. The first two digits of the last octet become network addresses, so you get the additional networks 00000000 (0), 01000000 (64), 10000000 (128) and 11000000 (192). (Some administrators will only use two of the subnetworks using 255.255.255.192 as a subnet mask. For more information on this topic, see RFC 1878.) In these four networks, the last 6 binary digits can be used for host addresses.

Using a subnet mask of 255.255.255.192, your 192.168.123.0 network then becomes the four networks 192.168.123.0, 192.168.123.64, 192.168.123.128 and 192.168.123.192. These four networks would have as valid host addresses:

192.168.123.1–62 192.168.123.65–126 192.168.123.129–190 192.168.123.193–254

Remember, again, that binary host addresses with all ones or all zeros are invalid, so you cannot use addresses with the last octet of 0, 63, 64, 127, 128, 191, 192, or 255.

You can see how this works by looking at two host addresses, 192.168.123.71 and 192.168.123.133. If you used the default Class C subnet mask of 255.255.255.0, both addresses are on the 192.168.123.0 network. However, if you use the subnet mask of 255.255.255.192, they are on different networks; 192.168.123.71 is on the 192.168.123.64 network, 192.168.123.133 is on the 192.168.123.128 network.

Default gateways

If a TCP/IP computer needs to communicate with a host on another network, it will usually communicate through a device called a router. In TCP/IP terms, a router that is specified on a host, which links the host’s subnet to other networks, is called a default gateway. This section explains how TCP/IP determines whether or not to send packets to its default gateway to reach another computer or device on the network.

When a host attempts to communicate with another device using TCP/IP, it performs a comparison process using the defined subnet mask and the destination IP address versus the subnet mask and its own IP address. The result of this comparison tells the computer whether the destination is a local host or a remote host.

If the result of this process determines the destination to be a local host, then the computer will send the packet on the local subnet. If the result of the comparison determines the destination to be a remote host, then the computer will forward the packet to the default gateway defined in its TCP/IP properties. It is then the responsibility of the router to forward the packet to the correct subnet.

ipaddress — IPv4/IPv6 manipulation library

Tools

We don’t need to install anything, as this module comes built-in and it was introduced in Python 3.3, so if you have Python 3.3+ (and I’m sure you do), you’re good to go

#check your python version on python environment:

C:\Users\Kwesi_Welbred>python — version
Python 3.8.3

#using ipython environment

C:\Users\Kwesi_Welbred> ipython

Creating Address/Network/Interface objects

The simplest way to create addresses is to use the ipaddress.ip_address() factory function, which automatically determines whether to create an IPv4 or IPv6 address based on the passed in value:

In [3]: #import the library
In [3]: import ipaddress
# creating an IPv4 and IPv6 Addresses
ip4 = ipaddress.IPv4Address("192.168.1.1")
ip6 = ipaddress.IPv6Address("2001:db8::1")

Defining Networks

Host addresses are usually grouped together into IP networks, so ipaddress provides a way to create, inspect and manipulate network definitions. IP network objects are constructed from strings that define the range of host addresses that are part of that network. The simplest form for that information is a “network address/network prefix” pair, where the prefix defines the number of leading bits that are compared to determine whether or not an address is part of the network and the network address defines the expected value of those bits.

As for addresses, a factory function is provided that determines the correct IP version automatically:

#defining ipv4 and v6 network
ipaddress.ip_network('192.0.2.0/24')
ipaddress.ip_network('2001:db8::0/96')

Inspecting IP address Networks

You’ve gone to the trouble of creating an IPv(4|6)(Address|Network|Interface) object, so you probably want to get information about it. ipaddress tries to make doing this easy and in intuitive way.

In [6]: addr4 = ipaddress.ip_address('192.0.2.1')
In [7]: addr4
Out[7]: IPv4Address('192.0.2.1')
In [11]: addr6 = ipaddress.ip_address('2001:db8::1')
In [12]: addr6
Out[12]: IPv6Address('2001:db8::1')
#you can just call the variable/ the variable.version
In [14]: addr6.version
Out[14]: 6

Obtaining the network from an interface:

In [15]: host4 = ipaddress.ip_interface('192.0.2.1/24')In [16]: host4
Out[16]: IPv4Interface('192.0.2.1/24')
In [18]: host6 = ipaddress.ip_interface('2001:db8::1/96')
In [19]: host6
Out[19]: IPv6Interface('2001:db8::1/96')

Finding out how many individual addresses are in a network:

In [14]: net4 = ipaddress.ip_network('192.0.2.0/24')
In [15]: net4
Out[15]: IPv4Network('192.0.2.0/24')
In [16]: net4.num_addresses
Out[16]: 256
In [22]: net6 = ipaddress.ip_network('2001:db8::0/96')
In [23]: net6
Out[23]: IPv6Network('2001:db8::/96')
In [24]: net6.num_addresses
Out[24]: 4294967296

Iterating through the “usable” addresses on a network:

#ipv4 addresses
In [26]: net4 = ipaddress.ip_network('192.0.2.0/24')
In [27]: for x in net4.hosts():
...: print(x)
...:
192.0.2.1
192.0.2.2
192.0.2.3
192.0.2.4
192.0.2.5
192.0.2.6
192.0.2.7
192.0.2.8
192.0.2.9
192.0.2.10

...
192.0.2.252
192.0.2.253
192.0.2.254
#ipv6 addresses
In [26]: net6 = ipaddress.ip_network('2001:db8::0/96')
In [27]: for x in net6.hosts():
...: print(x)
2001:db8::20:3993
2001:db8::20:3994
2001:db8::20:3995
2001:db8::20:3996
2001:db8::20:3997
2001:db8::20:3998
2001:db8::20:3999
2001:db8::20:399a
2001:db8::20:399b
................. to be continued 😋

Obtaining the netmask (i.e. set bits corresponding to the network prefix) or the host mask (any bits that are not part of the netmask):

net4 = ipaddress.ip_network('192.0.2.0/24')
>>> net4.netmask
IPv4Address('255.255.255.0')
>>> net4.hostmask
IPv4Address('0.0.0.255')
netV6 = ipaddress.ip_network('2001:db8::0/96')
>>> net6.netmask
IPv6Address('ffff:ffff:ffff:ffff:ffff:ffff::')
>>> net6.hostmask
IPv6Address('::ffff:ffff')

Exploding or compressing the address:

#Exploding and compressing the IPs
In [5]: addr6 = ipaddress.ip_address('2001:db8::1')
In [7]: addr6.exploded
Out[7]: '2001:0db8:0000:0000:0000:0000:0000:0001'
In [10]: addr6.compressed
Out[10]: '2001:db8::1'
#Exploding and compressing the networks
In [15]: netV6 = ipaddress.ip_network('2001:db8::0/96')
In [16]: netV6.exploded
Out[16]: '2001:0db8:0000:0000:0000:0000:0000:0000/96'
In [17]: netV6.compressed
Out[17]: '2001:db8::/96'

In [6]: addr4 = ipaddress.ip_address('192.0.2.1')
In [8]: addr4.exploded
Out[8]: '192.0.2.1'
.........work out the rest using the example above.💻

While IPv4 doesn’t support explosion or compression, the associated objects still provide the relevant properties so that version neutral code can easily ensure the most concise or most verbose form is used for IPv6 addresses while still correctly handling IPv4 addresses.

Networks as lists of Addresses

It’s sometimes useful to treat networks as lists. This means it is possible to index them like this:

>>> net4[1]
IPv4Address('192.0.2.1')
>>> net4[-1]
IPv4Address('192.0.2.255')
>>> net6[1]
IPv6Address('2001:db8::1')
>>> net6[-1]
IPv6Address('2001:db8::ffff:ffff')

It also means that network objects lend themselves to using the list membership test syntax like this:

if address in network:
# do something

Containment testing is done efficiently based on the network prefix:

>>> addr4 = ipaddress.ip_address('192.0.2.1')
>>> addr4 in ipaddress.ip_network('192.0.2.0/24')
True
>>> addr4 in ipaddress.ip_network('192.0.3.0/24')
False

Comparisons

ipaddress provides some simple, hopefully intuitive ways to compare objects, where it makes sense:

>>> ipaddress.ip_address('192.0.2.1') < ipaddress.ip_address('192.0.2.2')
True

A TypeError exception is raised if you try to compare objects of different versions or different types.

Using IP Addresses with other modules

Other modules that use IP addresses (such as socket) usually won’t accept objects from this module directly. Instead, they must be coerced to an integer or string that the other module will accept:

>>> addr4 = ipaddress.ip_address('192.0.2.1')
>>> str(addr4)
'192.0.2.1'
>>> int(addr4)
3221225985

Getting more detail when instance creation fails

When creating address/network/interface objects using the version-agnostic factory functions, any errors will be reported as ValueError with a generic error message that simply says the passed in value was not recognized as an object of that type. The lack of a specific error is because it’s necessary to know whether the value is supposed to be IPv4 or IPv6 in order to provide more detail on why it has been rejected.

To support use cases where it is useful to have access to this additional detail, the individual class constructors actually raise the ValueError subclasses ipaddress.AddressValueError and ipaddress.NetmaskValueError to indicate exactly which part of the definition failed to parse correctly.

The error messages are significantly more detailed when using the class constructors directly. For example:

>>> ipaddress.ip_address("192.168.0.256")
Traceback (most recent call last):
...
ValueError: '192.168.0.256' does not appear to be an IPv4 or IPv6 address
>>> ipaddress.IPv4Address("192.168.0.256")
Traceback (most recent call last):
...
ipaddress.AddressValueError: Octet 256 (> 255) not permitted in '192.168.0.256'
>>> ipaddress.ip_network("192.168.0.1/64")
Traceback (most recent call last):
...
ValueError: '192.168.0.1/64' does not appear to be an IPv4 or IPv6 network
>>> ipaddress.IPv4Network("192.168.0.1/64")
Traceback (most recent call last):
...
ipaddress.NetmaskValueError: '64' is not a valid netmask

However, both of the module specific exceptions have ValueError as their parent class, so if you’re not concerned with the particular type of error, you can still write code like the following:

try:
network = ipaddress.IPv4Network(address)
except ValueError:
print('address/netmask is invalid for IPv4:', address)

References:

--

--