def setSessionKey(self, sesskey): log("set session key %r" % sesskey) # Low-level construction is now delayed until data are sent. # This is to allow use of iterators that generate messages # only when we're ready to do I/O so that we can effeciently # transmit large files. Because we delay messages, we also # have to delay setting the session key to retain proper # ordering. # The low-level output queue supports strings, a special close # marker, and iterators. It doesn't support callbacks. We # can create a allback by providing an iterator that doesn't # yield anything. # The hack fucntion below is a callback in iterator's # clothing. :) It never yields anything, but is a generator # and thus iterator, because it contains a yield statement. def hack(): self.__hmac_send = hmac.HMAC(sesskey, digestmod=ZEO.hash) self.__hmac_recv = hmac.HMAC(sesskey, digestmod=ZEO.hash) if False: yield b'' self.message_output(hack())
def close(self): """Prevent ConnectionManager from opening new connections""" self.closed = 1 self.cond.acquire() try: t = self.thread self.thread = None finally: self.cond.release() if t is not None: log("CM.close(): stopping and joining thread") t.stop() t.join(30) if t.isAlive(): log("CM.close(): self.thread.join() timed out", level=logging.WARNING) for fd, obj in list(self.map.items()): if obj is not self.trigger: try: obj.close() except: logging.getLogger(__name__ + '.' + self.__class__.__name__).critical( "Couldn't close a dispatcher.", exc_info=sys.exc_info()) self.map.clear() self.trigger.pull_trigger() try: self.loop_thread.join(9) except RuntimeError: pass # we are the thread :) self.trigger.close()
def close(self): """Prevent ConnectionManager from opening new connections""" self.closed = 1 self.cond.acquire() try: t = self.thread self.thread = None finally: self.cond.release() if t is not None: log("CM.close(): stopping and joining thread") t.stop() t.join(30) if t.isAlive(): log("CM.close(): self.thread.join() timed out", level=logging.WARNING) for fd, obj in list(self.map.items()): if obj is not self.trigger: try: obj.close() except: logging.getLogger(__name__+'.'+self.__class__.__name__ ).critical( "Couldn't close a dispatcher.", exc_info=sys.exc_info()) self.map.clear() self.trigger.pull_trigger() try: self.loop_thread.join(9) except RuntimeError: pass # we are the thread :) self.trigger.close()
def message_output(self, message): if __debug__: if self._debug: log("message_output %d bytes: %s hmac=%d" % (len(message), short_repr(message), self.__hmac_send and 1 or 0), level=TRACE) if self.__closed: raise DisconnectedError( "This action is temporarily unavailable.<p>") self.__output_lock.acquire() try: # do two separate appends to avoid copying the message string if self.__hmac_send: self.__output.append(struct.pack(">I", len(message) | MAC_BIT)) self.__hmac_send.update(message) self.__output.append(self.__hmac_send.digest()) else: self.__output.append(struct.pack(">I", len(message))) if len(message) <= SEND_SIZE: self.__output.append(message) else: for i in range(0, len(message), SEND_SIZE): self.__output.append(message[i:i + SEND_SIZE]) finally: self.__output_lock.release()
def handle_accept(self): try: sock, addr = self.accept() except socket.error as msg: log("accepted failed: %s" % msg) return # We could short-circuit the attempt below in some edge cases # and avoid a log message by checking for addr being None. # Unfortunately, our test for the code below, # quick_close_doesnt_kill_server, causes addr to be None and # we'd have to write a test for the non-None case, which is # *even* harder to provoke. :/ So we'll leave things as they # are for now. # It might be better to check whether the socket has been # closed, but I don't see a way to do that. :( # Drop flow-info from IPv6 addresses if addr: # Sometimes None on Mac. See above. addr = addr[:2] try: c = self.factory(sock, addr) except: if sock.fileno() in asyncore.socket_map: del asyncore.socket_map[sock.fileno()] ZEO.zrpc.log.logger.exception("Error in handle_accept") else: log("connect from %s: %s" % (repr(addr), c))
def try_connecting(self, timeout): """Try connecting to all self.mgr.addrlist addresses. Return 1 if a preferred connection was found; 0 if no connection was found; and -1 if a fallback connection was found. If no connection is found within timeout seconds, return 0. """ log("CT: attempting to connect on %d sockets" % len(self.mgr.addrlist)) deadline = time.time() + timeout wrappers = self._create_wrappers() for wrap in wrappers.keys(): if wrap.state == "notified": return 1 try: if time.time() > deadline: return 0 r = self._connect_wrappers(wrappers, deadline) if r is not None: return r if time.time() > deadline: return 0 r = self._fallback_wrappers(wrappers, deadline) if r is not None: return r # Alas, no luck. assert not wrappers finally: for wrap in wrappers.keys(): wrap.close() del wrappers return 0
def setSessionKey(self, sesskey): log("set session key %r" % sesskey) # Low-level construction is now delayed until data are sent. # This is to allow use of iterators that generate messages # only when we're ready to do I/O so that we can effeciently # transmit large files. Because we delay messages, we also # have to delay setting the session key to retain proper # ordering. # The low-level output queue supports strings, a special close # marker, and iterators. It doesn't support callbacks. We # can create a allback by providing an iterator that doesn't # yield anything. # The hack fucntion below is a callback in iterator's # clothing. :) It never yields anything, but is a generator # and thus iterator, because it contains a yield statement. def hack(): self.__hmac_send = hmac.HMAC(sesskey, digestmod=ZEO.hash) self.__hmac_recv = hmac.HMAC(sesskey, digestmod=ZEO.hash) if False: yield '' self.message_output(hack())
def message_output(self, message): if __debug__: if self._debug: log("message_output %d bytes: %s hmac=%d" % (len(message), short_repr(message), self.__hmac_send and 1 or 0), level=TRACE) if self.__closed: raise DisconnectedError( "This action is temporarily unavailable.<p>") self.__output_lock.acquire() try: # do two separate appends to avoid copying the message string if self.__hmac_send: self.__output.append(struct.pack(">I", len(message) | MAC_BIT)) self.__hmac_send.update(message) self.__output.append(self.__hmac_send.digest()) else: self.__output.append(struct.pack(">I", len(message))) if len(message) <= SEND_SIZE: self.__output.append(message) else: for i in range(0, len(message), SEND_SIZE): self.__output.append(message[i:i+SEND_SIZE]) finally: self.__output_lock.release()
def _open_socket(self): if type(self.addr) == types.TupleType: self.create_socket(socket.AF_INET, socket.SOCK_STREAM) else: self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM) self.set_reuse_addr() log("listening on %s" % str(self.addr), logging.INFO) self.bind(self.addr) self.listen(5)
def decode(self, msg): """Decodes msg and returns its parts""" unpickler = cPickle.Unpickler(StringIO(msg)) unpickler.find_global = server_find_global try: return unpickler.load() # msgid, flags, name, args except: log("can't decode message: %s" % short_repr(msg), level=logging.ERROR) raise
def server_decode(msg): """Decodes msg and returns its parts""" unpickler = Unpickler(StringIO(msg)) unpickler.find_global = server_find_global try: return unpickler.load() # msgid, flags, name, args except: log("can't decode message: %s" % short_repr(msg), level=logging.ERROR) raise
def connect_done(self, conn, preferred): # Called by ConnectWrapper.notify_client() after notifying the client log("CM.connect_done(preferred=%s)" % preferred) self.cond.acquire() try: self.connection = conn if preferred: self.thread = None self.cond.notifyAll() # Wake up connect(sync=1) finally: self.cond.release()
def decode(msg): """Decodes msg and returns its parts""" unpickler = Unpickler(BytesIO(msg)) unpickler.find_global = find_global try: unpickler.find_class = find_global # PyPy, zodbpickle, the non-c-accelerated version except AttributeError: pass try: return unpickler.load() # msgid, flags, name, args except: log("can't decode message: %s" % short_repr(msg), level=logging.ERROR) raise
def close_conn(self, conn): # Called by the connection when it is closed self.cond.acquire() try: if conn is not self.connection: # Closing a non-current connection log("CM.close_conn() non-current", level=BLATHER) return log("CM.close_conn()") self.connection = None finally: self.cond.release() self.client.notifyDisconnected() if not self.closed: self.connect()
class Dispatcher(asyncore.dispatcher): """A server that accepts incoming RPC connections""" __super_init = asyncore.dispatcher.__init__ def __init__(self, addr, factory=Connection): self.__super_init() self.addr = addr self.factory = factory self._open_socket() def _open_socket(self): if type(self.addr) == types.TupleType: self.create_socket(socket.AF_INET, socket.SOCK_STREAM) else: self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM) self.set_reuse_addr() log("listening on %s" % str(self.addr), logging.INFO) self.bind(self.addr) self.listen(5) def writable(self): return 0 def readable(self): return 1 def handle_accept(self): try: sock, addr = self.accept() except socket.error, msg: log("accepted failed: %s" % msg) return c = self.factory(sock, addr) log("connect from %s: %s" % (repr(addr), c))
def notify_client(self): """Call the client's notifyConnected(). If this succeeds, call the manager's connect_done(). If the client is already connected, we assume it's a fallback connection, and the new connection must be a preferred connection. The client will close the old connection. """ try: self.client.notifyConnected(self.conn) except: log("CW: error in notifyConnected (%s)" % repr(self.addr), level=logging.ERROR, exc_info=True) self.close() return self.state = "notified" self.mgr.connect_done(self.conn, self.preferred)
def run(self): delay = self.mgr.tmin success = 0 # Don't wait too long the first time. # TODO: make timeout configurable? attempt_timeout = 5 while not self.stopped: success = self.try_connecting(attempt_timeout) if not self.one_attempt.isSet(): self.one_attempt.set() attempt_timeout = 75 if success > 0: break time.sleep(delay) if self.mgr.is_connected(): log("CT: still trying to replace fallback connection", level=logging.INFO) delay = min(delay * 2, self.mgr.tmax) log("CT: exiting thread: %s" % self.getName())
def connect(self, sync=0): self.cond.acquire() try: if self.connection is not None: return t = self.thread if t is None: log("CM.connect(): starting ConnectThread") self.thread = t = ConnectThread(self, self.client) t.setDaemon(1) t.start() if sync: while self.connection is None and t.isAlive(): self.cond.wait(self.sync_wait) if self.connection is None: log("CM.connect(sync=1): still waiting...") assert self.connection is not None finally: self.cond.release()
def run(self): delay = self.mgr.tmin success = 0 # Don't wait too long the first time. # TODO: make timeout configurable? attempt_timeout = 5 while not self.stopped: success = self.try_connecting(attempt_timeout) if not self.one_attempt.isSet(): self.one_attempt.set() attempt_timeout = 75 if success > 0: break time.sleep(delay) if self.mgr.is_connected(): log("CT: still trying to replace fallback connection", level=logging.INFO) delay = min(delay*2, self.mgr.tmax) log("CT: exiting thread: %s" % self.getName())
def __init__(self, domain, addr, mgr, client): """Store arguments and create non-blocking socket.""" self.domain = domain self.addr = addr self.mgr = mgr self.client = client # These attributes are part of the interface self.state = "closed" self.sock = None self.conn = None self.preferred = 0 log("CW: attempt to connect to %s" % repr(addr)) try: self.sock = socket.socket(domain, socket.SOCK_STREAM) except socket.error as err: log("CW: can't create socket, domain=%s: %s" % (domain, err), level=logging.ERROR) self.close() return self.sock.setblocking(0) self.state = "opened"
def test_connection(self): """Establish and test a connection at the zrpc level. Call the client's testConnection(), giving the client a chance to do app-level check of the connection. """ self.conn = ManagedClientConnection(self.sock, self.addr, self.mgr) self.sock = None # The socket is now owned by the connection try: self.preferred = self.client.testConnection(self.conn) self.state = "tested" except ReadOnlyError: log("CW: ReadOnlyError in testConnection (%s)" % repr(self.addr)) self.close() return except: log("CW: error in testConnection (%s)" % repr(self.addr), level=logging.ERROR, exc_info=True) self.close() return if self.preferred: self.notify_client()
def _open_socket(self): 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) else: self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM) self.set_reuse_addr() log("listening on %s" % str(self.addr), logging.INFO) self.bind(self.addr) self.listen(5)
def _open_socket(self): if type(self.addr) == tuple: 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) else: self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM) self.set_reuse_addr() log("listening on %s" % str(self.addr), logging.INFO) self.bind(self.addr) self.listen(5)
def _connect_wrappers(self, wrappers, deadline): # Next wait until they all actually connect (or fail) # The deadline is necessary, because we'd wait forever if a # sockets never connects or fails. while wrappers: if self.stopped: for wrap in wrappers.keys(): wrap.close() return 0 # Select connecting wrappers connecting = [wrap for wrap in wrappers.keys() if wrap.state == "connecting"] if not connecting: break if time.time() > deadline: break try: r, w, x = select.select([], connecting, connecting, 1.0) log("CT: select() %d, %d, %d" % tuple(map(len, (r,w,x)))) except select.error as msg: log("CT: select failed; msg=%s" % str(msg), level=logging.WARNING) continue # Exceptable wrappers are in trouble; close these suckers for wrap in x: log("CT: closing troubled socket %s" % str(wrap.addr)) del wrappers[wrap] wrap.close() # Writable sockets are connected for wrap in w: wrap.connect_procedure() if wrap.state == "notified": del wrappers[wrap] # Don't close this one for wrap in wrappers.keys(): wrap.close() return 1 if wrap.state == "closed": del wrappers[wrap]
def _connect_wrappers(self, wrappers, deadline): # Next wait until they all actually connect (or fail) # The deadline is necessary, because we'd wait forever if a # sockets never connects or fails. while wrappers: if self.stopped: for wrap in wrappers.keys(): wrap.close() return 0 # Select connecting wrappers connecting = [ wrap for wrap in wrappers.keys() if wrap.state == "connecting" ] if not connecting: break if time.time() > deadline: break try: r, w, x = select.select([], connecting, connecting, 1.0) log("CT: select() %d, %d, %d" % tuple(map(len, (r, w, x)))) except select.error as msg: log("CT: select failed; msg=%s" % str(msg), level=logging.WARNING) continue # Exceptable wrappers are in trouble; close these suckers for wrap in x: log("CT: closing troubled socket %s" % str(wrap.addr)) del wrappers[wrap] wrap.close() # Writable sockets are connected for wrap in w: wrap.connect_procedure() if wrap.state == "notified": del wrappers[wrap] # Don't close this one for wrap in wrappers.keys(): wrap.close() return 1 if wrap.state == "closed": del wrappers[wrap]
def connect_procedure(self): """Call sock.connect_ex(addr) and interpret result.""" if self.state in ("opened", "connecting"): try: err = self.sock.connect_ex(self.addr) except socket.error as msg: log("CW: connect_ex(%r) failed: %s" % (self.addr, msg), level=logging.ERROR) self.close() return log("CW: connect_ex(%s) returned %s" % (self.addr, errno.errorcode.get(err) or str(err))) if err in _CONNECT_IN_PROGRESS: self.state = "connecting" return if err not in _CONNECT_OK: log("CW: error connecting to %s: %s" % (self.addr, errno.errorcode.get(err) or str(err)), level=logging.WARNING) self.close() return self.state = "connected" if self.state == "connected": self.test_connection()
def setSessionKey(self, sesskey): log("set session key %r" % sesskey) self.__hmac_send = hmac.HMAC(sesskey, digestmod=sha) self.__hmac_recv = hmac.HMAC(sesskey, digestmod=sha)
def handle_read(self): self.__input_lock.acquire() try: # Use a single __inp buffer and integer indexes to make this fast. try: d = self.recv(8192) except socket.error, err: if err[0] in expected_socket_read_errors: return raise if not d: return input_len = self.__input_len + len(d) msg_size = self.__msg_size state = self.__state has_mac = self.__has_mac inp = self.__inp if msg_size > input_len: if inp is None: self.__inp = d elif type(self.__inp) is StringType: self.__inp = [self.__inp, d] else: self.__inp.append(d) self.__input_len = input_len return # keep waiting for more input # load all previous input and d into single string inp if isinstance(inp, StringType): inp = inp + d elif inp is None: inp = d else: inp.append(d) inp = "".join(inp) offset = 0 while (offset + msg_size) <= input_len: msg = inp[offset:offset + msg_size] offset = offset + msg_size if not state: msg_size = struct.unpack(">I", msg)[0] has_mac = msg_size & MAC_BIT if has_mac: msg_size ^= MAC_BIT msg_size += 20 elif self.__hmac_send: raise ValueError("Received message without MAC") state = 1 else: msg_size = 4 state = 0 # Obscure: We call message_input() with __input_lock # held!!! And message_input() may end up calling # message_output(), which has its own lock. But # message_output() cannot call message_input(), so # the locking order is always consistent, which # prevents deadlock. Also, message_input() may # take a long time, because it can cause an # incoming call to be handled. During all this # time, the __input_lock is held. That's a good # thing, because it serializes incoming calls. if has_mac: mac = msg[:20] msg = msg[20:] if self.__hmac_recv: self.__hmac_recv.update(msg) _mac = self.__hmac_recv.digest() if mac != _mac: raise ValueError("MAC failed: %r != %r" % (_mac, mac)) else: log("Received MAC but no session key set") elif self.__hmac_send: raise ValueError("Received message without MAC") self.message_input(msg) self.__state = state self.__has_mac = has_mac self.__msg_size = msg_size self.__inp = inp[offset:] self.__input_len = input_len - offset
def error(self, exc_info): log("Error raised in delayed method", logging.ERROR, exc_info=True) self.return_error(self.msgid, 0, *exc_info[:2])
def handle_read(self): self.__input_lock.acquire() try: # Use a single __inp buffer and integer indexes to make this fast. try: d = self.recv(8192) except socket.error as err: if err[0] in expected_socket_read_errors: return raise if not d: return input_len = self.__input_len + len(d) msg_size = self.__msg_size state = self.__state has_mac = self.__has_mac inp = self.__inp if msg_size > input_len: if inp is None: self.__inp = d elif isinstance(self.__inp, six.binary_type): self.__inp = [self.__inp, d] else: self.__inp.append(d) self.__input_len = input_len return # keep waiting for more input # load all previous input and d into single string inp if isinstance(inp, six.binary_type): inp = inp + d elif inp is None: inp = d else: inp.append(d) inp = b"".join(inp) offset = 0 while (offset + msg_size) <= input_len: msg = inp[offset:offset + msg_size] offset = offset + msg_size if not state: msg_size = struct.unpack(">I", msg)[0] has_mac = msg_size & MAC_BIT if has_mac: msg_size ^= MAC_BIT msg_size += 20 elif self.__hmac_send: raise ValueError("Received message without MAC") state = 1 else: msg_size = 4 state = 0 # Obscure: We call message_input() with __input_lock # held!!! And message_input() may end up calling # message_output(), which has its own lock. But # message_output() cannot call message_input(), so # the locking order is always consistent, which # prevents deadlock. Also, message_input() may # take a long time, because it can cause an # incoming call to be handled. During all this # time, the __input_lock is held. That's a good # thing, because it serializes incoming calls. if has_mac: mac = msg[:20] msg = msg[20:] if self.__hmac_recv: self.__hmac_recv.update(msg) _mac = self.__hmac_recv.digest() if mac != _mac: raise ValueError("MAC failed: %r != %r" % (_mac, mac)) else: log("Received MAC but no session key set") elif self.__hmac_send: raise ValueError("Received message without MAC") self.message_input(msg) self.__state = state self.__has_mac = has_mac self.__msg_size = msg_size self.__inp = inp[offset:] self.__input_len = input_len - offset finally: self.__input_lock.release()
def error(self, exc_info): self.ready.wait() log("Error raised in delayed method", logging.ERROR, exc_info=exc_info) self.conn.call_from_thread(Delay.error, self, exc_info)
def error(self, exc_info): self.sent = 'error' log("Error raised in delayed method", logging.ERROR, exc_info=True) self.conn.return_error(self.msgid, *exc_info[:2])
def handle_accept(self): try: sock, addr = self.accept() except socket.error, msg: log("accepted failed: %s" % msg) return
class Dispatcher(asyncore.dispatcher): """A server that accepts incoming RPC connections""" __super_init = asyncore.dispatcher.__init__ def __init__(self, addr, factory=Connection): self.__super_init() self.addr = addr self.factory = factory self._open_socket() def _open_socket(self): 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) else: self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM) self.set_reuse_addr() log("listening on %s" % str(self.addr), logging.INFO) self.bind(self.addr) self.listen(5) def writable(self): return 0 def readable(self): return 1 def handle_accept(self): try: sock, addr = self.accept() except socket.error, msg: log("accepted failed: %s" % msg) return # We could short-circuit the attempt below in some edge cases # and avoid a log message by checking for addr being None. # Unfortunately, our test for the code below, # quick_close_doesnt_kill_server, causes addr to be None and # we'd have to write a test for the non-None case, which is # *even* harder to provoke. :/ So we'll leave things as they # are for now. # It might be better to check whether the socket has been # closed, but I don't see a way to do that. :( # Drop flow-info from IPv6 addresses if addr: # Sometimes None on Mac. See above. addr = addr[:2] try: c = self.factory(sock, addr) except: if sock.fileno() in asyncore.socket_map: del asyncore.socket_map[sock.fileno()] ZEO.zrpc.log.logger.exception("Error in handle_accept") else: log("connect from %s: %s" % (repr(addr), c))