[MLton] Socket.connectNB on NetBSD
Stephen Weeks
MLton@mlton.org
Fri, 28 Nov 2003 13:52:12 -0800
> This succeeds on NetBSD but fails on the university's Redhat 7.3
> boxen. The errno is set at EINPROGRESS (115 in Linux). I agree it
> should fail. Now for the fun part:
>
> Adding a line which sets sa2.sin_addr.s_addr = INADDR_LOOPBACK gives
> the intended failure behaviour on NetBSD.
I see the same behavior. But on my SunOS machine, I still get 0.
Here are some more test programs to figure out what's going on.
Program 1 is the original which binds to port 0 with an unspecified
internet address.
Program 2 drops the listen from program 1.
Program 3 is like 1, but specifies the internet address as 127.0.0.1.
Program 4 drops the listen from program 3.
----------------------------------------------------------------------
(* Program 1 *)
val socket = INetSock.TCP.socket ()
val _ = Socket.bind (socket, INetSock.any 0)
val _ = Socket.listen (socket, 5)
val _ =
print (concat
[Bool.toString (Socket.connectNB (INetSock.TCP.socket (),
Socket.Ctl.getSockName socket)),
"\n"])
(* Program 2 *)
val socket = INetSock.TCP.socket ()
val _ = Socket.bind (socket, INetSock.any 0)
val _ =
print (concat
[Bool.toString (Socket.connectNB (INetSock.TCP.socket (),
Socket.Ctl.getSockName socket)),
"\n"])
(* Program 3 *)
val socket = INetSock.TCP.socket ()
val _ = Socket.bind (socket,
INetSock.toAddr (valOf (NetHostDB.fromString "127.0.0.1"),
0))
val _ = Socket.listen (socket, 5)
val _ =
print (concat
[Bool.toString (Socket.connectNB (INetSock.TCP.socket (),
Socket.Ctl.getSockName socket)),
"\n"])
(* Program 4 *)
val socket = INetSock.TCP.socket ()
val _ = Socket.bind (socket,
INetSock.toAddr (valOf (NetHostDB.fromString "127.0.0.1"),
0))
val _ =
print (concat
[Bool.toString (Socket.connectNB (INetSock.TCP.socket (),
Socket.Ctl.getSockName socket)),
"\n"])
----------------------------------------------------------------------
Here are the results of running the programs on the three platforms.
prog 1 prog 2 prog 3 prog 4
Linux false false false false
NetBSD true false true false
SunOS true error true error
The error was
unhandled exception: SysErr: Connection refused [<UNKNOWN>]
Columns 1 and 3 are the same, which shows that it doesn't matter
whether the address is unspecified or is 127.0.0.1. The data also
shows that as long as the listen has happened, then the connectNB
succeeds. If the listen hasn't happened, then the connect fails,
but in different ways. On Linux and NetBSD, the connect fails with
EINPROGRESS, while on SunOS the connect fails with ECONNREFUSED. I
guess both of those are allowable socket behavior.
So, my conjecture as to what's going on is that the socket
implementation on NetBSD and SunOS are "optimizing" the connect even
though there hasn't been an accept yet on the other end. That is,
because the listen has happened, they go ahead and create the socket
pair. They then arrange for the first accept to get the appropriate
end of the pair.
The following program shows that this is indeed hapenning.
----------------------------------------------------------------------
val socket = INetSock.TCP.socket ()
val _ = Socket.bind (socket, INetSock.any 0)
val _ = Socket.listen (socket, 5)
val socket' = INetSock.TCP.socket ()
val b = Socket.connectNB (socket', Socket.Ctl.getSockName socket)
val _ =
if b
then
let
val (socket'', _) = Socket.accept socket
fun msg (name, s) =
let
fun z a =
let
val (in_addr, port) = INetSock.fromAddr a
in
concat [NetHostDB.toString in_addr,
":", Int.toString port]
end
in
print (concat [name, " ",
z (Socket.Ctl.getSockName s), " ",
z (Socket.Ctl.getPeerName s), "\n"])
end
val _ = msg ("socket'", socket')
val _ = msg ("socket''", socket'')
in
()
end
else
print "OK\n"
----------------------------------------------------------------------
Here is the output of the program on the three platforms.
Linux
OK
NetBSD
socket' 127.0.0.1:65365 127.0.0.1:65366
socket'' 127.0.0.1:65366 127.0.0.1:65365
SunOS
socket' 127.0.0.1:32833 127.0.0.1:32832
socket'' 127.0.0.1:32832 127.0.0.1:32833
NetBSD and SunOS can do this optimization when both endpoints are on
the same machine. As the following program shows, all three platforms
perform the same when doing a connectNB to another machine that isn't
listening.
------------------------------------------------------------
val _ =
print (concat
[Bool.toString
(Socket.connectNB
(INetSock.TCP.socket (),
INetSock.toAddr (NetHostDB.addr
(valOf (NetHostDB.getByName "www.mlton.org")),
4000))),
"\n"])
------------------------------------------------------------
This program prints false on Linux, NetBSD, and SunOS.
My conclusion is that we have encountered a difference in socket
behavior due to an optimization performed by NetBSD and SunOS but not
by Linux. I don't see any reason why we shouldn't expose this
difference in behavior. So, I'm going to remove the test from our
socket regression.