Commit d4fe4145 authored by Jim Fulton's avatar Jim Fulton

Bug fixed

- ZEO didn't work with IPv6 addrsses.
  Added IPv6 support contributed by Martin v. Lowis.
parent 88e4dc41
...@@ -164,6 +164,9 @@ http://www.zope.com/Products/ZopeProducts/ZRS. In general, it could ...@@ -164,6 +164,9 @@ http://www.zope.com/Products/ZopeProducts/ZRS. In general, it could
be used to with a system that arranges to provide hot backups of be used to with a system that arranges to provide hot backups of
servers in the case of failure. servers in the case of failure.
If a single address resolves to multiple IPv4 or IPv6 addresses,
the client will connect to an arbitrary of these addresses.
Authentication Authentication
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~
......
...@@ -8,6 +8,9 @@ ...@@ -8,6 +8,9 @@
Bugs fixed Bugs fixed
---------- ----------
- ZEO didn't work with IPv6 addrsses.
Added IPv6 support contributed by Martin v. Löwis.
- Changes in way that garage collection treats dictionaries in Python - Changes in way that garage collection treats dictionaries in Python
2.7 broke the object/connection cache implementation. 2.7 broke the object/connection cache implementation.
(https://bugs.launchpad.net/zodb/+bug/641481) (https://bugs.launchpad.net/zodb/+bug/641481)
......
...@@ -16,6 +16,7 @@ import os ...@@ -16,6 +16,7 @@ import os
import sys import sys
import time import time
import random import random
import socket
import asyncore import asyncore
import threading import threading
import logging import logging
...@@ -1197,3 +1198,18 @@ class MSTThread(threading.Thread): ...@@ -1197,3 +1198,18 @@ class MSTThread(threading.Thread):
c.close() c.close()
except: except:
pass pass
# Run IPv6 tests if V6 sockets are supported
try:
socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
except (socket.error, AttributeError):
pass
else:
class V6Setup:
def _getAddr(self):
return '::1', forker.get_port(self)
_g = globals()
for name, value in _g.items():
if isinstance(value, type) and issubclass(value, CommonSetupTearDown):
_g[name+"V6"] = type(name+"V6", (V6Setup, value), {})
...@@ -1148,13 +1148,13 @@ def client_has_newer_data_than_server(): ...@@ -1148,13 +1148,13 @@ def client_has_newer_data_than_server():
... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE ... # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
ZEO.ClientStorage CRITICAL client ZEO.ClientStorage CRITICAL client
Client has seen newer transactions than server! Client has seen newer transactions than server!
ZEO.zrpc ERROR (...) CW: error in notifyConnected (('localhost', ...)) ZEO.zrpc ERROR (...) CW: error in notifyConnected (('127.0.0.1', ...))
Traceback (most recent call last): Traceback (most recent call last):
... ...
ClientStorageError: client Client has seen newer transactions than server! ClientStorageError: client Client has seen newer transactions than server!
ZEO.ClientStorage CRITICAL client ZEO.ClientStorage CRITICAL client
Client has seen newer transactions than server! Client has seen newer transactions than server!
ZEO.zrpc ERROR (...) CW: error in notifyConnected (('localhost', ...)) ZEO.zrpc ERROR (...) CW: error in notifyConnected (('127.0.0.1', ...))
Traceback (most recent call last): Traceback (most recent call last):
... ...
ClientStorageError: client Client has seen newer transactions than server! ClientStorageError: client Client has seen newer transactions than server!
......
...@@ -188,7 +188,7 @@ class ConnectionManager(object): ...@@ -188,7 +188,7 @@ class ConnectionManager(object):
if (len(addr) == 2 if (len(addr) == 2
and isinstance(addr[0], types.StringType) and isinstance(addr[0], types.StringType)
and isinstance(addr[1], types.IntType)): and isinstance(addr[1], types.IntType)):
return socket.AF_INET return socket.AF_INET # also denotes IPv6
# not anything I know about # not anything I know about
return None return None
...@@ -436,10 +436,25 @@ class ConnectThread(threading.Thread): ...@@ -436,10 +436,25 @@ class ConnectThread(threading.Thread):
del wrappers del wrappers
return 0 return 0
def _expand_addrlist(self):
for domain, (host, port) in self.addrlist:
# AF_INET really means either IPv4 or IPv6, possibly
# indirected by DNS. By design, DNS lookup is deferred
# until connections get established, so that DNS
# reconfiguration can affect failover
if domain == socket.AF_INET:
for (family, socktype, proto, cannoname, sockaddr
) in socket.getaddrinfo(host, port):
# for IPv6, drop flowinfo, and restrict addresses
# to [host]:port
yield family, sockaddr[:2]
else:
yield domain, addr
def _create_wrappers(self): def _create_wrappers(self):
# Create socket wrappers # Create socket wrappers
wrappers = {} # keys are active wrappers wrappers = {} # keys are active wrappers
for domain, addr in self.addrlist: for domain, addr in self._expand_addrlist():
wrap = ConnectWrapper(domain, addr, self.mgr, self.client) wrap = ConnectWrapper(domain, addr, self.mgr, self.client)
wrap.connect_procedure() wrap.connect_procedure()
if wrap.state == "notified": if wrap.state == "notified":
......
...@@ -15,6 +15,23 @@ import asyncore ...@@ -15,6 +15,23 @@ import asyncore
import socket import socket
import types import types
# _has_dualstack: True if the dual-stack sockets are supported
try:
# Check whether IPv6 sockets can be created
s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
except (socket.error, AttributeError):
_has_dualstack = False
else:
# Check whether enabling dualstack (disabling v6only) works
try:
s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, False)
except (socket.error, AttributeError):
_has_dualstack = False
else:
_has_dualstack = True
s.close()
del s
from ZEO.zrpc.connection import Connection from ZEO.zrpc.connection import Connection
from ZEO.zrpc.log import log from ZEO.zrpc.log import log
import ZEO.zrpc.log import ZEO.zrpc.log
...@@ -35,6 +52,20 @@ class Dispatcher(asyncore.dispatcher): ...@@ -35,6 +52,20 @@ class Dispatcher(asyncore.dispatcher):
def _open_socket(self): def _open_socket(self):
if type(self.addr) == types.TupleType: if type(self.addr) == types.TupleType:
if self.addr[0] == '' and _has_dualstack:
# Wildcard listen on all interfaces, both IPv4 and
# IPv6 if possible
self.create_socket(socket.AF_INET6, socket.SOCK_STREAM)
self.socket.setsockopt(
socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, False)
elif ':' in self.addr[0]:
self.create_socket(socket.AF_INET6, socket.SOCK_STREAM)
if _has_dualstack:
# On Linux, IPV6_V6ONLY is off by default.
# If the user explicitly asked for IPv6, don't bind to IPv4
self.socket.setsockopt(
socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, True)
else:
self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
else: else:
self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM) self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM)
...@@ -56,6 +87,9 @@ class Dispatcher(asyncore.dispatcher): ...@@ -56,6 +87,9 @@ class Dispatcher(asyncore.dispatcher):
log("accepted failed: %s" % msg) log("accepted failed: %s" % msg)
return return
# Drop flow-info from IPv6 addresses
addr = addr[:2]
# We could short-circuit the attempt below in some edge cases # We could short-circuit the attempt below in some edge cases
# and avoid a log message by checking for addr being None. # and avoid a log message by checking for addr being None.
# Unfortunately, our test for the code below, # Unfortunately, our test for the code below,
......
...@@ -120,6 +120,10 @@ class SizedMessageAsyncConnection(asyncore.dispatcher): ...@@ -120,6 +120,10 @@ class SizedMessageAsyncConnection(asyncore.dispatcher):
self.__super_init(sock, map) self.__super_init(sock, map)
# asyncore overwrites addr with the getpeername result
# restore our value
self.addr = addr
def setSessionKey(self, sesskey): def setSessionKey(self, sesskey):
log("set session key %r" % sesskey) log("set session key %r" % sesskey)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment