SF.net SVN: jython:[7193] trunk/jython

[email protected] Thu, 03 Feb 2011 21:56:23 +0000
Newsgroups gmane.comp.lang.jython.cvs
Message-ID <[email protected]>
Revision: 7193
          http://jython.svn.sourceforge.net/jython/?rev=7193&view=rev
Author:   amak
Date:     2011-02-03 21:56:23 +0000 (Thu, 03 Feb 2011)

Log Message:
-----------
Checking in more support for IPV6, including support for 4-tuples including scope, etc.

Modified Paths:
--------------
    trunk/jython/Lib/socket.py
    trunk/jython/Lib/test/test_socket.py
    trunk/jython/NEWS

Modified: trunk/jython/Lib/socket.py
===================================================================
--- trunk/jython/Lib/socket.py	2011-02-02 10:36:55 UTC (rev 7192)
+++ trunk/jython/Lib/socket.py	2011-02-03 21:56:23 UTC (rev 7193)
@@ -314,26 +314,20 @@
     def __init__(self, socket=None):
         if socket:
             self.jchannel = socket.getChannel()
-            self.host = socket.getInetAddress().getHostAddress()
-            self.port = socket.getPort()
         else:
             self.jchannel = java.nio.channels.SocketChannel.open()
-            self.host = None
-            self.port = None
         self.jsocket = self.jchannel.socket()
         self.socketio = org.python.core.io.SocketIO(self.jchannel, 'rw')
 
-    def bind(self, host, port, reuse_addr):
+    def bind(self, jsockaddr, reuse_addr):
         self.jsocket.setReuseAddress(reuse_addr)
-        self.jsocket.bind(java.net.InetSocketAddress(host, port))
+        self.jsocket.bind(jsockaddr)
 
-    def connect(self, host, port):
-        self.host = host
-        self.port = port
+    def connect(self, jsockaddr):
         if self.mode == MODE_TIMEOUT:
-            self.jsocket.connect(java.net.InetSocketAddress(self.host, self.port), self._timeout_millis)
+            self.jsocket.connect (jsockaddr, self._timeout_millis)
         else:
-            self.jchannel.connect(java.net.InetSocketAddress(self.host, self.port))
+            self.jchannel.connect(jsockaddr)
 
     def finish_connect(self):
         return self.jchannel.finishConnect()
@@ -382,15 +376,11 @@
         (SOL_SOCKET, SO_TIMEOUT):     'SoTimeout',
     }
 
-    def __init__(self, host, port, backlog, reuse_addr):
+    def __init__(self, jsockaddr, backlog, reuse_addr):
         self.jchannel = java.nio.channels.ServerSocketChannel.open()
         self.jsocket = self.jchannel.socket()
-        if host:
-            bindaddr = java.net.InetSocketAddress(host, port)
-        else:
-            bindaddr = java.net.InetSocketAddress(port)
         self.jsocket.setReuseAddress(reuse_addr)
-        self.jsocket.bind(bindaddr, backlog)
+        self.jsocket.bind(jsockaddr, backlog)
         self.socketio = org.python.core.io.ServerSocketIO(self.jchannel, 'rw')
 
     def accept(self):
@@ -422,20 +412,16 @@
         (SOL_SOCKET, SO_TIMEOUT):     'SoTimeout',
     }
 
-    def __init__(self, port=None, address=None, reuse_addr=0):
+    def __init__(self, jsockaddr=None, reuse_addr=0):
         self.jchannel = java.nio.channels.DatagramChannel.open()
         self.jsocket = self.jchannel.socket()
-        if port is not None:
-            if address is not None:
-                local_address = java.net.InetSocketAddress(address, port)
-            else:
-                local_address = java.net.InetSocketAddress(port)
+        if jsockaddr is not None:
             self.jsocket.setReuseAddress(reuse_addr)
-            self.jsocket.bind(local_address)
+            self.jsocket.bind(jsockaddr)
         self.socketio = org.python.core.io.DatagramSocketIO(self.jchannel, 'rw')
 
-    def connect(self, host, port):
-        self.jchannel.connect(java.net.InetSocketAddress(host, port))
+    def connect(self, jsockaddr):
+        self.jchannel.connect(jsockaddr)
 
     def disconnect(self):
         """
@@ -469,12 +455,11 @@
             bytes_sent = self.jchannel.send(byte_buf, socket_address)
         return bytes_sent
 
-    def sendto(self, byte_array, host, port, flags):
-        socket_address = java.net.InetSocketAddress(host, port)
+    def sendto(self, byte_array, jsockaddr, flags):
         if self.mode == MODE_TIMEOUT:
-            return self._do_send_net(byte_array, socket_address, flags)
+            return self._do_send_net(byte_array, jsockaddr, flags)
         else:
-            return self._do_send_nio(byte_array, socket_address, flags)
+            return self._do_send_nio(byte_array, jsockaddr, flags)
 
     def send(self, byte_array, flags):
         if self.mode == MODE_TIMEOUT:
@@ -526,8 +511,7 @@
         else:
             return self._do_receive_nio(0, num_bytes, flags)
 
-# For now, we DO NOT have complete IPV6 support.
-has_ipv6 = False
+has_ipv6 = True # IPV6 FTW!
 
 # Name and address functions
 
@@ -607,6 +591,88 @@
             assert protocol == IPPROTO_UDP, "Only IPPROTO_UDP supported on SOCK_DGRAM sockets"
         return _udpsocket()
 
+class _ip_address_t:
+
+    def __str__(self):
+        return "('%s', %d)" % (self.sockaddr, self.port)
+
+class _ipv4_address_t(_ip_address_t):
+
+    def __init__(self, sockaddr, port, jaddress):
+        self.sockaddr = sockaddr
+        self.port     = port
+        self.jaddress = jaddress
+
+    def __getitem__(self, index):
+        if   0 == index:
+            return self.sockaddr
+        elif 1 == index:
+            return self.port
+        else:
+            raise IndexError()
+
+    def __len__(self):
+        return 2
+
+class _ipv6_address_t(_ip_address_t):
+
+    def __init__(self, sockaddr, port, jaddress):
+        self.sockaddr = sockaddr
+        self.port     = port
+        self.jaddress = jaddress
+
+    def __getitem__(self, index):
+        if   0 == index:
+            return self.sockaddr
+        elif 1 == index:
+            return self.port
+        elif 2 == index:
+            return 0
+        elif 3 == index:
+            return self.jaddress.scopeId
+        else:
+            raise IndexError()
+
+    def __len__(self):
+        return 4
+
+def _get_jsockaddr(address_object, for_udp=False):
+    if address_object is None:
+        return java.net.InetSocketAddress(0) # Let the system pick an ephemeral port
+    if isinstance(address_object, _ip_address_t):
+        return java.net.InetSocketAddress(address_object.jaddress, address_object[1]) 
+    error_message = "Address must be a 2-tuple (ipv4: (host, port)) or a 4-tuple (ipv6: (host, port, flow, scope))"
+    if not isinstance(address_object, tuple) or \
+            len(address_object) not in [2,4] or \
+            not isinstance(address_object[0], basestring) or \
+            not isinstance(address_object[1], (int, long)):
+        raise TypeError(error_message)
+    if len(address_object) == 4 and not isinstance(address_object[3], (int, long)):
+        raise TypeError(error_message)
+    hostname, port = address_object[0].strip(), address_object[1]
+    if for_udp:
+        if hostname == "":
+            hostname = INADDR_ANY
+        elif hostname == "<broadcast>":
+            hostname = INADDR_BROADCAST
+    else:
+        if hostname == "":
+            hostname = None
+    if hostname is None:
+        return java.net.InetSocketAddress(port)
+    if isinstance(hostname, unicode):
+        # XXX: Should be encode('idna') (See CPython
+        # socketmodule::getsockaddrarg), but Jython's idna support is
+        # currently broken
+        hostname = hostname.encode()
+    if len(address_object) == 4:
+        # There is no way to get a Inet6Address: Inet6Address.getByName() simply calls
+        # InetAddress.getByName,() which also returns Inet4Address objects
+        # If users want to use IPv6 address, scoped or not, 
+        # they should use getaddrinfo(family=AF_INET6)
+        pass
+    return java.net.InetSocketAddress(java.net.InetAddress.getByName(hostname), port)
+
 _ipv4_addresses_only = False
 
 def _use_ipv4_addresses_only(value):
@@ -639,11 +705,12 @@
                 else:
                     canonname = asPyString(a.getCanonicalHostName())
                 if host is None and passive_mode and not canonname_mode:
-                    sockname = INADDR_ANY
+                    sockaddr = INADDR_ANY
                 else:
-                    sockname = asPyString(a.getHostAddress())
+                    sockaddr = asPyString(a.getHostAddress())
                 # TODO: Include flowinfo and scopeid in a 4-tuple for IPv6 addresses
-                results.append((family, socktype, proto, canonname, (sockname, port)))
+                sock_tuple = {AF_INET : _ipv4_address_t, AF_INET6 : _ipv6_address_t}[family](sockaddr, port, a)
+                results.append((family, socktype, proto, canonname, sock_tuple))
         return results
     except java.lang.Exception, jlx:
         raise _map_exception(jlx)
@@ -800,24 +867,6 @@
     def _get_jsocket(self):
         return self.sock_impl.jsocket
 
-def _unpack_address_tuple(address_tuple):
-    # TODO: Upgrade to support the 4-tuples used for IPv6 addresses
-    # which include flowinfo and scope_id.
-    # To be upgraded in synch with getaddrinfo
-    error_message = "Address must be a tuple of (hostname, port)"
-    if not isinstance(address_tuple, tuple) or \
-            not isinstance(address_tuple[0], basestring) or \
-            not isinstance(address_tuple[1], (int, long)):
-        raise TypeError(error_message)
-    hostname = address_tuple[0]
-    if isinstance(hostname, unicode):
-        # XXX: Should be encode('idna') (See CPython
-        # socketmodule::getsockaddrarg), but Jython's idna support is
-        # currently broken
-        hostname = hostname.encode()
-    hostname = hostname.strip()
-    return hostname, address_tuple[1]
-
 class _tcpsocket(_nonblocking_api_mixin):
 
     sock_impl = None
@@ -833,7 +882,7 @@
         assert not self.sock_impl
         assert not self.local_addr
         # Do the address format check
-        _unpack_address_tuple(addr)
+        _get_jsockaddr(addr)
         self.local_addr = addr
 
     def listen(self, backlog):
@@ -841,11 +890,7 @@
         try:
             assert not self.sock_impl
             self.server = 1
-            if self.local_addr:
-                host, port = _unpack_address_tuple(self.local_addr)
-            else:
-                host, port = "", 0
-            self.sock_impl = _server_socket_impl(host, port, backlog, self.pending_options[ (SOL_SOCKET, SO_REUSEADDR) ])
+            self.sock_impl = _server_socket_impl(_get_jsockaddr(self.local_addr), backlog, self.pending_options[ (SOL_SOCKET, SO_REUSEADDR) ])
             self._config()
         except java.lang.Exception, jlx:
             raise _map_exception(jlx)
@@ -867,22 +912,14 @@
         except java.lang.Exception, jlx:
             raise _map_exception(jlx)
 
-    def _get_host_port(self, addr):
-        host, port = _unpack_address_tuple(addr)
-        if host == "":
-            host = java.net.InetAddress.getLocalHost()
-        return host, port
-
     def _do_connect(self, addr):
         try:
             assert not self.sock_impl
-            host, port = self._get_host_port(addr)
             self.sock_impl = _client_socket_impl()
             if self.local_addr: # Has the socket been bound to a local address?
-                bind_host, bind_port = _unpack_address_tuple(self.local_addr)
-                self.sock_impl.bind(bind_host, bind_port, self.pending_options[ (SOL_SOCKET, SO_REUSEADDR) ])
+                self.sock_impl.bind(_get_jsockaddr(self.local_addr), self.pending_options[ (SOL_SOCKET, SO_REUSEADDR) ])
             self._config() # Configure timeouts, etc, now that the socket exists
-            self.sock_impl.connect(host, port)
+            self.sock_impl.connect(_get_jsockaddr(addr))
         except java.lang.Exception, jlx:
             raise _map_exception(jlx)
 
@@ -983,7 +1020,7 @@
 class _udpsocket(_nonblocking_api_mixin):
 
     sock_impl = None
-    addr = None
+    connected = False
 
     def __init__(self):
         _nonblocking_api_mixin.__init__(self)
@@ -991,24 +1028,19 @@
     def bind(self, addr):
         try:
             assert not self.sock_impl
-            host, port = _unpack_address_tuple(addr)
-            if host == "":
-                host = INADDR_ANY
-            host_address = java.net.InetAddress.getByName(host)
-            self.sock_impl = _datagram_socket_impl(port, host_address, self.pending_options[ (SOL_SOCKET, SO_REUSEADDR) ])
+            self.sock_impl = _datagram_socket_impl(_get_jsockaddr(addr, True), self.pending_options[ (SOL_SOCKET, SO_REUSEADDR) ])
             self._config()
         except java.lang.Exception, jlx:
             raise _map_exception(jlx)
 
     def _do_connect(self, addr):
         try:
-            host, port = _unpack_address_tuple(addr)
-            assert not self.addr
-            self.addr = addr
+            assert not self.connected, "Datagram Socket is already connected"
             if not self.sock_impl:
                 self.sock_impl = _datagram_socket_impl()
                 self._config()
-                self.sock_impl.connect(host, port)
+                self.sock_impl.connect(_get_jsockaddr(addr))
+            self.connected = True
         except java.lang.Exception, jlx:
             raise _map_exception(jlx)
 
@@ -1029,17 +1061,14 @@
             if not self.sock_impl:
                 self.sock_impl = _datagram_socket_impl()
                 self._config()
-            host, port = _unpack_address_tuple(addr)
-            if host == "<broadcast>":
-                host = INADDR_BROADCAST
             byte_array = java.lang.String(data).getBytes('iso-8859-1')
-            result = self.sock_impl.sendto(byte_array, host, port, flags)
+            result = self.sock_impl.sendto(byte_array, _get_jsockaddr(addr, True), flags)
             return result
         except java.lang.Exception, jlx:
             raise _map_exception(jlx)
 
     def send(self, data, flags=None):
-        if not self.addr: raise error(errno.ENOTCONN, "Socket is not connected")
+        if not self.connected: raise error(errno.ENOTCONN, "Socket is not connected")
         byte_array = java.lang.String(data).getBytes('iso-8859-1')
         return self.sock_impl.send(byte_array, flags)
 

Modified: trunk/jython/Lib/test/test_socket.py
===================================================================
--- trunk/jython/Lib/test/test_socket.py	2011-02-02 10:36:55 UTC (rev 7192)
+++ trunk/jython/Lib/test/test_socket.py	2011-02-03 21:56:23 UTC (rev 7193)
@@ -1502,6 +1502,73 @@
         doAddressTest(socket.getaddrinfo("localhost", 0, socket.AF_UNSPEC, socket.SOCK_STREAM, 0, 0))
         socket._use_ipv4_addresses_only(False)
 
+    def testAddrTupleTypes(self):
+        ipv4_address_tuple = socket.getaddrinfo("localhost", 80, socket.AF_INET, socket.SOCK_STREAM, 0, 0)[0][4]
+        self.failUnlessEqual(ipv4_address_tuple[0], "127.0.0.1")
+        self.failUnlessEqual(ipv4_address_tuple[1], 80)
+        self.failUnlessRaises(IndexError, lambda: ipv4_address_tuple[2])
+        self.failUnlessEqual(str(ipv4_address_tuple), "('127.0.0.1', 80)")
+
+        ipv6_address_tuple = socket.getaddrinfo("localhost", 80, socket.AF_INET6, socket.SOCK_STREAM, 0, 0)[0][4]
+        self.failUnless     (ipv6_address_tuple[0] in ["::1", "0:0:0:0:0:0:0:1"])
+        self.failUnlessEqual(ipv6_address_tuple[1], 80)
+        self.failUnlessEqual(ipv6_address_tuple[2], 0)
+        # Can't have an expectation for scope
+        try:
+            ipv6_address_tuple[3]
+        except IndexError:
+            self.fail("Failed to retrieve third element of ipv6 4-tuple")
+        self.failUnlessRaises(IndexError, lambda: ipv6_address_tuple[4])
+        self.failUnless(str(ipv6_address_tuple) in ["('::1', 80)", "('0:0:0:0:0:0:0:1', 80)"])
+
+class TestJython_get_jsockaddr(unittest.TestCase):
+    "These tests are specific to jython: they test a key internal routine"
+
+    def testIPV4AddressesFromGetAddrInfo(self):
+        local_addr = socket.getaddrinfo("localhost", 80, socket.AF_INET, socket.SOCK_STREAM, 0, 0)[0][4]
+        sockaddr = socket._get_jsockaddr(local_addr)
+        self.failUnless(isinstance(sockaddr, java.net.InetSocketAddress), "_get_jsockaddr returned wrong type: '%s'" % str(type(sockaddr)))
+        self.failUnlessEqual(sockaddr.address.hostAddress, "127.0.0.1")
+        self.failUnlessEqual(sockaddr.port, 80)
+
+    def testIPV6AddressesFromGetAddrInfo(self):
+        local_addr = socket.getaddrinfo("localhost", 80, socket.AF_INET6, socket.SOCK_STREAM, 0, 0)[0][4]
+        sockaddr = socket._get_jsockaddr(local_addr)
+        self.failUnless(isinstance(sockaddr, java.net.InetSocketAddress), "_get_jsockaddr returned wrong type: '%s'" % str(type(sockaddr)))
+        self.failUnless(sockaddr.address.hostAddress in ["::1", "0:0:0:0:0:0:0:1"])
+        self.failUnlessEqual(sockaddr.port, 80)
+
+    def testAddressesFrom2Tuple(self):
+        for addr_tuple in [
+            ("localhost", 80),
+            ]:
+            sockaddr = socket._get_jsockaddr(addr_tuple)
+            self.failUnless(isinstance(sockaddr, java.net.InetSocketAddress), "_get_jsockaddr returned wrong type: '%s'" % str(type(sockaddr)))
+            self.failUnless(sockaddr.address.hostAddress in ["127.0.0.1", "::1", "0:0:0:0:0:0:0:1"])
+            self.failUnlessEqual(sockaddr.port, 80)
+
+    def testAddressesFrom4Tuple(self):
+        # This test disabled: cannot construct IPV6 addresses from 4-tuple
+        return
+        for addr_tuple in [
+            ("localhost", 80, 0, 0),
+            ]:
+            sockaddr = socket._get_jsockaddr(addr_tuple)
+            self.failUnless(isinstance(sockaddr, java.net.InetSocketAddress), "_get_jsockaddr returned wrong type: '%s'" % str(type(sockaddr)))
+            self.failUnless(isinstance(sockaddr.address, java.net.Inet6Address), "_get_jsockaddr returned wrong address type: '%s'" % str(type(sockaddr.address)))
+            self.failUnless(sockaddr.address.hostAddress in ["::1", "0:0:0:0:0:0:0:1"])
+            self.failUnlessEqual(sockaddr.address.scopeId, 0)
+            self.failUnlessEqual(sockaddr.port, 80)
+
+    def testSpecialHostnames(self):
+        for addr_tuple, for_udp, expected_hostname in [
+            ( ("", 80),            False, socket.INADDR_ANY),
+            ( ("", 80),            True,  socket.INADDR_ANY),
+            ( ("<broadcast>", 80), True,  socket.INADDR_BROADCAST),
+            ]:
+            sockaddr = socket._get_jsockaddr(addr_tuple, for_udp)
+            self.failUnlessEqual(sockaddr.hostName, expected_hostname, "_get_jsockaddr returned wrong hostname '%s' for special hostname '%s'" % (sockaddr.hostName, expected_hostname))
+
 class TestExceptions(unittest.TestCase):
 
     def testExceptionTree(self):
@@ -1728,10 +1795,12 @@
     if sys.platform[:4] == 'java':
         tests.append(TestJythonTCPExceptions)
         tests.append(TestJythonUDPExceptions)
+        tests.append(TestJython_get_jsockaddr)
     # TODO: Broadcast requires permission, and is blocked by some firewalls
     # Need some way to discover the network setup on the test machine
     if False:
         tests.append(UDPBroadcastTest)
+#    tests = [TestJython_get_jsockaddr]
     suites = [unittest.makeSuite(klass, 'test') for klass in tests]
     test_support.run_suite(unittest.TestSuite(suites))
 

Modified: trunk/jython/NEWS
===================================================================
--- trunk/jython/NEWS	2011-02-02 10:36:55 UTC (rev 7192)
+++ trunk/jython/NEWS	2011-02-03 21:56:23 UTC (rev 7193)
@@ -5,6 +5,7 @@
     - [ 1667 ] thread.local subclasses with constructor params fail
     - [ 1698 ] warnings module fails under JSR-223
     - [ 1697 ] Wrong error message when http connection can not be established
+    - [ 1210 ] Lib/socket.py doesn't allow IPv6 sockets and fails with an AssertionError
     - [ 1700 ] "virtualenv is not compatible" to 2.5.2rc3
 
 Jython 2.5.2rc3


This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.

------------------------------------------------------------------------------
The modern datacenter depends on network connectivity to access resources
and provide services. The best practices for maximizing a physical server's
connectivity to a physical network are well understood - see how these
rules translate into the virtual world? 
http://p.sf.net/sfu/oracle-sfdevnlfb