# Copyright (c) 2024 Arista Networks, Inc.
# Use of this source code is governed by the Apache License 2.0
# that can be found in the COPYING file.
#
# The purpose of this file is to provide ip utilities that are
# safe for both ipv4 and ipv6
# DO NOT use network.subnets() due to performance impact.
# DO NOT use network.hosts() due to performance impact.
import ipaddress
import json
from typing import List
from .exceptions import (
IpSubnetIndexException,
IpHostIndexException,
IpNetworkOverlapException,
)
[docs]
def is_ipv4(ip):
# Determine if the given network is IPv4
try:
ipaddress.IPv4Network(ip, strict=False)
return True
except ValueError:
return False
[docs]
def is_ipv6(ip):
# Determine if the given network is IPv6
try:
ipaddress.IPv6Network(ip, strict=False)
return True
except ValueError:
return False
[docs]
def number_of_usable_addresses(network):
net = ipaddress.ip_network(network, strict=False)
if net.version == 4 and net.num_addresses > 2:
# exclude addresses for identification (first)
# and broadcast (last)
return net.num_addresses - 2
elif net.version == 6 and net.num_addresses > 2:
# exclude anycast (first) address
return net.num_addresses - 1
else:
# for a p2p-link network use all 2 addresses
return net.num_addresses
[docs]
def first_usable_address(network):
net = ipaddress.ip_network(network, strict=False)
if net.num_addresses < 3:
return net[0]
return str(net[1])
[docs]
def last_usable_address(network):
net = ipaddress.ip_network(network, strict=False)
if net.num_addresses < 3:
return net[-1]
return str(net[-2]) if net.version == 4 else str(net[-1])
[docs]
def get_number_subnets(network, subnet_mask: int):
# Calculate the total number of subnets of the given mask
# on the given network
network = ipaddress.ip_network(network, strict=False)
bit_difference = subnet_mask - network.prefixlen
total_subnets = 2 ** bit_difference
return total_subnets
[docs]
def get_subnet_by_index(network, subnet_mask: int, index: int,
hostname: str = None, poolname: str = None):
# Return the subnet at the specified index.
# Input index is 0 based.
total_subnets = get_number_subnets(network, subnet_mask)
# Ensure the index is within the valid range
if index < 0 or index >= total_subnets:
raise IpSubnetIndexException(
network, subnet_mask, total_subnets, index,
hostname, poolname)
# Calculate the offset for the subnet at the given index
if is_ipv6(network):
bit_length = 128
else:
bit_length = 32
network = ipaddress.ip_network(network, strict=False)
offset = index * (1 << (bit_length - subnet_mask))
new_subnet_address = int(network.network_address) + offset
subnet = ipaddress.ip_network(
f"{ipaddress.ip_address(new_subnet_address)}/{subnet_mask}",
strict=False)
return subnet
[docs]
def get_ip_from_subnet(network, index: int, hostname: str = None,
poolname: str = None):
# Return the ip address at the specified index.
# Input index is 0 based.
num_addresses = number_of_usable_addresses(network)
if (network.version == 4 and network.prefixlen < 31) or (
network.version == 6 and network.prefixlen < 127):
# When indexing directly into non p2p-link networks,
# skip identification/anycast address
index += 1
if index > num_addresses:
raise IpHostIndexException(
network, num_addresses, index, hostname, poolname)
return network[index]
[docs]
def overlapping_networks_check(networks: List[str], hostname: str = None,
poolname: str = None):
# Given a list of networks ensures they do not overlap
for idx1 in range(len(networks)):
if networks[idx1].strip() == "":
continue
network1 = ipaddress.ip_network(networks[idx1], strict=False)
idx2 = idx1 + 1
while idx2 < len(networks):
network2 = ipaddress.ip_network(networks[idx2], strict=False)
if network1.overlaps(network2):
raise IpNetworkOverlapException(
network1, network2, hostname, poolname)
idx2 += 1