TCPSocket doesn't detect remote disconnection inmediatelly

  • Thread starter Iñaki Baz Castillo
  • Start date

I

Iñaki Baz Castillo

Hi, I'm experimenting an annoying issue when a server disconnects a TCP=20
connection established by a Ruby client.

Let me start with an example of what I consider the expected behavior:


a) Using Linux "telnet" command:

=2D First connect to google.com:

~$ telnet google.com 80 =
=20

Trying 209.85.229.106... =
=20
Connected to google.com. =
=20
Escape character is '^]'.

=2D Then write something ("hello\n"):

hello

=2D So I receive a 400 response:

HTTP/1.0 400 Bad Request
[...]

=2D And a warning:

"Connection closed by foreign host."

=2D telnet exits with status 1 due to the remote disconnection error.



b) Using Ruby 1.9.1 TCPSocket:

=2D First connect:

irb> require "socket"
true
irb> socket =3D TCPSocket.new("google.com", 80)
#<TCPSocket:0x00000001010288>

=2D Now netstat shows the connection as ESTABLISHED (ok).

=2D Write "hello\n" into the socket:

irb> socket.puts "hello"
nil

=2D I receive the same 400 response from the www server. Now netstat shows =
the=20
connection as CLOSE_WAIT (OK?).

=2D Write "hello\n" again (IMHO it should fail):

irb> socket.puts "hello"
nil <---- didn't raise !!!

=2D Doing a ngrep I see that irb has sent "hello\n" over the previous TCP=20
connection but this time it doesn't receive response from the www server. T=
his=20
is, irb is using a closed connection!:

T 2010/01/12 20:12:04.072705 192.168.1.10:35272 -> 209.85.229.105:80 [AP]
hello
#

=2D But after this new "puts" now netstat shows nothing! (no connection =3D=

=2D Repeat again:

irb> socket.puts "hello"
Errno::EPIPE: Broken pipe
from (irb):6:in `write'
from (irb):6:in `puts'
from (irb):6
from /usr/bin/irb:12:in `<main>'

=2D Now it raises!


Why didn't Ruby raise in the second "puts" attemp??? the server already clo=
sed=20
the connection! Why does Ruby realize of it after a new retry?

Well, this issue causes one request lost (the second one) since there is no=
=20
way to know if the server accepted or dropped the request.

Is it a bug? is there a way to check the socket connection before sending=20
(loosing in fact) a request? (TCPSocket#closed? is not valid here as it jus=
t=20
returns true in case *we* have closed a socket).

Thanks a lot for any help.



=2D-=20
I=C3=B1aki Baz Castillo <[email protected]>
 
Ad

Advertisements

B

Brian Candler

Iñaki Baz Castillo said:
Hi, I'm experimenting an annoying issue when a server disconnects a TCP
connection established by a Ruby client.

Try writing a C program which uses the Unix socket API directly. It will
be instructive. The book "Unix Network Programming (vol 1)" by the late
Richard Stevens is an excellent authoritative tome.
 
I

Iñaki Baz Castillo

El Martes, 12 de Enero de 2010, Brian Candler escribi=C3=B3:
=20
Try writing a C program which uses the Unix socket API directly. It will
be instructive. The book "Unix Network Programming (vol 1)" by the late
Richard Stevens is an excellent authoritative tome.

I'll do it. But it would be great if at least I could know in advance if wh=
at=20
I described is the expected behavior :)


=2D-=20
I=C3=B1aki Baz Castillo <[email protected]>
 
I

Iñaki Baz Castillo

El Martes, 12 de Enero de 2010, I=C3=B1aki Baz Castillo escribi=C3=B3:
El Martes, 12 de Enero de 2010, Brian Candler escribi=C3=B3:
=20
I'll do it. But it would be great if at least I could know in advance if
what I described is the expected behavior :)

Well, I've not tested in C yet, but I confirm that in PHP the same occurs :)

=20


=2D-=20
I=C3=B1aki Baz Castillo <[email protected]>
 
A

Aaron Turner

El Martes, 12 de Enero de 2010, Brian Candler escribi=F3:

I'll do it. But it would be great if at least I could know in advance if = what
I described is the expected behavior :)

Long story short, what you're seeing with Ruby is not a bug, but
rather how TCP sockets work. TCP sockets are bi-directional and when
one side "closes" the connection, they are not actually causing the
TCP session to tear down as telnet implies, but rather is informing
the other end of the connection that they have no more data to send.
The receive direction is still technically open via TCP, although the
HTTP protocol specifies no more communication is possible.

I also recommend "Unix Network Programming (vol 1)" by Stevens if you
want to understand the details of sockets and network communication.
Since network programming utilizes much of the same API constructs as
file IO, many newbie network programmers make these sorta mistakes
because they expect network sockets to behave like file descriptors
when they do not.

--=20
Aaron Turner
http://synfin.net/
http://tcpreplay.synfin.net/ - Pcap editing and replay tools for Unix & Win=
dows
Those who would give up essential Liberty, to purchase a little temporary
Safety, deserve neither Liberty nor Safety.
-- Benjamin Franklin
"carpe diem quam minimum credula postero"
 
I

Iñaki Baz Castillo

El Martes, 12 de Enero de 2010, Aaron Turner escribi=F3:
Long story short, what you're seeing with Ruby is not a bug, but
rather how TCP sockets work. TCP sockets are bi-directional and when
one side "closes" the connection, they are not actually causing the
TCP session to tear down as telnet implies, but rather is informing
the other end of the connection that they have no more data to send.
The receive direction is still technically open via TCP, although the
HTTP protocol specifies no more communication is possible.

Understood, thanks a lot.

Then I wonder if there is some way to detect that the remote has closed its=
=20
side. When it occurs the remote sends a TCP segment with ACK+FIN flags=20
enabled.

Is there a way to detect it in Ruby using TCPSocket? if there some attribut=
e=20
of the socket object that changes whe such ACK+FIN arrives? (however I've=20
tried most of the TCPSocket methods and found nothing).

The underlaying system of courses knows it as in case a new TCP segment is=
=20
sent (by the Ruby client) it's sent with big PUSH enabled, knowing that the=
=20
remote will reply with RESET flag enabled. Is the Ruby TCPSocket aware that=
=20
its message is sent with PUSH flag enabled? if so it could raise something=
=20
(optionally) so I know that I'll get no response and could reconnect and=20
retry. Is it possible?

Humm, I think that all this stuff is done at kernel TCP layer so the socket=
=20
user has nothing to deal with... am I right?

I also recommend "Unix Network Programming (vol 1)" by Stevens if you
want to understand the details of sockets and network communication.
Since network programming utilizes much of the same API constructs as
file IO, many newbie network programmers make these sorta mistakes
because they expect network sockets to behave like file descriptors
when they do not.

Sure. Thanks a lot.



=2D-=20
I=F1aki Baz Castillo <[email protected]>
 
Ad

Advertisements

A

Aaron Turner

El Martes, 12 de Enero de 2010, Aaron Turner escribi=F3:

Understood, thanks a lot.

Then I wonder if there is some way to detect that the remote has closed i= ts
side. When it occurs the remote sends a TCP segment with ACK+FIN flags
enabled.

Is there a way to detect it in Ruby using TCPSocket? if there some attrib= ute
of the socket object that changes whe such ACK+FIN arrives? (however I've
tried most of the TCPSocket methods and found nothing).

There are two ways of doing this:

1) The application running over TCP. In this case the HTTP 400 error
is telling you that the web server is done talking to you and no
future requests will be processed.

2) The other way is when you read, did you get an EOF error? EOF =3D=3D
server has called shutdown() on the socket.

Ruby sockets behave just like C sockets. It's just the Ruby language
wrapping them. There's nothing "special" about ruby sockets. What
this means is that the best resource isn't the ruby docs- they just
document the ruby API. To understand how sockets work, read Richard
Stevens or start doing google searches on things like "tcp detecting
close".
The underlaying system of courses knows it as in case a new TCP segment i= s
sent (by the Ruby client) it's sent with big PUSH enabled, knowing that t= he
remote will reply with RESET flag enabled. Is the Ruby TCPSocket aware th= at
its message is sent with PUSH flag enabled? if so it could raise somethin= g
(optionally) so I know that I'll get no response and could reconnect and
retry. Is it possible?

The PSH (Push) flag has nothing to do with it. PSH just tells the
receiving host that the TCP packet has interesting data for the
application and it should quickly "push it up" to the application for
processing.
Humm, I think that all this stuff is done at kernel TCP layer so the sock= et
user has nothing to deal with... am I right?

Depends on what you mean by "all this stuff". If you mean making TCP
a reliable, bi-directional stream & session based programming
construct then yes, the kernel does that for you. If you mean dealing
with knowing when you can read/write to the socket, then no, that's
your job. You the programmer have a lot of control over tcp sockets
and are expected to know how to use them properly. As I said last
time, while TCP sockets utilize a very similar API as File IO, but
they are more complicated and you need to know how to use them.

Good luck.

--=20
Aaron Turner
http://synfin.net/
http://tcpreplay.synfin.net/ - Pcap editing and replay tools for Unix & Win=
dows
Those who would give up essential Liberty, to purchase a little temporary
Safety, deserve neither Liberty nor Safety.
-- Benjamin Franklin
"carpe diem quam minimum credula postero"
 
Ad

Advertisements

I

Iñaki Baz Castillo

El Mi=E9rcoles, 13 de Enero de 2010, Aaron Turner escribi=F3:
=20
There are two ways of doing this:
=20
1) The application running over TCP. In this case the HTTP 400 error
is telling you that the web server is done talking to you and no
future requests will be processed.

I'm not talking with a HTTP server, but with a SIP server. When the SIP ser=
ver=20
restarts its sends a TCP packet with FIN flag to me (Ruby client).

=20
2) The other way is when you read, did you get an EOF error? EOF =3D=3D
server has called shutdown() on the socket.

I have good news for myself :)

I've a thread reading the responses received in the socket:

sip_response =3D @@socket.readline("\r\n\r\n")

I use "\r\n\r\n" (double CRLF) as a SIP response with no body ends with CRL=
=46=20
CRLF (as HTTP I think).

However when the remote disconnects I didn't get the exception EOFError.

The very good new is that it's a bug in my code! In the rescue block I did =
a=20
typo so I wasn't invoking the reconnection command :)


Really thanks for the good help received in this thread :)


=2D-=20
I=F1aki Baz Castillo <[email protected]>
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top