How Does Linux Choose a Client Port for TCP Connections? Deep Dive into the Kernel
This article explains the Linux kernel's process for selecting a client-side TCP port during connect() and bind(), covering the relevant source code, random port selection algorithm, conflict checks, and common error scenarios such as "Cannot assign requested address".
In a TCP connection the client must pick a local port before the handshake. This article explains how Linux determines that port, both when the client calls connect() and when it explicitly calls bind().
1. Creating a socket
The client first creates a socket with socket(AF_INET, SOCK_STREAM, 0). The system call allocates several kernel objects (file, socket, sock) that are linked together. The relevant kernel source is shown.
//file: net/socket.c
SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
{
sock_create(family, type, protocol, &sock);
sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
...
}2. connect() – port selection
2.1 Call chain
When connect(fd, …) is invoked, the kernel looks up the socket object and calls sock->ops->connect, which resolves to inet_stream_connect and eventually tcp_v4_connect.
//file: net/ipv4/tcp_ipv4.c
int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
{
tcp_set_state(sk, TCP_SYN_SENT);
err = inet_hash_connect(&tcp_death_row, sk); // choose a port
err = tcp_connect(sk); // send SYN
}2.2 Selecting an available port
The function inet_hash_connect forwards to __inet_hash_connect, which uses two key parameters: inet_sk_port_offset(sk) (a random offset derived from the destination address) and __inet_check_established (conflict check).
//file: net/ipv4/inet_hashtables.c
int inet_hash_connect(struct inet_timewait_death_row *death_row,
struct sock *sk)
{
return __inet_hash_connect(death_row, sk,
inet_sk_port_offset(sk),
__inet_check_established,
__inet_hash_nolisten);
}The algorithm reads the configured local port range ( net.ipv4.ip_local_port_range, default 32768‑61000), then iterates from a random offset, skipping reserved ports ( net.ipv4.ip_local_reserved_ports) and ports already present in the hash table hinfo->bhash. If a free port is found, it is returned; otherwise -EADDRNOTAVAIL triggers the user‑visible “Cannot assign requested address” error.
The default range provides 28 232 ports; adjust it via sysctl -w net.ipv4.ip_local_port_range="10000 60000" if needed.
2.3 When a port is already in use
If the candidate port appears in the hash table, the kernel calls check_established (implemented as __inet_check_established) to see whether an existing ESTABLISHED connection has the identical 4‑tuple. If the 4‑tuple differs (e.g., different server port), the port is still usable, allowing the same local port to serve multiple concurrent connections.
//file: net/ipv4/inet_hashtables.c
static int __inet_check_established(...){
// iterate over ESTABLISHED sockets
if (INET_MATCH(...)) goto not_unique;
return 0; // unique, can use
not_unique:
return -EADDRNOTAVAIL;
}2.4 Sending the SYN
After a port is chosen, tcp_connect builds a SYN packet, queues it, transmits it, and starts the retransmission timer.
//file: net/ipv4/tcp_output.c
int tcp_connect(struct sock *sk)
{
buff = alloc_skb_fclone(MAX_TCP_HEADER + 15, sk->sk_allocation);
tcp_init_nondata_skb(buff, tp->write_seq++, TCPHDR_SYN);
tcp_connect_queue_skb(sk, buff);
err = tp->fastopen_req ? tcp_send_syn_data(sk, buff)
: tcp_transmit_skb(sk, buff, 1, sk->sk_allocation);
inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,
inet_csk(sk)->icsk_rto, TCP_RTO_MAX);
}3. bind() – explicit port selection
If the application calls bind() before connect(), the port is fixed in inet_num. Supplying a non‑zero port forces the kernel to use it; supplying 0 triggers a similar random‑range algorithm, but the conflict check only looks at sockets in the BIND state, so a port used once will not be reused by default. Using bind() on a client socket is generally discouraged because it bypasses the normal connect() selection logic.
//file: net/ipv4/af_inet.c
int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
{
unsigned short snum = ntohs(addr->sin_port);
if (snum && snum < PROT_SOCK && !ns_capable(...))
goto out;
if (sk->sk_prot->get_port(sk, snum)) {
err = -EADDRINUSE;
goto out_release_sock;
}
}4. Conclusions
The client’s local port is chosen either by connect() (random walk through ip_local_port_range) or by a prior bind(). Failure to find a free port results in “Cannot assign requested address”, often indicating an overly small ip_local_port_range. Reserved ports can be excluded via ip_local_reserved_ports. A single local port may serve multiple TCP connections as long as their 4‑tuples differ, so the practical limit on concurrent connections exceeds 65 535.
Because the selection algorithm scans the range from a random start, a nearly exhausted range can increase CPU usage of the connect() system call.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Liangxu Linux
Liangxu, a self‑taught IT professional now working as a Linux development engineer at a Fortune 500 multinational, shares extensive Linux knowledge—fundamentals, applications, tools, plus Git, databases, Raspberry Pi, etc. (Reply “Linux” to receive essential resources.)
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
