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) ssl_ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH) ssl_ctx.check_hostname = False ssl_ctx.verify_mode = ssl.CERT_NONE # These tests fail with ConnectionAbortedErrors with TLS # 1.3 on windows python 3.7.4 (which includes an upgrade # to openssl 1.1.c. Other platforms might be affected with # newer openssl too). Disable it until we figure out # what's up. # Update 2021-12-28: Still happening with Python 3.10 on # Windows. OP_NO_TLSv1_3 now raises a DeprecationWarning. with ignore_deprecation(): ssl_ctx.options |= getattr(ssl, "OP_NO_TLSv1_3", 0) client = SSLIOStream(socket.socket(), ssl_options=ssl_ctx) 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()
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)
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 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(("10.0.0.7", port)) self.assertIsNotNone(client.socket.cipher()) finally: if server is not None: server.stop() if client is not None: client.close()
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()
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
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)
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
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 _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)
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)