In this step-by-step tutorial, you’ll learn how to inspect and manipulate IP addresses as Python objects with Python’s ipaddress module, improving your understanding of IP address mechanics and patterns used by the module.
Python’s ipaddress
module is an underappreciated gem from the Python standard library. You don’t have to be a full-blown network engineer to have been exposed to IP addresses in the wild. IP addresses and networks are ubiquitous in software development and infrastructure. They underpin how computers, well, address each other.
Learning through doing is an effective way to master IP addresses. The ipaddress
module allows you to do just that by viewing and manipulating IP addresses as Python objects. In this tutorial, you’ll get a better grasp of IP addresses by using some of the features of Python’s ipaddress
module.
In this tutorial, you’ll learn:
**ipaddress**
module cleverly uses a classic design pattern to allow you to do more with lessTo follow along, you just need Python 3.3 or higher since ipaddress
was added to the Python standard library in that version. The examples in this tutorial were generated using Python 3.8.
If you remember only one concept about IP addresses, then remember this: an IP address is an integer. This piece of information will help you better understand both how IP addresses function and how they can be represented as Python objects.
Before you jump into any Python code, it can be helpful to see this concept fleshed out mathematically. If you’re here just for some examples of how to use the ipaddress
module, then you can skip down to the next section, on using the module itself.
You saw above that an IP address boils down to an integer. A fuller definition is that an IPv4 address is a 32-bit integer used to represent a host on a network. The term host is sometimes used synonymously with an address.
It follows that there are 232 possible IPv4 addresses, from 0 to 4,294,967,295 (where the upper bound is 232 - 1). But this is a tutorial for human beings, not robots. No one wants to ping the IP address 0xdc0e0925
.
The more common way to express an IPv4 address is using quad-dotted notation, which consists of four dot-separated decimal integers:
220.14.9.37
It’s not immediately obvious what underlying integer the address 220.14.9.37
represents, though. Formulaically, you can break the IP address 220.14.9.37
into its four octet components:
>>> (
... 220 * (256 ** 3) +
... 14 * (256 ** 2) +
... 9 * (256 ** 1) +
... 37 * (256 ** 0)
... )
3691907365
As shown above, the address 220.14.9.37
represents the integer 3,691,907,365. Each octet is a byte, or a number from 0 to 255. Given this, you can infer that the maximum IPv4 address is 255.255.255.255
(or FF.FF.FF.FF
in hex notation), while the minimum is 0.0.0.0
.
Next, you’ll see how Python’s ipaddress
module does this calculation for you, allowing you to work with the human-readable form and let the address arithmetic occur out of sight.
ipaddress
ModuleTo follow along, you can fetch your computer’s external IP address to work with at the command line:
$ curl -sS ifconfig.me/ip
220.14.9.37
This requests your IP address from the site ifconfig.me, which can be used to show an array of details about your connection and network.
Note: In the interest of technical correctness, this is quite possibly not your computer’s very own public IP address. If your connection sits behind a NATed router, then it’s better thought of as an “agent” IP through which you reach the Internet.
Now open up a Python REPL. You can use the IPv4Address
class to build a Python object that encapsulates an address:
>>> from ipaddress import IPv4Address
>>> addr = IPv4Address("220.14.9.37")
>>> addr
IPv4Address('220.14.9.37')
Passing a str
such as "220.14.9.37"
to the IPv4Address
constructor is the most common approach. However, the class can also accept other types:
>>> IPv4Address(3691907365) # From an int
IPv4Address('220.14.9.37')
>>> IPv4Address(b"\xdc\x0e\t%") # From bytes (packed form)
IPv4Address('220.14.9.37')
While constructing from a human-readable str
is probably the more common way, you might see bytes
input if you’re working with something like TCP packet data.
The conversions above are possible in the other direction as well:
>>> int(addr)
3691907365
>>> addr.packed
b'\xdc\x0e\t%'
In addition to allowing round-trip input and output to different Python types, instances of IPv4Address
are also hashable. This means you can use them as keys in a mapping data type such as a dictionary:
>>> hash(IPv4Address("220.14.9.37"))
4035855712965130587
>>> num_connections = {
... IPv4Address("220.14.9.37"): 2,
... IPv4Address("100.201.0.4"): 16,
... IPv4Address("8.240.12.2"): 4,
... }
On top of that, IPv4Address
also implements methods that allow for comparisons using the underlying integer:
>>> IPv4Address("220.14.9.37") > IPv4Address("8.240.12.2")
True
>>> addrs = (
... IPv4Address("220.14.9.37"),
... IPv4Address("8.240.12.2"),
... IPv4Address("100.201.0.4"),
... )
>>> for a in sorted(addrs):
... print(a)
...
8.240.12.2
100.201.0.4
220.14.9.37
You can use any of the standard comparison operators to compare the integer values of address objects.
Note: This tutorial focuses on Internet Protocol version 4 (IPv4) addresses. There are also IPv6 addresses, which are 128-bit rather than 32-bit and are expressed in a headier form such as 2001:0:3238:dfe1:63::fefb
. Since the arithmetic of addresses is largely the same, this tutorial cuts one variable out of the equation and focuses on IPv4 addresses.
The ipaddress
module features a more flexible factory function, ip_address()
, which accepts an argument that represents either an IPv4 or an IPv6 address and does its best to return either an IPv4Address
or an IPv6Address
instance, respectively.
In this tutorial, you’ll cut to the chase and build address objects with IPv4Address
directly.
As you’ve seen above, the constructor itself for IPv4Address
is short and sweet. It’s when you start lumping addresses into groups, or networks, that things become more interesting.
#python #developer