Calculating the Address Range of a CIDR Network in Python
Learn how to determine the number of IP addresses and the full range of a CIDR network with Python. A detailed tutorial using the ipaddress module with practical examples.
InSkillCoach
Calculating the Address Range of a CIDR Network in Python
CIDR (Classless Inter-Domain Routing) is a method of IP address allocation and IP routing that replaced the earlier system of network address classes. Understanding how to calculate the IP address range of a CIDR network is essential for network management, IT security, and system administration.
In this guide, we will explore how to use Python to quickly and efficiently determine the IP address range of a CIDR network.
What Is CIDR Notation?
CIDR notation consists of two parts:
- An IP address (IPv4 or IPv6)
- A suffix indicating the size of the network prefix (the subnet mask)
For example, 192.168.1.0/24 indicates:
192.168.1.0is the network address/24means that the first 24 bits are fixed (which corresponds to a subnet mask of 255.255.255.0)- This network includes all addresses from
192.168.1.0to192.168.1.255
Using the ipaddress Module
Python includes a powerful module called ipaddress in its standard library (since Python 3.3), which greatly simplifies calculations involving IP addresses and networks.
Installation
No installation is required because the ipaddress module is part of the Python standard library:
import ipaddress
Determining the Address Range of a CIDR Network
Here is how to calculate the address range of a CIDR network:
import ipaddress
def get_cidr_range(cidr_notation):
"""
Calculates the IP address range of a CIDR network.
Args:
cidr_notation (str): CIDR notation (e.g., '192.168.1.0/24')
Returns:
dict: Dictionary containing information about the network
"""
try:
network = ipaddress.ip_network(cidr_notation, strict=False)
# Collect network information
network_info = {
'network_address': str(network.network_address),
'broadcast_address': str(network.broadcast_address) if hasattr(network, 'broadcast_address') else None,
'netmask': str(network.netmask),
'prefix_length': network.prefixlen,
'num_addresses': network.num_addresses,
'first_usable_address': str(next(network.hosts())) if network.num_addresses > 1 else None,
'last_usable_address': str(list(network.hosts())[-1]) if network.num_addresses > 1 else None,
'address_range': f"{next(network.hosts())} - {list(network.hosts())[-1]}" if network.num_addresses > 1 else None,
'is_private': network.is_private,
'version': 'IPv4' if network.version == 4 else 'IPv6'
}
return network_info
except ValueError as e:
return {'error': str(e)}
# Usage examples
cidr_examples = [
'192.168.1.0/24', # Typical local network (/24)
'10.0.0.0/8', # Class A private network
'172.16.0.0/12', # Class B private network
'8.8.8.0/24', # Public network (containing Google DNS)
'192.168.0.1/32', # A single IP address
'2001:db8::/32', # IPv6 network
]
for cidr in cidr_examples:
print(f"\nInformations pour le réseau {cidr}:")
network_info = get_cidr_range(cidr)
if 'error' in network_info:
print(f"Erreur: {network_info['error']}")
continue
# Display network information
for key, value in network_info.items():
print(f"- {key}: {value}")
Result for an IPv4 Network (/24)
Informations pour le réseau 192.168.1.0/24:
- network_address: 192.168.1.0
- broadcast_address: 192.168.1.255
- netmask: 255.255.255.0
- prefix_length: 24
- num_addresses: 256
- first_usable_address: 192.168.1.1
- last_usable_address: 192.168.1.254
- address_range: 192.168.1.1 - 192.168.1.254
- is_private: True
- version: IPv4
Understanding the Number of IP Addresses in a CIDR Network
The number of IP addresses in a CIDR network can be calculated with the formula: 2^(32-prefix) for IPv4 and 2^(128-prefix) for IPv6.
Here is a function that explains this relationship:
def explain_cidr_size(prefix_length, ip_version=4):
"""
Explains the size of a CIDR network and calculates the number of IP addresses.
Args:
prefix_length (int): CIDR prefix length (e.g., 24 in /24)
ip_version (int): IP version (4 or 6)
Returns:
dict: Explanation and calculations
"""
if ip_version == 4:
total_bits = 32
max_prefix = 32
else: # IPv6
total_bits = 128
max_prefix = 128
if prefix_length < 0 or prefix_length > max_prefix:
return {'error': f'Préfixe invalide pour IPv{ip_version}'}
host_bits = total_bits - prefix_length
num_addresses = 2 ** host_bits
if ip_version == 4:
# For IPv4, we generally count usable addresses
# (excluding the network and broadcast addresses)
usable_addresses = max(0, num_addresses - 2) if prefix_length < 31 else num_addresses
else:
# For IPv6, addresses are generally not subtracted
usable_addresses = num_addresses
explanation = {
'prefix_length': prefix_length,
'ip_version': f'IPv{ip_version}',
'total_bits': total_bits,
'network_bits': prefix_length,
'host_bits': host_bits,
'formula': f'2^{host_bits}',
'total_addresses': num_addresses,
'usable_addresses': usable_addresses
}
return explanation
# Comparison table of different CIDR prefixes
print("Tableau comparatif des tailles de réseaux CIDR pour IPv4:")
print("-" * 80)
print(f"{'Préfixe':<10}{'Masque':<15}{'Bits hôte':<15}{'Nombre d\'adresses':<20}{'Adresses utilisables':<20}")
print("-" * 80)
for prefix in [8, 16, 20, 24, 27, 28, 29, 30, 31, 32]:
info = explain_cidr_size(prefix)
# Subnet mask calculation
if prefix == 32:
mask = "255.255.255.255"
else:
mask_int = (0xffffffff >> (32 - prefix)) << (32 - prefix)
mask = '.'.join([str((mask_int >> i) & 0xff) for i in [24, 16, 8, 0]])
print(f"/{prefix:<9}{mask:<15}{info['host_bits']:<15}{info['total_addresses']:<20}{info['usable_addresses']:<20}")
Comparison Table Output
Tableau comparatif des tailles de réseaux CIDR pour IPv4:
--------------------------------------------------------------------------------
Préfixe Masque Bits hôte Nombre d'adresses Adresses utilisables
--------------------------------------------------------------------------------
/8 255.0.0.0 24 16777216 16777214
/16 255.255.0.0 16 65536 65534
/20 255.255.240.0 12 4096 4094
/24 255.255.255.0 8 256 254
/27 255.255.255.224 5 32 30
/28 255.255.255.240 4 16 14
/29 255.255.255.248 3 8 6
/30 255.255.255.252 2 4 2
/31 255.255.255.254 1 2 2
/32 255.255.255.255 0 1 1
CIDR Special Cases
/31 Networks - Point-to-Point (RFC 3021)
RFC 3021 introduced the use of the /31 prefix for point-to-point links, which saves IP addresses:
def analyze_point_to_point_network(cidr_notation):
"""
Analyzes a point-to-point network with a /31 prefix.
Args:
cidr_notation (str): CIDR notation (must be /31)
Returns:
dict: Information about the point-to-point link
"""
try:
network = ipaddress.ip_network(cidr_notation, strict=False)
if network.prefixlen != 31:
return {'error': 'Cette fonction est conçue pour les réseaux /31 uniquement'}
# In a /31 network, both addresses are usable (RFC 3021)
addresses = list(network.hosts())
if len(addresses) != 2:
return {'error': 'Nombre d\'adresses incorrect pour un réseau /31'}
return {
'network': str(network),
'device_a': str(addresses[0]),
'device_b': str(addresses[1]),
'note': 'Configuration point à point (RFC 3021)'
}
except ValueError as e:
return {'error': str(e)}
# Point-to-point link example
p2p_example = '192.168.1.0/31'
p2p_info = analyze_point_to_point_network(p2p_example)
print(f"\nLiaison point à point {p2p_example}:")
for key, value in p2p_info.items():
print(f"- {key}: {value}")
/32 Networks - Single Address
For individual addresses (such as loopback interfaces):
def analyze_host_address(ip_address):
"""
Analyzes an individual IP address (/32 prefix).
Args:
ip_address (str): IP address (with or without /32)
Returns:
dict: Information about the address
"""
try:
# Make sure the address has a /32 prefix
if '/' not in ip_address:
ip_address = f"{ip_address}/32"
host = ipaddress.ip_interface(ip_address)
if host.network.prefixlen != 32:
return {'error': 'Cette fonction est conçue pour les adresses /32 uniquement'}
return {
'address': str(host.ip),
'prefix': host.network.prefixlen,
'network': str(host.network),
'is_private': host.ip.is_private,
'note': 'Adresse unique (souvent utilisée pour les interfaces loopback)'
}
except ValueError as e:
return {'error': str(e)}
# Single address example
host_example = '10.0.0.1/32'
host_info = analyze_host_address(host_example)
print(f"\nAdresse unique {host_example}:")
for key, value in host_info.items():
print(f"- {key}: {value}")
Subnet Management (Subnetting)
Dividing a network into subnets is a common task for network administrators. Python makes this operation easy:
def create_subnets(cidr_notation, new_prefix_length):
"""
Divides a CIDR network into subnets.
Args:
cidr_notation (str): CIDR notation of the parent network
new_prefix_length (int): Prefix length for the subnets
Returns:
list: List of subnets
"""
try:
network = ipaddress.ip_network(cidr_notation, strict=False)
if new_prefix_length <= network.prefixlen:
return {'error': 'Le nouveau préfixe doit être plus grand que le préfixe du réseau parent'}
subnets = list(network.subnets(new_prefix=new_prefix_length))
# Limit the number of displayed subnets to avoid overly large output
display_limit = 10
total_subnets = len(subnets)
result = {
'parent_network': str(network),
'parent_prefix': network.prefixlen,
'new_prefix': new_prefix_length,
'total_subnets': total_subnets,
'subnets_sample': [str(subnet) for subnet in subnets[:display_limit]]
}
if total_subnets > display_limit:
result['note'] = f"Affichage limité aux {display_limit} premiers sous-réseaux sur un total de {total_subnets}"
return result
except ValueError as e:
return {'error': str(e)}
# Subnet division example
subnet_example = '192.168.0.0/24'
new_prefix = 26
subnet_info = create_subnets(subnet_example, new_prefix)
print(f"\nDivision de {subnet_example} en sous-réseaux /{new_prefix}:")
for key, value in subnet_info.items():
if key == 'subnets_sample':
print(f"- Exemples de sous-réseaux:")
for i, subnet in enumerate(value, 1):
print(f" {i}. {subnet}")
else:
print(f"- {key}: {value}")
Practical Application: Network Planning
Here is a practical example of planning a network with multiple subnets that have specific requirements in terms of host count:
def plan_network(base_network, requirements):
"""
Plans a network by allocating subnets according to specific requirements.
Args:
base_network (str): Base network in CIDR notation
requirements (dict): Dictionary mapping subnet names to the number of hosts required
Returns:
dict: Plan of allocated subnets
"""
try:
network = ipaddress.ip_network(base_network, strict=False)
sorted_requirements = sorted(requirements.items(), key=lambda x: x[1], reverse=True)
allocated_subnets = {}
next_network = network
for name, hosts_needed in sorted_requirements:
# Calculate the prefix required for the number of hosts
# Formula: 2^(32-prefix) - 2 ≥ hosts_needed (for IPv4)
prefix = 32
while prefix > 0 and (2 ** (32 - prefix) - 2 < hosts_needed):
prefix -= 1
if prefix <= network.prefixlen:
return {'error': f"Impossible d'allouer suffisamment d'adresses pour {name}"}
# Get the next available subnet
try:
subnet = next(next_network.subnets(new_prefix=prefix))
allocated_subnets[name] = {
'subnet': str(subnet),
'prefix': prefix,
'addresses_total': subnet.num_addresses,
'addresses_usable': subnet.num_addresses - 2,
'network_address': str(subnet.network_address),
'broadcast_address': str(subnet.broadcast_address),
'first_usable': str(next(subnet.hosts())),
'last_usable': str(list(subnet.hosts())[-1])
}
# Update the remaining network
remaining_networks = list(next_network.address_exclude(subnet))
if remaining_networks:
next_network = remaining_networks[0]
else:
break
except StopIteration:
return {'error': f"Espace d'adressage épuisé, impossible d'allouer pour {name}"}
return {
'base_network': str(network),
'allocated_subnets': allocated_subnets
}
except ValueError as e:
return {'error': str(e)}
# Enterprise network planning example
base_network = '10.0.0.0/22' # /22 block (1022 usable addresses)
# Requirements for different departments
department_requirements = {
'Bureau': 500, # Office needs 500 hosts
'Serveurs': 100, # Servers need 100 hosts
'Invités': 200, # Guest network needs 200 hosts
'IoT': 50, # IoT devices need 50 addresses
'Management': 30 # Management network needs 30 addresses
}
network_plan = plan_network(base_network, department_requirements)
print(f"\nPlan de réseau pour {base_network}:")
if 'error' in network_plan:
print(f"Erreur: {network_plan['error']}")
else:
print(f"Réseau de base: {network_plan['base_network']}")
print("\nAllocation des sous-réseaux:")
for dept, subnet_info in network_plan['allocated_subnets'].items():
print(f"\n{dept}:")
for key, value in subnet_info.items():
print(f"- {key}: {value}")
IPv6 Compatibility
All of the functions above also work with IPv6, although some concepts, such as the broadcast address, do not exist in IPv6:
def get_ipv6_cidr_range(cidr_notation):
"""
Calculates the address range for an IPv6 CIDR network.
Args:
cidr_notation (str): IPv6 CIDR notation (e.g., '2001:db8::/64')
Returns:
dict: Information about the IPv6 network
"""
try:
network = ipaddress.ip_network(cidr_notation, strict=False)
if network.version != 6:
return {'error': 'Cette fonction est conçue pour les réseaux IPv6 uniquement'}
# Limit the output to a few addresses for IPv6
# (otherwise we would have millions or billions of entries)
hosts_iter = network.hosts()
first_host = next(hosts_iter)
# For IPv6, we only show the first and last addresses
# because there are usually a very large number of them
sample_hosts = [str(first_host)]
# For /64 and larger networks, computing the last address is very expensive
# so we build it manually
if network.prefixlen <= 64:
# For a prefix ≤ 64, explicitly compute the last address
last_host_str = str(network.network_address + (1 << (128 - network.prefixlen)) - 1)
sample_note = "Trop d'adresses pour énumérer"
else:
# For a prefix > 64, we can enumerate the hosts
# Advance through the iterator to find the last address
last_host = None
count = 0
limit = 1000 # Limit iteration for larger prefixes
for host in hosts_iter:
last_host = host
count += 1
if count >= limit:
break
last_host_str = str(last_host) if last_host else "..."
sample_note = f"Échantillon limité à {count+1} adresses" if count < limit else f"Échantillon limité aux premières et dernières adresses"
sample_hosts.append(last_host_str)
return {
'network_address': str(network.network_address),
'prefix_length': network.prefixlen,
'num_addresses': network.num_addresses,
'address_sample': sample_hosts,
'sample_note': sample_note,
'is_private': network.is_private,
'is_global': network.is_global,
'version': 'IPv6'
}
except ValueError as e:
return {'error': str(e)}
# IPv6 network examples
ipv6_examples = [
'2001:db8::/32', # Documentation block
'2001:db8::/48', # Typical allocation for a site
'2001:db8::/64', # Typical network for a segment
'fe80::/64', # Link-local
'fd00::/8' # Unique Local Address (ULA)
]
for cidr in ipv6_examples:
print(f"\nInformations pour le réseau IPv6 {cidr}:")
network_info = get_ipv6_cidr_range(cidr)
if 'error' in network_info:
print(f"Erreur: {network_info['error']}")
continue
for key, value in network_info.items():
print(f"- {key}: {value}")
Conclusion
Managing CIDR networks is a fundamental skill for IT professionals. Python’s ipaddress module offers an elegant and powerful way to work with IP addresses and networks.
Here are the key takeaways:
- CIDR notation combines an IP address and a prefix to define a network
- The prefix determines how many IP addresses are available in the network
- Python makes network calculations easy with its
ipaddressmodule - The concepts apply to both IPv4 and IPv6, with a few differences
This knowledge is essential for:
- Planning and designing enterprise networks
- Understanding network segmentation
- Configuring network equipment
- Developing network monitoring or automation applications
- Improving network security through proper segmentation
To deepen your knowledge, see the official ipaddress module documentation and the RFCs related to Internet standards.
About InSkillCoach
Training and technology expert
Coach specializing in advanced technologies and AI, backed by GNeurone Inc.
Certifications:
- AWS Certified Solutions Architect – Professional
- Google Cloud certifications
- Microsoft Certified: DevOps Engineer Expert
- Certified Kubernetes Administrator (CKA)
- CompTIA Security+
Comments
Comments are powered by GitHub Discussions
Sign in with GitHub to join the discussion