IP addressing fundamentals: IPv4 and IPv6, CIDR and subnet sizing, private, loopback and link-local ranges, NAT, and how they show up in cloud VNets and Kubernetes.
An IP address identifies a network interface so packets can be routed to it.
192.168.1.10. About 4.3 billion addresses, which ran out, so most networks use private ranges behind NAT.2001:db8::1. Enough addresses for everything to be globally routable.10.0.1.0/24 means the first 24 bits are the network, leaving 8 bits (256 addresses) for hosts.10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16. Loopback: 127.0.0.1, ::1.| IPv4 | IPv6 | |
|---|---|---|
| Size | 32 bits | 128 bits |
| Notation | 203.0.113.7 |
2001:db8:85a3::8a2e:370:7334 (zeros compressed with ::) |
| Loopback | 127.0.0.1 |
::1 |
| Private/local | 10/8, 172.16/12, 192.168/16 | fc00::/7 (unique local), fe80::/10 (link-local) |
| NAT | Common | Usually not needed |
| CIDR | Host bits | Addresses | Typical use |
|---|---|---|---|
| /32 | 0 | 1 | A single host in a firewall rule |
| /28 | 4 | 16 | Small subnet (e.g. a gateway subnet) |
| /24 | 8 | 256 | Common subnet size |
| /16 | 16 | 65,536 | A whole VNet / VPC |
| /8 | 24 | 16.7 million | 10.0.0.0/8 private block |
Cloud providers reserve a few addresses per subnet (Azure and AWS reserve 5), so a /24 gives 251 usable IPs.
127.0.0.0/8: loopback, never leaves the machine.169.254.0.0/16: link-local. Cloud metadata services live at 169.254.169.254.100.64.0.0/10: carrier-grade NAT, also used by some Kubernetes and VPN setups.0.0.0.0: "all interfaces" when binding a server; "unknown" otherwise.A team created an AKS cluster with Azure CNI in a /24 subnet. Each pod got a real VNet IP, so with 3 nodes × 30 pods plus nodes and reserved addresses, they ran out of IPs. New pods stuck in ContainerCreating with an IP allocation error. The fix was a larger subnet (/20) and later Azure CNI Overlay, where pods use a separate private CIDR that does not consume VNet addresses. The lesson: plan CIDR ranges for growth, and make sure they do not overlap with on-premises or peered networks.
Checks basic networking literacy that every backend and cloud engineer needs.
Green flags
Red flags
10.0.1.0/24
00001010 . 00000000 . 00000001 . 00000000
└───────── network (24 bits) ─────────┘ └ host (8) ┘
range: 10.0.1.0 – 10.0.1.255
mask: 255.255.255.0
Two hosts are on the same subnet if their network bits match; otherwise traffic goes via a router (the default gateway).
laptop 192.168.1.20:51544 ──► home router ──► 203.0.113.7:40001 ──► server 198.51.100.5:443
(rewrites source, remembers the mapping)
reply 198.51.100.5:443 ──► 203.0.113.7:40001 ──► router looks up mapping ──► 192.168.1.20:51544
Consequences:
localhost may resolve to ::1 first; a server bound only to 127.0.0.1 then refuses the connection.AddressFamily.InterNetworkV6 and DualMode = true accept both.172.17.0.0/16, and containers are NATed out through the host.var ip = IPAddress.Parse("10.0.1.25");
var network = IPNetwork.Parse("10.0.0.0/16"); // System.Net.IPNetwork, .NET 8+
bool inside = network.Contains(ip); // true
bool isLoopback = IPAddress.IsLoopback(ip); // false
ip addr (Linux) or ipconfig (Windows) and identify your private IP, subnet mask and gateway.10.20.0.0/22 (1,024 minus reserved).172.16.0.0/12 with IPNetwork.Contains.// Allow-list check for an internal admin endpoint using CIDR ranges (.NET 8+ System.Net.IPNetwork)
var allowed = new[]
{
IPNetwork.Parse("10.0.0.0/8"),
IPNetwork.Parse("192.168.0.0/16"),
IPNetwork.Parse("fd00::/8")
};
app.MapGet("/admin/health-details", (HttpContext ctx) =>
{
var remote = ctx.Connection.RemoteIpAddress;
if (remote is null) return Results.Forbid();
if (remote.IsIPv4MappedToIPv6) remote = remote.MapToIPv4(); // ::ffff:10.0.0.5 → 10.0.0.5
return allowed.Any(n => n.Contains(remote))
? Results.Ok(GetDetails())
: Results.Forbid();
});
// Behind a proxy, RemoteIpAddress is the proxy's IP unless ForwardedHeaders is configured.
# Useful commands
ip addr show # Linux interfaces and CIDRs
ip route # default gateway and routes
ipconfig /all # Windows
curl -s https://ifconfig.me # public IP as seen from outside (after NAT)
Mistake: Treating the IP the server sees as the user's real address. Why it is wrong: Behind NAT or a proxy it is the NAT gateway or proxy IP, and forwarded headers from untrusted sources can be spoofed. Instead: Read forwarded headers only from known proxies, and never use an IP alone as identity.
Mistake: Choosing tiny subnets for clusters. Why it is wrong: With CNIs that give pods VNet IPs (for example Azure CNI without overlay), a /24 fills up after a few nodes and new pods fail to start. Instead: Size for maximum nodes times pods per node plus reserved addresses, or use an overlay network.
Mistake: Overlapping CIDRs between networks that will be peered or joined by VPN. Why it is wrong: Routes become ambiguous and traffic silently goes to the wrong place. Instead: Plan one address scheme across environments and on-premises before creating networks.
Mistake: Comparing addresses without handling IPv4-mapped IPv6.
Why it is wrong: A dual-mode Kestrel socket reports an IPv4 client as ::ffff:10.0.0.5, so a check against 10.0.0.0/8 fails and legitimate callers are blocked.
Instead: Call MapToIPv4() when IsIPv4MappedToIPv6 is true, or include both forms in allow-lists.
Mistake: Thinking NAT is a security boundary. Why it is wrong: NAT only hides addresses; it does not filter outbound traffic, and with IPv6 there is often no NAT at all. Instead: Use firewalls, security groups and network policies explicitly for both IPv4 and IPv6.
Even as a junior topic, IP addressing drives real incidents: subnet exhaustion in Kubernetes, overlapping ranges after peering, SNAT port exhaustion on a NAT gateway, and allow-lists that break when traffic starts flowing through a proxy.
| Topic | IPv4 | IPv6 |
|---|---|---|
| Address shortage | Yes, solved with private ranges and NAT | No |
| NAT | Common | Usually unnecessary |
| Exposure | Hidden behind NAT by default | Globally routable; firewalls essential |
| Enterprise adoption | Universal | Growing; dual-stack is common during transition |
| Common pitfalls | SNAT exhaustion, overlapping CIDRs | Broken IPv6 paths causing slow fallback, missed firewall rules |
| Kubernetes networking choice | IP usage | Trade-off |
|---|---|---|
| Pods get VNet IPs (flat) | High | Direct routing to pods, needs large subnets |
| Overlay (separate pod CIDR) | Low | Saves VNet space, pods NATed for outbound |
I keep in mind that IP addresses are routing information, not identity. I use them for coarse controls (private endpoints, firewall rules, network policies) and rely on authentication for anything that matters, and I block the metadata address for any feature that fetches user-supplied URLs.
A port is a 16-bit number (0 to 65535) that lets one IP address host many services. A socket is the OS object for one endpoint: a listening socket bound to IP:port, and a connected socket for each accepted connection, identified by the 4-tuple (client IP, client port, server IP, server port).
The Docker problem is almost always binding: the app listens on localhost (127.0.0.1) inside the container, which is unreachable from outside its network namespace, or it listens on a different port from the one you mapped. The official .NET 10 images set ASPNETCORE_HTTP_PORTS=8080, which binds all interfaces on 8080 (the default since .NET 8), so map it with -p 5000:8080 and do not override it with a localhost URL.
A firewall allows or blocks traffic based on rules (source, destination, port, protocol, and for L7 firewalls/WAFs, HTTP content).
Troubleshoot by symptom: timeout usually means dropped by a firewall or routing, refused means reached the host but nothing listening, reset after idle often means a firewall or load balancer idle timeout removed the connection state. Test from the source (nc -vz, Test-NetConnection), check rules and flow logs in both directions, and set TCP keep-alives or shorter pool idle timeouts below the firewall's idle timeout.
Last updated 23 Oct 2026.