Before a VPC exists you have to size its subnets, and a /24 that "looks big enough" is how a training cluster runs out of addresses mid-scale-up. Given a CIDR block, derive its network address, broadcast address, netmask and usable host count, and say which of a list of IPs fall inside it.
An IPv4 address is 32 bits. A CIDR block A.B.C.D/p fixes the leading p bits (the network part) and leaves 32 - p host bits free. The netmask is p ones followed by 32 - p zeros.
Solve this with integer bit arithmetic — do not use the ipaddress module.
Implement:
def subnet_report(cidr, ips):
Return a dict with keys, in this order: "network", "broadcast", "netmask" (all dotted-quad strings), "usable_hosts" (int), and "in_subnet" (list of bools, one per entry of ips, in order).
print(subnet_report("10.0.5.37/22", ["10.0.4.9", "10.0.8.1", "10.0.7.255"]))
Output:
{'network': '10.0.4.0', 'broadcast': '10.0.7.255', 'netmask': '255.255.252.0', 'usable_hosts': 1022, 'in_subnet': [True, False, True]}
A /22 borrows 2 bits from the third octet, so the block spans 10.0.4.0-10.0.7.255: 1024 addresses, 1022 assignable.
print(subnet_report("10.0.5.37/22", ["10.0.4.9", "10.0.8.1", "10.0.7.255"])){'network': '10.0.4.0', 'broadcast': '10.0.7.255', 'netmask': '255.255.252.0', 'usable_hosts': 1022, 'in_subnet': [True, False, True]}A /22 mask is 255.255.252.0. ANDing 10.0.5.37 with it clears the low 10 host bits and gives the network 10.0.4.0; setting all host bits gives the broadcast 10.0.7.255. The block holds 2**10 = 1024 addresses, minus network and broadcast, so 1022 are assignable. 10.0.8.1 masks to 10.0.8.0, a different network, so it is outside; the broadcast address itself is still inside the block.
cidr is a valid IPv4 block, prefix length 0 to 322 ** (32 - p) - 2 for p <= 30, 2 for a /31, 1 for a /32ipaddress module