Part VI — The Transport Layer: UDP
21. Ports, sockets, and the connection 4-tuple
21.1 Ports
A port is a 16-bit number (0–65535) that identifies which application endpoint on a host a segment belongs to. It's the demultiplexing key at Layer 4.
| Range | Name | Use |
|---|---|---|
| 0–1023 | Well-known / system | Binding usually needs root/CAP_NET_BIND_SERVICE. 22 SSH, 53 DNS, 80 HTTP, 443 HTTPS, 123 NTP, 25 SMTP. |
| 1024–49151 | Registered | App vendors register these (3306 MySQL, 6379 Redis, 5432 Postgres). Not enforced. |
| 49152–65535 | Dynamic / ephemeral (IANA) | Source ports for outbound connections. Linux actually uses net.ipv4.ip_local_port_range, default 32768 60999. |
21.2 The socket and the 4-tuple
A TCP connection is uniquely identified by the 4-tuple:
(source IP, source port, destination IP, destination port)
Plus the protocol, it's a 5-tuple. The kernel uses it as the hash key to find the right connection for every incoming segment.
Consequences:
- One server
:443can hold millions of simultaneous connections — they differ in the client IP/port, not the server side. - A single client can open at most ~28 000 connections to the same
(dst IP, dst port) before exhausting its ephemeral range — then
connect()returnsEADDRNOTAVAIL. More client IPs, or more destination IPs/ports, orSO_REUSEADDR/IP_BIND_ADDRESS_NO_PORTextend this. SO_REUSEPORT: multiple listening sockets (one per worker thread/process) can bind the same port; the kernel load-balances incoming connections across them. This is how nginx/HAProxy/Envoy scale across cores without a single accept lock.
21.3 Listening vs connected sockets
$ ss -tanp
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 511 0.0.0.0:443 0.0.0.0:* # accepts new conns
ESTAB 0 0 10.0.0.5:443 203.0.113.9:54321 # one client
ESTAB 0 0 10.0.0.5:443 203.0.113.9:54322 # same client, diff port
LISTENsocket: bound to(local IP or wildcard, port).Send-Qhere = the accept queue (backlog) capacity;Recv-Q= completed connections waiting foraccept().- Each
accept()returns a new socket bound to the full 4-tuple. backlog(fromlisten(fd, backlog)) is capped bynet.core.somaxconn(default 4096 on modern Linux). If it overflows, new SYNs are dropped → clients retry after 1 s → visible "sometimes takes 1s to connect."
21.4 War story
A payments service capped out at ~28 000 req/s to its downstream fraud API and
threw cannot assign requested address. It opened a fresh connection per request
to a single fraud-api:443 VIP, exhausting ephemeral ports faster than
TIME_WAIT (60 s) cleared them. Fix: an HTTP connection pool with keep-alive
(reuse a few hundred connections). Throughput went to 200 k req/s.
Lesson: "connection per request" doesn't scale; the 4-tuple space to a single
destination is only ~28k wide.
Try it yourself
sysctl net.ipv4.ip_local_port_range net.core.somaxconn
ss -s # summary: sockets by state
ss -tan state time-wait | wc -l # how many TIME_WAIT
ss -tlnp # listening TCP sockets + owning process
cat /proc/sys/net/ipv4/tcp_max_syn_backlog