Issue1005

classification
Title: UDP Socket implicit create and unbound socket timeout
Type: behaviour Severity: normal
Components: Library Versions: 2.2.2
Milestone:
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: amak Nosy List: amak, fwierzbicki, rluse
Priority: Keywords:

Created on 2008-03-06.13:39:07 by rluse, last changed 2008-03-11.19:55:55 by amak.

Messages
msg3060 (view) Author: Bob Luse (rluse) Date: 2008-03-06.13:39:06
1. The UDP socket implicitly created by the send/sendto methods is not
correctly configured (in relation to timeouts, etc).

2. Calling recv/recvfrom methods on an unbound UDP socket raises an
AssertionError (because a socket is not implicitly created).
msg3061 (view) Author: Alan Kennedy (amak) Date: 2008-03-06.13:52:55
See the jython-dev archives for further information.

http://www.nabble.com/UDP-Timeout-td15831206.html
http://sourceforge.net/mailarchive/forum.php?thread_name=8CA4C2CA69796CB-1590-4B7B%40FWM-M17.sysops.aol.com&forum_name=jython-dev
msg3068 (view) Author: Alan Kennedy (amak) Date: 2008-03-07.15:05:28
I've been reading up this bug some more, and realised that I've been
missing out on Bob's subtle point of implicitly created sockets being
bound to *some* address, even if not to a specific address, by virtue of
their creation; I'll refer to this address as the "assigned address".


So, what is the actual assigned address, in Java? Consider the following
code

//--------------------------------------------------------------
import java.io.IOException;

import java.nio.channels.DatagramChannel;

import java.nio.ByteBuffer;

import java.net.DatagramSocket;

import java.net.InetSocketAddress;



class udp_unbound_address

{

    public static void main ( String args[] )

        throws IOException

    {

        DatagramChannel chan = DatagramChannel.open();

        if (args.length > 0 && "sendto".compareTo(args[0]) == 0)

            chan.send(ByteBuffer.wrap("testdata".getBytes()), 

                new InetSocketAddress("www.jython.org", 8888));

        DatagramSocket sock = chan.socket();

        System.out.println("Is bound?: " + sock.isBound());

        System.out.println("Local address is: "

            + sock.getLocalAddress());

        System.out.println("Local port is: "

            + sock.getLocalPort());

        System.out.println("Local socket address is: "

            + sock.getLocalSocketAddress());

    }

}
//--------------------------------------------------------------

If you run this, you see that there is no assigned address when sockets
are created with java.nio. Here is the output from running on Unbuntu
7.10. (I get similar output on Windows Server 2003)

$ java -cp . udp_unbound_address 
Is bound?: false
Local address is: 0.0.0.0/0.0.0.0
Local port is: 0
Local socket address is: null

Making the socket do a sendto() first changes the picture slightly, in
that the socket now becomes bound, but there is still no assigned
hostname/IP or port.

$ java -cp . udp_unbound_address sendto
Is bound?: true
Local address is: 0.0.0.0/0.0.0.0
Local port is: 0
Local socket address is: 0.0.0.0/0.0.0.0:0

Note that this is different behaviour from the way old
java.net.DatagramSocket's behaved, which was to implicitly assign a port
to the socket, but not an IP address, regardless of the use of sendto.
Consider this code

//--------------------------------------------------------------
import java.io.IOException;

import java.net.DatagramPacket;

import java.net.DatagramSocket;

import java.net.InetSocketAddress;



class udp_unbound_address_net

{

    public static void main ( String args[] )

        throws IOException

    {

        DatagramSocket sock = new DatagramSocket();

        if (args.length > 0 && "sendto".compareTo(args[0]) == 0)

            sock.send(new DatagramPacket("testdata".getBytes(), 
                "testdata".getBytes().length,
                new InetSocketAddress("www.jython.org", 8888)
            ));

        System.out.println("Is bound?: " + sock.isBound());

        System.out.println("Local address is: "

            + sock.getLocalAddress());

        System.out.println("Local port is: "

            + sock.getLocalPort());

        System.out.println("Local socket address is: "

            + sock.getLocalSocketAddress());

    }

}
//--------------------------------------------------------------

Which gives the following results on Ubuntu, and similar on windows.

$ java -cp . udp_unbound_address_net
Is bound?: true
Local address is: 0.0.0.0/0.0.0.0
Local port is: 33066
Local socket address is: 0.0.0.0/0.0.0.0:33066

$ java -cp . udp_unbound_address_net sendto
Is bound?: true
Local address is: 0.0.0.0/0.0.0.0
Local port is: 33067
Local socket address is: 0.0.0.0/0.0.0.0:33067

(It is important to note that we cannot create UDP sockets using the old
java.net classes; to do so would prevent us from getting non-blocking
IO: it is only possible to do non-blocking IO on sockets created through
java.nio APIs).

Now lets look at cpython. If you try to get the address of a newly
created UDP socket, the result is an error on Windows

Python 2.4.4 (#71, Oct 18 2006, 08:34:43) [MSC v.1310 32 bit (Intel)] on
win32

>>> import socket

>>> s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

>>> s.getsockname()

Traceback (most recent call last):

  File "<stdin>", line 1, in ?

  File "<string>", line 1, in getsockname

socket.error: (10022, 'Invalid argument')

>>>

And on Unbuntu, we get the same result as Java, i.e. a null address

Python 2.5.1 (r251:54863, Oct  5 2007, 13:36:32) 
[GCC 4.1.3 20070929 (prerelease) (Ubuntu 4.1.2-16ubuntu2)] on linux2
>>> import socket
>>> s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
>>> s.getsockname()
('0.0.0.0', 0)
>>> 

If we do a sendto before getting the sockname, then the situation
changes slightly on cpython

On both windows and Unbuntu, we now get a assigned port number, but
still a null machine address

Python 2.4.4 (#71, Oct 18 2006, 08:34:43) [MSC v.1310 32 bit (Intel)] on
win32

>>> import socket

>>> s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

>>> s.sendto("testdata", ("www.jython.org", 8888) )

8

>>> s.getsockname()

('0.0.0.0', 1246)

>>>

Python 2.5.1 (r251:54863, Oct  5 2007, 13:36:32) 
[GCC 4.1.3 20070929 (prerelease) (Ubuntu 4.1.2-16ubuntu2)] on linux2
>>> import socket
>>> s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
>>> s.sendto("testdata", ("www.jython.org", 8888) )
8
>>> s.getsockname()
('0.0.0.0', 33063)
>>> 

So, let's try to relate that to our problem about whether or not
implicitly create a socket on unbound recvfrom, or raise an exception.
Implicitly creating a socket is only useful if there is a usable (i.e.
connectable/sendable from a different socket) assigned address, that can
somehow be publicized (other than sent through the socket itself). But
there is no usable assigned address, in java or cpython, so I see no
point in implicitly creating the socket; it can be used for nothing.

It is interesting to note that there is an outstanding bug in the Sun
database which complains that "The DatagramChannel.receive method does
not specify the behavior when the channel is not bound."


http://bugs.sun.com/view_bug.do?bug_id=6621689

It says

"""
Since 1.4, our implementation has returned null, even for the blocking
case. Throw NotNotBoundException or explicitly binding the channel's
socket might have been better choices but it cannot now be changed.
"""

So the original java.nio interpretation was different again; it
recognised the unbound state of the socket and returned null for all
receive() operations . Of the two alternatives proposed, i.e.

1. throw a NotBoundException
2. explicitly binding the channel's socket

I still favour the first, because it clearly illustrates to the user
that whatever they think they are expecting is not going to happen.
Cpython favors the second option, but you get different behaviour on
different platforms (see below).

If it were possible to get an usable assigned address for the implicitly
created socket, then it *might* be useful to implicitly create the
socket. But, as demonstrated above, there is no usable address.

It is worth noting that bug #6621689 has been accepted as a bug, and
that the fix is in progress, but no indication is given of what the
chosen fix is.

Lastly, I wanted to point out that there is different behaviour from
cpython on different platforms when a recvfrom is attempted on an
unbound socket.

Windows raises an error

c:\Python24>python

Python 2.4.4 (#71, Oct 18 2006, 08:34:43) [MSC v.1310 32 bit (Intel)] on
win32

>>> import socket

>>> s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

>>> s.recvfrom(1024)

Traceback (most recent call last):

  File "<stdin>", line 1, in ?

socket.error: (10022, 'Invalid argument')


Whereas Ubuntu hangs on the same code

Python 2.5.1 (r251:54863, Oct  5 2007, 13:36:32) 
[GCC 4.1.3 20070929 (prerelease) (Ubuntu 4.1.2-16ubuntu2)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import socket
>>> s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
>>> result = s.recvfrom(1024)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyboardInterrupt

In Bob's original example, setting a timeout seems to have modified this
behaviour, so that both platforms run the code and timeout, i.e.

Python 2.4.4 (#71, Oct 18 2006, 08:34:43) [MSC v.1310 32 bit (Intel)] on
win32

Type "help", "copyright", "credits" or "license" for more information.

>>> import socket

>>> s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

>>> s.settimeout(1.0)

>>> s.recvfrom(1024)

Traceback (most recent call last):

  File "<stdin>", line 1, in ?

socket.timeout: timed out

>>>

Python 2.5.1 (r251:54863, Oct  5 2007, 13:36:32) 
[GCC 4.1.3 20070929 (prerelease) (Ubuntu 4.1.2-16ubuntu2)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import socket
>>> s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
>>> s.settimeout(1.0)
>>> s.recvfrom(1024)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
socket.timeout: timed out

So, in summary, this whole area seems to be poorly understand, and
varyingly implemented.

Cpython gives different behaviour across different platforms, but gives
consistent behaviour for Bob's original code.

Java gives consistent behaviour on all platforms. 

But if we adopt the stance of "implicit create sockets for unbound
recvfrom()s", then there is nothing can be done with the resulting
socket; on the java platform anyway. (although perhaps the fix to bug
#6621689 might change this).

I need to think about this over the weekend.

All opinions welcome.

Alan.
msg3069 (view) Author: Bob Luse (rluse) Date: 2008-03-07.17:01:47
[Alan]
So, in summary, this whole area seems to be poorly understand, and
varyingly implemented.

Truer words were never spoken.

Excellent analysis Alan.  From my view, the question is whether you can
you do a receive on an 'unbound' socket that has a timeout set.  I also
understand that with the history of c and unix and the way Java has been
  implemented, the design of a Jython solution is not easy.

I think the Python implementation is reasonable since if you don't set a
timeout and then do a recv on an 'unbound' port, the timeout defaults to
None which means the program will hang for ever and so it throws an
error.  But, if you set a timeout, it allows you to do the recv and then
throws an exception when the timer kicks off, and then the program
continues.    


Which means the following is really a 1 second delay;

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.settimeout(1.0)
result = s.recvfrom(1024)

I understand your point that the recvfrom will never actually receive a
packet and so it should be an error.  But consider the following:

i = 5
j = 10
print i

j is never used so is that an error?  I don't know but in a language
like Python or Jython, I would expect it to run.

So:

----------------------------
import socket

i = 5

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

result = s.recvfrom(1024)

print i
-------------------

The print i statement will never be run, so I think it is reasonable
here to throw an exception on the recvfrom.

But, if you add the settimeout, then:

----------------------------
import socket

i = 5

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s = settimeout(1.0)
result = s.recvfrom(1024)

print i
----------------

This will/should delay for a second then print '5' and then terminate
normally. 

I would expect that this is the way a program would work in Python or
Jython. 

Now, when you do a sendto, then:

----------------------------
import socket

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.sendto("testdata", ('192.168.1.200', 10000))

result = s.recvfrom(1024)
--------------------------------------

In this case,whether or not a timeout is set, if there is a program
receiving at 192.168.1.200 port 10000, it will receive the "testdata"
packet and the hostname and port of our program that is attached to s.
The script doesn't really care what the number of the port is that the
OS attached to s just so we get our data when packets arrive back at
that port.

Finally, consider a multi threaded case. (the actual threaded scripts
have not been tested) 

----------------------------
import socket

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.settimeout(2.0)

Have one thread that has access to s do the following:

while(True): 
    try:
        result = s.recvfrom(1024)
    except timeout, to:
        pass

And another thread that shares s do this:

------------------------------------------

while (True):

    time.sleep(10.0)

    s.sendto("testdata", ("192.168.1.200", 10000))

------------------------------------------------

It seems reasonable to me to expect that to work.

I honestly don't know which way is 'right',  but I think you are in for
a lot of push back if you make this change.

Let me know if you want more of my input.  And have a good weekend.
msg3070 (view) Author: Bob Luse (rluse) Date: 2008-03-08.02:41:35
Hi Alan,

Well I've done some homework today too, and I have found some
interesting things.  First, the IETF standard for UDP, RFC 768 is still
in effect.  It is dated August 1980.  Yes, Jimmy Carter was the US
President and I was programming on a Control Data main frame with FORTRAN!

Anyway, it is literally less than 3 pages long and you can read it in
less than 5 minutes.  The lines that mainly concern us are these:


User Interface
--------------

A user interface should allow

  the creation of new receive ports,

  receive  operations  on the receive  ports that return the data octets
  and an indication of source port and source address,

  and an operation  that allows  a datagram  to be sent,  specifying the
  data, source and destination ports and addresses to be sent.
-----

That's it.  User interface in this case means the application above UDP.

I also have finally bought and begun to read:

UNIX Network Programming Volume 1, Third Edition: The Sockets Networking API
By 	W. Richard Stevens; Bill Fenner; Andrew M. Rudoff

It is a bit slow going as you may expect, but I can already recommend it
to you.  It talks about the history and the common problems that have
developed.

It is interesting that he calls what you just started calling assigned
ports, ephemeral ports.  I like your name better. He also talks about
the history of why UDP does not notify the application when it receives
the ICMP message saying that there is no server on the port.  It also
talks about host addresses.  That there are situations where the local
address can change with with every send to the same server.  That looks
like it may explain some of the issues you were finding.     

For our case, you already fixed this situation:

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.sendto('testdata', ('192.168.1.103', 375))

buf, addr = s.recvfrom(2048)

If the server is up and it responds with a packet back, then this script
should read the packet and then the script should terminate.  Otherwise,
it should hang forever.  But, one thing that I wasn't thinking about was
this case:

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.sendto('testdata', ('192.168.1.103', 375))
s.sendto('testdata', ('192.168.1.104', 375))  # <- different host
s.sendto('testdata', ('192.168.1.104', 3750))  # <- different port

buf, addr = s.recvfrom(2048)

Multiple sends to multiple server through the UDP port. 
This should respond just like the previous one.  If it gets one packet
from any of these servers, it will read it and terminate, otherwise it
will hang forever.  Interestingly, your code handles this, but CPython
does not!  It is clearly a bug in CPython.  I haven't tried Java yet and
I am also setting up to try the equivalent C programs.

For the other case, when there is a recv with no send, I am coming
closer and closer to your opinion, that an error should be generated
rather than allowing the read.  I guess my main concern was that
existing CPython code that was running may break.  But, maybe that is a
good thing.  What I am going to do is run the equivalent in c of the
following two examples and see how c handles it.

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

buf, addr = s.recvfrom(2048)


and

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.settimout(2.0)
buf, addr = s.recvfrom(2048)

I will let you know the results.
msg3072 (view) Author: Alan Kennedy (amak) Date: 2008-03-08.16:17:28
Thanks for all your research Bob, it has been most enlightening.

I have checked in some changes, namely

1. The simple fix for the obvious bug relating to failure to configure
the socket implicitly created by _udpsocket.sendto()

2. Cpython compatible behaviour for the recvfrom() method, in the case
where the socket is unbound.

In relation to the latter bug, I have checked in both options in the
code, but commented out my favoured approach, which is to raise an
exception.

Depending on further research results, the outcome of the Sun bug
relating to DatagramChannel.receive, and community input, we may yet
choose the exception route.

I'm leaving this bug open until we make a final decision.
msg3073 (view) Author: Bob Luse (rluse) Date: 2008-03-09.08:23:19
I think that for the recv/recvfrom issue that this is a very reasonable
solution.  Jython 2.2.2 will test this even more.

I still think there is problem with the send/sendto timeout.  (I will
open a separate problem if you like, just let me know).   


----------------------
import socket

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

print s.gettimeout()

s.settimeout(1.0)    # settimeout here done before the sendto

print s.gettimeout()

s.sendto('testdata', ('192.168.1.103', 9876))

print s.gettimeout()

print 'The program hangs here, even though s.gettimeout = 1.0'

buf, addr = s.recvfrom(2048)

---------------------------------------------

When the settimeout is performed after the sendto, it works properly.

I suspect that when config is run, that it sets the timeout to default
which is None, but the value of timeout in for the script is still 1.0.   

(By the way, because of this testing, I will be opening a problem with
Python 2.5.2 on Windows).
msg3074 (view) Author: Alan Kennedy (amak) Date: 2008-03-09.16:58:27
[Bob]
> I still think there is problem with the send/sendto timeout.
> (I will open a separate problem if you like, just let me know).

Are you sure about that?

Your code works just fine for me, on windows at least. (And I see no
reason it should operate differently on other platforms).

Here is my version, with some added print statements

#-=-=-=-=-=-=-=
import socket

timeout = 2.0

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

print "Before timeout set, timeout is %s" % s.gettimeout()
s.settimeout(timeout)    # settimeout here done before the sendto
print "After timeout set, timeout is %s" % s.gettimeout()

s.sendto('testdata', ('192.168.1.103', 9876))
print "After socket created, timeout is %s" % s.gettimeout()

print "Under the covers, jsocket timeout is %d milliseconds" %
s._sock.sock_impl.jsocket.getSoTimeout()

print 'The program hangs here, even though s.gettimeout = %1.1lf' % timeout

buf, addr = s.recvfrom(2048)
#-=-=-=-=-=-=-=

Which gives the following output
#-------------------------------------
Before timeout set, timeout is None
After timeout set, timeout is 2.0
After socket created, timeout is 2.0
Under the covers, jsocket timeout is 2000 milliseconds
The program hangs here, even though s.gettimeout = 2.0
Traceback (innermost last):
  File "bob_luse_20080709.py", line 18, in ?
  File "C:\jython\release_22_maint\Lib\socket.py", line 785, in recvfrom
timeout: timed out
#-------------------------------------

Are you sure that you have the latest version of the code?

I would expect to see the behaviour you describe *only* if the new
one-line addition from revision 4194 to the sendto method has not been
made, i.e. line 753 should read "self._config()"; if it does not, you
have an old version.
msg3075 (view) Author: Bob Luse (rluse) Date: 2008-03-09.22:04:32
My apologies.  I thought I had your last change in my test, but I did
not.  It now works perfectly forme a s well.  Thanks.
msg3084 (view) Author: Alan Kennedy (amak) Date: 2008-03-11.19:55:55
On re-reading the Sun java bug report listed below, I see that the
proposed resolution is to behave as cpython does, i.e. to implicitly
open the socket.

Since implicit opening is what both cpython and java programmers will
expect, we should adopt that resolution as well.

The code to implement it is already checked in, so I'm closing this bug.
History
Date User Action Args
2008-03-11 19:55:56amaksetstatus: open -> closed
resolution: fixed
messages: + msg3084
2008-03-09 22:04:32rlusesetmessages: + msg3075
2008-03-09 16:58:28amaksetmessages: + msg3074
2008-03-09 08:23:20rlusesetmessages: + msg3073
2008-03-08 16:17:29amaksetmessages: + msg3072
2008-03-08 02:41:36rlusesetmessages: + msg3070
2008-03-07 17:01:47rlusesetmessages: + msg3069
2008-03-07 15:05:29amaksetmessages: + msg3068
2008-03-06 18:57:31fwierzbickisetnosy: + fwierzbicki
2008-03-06 13:52:55amaksetassignee: amak
messages: + msg3061
2008-03-06 13:39:07rlusecreate