From: <am...@us...> - 2011-02-03 21:56:30
|
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. |