CIDR Notation Explained
CIDR (Classless Inter-Domain Routing) notation compresses subnet masks into a single number after a slash. The number represents the count of consecutive 1-bits in the mask. A /24 means 255.255.255.0, leaving 8 bits for hosts and allowing 254 usable addresses.1
Binary math behind CIDR
Every IPv4 address is 32 binary bits. CIDR notation compresses the subnet mask into a single prefix number that counts how many leading bits are ones. A /24 mask is 24 ones followed by 8 zeros, written as 11111111.11111111.11111111.00000000, which equals 255.255.255.0 in decimal. The bits after the prefix count control the host space: 8 remaining bits yield 2^8 = 256 total addresses, of which 254 are usable after reserving the network and broadcast addresses.2 Consequently, moving from /24 to /25 splits the host space in half, halving usable addresses from 254 to 126. Building on this pattern, each additional prefix bit cuts the address pool in half while doubling the number of subnets you can create from the same address block.
Planning subnets for your network
Before allocating any subnet, identify the maximum device count expected in each segment within the next two years. Adding 20 to 30 percent headroom to that number gives you the minimum host space requirement. From that host count, choose the smallest CIDR prefix whose 2^(32−n) − 2 value satisfies the requirement.2 For a segment expecting 60 devices, a /26 (62 usable hosts) fits. A /27 (30 usable hosts) does not. Yet assigning a /24 wastes 192 addresses the segment will never use, which matters in address-constrained environments where every public IP costs money or where private address space is limited by corporate policy.
Block boundary alignment
Align subnets to their natural block boundaries. A /26 always starts at an address divisible by 64. A /28 starts at a multiple of 16. A /30 starts at a multiple of 4. Following these alignment rules prevents routing confusion and simplifies access control list entries, because each subnet fits cleanly within the parent block without overlapping its neighbors. Misaligned subnets cause overlapping address ranges that break routing protocols and make firewall rules unreliable.
Counting usable addresses correctly
The formula 2^(32 − prefix) − 2 gives the usable host count, and it is easy to apply before committing an allocation. For a /29, that is 2^3 − 2 = 6 usable hosts. Keep in mind that the first address is always the network address and the last is always the broadcast address, so neither is available for hosts. Routers and DHCP scopes enforce this distinction automatically, but manual spreadsheets often miscount by including one of the reserved addresses. When documenting planned allocations, include both the total address count and the usable count so operations staff do not routinize an oversized subnet just because the math looked acceptable.
Using the subnet calculator
Enter any IPv4 or IPv6 address into the calculator along with a CIDR prefix. Results appear instantly: network address, broadcast address, usable host range, and total host count. Use the CIDR prefix spinner to step through prefix lengths and watch the host count change in real time. Consequently, you can quickly find the smallest prefix that covers your device count without mental arithmetic.
For IPv6, the same prefix field works. Enter a /64 to see the standard segment size or a /48 to see the full site allocation boundary. Copy individual result fields with the copy buttons when building firewall rules or cloud VPC configurations that require specific address formats. This interaction model is especially useful when planning cloud subnets, where each prefix step changes the available address count by a factor of two.
VLSM and CIDR working together in enterprise networks
Variable Length Subnet Masking (VLSM) applies CIDR prefix lengths selectively to different segments within one address block, giving network engineers precise control over how address space is divided. A company allocated 10.0.0.0/22 from its ISP divides that parent block into subnets of different sizes: a /24 for the office LAN (254 hosts), a /28 for the server room (14 hosts), and /30s for each of six WAN links (2 hosts each). Building on this, VLSM planning requires assigning the largest subnets first to prevent fragmentation. Visualize the /22 as a block of 1,024 addresses: place the /24 at 10.0.0.0, the /28 at 10.0.1.0, and the /30s at 10.0.1.16 through 10.0.1.40.
Verifying boundaries with the calculator
CapyToolkit's calculator verifies that each allocation falls on the correct boundary. A /28 must start at an address divisible by 16, and a /30 at an address divisible by 4. Misaligned boundaries cause overlapping subnets that break routing. The calculator flags misaligned allocations before they reach production, saving hours of troubleshooting in production environments where overlapping subnets cause intermittent connectivity failures.
CIDR and cloud VPC addressing patterns
Cloud VPCs follow CIDR conventions with provider-specific constraints. AWS limits VPC CIDRs between /16 and /28. Azure VNet subnets range from /2 to /29.34 An AWS VPC with 10.0.0.0/16 contains 65,536 addresses, subdivided into subnets that must all fall within the VPC CIDR. Building on this, peering two VPCs with overlapping CIDRs fails silently: traffic routes locally instead of traversing the peering connection. When planning a multi-VPC architecture with one VPC per environment, allocating non-overlapping /16 blocks from 10.0.0.0/8 (10.0.0.0/16 for dev, 10.1.0.0/16 for staging, 10.2.0.0/16 for production) prevents peering conflicts and makes route tables readable at a glance. The calculator confirms that any candidate CIDR falls within the parent block before you commit it to a Terraform VPC resource, catching misconfigurations early.
Multi-VPC peering constraints
When planning a multi-VPC architecture with one VPC per environment, allocating non-overlapping /16 blocks from 10.0.0.0/8 prevents peering conflicts and makes route tables readable at a glance. Overlapping CIDRs cause silent routing failures where traffic never reaches the peered VPC, and the calculator catches these conflicts before they reach production. Always verify that each VPC CIDR falls within the parent block before committing it to a Terraform VPC resource.
Team conventions help keep multi-VPC allocations consistent. Document the chosen parent block and environment-to-CIDR mapping in a shared README or Terraform workspace variable file so new team members can see the assignment pattern at a glance. Revisit the allocation whenever a new environment such as DR or shared services is added, because it is far easier to reserve the CIDR early than to renumber an established VPC later.
CIDR for IPv6: simpler rules, wider address space
IPv6 CIDR follows the same prefix-length logic as IPv4, just with 128 bits instead of 32.5 The global unicast range is 2000::/3 (the first 3 bits are 001), and ISPs typically delegate a /48 or /56 to each site.6 A /48 provides 65,536 /64 subnets, one for each VLAN or LAN segment. Building on this, IPv6 planning is dramatically simpler than IPv4 because conservation is unnecessary: every segment gets a /64 regardless of device count. A /64 contains 18 quintillion addresses, and even the largest enterprise LAN will never exhaust one.5 CapyToolkit handles both families identically: enter an address, set the prefix, and see the network boundary, whether that boundary covers 254 IPv4 hosts or 18 quintillion IPv6 addresses.
Notes
A /24 always has 256 total addresses (254 usable hosts). A /30 has 4 addresses (2 usable). A /31 is point-to-point with 2 addresses (RFC 3021). The formula for usable hosts is 2^(32-prefix) − 2, except for /31 and /32 which follow special rules.
Examples
/24 subnet
Subnet mask: 255.255.255.0 Total addresses: 256 Usable hosts: 254 Example range: 192.168.1.0 – 192.168.1.255
/16 subnet
Subnet mask: 255.255.0.0 Total addresses: 65,536 Usable hosts: 65,534 Example range: 10.0.0.0 – 10.0.255.255
/30 point-to-point
Subnet mask: 255.255.255.252 Total addresses: 4 Usable hosts: 2 Example range: 10.1.1.0 – 10.1.1.3
/28 small subnet
Subnet mask: 255.255.255.240 Total addresses: 16 Usable hosts: 14 Block size: 16 addresses per subnet
Verify with the IPv4 / IPv6 Subnet & CIDR Calculator tool.
/24 subnet
Subnet mask: 255.255.255.0 Total addresses: 256 Usable hosts: 254 Example range: 192.168.1.0 – 192.168.1.255
- 1.
V. Fuller, T. Li, J. Yu, K. Varadhan, "Classless Inter-Domain Routing (CIDR): an Address Assignment and Aggregation Strategy," RFC 1519, IETF, September 1995. https://www.rfc-editor.org/info/rfc1519
- 2.
Cisco Systems, "Configure IP Addresses and Unique Subnets for New Users," cisco.com, 2024. https://www.cisco.com/c/en/us/support/docs/ip/routing-information-protocol-rip/13788-3.html
- 3.
Amazon Web Services, "VPC CIDR blocks," docs.aws.amazon.com, accessed June 2026. https://docs.aws.amazon.com/vpc/latest/userguide/vpc-cidr-blocks.html
- 4.
Microsoft, "Azure Virtual Network FAQ," learn.microsoft.com, accessed June 2026. https://learn.microsoft.com/en-us/azure/virtual-network/virtual-networks-faq
- 5.
"IPv6," Wikipedia, accessed June 2026. https://en.wikipedia.org/wiki/IPv6
- 6.
R. Hinden, S. Deering, E. Nordmark, "IPv6 Global Unicast Address Format," RFC 3587, IETF, July 2003. https://www.rfc-editor.org/info/rfc3587
It is the number of consecutive 1-bits in the subnet mask. /24 means the first 24 bits are the network portion (255.255.255.0), leaving 8 bits for host addresses.
Use 2^(32 − prefix) − 2. For a /24, that gives 2^8 − 2 = 254 usable hosts. The −2 accounts for the network and broadcast addresses. CapyToolkit performs this calculation instantly so you can skip the mental math.
RFC 3021 allows /31 subnets for point-to-point links. With only 2 addresses, there is no room for a network and broadcast address, but on a two-host link both addresses are usable.
A /25 splits a /24 in half. It has a subnet mask of 255.255.255.128, giving 128 total addresses (126 useful). Network engineers use this to divide a larger segment into two smaller subnets.
Technically any value from 0 to 32 is valid, but common practice uses multiples of 8 (/8, /16, /24) for classful thinking, or /22, /23, /25, /26, /27, /28, /29, /30 for subnetting.
Subnet Calculations in Python
Python's ipaddress module (built in since Python 3.3) handles IPv4 and IPv6 subnetting without any external dependencies.1 You can create networks, iterate hosts, calculate supernets, and test membership with simple Python objects.
The ipaddress module
Python's ipaddress module ships with the standard library from Python 3.3 onward, so no pip install is required.1 Three main classes cover all subnetting needs: IPv4Network and IPv6Network for subnet objects, and ip_network() as a constructor that auto-detects the address family. Calling ip_network('192.168.0.0/24') returns an IPv4Network object with properties for network_address, broadcast_address, netmask, num_addresses, and hosts(). Building on this, ip_interface() handles addresses with host bits set, useful when parsing router interface configurations where the host portion is intentional. The module's objects are hashable and comparable, so they work directly in sets and sorted() without extra conversion. The module also supports reverse DNS lookups through the .reverse_pointer property, and provides .compressed and .exploded representations for IPv6 addresses.
Parsing and validating CIDR input
Parsing untrusted CIDR input safely requires catching the ValueError that ip_network() raises for invalid strings. Wrapping the call in a try/except block and returning None or raising a domain-specific error keeps the caller's contract clean. The strict parameter matters: ip_network('192.168.0.5/24', strict=True) raises ValueError because host bits are set; strict=False silently masks them to '192.168.0.0/24'. Consequently, use strict=True when validating user input that should already be a clean network address, and strict=False when parsing router configurations that embed host bits in the CIDR string. After parsing, the overlaps() method checks if two networks collide before committing either to an IPAM or cloud configuration.
Automation patterns with ipaddress
In infrastructure-as-code workflows, Python's ipaddress module slots naturally into Terraform pre-validation scripts and Ansible dynamic inventory generators. A common pattern reads a CIDR allocation from a YAML config, validates it with ip_network(), then yields subnets via the subnets() method for each availability zone. Building on this, collapse_addresses() merges a list of individual /32 host entries into their covering summary route before pushing updates to a BGP configuration.2 For CI pipelines that lint network configs, iterating over the networks in an ACL file and calling overlaps() on every pair catches conflicting rules before they reach production. Furthermore, ip_address() membership testing (address in network) provides fast per-packet classification in network analysis scripts.
Testing membership and overlaps
The overlaps() method checks if two networks collide before committing either to an IPAM or cloud configuration. This is especially useful in CI pipelines that lint network configs, where iterating over the networks in an ACL file and calling overlaps() on every pair catches conflicting rules before they reach production. CapyToolkit uses the same overlap-checking logic to validate that subnets do not collide with each other within a larger CIDR block.
Membership testing with the in operator is just as important when enforcing host-to-network policies. An expression like ipaddress.ip_address('192.168.1.50') in ipaddress.ip_network('192.168.1.0/24') returns True for allowed hosts and False for anything outside the range. Scripts that process access-control lists or VPN allow-lists can rely on this behavior without writing custom bitwise checks, which keeps the intent readable and reduces the chance of off-by-one mistakes in subnet boundary comparisons.
Serializing subnet data to JSON and YAML
The ipaddress module returns objects that are not directly JSON-serializable. Calling str() on an IPv4Network or IPv4Address converts it to a human-readable string that json.dumps() handles natively. For YAML output, PyYAML serializes ipaddress objects without extra configuration. Building on this, a common pattern converts a list of network objects into a list of CIDR strings for Terraform variable files: json.dumps([str(n) for n in networks]) writes a JSON array that Terraform's jsondecode() parses directly into a list(string) variable. CapyToolkit uses the same serialization approach when exporting calculated subnet ranges for use in infrastructure-as-code templates. For API responses, the .compressed property on IPv6 addresses produces the shortest possible representation, while .exploded gives the full eight-hextet form that some systems require for database storage or DNS PTR records.
IPv6 subnetting with ipaddress
IPv6 support in the ipaddress module mirrors the IPv4 API exactly. IPv6Network('2001:db8:abcd::/48') provides the same properties: network_address, broadcast_address, num_addresses, hosts(), subnets(), and supernet(). The only difference is the address length: 128 bits instead of 32.3 Building on this, a typical IPv6 allocation exercise takes a /48 from an ISP and splits it into /64 segments for each VLAN. The subnets() method with new_prefix=64 yields 65,536 /64 blocks, more than enough for any site's VLAN count.4 Iterating all hosts in a /64 is not feasible (2^64 addresses), so the module's hosts() generator is useful only for subnets of /120 or larger.
Comparing IPv4 and IPv6 prefix selection
IPv6 planning is simpler than IPv4 because address conservation is irrelevant. Assign a /64 to every segment regardless of device count. A point-to-point link gets the same /64 as a LAN with 1,000 hosts. This eliminates the hosts-to-prefix calculation entirely. The only planning decision is which /48 or /56 block to assign each site. CapyToolkit's calculator handles both families: enter an IPv6 address and a prefix to see the network boundary, first host, and last host in compressed notation.
Building a subnet allocation tracker with ipaddress
Production IPAM systems often start as simple allocation trackers. A Python script that maintains a set of allocated IPv4Network objects can validate new requests by checking overlaps() against existing allocations. Converting a YAML config file of assigned CIDRs into ipaddress objects takes one line per entry: ipaddress.ip_network(entry['cidr']). Building on this, a Flask or FastAPI endpoint that accepts a requested prefix size, validates it against remaining free space, and returns an available CIDR gives your team a self-service subnet allocation API. The overlaps() method catches conflicting requests before they reach a cloud provider's VPC console, where conflicting CIDRs produce cryptic error messages.
Exporting allocation data to Netbox or Nautobot
Network source-of-truth platforms like NetBox and Nautobot store IP prefixes and addresses in a structured database. Python scripts that read from these platforms' REST APIs, perform allocation calculations with ipaddress, and write results back create automated workflows. A script that queries NetBox for all prefixes in a VLAN, identifies gaps between allocated blocks, and returns the next available /28 combines NetBox's data model with ipaddress's arithmetic, giving you a programmatic allocation engine without building custom IPAM logic from scratch.
Notes
Use ipaddress.ip_network() to create network objects. The strict=True argument (the default since Python 3.5) rejects host bits set in the address. Use strict=False to auto-correct. Hosts() returns usable addresses; use the network object itself to include network/broadcast. Supernet() merges adjacent subnets.
Examples
Create a subnet and list hosts
import ipaddress
net = ipaddress.ip_network("192.168.1.0/28")
print(f"Network: {net.network_address}")
print(f"Broadcast: {net.broadcast_address}")
print(f"Usable hosts: {net.num_addresses - 2}")
for host in net.hosts():
print(host) Subnet a network into smaller subnets
import ipaddress
net = ipaddress.ip_network("10.0.0.0/24")
subnets = list(net.subnets(new_prefix=26))
print(f"Created {len(subnets)} /26 subnets:")
for sn in subnets:
print(f" {sn}") Test if an IP belongs to a network
import ipaddress
net = ipaddress.ip_network("192.168.1.0/24")
ip = ipaddress.ip_address("192.168.1.100")
print(ip in net) # True
ip2 = ipaddress.ip_address("10.0.0.1")
print(ip2 in net) # False Verify with the IPv4 / IPv6 Subnet & CIDR Calculator tool.
Create a subnet and list hosts
import ipaddress
net = ipaddress.ip_network("192.168.1.0/28")
print(f"Network: {net.network_address}")
print(f"Broadcast: {net.broadcast_address}")
print(f"Usable hosts: {net.num_addresses - 2}")
for host in net.hosts():
print(host) - 1.
Python Software Foundation, "ipaddress — IPv4/IPv6 manipulation library," docs.python.org, accessed June 2026. https://docs.python.org/3/library/ipaddress.html
- 2.
Python Software Foundation, "An introduction to the ipaddress module," docs.python.org, accessed June 2026. https://docs.python.org/3/howto/ipaddress.html
- 3.
R. Hinden, S. Deering, E. Nordmark, "IPv6 Global Unicast Address Format," RFC 3587, IETF, July 2003. https://www.rfc-editor.org/info/rfc3587
- 4.
"IPv6," Wikipedia, accessed June 2026. https://en.wikipedia.org/wiki/IPv6
Yes. ipaddress.ip_network("2001:db8::/32") works the same way. You can subnet, iterate hosts, and perform all the same operations on IPv6 networks as IPv4. The module handles address family detection automatically.
Use net.subnets(new_prefix=26). This returns a generator of four /26 subnets, each with 64 addresses (62 usable hosts). CapyToolkit runs Python examples locally, so nothing is sent to a server.
strict=True (the default) raises an error if host bits are set in the network address. strict=False silently masks off the host bits. For example, ip_network("192.168.1.5/24", strict=False) creates 192.168.1.0/24.
Yes. Use ipaddress.collapse_addresses() to merge a list of networks into the smallest possible set of supernets. For two adjacent /24s, it returns the covering /23. This is useful for route summarization tasks.
The official Python documentation at docs.python.org/3/library/ipaddress.html covers every class and method. For practical examples, the Python HOWTO at docs.python.org/3/howto/ipaddress.html walks through common use cases including subnetting, supernetting, and address iteration.
Subnet Calculations in Bash
Linux and WSL systems can use ipcalc or sipcalc to perform subnet math from the command line. On macOS, sipcalc is available via Homebrew. For quick calculations without tools, bitwise operations in Bash or Python work as a fallback.1
ipcalc and sipcalc overview
ipcalc is a single-binary tool available in most Linux package managers (apt install ipcalc, dnf install ipcalc). Given a CIDR range, it prints the network address, broadcast address, first and last usable host, and host count in a human-readable format.1 sipcalc provides a superset of that output, adding binary representation, Cisco wildcard masks, and a subnet-split view. Homebrew users on macOS install sipcalc with brew install sipcalc.2 Yet the two tools are not drop-in replacements: ipcalc flags differ between the Debian package and the Red Hat package, so -s for split works on some systems but not others. Building on this, knowing which variant you have avoids unexpected flag failures in shared scripts.
Parsing CIDR input in shell scripts
Extracting specific fields from ipcalc output uses grep and awk. The pattern grep '^Broadcast' | awk '{print $2}' isolates the broadcast address from the full output block. For scripts that must run without ipcalc installed, Bash bitwise arithmetic handles the same math.3 Converting a dotted-decimal IP to a 32-bit integer uses left-shift and OR: ip=$((a<<24 | b<<16 | c<<8 | d)). Consequently, the network address becomes $((ip & mask)) and the broadcast is $((ip | ~mask & 0xFFFFFFFF)).
Converting between decimal and dotted notation
Printing back to dotted decimal uses printf with right-shift and bitwise AND: printf '%d.%d.%d.%d'. Furthermore, the CIDR prefix length maps to a mask with $((0xFFFFFFFF << (32 - prefix) & 0xFFFFFFFF)).4 This mapping is deterministic: every prefix length always produces the same 32-bit mask value. These one-liners let you embed subnet calculations directly in shell scripts that run on any Linux or macOS system without installing Python or additional runtimes.
CI pipeline integration
Subnet validation scripts belong in the pre-commit or CI lint stage before Terraform plan or Ansible syntax-check. A compact validation block tests every CIDR in a config file: read each line, pass it through ip_network validation via Python -c or ipcalc, and exit 1 on any failure. Building on this, the script catches both malformed CIDR strings and host-bit collisions before the plan stage where errors surface as cryptic provider messages. In Dockerfile-based CI images, adding RUN apt-get install -y ipcalc to the base image makes the tool available without pinning a version. Yet for portability across Linux and macOS build agents, the Python-based approach using python3 -c 'import ipaddress; ipaddress.ip_network(...)' requires no additional packages and runs anywhere Python 3 is present.
Generating subnet reports for documentation with Bash
A Bash script that reads a list of CIDRs from a file and outputs a structured report (network address, broadcast, usable count, and wildcard mask) replaces manual subnet lookups during audits. Using ipcalc in a while loop processes each line: while read cidr; do ipcalc "$cidr"; done < subnets.txt. Building on this, piping ipcalc output through awk and column -t formats the results into a table suitable for Markdown documentation or Confluence pages. A script that reads a CIDR allocation spreadsheet and validates every range for overlaps takes roughly 20 lines of Bash when combined with ip_network validation via python3 -c. The CapyToolkit subnet calculator provides the same results interactively, while the Bash approach batch-processes hundreds of ranges for quarterly documentation updates.
Formatting output for documentation
Piping ipcalc output through awk and column -t formats the results into a table suitable for Markdown documentation or Confluence pages. Building on this, redirecting the output to a file with timestamp in the name creates an audit trail that network teams can reference during change reviews. For quarterly audits, a script that batch-processes hundreds of CIDRs and outputs structured tables saves hours of manual lookup work.
Subnetting on macOS and cross-platform considerations
macOS ships without ipcalc in the base system. Installing Homebrew and running brew install ipcalc adds it, but the macOS version differs from the Linux version: some flags like -s (split) may not be available. sipcalc (brew install sipcalc) is the more consistent choice across platforms, offering identical output on macOS, Linux, and WSL.2 Building on this, for scripts that must run on all three platforms, the pure Bash bitwise approach is the most portable: it depends only on Bash itself, with no external tools. The $(( )) arithmetic expansion in Bash handles 32-bit integer math for any IPv4 calculation, and printf converts the integer back to dotted decimal. When ipcalc or sipcalc output format differences between platforms cause parsing failures, the self-contained Bash arithmetic approach eliminates the dependency entirely.
Cross-platform scripting tips
When writing scripts that must run on all three platforms, test the output format of each tool variant before relying on it in production. The pure Bash bitwise approach is the most portable since it depends only on Bash itself. CapyToolkit provides the same subnet calculations in a browser, eliminating cross-platform scripting concerns entirely. For teams that need consistent results across Linux, macOS, and WSL without installing any tools, the browser-based approach avoids the subtle flag differences between ipcalc variants.
When a script must stay in Bash, guard against environment drift by pinning the tool path and checking for its presence before use. A short conditional that runs command -v ipcalc and falls back to the bitwise math when the binary is missing keeps the same script working on a fresh macOS machine, a minimal Alpine container, and a full Debian host. Documenting which code path ran, either through a comment in the output or a log line, makes the eventual debugging session far shorter when a colleague inherits the script months later.
Integrating subnet validation into Terraform pre-commit hooks
A pre-commit hook that extracts every CIDR string from staged .tf files and validates the host count against declared instance counts catches oversubscribed subnets before terraform apply. The hook uses grep to extract CIDR patterns, a Bash loop to call ipcalc or python3 for each one, and exits non-zero when any subnet's usable count falls below a threshold (for example, requiring at least 10% free headroom). Building on this, a hook that also checks CIDR overlaps across all .tf files in the repository prevents conflicting allocations from merging into the main branch. Running this hook locally in under one second with Bash and ipcalc is fast enough to be part of a standard git commit workflow without frustrating developers. CapyToolkit provides the same validation interactively: enter a CIDR and confirm the usable count matches your planned instance count before writing the Terraform resource block.
Notes
ipcalc takes a CIDR prefix and displays network, broadcast, wildcard, and host ranges. sipcalc provides more detailed output including binary representation and split subnets. Install via apt install ipcalc or brew install sipcalc. For quick math: use printf and bitwise ops in Bash.
Examples
ipcalc basic usage
ipcalc 192.168.1.0/28 # Network: 192.168.1.0/28 # Broadcast: 192.168.1.15 # HostMin: 192.168.1.1 # HostMax: 192.168.1.14 # Hosts/Net: 14
ipcalc split a subnet
ipcalc -s 192.168.1.0/24 4 # Split 192.168.1.0/24 into 4 equal subnets # 192.168.1.0/26 ... 192.168.1.63/26 # 192.168.1.64/26 ... 192.168.1.127/26 # ...
sipcalc detailed output
sipcalc 10.0.0.0/22 # -[ipv4 : 10.0.0.0/22] - 0 # [Host address: 10.0.0.0] # [Network address: 10.0.0.0] # [Broadcast address: 10.0.3.255] # [Hosts/Net: 1022]
Bash bitwise subnet math
# Calculate broadcast for 192.168.5.0/24 prefix=24 host_bits=$((32 - prefix)) broadcast=$(( (192<<24) + (168<<16) + (5<<8) + (255>>host_bits<<host_bits) )) printf "%d.%d.%d.%d\n" $((broadcast>>24&255)) $((broadcast>>16&255)) $((broadcast>>8&255)) $((broadcast&255))
Verify with the IPv4 / IPv6 Subnet & CIDR Calculator tool.
ipcalc basic usage
ipcalc 192.168.1.0/28 # Network: 192.168.1.0/28 # Broadcast: 192.168.1.15 # HostMin: 192.168.1.1 # HostMax: 192.168.1.14 # Hosts/Net: 14
- 1.
Krischan Jodies, "ipcalc(1) — General Commands Manual," manpages.debian.org, December 2023. https://manpages.debian.org/unstable/ipcalc/ipcalc.1.en.html
- 2.
Simon Ekstrand, "Sipcalc," routemeister.net, accessed June 2026. https://www.routemeister.net/projects/sipcalc/
- 3.
Greg's Wiki, "BashFAQ/114 — How do I operate on IP addresses and netmasks?," mywiki.wooledge.org, accessed June 2026. https://mywiki.wooledge.org/BashFAQ/114
- 4.
GNU Project, "Shell Arithmetic," gnu.org, accessed June 2026. https://www.gnu.org/software/bash/manual/bash.html#Shell-Arithmetic
ipcalc is not always available on macOS. Install sipcalc instead via Homebrew: brew install sipcalc. sipcalc provides similar output with additional detail like binary breakdowns.
ipcalc may display unexpected results for very large subnets because the host range spans millions of addresses. For large subnets, use sipcalc or Python/ipaddress which handle them more gracefully. CapyToolkit handles any prefix length in the browser without installation.
Yes. ipcalc and sipcalc are command-line tools designed for scripting. Parse their output with grep, awk, or cut. Python's ipaddress module is also excellent for automation and is available everywhere Python runs.
ipcalc is simpler and available on most Linux distros. sipcalc is more detailed, showing binary representation, split subnets, and Cisco wildcard masks. Install sipcalc for more thorough analysis.
Use Bash bitwise arithmetic to compute network addresses and broadcast addresses from any CIDR. The formula uses left-shift and OR to convert dotted decimal to an integer, then bitwise AND with the mask to get the network address. CapyToolkit provides the same calculations in a browser if you prefer not to script it.
IPv6 Subnetting Basics
IPv6 subnetting is simpler than IPv4 because the standard subnet size is always /64, giving 2^64 addresses per segment.1 Understanding the /48 allocation, /64 subnet, and /128 single-host prefix covers most real-world networking scenarios you will encounter.
IPv6 address space structure
IPv6 uses 128-bit addresses grouped into eight 16-bit hextets separated by colons, providing an address space so vast that conservation strategies are unnecessary for any practical deployment. The standard notation rules compress leading zeros within a hextet and replace one run of consecutive all-zero hextets with ::. Building on this, the address 2001:0db8:0000:0000:0000:0000:0000:0001 compresses to 2001:db8::1.1 The global unicast space (2000::/3) is where ISPs allocate addresses to organizations and end sites around the world.
Global unicast and link-local ranges
A typical ISP allocation is a /48, which contains 2^(128 minus 48) = 2^80 addresses and 65,536 /64 subnets for you to assign across your network.2 Consequently, a single /48 covers every segment in a large office campus with room for future growth without needing to conserve addresses the way IPv4 forces you to. Link-local addresses in the fe80::/10 range are auto-generated by every interface and stay within the local network segment, since they are not routable beyond the local link.
Sizing subnets for real deployments
For most deployments, every LAN segment is a /64. This is not a recommendation but a requirement: SLAAC (Stateless Address Autoconfiguration) embeds the 64-bit EUI-64 interface identifier into the host portion of the address and breaks on subnets smaller than /64.3 Furthermore, DHCPv6 stateless mode also assumes /64 boundaries to function correctly.
Point-to-point and VLAN considerations
Point-to-point links between routers conventionally use /64 rather than the IPv4 equivalent /30, because the address space is large enough that conservation is unnecessary in IPv6. Yet some operators use /127 for router-to-router links to prevent certain prefix-based attacks defined in RFC 6164. For VLAN segmentation, assign a separate /64 per VLAN from within your /48 allocation, taking advantage of the 65,536 /64s available so each VLAN gets its own isolated segment with no address exhaustion concern.
Documenting which /64 maps to which VLAN keeps the plan readable as the network grows. A simple spreadsheet that pairs each VLAN id with its /64 boundary prevents two teams from accidentally requesting the same segment from the /48. When you later add a site or merge two networks, having that mapping on hand makes it straightforward to confirm that no two VLANs share a prefix before the change reaches a router.
Using the calculator with IPv6
Enter any IPv6 address into the calculator input. The tool auto-detects the colon-separated format and switches to 128-bit mode for you. Set the prefix to /64 for a standard LAN segment or /48 to see the full site allocation boundary at once. The results show the network address in compressed notation, the first and last addresses in the subnet, and the total address count expressed in scientific notation because 2^64 exceeds what fits on screen as a digit string. Consequently, the calculator focuses on boundary addresses rather than host counts for large IPv6 ranges, which is what you need when planning firewall rules or VLAN allocations.
Planning VLAN allocation with /48 prefixes
Use the /48 prefix to plan VLAN allocation: each of the 65,536 available /64s within your /48 maps to one segment. Copy the network address field for firewall rules that require the exact subnet boundary. When assigning sequential /64s to VLANs, the calculator confirms that each network address lands on the correct hextet boundary so overlapping allocations never reach your router configuration.
IPv6 address planning for multi-site organizations
A well-structured IPv6 address plan mirrors your site and VLAN hierarchy in the prefix bits, making routes summarizable and firewall rules readable at a glance. Start with your ISP-allocated /48 and use the fourth hextet (bits 49 through 64) to encode the site, and the fifth hextet (bits 65 through 80) to encode the VLAN within that site. For example, site 1 VLAN 10 gets 2001:db8:0:1:10::/64, while site 2 VLAN 20 gets 2001:db8:0:2:20::/64.
Building on this, each site's routes summarize to a single /48 or /56 at the WAN edge, reducing the number of BGP or static routes you must maintain across your infrastructure. Firewall rules that permit "all site 1 VLANs" match 2001:db8:0:1::/48 instead of listing every /64 individually. Consequently, the addressing plan becomes a routing and security tool, not just a way to assign unique identifiers. The subnet calculator verifies each /64 boundary before you commit the plan to router configurations, ensuring that VLAN 10's range does not accidentally overlap with VLAN 20 at any site.
SLAAC privacy extensions and DHCPv6 coexistence
SLAAC originally used the device's MAC address to build the 64-bit interface identifier (EUI-64), which raised privacy concerns because the address never changed. RFC 4941 introduced privacy extensions (temporary addresses) that generate random 64-bit interface IDs that rotate over time to make tracking harder.3 Windows, macOS, iOS, and Android all enable privacy extensions by default, meaning a device may use both a stable EUI-64 address and one or more temporary addresses simultaneously on the same interface.
For network administrators, this means firewall rules based on IPv6 addresses must account for address rotation. Building on this, DHCPv6 provides a stable assignment mechanism alongside SLAAC: the router advertises the M (Managed) flag to tell clients to request a DHCPv6 address, or the O (Other) flag for DHCPv6-provided DNS and NTP without address assignment. The BE900 and most modern routers support both SLAAC and DHCPv6 simultaneously. Consequently, the recommended approach for managed networks is SLAAC plus DHCPv6 stateless (O flag): devices self-configure their addresses via SLAAC with privacy extensions, while DHCPv6 provides consistent DNS server information that you control.
Notes
IPv6 uses hexadecimal notation with colon separators (2001:db8::1). A /64 is the minimum recommended subnet and is required for SLAAC (stateless address autoconfiguration). /48 is the typical ISP allocation to a site. /128 is a single host address (like IPv4's /32). Leading zeros in each hextet can be omitted, and consecutive all-zero groups are replaced with :: once.
Examples
Standard /64 subnet
Network: 2001:0db8:abcd:0001::/64 First host: 2001:0db8:abcd:0001::1 Last host: 2001:0db8:abcd:0001:ffff:ffff:ffff:fffe Size: 2^64 addresses (18 quintillion)
ISP /48 allocation
ISP gives: 2001:0db8:abcd::/48 You get: 65,536 /64 subnets Subnet 1: 2001:0db8:abcd:0000::/64 Subnet 2: 2001:0db8:abcd:0001::/64 ...through subnet 65535
Single host /128
Interface: 2001:0db8:abcd:0001::5/128 This is a single address, with no network and no broadcast. Used for loopback or static host routes.
Shortened notation rules
Original: 2001:0db8:0000:0000:0000:0000:0000:0001 Compressed: 2001:db8::1 Rules: Omit leading zeros in each group. Replace one run of consecutive zero groups with ::. Full form has exactly 8 hextets.
Verify with the IPv4 / IPv6 Subnet & CIDR Calculator tool.
Standard /64 subnet
Network: 2001:0db8:abcd:0001::/64 First host: 2001:0db8:abcd:0001::1 Last host: 2001:0db8:abcd:0001:ffff:ffff:ffff:fffe Size: 2^64 addresses (18 quintillion)
- 1.
R. Hinden and S. Deering, "IP Version 6 Addressing Architecture," RFC 4291, IETF, February 2006. https://www.rfc-editor.org/info/rfc4291
- 2.
T. Narten, G. Huston, L. Roberts, "IPv6 Address Assignment to End Sites," RFC 6177, IETF, March 2011. https://www.rfc-editor.org/info/rfc6177
- 3.
"IPv6," Wikipedia, accessed June 2026. https://en.wikipedia.org/wiki/IPv6
A /64 is required for Stateless Address Autoconfiguration (SLAAC), which is the default method for hosts to self-assign IPv6 addresses. It also provides enough addresses for any conceivable subnet, which is why this boundary is used as the standard calculation unit.
A /48 contains 2^(64 minus 48) = 65,536 /64 subnets. This is the typical allocation for a single site or organization, and it gives you more VLANs than most enterprises ever need.
It is a /128. This maps a single interface identifier to one address. It is used for loopback (::1/128) or specific host routing, similar to 127.0.0.1/32 in IPv4.
Yes. You can subnet a /64 into /65, /66, and so on. However, subnets smaller than /64 break SLAAC and are unusual in practice. Most deployments stick with /64 for simplicity and compatibility across all operating systems.
Every IPv6 interface has a link-local address in the fe80::/10 range. It is auto-generated and used for neighbor discovery and local communication, similar to 169.254.x.x in IPv4. It is not routable beyond the local segment.
Yes. CapyToolkit handles IPv6 alongside IPv4 with the same interface. Enter any IPv6 address and prefix to see the network boundary, first host, and last host in compressed notation.
Subnet Calculations in JavaScript
JavaScript handles IPv4 subnet math natively with bitwise operators. For IPv6, BigInt is required because IPv6 addresses exceed 32-bit integer capacity.1 Two Node.js packages, ip-cidr and netmask, simplify CIDR parsing and are widely used in network tooling and Kubernetes operators.
Core library overview
The ip-cidr npm package (v4, MIT license) parses CIDR strings and provides start(), end(), toArray(), and contains() methods.2 Installing it takes one command: npm install ip-cidr. The netmask package offers a more compact API with a Netmask class that exposes base, mask, bitmask, broadcast, first, last, and contains() properties on construction from CIDR or dotted-decimal input.3 For pure IPv4 math without dependencies, JavaScript bitwise operators work directly: (ip | 0) and >>> (unsigned right shift) handle 32-bit arithmetic in any browser. Consequently, a complete IPv4 subnet calculator fits in under 20 lines of vanilla JavaScript without any npm install. For new projects, ip-cidr provides the cleaner API with built-in validation; for zero-dependency browser scripts, the bitwise approach is self-contained.
Parsing and validating CIDR in JavaScript
Validating CIDR input before calculation prevents incorrect results from malformed strings. A two-pass approach, combining a structural regex check with a numeric range validation, catches both malformed formats and out-of-range octets that a single check would miss. Skipping this step is the most common reason subnet scripts silently return wrong network or broadcast addresses.
Structural validation with a regular expression
A regex check, such as /^(\d{1,3}\.){3}\d{1,3}\/\d{1,2}$/, rejects obviously malformed strings before parsing each octet, filtering out missing octets, non-numeric characters, and absent prefix lengths in a single step. This fast pre-check avoids running more expensive parsing logic on input that is clearly not a CIDR string. Yet regex alone cannot catch values like 300.0.0.0/24 where an octet exceeds 255, so a second pass is still required to verify each numeric range.
Range checking and library-based validation
The second pass parses each octet and verifies it falls within [0, 255], while the prefix length must be between 0 and 32 for IPv4. For library-based validation, ip-cidr.isValidCIDR(string) returns a boolean without throwing, which is convenient for form-like input checks. Building on this, for production scripts that parse CIDR input from config files or API responses, wrapping the parse call in a try/catch prevents unhandled exceptions from crashing a Node.js process when the input is malformed.
Automation patterns in Node.js
Node.js subnet scripts slot into infrastructure automation pipelines via CLI scripts, Express APIs, or Kubernetes controllers. A common pattern reads a CIDR allocation from a JSON config and iterates subnets to assign one per microservice namespace. Building on this, a custom kubectl plugin can parse the cluster pod CIDR and warn when remaining IP count drops below a threshold.
Client-side and address-enumeration patterns
For browser applications, the same bitwise IPv4 logic runs client-side without a server, following the same approach used in CapyToolkit's subnet calculator. Furthermore, ip-cidr's toArray() method generates the full list of addresses in a block, useful for populating DHCP reservations or IP address management (IPAM) systems from a script. For large blocks, page through the results with a limit parameter instead of materializing the entire range in memory at once.
The ip-cidr package also exposes a loop() method that runs a callback function against each address in the range, accepting the same from and limit chunking options as toArray(). Iterating with loop() instead of building a full array up front keeps memory use flat when scanning a /16 or larger block for IPAM reconciliation or DHCP reservation checks.
IPv6 subnet calculation with BigInt in JavaScript
IPv6 addresses are 128 bits, far exceeding JavaScript's 53-bit safe integer range.4 BigInt handles this natively: represent an IPv6 address as a BigInt, compute the mask with bitwise operations, and derive the network address. The key operators are << (left shift), >> (right shift), & (AND), | (OR), and ~ (NOT), all of which work on BigInt values. The critical detail: ~ on BigInt produces an infinite-precision negative, so you must mask the result with & ((1n << 128n) - 1n) to keep it within 128 bits.
Building on this, a practical IPv6 subnet calculator function takes a CIDR string like "2001:db8:abcd:0001::/64", parses the address into BigInt, computes the mask as (1n << (128n - prefixLen)) - 1n << prefixLen, and returns the network address formatted back to colon-heavy notation. The code examples section includes a simplified version; a production implementation handles compressed notation (::) by expanding to eight hextets before conversion. Consequently, you can build a browser-based IPv6 subnet tool that runs entirely client-side, matching the CapyToolkit approach for IPv4.
Building a subnet validation API with Express and ip-cidr
A lightweight Express API that validates CIDR input and returns subnet details serves as a backend for IPAM frontends or Terraform pre-validation hooks.5 The endpoint accepts a query parameter (?cidr=10.0.0.0/24), passes it through ip-cidr's isValidCIDR check, and returns a JSON object with network address, broadcast, usable range, and host count. Invalid input returns a 400 status with an error message.
Building on this, adding an overlap-check endpoint accepts two CIDR strings and returns whether they overlap, using ip-cidr's contains() method on each network. This catches conflicting allocations before they reach your cloud provider. For CI pipeline integration, the API runs as a container that your Terraform plan step calls via curl before applying. If the API reports an overlap, the pipeline fails with a clear error message instead of producing a cryptic provider error mid-deployment. The entire service fits in under 50 lines of Express code using the ip-cidr package.
Notes
// IPv4 subnet calculation in vanilla JavaScript function subnetInfo(cidr) { const [addr, prefix] = cidr.split('/'); const prefixLen = parseInt(prefix, 10); const octets = addr.split('.').map(Number); const ip = ((octets[0] << 24) | (octets[1] << 16) | (octets[2] << 8) | octets[3]) >>> 0; const mask = prefixLen === 0 ? 0 : (~0 << (32 - prefixLen)) >>> 0; const network = (ip & mask) >>> 0; const broadcast = (network | ~mask) >>> 0; const toIP = n => [(n >>> 24) & 255, (n >>> 16) & 255, (n >>> 8) & 255, n & 255].join('.'); return { network: toIP(network), broadcast: toIP(broadcast), firstHost: toIP(network + 1), lastHost: toIP(broadcast - 1), usableHosts: Math.pow(2, 32 - prefixLen) - 2, }; } console.log(subnetInfo('192.168.1.0/26'));
Examples
Vanilla IPv4 subnet info
subnetInfo('10.0.0.0/22');
// { network: '10.0.0.0', broadcast: '10.0.3.255',
// firstHost: '10.0.0.1', lastHost: '10.0.3.254',
// usableHosts: 1022 } ip-cidr package usage
const IPCIDR = require('ip-cidr');
const cidr = new IPCIDR('192.168.1.0/27');
console.log(cidr.start()); // '192.168.1.1'
console.log(cidr.end()); // '192.168.1.30'
console.log(IPCIDR.isValidCIDR('192.168.1.0/27')); // true IPv6 with BigInt
// IPv6 requires BigInt for 128-bit math const prefix6 = 64n; const ip6 = 0x20010db8n << 96n; // 2001:db8:: simplified const mask6 = prefix6 === 0n ? 0n : (~0n << (128n - prefix6)) & ((1n << 128n) - 1n); const network6 = ip6 & mask6; console.log(network6.toString(16));
Verify with the IPv4 / IPv6 Subnet & CIDR Calculator tool.
Vanilla IPv4 subnet info
subnetInfo('10.0.0.0/22');
// { network: '10.0.0.0', broadcast: '10.0.3.255',
// firstHost: '10.0.0.1', lastHost: '10.0.3.254',
// usableHosts: 1022 } - 1.
Mozilla Developer Network, "Number.MAX_SAFE_INTEGER," developer.mozilla.org, accessed June 2026. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER
- 2.
Alexander Balasyan, "ip-cidr," npm, accessed June 2026. https://www.npmjs.com/package/ip-cidr
- 3.
rs, "netmask," npm, accessed June 2026. https://www.npmjs.com/package/netmask
- 4.
Mozilla Developer Network, "BigInt," developer.mozilla.org, accessed June 2026. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt
- 5.
Express.js, "Express 4 API Reference," expressjs.com, accessed June 2026. https://expressjs.com/en/api.html
Yes. JavaScript bitwise operators work in any browser without Node.js or npm packages, and CapyToolkit relies on that same capability for its client-side IPv4 calculations. The >>> (unsigned right shift) operator is essential for correct 32-bit unsigned arithmetic in the browser.
ip-cidr and netmask are both actively maintained. ip-cidr has a cleaner API for validation (isValidCIDR) and range operations. netmask is more compact if you only need network/broadcast/mask fields. Check npm for the current version before installing.
JavaScript treats bitwise operands as signed 32-bit integers. Addresses with the top bit set (128.x.x.x and above) appear negative after bitwise operations. Use >>> 0 (unsigned right shift by zero) to convert back to an unsigned 32-bit value before arithmetic.
IPv6 requires BigInt because 128-bit addresses exceed JavaScript's safe integer range. Use BigInt literals (128n, 0n) and BigInt shift operators. The bitwise NOT operator ~ on BigInt produces a signed infinite-precision negative, so mask the result explicitly with & ((1n << 128n) - 1n) to keep it within 128 bits.
Using ip-cidr, call cidr.toArray() to get all IPs as strings. For large blocks, prefer cidr.toArray({ from: '0', limit: 100 }) to page through them. Calling toArray() on a /16 generates 65,536 strings, so iterate only what you need rather than materializing the entire range in memory.
Subnet Calculations in PowerShell
PowerShell accesses .NET subnet types directly without installing modules.1 System.Net.IPAddress and System.Net.IPNetwork (available in .NET 8 and PowerShell 7.4+) handle CIDR parsing, network math, and address validation in a few lines.
.NET IP types in PowerShell
PowerShell on .NET 8 (PowerShell 7.4+) can use [System.Net.IPNetwork]::Parse("192.168.1.0/24") to obtain a network object with BaseAddress, PrefixLength, and Contains() method.1 Building on this, [System.Net.IPAddress] provides Parse(), TryParse(), and AddressFamily for both IPv4 and IPv6 without any module install. Detecting the PowerShell edition at runtime lets your script branch to the right approach for the host environment, which is the same edition-aware pattern CapyToolkit uses to keep its browser-based calculator portable across JavaScript engines.
IPNetwork on PowerShell 7.4+ and .NET 8
With [System.Net.IPNetwork]::Parse you get a strongly typed object that exposes the network address, broadcast, prefix length, and a Contains() method for membership checks in a single line of code. This is the simplest path when you control the PowerShell version, because the parser handles CIDR validation and address arithmetic without any manual bit-shifting logic on your part, and it throws a clear exception for malformed input.
Falling back on Windows PowerShell 5.1
Windows PowerShell 5.1 does not include the IPNetwork type, so on that platform manual bitwise math or the community NetCalculator module fills the gap. Checking $PSVersionTable.PSVersion and $PSVersionTable.PSEdition at script start lets you branch between the built-in parser and a manual implementation depending on the host. The manual function in the code examples section of this guide works on both editions, so you can ship one script everywhere.
The -shl and -shr bitwise shift operators used in the manual fallback function have been part of PowerShell since version 3.0, along with -band, -bor, -bxor, and -bnot, so the same bitwise math runs unmodified on Windows PowerShell 5.1 without requiring any additional module. Because these operators work directly on 64-bit integers, casting each octet to [Int64] before shifting avoids the sign-extension problems that surface when values are left as PowerShell's default 32-bit [int] type.
Bitwise CIDR math in PowerShell
Manual IPv4 subnet math in PowerShell uses [Int64] to avoid signed 32-bit overflow.2 Converting a dotted-decimal IP to an integer requires bit-shifting each octet and combining with -bor: $ip = ($a -shl 24) -bor ($b -shl 16) -bor ($c -shl 8) -bor $d. The subnet mask becomes $mask = [System.Convert]::ToInt64(('1' * $prefix).PadRight(32, '0'), 2). Bitwise AND gives the network address; OR with the inverted mask gives broadcast. Yet the invert operation -bnot in PowerShell works on 64-bit integers, so mask the result to 32 bits with -band 0xFFFFFFFF to avoid sign extension. Converting back to dotted decimal uses bit-shift and -band on each 8-bit group.
Automation patterns for network scripts
PowerShell subnet scripts integrate naturally into network inventory and Active Directory automation pipelines. A common pattern reads a list of CIDRs from a CSV file using Import-Csv, validates each with TryParse, and outputs host ranges to a new CSV for the IPAM team. Building on this, DSC (Desired State Configuration) resources for Windows Server networking accept CIDR strings directly, so validated CIDR input from PowerShell validation scripts feeds DSC configurations without manual conversion. For Azure PowerShell automation, Az.Network cmdlets like New-AzVirtualNetworkSubnetConfig accept AddressPrefix as a CIDR string, so pre-validating the CIDR with [System.Net.IPNetwork] before calling Az prevents provider errors that surface minutes into a deployment.
Testing subnet containment in PowerShell
Determining whether an IP address falls within a CIDR range is a common task in log analysis and firewall auditing. On PowerShell 7.4+, the solution is one line: [System.Net.IPNetwork]::Parse("10.0.0.0/16").Contains([System.Net.IPAddress]::Parse("10.0.5.20")) returns True. For older PowerShell versions, the manual bitwise approach compares ($ipInt -band $maskInt) -eq $netInt, where all three values are [Int64] representations of the IP, mask, and network address.
Building a log-analysis script around containment checks
A practical log-analysis script reads a CSV of source IPs and checks each against a list of known subnets, tagging each row with the matching subnet name. This identifies which segments generate the most traffic and catches anomalies like a user VLAN IP appearing in server-subnet logs. The bitwise function in the code examples section of this guide serves as the core of such scripts: wrap it in a function, pipe your IP list through it, and export the results to CSV for reporting.
Generating subnet reports for IPAM with PowerShell
PowerShell scripts that enumerate every subnet in a CIDR allocation and output structured data (network address, broadcast, usable range, host count) feed directly into IPAM spreadsheets or databases. A script reads a list of allocated CIDRs from a JSON or CSV file, runs each through the Get-SubnetInfo function shown in the code examples, and exports the results with Export-Csv. Schedule this script to run weekly and diff the output against the previous week's report to catch unauthorized subnet allocations.
Building on this, integrating the script with Azure Automation or AWS Systems Manager Run Command lets you generate subnet utilization reports across all VPCs in an account without logging into each one.3 The script queries the AWS EC2 DescribeSubnets or Azure AzVirtualNetwork API, extracts the CIDR for each subnet, calculates the usable range, and compares it against actual instance counts from the API.4 Consequently, you get a utilization percentage per subnet that identifies candidates for downsizing (a /24 with 3 instances) and candidates for expansion (a /28 at 90% capacity).
Notes
# IPv4 subnet calculation in PowerShell (no modules required) function Get-SubnetInfo { param([string]$CIDR) $parts = $CIDR -split '/' $addr = $parts[0] $prefix = [int]$parts[1] $octets = $addr -split '\.' | ForEach-Object { [int]$_ } $ip = [Int64](($octets[0] -shl 24) -bor ($octets[1] -shl 16) -bor ($octets[2] -shl 8) -bor $octets[3]) $mask = if ($prefix -eq 0) { 0L } else { [Int64]([System.Convert]::ToInt64(('1' * $prefix).PadRight(32,'0'), 2)) } $net = $ip -band $mask $bcast = ($net -bor (-bnot $mask -band 0xFFFFFFFF)) -band 0xFFFFFFFF $toIP = { param($n) "$(($n -shr 24) -band 255).$(($n -shr 16) -band 255).$(($n -shr 8) -band 255).$($n -band 255)" } [pscustomobject]@{ Network = & $toIP $net Broadcast = & $toIP $bcast FirstHost = & $toIP ($net + 1) LastHost = & $toIP ($bcast - 1) UsableHosts = [Math]::Pow(2, 32 - $prefix) - 2 } } Get-SubnetInfo '10.0.0.0/24'
Examples
Manual subnet info function
Get-SubnetInfo -CIDR '172.16.0.0/20' # Network: 172.16.0.0 # Broadcast: 172.16.15.255 # FirstHost: 172.16.0.1 # LastHost: 172.16.15.254 # UsableHosts: 4094
.NET 8 IPNetwork (PS 7.4+)
$net = [System.Net.IPNetwork]::Parse('192.168.1.0/26')
$net.BaseAddress # 192.168.1.0
$net.PrefixLength # 26
$net.Contains([System.Net.IPAddress]::Parse('192.168.1.50')) # True Verify with the IPv4 / IPv6 Subnet & CIDR Calculator tool.
Manual subnet info function
Get-SubnetInfo -CIDR '172.16.0.0/20' # Network: 172.16.0.0 # Broadcast: 172.16.15.255 # FirstHost: 172.16.0.1 # LastHost: 172.16.15.254 # UsableHosts: 4094
- 1.
Microsoft Learn, "IPNetwork Struct," learn.microsoft.com, accessed June 2026. https://learn.microsoft.com/en-us/dotnet/api/system.net.ipnetwork?view=net-8.0
- 2.
Microsoft Learn, "about_Arithmetic_Operators," learn.microsoft.com, accessed June 2026. https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_arithmetic_operators?view=powershell-7.6
- 3.
Amazon Web Services, "Use the AWS Tools for Windows PowerShell with Run Command," docs.aws.amazon.com, accessed June 2026. https://docs.aws.amazon.com/systems-manager/latest/userguide/walkthrough-powershell.html
- 4.
Amazon Web Services, "DescribeSubnets," docs.aws.amazon.com, accessed June 2026. https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeSubnets.html
PowerShell 7.4 and later on .NET 8 include the built-in [System.Net.IPNetwork] type for parsing and working with CIDR ranges, so no separate subnet calculator module is required. CapyToolkit takes a similar built-in-first approach by relying on JavaScript's native bitwise operators for its subnet calculations. Windows PowerShell 5.1 does not include the IPNetwork type, so use the manual bitwise function shown in the code examples above.
PowerShell's [int] is a signed 32-bit integer. IP addresses with the top bit set (128.x.x.x and above) overflow into negative values during bit operations. [Int64] uses 64-bit arithmetic, preventing overflow. Mask the result to 32 bits with -band 0xFFFFFFFF before converting back to dotted decimal.
On PowerShell 7.4+, use [System.Net.IPNetwork]::Parse($cidr).Contains($ip), which is the direct equivalent of the containment check CapyToolkit performs when you test whether an address falls inside a CIDR block. On older versions, compare the IP integer ANDed with the mask against the network integer: ($ipInt -band $maskInt) -eq $netInt.
Yes. Azure Automation runbooks support PowerShell 7.2 and later. The manual bitwise function in this guide runs without module dependencies, making it portable to Azure Automation without installing custom modules.
Iterate from ($networkInt + 1) to ($broadcastInt - 1), converting each integer to dotted decimal. For large subnets, use Select-Object -First N to limit output. A /24 contains 254 host IPs while a /16 contains 65,534, so only iterate what you need.
Subnet Calculations in Go
Go's standard library provides two complementary packages for subnet math: net (available since Go 1) and netip (added in Go 1.18). The netip package is the modern choice because its types are comparable, zero-allocation, and work correctly with both IPv4 and IPv6.1
net and netip package overview
The net package's net.ParseCIDR() function parses a CIDR string into a net.IP (the host address) and *net.IPNet (the network). net.IPNet provides Contains() for membership checks and a String() method for formatted output. Yet net.IP is a byte slice that is not directly comparable with ==, which makes it awkward in maps and sets. Building on this, the netip package (Go 1.18+) introduces netip.Addr and netip.Prefix as immutable, comparable value types.2 netip.ParsePrefix() parses CIDR strings; prefix.Masked() returns the network address; prefix.Contains() checks membership. Consequently, netip is the recommended choice for new Go code where Go 1.18 or later is available. Migrating existing code from net.IP to netip.Addr typically means replacing pointer-based comparisons with direct equality checks, which simplifies set membership logic and removes a class of subtle bugs in concurrent map access.
Parsing and validating CIDR input in Go
Parsing user-provided CIDR strings in Go uses netip.ParsePrefix() with an error return. The returned error is non-nil for invalid input including out-of-range octets, invalid prefix lengths, and non-CIDR strings, so callers can distinguish malformed input from valid but host-bit-bearing addresses with a single if-check. Building on this, prefix.Masked() normalizes addresses that have host bits set, so ParsePrefix("192.168.1.5/24").Masked() returns 192.168.1.0/24.2 For strict validation that rejects host bits, compare the original prefix with its Masked() form and reject the input when the two differ.
Normalizing host-bit addresses before storage
Normalizing addresses at the validation boundary prevents host-bit drift later in the pipeline. A function that accepts user input, calls Masked(), and stores only the normalized prefix ensures downstream allocation logic never encounters unexpected host bits. Furthermore, the Addr().Is4() and Addr().Is6() methods distinguish IPv4 from IPv6 when handling both families in the same function. CapyToolkit applies the same normalization principle when it trims host bits from a CIDR before displaying the network address, so the value you paste into a Go program matches what the calculator shows.
Automation patterns in Go
Go subnet code appears in Kubernetes controllers, network proxies, and cloud CLI tools. A controller that assigns CIDRs to nodes reads the cluster pod CIDR, splits it into per-node /24 blocks using the Prefix.Addr() and Prefix.Bits() fields, and stores allocations in a map keyed by node name. Building on this, the same pattern generates IPAM entries for Terraform external data sources by writing JSON to stdout from a Go binary that reads CIDR config from a file.
Iterating every address in a subnet
For network scanning tools, iterating all addresses in a subnet uses a loop from prefix.Addr() incrementing via addr.Next() until Next() returns an address outside the prefix, which is the standard approach for generating host IPs without third-party libraries.3 This pattern works for any prefix length and allocates no intermediate slices, so it stays efficient even when the subnet spans several thousand addresses.
Allocating CIDRs from a parent block in Go
Splitting a parent CIDR into smaller subnets is a common task in IPAM tools and Kubernetes controllers. The netip package does not provide a built-in subnets() method like Python, but the pattern is straightforward: extract the prefix length, increment it by the number of additional bits, then loop through the address space using addr.Next(). Building on this, a function that takes a netip.Prefix and a target prefix length and returns a slice of child prefixes covers the most common allocation pattern. On each iteration, assign the current address as the network (using netip.PrefixFrom(addr, targetLen)), advance to the next subnet boundary, and continue until the parent prefix no longer contains the current address. CapyToolkit performs this same parent-block splitting when calculating subnet ranges within a larger CIDR.
Building CLIs with Cobra and netip
Go CLI tools for network automation typically use the Cobra library for command structure and netip for address math. A command like subnet-cli info 192.168.1.0/26 that prints network, broadcast, first host, last host, and usable count gives network engineers a fast alternative to web-based calculators in scripting contexts. Building on this, a second command subnet-cli split 10.0.0.0/24 --prefix 26 outputs four /26 blocks, ready to paste into a cloud console. Cobra's flag parsing, combined with Go's strong typing, catches invalid prefix inputs at the argument level rather than during address parsing. The result is a self-contained binary with no runtime dependencies: no Python interpreter, no npm, no external packages.4
Netip performance characteristics for high-throughput tools
The netip package's performance characteristics matter for network tools that process millions of addresses. netip.Prefix and netip.Addr are small value types (32 bytes and 16 bytes respectively) that live on the stack, avoiding heap allocation in tight loops.3 This contrasts with older Go IP libraries that return pointers and trigger garbage collection pressure. Building on this, benchmarking netip.ParsePrefix() shows sub-microsecond parse times, making it suitable for packet processing pipelines that inspect source and destination addresses against CIDR-based ACLs. Programs that replace map[string]bool sets with map[netip.Prefix]bool benefit from the comparable-typed keys: lookups are hash-based and allocation-free.
Comparing netip to raw bitwise operations
Raw bitwise operations on uint32 values are faster than netip for the specific case of IPv4 network/broadcast calculation, but they sacrifice correctness and readability. netip handles both address families, validates input, and works with prefix types that prevent off-by-one errors in allocation loops. For network engineering tools where correctness matters more than microsecond performance, netip's safety guarantees are worth the small overhead. The standard library's net package remains necessary for socket-level operations like net.Dial() and net.Listen(), which do not accept netip types.5
Both netip.Addr and netip.Prefix implement Compare() methods for use with Go's sort package, letting a slice of allocated subnets sort predictably by validity, address family, then prefix length without any custom comparator. Building the same ordering with raw uint32 values means writing that comparator by hand, and getting IPv4-before-IPv6 ordering right when mixing address families requires additional bookkeeping that netip handles internally.
Notes
package main
import ( "fmt" "net/netip" )
func subnetInfo(cidr string) { prefix, err := netip.ParsePrefix(cidr) if err != nil { fmt.Println("invalid CIDR:", err) return } masked := prefix.Masked() network := masked.Addr() bits := prefix.Bits() fmt.Printf("Network: %s\n", network) fmt.Printf("Prefix: /%d\n", bits) fmt.Printf("Host bits: %d\n", prefix.Addr().BitLen()-bits) }
func main() { subnetInfo("192.168.1.0/26") }
Examples
Parse and inspect a prefix
prefix, _ := netip.ParsePrefix("10.0.0.0/22")
masked := prefix.Masked()
fmt.Println(masked.Addr()) // 10.0.0.0
fmt.Println(masked.Bits()) // 22
fmt.Println(prefix.Contains(netip.MustParseAddr("10.0.2.100"))) // true Iterate host addresses in a /29
p, _ := netip.ParsePrefix("10.0.0.0/29")
for addr := p.Addr().Next(); p.Contains(addr); addr = addr.Next() {
fmt.Println(addr) // 10.0.0.1 through 10.0.0.6
} Normalize a host-bit CIDR
parsed, _ := netip.ParsePrefix("192.168.1.5/24")
network := parsed.Masked()
fmt.Println(network) // 192.168.1.0/24 Verify with the IPv4 / IPv6 Subnet & CIDR Calculator tool.
Parse and inspect a prefix
prefix, _ := netip.ParsePrefix("10.0.0.0/22")
masked := prefix.Masked()
fmt.Println(masked.Addr()) // 10.0.0.0
fmt.Println(masked.Bits()) // 22
fmt.Println(prefix.Contains(netip.MustParseAddr("10.0.2.100"))) // true - 1.
Go Authors, "Go 1.18 Release Notes," go.dev, March 2022. https://go.dev/doc/go1.18
- 2.
Go Authors, "net/netip," pkg.go.dev, accessed June 2026. https://pkg.go.dev/net/netip
- 3.
Go Authors, "net/netip: add new IP address package," github.com, August 2021. https://github.com/golang/go/issues/46518
- 4.
spf13/cobra, "Cobra: A Modern CLI Library for Go," github.com, accessed June 2026. https://github.com/spf13/cobra
- 5.
Go Authors, "net," pkg.go.dev, accessed June 2026. https://pkg.go.dev/net
Use netip for new code. Its Addr and Prefix types are immutable, comparable, and allocation-free, which makes them better for maps, sets, and high-throughput network code. CapyToolkit uses the same allocation-free design philosophy for its in-browser subnet calculations, so results appear without server round trips. The net package is still required for interoperability with net.Conn, net.Listener, and other I/O interfaces.
Use netip.ParsePrefix(cidr) for the modern approach. It returns (netip.Prefix, error). Call .Masked() on the result to normalize host bits. For legacy code using the net package, net.ParseCIDR(cidr) returns (net.IP, *net.IPNet, error).
With netip: prefix.Contains(addr) returns a bool. With net: ipNet.Contains(ip) does the same. The netip version avoids allocation and works correctly for both IPv4 and IPv6 without checking the address family.
Start from prefix.Masked().Addr().Next() (first host) and call addr.Next() each iteration. Stop when the next address falls outside the prefix. This approach works for any prefix length without precomputing the host count.
The same netip package handles both IPv4 and IPv6 transparently. netip.ParsePrefix("2001:db8::/32") returns a Prefix with Bits()=32 and Addr().Is6()=true. Iteration and containment checks use the same API as IPv4. CapyToolkit also treats IPv4 and IPv6 identically in its subnet calculator, so the same workflow covers both address families without switching tools.