Subject: | Incorrect error code for IO::Socket::INET |
Date: | Mon, 08 Jul 2013 16:57:11 -0400 |
To: | bug-IO [...] rt.cpan.org |
From: | Shmuel (Seymour J.) Metz <shmuel+bitcard.org [...] patriot.net> |
See 115706 in the old CPAN tracking system.
Per Ben Morrow <ben@morrow.me.uk> in thread "IO::Socket::INET hostname
restrictions? " on comp.lang.perl.misc, archived at
<http://www.rhinocerus.net/forum/lang-perl-misc/690988-io-socket-inet-hostname-restrictions.html>:
*Interesting*. I believe there's at least one bug here, in
IO::Socket, but it's not entirely clear.
First, the workaround: don't use Timeout. Find some other way of
timing out the operation, like alarm(). The CNAMEs are a red
herring: what matters is that that machine doesn't accept
connections on port 43. The 'Invalid argument' is just the wrong
error: without Timeout, you should see 'Connection refused',
which is the right answer.
[You don't need to read the rest unless you're interested...]
That ->new call gives me exactly the same result as you: EINVAL
from connect(). Since I didn't think connect(2) was even *able*
to return EINVAL, I ran it under ktrace: this showed two calls to
connect. The first failed with EINPROGRESS, the second with
EINVAL. Retrying without Timeout consistently gave
'Connection refused' instead.
Looking at the source of IO::Socket, the logic if Timeout is
specified is approximately
set non-blocking mode on the socket
try to connect
if connect fails with EINPROGRESS
select(2) for write, with the given timeout
if the select doesn't time out
retry the connect
if that connect succeeds, return success
if it failed with EISCONN, or we are on Win32 and it failed
with WSAEINVAL, return *success*
otherwise, return the error from the second connect
The EISCONN case is the strange one. There is a comment
# Some systems refuse to re-connect() to
# an already open socket and set errno to EISCONN.
# Windows sets errno to WSAEINVAL (10022)
and it turns out that it's not just Win32 that returns EINVAL in
that case, it's at least current versions of FreeBSD and,
presumably, OS/2 as well. However, even given that, returning
success is *completely wrong*.
The sequence of events is
- app calls connect(2) on a non-blocking socket
- connect(2) fails with EINPROGRESS
- app calls select(2)-for-write
- some time later, a RST packet comes in
- select(2) returns socket as ready
- app calls connect(2) again
- connect(2) fails with EISCONN/EINVAL
The EISCONN/EINVAL error is saying 'this socket isn't fit to be
reused': it doesn't tell you *anything* about whether the
second connection attempt succeeded or failed. (In fact, you get
the same EINVAL if you attempt to re-connect a TCP socket while
it's in TIME_WAIT state after a successful close.)
Looking about a bit, I found http://cr.yp.to/docs/connect.html,
which mentions this connect-twice strategy as a way of performing
a non-blocking connect and still getting the correct error. It
also says that it doesn't always work, and that you should use
getpeername instead (and for all djb's... um... charm, he's usually
right about things like this). So, I think this counts as a bug
in IO::Socket, since there are common situations on common
operating systems where it doesn't work properly.
[Oddly, it looks from the CVS log as though FreeBSD used to return
EISCONN in this situation, as the IO::Socket code was
kind-of expecting, but it was changed to EINVAL in 2006. It's not
clear to me why, especially since there now appears to be no way
to get an EISCONN error from a TCP socket.]
The attached patch from Ben Morrow works fine for OS/2.
--
Shmuel (Seymour J.) Metz, SysProg and JOAT
Atid/2 <http://patriot.net/~shmuel>
We don't care. We don't have to care, we're Congress.
(S877: The Shut up and Eat Your spam act of 2003)
Path: border1.nntp.dca.giganews.com!novia!amsnews11.chello.com!xlned.com!feeder5.xlned.com!feed.xsnews.nl!border-1.ams.xsnews.nl!ecngs!feeder.ecngs.de!border1.nntp.ams.giganews.com!nntp.giganews.com!local2.nntp.ams.giganews.com!nntp.bt.com!news.bt.com.POSTED!not-for-mail
NNTP-Posting-Date: Wed, 07 Dec 2011 18:03:06 -0600
Newsgroups: comp.lang.perl.misc
Subject: Re: IO::Socket::INET hostname restrictions?
References: <4edae754$3$fuzhry+tra$mr2ice@news.patriot.net> <Nf-dnZw65uvr7kDTnZ2dnUVZ8tydnZ2d@bt.com> <4edf82d9$21$fuzhry+tra$mr2ice@news.patriot.net> <dfGdndQWk9hVEULTnZ2dnUVZ7rqdnZ2d@bt.com>
From: Ben Morrow <ben@morrow.me.uk>
Content-type: text/plain; charset=UTF-8
X-Newsreader: trn 4.0-test77 (Sep 1, 2010)
Originator: mauzo@anubis.morrow.me.uk (Ben Morrow)
Date: Wed, 7 Dec 2011 23:54:09 +0000
Message-ID: <1ua5r8-ac2.ln1@anubis.morrow.me.uk>
Lines: 42
X-Usenet-Provider: http://www.giganews.com
X-AuthenticatedUsername: NoAuthUser
X-Trace: sv3-6W3s8xwBWJ4+yCaM6dkp6rgox1m2lVg+sCrGOm/PkK43FvIOrD5BwGh8lI3mCq6PMcpgny6zD7AJ32x!vIziyMZB8hczj/6H1D1GvkShrARl6AOkp+K2QvbExwVXWfWqAZnlMTqxdrsOpBilsyksvkQ=
X-Complaints-To: abuse@btinternet.com
X-DMCA-Complaints-To: abuse@btinternet.com
X-Abuse-and-DMCA-Info: Please be sure to forward a copy of ALL headers
X-Abuse-and-DMCA-Info: Otherwise we will be unable to process your complaint properly
X-Postfilter: 1.3.40
Bytes: 3102
X-Original-Bytes: 2912
Xref: number.nntp.dca.giganews.com comp.lang.perl.misc:690120
Quoth Ben Morrow <ben@morrow.me.uk>:
>
> I do intend to report this as a bug in IO::Socket, when I get round to
> it.
If and when you get gcc working, would you be able to test this patch?
You would need to download and unpack the IO-1.25 tarball from CPAN,
apply the patch, and verify that it both passes 'make test' and gives a
more sensible error in the situation we've been discussing.
If you don't want to install the patched version you can use 'perl
-Mblib' from within the build directory to test the built-but-not-
installed modules. If you do want to install it, it should be perfectly
safe to install with 'make install': it will simply overwrite the IO::*
modules which came with your perl with newer versions.
Ben
diff -ur IO-1.25/lib/IO/Socket.pm IO/lib/IO/Socket.pm
--- IO-1.25/lib/IO/Socket.pm 2009-05-14 00:47:42.000000000 +0100
+++ IO/lib/IO/Socket.pm 2011-12-07 16:35:52.859688028 +0000
@@ -122,12 +122,12 @@
$err = $! || (exists &Errno::ETIMEDOUT ? &Errno::ETIMEDOUT : 1);
$@ = "connect: timeout";
}
- elsif (!connect($sock,$addr) &&
- not ($!{EISCONN} || ($! == 10022 && $^O eq 'MSWin32'))
- ) {
- # Some systems refuse to re-connect() to
- # an already open socket and set errno to EISCONN.
- # Windows sets errno to WSAEINVAL (10022)
+ elsif (!getpeername($sock)) {
+ if ($!{ENOTCONN}) {
+ # this read will fail, but will give us the error
+ # from connect(2) in $!
+ sysread $sock, my $tmp, 1;
+ }
$err = $!;
$@ = "connect: $!";
}