DevToolBoxGRATIS
Blog

Calculadora de Subredes IP: Notación CIDR & Subnetting — Guía Completa

13 min de lecturapor DevToolBox

TL;DR

A subnet divides an IP network into smaller segments. CIDR notation like 192.168.1.0/24 means 24 bits for the network, 8 bits for hosts = 254 usable addresses. Calculate usable hosts with 2^(32 - prefix) - 2. Use VLSM to allocate different-sized subnets for different needs. AWS reserves 5 IPs per subnet (not 2). Docker uses 172.17.0.0/16, Kubernetes pods use 10.244.0.0/16. Try our online IP subnet calculator to compute any CIDR block instantly.

CIDR Notation Basics — IP Classes, Subnet Masks, and Address Structure

CIDR (Classless Inter-Domain Routing) is the modern standard for IP address allocation and routing. Before CIDR, IP addresses were divided into fixed classes — Class A, B, and C — which caused massive waste of address space. CIDR allows any prefix length from /0 to /32, enabling precise allocation.

The Original IP Classes (Pre-CIDR)

ClassFirst OctetCIDR Equiv.Default MaskMax Hosts
A1–126/8255.0.0.016,777,214
B128–191/16255.255.0.065,534
C192–223/24255.255.255.0254
D224–239Multicast only
E240–255Reserved/Experimental

How CIDR Notation Works — Bit-Level Breakdown

A subnet mask is a 32-bit number where network bits are 1 and host bits are 0. The prefix length in CIDR notation is simply the count of consecutive 1 bits from the left:

# 192.168.1.0/24 — breakdown:
IP Address:  192.168.1.0   = 11000000.10101000.00000001.00000000
Subnet Mask: 255.255.255.0 = 11111111.11111111.11111111.00000000
                              |<--- 24 network bits --->|<8 host->|

Network Address  = IP AND Mask = 192.168.1.0   (all host bits = 0)
Broadcast Address              = 192.168.1.255  (all host bits = 1)
First Usable Host              = 192.168.1.1
Last Usable Host               = 192.168.1.254
Usable Hosts                   = 2^8 - 2 = 254

# 10.0.0.0/8 — breakdown:
IP Address:  10.0.0.0      = 00001010.00000000.00000000.00000000
Subnet Mask: 255.0.0.0     = 11111111.00000000.00000000.00000000
                              |< 8 >|  |<------- 24 host bits ------->|
Usable Hosts = 2^24 - 2 = 16,777,214

# Converting prefix to mask in Python:
import ipaddress
net = ipaddress.IPv4Network('192.168.1.0/24')
print(net.netmask)         # 255.255.255.0
print(net.hostmask)        # 0.0.0.255
print(net.num_addresses)   # 256
print(net.network_address) # 192.168.1.0
print(net.broadcast_address) # 192.168.1.255

JavaScript IP Subnet Calculator — Pure JS Implementation

Here is a complete, dependency-free JavaScript function that parses any CIDR block and returns all subnet details: network address, broadcast address, first/last usable host, host count, subnet mask, and wildcard mask.

/**
 * Parse a CIDR string and compute subnet details.
 * @param {string} cidr - e.g. "192.168.1.0/24" or "10.0.0.5/16"
 * @returns {object} Subnet calculation results
 */
function calculateSubnet(cidr) {
  const [ipStr, prefixStr] = cidr.split('/');
  const prefix = parseInt(prefixStr, 10);

  if (prefix < 0 || prefix > 32) {
    throw new Error('Prefix length must be between 0 and 32');
  }

  // Convert IP string to a 32-bit integer
  const ipToInt = (ip) =>
    ip.split('.').reduce((acc, octet) => (acc << 8) | parseInt(octet, 10), 0) >>> 0;

  // Convert 32-bit integer back to dotted-decimal string
  const intToIp = (n) =>
    [(n >>> 24) & 255, (n >>> 16) & 255, (n >>> 8) & 255, n & 255].join('.');

  const ipInt = ipToInt(ipStr);

  // Build mask: prefix 1-bits followed by (32-prefix) 0-bits
  const maskInt = prefix === 0 ? 0 : (0xFFFFFFFF << (32 - prefix)) >>> 0;
  const wildcardInt = (~maskInt) >>> 0;

  const networkInt = (ipInt & maskInt) >>> 0;
  const broadcastInt = (networkInt | wildcardInt) >>> 0;

  const totalAddresses = Math.pow(2, 32 - prefix);
  const usableHosts = prefix >= 31 ? totalAddresses : totalAddresses - 2;

  return {
    cidr,
    ipAddress:        ipStr,
    prefix,
    subnetMask:       intToIp(maskInt),
    wildcardMask:     intToIp(wildcardInt),
    networkAddress:   intToIp(networkInt),
    broadcastAddress: intToIp(broadcastInt),
    firstUsableHost:  prefix >= 31 ? intToIp(networkInt) : intToIp(networkInt + 1),
    lastUsableHost:   prefix >= 31 ? intToIp(broadcastInt) : intToIp(broadcastInt - 1),
    totalAddresses,
    usableHosts,
    ipClass: (() => {
      const firstOctet = (networkInt >>> 24) & 255;
      if (firstOctet < 128) return 'A';
      if (firstOctet < 192) return 'B';
      if (firstOctet < 224) return 'C';
      if (firstOctet < 240) return 'D (Multicast)';
      return 'E (Reserved)';
    })(),
    isPrivate: (() => {
      const a = (networkInt >>> 24) & 255;
      const b = (networkInt >>> 16) & 255;
      return (a === 10) ||
             (a === 172 && b >= 16 && b <= 31) ||
             (a === 192 && b === 168) ||
             (a === 127);
    })(),
  };
}

// Example usage:
const result = calculateSubnet('192.168.1.0/24');
console.log(result);
/* Output:
{
  cidr: '192.168.1.0/24',
  ipAddress: '192.168.1.0',
  prefix: 24,
  subnetMask: '255.255.255.0',
  wildcardMask: '0.0.0.255',
  networkAddress: '192.168.1.0',
  broadcastAddress: '192.168.1.255',
  firstUsableHost: '192.168.1.1',
  lastUsableHost: '192.168.1.254',
  totalAddresses: 256,
  usableHosts: 254,
  ipClass: 'C',
  isPrivate: true
}
*/

// Check if an IP is within a subnet
function isIpInSubnet(ip, cidr) {
  const { networkAddress, broadcastAddress } = calculateSubnet(cidr);
  const ipToInt = (s) => s.split('.').reduce((a, o) => (a << 8) | +o, 0) >>> 0;
  const ipInt = ipToInt(ip);
  return ipInt >= ipToInt(networkAddress) && ipInt <= ipToInt(broadcastAddress);
}

console.log(isIpInSubnet('192.168.1.100', '192.168.1.0/24')); // true
console.log(isIpInSubnet('192.168.2.1',   '192.168.1.0/24')); // false

// Split a subnet into smaller subnets
function splitSubnet(cidr, newPrefix) {
  const { networkAddress, prefix } = calculateSubnet(cidr);
  if (newPrefix <= prefix) throw new Error('New prefix must be larger than original');
  const count = Math.pow(2, newPrefix - prefix);
  const blockSize = Math.pow(2, 32 - newPrefix);

  const ipToInt = (s) => s.split('.').reduce((a, o) => (a << 8) | +o, 0) >>> 0;
  const intToIp = (n) => [(n>>>24)&255,(n>>>16)&255,(n>>>8)&255,n&255].join('.');

  const baseInt = ipToInt(networkAddress);
  return Array.from({ length: count }, (_, i) =>
    `${intToIp((baseInt + i * blockSize) >>> 0)}/${newPrefix}`
  );
}

console.log(splitSubnet('192.168.1.0/24', 26));
// ['192.168.1.0/26', '192.168.1.64/26', '192.168.1.128/26', '192.168.1.192/26']

Python ipaddress Module — Complete Subnet Calculation

Python's built-in ipaddress module (Python 3.3+) provides comprehensive IP networking capabilities with no external dependencies. It handles both IPv4 and IPv6 natively.

import ipaddress

# ── IPv4 Network ──────────────────────────────────────────────────────────────
# strict=False allows host bits to be set (e.g., 192.168.1.5/24 → 192.168.1.0/24)
net = ipaddress.IPv4Network('192.168.1.0/24', strict=True)

print(net.network_address)   # 192.168.1.0
print(net.broadcast_address) # 192.168.1.255
print(net.netmask)           # 255.255.255.0
print(net.hostmask)          # 0.0.0.255
print(net.prefixlen)         # 24
print(net.num_addresses)     # 256
print(net.max_prefixlen)     # 32

# Usable hosts (excludes network + broadcast)
hosts = list(net.hosts())
print(f"First host: {hosts[0]}")   # 192.168.1.1
print(f"Last host:  {hosts[-1]}")  # 192.168.1.254
print(f"Host count: {len(hosts)}") # 254

# Network properties
print(net.is_private)   # True  (RFC 1918)
print(net.is_global)    # False
print(net.is_loopback)  # False
print(net.is_multicast) # False

# ── IPv4 Interface (IP + network context) ────────────────────────────────────
iface = ipaddress.IPv4Interface('192.168.1.5/24')
print(iface.ip)          # 192.168.1.5  (the specific host IP)
print(iface.network)     # 192.168.1.0/24 (the network it belongs to)
print(iface.netmask)     # 255.255.255.0

# ── Subnets — splitting into smaller blocks ───────────────────────────────────
net = ipaddress.IPv4Network('10.0.0.0/16')

# Split /16 into /18 subnets (4 subnets)
subnets = list(net.subnets(new_prefix=18))
for s in subnets:
    print(s)
# 10.0.0.0/18
# 10.0.64.0/18
# 10.0.128.0/18
# 10.0.192.0/18

# Split by specifying number of additional bits (prefixlen_diff)
# Split /24 into /26 (2 additional bits = 4 subnets)
net24 = ipaddress.IPv4Network('192.168.1.0/24')
for sub in net24.subnets(prefixlen_diff=2):
    hosts = net24.num_addresses // 4 - 2
    print(f"{sub}  →  {list(sub.hosts())[0]} – {list(sub.hosts())[-1]}  ({hosts} hosts)")
# 192.168.1.0/26  →  192.168.1.1 – 192.168.1.62   (62 hosts)
# 192.168.1.64/26 →  192.168.1.65 – 192.168.1.126  (62 hosts)
# 192.168.1.128/26→  192.168.1.129 – 192.168.1.190 (62 hosts)
# 192.168.1.192/26→  192.168.1.193 – 192.168.1.254 (62 hosts)

# ── Supernet — aggregating into a larger block ────────────────────────────────
small = ipaddress.IPv4Network('192.168.1.0/25')
print(small.supernet())   # 192.168.1.0/24
print(small.supernet(new_prefix=22))  # 192.168.0.0/22

# ── Containment check ────────────────────────────────────────────────────────
net = ipaddress.IPv4Network('10.0.0.0/8')
ip = ipaddress.IPv4Address('10.5.6.7')
print(ip in net)  # True

# ── Summarize a range into CIDR blocks (reverse lookup) ─────────────────────
first = ipaddress.IPv4Address('192.168.1.0')
last  = ipaddress.IPv4Address('192.168.1.255')
summary = list(ipaddress.summarize_address_range(first, last))
print(summary)  # [IPv4Network('192.168.1.0/24')]

# Collapse overlapping networks
networks = [
    ipaddress.IPv4Network('10.0.0.0/24'),
    ipaddress.IPv4Network('10.0.1.0/24'),
    ipaddress.IPv4Network('10.0.0.0/23'),  # overlaps with both above
]
collapsed = list(ipaddress.collapse_addresses(networks))
print(collapsed)  # [IPv4Network('10.0.0.0/23')]

Subnet Reference Table — /8 to /32 with Host Counts and Masks

The table below covers every practically significant prefix length, from large backbone blocks to single-host routes. Bookmark this as a quick reference when designing network architectures.

PrefixSubnet MaskTotal IPsUsable HostsCommon Use
/8255.0.0.016,777,21616,777,214Large ISP / Class A private (10.0.0.0/8)
/12255.240.0.01,048,5761,048,574RFC 1918 172.16.0.0/12 private range
/16255.255.0.065,53665,534Large org LAN / AWS VPC typical size
/20255.255.240.04,0964,094Medium data center segment
/21255.255.248.02,0482,046Campus network block
/22255.255.252.01,0241,022Large office LAN
/23255.255.254.0512510Medium office / Kubernetes node block
/24255.255.255.0256254Standard LAN / AWS subnet typical
/25255.255.255.128128126Half of /24, split LAN
/26255.255.255.1926462Small department LAN
/27255.255.255.2243230Small office segment
/28255.255.255.2401614AWS minimum subnet size
/29255.255.255.24886Small cluster / management network
/30255.255.255.25242Point-to-point link (router↔router)
/31255.255.255.25422P2P link, no network/broadcast (RFC 3021)
/32255.255.255.25511Single host route / loopback

IPv6 Subnetting — /48, /64, /128, and Prefix Length

IPv6 uses 128-bit addresses written as eight groups of four hexadecimal digits separated by colons, such as 2001:0db8:85a3:0000:0000:8a2e:0370:7334. Consecutive groups of zeros can be replaced with :: once per address. The standard allocation hierarchy is:

  • /32 — ISP allocation from Regional Internet Registry (RIR)
  • /48 — Typical allocation to a single organization or site
  • /56 — Sometimes given to home users by ISPs
  • /64 — A single LAN segment; required for SLAAC (Stateless Address Autoconfiguration)
  • /128 — A single host (like /32 in IPv4)
import ipaddress

# ── IPv6 Network ──────────────────────────────────────────────────────────────
net6 = ipaddress.IPv6Network('2001:db8::/32')
print(net6.network_address)   # 2001:db8::
print(net6.prefixlen)         # 32
print(net6.num_addresses)     # 79228162514264337593543950336 (2^96)

# A /48 — typical organization block
org = ipaddress.IPv6Network('2001:db8:abcd::/48')
print(f"Subnets available (/64): {2**(64-48):,}")  # 65,536 LANs

# Split /48 into /64 subnets (each LAN segment)
subnets_64 = list(org.subnets(new_prefix=64))
print(f"First LAN: {subnets_64[0]}")   # 2001:db8:abcd::/64
print(f"Second LAN: {subnets_64[1]}")  # 2001:db8:abcd:1::/64
print(f"Total /64 subnets: {len(subnets_64)}")  # 65536

# A /64 LAN segment — how many addresses?
lan = ipaddress.IPv6Network('2001:db8:abcd:1::/64')
print(f"Addresses in /64: {lan.num_addresses:,}")
# 18,446,744,073,709,551,616 (2^64)

# Special IPv6 addresses
loopback  = ipaddress.IPv6Address('::1')
link_local = ipaddress.IPv6Network('fe80::/10')
ula        = ipaddress.IPv6Network('fc00::/7')  # Unique Local (private)

print(loopback.is_loopback)               # True
print(ipaddress.IPv6Address('fe80::1').is_link_local)  # True
print(ipaddress.IPv6Address('fd00::1') in ula)         # True

# IPv6 address types
addr = ipaddress.IPv6Address('2001:db8::1')
print(addr.is_global)       # True (public routable)
print(addr.is_private)      # False
print(addr.is_multicast)    # False
print(addr.is_unspecified)  # False (:: is unspecified)

# EUI-64: IPv6 auto-configuration from MAC address
# Given MAC: 00:1A:2B:3C:4D:5E
# 1. Split at middle: 00:1A:2B | 3C:4D:5E
# 2. Insert FF:FE: 00:1A:2B:FF:FE:3C:4D:5E
# 3. Flip 7th bit of first byte: 02:1A:2B:FF:FE:3C:4D:5E
# 4. Combine with prefix fe80::/64:
# Result: fe80::021a:2bff:fe3c:4d5e

def mac_to_eui64(mac, prefix='fe80::'):
    parts = mac.lower().replace('-', ':').split(':')
    parts[0] = format(int(parts[0], 16) ^ 0x02, '02x')  # flip bit 7
    eui64 = parts[:3] + ['ff', 'fe'] + parts[3:]
    groups = [''.join(eui64[i:i+2]) for i in range(0, 8, 2)]
    return f"{prefix}{':'.join(groups)}"

print(mac_to_eui64('00:1A:2B:3C:4D:5E'))
# fe80::021a:2bff:fe3c:4d5e

VLSM — Split 192.168.1.0/24 into Multiple Subnets of Different Sizes

Variable Length Subnet Masking (VLSM) lets you carve a single IP block into subnets of different sizes to avoid wasting addresses. The key rule: always allocate the largest subnet first, work down in size.

Scenario: You have 192.168.1.0/24 (254 usable hosts) and need to create:

  • Department A: 100 hosts needed
  • Department B: 50 hosts needed
  • Department C: 25 hosts needed
  • WAN Link 1: 2 hosts (router point-to-point)
  • WAN Link 2: 2 hosts (router point-to-point)
# VLSM Allocation for 192.168.1.0/24
# Rule: sort requirements largest-first, allocate sequentially

# Step 1 — Dept A needs 100 hosts → smallest subnet that fits ≥ 102 total
# 2^7 = 128 addresses → /25 (126 usable hosts) ✓
Dept A: 192.168.1.0/25
  Network:    192.168.1.0
  Broadcast:  192.168.1.127
  Usable:     192.168.1.1 – 192.168.1.126  (126 hosts)
  Remaining:  192.168.1.128/25

# Step 2 — Dept B needs 50 hosts → /26 (62 usable hosts) ✓
Dept B: 192.168.1.128/26
  Network:    192.168.1.128
  Broadcast:  192.168.1.191
  Usable:     192.168.1.129 – 192.168.1.190  (62 hosts)
  Remaining:  192.168.1.192/26

# Step 3 — Dept C needs 25 hosts → /27 (30 usable hosts) ✓
Dept C: 192.168.1.192/27
  Network:    192.168.1.192
  Broadcast:  192.168.1.223
  Usable:     192.168.1.193 – 192.168.1.222  (30 hosts)
  Remaining:  192.168.1.224/27

# Step 4 — WAN Link 1 needs 2 hosts → /30 (2 usable hosts) ✓
WAN1: 192.168.1.224/30
  Network:    192.168.1.224
  Broadcast:  192.168.1.227
  Usable:     192.168.1.225 – 192.168.1.226  (2 hosts)
  Remaining:  192.168.1.228/30 + 192.168.1.232/29 + ...

# Step 5 — WAN Link 2 needs 2 hosts → /30
WAN2: 192.168.1.228/30
  Network:    192.168.1.228
  Broadcast:  192.168.1.231
  Usable:     192.168.1.229 – 192.168.1.230  (2 hosts)
  Remaining:  192.168.1.232/29 (unused, available for future)

# ── Python VLSM allocator ──────────────────────────────────────────────────
import ipaddress

def vlsm_allocate(base_cidr, requirements):
    """
    Allocate subnets using VLSM.
    requirements: list of (name, min_hosts) sorted largest-first
    """
    pool = ipaddress.IPv4Network(base_cidr, strict=False)
    available = [pool]
    allocations = []

    for name, min_hosts in sorted(requirements, key=lambda x: x[1], reverse=True):
        # Find the smallest prefix that provides enough hosts
        needed_prefix = 32
        for prefix in range(30, -1, -1):
            if (2 ** (32 - prefix) - 2) >= min_hosts:
                needed_prefix = prefix
            else:
                break

        # Find first available block that can fit this prefix
        for i, block in enumerate(available):
            if block.prefixlen <= needed_prefix:
                # Carve the subnet from this block
                subnet = list(block.subnets(new_prefix=needed_prefix))[0]
                allocations.append((name, subnet, min_hosts))
                available.pop(i)
                # Return remaining space to pool
                remaining = list(block.address_exclude(subnet))
                available = remaining + available[i:]
                break

    return allocations

reqs = [
    ('Dept A', 100),
    ('Dept B', 50),
    ('Dept C', 25),
    ('WAN 1', 2),
    ('WAN 2', 2),
]

results = vlsm_allocate('192.168.1.0/24', reqs)
for name, subnet, needed in results:
    hosts = list(subnet.hosts())
    print(f"{name:8} → {str(subnet):20} usable: {len(hosts):3}  needed: {needed}")

# Dept A   → 192.168.1.0/25        usable: 126  needed: 100
# Dept B   → 192.168.1.128/26      usable:  62  needed:  50
# Dept C   → 192.168.1.192/27      usable:  30  needed:  25
# WAN 1    → 192.168.1.224/30      usable:   2  needed:   2
# WAN 2    → 192.168.1.228/30      usable:   2  needed:   2

Node.js ip-cidr Library — npm Install and Usage

The ip-cidr npm package provides a clean API for working with CIDR blocks in Node.js applications. It supports both IPv4 and IPv6 and is useful for backend validation, firewall rule engines, and IP address management tools.

# Install the package
npm install ip-cidr

# Also useful: the 'cidr-tools' package for set operations
npm install cidr-tools

# ── Basic ip-cidr usage ───────────────────────────────────────────────────────
const IPCIDR = require('ip-cidr');

const cidr = new IPCIDR('192.168.1.0/24');

// Validation
console.log(IPCIDR.isValidCIDR('192.168.1.0/24')); // true
console.log(IPCIDR.isValidCIDR('256.0.0.0/24'));   // false

// Network info
console.log(cidr.start());    // '192.168.1.0'  (network address)
console.log(cidr.end());      // '192.168.1.255' (broadcast)
console.log(cidr.toString()); // '192.168.1.0/24'

// Get specific addresses
const options = { type: 'addressObject' };
console.log(cidr.start(options)); // { address: '192.168.1.0', subnet: '/24' }

// Containment check
const cidr2 = new IPCIDR('10.0.0.0/8');
const isContained = new IPCIDR('10.5.0.0/16');
// Manual containment: compare integer ranges

// Iterate over IPs (be careful with large ranges!)
const smallCidr = new IPCIDR('192.168.1.248/29');
smallCidr.loop((ip) => {
  console.log(ip);
});
// 192.168.1.248
// 192.168.1.249
// ...
// 192.168.1.255

// Get array of all IPs in range
const ips = smallCidr.toArray();
console.log(ips); // ['192.168.1.248', ..., '192.168.1.255']

// Get start/end as integers for comparison
const startInt = smallCidr.start({ type: 'bigInteger' });
const endInt   = smallCidr.end({ type: 'bigInteger' });

// ── cidr-tools: set operations ────────────────────────────────────────────────
const { merge, exclude, contains, overlap, expand } = require('cidr-tools');

// Merge overlapping/adjacent CIDRs
const merged = merge(['10.0.0.0/24', '10.0.1.0/24']);
console.log(merged); // ['10.0.0.0/23']

// Exclude a subnet from a block
const remaining = exclude(['10.0.0.0/24'], ['10.0.0.0/26']);
console.log(remaining); // ['10.0.0.64/26', '10.0.0.128/25']

// Check containment
console.log(contains('10.0.0.0/8', '10.5.6.0/24')); // true
console.log(contains('10.0.0.0/8', '192.168.1.0/24')); // false

// Check overlap between two CIDR blocks
console.log(overlap('10.0.0.0/8', '10.5.0.0/16')); // true
console.log(overlap('10.0.0.0/8', '192.168.1.0/24')); // false

// ── Express.js IP allowlist middleware ─────────────────────────────────────────
const express = require('express');
const app = express();

const ALLOWED_CIDRS = ['10.0.0.0/8', '192.168.0.0/16', '172.16.0.0/12'];

function isPrivateIP(ip) {
  // Remove IPv6 prefix if present (e.g., ::ffff:192.168.1.1)
  const cleanIp = ip.replace(/^::ffff:/, '');
  return ALLOWED_CIDRS.some((cidrStr) => {
    const cidrObj = new IPCIDR(cidrStr);
    // Simplified containment via range check
    return cidrObj.contains ? cidrObj.contains(cleanIp) : false;
  });
}

app.use((req, res, next) => {
  const clientIp = req.ip || req.connection.remoteAddress;
  if (!isPrivateIP(clientIp)) {
    return res.status(403).json({ error: 'Access denied: IP not in allowlist' });
  }
  next();
});

Go net Package — ParseCIDR, Contains, and Subnet Ops

Go's standard library net package provides robust IP and CIDR handling with zero dependencies. It is the foundation for Go-based network tools like Kubernetes, Docker, and Terraform.

package main

import (
    "encoding/binary"
    "fmt"
    "math"
    "net"
)

func main() {
    // ── net.ParseCIDR ───────────────────────────────────────────────────────────
    // Returns the IP address (with host bits) AND the network
    ip, ipNet, err := net.ParseCIDR("192.168.1.42/24")
    if err != nil {
        panic(err)
    }

    fmt.Println(ip)              // 192.168.1.42  (the original IP)
    fmt.Println(ipNet.IP)        // 192.168.1.0   (network address)
    fmt.Println(ipNet.Mask)      // ffffff00       (hex mask)

    ones, bits := ipNet.Mask.Size()
    fmt.Printf("/%d of %d bits
", ones, bits) // /24 of 32 bits

    // Human-readable subnet mask
    mask := net.IP(ipNet.Mask)
    fmt.Println(mask.String()) // 255.255.255.0 (trick: treat mask as IPv4)

    // ── Containment: IPNet.Contains ───────────────────────────────────────────
    _, subnet, _ := net.ParseCIDR("10.0.0.0/8")

    testIPs := []string{"10.5.6.7", "192.168.1.1", "10.255.255.255"}
    for _, testIP := range testIPs {
        fmt.Printf("%s in %s: %v
", testIP, subnet, subnet.Contains(net.ParseIP(testIP)))
    }
    // 10.5.6.7       in 10.0.0.0/8: true
    // 192.168.1.1    in 10.0.0.0/8: false
    // 10.255.255.255 in 10.0.0.0/8: true

    // ── Manual subnet arithmetic ──────────────────────────────────────────────
    _, network, _ := net.ParseCIDR("192.168.1.0/24")

    netIP := network.IP.To4()
    netInt := binary.BigEndian.Uint32(netIP)

    ones2, _ := network.Mask.Size()
    hostBits := 32 - ones2
    totalAddrs := uint32(math.Pow(2, float64(hostBits)))

    networkAddr := netInt
    broadcastAddr := netInt | (totalAddrs - 1)
    firstHost := networkAddr + 1
    lastHost := broadcastAddr - 1
    usableHosts := totalAddrs - 2

    intToIP := func(n uint32) net.IP {
        ip := make(net.IP, 4)
        binary.BigEndian.PutUint32(ip, n)
        return ip
    }

    fmt.Printf("Network:   %s
", intToIP(networkAddr))   // 192.168.1.0
    fmt.Printf("Broadcast: %s
", intToIP(broadcastAddr)) // 192.168.1.255
    fmt.Printf("First:     %s
", intToIP(firstHost))     // 192.168.1.1
    fmt.Printf("Last:      %s
", intToIP(lastHost))      // 192.168.1.254
    fmt.Printf("Hosts:     %d
", usableHosts)            // 254

    // ── Split subnet into smaller blocks ──────────────────────────────────────
    _, base, _ := net.ParseCIDR("10.0.0.0/16")
    splitPrefix := 18 // split into /18 blocks

    baseMaskOnes, _ := base.Mask.Size()
    count := int(math.Pow(2, float64(splitPrefix-baseMaskOnes)))

    blockSize := uint32(math.Pow(2, float64(32-splitPrefix)))
    baseInt := binary.BigEndian.Uint32(base.IP.To4())

    fmt.Printf("
Splitting %s into /%d subnets:
", base, splitPrefix)
    for i := 0; i < count; i++ {
        startInt := baseInt + uint32(i)*blockSize
        subnet := &net.IPNet{
            IP:   intToIP(startInt),
            Mask: net.CIDRMask(splitPrefix, 32),
        }
        fmt.Printf("  %s
", subnet)
    }
    // 10.0.0.0/18
    // 10.0.64.0/18
    // 10.0.128.0/18
    // 10.0.192.0/18

    // ── Check if IP is private (RFC 1918) ────────────────────────────────────
    privateRanges := []*net.IPNet{}
    for _, cidr := range []string{"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "127.0.0.0/8"} {
        _, network, _ := net.ParseCIDR(cidr)
        privateRanges = append(privateRanges, network)
    }

    isPrivate := func(ip net.IP) bool {
        for _, network := range privateRanges {
            if network.Contains(ip) {
                return true
            }
        }
        return false
    }

    fmt.Println(isPrivate(net.ParseIP("192.168.1.1"))) // true
    fmt.Println(isPrivate(net.ParseIP("8.8.8.8")))     // false
}

AWS and Cloud Subnetting — VPC Design and Reserved Addresses

Cloud VPC networking follows the same CIDR principles but with platform-specific constraints. Understanding these is critical when designing production AWS, GCP, or Azure network architectures.

AWS VPC Subnetting Rules

# AWS VPC CIDR constraints:
# - VPC CIDR: /16 to /28 (16 to 65,536 addresses)
# - Subnet CIDR: /16 to /28
# - Subnet must be a subset of VPC CIDR
# - AWS ALWAYS reserves 5 IPs per subnet (not the standard 2)

# Example: VPC 10.0.0.0/16 → subnet 10.0.1.0/24
Reserved addresses in 10.0.1.0/24:
  10.0.1.0   → Network address (standard)
  10.0.1.1   → AWS VPC router (gateway)
  10.0.1.2   → AWS DNS server (always VPC_CIDR_BASE + 2)
  10.0.1.3   → AWS future use (reserved)
  10.0.1.255 → Broadcast address (standard)

Usable for EC2 instances: 10.0.1.4 – 10.0.1.254 = 251 addresses (not 254!)

# For /28 (minimum subnet size): 16 - 5 = 11 usable addresses
# For /24: 256 - 5 = 251 usable addresses

# ── Typical 3-tier AWS VPC architecture (10.0.0.0/16) ─────────────────────────
VPC: 10.0.0.0/16  (65,536 addresses)

# Public subnets (one per AZ) — for ALB, NAT Gateway, Bastion
10.0.1.0/24   (us-east-1a public)  → 251 usable
10.0.2.0/24   (us-east-1b public)  → 251 usable
10.0.3.0/24   (us-east-1c public)  → 251 usable

# Private subnets (one per AZ) — for EC2, ECS, RDS
10.0.11.0/24  (us-east-1a private) → 251 usable
10.0.12.0/24  (us-east-1b private) → 251 usable
10.0.13.0/24  (us-east-1c private) → 251 usable

# Database subnets (one per AZ) — for RDS Multi-AZ
10.0.21.0/24  (us-east-1a db)      → 251 usable
10.0.22.0/24  (us-east-1b db)      → 251 usable
10.0.23.0/24  (us-east-1c db)      → 251 usable

# Reserved for future expansion
10.0.100.0/22 (future use)         → 1,019 usable

# ── AWS CloudFormation CIDR helper function ────────────────────────────────────
# Fn::Cidr splits a CIDR block into smaller subnets
Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16

  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      # Fn::Cidr: [cidr, count, hostBits]
      # Split 10.0.0.0/16 into 256 subnets of /24 (8 host bits), take first
      CidrBlock: !Select [0, !Cidr [!GetAtt VPC.CidrBlock, 256, 8]]
      AvailabilityZone: !Select [0, !GetAZs '']

  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: !Select [10, !Cidr [!GetAtt VPC.CidrBlock, 256, 8]]
      AvailabilityZone: !Select [0, !GetAZs '']

# ── Terraform AWS VPC subnetting ──────────────────────────────────────────────
# main.tf
variable "vpc_cidr" {
  default = "10.0.0.0/16"
}

variable "availability_zones" {
  default = ["us-east-1a", "us-east-1b", "us-east-1c"]
}

resource "aws_vpc" "main" {
  cidr_block           = var.vpc_cidr
  enable_dns_hostnames = true
  enable_dns_support   = true
}

resource "aws_subnet" "public" {
  count             = length(var.availability_zones)
  vpc_id            = aws_vpc.main.id
  # cidrsubnet(prefix, newbits, netnum)
  # cidrsubnet("10.0.0.0/16", 8, count.index) → 10.0.0.0/24, 10.0.1.0/24, ...
  cidr_block        = cidrsubnet(var.vpc_cidr, 8, count.index)
  availability_zone = var.availability_zones[count.index]
  map_public_ip_on_launch = true
}

resource "aws_subnet" "private" {
  count             = length(var.availability_zones)
  vpc_id            = aws_vpc.main.id
  cidr_block        = cidrsubnet(var.vpc_cidr, 8, count.index + 10)
  availability_zone = var.availability_zones[count.index]
}

# ── GCP and Azure equivalents ─────────────────────────────────────────────────
# GCP VPC: /8 to /29 subnets, only 4 reserved IPs (not 5 like AWS)
#   .0 = network, .1 = gateway, .2 = DNS, .255 = broadcast

# Azure VNet: /8 to /29, 5 reserved IPs like AWS:
#   .0 = network, .1 = gateway, .2-.3 = Azure DNS, .255 = broadcast

Common Use Cases — Firewalls, Docker, Kubernetes, and More

Subnetting knowledge applies across every layer of modern infrastructure. Here are the most practical scenarios you'll encounter as a developer or DevOps engineer:

Firewall Rules — CIDR-Based Access Control

# ── iptables (Linux) ─────────────────────────────────────────────────────────
# Allow SSH only from your office IP range (203.0.113.0/24)
iptables -A INPUT -p tcp --dport 22 -s 203.0.113.0/24 -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -j DROP

# Allow all traffic from private networks, block public
iptables -A INPUT -s 10.0.0.0/8     -j ACCEPT
iptables -A INPUT -s 172.16.0.0/12  -j ACCEPT
iptables -A INPUT -s 192.168.0.0/16 -j ACCEPT
iptables -A INPUT -j DROP

# Allow HTTP/HTTPS from anywhere, restrict admin port to VPN
iptables -A INPUT -p tcp --dport 80  -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
iptables -A INPUT -p tcp --dport 8080 -s 10.8.0.0/24 -j ACCEPT  # VPN subnet
iptables -A INPUT -p tcp --dport 8080 -j DROP

# ── nginx — restrict admin endpoints by CIDR ─────────────────────────────────
server {
    listen 443 ssl;
    server_name app.example.com;

    location / {
        proxy_pass http://app:3000;
    }

    location /admin {
        # Only allow from office IP range and VPN
        allow 203.0.113.0/24;  # Office
        allow 10.8.0.0/16;     # VPN
        allow 127.0.0.1;       # Localhost
        deny all;
        proxy_pass http://app:3000;
    }
}

# ── AWS Security Group rules ──────────────────────────────────────────────────
# Terraform: allow MySQL only from app subnet
resource "aws_security_group_rule" "mysql_from_app" {
  type              = "ingress"
  from_port         = 3306
  to_port           = 3306
  protocol          = "tcp"
  cidr_blocks       = ["10.0.11.0/24", "10.0.12.0/24", "10.0.13.0/24"]
  security_group_id = aws_security_group.database.id
}

# ── UFW (Uncomplicated Firewall) ──────────────────────────────────────────────
ufw allow from 192.168.1.0/24 to any port 22   # SSH from LAN
ufw allow from 10.0.0.0/8 to any port 5432      # PostgreSQL from private
ufw deny 22   # Block SSH from everywhere else
ufw enable

Docker Networking — Bridge, Overlay, and Custom Networks

# ── Docker default networks ───────────────────────────────────────────────────
# bridge (docker0): 172.17.0.0/16  — default for single-host containers
# host: shares host network (no isolation)
# none: no networking

# Inspect the default bridge
docker network inspect bridge
# "Subnet": "172.17.0.0/16"
# "Gateway": "172.17.0.1"
# Each container gets an IP from 172.17.0.2 onwards

# ── Create a custom bridge network with specific CIDR ─────────────────────────
docker network create   --driver bridge   --subnet 172.20.0.0/16   --ip-range 172.20.240.0/20   --gateway 172.20.0.1   myapp-network

# Assign a static IP to a container
docker run -d   --name mysql   --network myapp-network   --ip 172.20.240.10   mysql:8.0

docker run -d   --name app   --network myapp-network   --ip 172.20.240.11   myapp:latest

# ── docker-compose.yml: custom subnets ────────────────────────────────────────
version: '3.8'

services:
  app:
    image: myapp:latest
    networks:
      frontend:
        ipv4_address: 172.28.1.10
      backend:
        ipv4_address: 172.29.1.10

  db:
    image: postgres:15
    networks:
      backend:
        ipv4_address: 172.29.1.20

  nginx:
    image: nginx:alpine
    networks:
      frontend:
        ipv4_address: 172.28.1.5

networks:
  frontend:
    driver: bridge
    ipam:
      config:
        - subnet: 172.28.0.0/16
          ip_range: 172.28.1.0/24
          gateway: 172.28.0.1

  backend:
    driver: bridge
    ipam:
      config:
        - subnet: 172.29.0.0/16
          ip_range: 172.29.1.0/24
          gateway: 172.29.0.1

# ── Docker Swarm overlay networks ─────────────────────────────────────────────
# Default overlay for Swarm: 10.0.0.0/24 (ingress)
# Ingress network: 10.0.0.0/24 (load balancing)

docker network create   --driver overlay   --subnet 10.20.0.0/16   --gateway 10.20.0.1   swarm-overlay

# ── Check container IP addresses ──────────────────────────────────────────────
docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' container_name

# List all Docker networks and their subnets
docker network ls --format '{{.Name}}' | while read net; do
  echo -n "$net: "
  docker network inspect "$net" --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}'
done

Kubernetes Pod CIDR and Service CIDR

# ── Kubernetes CIDR ranges ───────────────────────────────────────────────────
# Pod CIDR:       10.244.0.0/16  (Flannel default)
# Service CIDR:   10.96.0.0/12   (ClusterIP services)
# Node gets /24:  10.244.0.0/24, 10.244.1.0/24, ...

# kubeadm init with custom CIDRs
kubeadm init   --pod-network-cidr=10.244.0.0/16   --service-cidr=10.96.0.0/12   --apiserver-advertise-address=192.168.1.10

# ── CNI plugin CIDR defaults ──────────────────────────────────────────────────
# Flannel:  10.244.0.0/16  (each node gets /24 = 254 pods max)
# Calico:   192.168.0.0/16 (configurable, supports BGP)
# Weave:    10.32.0.0/12
# Cilium:   10.0.0.0/8     (highly configurable)

# View pod CIDRs per node
kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"	"}{.spec.podCIDR}{"
"}{end}'
# node-1    10.244.0.0/24
# node-2    10.244.1.0/24
# node-3    10.244.2.0/24

# ── Service types and IPs ─────────────────────────────────────────────────────
# ClusterIP: virtual IP from service CIDR (10.96.0.0/12)
#   - Only reachable within the cluster
#   - kube-proxy programs iptables/IPVS rules

# NodePort: exposes on all nodes at static port (30000-32767)
# LoadBalancer: cloud provider assigns external IP

# Get all services and their ClusterIPs
kubectl get svc --all-namespaces -o wide

# ── NetworkPolicy: restrict pod-to-pod traffic with CIDR ─────────────────────
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: restrict-db-access
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: postgres
  policyTypes:
    - Ingress
  ingress:
    # Only allow from app pods in the same namespace
    - from:
        - podSelector:
            matchLabels:
              app: backend
        - namespaceSelector:
            matchLabels:
              name: production
      ports:
        - port: 5432

    # Also allow from monitoring subnet
    - from:
        - ipBlock:
            cidr: 10.0.20.0/24    # monitoring subnet
            except:
              - 10.0.20.100/32    # exclude specific IP
      ports:
        - port: 9187  # postgres_exporter

# ── Kubernetes IPVS vs iptables ───────────────────────────────────────────────
# iptables mode: O(n) rules, degrades with many services
# IPVS mode: O(1) hash table, better for large clusters

# Enable IPVS in kube-proxy
# In kube-proxy configmap:
mode: "ipvs"
ipvs:
  scheduler: "rr"  # round-robin

# Check current mode
kubectl logs -n kube-system kube-proxy-xxx | grep "Using"
# I0101 Using ipvs Proxier.

Try Our IP Subnet Calculator

Compute network address, broadcast, usable host range, subnet mask, wildcard mask, and more for any CIDR block — instantly, with no installation required.

Open IP Subnet Calculator →

Key Takeaways

  • CIDR notation: IP/prefix where prefix = count of network bits. /24 = 256 total, 254 usable hosts.
  • Host count formula: 2^(32 - prefix) - 2. The -2 removes network address and broadcast address.
  • Network address: all host bits = 0. Broadcast address: all host bits = 1. Neither can be assigned to a host.
  • Private ranges (RFC 1918): 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16. Not routable on the public internet.
  • VLSM: allocate largest subnet first, work down. Avoids wasting addresses by assigning right-sized blocks to each segment.
  • AWS reserves 5 IPs per subnet (not 2): network, router, DNS, future, broadcast. A /28 gives only 11 usable addresses.
  • Docker default: 172.17.0.0/16 for bridge. Use custom subnets in production to avoid conflicts with your VPC.
  • Kubernetes pod CIDR: 10.244.0.0/16 (Flannel). Each node gets a /24 = max 254 pods per node.
  • IPv6 /64: required for SLAAC. One /64 contains 2^64 ≈ 18 quintillion addresses — always use /64 for LAN segments.
  • Python ipaddress and Go net handle all subnet math natively — no external dependencies needed.
𝕏 Twitterin LinkedIn
¿Fue útil?

Mantente actualizado

Recibe consejos de desarrollo y nuevas herramientas.

Sin spam. Cancela cuando quieras.

Prueba estas herramientas relacionadas

🌐IP Subnet Calculator

Artículos relacionados

Decodificador PEM: Decodificar Certificados SSL Online — Guía Completa

Decodifica e inspecciona archivos PEM, certificados SSL y claves privadas. Guía con OpenSSL, Node.js tls, Python cryptography, cadenas de certificados, mTLS y Let's Encrypt.

Tester CORS: Corregir Errores CORS y Configurar Solicitudes Cross-Origin — Guía Completa

Corrija errores CORS y configure solicitudes cross-origin. Guía con cabeceras CORS, solicitudes preflight, Express/Next.js/Nginx/FastAPI, credenciales y seguridad.

HTML Escape/Unescape: Codificar Caracteres Especiales Online — Guía Completa

Escape y unescape de HTML, URL, JSON, SQL y cadenas shell. Guía sobre prevención XSS, entidades HTML, codificación URL, JSON, inyección SQL y regex.