Last active
December 16, 2021 21:21
-
-
Save wonderb0lt/a25779f83e2accc50baccffbf52e3e0c to your computer and use it in GitHub Desktop.
Fun with subnet fragmentation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from sys import argv | |
from ipaddress import IPv4Network | |
class SubnetUsage: | |
def __init__(self, subnet_cidr): | |
self.subnet_cidr = subnet_cidr | |
self.network = IPv4Network(subnet_cidr) | |
self.used = [] | |
def mark(self, ip_or_cidr): | |
self.used.append(IPv4Network(ip_or_cidr)) | |
self.used = sorted(self.used) # Lazy implementation of sorted list, sue me | |
def is_allocatable(self, cidr): | |
our_network = IPv4Network(cidr) | |
return not any(map(lambda their_network: our_network.overlaps(their_network), self.used)) | |
def find_blocks(self, prefix=28): | |
for block in self.network.subnets(new_prefix=prefix): | |
if self.is_allocatable(block): | |
yield block | |
def static_list(): | |
usage = SubnetUsage('172.18.106.0/24') | |
usage.mark('172.18.106.0') | |
usage.mark('172.18.106.1') | |
usage.mark('172.18.106.2') | |
usage.mark('172.18.106.3') | |
usage.mark('172.18.106.255') | |
# Single ENIs | |
usage.mark('172.18.106.78') | |
usage.mark('172.18.106.82') | |
usage.mark('172.18.106.54') | |
usage.mark('172.18.106.83') | |
usage.mark('172.18.106.106') | |
usage.mark('172.18.106.209') | |
usage.mark('172.18.106.84') | |
usage.mark('172.18.106.102') | |
# ip ranges | |
usage.mark('172.18.106.128/28') | |
usage.mark('172.18.106.160/28') | |
usage.mark('172.18.106.144/28') | |
usage.mark('172.18.106.128/28') | |
usage.mark('172.18.106.160/28') | |
usage.mark('172.18.106.144/28') | |
usage.mark('172.18.106.16/28') | |
usage.mark('172.18.106.32/28') | |
usage.mark('172.18.106.192/28') | |
usage.mark('172.18.106.224/28') | |
usage.mark('172.18.106.112/28') | |
usage.mark('172.18.106.176/28') | |
return usage | |
def _per_subnet_usage(ec2, subnet_id): | |
cidr_block = ec2.describe_subnets(SubnetIds=[subnet_id])['Subnets'][0]['CidrBlock'] | |
enis = ec2.describe_network_interfaces(Filters=[{'Name':'subnet-id','Values':[subnet_id]}])['NetworkInterfaces'] | |
usage = SubnetUsage(cidr_block) | |
for iface in enis: | |
usage.mark(iface['PrivateIpAddress']) | |
if 'Ipv4Prefixes' in iface: | |
for prefix in iface['Ipv4Prefixes']: | |
usage.mark(prefix['Ipv4Prefix']) | |
return usage | |
def aws_per_subnet_usage(): | |
import boto3 | |
ec2 = boto3.client('ec2', region_name='eu-central-1') | |
subnet_id = input('Enter subnet ID: ') | |
return _per_subnet_usage(ec2, subnet_id) | |
def aws_per_vpc_availability(): | |
import boto3 | |
ec2 = boto3.client('ec2', region_name='eu-central-1') | |
vpc_id = input('VPC ID? ') | |
all_subnets = ec2.describe_subnets(Filters=[{'Name':'vpc-id', 'Values': [vpc_id]}])['Subnets'] | |
all_usages = map(lambda subnet: list(_per_subnet_usage(ec2, subnet['SubnetId']).find_blocks(28)), all_subnets) | |
return zip(map(lambda subnet: subnet['SubnetId'], all_subnets), all_usages) | |
if len(argv) <= 1 or argv[1] == 'static': | |
usage = static_list() | |
blocks = list(usage.find_blocks(28)) | |
print(f'Static list of IPs: The gathered subnet has {len(blocks)} free blocks of size /28') | |
elif argv[1] == 'aws_subnet': | |
usage = aws_per_subnet_usage() | |
blocks = list(usage.find_blocks(28)) | |
print(f'AWS subnet: The specified subnet has >> {len(blocks)} << free blocks of size /28') | |
elif argv[1] == 'aws_vpc': | |
for subnet, available_blocks in aws_per_vpc_availability(): | |
print(f'Subnet {subnet} has >> {len(available_blocks)} << free blocks of size /28') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment