status
this chapter is in active development
expect live edits and rapid iteration (except for when i am really busy with other stuff) while this material is written.
status
this chapter is in active development
expect live edits and rapid iteration (except for when i am really busy with other stuff) while this material is written.
dns gave us an address.
tcp (per RFC 9293, the sober rewrite of RFC 793) is the gatekeeper deciding whether we get to speak to it.
no handshake, no data, no excuses.
socket(AF_INET6, SOCK_STREAM, 0) reserves kernel state.
connect() kicks the whole dance off: the kernel chooses an ephemeral source port (see /proc/sys/net/ipv4/ip_local_port_range), bolts on options (mss, sack, timestamps, window scale), and throws a SYN at the target.
your process blocks unless you go non-blocking, so every retry and timeout you feel here is pure kernel behavior.
complaining at the application layer won't change anything.
syn - client shouts "here's my isn, here's my window scale." if this packet never leaves your host, look at routing tables, not the server.
syn+ack - server agrees and supplies its isn. if the port is closed, you get an rst instead and userland calls it "connection refused." fast, honest failure.
ack - client confirms and the socket slides into ESTABLISHED. now send()/recv() actually mean something.
want proof? run tcpdump -n host example.org and port 443 while curling the same host:
14:01:53.962394 IP6 [...].43486 > [...].443: Flags [S], seq 4160096982, win 64800, options [mss 1440,sackOK,TS val 10102034 ecr 0,nop,wscale 10], length 0
14:01:54.124332 IP6 [...].443 > [...].43486: Flags [S.], seq 2388760338, ack 4160096983, win 64260, options [mss 1344,sackOK,TS val 2107005485 ecr 10102034,nop,wscale 7], length 0
14:01:54.124408 IP6 [...].43486 > [...].443: Flags [.], ack 1, win 64, options [nop,nop,TS val 10102196 ecr 2107005485], length 0
three lines, three packets. Flags [S] is the syn, Flags [S.] is syn+ack, Flags [.] is the final ack. the timestamps tell you exactly how long each leg took: here the syn left at .962394, the syn+ack arrived at .124332 (about 162 ms later), and the client acked almost instantly at .124408. that 162 ms is one rtt to the server. if you see the syn but no syn+ack, something between you and the server is eating packets. if the syn+ack arrives but the final ack never does, check your local firewall or nat table.
SYN-SENT means you left the driveway but have not heard back.
sitting here forever screams packet filters or asymmetric routes.
SYN-RECEIVED on the server means it answered and is waiting for you; if it fills up, something upstream is eating ACKs.
ESTABLISHED is the boring happy path.
TIME-WAIT is just the client cleaning up for roughly 2 msl (usually a minute). mountains of TIME-WAIT sockets after a load test are normal; stop deleting them.
connection refused means an rst arrived instead of syn+ack. no listener, or a firewall explicitly rejected you.
connection timed out means silence. packets dropped, server down, or some security group doing a quiet drop. curl waits hundreds of seconds unless you set --connect-timeout.
no route to host means the kernel could not even figure out how to send the syn. fix your routing before blaming tls.
run curl -v --connect-timeout 2 https://example.org; it only prints * Connected to ... after the ack lands.
ss -o state syn-sent '( dport = :443 )' lists every pending handshake and its retransmission timer.
ss --info (tcp_info) leaks rtt and congestion state immediately post-connect. yes, you can grab it via getsockopt().
on a lan this handshake is pocket change.
across the planet it is the line item that wrecks your latency budget.
treat it like math, not folklore.
once the handshake lands, tls takes over to make the pipe worth using.