def _run_ssl_connect_callback(self): if self._state & self.io_loop.WRITE: self._state = self._state & ~self.io_loop.WRITE self.io_loop.update_handler(self.fileno(), self._state) if hasattr(BaseSSLIOStream, "_finish_ssl_connect"): BaseSSLIOStream._finish_ssl_connect(self) else: BaseSSLIOStream._run_ssl_connect_callback(self)
def connect(self, reconnecting=False): self.logger.debug('CONNECTING...') _sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) if self.ssl is None: self._stream = IOStream(_sock) elif self.ssl is True: self._stream = SSLIOStream(_sock) else: self._stream = SSLIOStream(_sock, ssl_options=self.ssl) self._stream.connect((self.address, self.port), self._register)
def connect_to_server(self, server_cls): server = client = None try: sock, port = bind_unused_port() server = server_cls(ssl_options=_server_ssl_options()) server.add_socket(sock) client = SSLIOStream(socket.socket(), ssl_options=dict(cert_reqs=ssl.CERT_NONE)) yield client.connect(('127.0.0.1', port)) self.assertIsNotNone(client.socket.cipher()) finally: if server is not None: server.stop() if client is not None: client.close()
def _connect(self): nodes=[] edges=[] s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) self._stream = SSLIOStream(s) log.info('Connecting to %s', self._address) self._stream.connect(self._address)
class SSLStreamReader(object): def __init__(self, host, port): self._address = (host, port) self._stream = None self._clients = set() def add(self, client): self._clients.add(client) log.debug('Added client %s', client) if self._stream is None: self._connect() self._read_stream() def remove(self, client): self._clients.remove(client) log.debug('Removed client %s', client) @staticmethod def process(data): return data @staticmethod def write_message(client, data): pass def _connect(self): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) self._stream = SSLIOStream(s) log.debug('Connecting to %s', self._address) self._stream.connect(self._address) def _disconnect(self): self._stream.close() self._stream = None log.debug('Disconnected from %s', self._address) @coroutine def _read_stream(self): log.debug('Reading stream from %s', self._address) while self._clients: data = yield Task(self._stream.read_until, "\n") output = self.process(data) for client in self._clients: self.write_message(client, output) self._disconnect() return
def __init__(self, uid, port): super(Client, self).__init__('client') self.uid = uid self.port = port self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.conn = SSLIOStream(self.sock, ssl_options={'ca_certs':SERVER_CRT_PATH, 'cert_reqs':ssl.CERT_REQUIRED}) self.conn.connect(('127.0.0.1', self.port), self.on_connect)
class FeedbackConn(): def __init__(self, host=None, certfile=None, loop=None): self.ioloop = loop or ioloop.IOLoop.instance() self.host = host or settings.get('feedback_host') self.certfile = certfile or settings.get('certfile') self.connect() def connect(self): self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) self.stream = SSLIOStream(self.s, read_chunk_size=38, ssl_options=dict(certfile=self.certfile)) self.stream.connect(self.host, self._on_connect) self.stream.read_until_close(self._on_close, streaming_callback=self._on_read) def _on_connect(self): logging.info("connected to %s:%d" % (self.host[0], self.host[1])) def _on_read(self, data): #logging.info("read %d bytes" % len(data)) if len(data) == 38: timestamp, toklen, token = struct.unpack_from('!IH32s', data, 0) logging.info("flagging user %s at %d" % (token.encode('hex'), timestamp)) mc.set(token.encode('hex'), timestamp) def _on_close(self, data): logging.info("disconnected %s:%d" % (self.host[0], self.host[1])) try: self.stream.close() self.s.close() except: pass self.ioloop.add_timeout(time.time()+settings.get('feedback_reconnect_lag'), self.connect)
def _Connect(self): try: ssl_options = {'certfile': secrets.GetSecretFile(self._settings['certfile'])} self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) self._stream = SSLIOStream(self._sock, io_loop=self._io_loop, ssl_options=ssl_options) self._stream.set_close_callback(self._OnClose) self._stream.connect(self._host, self._OnConnect) except KeyError: logging.warning('failed to initialize connection to APN service at %s:%d ' 'whose certificate is missing from secrets/%s' % (self._host[0], self._host[1], self._settings['certfile'])) return except: self._stream = None raise
class _BaseSSLService(object): """Base SSL connection to Apple's push notification servers. Retry on disconnect is handled via an exponential backoff. """ _MAX_BACKOFF_SECS = 600.0 # 10 minutes _PUSH_TOKEN_FMT = '%s:%s' def __init__(self, settings, host_key): self._settings = settings self._host = settings[host_key] self._retries = 0 self._io_loop = IOLoop.current() self._ResetBackoff() self._Connect() def IsValid(self): return self._stream is not None def _FormatPushToken(self, token): return _BaseSSLService._PUSH_TOKEN_FMT % (self._settings['token-prefix'], token) def _Connect(self): try: ssl_options = {'certfile': secrets.GetSecretFile(self._settings['certfile'])} self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) self._stream = SSLIOStream(self._sock, io_loop=self._io_loop, ssl_options=ssl_options) self._stream.set_close_callback(self._OnClose) self._stream.connect(self._host, self._OnConnect) except KeyError: logging.warning('failed to initialize connection to APN service at %s:%d ' 'whose certificate is missing from secrets/%s' % (self._host[0], self._host[1], self._settings['certfile'])) return except: self._stream = None raise def _OnConnect(self): logging.info("connected to %s:%d" % (self._host[0], self._host[1])) def _ResetBackoff(self): """Resets backoff to 'reconnect_lag' setting.""" self._backoff = self._settings.get('reconnect_lag') def _OnClose(self): logging.info("disconnected from %s:%d" % (self._host[0], self._host[1])) try: self._stream.close() except: pass finally: self._stream = None timeout = time.time() + self._backoff self._io_loop.add_timeout(timeout, self._Connect) self._backoff = min(_BaseSSLService._MAX_BACKOFF_SECS, self._backoff * 2)
def _handle_connection(self, connection, address): if self.ssl_options is not None: assert ssl, "Python 2.6+ and OpenSSL required for SSL" try: connection = ssl_wrap_socket(connection, self.ssl_options, server_side=True, do_handshake_on_connect=False) except ssl.SSLError as err: if err.args[0] == ssl.SSL_ERROR_EOF: return connection.close() else: raise except socket.error as err: # If the connection is closed immediately after it is created # (as in a port scan), we can get one of several errors. # wrap_socket makes an internal call to getpeername, # which may return either EINVAL (Mac OS X) or ENOTCONN # (Linux). If it returns ENOTCONN, this error is # silently swallowed by the ssl module, so we need to # catch another error later on (AttributeError in # SSLIOStream._do_ssl_handshake). # To test this behavior, try nmap with the -sT flag. # https://github.com/tornadoweb/tornado/pull/750 if errno_from_exception(err) in (errno.ECONNABORTED, errno.EINVAL): return connection.close() else: raise try: if self.ssl_options is not None: stream = SSLIOStream(connection, max_buffer_size=self.max_buffer_size, read_chunk_size=self.read_chunk_size) else: stream = IOStream(connection, max_buffer_size=self.max_buffer_size, read_chunk_size=self.read_chunk_size) future = self.handle_stream(stream, address) if future is not None: IOLoop.current().add_future(gen.convert_yielded(future), lambda f: f.result()) except Exception: app_log.error("Error in connection callback", exc_info=True)
def _create_stream(self, addrinfo): af = addrinfo[0][0] if self.parsed.scheme == "https": ssl_options = {} if self.request.validate_cert: ssl_options["cert_reqs"] = ssl.CERT_REQUIRED if self.request.ca_certs is not None: ssl_options["ca_certs"] = self.request.ca_certs else: ssl_options["ca_certs"] = _DEFAULT_CA_CERTS if self.request.client_key is not None: ssl_options["keyfile"] = self.request.client_key if self.request.client_cert is not None: ssl_options["certfile"] = self.request.client_cert # SSL interoperability is tricky. We want to disable # SSLv2 for security reasons; it wasn't disabled by default # until openssl 1.0. The best way to do this is to use # the SSL_OP_NO_SSLv2, but that wasn't exposed to python # until 3.2. Python 2.7 adds the ciphers argument, which # can also be used to disable SSLv2. As a last resort # on python 2.6, we set ssl_version to TLSv1. This is # more narrow than we'd like since it also breaks # compatibility with servers configured for SSLv3 only, # but nearly all servers support both SSLv3 and TLSv1: # http://blog.ivanristic.com/2011/09/ssl-survey-protocol-support.html if sys.version_info >= (2, 7): # In addition to disabling SSLv2, we also exclude certain # classes of insecure ciphers. ssl_options["ciphers"] = "DEFAULT:!SSLv2:!EXPORT:!DES" else: # This is really only necessary for pre-1.0 versions # of openssl, but python 2.6 doesn't expose version # information. ssl_options["ssl_version"] = ssl.PROTOCOL_TLSv1 return SSLIOStream(socket.socket(af), io_loop=self.io_loop, ssl_options=ssl_options, max_buffer_size=self.max_buffer_size) else: return IOStream(socket.socket(af), io_loop=self.io_loop, max_buffer_size=self.max_buffer_size)
def __init__(self, io_loop, client, request, callback): self.start_time = time.time() self.io_loop = io_loop self.client = client self.request = request self.callback = callback self.code = None self.headers = None self.chunks = None self._decompressor = None # Timeout handle returned by IOLoop.add_timeout self._timeout = None with stack_context.StackContext(self.cleanup): parsed = urlparse.urlsplit(self.request.url) if ":" in parsed.netloc: host, _, port = parsed.netloc.partition(":") port = int(port) else: host = parsed.netloc port = 443 if parsed.scheme == "https" else 80 if self.client.hostname_mapping is not None: host = self.client.hostname_mapping.get(host, host) if parsed.scheme == "https": ssl_options = {} if request.validate_cert: ssl_options["cert_reqs"] = ssl.CERT_REQUIRED if request.ca_certs is not None: ssl_options["ca_certs"] = request.ca_certs else: ssl_options["ca_certs"] = _DEFAULT_CA_CERTS self.stream = SSLIOStream(socket.socket(), io_loop=self.io_loop, ssl_options=ssl_options) else: self.stream = IOStream(socket.socket(), io_loop=self.io_loop) timeout = min(request.connect_timeout, request.request_timeout) if timeout: self._connect_timeout = self.io_loop.add_timeout( self.start_time + timeout, self._on_timeout) self.stream.set_close_callback(self._on_close) self.stream.connect((host, port), functools.partial(self._on_connect, parsed))
def _handle_connection(self, connection, address): if self.ssl_options is not None: assert ssl, "Python 2.6+ and OpenSSL required for SSL" try: connection = ssl_wrap_socket(connection, self.ssl_options, server_side=True, do_handshake_on_connect=False) except ssl.SSLError as err: if err.args[0] == ssl.SSL_ERROR_EOF: return connection.close() else: raise except socket.error as err: # If the connection is closed immediately after it is created # (as in a port scan), we can get one of several errors. # wrap_socket makes an internal call to getpeername, # which may return either EINVAL (Mac OS X) or ENOTCONN # (Linux). If it returns ENOTCONN, this error is # silently swallowed by the ssl module, so we need to # catch another error later on (AttributeError in # SSLIOStream._do_ssl_handshake). # To test this behavior, try nmap with the -sT flag. # https://github.com/facebook/tornado/pull/750 if err.args[0] in (errno.ECONNABORTED, errno.EINVAL): return connection.close() else: raise try: if self.ssl_options is not None: stream = SSLIOStream(connection, io_loop=self.io_loop, max_buffer_size=self.max_buffer_size) else: stream = IOStream(connection, io_loop=self.io_loop, max_buffer_size=self.max_buffer_size) self.handle_stream(stream, address) # 这个_handle_conntion基本是用在底层iostream的处理上面, # 而路由的分发,我猜测应该是在生成request对象那个时候才开始的 except Exception: app_log.error("Error in connection callback", exc_info=True)
def _make_iostream(self, s): assert ssl, "Python 2.6+ and OpenSSL required for SSL" try: s = ssl.wrap_socket(s, server_side=True, do_handshake_on_connect=False, **self.ssl_options) except ssl.SSLError as err: if err.args[0] == ssl.SSL_ERROR_EOF: s.close() return else: raise except socket.error as err: if err.args[0] == errno.ECONNABORTED: s.close() return else: raise return SSLIOStream(s)
def _handle_connection(self, connection, address): if self.ssl_options is not None: assert ssl, "Python 2.6+ and OpenSSL required for SSL" try: connection = ssl_wrap_socket(connection, self.ssl_options, server_side=True, do_handshake_on_connect=False) except ssl.SSLError as err: if err.args[0] == ssl.SSL_ERROR_EOF: return connection.close() else: raise except socket.error as err: if errno_from_exception(err) in (errno.ECONNABORTED, errno.EINVAL): return connection.close() else: raise try: if self.ssl_options is not None: stream = SSLIOStream(connection, io_loop=self.io_loop, max_buffer_size=self.max_buffer_size, read_chunk_size=self.read_chunk_size) else: stream = IOStream(connection, io_loop=self.io_loop, max_buffer_size=self.max_buffer_size, read_chunk_size=self.read_chunk_size) future = self.handle_stream(stream, address) if future is not None: self.io_loop.add_future(future, lambda f: f.result()) except Exception: app_log.error("Error in connection callback", exc_info=True)
class FeedbackConn(): def __init__(self, host=None, certfile=None, loop=None): self.ioloop = loop or ioloop.IOLoop.instance() self.host = host or settings.get('feedback_host') self.certfile = certfile or settings.get('certfile') self.connect() def connect(self): self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) self.stream = SSLIOStream(self.s, read_chunk_size=38, ssl_options=dict(certfile=self.certfile)) self.stream.connect(self.host, self._on_connect) self.stream.read_until_close(self._on_close, streaming_callback=self._on_read) def _on_connect(self): logging.info("connected to %s:%d" % (self.host[0], self.host[1])) def _on_read(self, data): #logging.info("read %d bytes" % len(data)) if len(data) == 38: timestamp, toklen, token = struct.unpack_from('!IH32s', data, 0) logging.info("flagging user %s at %d" % (token.encode('hex'), timestamp)) mc.set(token.encode('hex'), timestamp) def _on_close(self, data): logging.info("disconnected %s:%d" % (self.host[0], self.host[1])) try: self.stream.close() self.s.close() except: pass self.ioloop.add_timeout( time.time() + settings.get('feedback_reconnect_lag'), self.connect)
server_side=True, do_handshake_on_connect=False, **self.ssl_options) except ssl.SSLError, err: if err.args[0] == ssl.SSL_ERROR_EOF: s.close() return else: raise except socket.error, err: if err.args[0] == errno.ECONNABORTED: s.close() return else: raise return SSLIOStream(s) class Client(TornadoConnection): def __init__(self, *args, **kwargs): Connection.__init__(self, *args, **kwargs) self.ssl_options = None @_o def connect(self, host, port): s = socket.socket() iostream = self._make_iostream(s) self._stack_conn = _Connection(iostream) self._stack_conn.attach(self) self._stack_conn.connect((host, port))
class APNSConn(): def __init__(self, host=None, certfile=None, loop=None): self.stats = { 'disconnects': 0, 'notifications': 0, 'invalid_tokens': 0, } self.started = time.time() self.ioloop = loop or ioloop.IOLoop.instance() self.host = host or settings.get('apns_host') self.certfile = certfile or settings.get('certfile') self.write_queue = deque() self.recent = deque(maxlen=100) # N.B. Python upgrades ints to longs and this is a binary protocol so... self.generation = ctypes.c_uint32(0) self.connect() def get_stats(self): stats = self.stats.copy() stats['queue_len'] = len(self.write_queue) stats['uptime'] = time.time() - self.started return stats def push(self, token, alert=None, badge=None, sound=None, expiry=None, extra=None, timestamp=None): ''' This is really the only api you want to use. Pushes a notification onto the queue for non-flagged users. The queue is processed in the tornado event loop. ''' flagged = mc.get(token) if flagged: if not timestamp or timestamp > flagged: self.stats['invalid_tokens'] += 1 return False self.generation.value += 1 identifier = self.generation.value msg = create_message(token, alert=alert, badge=badge, sound=sound, identifier=identifier, expiry=expiry, extra=extra) if len(msg) > MAX_PAYLOAD_BYTES: raise ValueError, u"max payload(%d) exceeded: %d" % ( MAX_PAYLOAD_BYTES, len(msg)) self.write_queue.append(msg) self.recent.append(dict(identifier=identifier, token=token)) self.ioloop.add_callback(self.push_one) return True def push_one(self): if len(self.write_queue) and not self.stream.closed(): msg = self.write_queue.popleft() try: self.stream.write(msg) self.stats['notifications'] += 1 except: self.write_queue.appendleft(msg) return False return True return False def connect(self): self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) self.stream = SSLIOStream(self.s, ssl_options=dict(certfile=self.certfile)) self.stream.connect(self.host, self._on_connect) self.stream.read_until_close(self._on_close, streaming_callback=self._on_read) def _on_connect(self): ''' Process the backlog, hoss. ''' logging.info('connected to %s:%d' % (self.host[0], self.host[1])) while self.push_one(): continue def _on_read(self, data): ''' The only message we expect here is an error response... sadly, followed by a disconnect. ''' logging.info('_on_read: %d bytes' % (len(data))) try: status, identifier, err_string = parse_response(data) logging.warning('_on_read err: %d %d %s' % (status, identifier, err_string)) if status == 8: for msg in self.recent: if msg['identifier'] == identifier: token = msg['token'] logging.info('flagging token: %s' % (token)) self.stats['invalid_tokens'] += 1 mc.set(token, int(time.time())) except: logging.info('parse_response failed') def _on_close(self, data): ''' This triggers the reconnect given reconnect_lag from settings. This should probably be an exponential backoff. ''' logging.info('_on_close') self.stats['disconnects'] += 1 try: self.stream.close() self.s.close() except: pass self.ioloop.add_timeout( time.time() + settings.get('apns_reconnect_lag'), self.connect)
class H2Client(object): ALPN_HTTP2_PROTOCOL = b'h2' USER_AGENT = 'Tornado 4.3 hyper-h2/1.0.0' def __init__(self, io_loop=None, config=None): self.io_loop = io_loop or IOLoop.current() self.conn = H2Connection() self.known_proto = None self.authority = None self.io_stream = None self.ssl_context = ssl.create_default_context( purpose=ssl.Purpose.SERVER_AUTH) self.ssl_context.check_hostname = False self.ssl_context.verify_mode = ssl.CERT_NONE self.ssl_context.set_alpn_protocols([self.ALPN_HTTP2_PROTOCOL]) self.are_settings_acked = False self.pending_requests = [] #self.settings_acked_future = Future() self.responses = {} self.pushes = {} self.data_received_size = 0 self.last_time_data_recvd = None @gen.coroutine def connect(self, host, port): self.authority = host s = socket.socket() self.io_stream = SSLIOStream(s, ssl_options=self.ssl_context) yield self.io_stream.connect((host, port), server_hostname=host) logger.debug("Connected!") self.known_proto = self.io_stream.socket.selected_alpn_protocol() assert self.known_proto == self.ALPN_HTTP2_PROTOCOL, "ALPN protocol was not h2, was {} instead".format( self.known_proto) self.io_stream.set_close_callback(self.connection_lost) logger.debug("Talking to a valid HTTP2 server! Sending preamble") self.conn.initiate_connection() self.io_stream.read_until_close(streaming_callback=self.data_received) data = self.conn.data_to_send() yield self.io_stream.write(data) logger.debug("Preamble Sent! Should be connected now") @gen.coroutine def close_connection(self): self.io_stream.set_close_callback(lambda: None) self.conn.close_connection() data = self.conn.data_to_send() yield self.io_stream.write(data) # @gen.coroutine def data_received(self, data): """ Called by Tornado when data is received on the connection. We need to check a few things here. Firstly, we want to validate that we actually negotiated HTTP/2: if we didn't, we shouldn't proceed! Then, we want to pass the data to the protocol stack and check what events occurred. """ self.data_received_size += len(data) self.last_time_data_recvd = time.time() if not self.known_proto: self.known_proto = self.io_stream.socket.selected_alpn_protocol() assert self.known_proto == b'h2' events = self.conn.receive_data(data) for event in events: #print("Processing event: {}".format(event)) if isinstance(event, ResponseReceived): self.handle_response(event) elif isinstance(event, DataReceived): self.handle_data(event) elif isinstance(event, StreamEnded): #self.end_stream(event) logger.debug("Got event Stream ended for stream {}".format( event.stream_id)) elif isinstance(event, SettingsAcknowledged): self.settings_acked(event) elif isinstance(event, StreamReset): logger.debug("A Stream reset!: %d" % event.error_code) elif isinstance(event, WindowUpdated): self.window_updated(event) elif isinstance(event, PushedStreamReceived): self.handle_push(event) elif isinstance(event, TrailersReceived): self.handle_response(event, response_type="trailers") else: logger.debug( "Received an event we don't handle: {}".format(event)) data = self.conn.data_to_send() if data: #print("Responding to the server: {}".format(data)) self.io_stream.write(data) def settings_acked(self, event): """ Called when the remote party ACKs our settings. We send a SETTINGS frame as part of the preamble, so if we want to be very polite we can wait until the ACK for that frame comes before we start sending our request. """ self.are_settings_acked = True #self.settings_acked_future.set_result(True) def _get_stream_response_holder(self, stream_id): try: return self.responses[stream_id] except KeyError: return self.pushes[stream_id] def handle_response(self, event, response_type="headers"): """ Handle the response by storing the response headers. """ try: response = self._get_stream_response_holder(event.stream_id) except KeyError: logger.exception( "Unable to find a response future for stream {} while handling a response" .format(event.stream_id)) else: response[response_type] = event.headers if event.stream_ended is not None: try: future = response.pop("future") except KeyError: logger.exception( "No future associated with the response for stream {} while handling a response" .format(event.stream_id)) else: future.set_result(response) try: self.end_stream(event.stream_ended) except H2StreamClosedError as e: logger.exception( "Got an exception trying to end a stream after handling a response for stream: {}" .format(e.stream_id)) def handle_data(self, event): """ We handle data that's received """ self.conn.acknowledge_received_data(event.flow_controlled_length, event.stream_id) try: response = self._get_stream_response_holder(event.stream_id) except KeyError: logger.debug( "Unable to find a response future for stream {} while handling a data. Adding one now" .format(event.stream_id)) # response = {'future': Future()} # self.responses[event.stream_id] = response else: if "data" not in response: response["data"] = event.data else: response["data"] = response["data"] + event.data if event.stream_ended is not None: try: future = response.pop("future") except KeyError: logger.debug( "No future associated with the response for stream {} while handling data" .format(event.stream_id)) else: future.set_result(response) try: self.end_stream(event.stream_ended) except H2StreamClosedError as e: logger.exception( "Got an exception trying to end a stream after handling a response for stream: {}" .format(e.stream_id)) def handle_push(self, event): self.pushes[event.pushed_stream_id] = { "parent_stream_id": event.parent_stream_id, "request_headers": event.headers } # @gen.coroutine def end_stream(self, event): """ We call this when the stream is cleanly ended by the remote peer. That means that the response is complete. """ self.conn.end_stream(event.stream_id) yield self.io_stream.write(self.conn.data_to_send()) logger.debug("Closed Stream {}".format(event.stream_id)) def window_updated(self, event): """ I don't think I actually need to do anything with this in Tornado, since sending data uses futures to continue """ pass def connection_lost(self): """ Called by Twisted when the connection is gone. Regardless of whether it was clean or not, we want to stop the reactor. """ logger.debug("Connection was lost!") def get_request(self, path): return self.send_bodyless_request(path, "GET") fetch = get_request def head_request(self, path): return self.send_bodyless_request(path, "HEAD") @gen.coroutine def send_bodyless_request(self, path, method): # if not self.settings_acked_future.done(): # print("Settings haven't been acked, yield until they are") # yield self.settings_acked_future # print("Settings acked! Let's send this pending request") request_headers = [ (':method', method), (':authority', self.authority), (':scheme', 'https'), (':path', path), ('user-agent', self.USER_AGENT), ] stream_id = self.conn.get_next_available_stream_id() logger.debug("Generating HEADER frame to send for stream_id {}".format( stream_id)) self.conn.send_headers(stream_id, request_headers, end_stream=True) response_future = Future() self.responses[stream_id] = {'future': response_future} logger.debug("Writing out to the stream") yield self.io_stream.write(self.conn.data_to_send()) logger.debug("Request sent! Waiting for response") result = yield response_future logger.debug("Got a result") raise gen.Return(result) def post_file_request(self, path, file_path): # First, we need to work out how large the file is. file_size = os.stat(file_path).st_size # Next, we want to guess a content-type and content-encoding. content_type, content_encoding = mimetypes.guess_type(file_path) # We can now open the file. file_obj = open(file_path, 'rb') self.post_request(path=path, file_obj=file_obj, file_size=file_size, content_type=content_type, content_encoding=content_encoding) def post_request(self, path, file_obj, file_size=None, content_type=None, content_encoding=None): """ Send the POST request. A POST request is made up of one headers frame, and then 0+ data frames. This method begins by sending the headers, and then starts a series of calls to send data. """ # if not self.settings_acked_future.done(): # print("Settings haven't been acked, yield until they are") # yield self.settings_acked_future # print("Settings acked! Let's send this pending post request") if type(file_obj) is str: file_size = len(file_obj) file_obj = StringIO(file_obj) # Now we can build a header block. request_headers = [ (':method', 'POST'), (':authority', self.authority), (':scheme', 'https'), (':path', path), ('user-agent', self.USER_AGENT), ('content-length', str(file_size)), ] if content_type is not None: request_headers.append(('content-type', content_type)) if content_encoding is not None: request_headers.append(('content-encoding', content_encoding)) stream_id = self.conn.get_next_available_stream_id() self.conn.send_headers(stream_id, request_headers) # We now need to send all the relevant data. We do this by checking # what the acceptable amount of data is to send, and sending it. If we # find ourselves blocked behind flow control, we then place a deferred # and wait until that deferred fires. response_future = Future() self.responses[stream_id] = {'future': response_future} # We now need to send a number of data frames. try: while file_size > 0: # Firstly, check what the flow control window is for the current stream. window_size = self.conn.local_flow_control_window( stream_id=stream_id) # Next, check what the maximum frame size is. max_frame_size = self.conn.max_outbound_frame_size # We will send no more than the window size or the remaining file size # of data in this call, whichever is smaller. bytes_to_send = min(window_size, file_size) while bytes_to_send > 0: chunk_size = min(bytes_to_send, max_frame_size) data_chunk = file_obj.read(chunk_size) self.conn.send_data(stream_id=stream_id, data=data_chunk) yield self.io_stream.write(self.conn.data_to_send()) bytes_to_send -= chunk_size file_size -= chunk_size except StreamClosedError: logger.warning( "Connection was lost while sending stream {}".format( stream_id)) else: self.conn.end_stream(stream_id=stream_id) finally: file_obj.close() result = yield response_future raise gen.Return(result) @gen.coroutine def update_settings(self, new_settings): self.conn.update_settings(new_settings=new_settings) data = self.conn.data_to_send() if data: self.io_stream.write(data)
def __init__(self, io_loop, client, request, release_callback, final_callback, max_buffer_size): self.start_time = time.time() self.io_loop = io_loop self.client = client self.request = request self.release_callback = release_callback self.final_callback = final_callback self.code = None self.headers = None self.chunks = None self._decompressor = None # Timeout handle returned by IOLoop.add_timeout self._timeout = None with stack_context.StackContext(self.cleanup): parsed = urlparse.urlsplit(_unicode(self.request.url)) if ssl is None and parsed.scheme == "https": raise ValueError("HTTPS requires either python2.6+ or " "curl_httpclient") if parsed.scheme not in ("http", "https"): raise ValueError("Unsupported url scheme: %s" % self.request.url) # urlsplit results have hostname and port results, but they # didn't support ipv6 literals until python 2.7. netloc = parsed.netloc if "@" in netloc: userpass, _, netloc = netloc.rpartition("@") match = re.match(r'^(.+):(\d+)$', netloc) if match: host = match.group(1) port = int(match.group(2)) else: host = netloc port = 443 if parsed.scheme == "https" else 80 if re.match(r'^\[.*\]$', host): # raw ipv6 addresses in urls are enclosed in brackets host = host[1:-1] if self.client.hostname_mapping is not None: host = self.client.hostname_mapping.get(host, host) if request.allow_ipv6: af = socket.AF_UNSPEC else: # We only try the first IP we get from getaddrinfo, # so restrict to ipv4 by default. af = socket.AF_INET addrinfo = socket.getaddrinfo(host, port, af, socket.SOCK_STREAM, 0, 0) af, socktype, proto, canonname, sockaddr = addrinfo[0] if parsed.scheme == "https": ssl_options = {} if request.validate_cert: ssl_options["cert_reqs"] = ssl.CERT_REQUIRED if request.ca_certs is not None: ssl_options["ca_certs"] = request.ca_certs else: ssl_options["ca_certs"] = _DEFAULT_CA_CERTS if request.client_key is not None: ssl_options["keyfile"] = request.client_key if request.client_cert is not None: ssl_options["certfile"] = request.client_cert # SSL interoperability is tricky. We want to disable # SSLv2 for security reasons; it wasn't disabled by default # until openssl 1.0. The best way to do this is to use # the SSL_OP_NO_SSLv2, but that wasn't exposed to python # until 3.2. Python 2.7 adds the ciphers argument, which # can also be used to disable SSLv2. As a last resort # on python 2.6, we set ssl_version to SSLv3. This is # more narrow than we'd like since it also breaks # compatibility with servers configured for TLSv1 only, # but nearly all servers support SSLv3: # http://blog.ivanristic.com/2011/09/ssl-survey-protocol-support.html if sys.version_info >= (2, 7): ssl_options["ciphers"] = "DEFAULT:!SSLv2" else: # This is really only necessary for pre-1.0 versions # of openssl, but python 2.6 doesn't expose version # information. ssl_options["ssl_version"] = ssl.PROTOCOL_SSLv3 self.stream = SSLIOStream(socket.socket(af, socktype, proto), io_loop=self.io_loop, ssl_options=ssl_options, max_buffer_size=max_buffer_size) else: self.stream = IOStream(socket.socket(af, socktype, proto), io_loop=self.io_loop, max_buffer_size=max_buffer_size) timeout = min(request.connect_timeout, request.request_timeout) if timeout: self._timeout = self.io_loop.add_timeout( self.start_time + timeout, self._on_timeout) self.stream.set_close_callback(self._on_close) self.stream.connect(sockaddr, functools.partial(self._on_connect, parsed))
def _make_server_iostream(self, connection, **kwargs): connection = ssl.wrap_socket(connection, server_side=True, do_handshake_on_connect=False, **_server_ssl_options()) return SSLIOStream(connection, **kwargs)
do_handshake_on_connect=False, **self.ssl_options) except ssl.SSLError, err: if err.args[0] == ssl.SSL_ERROR_EOF: return connection.close() else: raise except socket.error, err: if err.args[0] == errno.ECONNABORTED: return connection.close() else: raise try: if self.ssl_options is not None: stream = SSLIOStream(connection, io_loop=self.io_loop, max_buffer_size=max_buffer_size, read_chunk_size=read_chunk_size) else: stream = IOStream(connection, io_loop=self.io_loop, max_buffer_size=max_buffer_size, read_chunk_size=read_chunk_size) self.handle_stream(stream, address) except Exception: _logger.error("Error in connection callback", exc_info=True) tornado.netutil.TCPServer._handle_connection = _handle_connection class MS3App(tornado.web.Application): """ """ def __init__(self, args=None, debug=False):
def _connect(self): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) self._stream = SSLIOStream(s) log.debug('Connecting to %s', self._address) self._stream.connect(self._address)
def _handle_connection(self, connection, address): # _handle_connection就比较简单了,跳过那些ssl的处理,简化为两句: # stream = IOStream(connection, io_loop=self.io_loop)和 # self.handle_stream()。 # 这里IOStream代表了【IO层】(也就是读写方面的东西),以后再说,反正读写是不愁了。 # 接着是调用handle_stream。我们可以看到,不论应用层是什么协议 # (或者自定义协议),当有新连接到来时走的流程是差不多的,都要经历一番上诉的回调, # 不同之处就在于这个handle_stream方法。这个方法是由【子类自定义覆盖的】, # 它的HTTP实现已经在上一节看过了。 # if self.ssl_options is not None: assert ssl, "Python 2.6+ and OpenSSL required for SSL" try: connection = ssl_wrap_socket(connection, self.ssl_options, server_side=True, do_handshake_on_connect=False) except ssl.SSLError as err: if err.args[0] == ssl.SSL_ERROR_EOF: return connection.close() else: raise except socket.error as err: # If the connection is closed immediately after it is created # (as in a port scan), we can get one of several errors. # wrap_socket makes an internal call to getpeername, # which may return either EINVAL (Mac OS X) or ENOTCONN # (Linux). If it returns ENOTCONN, this error is # silently swallowed by the ssl module, so we need to # catch another error later on (AttributeError in # SSLIOStream._do_ssl_handshake). # To test this behavior, try nmap with the -sT flag. # https://github.com/facebook/tornado/pull/750 if err.args[0] in (errno.ECONNABORTED, errno.EINVAL): return connection.close() else: raise try: if self.ssl_options is not None: stream = SSLIOStream(connection, io_loop=self.io_loop, max_buffer_size=self.max_buffer_size) else: stream = IOStream(connection, io_loop=self.io_loop, max_buffer_size=self.max_buffer_size) # 调用handle_stream,传入创建的IOStream对象初始化一个HTTPConnection对象, # HTTPConnection封装了IOStream的一些操作,用于处理HTTPRequest并返回。 # 1.1.0 中的写法: # ====important=====# # HTTPConnection(stream, address, self.request_callback, self.no_keep_alive, self.xheaders) self.handle_stream(stream, address) # 思路是很清晰的,客户端连接在这里被转化成一个IOStream。然后由handle_stream函数处理。 # 这个handle_stream就是我们前面提到过的未直接实现的接口,它是由HTTPServer类实现的。 # def handle_stream(self, stream, address): # HTTPConnection(stream, address, self.request_callback, # self.no_keep_alive, self.xheaders, self.protocol) # 最后,处理流程又回到了HTTPServer类中。可以预见,在HTTConnection这个类中, # stream将和我们注册的RequestHandler协作,一边读客户端请求,一边调用相应的handler处理。 except Exception: app_log.error("Error in connection callback", exc_info=True)
def _make_client_iostream(self, connection, **kwargs): return SSLIOStream(connection, io_loop=self.io_loop, **kwargs)
def __init__(self, io_loop, client, request, release_callback, final_callback, max_buffer_size): self.start_time = time.time() self.io_loop = io_loop self.client = client self.request = request self.release_callback = release_callback self.final_callback = final_callback self.code = None self.headers = None self.chunks = None self._decompressor = None # Timeout handle returned by IOLoop.add_timeout self._timeout = None with stack_context.StackContext(self.cleanup): parsed = urlparse.urlsplit(_unicode(self.request.url)) if ssl is None and parsed.scheme == "https": raise ValueError("HTTPS requires either python2.6+ or " "curl_httpclient") if parsed.scheme not in ("http", "https"): raise ValueError("Unsupported url scheme: %s" % self.request.url) # urlsplit results have hostname and port results, but they # didn't support ipv6 literals until python 2.7. netloc = parsed.netloc if "@" in netloc: userpass, _, netloc = netloc.rpartition("@") match = re.match(r'^(.+):(\d+)$', netloc) if match: host = match.group(1) port = int(match.group(2)) else: host = netloc port = 443 if parsed.scheme == "https" else 80 if re.match(r'^\[.*\]$', host): # raw ipv6 addresses in urls are enclosed in brackets host = host[1:-1] if self.client.hostname_mapping is not None: host = self.client.hostname_mapping.get(host, host) if request.allow_ipv6: af = socket.AF_UNSPEC else: # We only try the first IP we get from getaddrinfo, # so restrict to ipv4 by default. af = socket.AF_INET addrinfo = socket.getaddrinfo(host, port, af, socket.SOCK_STREAM, 0, 0) af, socktype, proto, canonname, sockaddr = addrinfo[0] if parsed.scheme == "https": ssl_options = {} if request.validate_cert: ssl_options["cert_reqs"] = ssl.CERT_REQUIRED if request.ca_certs is not None: ssl_options["ca_certs"] = request.ca_certs else: ssl_options["ca_certs"] = _DEFAULT_CA_CERTS if request.client_key is not None: ssl_options["keyfile"] = request.client_key if request.client_cert is not None: ssl_options["certfile"] = request.client_cert self.stream = SSLIOStream(socket.socket(af, socktype, proto), io_loop=self.io_loop, ssl_options=ssl_options, max_buffer_size=max_buffer_size) else: self.stream = IOStream(socket.socket(af, socktype, proto), io_loop=self.io_loop, max_buffer_size=max_buffer_size) timeout = min(request.connect_timeout, request.request_timeout) if timeout: self._timeout = self.io_loop.add_timeout( self.start_time + timeout, self._on_timeout) self.stream.set_close_callback(self._on_close) self.stream.connect(sockaddr, functools.partial(self._on_connect, parsed))
def connect(self): self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) self.stream = SSLIOStream(self.s, ssl_options=dict(certfile=self.certfile)) self.stream.connect(self.host, self._on_connect) self.stream.read_until_close(self._on_close, streaming_callback=self._on_read)
class APNSConn(): def __init__(self, host=None, certfile=None, loop=None): self.stats = { 'disconnects':0, 'notifications':0, 'invalid_tokens':0, } self.started = time.time() self.ioloop = loop or ioloop.IOLoop.instance() self.host = host or settings.get('apns_host') self.certfile = certfile or settings.get('certfile') self.write_queue = deque() self.recent = deque(maxlen=100) # N.B. Python upgrades ints to longs and this is a binary protocol so... self.generation = ctypes.c_uint32(0) self.connect() def get_stats(self): stats = self.stats.copy() stats['queue_len'] = len(self.write_queue) stats['uptime'] = time.time() - self.started return stats def push(self, token, alert=None, badge=None, sound=None, expiry=None, extra=None, timestamp=None): ''' This is really the only api you want to use. Pushes a notification onto the queue for non-flagged users. The queue is processed in the tornado event loop. ''' flagged = mc.get(token) if flagged: if not timestamp or timestamp > flagged: self.stats['invalid_tokens'] += 1 return False self.generation.value += 1 identifier = self.generation.value msg = create_message(token, alert=alert, badge=badge, sound=sound, identifier=identifier, expiry=expiry, extra=extra) if len(msg) > MAX_PAYLOAD_BYTES: raise ValueError, u"max payload(%d) exceeded: %d" % (MAX_PAYLOAD_BYTES, len(msg)) self.write_queue.append(msg) self.recent.append(dict(identifier=identifier, token=token)) self.ioloop.add_callback(self.push_one) return True def push_one(self): if len(self.write_queue) and not self.stream.closed(): msg = self.write_queue.popleft() try: self.stream.write(msg) self.stats['notifications'] += 1 except: self.write_queue.appendleft(msg) return False return True return False def connect(self): self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) self.stream = SSLIOStream(self.s, ssl_options=dict(certfile=self.certfile)) self.stream.connect(self.host, self._on_connect) self.stream.read_until_close(self._on_close, streaming_callback=self._on_read) def _on_connect(self): ''' Process the backlog, hoss. ''' logging.info('connected to %s:%d' % (self.host[0], self.host[1])) while self.push_one(): continue def _on_read(self, data): ''' The only message we expect here is an error response... sadly, followed by a disconnect. ''' logging.info('_on_read: %d bytes' % (len(data))) try: status, identifier, err_string = parse_response(data) logging.warning('_on_read err: %d %d %s' % (status, identifier, err_string)) if status == 8: for msg in self.recent: if msg['identifier'] == identifier: token = msg['token'] logging.info('flagging token: %s' % (token)) self.stats['invalid_tokens'] += 1 mc.set(token, int(time.time())) except: logging.info('parse_response failed') def _on_close(self, data): ''' This triggers the reconnect given reconnect_lag from settings. This should probably be an exponential backoff. ''' logging.info('_on_close') self.stats['disconnects'] += 1 try: self.stream.close() self.s.close() except: pass self.ioloop.add_timeout(time.time()+settings.get('apns_reconnect_lag'), self.connect)
def _run_ssl_connect_callback(self): if self._state & self.io_loop.WRITE: self._state = self._state & ~self.io_loop.WRITE self.io_loop.update_handler(self.fileno(), self._state) BaseSSLIOStream._run_ssl_connect_callback(self)
def _make_iostream(self, s): return SSLIOStream(s, ssl_options=self.ssl_options)
class SSLStreamReader(object): def __init__(self, host, port): self._address = (host, port) self._stream = None self._clients = set() self._nodes=[] self._edges=[] def add(self, client): self._clients.add(client) log.debug('Added client %s', client) if self._stream is None: self._connect() self._read_stream() def remove(self, client): self._clients.remove(client) log.debug('Removed client %s', client) @staticmethod def process(data): return data @staticmethod def write_message(client, data): pass def _connect(self): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) self._stream = SSLIOStream(s) log.debug('Connecting to %s', self._address) self._stream.connect(self._address) def _disconnect(self): self._stream.close() self._stream = None log.debug('Disconnected from %s', self._address) @coroutine def _read_stream(self): log.info('Reading stream from %s', self._address) lastVertex=None while self._clients: data = yield Task(self._stream.read_until, "\n") resultObject=None if '<hwstats' in data: result=None if lastVertex==None: resultObject=None else: result=[] result.append('{"type":"vertex", "event":"update", ') result.append('"id":') result.append(lastVertex) result.append(', "details":{"Temperature":') Start=data.find('temperature="')+13 End=data.find('" ',Start) result.append(data[Start:End]) result.append(', "Hostname":') Start=data.find('hostname="')+9 End=data.find('"',Start+1)+1 result.append(data[Start:End]) result.append(', "CPU":') Start=data.find('" cpuLoad="')+11 End=data.find('" ',Start) result.append(data[Start:End]) result.append(', "Free Memory":') Start=data.find('freeMem="')+9 End=data.find('" ',Start) result.append(data[Start:End]) result.append('}}') resultObject=result lastVertex=None elif '<vertex' in data or ('<edge' in data and 'type="2"' in data): resultObject = self.process(data) if '<vertex' in data: if 'add' in data: start=data.find('id="')+4 end=data.find('"',start) lastVertex=data[start:end] if '"add", ' in resultObject and '"vertex",' in resultObject: for i in range(len(self._nodes)): if (resultObject[5]==self._nodes[i][5]): self._nodes.pop(i) self._nodes.append(resultObject) resultObject[3]='"update", ' elif i==len(self._nodes)-1: self._nodes.append(resultObject) if len(self._nodes)==0: self._nodes.append(resultObject) elif('"remove", ' in resultObject): for i in range(len(self._nodes)): if ((resultObject[5]==self._nodes[i][5])): self._nodes.pop(i) break for i in range(len(self._edges)): if resultObject[5]==self._edges[i][5] or resultObject[5]==self._edges[i][7]: self._edges.pop(i) elif '<edge' in data: if 'event="add"' in resultObject and '"edge",' in resultObject: for i in range(len(self._edges)): if(resultObject[0:8]==self._edges[i][0:8] and resultObject!=self._edges[i] ): self._edges[i]=resultObject resultObject[3]='"update",' break elif (i==len(self._edges)-1): self._edges.append(resultObject) if len(self._edges)==0: self.edges.append(resultObject) elif '"remove", ' in resultObject and '"edge", ' in resultObject: for i in range(len(self._edges)): if (resultObject[5] == self._edges[i][5] and resultObject[7] == self._edges[i][7]) or (resultObject[5] == self._edges[i][7] and resultObject[7] == self._edges[i][5]): self._edges.pop(i) if resultObject!=None: resultObject=''.join(resultObject) for client in self._clients: print('data: '+data+'\nsending: '+resultObject) self.write_message(client, resultObject) self._disconnect() return
def _wrap_socket(self, sock): return SSLIOStream(sock, ssl_options=self.ssl_options)
class Client(Protocol): '''ssl client implementation.''' def __init__(self, uid, port): super(Client, self).__init__('client') self.uid = uid self.port = port self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.conn = SSLIOStream(self.sock, ssl_options={'ca_certs':SERVER_CRT_PATH, 'cert_reqs':ssl.CERT_REQUIRED}) self.conn.connect(('127.0.0.1', self.port), self.on_connect) def on_connect(self): self.conn.set_close_callback(self.on_close) self.pubkey, self.privkey = None, None self.init_keys() self.write(OP_PUBKEY, self.pubkey) # this is hardcoded currently in absence of proper tests if self.uid == 2: self.send_message(1, 'hello world') logger.debug('Client %s: Sending message "Hello World" to user with uid 1' % self.uid) self.read_line() def read_line(self): self.conn.read_until(CRLF, self.handle_line) def on_close(self): self.conn = None self.sock = None def init_keys(self): if os.path.isfile(CLIENT_PUB_PATH % self.uid) and os.path.isfile(CLIENT_PRIV_PATH % self.uid): with open(CLIENT_PUB_PATH % self.uid, 'rb') as pubfile, open(CLIENT_PRIV_PATH % self.uid, 'rb') as privfile: self.pubkey = pubfile.read().strip() self.privkey = privfile.read().strip() logger.debug('read existing pub/priv key for uid %s' % self.uid) else: self.pubkey, self.privkey = self.generate_keys() with open(CLIENT_PUB_PATH % self.uid, 'wb') as pubfile, open(CLIENT_PRIV_PATH % self.uid, 'wb') as privfile: pubfile.write(self.pubkey) privfile.write(self.privkey) logger.debug('written pub/priv key for uid %s' % self.uid) def write(self, *messages): for message in messages: self.conn.write('%s%s' % (message, CRLF)) def send_message(self, uid, message): with open(CLIENT_PUB_PATH % uid, 'rb') as pubfile: pubkey = pubfile.read() # encrypt message using receiver public key enc = self.encrypt_message(message, pubkey) # sign encrypted message for digital verification sig = self.generate_signature(enc[0]) message = (OP_MESSAGE, pubkey, enc[0], sig[0],) self.write(*message) @staticmethod def generate_keys(): random_generator = Random.new().read priv = RSA.generate(1024, random_generator) pub = priv.publickey() return (pub.exportKey().strip(), priv.exportKey().strip()) def generate_signature(self, message): '''sign messaging using our priv key''' k = RSA.importKey(self.privkey) h = MD5.new(message).digest() return k.sign(h, '') @staticmethod def verify_signature(pubkey, signature, message): '''verify signature using signing user public key''' k = RSA.importKey(pubkey) h = MD5.new(message).digest() return k.verify(h, signature) @staticmethod def encrypt_message(message, pubkey): '''encrypt message using receiving user public key''' k = RSA.importKey(pubkey) return k.encrypt(message, 32) def decrypt_message(self, enc): '''decrypt message using our priv key''' k = RSA.importKey(self.privkey) return k.decrypt(enc)
class _APNConn(object): def __init__(self, prev_buffers, cert_file, svr_tuple, encode_func): """ :param cert_file: 证书文件路径 :param prev_buffers: 之前残留的推送链表 按照苹果的尿性推送协议 谁也不知道到底有没有成功 F**K JOBS! """ self.__io_loop = IOLoop.instance() self.__cert = path.join(CERT_ROOT, cert_file) self.__svr_addr = svr_tuple self.__encode_func = encode_func self.__stream = None self.__connected = False #list自身的索引作为 self.sending_buffer = [] if prev_buffers and isinstance(prev_buffers, (list, tuple)): self.sending_buffer.extend(prev_buffers) self.__recv_buf = '' self.__connection_close_ts = None #从read读到的错误消息id,便于 self.__recv_err_msgid = None self.critical = False def force_close(self): self.__connected = False self.__stream.close_fd() self.__stream = None self.critical = True def connect(self): self.__stream = SSLIOStream( socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0), io_loop=self.__io_loop, ssl_options={ 'ssl_version': ssl.PROTOCOL_TLSv1, # 'ca_certs': path.join(CERT_ROOT, 'entrust_root_ca.pem'), 'certfile': self.__cert, # 'cert_reqs': ssl.CERT_REQUIRED }) self.__stream.set_close_callback(self._on_close) self.__stream.connect(self.__svr_addr, self.__on_connect) def __on_connect(self): self.__connected = True self.__stream.set_nodelay(True) self.__stream.read_until_close(self._last_closd_recv, self._on_recv) self.__send_batch() def __do_check(self, sent_len): if self.__recv_err_msgid: logger.debug('CHECK: %r' % self.__recv_err_msgid) if self.__recv_err_msgid is None: self.sending_buffer = self.sending_buffer[sent_len:] self.__send_batch() return self.sending_buffer = self.sending_buffer[self.__recv_err_msgid:] self.__recv_err_msgid = None self.__stream.close_fd() self.__stream = None self.critical = True def __send_batch(self): """ 连接断开后就停了 """ if not self.__connected: return if not self.__stream: return l = self.sending_buffer[:QUEUE_CAPACITY] for i, b in enumerate(l): self.__stream.write(self.__encode_func(i, *b)) logger.debug('>> %d - %s' % (i, b)) self.__io_loop.add_timeout(timedelta(seconds=CHECK_TIMESPAN), partial(self.__do_check, len(l))) def append(self, device_token, payload): """ :param device_token: app从apns服务器获取的64字节串 :param payload: 报文dict """ self.sending_buffer.append((device_token, payload)) def _last_closd_recv(self, buf): """ socket关闭时最后几个字节 """ if not buf: return self._on_recv(buf) def _on_recv(self, buf): logger.debug('<< %r' % buf) self.__recv_buf = self.__recv_buf + buf _ = err_resp(self.__recv_buf) if _ is None: return self.__recv_buf = '' errno, self.__recv_err_msgid = _ logger.fatal('apns err: %d - %d - %s' % (self.__recv_err_msgid, errno, resp_errno.get(errno))) def _on_close(self): self.critical = True logger.warn('closed') self.__connected = False self.__stream = None
def _make_client_iostream(self): return SSLIOStream(socket.socket(), ssl_options=dict(cert_reqs=ssl.CERT_NONE))
server_side=True, do_handshake_on_connect=False, **self.ssl_options) except ssl.SSLError, err: if err.args[0] == ssl.SSL_ERROR_EOF: return connection.close() else: raise except socket.error, err: if err.args[0] == errno.ECONNABORTED: return connection.close() else: raise try: if self.ssl_options is not None: stream = SSLIOStream(connection, io_loop=self.io_loop) else: stream = IOStream(connection, io_loop=self.io_loop) stream._debug_info = 'TCPServer._handle_connection' self.handle_stream(stream, address) except Exception: logging.error("Error in connection callback", exc_info=True) def bind_sockets(port, address=None, family=socket.AF_UNSPEC, backlog=128): """Creates listening sockets bound to the given port and address. Returns a list of socket objects (multiple sockets are returned if the given address maps to multiple IP addresses, which is most common for mixed IPv4 and IPv6 use).
def _make_client_iostream(self, connection, **kwargs): return SSLIOStream(connection, ssl_options=dict(cert_reqs=ssl.CERT_NONE), **kwargs)
class _BaseSSLService(object): """Base SSL connection to Apple's push notification servers. Retry on disconnect is handled via an exponential backoff. """ _MAX_BACKOFF_SECS = 600.0 # 10 minutes _PUSH_TOKEN_FMT = '%s:%s' def __init__(self, settings, host_key): self._settings = settings self._host = settings[host_key] self._retries = 0 self._io_loop = IOLoop.current() self._ResetBackoff() self._Connect() def IsValid(self): return self._stream is not None def _FormatPushToken(self, token): return _BaseSSLService._PUSH_TOKEN_FMT % ( self._settings['token-prefix'], token) def _Connect(self): try: ssl_options = { 'certfile': secrets.GetSecretFile(self._settings['certfile']) } self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) self._stream = SSLIOStream(self._sock, io_loop=self._io_loop, ssl_options=ssl_options) self._stream.set_close_callback(self._OnClose) self._stream.connect(self._host, self._OnConnect) except KeyError: logging.warning( 'failed to initialize connection to APN service at %s:%d ' 'whose certificate is missing from secrets/%s' % (self._host[0], self._host[1], self._settings['certfile'])) return except: self._stream = None raise def _OnConnect(self): logging.info("connected to %s:%d" % (self._host[0], self._host[1])) def _ResetBackoff(self): """Resets backoff to 'reconnect_lag' setting.""" self._backoff = self._settings.get('reconnect_lag') def _OnClose(self): logging.info("disconnected from %s:%d" % (self._host[0], self._host[1])) try: self._stream.close() except: pass finally: self._stream = None timeout = time.time() + self._backoff self._io_loop.add_timeout(timeout, self._Connect) self._backoff = min(_BaseSSLService._MAX_BACKOFF_SECS, self._backoff * 2)
server_side=True, do_handshake_on_connect=False, **self.ssl_options) except ssl.SSLError, err: if err.args[0] == ssl.SSL_ERROR_EOF: return connection.close() else: raise except socket.error, err: if err.args[0] == errno.ECONNABORTED: return connection.close() else: raise try: if self.ssl_options is not None: stream = SSLIOStream(connection, io_loop=self.io_loop) else: stream = IOStream(connection, io_loop=self.io_loop) self.handle_stream(stream, address) except Exception: app_log.error("Error in connection callback", exc_info=True) def bind_sockets(port, address=None, family=socket.AF_UNSPEC, backlog=128, flags=None): """Creates listening sockets bound to the given port and address. Returns a list of socket objects (multiple sockets are returned if the given address maps to multiple IP addresses, which is most common for mixed IPv4 and IPv6 use). Address may be either an IP address or hostname. If it's a hostname,
def _make_client_iostream(self, connection, **kwargs): context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH) context.check_hostname = False context.verify_mode = ssl.CERT_NONE return SSLIOStream(connection, ssl_options=context, **kwargs)
def _make_client_iostream(self): return SSLIOStream(socket.socket(), io_loop=self.io_loop)
class IrcConnection(object): def __init__(self, bot, server_name, address, port, nick, user, realname, owners, channels=None, password=None, ssl=None): self.bot = bot self.server_name = server_name self.owners = owners self.address = address self.port = port self.nick = nick self.user = user self.password = password self.ssl = ssl self.realname = realname self.initial_channels = set(channels) if channels else set() self.logger = getLogger(__name__) self._protocol_events = dict() self.channels = dict() self.users = dict() self.init_protocol_events() def init_protocol_events(self): self._protocol_events = { 'PRIVMSG' : self.on_privmsg, 'PING' : self.on_ping, 'JOIN' : self.on_join, '401' : self.on_nochan, '001' : self.on_welcome, 'KICK' : self.on_kick, 'PART' : self.on_part, '353' : self.on_names, 'NICK' : self.on_nick } def connect(self, reconnecting=False): self.logger.debug('CONNECTING...') _sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) if self.ssl is None: self._stream = IOStream(_sock) elif self.ssl is True: self._stream = SSLIOStream(_sock) else: self._stream = SSLIOStream(_sock, ssl_options=self.ssl) self._stream.connect((self.address, self.port), self._register) def _register(self): self.logger.debug('CONNECTED') if self.password: # TODO need to check for error responses self.authenticate() self.set_nick(self.nick) self.write_raw('USER %s 0 * :%s' % (self.user, self.realname)) self._next() def add_channel(self, channel): self.channels[channel] = [] def remove_channel(self, channel): del self.channels[channel] def add_user(self, channel, user): self.channels[channel].append(user) self.users[user.nick] = user def remove_user(self, channel, user): self.channels[channels].remove(user) del self.users[user.nick] def user_change_nick(self, old_nick, new_nick): user = self.users.pop(old_nick) user.nick = new_nick self.users[new_nick] = user def authenticate(self): self.logger.debug("Authenticating (%s)", self.password) self.write_raw('PASS %s' % self.password) def set_nick(self, nick): if not nick: raise IrcError('Cannot set empty nick') self.logger.debug('SETTING NICK {nick: %s}' % nick) self.write_raw('NICK %s' % nick) def join_channel(self, *channels): if not all([c for c in channels]): raise IrcError('Empty channel') self.logger.debug('JOINING CHANNEL(S): {channels: %s}' % repr(channels)) chan_def = ','.join(channels) self.write_raw('JOIN %s' % chan_def) def part_channel(self, *channels): if not all([c for c in channels]): raise IrcError('Empty channel') self.logger.debug('PARTING CHANNEL: {channels: %s}' % repr(channels)) chan_def = ','.join(channels) self.write_raw('PART :%s' % chan_def) def private_message(self, destination, message): if not message: raise IrcError('Cannot send empty message') if not destination: raise IrcError('Cannot send to empty destination') self.logger.debug('SENDING PRIVMSG: {destination: %s, message: %s}' % (destination, message)) self.write_raw('PRIVMSG %s :%s' % (destination, message)) def reply(self, event, message): if event.destination and event.destination != self.nick: destination = event.destination else: destination = event.nick self.private_message(destination, message) def reply_with_nick(self, event, message): if event.destination: message = '%s: %s' % (event.nick, message) self.reply(event, message) def kick(self, channel, user, comment=None): if not channel: raise IrcError('Cannot kick from empty channel') if not user: raise IrcError('Cannot kick empty player') self.logger.debug('KICKING {channel: %s, user: %s}' % (channel, user)) kick_str = 'KICK %s %s' % (channel, user) if comment: kick_str += ' :%s' % comment self.write_raw(kick_str) def write_raw(self, line): line.replace(EOL, '') self.logger.debug('WRITE RAW: {line: %s}' % line) self._stream.write(line + EOL) def read_raw(self, line): self.logger.debug('READ RAW: {line: %s}' % line.replace(EOL, '')) event = IrcEvent(self.nick, line) self.handle(event) self.bot.process_hooks(self, event) self.bot.process_plugins(self, event) self._next() def handle(self, event): if event.type in self._protocol_events: self._protocol_events[event.type](event) def _next(self): self._stream.read_until(EOL, self.read_raw) def on_welcome(self, event): self.logger.debug('RECIEVED RPL_WELCOME') if self.initial_channels: self.join_channel(*self.initial_channels) def on_ping(self, event): # One ping only, please self.logger.debug('RECIEVED PING') self.write_raw("PONG %s\r\n" % event.text) def on_privmsg(self, event): # :[email protected] PRIVMSG #xx :hi self.logger.debug('RECIEVED PRIVMSG {destination: %s,' ' message: %s}' % (event.destination, event.text)) pass def on_nick(self, event): old_nick = event.nick new_nick = event.text self.logger.debug('RECIEVED NICK {old_nick: %s, new_nick: %s}' % (old_nick, new_nick)) if event.nick == self.nick: self.nick = new_nick else: self.user_change_nick(old_nick, new_nick) def on_join(self, event): channel = event.destination nick = event.nick self.logger.debug('RECIEVED JOIN {channel: %s, nick: %s}' % (channel, nick)) if self.nick == nick: self.add_channel(channel) else: self.add_user(channel, IrcUser(nick)) def on_names(self, event): nick_chan, nicks_raw = event.parameters_raw.split(':') nicks = nicks_raw.split() if '@' in nick_chan: channel = nick_chan.split('@')[-1].strip() elif '=' in nick_chan: channel = nick_chan.split('=')[-1].strip() else: raise Exception self.logger.debug('RECIEVED NAMES {channel: %s, nick_count: %d}' % ( channel, len(nicks))) for nr in nicks: if nr[0] == '@': nick = nr[1:] elif nr[0] == '+': nick = nr[1:] else: nick = nr user = IrcUser(nick) self.add_user(channel, user) def on_nochan(self, event): channel = event.parameters[0] self.logger.debug('RECIEVED ERR_NOSUCHCHANNEL {channel: %s}' % channel) # :senor.crunchybueno.com 401 nodnc #xx :No such nick/channel self.remove_channel(channel) def on_part(self, event): nick = event.nick channel = event.destination self.logger.debug('RECIEVED PART {channel: %s, nick: %s}' % (channel, nick)) if event.nick == self.nick: self.logger.debug('IOBot parted from %s' % channel) self.remove_channel(channel) def on_kick(self, event): nick = event.parameters[0] channel = event.destination self.logger.debug('RECIEVED KICK {channel: %s, nick: %s}' % (channel, nick)) if event.parameters[0] == self.nick: self.logger.warning('IOBot was KICKed from %s' % channel) self.remove_channel(channel)
def _make_client_iostream(self, connection, **kwargs): context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) return SSLIOStream(connection, io_loop=self.io_loop, ssl_options=context, **kwargs)
class Client(Protocol): '''ssl client implementation.''' def __init__(self, uid, port): super(Client, self).__init__('client') self.uid = uid self.port = port self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.conn = SSLIOStream(self.sock, ssl_options={ 'ca_certs': SERVER_CRT_PATH, 'cert_reqs': ssl.CERT_REQUIRED }) self.conn.connect(('127.0.0.1', self.port), self.on_connect) def on_connect(self): self.conn.set_close_callback(self.on_close) self.pubkey, self.privkey = None, None self.init_keys() self.write(OP_PUBKEY, self.pubkey) # this is hardcoded currently in absence of proper tests if self.uid == 2: self.send_message(1, 'hello world') logger.debug( 'Client %s: Sending message "Hello World" to user with uid 1' % self.uid) self.read_line() def read_line(self): self.conn.read_until(CRLF, self.handle_line) def on_close(self): self.conn = None self.sock = None def init_keys(self): if os.path.isfile(CLIENT_PUB_PATH % self.uid) and os.path.isfile( CLIENT_PRIV_PATH % self.uid): with open(CLIENT_PUB_PATH % self.uid, 'rb') as pubfile, open(CLIENT_PRIV_PATH % self.uid, 'rb') as privfile: self.pubkey = pubfile.read().strip() self.privkey = privfile.read().strip() logger.debug('read existing pub/priv key for uid %s' % self.uid) else: self.pubkey, self.privkey = self.generate_keys() with open(CLIENT_PUB_PATH % self.uid, 'wb') as pubfile, open(CLIENT_PRIV_PATH % self.uid, 'wb') as privfile: pubfile.write(self.pubkey) privfile.write(self.privkey) logger.debug('written pub/priv key for uid %s' % self.uid) def write(self, *messages): for message in messages: self.conn.write('%s%s' % (message, CRLF)) def send_message(self, uid, message): with open(CLIENT_PUB_PATH % uid, 'rb') as pubfile: pubkey = pubfile.read() # encrypt message using receiver public key enc = self.encrypt_message(message, pubkey) # sign encrypted message for digital verification sig = self.generate_signature(enc[0]) message = ( OP_MESSAGE, pubkey, enc[0], sig[0], ) self.write(*message) @staticmethod def generate_keys(): random_generator = Random.new().read priv = RSA.generate(1024, random_generator) pub = priv.publickey() return (pub.exportKey().strip(), priv.exportKey().strip()) def generate_signature(self, message): '''sign messaging using our priv key''' k = RSA.importKey(self.privkey) h = MD5.new(message).digest() return k.sign(h, '') @staticmethod def verify_signature(pubkey, signature, message): '''verify signature using signing user public key''' k = RSA.importKey(pubkey) h = MD5.new(message).digest() return k.verify(h, signature) @staticmethod def encrypt_message(message, pubkey): '''encrypt message using receiving user public key''' k = RSA.importKey(pubkey) return k.encrypt(message, 32) def decrypt_message(self, enc): '''decrypt message using our priv key''' k = RSA.importKey(self.privkey) return k.decrypt(enc)