An Overview of Python’s “ipaddress” Module (original) (raw)

An introduction to the ipaddress module available on Python 3.3+ for manipulation of IPv4 and IPv6 addresses.

Python 3 ipaddress Module Overview

In this article we’ll take a look at the ipaddress module that is available on Python 3.3 and above. This tutorial is intended to serve as a handy reference for any network engineer wondering how to parse and work with IP addresses in Python.

In this overview article you’ll learn:

IPv4 vs IPv6 Addresses – A Primer

At a high level, IPv4 and IPv6 addresses are used for like purposes and functions. However, since there are major differences in the address structure for each protocol, this tutorial has separated into separate sections, one each for IPv4 and IPv6.

In today’s Internet, the IPv4 protocol controls the majority of IP processing and will remain so for the near future. The enhancements in scale and functionality that come with IPv6 are necessary for the future of the Internet and adoption is progressing. The adoption rate, however, remains slow to this date.

An IPv4 address is composed of 32 bits, organized into four eight bit groupings referred to as “octets”. The word “octet” is used to identify an eight-bit structure in place of the more common term “byte”, but they carry the same definition. The four octets are referred to as octet1, octet2, octet3, and octet4. This is a “dotted decimal” format where each eight-bit octet can have a decimal value based on eight bits from zero to 255.

IPv4 address example: 192.168.100.10

IPv4 address example (CIDR notation): 192.168.100.10/24

The /24 is CIDR notation to indicate that leading 24 of the 32 bits are used to identify the network portion of the address. Remembering that each octet is 8 bits long, this means that the first three octets (3 × 8 = 24) identify the network (192.168.100.x) and the remaining eight bits of the address identify the node (x.x.x.10).

CIDR notation can be anything from /8 bits through to /30 bits, with an occasional /32 bits (/31 is invalid), but /24 is often used. For example, your home network, or your school or company network is most likely identified with a /24 CIDR.

An older format for expressing the network identification is a network mask where the CIDR is expressed as a separate dotted decimal number. For example, a /24 CIDR equates to a network mask of 255.255.255.0.

An IPv6 address is 128 bits long, which is a significant increase over the 32 bits in an IPv4 address. There are many differences between IPv4 and IPv6, but the notable difference is in the addressing structure. The additional length provides an exponential increase in the number of networks and host that can be supported.

IPv6 address example: 2001:db8:abcd💯:1/64

Where the IPv4 address uses a dotted decimal format, the IPv6 protocol uses hexadecimal notation. Each position in an IPv6 address represents four bits with a value from 0 to f, organized as follows:

All IPv6 address structures used CIDR notation to determine how many of the leading bits are used for network identification with the balance used for host/interface identification. Given 128 bits, many options are available.

Python’s ipaddress Module and IPv4 Addresses

The ipaddress module is designed around CIDR notation, which is recommended because of its brevity and ease of use. The ipaddress module also includes methods to revert to a network mask if required.

The original definition of IPv4 addresses includes a “class” that is defined by address ranges in the first octet. The ipaddress module does not recognize IPv4 classes and is therefore not included in this tutorial.

The ipaddress module includes three specific IPv4 address object types:

  1. a “host” or an individual address object that does not include CIDR notation,
  2. an individual interface address object that includes CIDR notation, and
  3. and a network address object that refers to the range of IP addresses for the entire network.

The major difference between a “host” and an “interface” is that a host or ip_address object does not include CIDR notation, whereas an ip_interface object includes the CIDR notation:

Creating IPv4 Host Address Objects with ipaddress:

The ipaddress.ip_address() factory function is used to create an ip_address object. This automatically determines whether to create an IPv4 or IPv6 address based on the passed-in value (IPv6 addressing will be discussed at a latter point in this tutorial). As noted above, this object represents an IP Address as found in a packet traversing a network where CIDR is not required.

In many cases, the value used to create an ip_address object will be a string in the IPv4 dotted decimal format as per this example:

import ipaddress my_ip = ipaddress.ip_address('192.168.100.10') my_ip IPv4Address('192.168.100.10')

Alternatively, the IPv4 address may be entered in binary, as a decimal value of the full 32 bit binary value, or in hexadecimal format as per this example:

All 32 binary bits can be used to create an IPv4 address:

ipaddress.ip_address(0b11000000101010000110010000001010) IPv4Address('192.168.100.10')

The decimal value of the 32 bit binary number can also be used:

ipaddress.ip_address(3232261130) IPv4Address('192.168.100.10')

As can the hexadecimal value of the 32 bits:

ipaddress.ip_address(0xC0A8640A) IPv4Address('192.168.100.10')

The first example uses the full 32 bit address, and the second example is the decimal value of the 32 bit address. Both are unwieldy, error-prone and of limited value. The third example uses a hexadecimal value which can be useful as most packet formats from parsing or sniffing are represented in hexadecimal format.

Creating IPv4 Interface Address Objects with ipaddress:

The ipaddress.ip_interface() factory function is used to create an ip_interface object, which automatically determines whether to create an IPv4 or IPv6 address based on the passed-in value (IPv6 addressing will be discussed at a latter point in this tutorial).

As previously discussed, the ip_interface object represents the ip address found on a host or network interface where the CIDR (or mask) is required for proper handling of the packet.

An ip_interface object is used to represent IP addressing

for a host or router interface, including the CIDR:

my_ip = ipaddress.ip_interface('192.168.100.10/24') my_ip IPv4Interface('192.168.100.10/24')

This method translates the CIDR into a mask as would normally

be used on a host or router interface

my_ip.netmask IPv4Address('255.255.255.0')

One can use the same options in the creation of an ip_interface option as with an ip_address option (binary, decimal value, hexadecimal). However, the only way to effectively create an ip_interface with the proper CIDR notation or mask is with a dotted decimal IPv4 address string.

Creating IPv4 Network Address Objects with ipadress:

The ipaddress.ip_network() factory function is used to create an ip_network object, which automatically determines whether to create an IPv4 or IPv6 address based on the passed-in value (IPv6 addressing will be discussed at a latter point in this tutorial).

An IP network is defined as a range of consecutive IP address that define a network or subnet. Example:

The creation of an ip_network object follows the same syntax as the creation of an ip_interface object:

Creates an ip_network object. The IPv4 address and CIDR must be

a valid network address, the first address in an address range:

ipaddress.ip_network('192.168.100.0/24') IPv4Network('192.168.100.0/24')

In the above example, the network address used must be a valid network address, which is the first address in the range of IPv4 addresses that constitute the network. If this is not the case, Python will throw an exception:

Python will throw an exception if the address used is not

a valid network address. In the following, ".10" is a host address

not a valid network address ident cation, which is ".0":

ipaddress.ip_network('192.168.100.10/24') ValueError: "192.168.100.10/24 has host bits set"

When working with host or router interfaces, it is often necessary to determine the network address. This can be calculated, but takes several steps which can be accomplished in a single step using the strict=False option (strict=True is default).

If the network address needs to be calculated,

use the strict=False option. This will calculate and populate

the ip_network object with the network rather than the

interface address:

my_ip = ipaddress.ip_interface('192.168.100.10/24') my_ip IPv4Interface('192.168.100.10/24')

my_ip_net = ipaddress.ip_network(my_ip, strict=False) my_ip_net IPv4Network('192.168.100.0/24')

In the above example, the ip_interface address is known (192.168.100.10) but not the ip_network the interface belongs to. Using the strict=False option, the ip_network address (192.168.100.0/24) is calculated and populated in the ip_network object.

Python’s ipaddress Module and IPv6 Addresses

As with IPv4, the ipaddress module uses the same three basic factory functions already described for IPv4. includes include:

  1. a “host” or an individual address object that does not include CIDR notation,
  2. an interface address object that includes CIDR notation, and
  3. and a network address object that refers to the range of IP addresses for the entire network.

Since the detail is covered in the section on IPv4, a brief overview is only necessary.

Creating IPv6 Host Address Objects with ipaddress:

The ipaddress.ip_address() factory function is used to create an ip_address object. This automatically knows to use the IPv6 address format based on the passed-in value. Note that the CIDR notation is not used with the ip_address function.

In the majority of cases, the value used to create an ip_address object for IPv6 will be a string in the IPv6 quartet/hextet format as per this example:

Create an IPv6 Address Object for a Global Address:

ipaddress.ip_address('2001:db8:abcd💯:1') IPv6Address('2001:db8:abcd💯:1')

Create an IPv6 Address Object for a link-local address:

ipaddress.ip_address('fe80::1') IPv6Address('fe80::1')

As with IPv4, it is possible to create an IPv6 address object using the full binary, decimal, or hexadecimal value. This is unwieldy with 32 bits for an IPv4 address and is even more awkward for a 128 bit IPv6 address. As a practical matter, it is anticipated that the string representation of the eight quartets will be the norm.

Creating IPv6 Interface Address Objects with ipaddress:

The ipaddress.ip_interface() factory function is used to create an ip_interface object, which automatically create an IPv6 address based on the passed-in value. Note that the CIDR notation must be included in the function.

Creates an IP Interface Object for a Global Address:

ipaddress.ip_interface('2001:db8:abcd💯:1/64') IPv6Interface('2001:db8:abcd💯:1/64')

Creates an IP Interface Object for a Link-local Address:

ipaddress.ip_interface('fe80::1/64') IPv6Interface('fe80::1/64')

Creating IPv6 Network Address Objects with ipaddress:

The ipaddress.ip_network() factory function is used to create an ip_network object for IPv6 based on the passed-in value.

As with IPv4, an IPv6 network is defined as a range of consecutive IP address that can be assigned to specific host or router interfaces.

Using our previous example 2001:db8:abcd💯:/64, the /64 CIDR specifies that the four quartets make up the full network identification. Remember that the first three quartets are global ID assigned by the IPS and the fourth quartet identifies the internal subnet number. The balance of the 64 bits are used for host identification with a range from “0000:0000:0000:0001” though to “ffff:ffff:ffff:fffe”.

As with IPv4 addressing, the first and last address in an IPv6 subnet cannot be used for host addressing. Given a /64 CIDR, this means that there are 2 to the 64th power (minus 2) possible host addresses, which is means there are 18,446,744,073,709,551,614 mathematically possible host addresses per network/subnet.

Creates an IP Network Object for a Global Address:

myIPv6net = ipaddress.ip_network('2001:db8:abcd💯:/64') myIPv6net IPv6Network('2001:db8:abcd💯:/64')

Creates an IP Network Object for a Link-local Address:

myIPv6 = ipaddress.ip_network('fe80::/64') myIPv6 IPv6Network('fe80::/64')

The above global address is broken down as follows:

Additional Resources

These are some additional resources where you can learn about the ipaddress module in Python: