1
votes

I tried to let tcp server bind to a close_wait port, but it caused a Errno::EADDRINUSE error.

I created a tcp server that listen on port 55555. Then client connected to that server. After some ops, run ss -at | grep 55555.

# ss -at | grep 55555
LISTEN     0      128                     *:55555                    *:*
FIN-WAIT-2 0      0               127.0.0.1:55555            127.0.0.1:16413
CLOSE-WAIT 0      0               127.0.0.1:16413            127.0.0.1:55555

I tried to bind port 16413, it caused a Errno::EADDRINUSE error. But if I connected to a ESTAB socket, the socket could bind to the port(such as 22385 below).

# ss -at | grep 55555
LISTEN     0      128                     *:55555                    *:*
ESTAB      0      0               127.0.0.1:22385            127.0.0.1:55555
ESTAB      0      0               127.0.0.1:55555            127.0.0.1:22385 

Some scripts by ruby to reproduce the problem.

tcp_server_close_wait.rb

require 'socket'

server = TCPServer.new 55555 # Server bind to port 2000
loop do
  client = server.accept    # Wait for a client to connect
  client.puts "Hello !"
  client.puts "Time is #{Time.now}"
  client.shutdown(Socket::SHUT_WR)
end

tcp_server.rb

require 'socket'

server = TCPServer.new 55555 # Server bind to port 2000
loop do
  client = server.accept    # Wait for a client to connect
  client.puts "Hello !"
  client.puts "Time is #{Time.now}"
  client.close
end

tcp_client.rb

require 'socket'
include Socket::Constants
socket = Socket.new( AF_INET, SOCK_STREAM, 0 )
sockaddr = Socket.pack_sockaddr_in( 55555, '127.0.0.1' )
socket.connect( sockaddr )
res = socket.read
puts res

sleep 10000

** tcp_bind.rb **

require 'socket'

# use Addrinfo
socket = Socket.new(:INET, :STREAM, 0)
socket.bind(Addrinfo.tcp("0.0.0.0", ARGV[0].to_i))

Create close_wait bind.

  1. run ruby tcp_server_close_wait.rb

  2. run ruby tcp_client.rb

  3. run ss -at | grep 55555 to find client port

  4. run ruby tcp_bind.rb $client_port

Create ESTAB bind. 1. run ruby tcp_server.rb

  1. run ruby tcp_client.rb

  2. run ss -at | grep 55555 to find client port

  3. run ruby tcp_bind.rb $client_port

1
Do some research about the SO_REUSEADDR socket option.Some programmer dude
SO_REUSEADDR has nothing to do with the problem. Every tcp server && client was created without SO_REUSEADDRAnien
Is SO_REUSEADDR is set on a socket in the TIME_WAIT state, then another socket should be able to bind to the same address as the closing socket is/was bound to.Some programmer dude
My problem is that close wait can't be bind to, but ESTAB can be.Anien
Remember that a local end-point of a TCP and UDP socket is identified by the protocol (TCP or UDP), the address, and the port number. If any of these are different from some other end-point, then they are different end-points. And end-point identified by TCP:*:55555 is different from an end-point identified by TCP:127.0.0.1:55555.Some programmer dude

1 Answers

0
votes

I repeated the same test with Linux version 4.4.74-18.20 by using C programs.

I got different results than OP.

When SO_REUSEPORT was disabled, binding failed for both ports:

  • port of client of established TCP connection (ESTAB)
  • port of client of half open TCP connection (CLOSE-WAIT)

When SO_REUSEPORT was enabled (for all sockets), binding success for the both ports.

See socket(7) man page for getting more information about SO_REUSEPORT.