When two TCP sockets share a local address
Open a browser to one site, then another: each connection gets its own local ephemeral port. But the rules for when Linux lets two sockets share a local (IP, port) pair are surprisingly subtle — especially when bind(), address selection, and socket options enter the picture.
To explore the edge cases, we can shrink the ephemeral port range to a single port and run short Python snippets. This forces address collisions that would otherwise be masked by the OS picking fresh ports each time. Set the range with:
sysctl -w net.ipv4.ip_local_port_range='60000 60000'
Calls to socket(), bind(), and connect() then either succeed or fail in ways that reveal the kernel's logic. Each quiz below follows a common setup that, for brevity, we omit from the snippets.
Scenario 1: Different local IPs, same local port
Two sockets connect to the same remote 127.9.9.9:1234, but bind to different local IP addresses. Does that permit port sharing?
Quiz 1 — Bind both sockets to distinct, explicit local IPs, and let the OS pick the local port (from a range of size one). The OS must assign port 60000 to the first socket, forcing a conflict for the second.
GOTO Answer #1
Quiz 2 — Same as before, but the first socket uses automatic local address selection (both IP and port). With only one ephemeral port and one local route, the OS picks that single address. The second socket then attempts an explicit bind.
GOTO Answer #2
Quiz 3 — Reverse the order from Quiz 2: first a socket bound explicitly, then one that relies on the OS's automatic selection. Ordering shouldn't change the outcome — or should it?
GOTO Answer #3
Scenario 2: Same local address, different remote IPs
Now the local side is fixed at 127.0.0.1:60000, and two sockets connect to distinct remote addresses. The question remains whether port sharing is possible.
Quiz 4 — A baseline: connect() twice from the default local address to two different remote IPs.
GOTO Answer #4
Quiz 5 — Bind the local IP explicitly for both sockets, but let the OS choose the port.
GOTO Answer #5
Quiz 6 — Both bind() calls specify the complete local address, IP and port.
GOTO Answer #6
Quiz 7 — The same scenario as Quiz 6, but both sockets enable SO_REUSEADDR. Start with an automatic OS-selected address, then attempt an explicit bind to the identical (IP, port) with the option set on both sockets.
GOTO Answer #7
Quiz 8 — The mirror of Quiz 7: bind explicitly first, then ask the OS to auto-select the local address. Common sense says the outcome should match, but the kernel's address selection may not be symmetric.
GOTO Answer #8
How Linux tri-states a TCP port
The port conflict checks that happen during bind() and connect() feel like black-box behavior. But Linux keeps track of every TCP port in a hash table called bhash, where each entry leads to a chain of bind buckets. A bind bucket groups sockets together by network namespace, VRF device, and local port number. In the simplest case — a single namespace and no VRFs — it's just the local port that matters.
Each bind bucket holds a linked list of its owner sockets. When a local address needs to be assigned to a socket, the kernel walks the chain looking for a bucket with a matching port. If the bucket doesn't exist, it's created. Once the bucket is found, its fastreuse field determines what can happen next. That field is one of three values: -1, 0, or +1.
What the tri-state means
The fastreuse field summarizes two properties of the bind bucket: what sockets are in it, and when the local port can be shared. The first part is fairly simple:
| fastreuse is | owners list contains |
|---|---|
| -1 | sockets connect()'ed from an ephemeral port |
| 0 | sockets bound without SO_REUSEADDR |
| +1 | sockets bound with SO_REUSEADDR |
Port sharing rules are where the state really matters:
| Can I … when … | fastreuse = -1 | fastreuse = 0 | fastreuse = +1 |
|---|---|---|---|
| bind() to the same port (ephemeral or specified) | yes IFF local IP is unique ① | ← idem | ← idem |
| bind() to the specific port with SO_REUSEADDR | yes IFF local IP is unique OR conflicting socket uses SO_REUSEADDR ① | ← idem | yes ② |
| connect() from the same ephemeral port to the same remote (IP, port) | yes IFF local IP unique ③ | no ③ | no ③ |
| connect() from the same ephemeral port to a unique remote (IP, port) | yes ③ | no ③ | no ③ |
Breaking that table down into digestible rules:
bind()— early local address allocation — always succeeds if there's no local IP address conflict with an existing socket.connect()— late local address allocation — always fails unless the bucket is in thefastreuse = -1state.connect()only succeeds when neither local nor remote addresses conflict.SO_REUSEADDRpermits local address sharing, provided all conflicting sockets also set it (and none is listening).
That can be verified directly on a live kernel using drgn, the programmable debugger. A quick script can dump a bind bucket's state and confirm how the table behaves in practice.
There is, however, a subtlety that tables miss. A bind bucket doesn't necessarily stay in its initial fastreuse state. The state can change when a new socket binds to the same port, and the only possible transition is into fastreuse = 0. That happens when a socket is bound that:
- doesn't conflict with existing owners, and
- doesn't have
SO_REUSEADDRenabled.

The exact logic lives in inet_csk_get_port → inet_csk_update_fastreuse.
Why this matters in practice
The next time bind() returns EADDRINUSE or connect() fails with EADDRNOTAVAIL, the cause is likely a bind bucket in a state that disallows sharing. You now know where to look and have the tools to inspect it.
There's a particularly relevant corner case: binding sockets with SO_REUSEADDR to reserve a range of local ports can, under the wrong conditions, prevent regular connect()-ed sockets from using those same ports. The Linux community has been working on a cleaner solution — a new socket option to specify the local port range directly, which avoids the bind trick entirely. That option is slated for Linux 6.3 and will restore the ability to share local ports with regular connect()-ed sockets.
Closing thoughts
The question "when can two TCP sockets share a local address?" has an answer too complicated for a single sentence — and even this deep dive doesn't cover everything. SO_REUSEPORT and listening sockets add more layers of nuance that were deliberately left out here.
One practical takeaway stands out: using bind() purely to pick an egress IP can have unintended consequences. The recommended pattern is to pair bind() with IP_BIND_ADDRESS_NO_PORT, letting the kernel assign the local port. Otherwise, you might silently block TCP ports from being reused. For UDP, that same advice doesn't apply today, as IP_BIND_ADDRESS_NO_PORT doesn't work there yet — a topic for another time.



