TCP Connection Lifecycle & Listen System Call Mechanics #
In a server application, the kernel must execute the listen() system call before it can accept incoming client connections.
In Go, high-level abstractions like http.ListenAndServe handle socket creation, binding, and listening under the hood:
......
srv := &http.Server{
Addr: 0.0.0.0:8080,
Handler: mux,
ReadTimeout: 10 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: time.Minute,
}
if err := srv.ListenAndServe(); err != nil {
log.Fatalf("Server crash: %v", err)
}
......
Protocol Listen & Socket Selection #
A socket file descriptor in user space is represented as a simple integer, which the kernel cannot use directly. Therefore, the kernel must map this file descriptor to the underlying struct socket core object.
During listen(), Linux compares the user-passed backlog parameter against the kernel parameter net.core.somaxconn and chooses the smaller value as the queue cap. Passing a backlog larger than net.core.somaxconn will have no extra effect on half/full connection limits.
Connection Queue Definitions (request_sock_queue) #
The request_sock_queue is the kernel data structure used to handle incoming client connection requests. Both the SYN queue (half-connection) and Accept queue (full-connection) are managed around this structure.
It contains rskq_accept_head, rskq_accept_tail, and listen_opt:
-
Full Connection Queue (Accept Queue): Does not require complex searching. The accept() process simply follows FIFO (First-In, First-Out) operations using a linked list.
-
Half Connection Queue (SYN Queue): Requires fast lookups for incoming handshake packets. It uses a hash table to manage connections along with tracking the active queue length.
Queue Initialization & Size Calculation #
When initializing the request_sock_queue, the kernel allocates memory and calculates the queue lengths:
- Takes the minimum of backlog and net.ipv4.sysctl_max_syn_backlog.
- Takes the maximum of the previous result and 8 (ensuring a minimum floor so low values don’t break connections).
- Rounds the value up to the next power of 2 for hash indexing.
If experiencing half-connection queue overflows, evaluate all three parameters: net.core.somaxconn, application backlog, and net.ipv4.sysctl_max_syn_backlog.
Client-Side Connect Call Stack #
- The client process passes its socket file descriptor (fd) to inet_stream_connect(). The socket starts in the SS_UNCONNECTED state.
- The kernel dynamically selects an available source port.
- The kernel constructs a SYN packet and transmits it over the wire.
Ephemeral Port Selection #
Port allocation generates a pseudo-random starting offset based on the target IP and target port, then checks for availability within the allowed ephemeral range.
When bind() is not explicitly called by the application, the kernel automatically assigns a port within the range defined by net.ipv4.ip_local_port_range (default: 32768 61000, providing 28,233 potential ports).
The kernel iterates through a while loop starting from the random offset, checking a global port hash table to find an unused port. Ports listed in net.ipv4.ip_local_reserved_ports are skipped.
Port Reuse & 5-Tuple Uniqueness #
If a candidate port is already in use, the kernel checks whether the active connection remains unique using the 5-tuple:
| Element | Description | Example |
|---|---|---|
| Protocol | Transport layer protocol | TCP / UDP |
| Source IP | Client machine address | 192.168.1.50 |
| Source Port | Ephemeral port allocated to client process | 52410 |
| Destination IP | Server machine address | 142.250.1.100 |
| Destination Port | Well-known port assigned to target service | 443 |
Because uniqueness is defined by the full 5-tuple, a client can initiate thousands of connections using the same local port, provided the destination IP or destination port differs. A client is not limited to 65,535 total concurrent connections across different remote hosts.
Handshake Execution & Packet Delivery #
The client creates a sk_buff (skb) configured as a SYN packet, sends it, and starts the retransmission timer using TCP_TIMEOUT_INIT.
sequenceDiagram
autonumber
participant Client
participant Server
Client->>Server: SYN
Note over Server: Pushed to SYN Queue
(Half-Connection)
Server-->>Client: SYN-ACK
Note over Server: Retransmission timer set
Client->>Server: ACK
Note over Server: Moved to Accept Queue
(Full-Connection)
1. Server Receives SYN #
- Locates the listening socket via the TCP header destination port.
- Checks if the half-connection queue has room.
- If full, checks young_ack status; if overloaded, the packet may be dropped.
- If tcp_syncookies is disabled and the queue overflows, tcp_syn_flood_action drops the packet.
- Constructs SYN-ACK, sends it, adds the entry to the SYN queue, and sets the retransmission timer.
2. Client Receives SYN-ACK #
- Changes socket state to TCP_ESTABLISHED.
- Disables the connection timer, constructs the final ACK packet, and sends it to the server.
3. Server Receives ACK #
- Searches the half-connection queue for the matching request_sock.
- Checks if the full-connection (Accept) queue is full.
- If space is available, allocates a real struct sock, removes the request from the half-connection queue, and appends it to the tail of the accept queue.
4. Application accept() #
Takes the fully established socket off the head of the accept queue and returns a file descriptor to the user process.
Performance Bottlenecks & Troubleshooting #
High CPU Usage During Out-of-Port Scenarios #
When client ephemeral ports run out, the kernel spends significant CPU cycles looping through the port hash table and acquiring locks.
it can remission by:
- Expand net.ipv4.ip_local_port_range.
- Use HTTP keep-alives (long-lived connections).
- Enable fast recycling or reuse of TIME_WAIT sockets via net.ipv4.tcp_tw_reuse.
First ACK / SYN-ACK MissingIf the accept queue is full and tcp_syncookies is disabled (0), incoming SYN packets are discarded. The client timer expires, retransmitting SYN packets with exponential backoff 1s, 2s, 4s.In environments using reverse proxies like Nginx, dropped initial SYN packets cause noticeable upstream latency spikes or timeout errors for end users.Third ACK MissingIf the server’s Accept Queue is full when the 3rd ACK arrives from the client, the server drops the ACK and retransmits the SYN-ACK packet as if the 3rd ACK was never sent.
Recommendations for High-Load Servers #
-
Enable SYN Cookies: Set net.ipv4.tcp_syncookies = 1 to handle SYN floods without dropping connection attempts.
-
Increase Queue Caps: Expand both net.core.somaxconn and net.ipv4.sysctl_max_syn_backlog.
-
Consume Accept Queue Fast: Ensure worker threads execute accept() efficiently without blocking.
-
Enable Abort on Overflow: Set net.ipv4.tcp_abort_on_overflow = 1 if you prefer fast failures (RST) over delayed connection hangs when queues are full.
Diagnostic Commands #
Check Accept Queue Overflows (SNMP) #
# Monitor cumulative accept queue drops in real-time
watch -n 1 'netstat -s | grep -i "overflowed"'Check Active Half-Connections (SYN Queue) #
# Count connections currently stuck in SYN_RECV state
netstat -antp | grep SYN_RECV | wc -l