I’m currently preparing for a network rollout and the preparation includes assigning subnets to the sites. There are subnets needed for management, wired users, wireless users, guests, and so on. Once subnets have been assigned, for some of the subnets, DHCP scopes need to be created. The team managing the server has requested that information on the subnets, gateway, and what IP the scope begins and ends with be provided as a CSV file. This will allow for easily importing the scopes into the server.

For my scenario, I have the information in a spreadsheet and I’m accessing the information using the openpyxl project. I am then using the ipaddress library to take the prefix from the spreadsheet and performing various calculations. Why use Python for this?

  • Writing CSV is time consuming for humans.
  • Although I’m quite good at performing calculations, I’m not better than a computer.
  • Using code means consistent output that is less error prone.

The goal is to create a line of CSV that looks like this:

VLAN 100 User,192.0.2.64,255.255.255.192,192.0.2.65,192.0.2.75,192.0.2.126,US0100 NY,example.com,

This line consists of:

  • Subnet name.
  • Network.
  • Network mask.
  • Gateway.
  • First IP in DHCP scope.
  • Last IP in DHCP scope.
  • Site name.
  • Domain.

The subnet name, site name, and domain name will not be covered as they don’t have anything with IP addresses to do. Let’s work through the other scenarios.

First, I will import IPv4Network from the ipaddress module:

from ipaddress import IPv4Network

Then, let’s create an IPv4Network object:

my_prefix = IPv4Network("192.0.2.64/26")

This will create an object that allows us to do a lot of very useful things. Before we get there, one of the benefits of using code is that it would throw an error if I try to put an invalid prefix such as 192.0.2.64/25:

Traceback (most recent call last):
  File "Blog_ip_address_examples.py", line 3, in <module>
    my_prefix = IPv4Network("192.0.2.64/25")
  File "ipaddress.py", line 1454, in __init__
    raise ValueError('%s has host bits set' % self)
ValueError: 192.0.2.64/25 has host bits set

Going back to the valid prefix 192.0.2.64/26, let’s see some of the useful things we can do with the object. We can see how many available IPs are in the subnet:

my_prefix.num_addresses

This will return 64 in our example.

We can further subnet 192.0.2.64/26 into two /27 subnets with the following code:

my_subnets = list(my_prefix.subnets())

This generates a list with two IPv4Network objects:

[IPv4Network('192.0.2.64/27'), IPv4Network('192.0.2.96/27')]

If we wanted /28 subnets instead, we could use the following code:

my_subnets = list(my_prefix.subnets(prefixlen_diff=2))

A diff of two means that the mask used is /26 + 2 which is /28. This produces the following objects:

[IPv4Network('192.0.2.64/28'), IPv4Network('192.0.2.80/28'), IPv4Network('192.0.2.96/28'), IPv4Network('192.0.2.112/28')]

There’s a lot more that can be done. Refer to the IPv4Network documentation for more examples. Now let’s back to producing the CSV that we wanted.

The first thing we want to do is to get the network when we have a prefix in CIDR notation. This is easily done using the following code:

my_network = my_prefix.network_address

This will return 192.0.2.64. Using the ipaddress module means we don’t need to do any regex or string splitting to find what the network is. It will also provide us with error checking that we would have to code ourselves otherwise.

The next step is to get the network mask based on the /26 notation. This is done using the following code:

my_netmask = my_prefix.netmask

It will return 255.255.255.192.

Next, let’s get the gateway IP, which is the first IP in the subnet. This is where it gets interesting. Performing this calculation in your head is easy but doing it with code (not using the ip address module) would be a lot more complex. You would then have to write code that can read a netmask to find where the subnet begins and then do a calculation. Using the ip address module, it’s really simple. The IPv4Network object we created acts as a container of addresses. That means that we can access each individual element using the same syntax we would when accessing entries in a list. To get the first usable IP in the subnet, we use the following syntax:

my_gw = my_prefix[1]

This will return 192.0.2.65. What if we wanted to use the last usable IP in the subnet as the GW? Then we could use the following code instead:

my_gw = my_prefix[-2]

If using -1, that would be the last IP in the subnet which is the broadcast address of 192.0.2.127. Using -2 references the penultimate IP which is 192.0.2.126.

Having learned how to access addresses in the object, it’s now easy to get the start and end range of our DHCP scope. For the start, we want to leave 10 addresses out of the scope that can be used for static assignments. That means that the first IP we want is 192.0.2.75. The last IP should be the penultimate IP which I just showed how to get. We will use the following code:

my_start_ip = my_prefix[11]
my_end_ip = my_prefix[-2]

This will give us 192.0.2.75 and 192.0.2.126.

We now have all the information we need so now it’s just a matter of putting it together in the proper format for the CSV file. We will use the following code for that:

my_csv = f"VLAN 100 User,{my_network},{my_netmask},{my_gw},{my_start_ip},{my_end_ip},US0100 NY,example.com,"

This will then provide the following output:

VLAN 100 User,192.0.2.64,255.255.255.192,192.0.2.65,192.0.2.75,192.0.2.126,US0100 NY,example.com,

I hope this post has given you some insight into how powerful Python can be in calculating IP addresses and how to programmatically create DHCP scopes.

Python – Using the IP Address Module to Calculate IPs
Tagged on:             

3 thoughts on “Python – Using the IP Address Module to Calculate IPs

  • August 8, 2023 at 10:42 am
    Permalink

    Very interesting article. I think it is the right time for me to start learning python and applying it in the real world. 🙂

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *