async def handle_stream(self, stream: IOStream, address): print("connect from {0:s}:{1:d}".format(address[0], address[1])) loop = IOLoop.current() #type: IOLoop frameBuffer = b'' Q = Queue(maxsize=10) while True: try: if not stream.reading(): dataFuture = stream.read_bytes( 12, partial=True) #type:futures.Future frameBuffer = frameBuffer + await gen.with_timeout( timedelta(seconds=12), dataFuture) print("CurrentBuffer:", frameBuffer) if len(frameBuffer) < 24: continue loop.run_in_executor( None, partial(self.wrappedDecode, frameBuffer, Q)) status = Q.get() frameBuffer = b'' if status == self.DECODE_SUC: await stream.write(bytes([0x3e])) else: await stream.write(bytes([0x6c])) except StreamClosedError: print("connection closed from {0:s}:{1:d}".format( address[0], address[1])) break except gen.TimeoutError: frameBuffer = b'' print("No response in 3 seconds {0:s}:{1:d}".format( address[0], address[1]))
class _HTTPConnection(object): _SUPPORTED_METHODS = set(["GET", "HEAD", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"]) # map of (host, port, af) ==> {'addrinfo': addrinfo, # 'last_update': timestamp} _DNS_CACHE = {} def __init__(self, io_loop, client, request, callback, max_buffer_size): 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 self._connect_timeout = None if self.request.headers.get('Connection') == 'close': self.keep_alive = False else: self.keep_alive = True 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] parsed_hostname = host # save final parsed host for _on_connect 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 # Ignore keep_alive logic if explicitly requesting non-presistent connections if self.keep_alive: self.stream_key = (host, port, parsed.scheme) if self.client.stream_map.has_key(self.stream_key): self._set_stream(parsed, parsed_hostname) if self.stream: return else: self.client.stream_map[self.stream_key] = collections.deque() af, socktype, proto, canonname, sockaddr = self._getaddrinfo(host, port, af) 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) self.stream.last_used = time.time() else: self.stream = IOStream(socket.socket(af, socktype, proto), io_loop=self.io_loop, max_buffer_size=max_buffer_size) self.stream.last_used = time.time() 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(sockaddr, functools.partial(self._on_connect, parsed, parsed_hostname)) def _getaddrinfo(self, host, port, af): """ Custom getaddrinfo method caches the response for `DNS_TTL` seconds """ key = (host, port, af) # cache based on this tuple now = datetime.datetime.now() # if bool(DNS_TTL) == False (e.g. 0.0, None, etc), always # ignore cache if not DNS_TTL or not key in self._DNS_CACHE: addrinfo = socket.getaddrinfo(host, port, af, socket.SOCK_STREAM, 0, 0) #Assign TTL for each IP addrinfo_dict = dict([(i,now) for i in addrinfo]) self._DNS_CACHE[key] = {'addrinfo': addrinfo_dict,"last_changed":now,"last_addrinfo":addrinfo,"last_updated":now} #If past DNS_TTL or it was changed recently elif random.randint(0,(now-self._DNS_CACHE[key]['last_changed']).seconds+1000) < 10 or (now-self._DNS_CACHE[key]['last_updated']).seconds>DNS_TTL: addrinfo = socket.getaddrinfo(host, port, af, socket.SOCK_STREAM, 0, 0) self._DNS_CACHE[key]['last_updated'] = now if addrinfo != self._DNS_CACHE[key]['last_addrinfo']: self._DNS_CACHE[key]['last_changed'] = now self._DNS_CACHE[key]['last_addrinfo'] = addrinfo for info in addrinfo: self._DNS_CACHE[key]['addrinfo'].update({info:now}) random_addr = None while not random_addr and self._DNS_CACHE[key]['addrinfo']: random_addr = random.choice(self._DNS_CACHE[key]['addrinfo'].keys()) if (now-self._DNS_CACHE[key]['addrinfo'][random_addr]).seconds > DNS_TTL: del self._DNS_CACHE[key]['addrinfo'][random_addr] random_addr = None if not random_addr: raise Exception("Error. No DNS could be resolved. We should never get here.") return random_addr @classmethod def clear_dns_cache(cls): cls._DNS_CACHE = {} def _set_stream(self, parsed, parsed_hostname): while self.client.stream_map[self.stream_key]: self.stream = self.client.stream_map[self.stream_key].pop() # Ditch closed streams and get a new one if self.stream.closed(): continue # Double check the stream isn't in use # Don't put back in the queue because if it's in use whoever's using it will, # or if it's closed it shouldn't be there if not (self.stream.reading() or self.stream.writing()): self.stream.set_close_callback(self._on_close) self._on_connect(parsed, parsed_hostname) self.stream.last_used = time.time() self.client._garbage_collect(self.stream_key) return else: self.stream.close() logging.warning("Stream is writing, this should be fixed now though") self.stream = None def _on_timeout(self): self._timeout = None self._run_callback(HTTPResponse(self.request, 599, request_time=time.time() - self.start_time, error=HTTPError(599, "Timeout"))) self.stream.close() def _on_connect(self, parsed, parsed_hostname): if self._connect_timeout is not None: self.io_loop.remove_timeout(self._connect_timeout) self._connect_timeout = None if self._timeout is not None: self.io_loop.remove_timeout(self._timeout) self._timeout = None if self.request.request_timeout: self._timeout = self.io_loop.add_timeout( self.start_time + self.request.request_timeout, self._on_timeout) if (self.request.validate_cert and isinstance(self.stream, SSLIOStream)): match_hostname(self.stream.socket.getpeercert(), # ipv6 addresses are broken (in # parsed.hostname) until 2.7, here is # correctly parsed value calculated in # __init__ parsed_hostname) if (self.request.method not in self._SUPPORTED_METHODS and not self.request.allow_nonstandard_methods): raise KeyError("unknown method %s" % self.request.method) for key in ('network_interface', 'proxy_host', 'proxy_port', 'proxy_username', 'proxy_password'): if getattr(self.request, key, None): raise NotImplementedError('%s not supported' % key) if "Connection" not in self.request.headers: self.request.headers["Connection"] = "close" if "Host" not in self.request.headers: if '@' in parsed.netloc: self.request.headers["Host"] = parsed.netloc.rpartition('@')[-1] else: self.request.headers["Host"] = parsed.netloc username, password = None, None if parsed.username is not None: username, password = parsed.username, parsed.password elif self.request.auth_username is not None: username = self.request.auth_username password = self.request.auth_password or '' if username is not None: auth = utf8(username) + b(":") + utf8(password) self.request.headers["Authorization"] = (b("Basic ") + base64.b64encode(auth)) if self.request.user_agent: self.request.headers["User-Agent"] = self.request.user_agent if not self.request.allow_nonstandard_methods: if self.request.method in ("POST", "PATCH", "PUT"): assert self.request.body is not None else: assert self.request.body is None if self.request.body is not None: self.request.headers["Content-Length"] = str(len( self.request.body)) if (self.request.method == "POST" and "Content-Type" not in self.request.headers): self.request.headers["Content-Type"] = "application/x-www-form-urlencoded" if self.request.use_gzip: self.request.headers["Accept-Encoding"] = "gzip" req_path = ((parsed.path or '/') + (('?' + parsed.query) if parsed.query else '')) request_lines = [utf8("%s %s HTTP/1.1" % (self.request.method, req_path))] for k, v in self.request.headers.get_all(): line = utf8(k) + b(": ") + utf8(v) if b('\n') in line: raise ValueError('Newline in header: ' + repr(line)) request_lines.append(line) self.stream.write(b("\r\n").join(request_lines) + b("\r\n\r\n")) if self.request.body is not None: self.stream.write(self.request.body) self.stream.read_until(b("\r\n\r\n"), self._on_headers) def _run_callback(self, response): if self.callback is not None: callback = self.callback self.callback = None callback(response) @contextlib.contextmanager def cleanup(self): try: yield except Exception, e: logging.warning("uncaught exception: %s" % self.request.url, exc_info=True) self._run_callback(HTTPResponse(self.request, 599, error=e, request_time=time.time() - self.start_time)) if hasattr(self, 'stream'): self._close_stream()
class Flashlight(object): """ TCP клиент фонарика принимает TLV комманды от :host :port и выполняет метод Словарь TLV выполняет функцию роутера команда -> метод для вызова """ def __init__(self, status='OFF', host='127.0.0.1', port=9999, timeout=2): self.status = status self.host = host self.port = port self.timeout = datetime.timedelta(seconds=timeout) self._init_time = datetime.datetime.now() self.color = '#ffffff' self._clear_command() def connect(self): logging.info('Connecting to the server: {} {}'.format(self.host, self.port)) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.stream = IOStream(s) self.stream.set_close_callback(self._on_close) self.stream.connect((self.host, self.port), self._on_connect) IOLoop.instance().start() if self.stream.closed(): logging.error("Can't connect to {} {}".format(self.host, self.port)) def close_connection(self): self.stream.close() IOLoop.instance().stop() IOLoop.instance().close() def on(self): self.status = 'ON' logging.info('Status changed to {status}'.format(status=self.status)) self.send_status() def off(self): self.status = 'OFF' logging.info('Status changed to {status}'.format(status=self.status)) self.send_status() def ch_color(self): value = char_sec_to_int(self.command[3:]) rgb = '#{}'.format(hex(value)[2:]) self.color = rgb logging.info('Switched to {color}'.format(color=self.color)) self.send_status() def send_status(self, callback=None): if not self.stream.reading(): self.stream.write('{:<4}{}\n'.format(self.status, self.color), callback=callback) def _on_close(self): self.close_connection() def _on_connect(self): self._callback() def _callback(self): self.stream.read_bytes(1, self._collect_command) def _clear_command(self): self.command = [] self.length = 0 def _collect_command(self, data): self.command.append(data) if len(self.command) == 3: self.length = char_sec_to_int(self.command[1:3]) elif self.length: self.length -= 1 if not self.length and len(self.command) >= 3: self._run_command() self._callback() def _run_command(self): TLV[ord(self.command[0])]['callback'](self) self._clear_command()
class RconConnection(object): def __init__(self, ip, port, rcon_password): self.ip = ip self.port = port self.rcon_password = rcon_password self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) self._stream = IOStream(self._socket) self.authed = False self.request_id = 0 self.error = None # use a deque for the queue because it supports popleft(), which is # what we want (FIFO) self._queue = deque() self._busy = False # async connect & call _auth when connected self._stream.connect((ip, port), self._auth) def _construct_packet(self, code, body): #packets are in the form dictated at # https://developer.valvesoftware.com/wiki/Source_RCON_Protocol #<packet size><request id><request code><body string><empty string> #strings must be null terminated, even the empty one if body is None: body = r'' self.request_id += 1 # use little endian packing of ints, terminate body with a null, # and add a null at the end for the empty string packet = struct.pack('<l', self.request_id) packet += struct.pack('<l', code) packet += body.encode('ascii') + '\x00\x00' # add packet length to the front of the packet packet = struct.pack('<l', len(packet)) + packet return packet def _send_packet(self, packet, callback=None): logging.debug("Sending packet %s", repr(packet)) self._stream.write(packet, callback) def _read_single_packet(self, callback=None): # reads a single packet from the stream and pushes the processed # response through the given callback if provided #logging.debug("Reading single packet from stream") def process_packet(packed_packet): #logging.debug("Processing packet: %s", repr(packed_packet)) #packet <id (packed int)><response code (packed int)><body>\x00\x00 curr_packet_id = struct.unpack('<l', packed_packet[0:4])[0] response_code = struct.unpack('<l', packed_packet[4:8])[0] message = packed_packet[8:].strip('\x00') #strip the terminators """logging.debug("ID: %d CODE: %d MESSAGE: %s", curr_packet_id, response_code, message)""" # we now have the packet message, response code, and response id, # so push it through the given callback if callback is not None: callback((curr_packet_id, response_code, message)) def process_packet_len(packed_packet_len): packet_len = struct.unpack('<l', packed_packet_len)[0] """logging.debug("Processing packet length: %s, length: %s", repr(packed_packet_len), packet_len)""" # read the entire packet #logging.debug("Reading the rest of the packet") self._stream.read_bytes(packet_len, process_packet) self._stream.read_bytes(4, process_packet_len) def _auth(self, data=None, auth_sent=False, junked=False): """ Called when connect is successful. First thing we always do after connecting is attempt to authenticate. Authentication is asynchronous. We'll utilise partials to do this """ if self.error: raise self.error if not auth_sent: auth_packet = self._construct_packet(SERVERDATA_AUTH, self.rcon_password) # _auth will be called again once the auth packet has been sent f = partial(self._auth, auth_sent=True) self._send_packet(auth_packet, f) elif not junked: # read the junk packet out, and call again with junked = True f = partial(self._auth, auth_sent=True, junked=True) self._read_single_packet(f) else: # now read the real response. call _auth_response once it has been # processed self._read_single_packet(self._auth_response) def _auth_response(self, response): logging.debug("Auth response: %s", repr(response)) if response[1] == SERVERDATA_AUTH_RESPONSE: if response[0] == -1: self.error = RconAuthError("Invalid RCON password specified") elif response[0] == self.request_id: self.authed = True logging.debug("Successfully authed") self._process_queue() else: self.error = RconAuthError("Expected packet id %d, got %d" % (response[0], self.request_id)) else: self.error = RconAuthError("Expected auth response, got %d" % (response[1])) def _exec(self, command, callback=None): """ Send a command packet to the server if we are authenticated. When doing this, we send a packet with SERVERDATA_EXEC_COMMAND and the command we want to execute, along with an empty SERVERDATA_COMMAND_RESPONSE, which the server will mirror back at us once it has sent the response to our command (in order, because TCP is ordered). Doing this lets us know exactly when we've received the full response to our command. """ if self.authed: self._busy = True # send command packet with no callback, it'll just execute and # and do nothing packet = self._construct_packet(SERVERDATA_EXEC_COMMAND, command) self._send_packet(packet) # add empty packet, with callback. this packet is just appended # to the stream's internal write queue packet = self._construct_packet(SERVERDATA_COMMAND_RESPONSE, r'') f = partial(self._command_sent_callback, handle_callback=callback) self._send_packet(packet, f) def _command_sent_callback(self, handle_callback=None): """ Called when a complete command has been sent (command + mirror packets) This will let us know when we should start reading. @param handle_callback The callback to be used once we've read all data from a response """ # begin reading f = partial(self._handle_multi_packet_read, complete_callback=handle_callback) self._read_single_packet(f) def _handle_multi_packet_read(self, data, previous=None, complete_callback=None): # data is a tuple in the form (id, code, message) if previous is None: previous = [data] else: previous.append(data) length = len(previous) if length > 0: """ To signify the end of a multi-line response, we'll receive an empty packet (which is the mirror of our empty packet), along with an additional empty packet whose body consists of solely \x01. So we need to check the last two packets. This means we'll receive a MINIMUM of THREE packets for ANY command response """ response_complete = False got_mirror_packet = False empty = previous[length - 2:] # last 2 packets in previous list for packet in empty: packet_id = packet[0] response_code = packet[1] message = packet[2] if (response_code == SERVERDATA_COMMAND_RESPONSE and packet_id == self.request_id): if got_mirror_packet and message == '\x01': """ Have already gotten mirror packet, so this packet is the expected packet after the mirror, with body of \x01 Therefore, our response is complete! """ response_complete = True else: got_mirror_packet = True if response_complete: self._busy = False if complete_callback is not None: complete = self._compile_multi_packet(previous) complete_callback(complete) self._process_queue() elif not response_complete: # response not complete, get the next packet f = partial(self._handle_multi_packet_read, previous=previous, complete_callback=complete_callback) self._read_single_packet(f) def _compile_multi_packet(self, packets): """ Compiles a list of packets into a single packet, chopping off the empty packets. @param packets A list of packet tuples @return Complete packet """ first = packets[0] response_id = first[0] response_code = first[1] message = "".join([x[2] for x in packets[:-2]]) return (response_id, response_code, message) def send_cmd(self, command, callback=None): if self.error: raise self.error if self.busy() or not self.authed: # we're already reading/writing from the socket. this command # should be queued. the queue is processed at the end of a # read cycle logging.debug("Stream is busy. Adding command to queue") self._add_to_queue((command, callback)) else: # execute the command! self._exec(command, callback) def _add_to_queue(self, qtuple): self._queue.append(qtuple) def _process_queue(self): """ Process any RCON commands in the queue. Called as soon as we are authed and after a command has been completed. Commands are added to the queue if `send_cmd` is called when we are busy (i.e authing or reading/writing) """ logging.debug("Processing RCON command queue") try: command, callback = self._queue.popleft() logging.debug("QUEUE - command: %s, callback: %s", command, callback) self.send_cmd(command, callback) except IndexError: pass except: logging.exception("Exception processing queue") def busy(self): return (self._stream.reading() or self._stream.writing() or self._busy) @property def closed(self): return self._stream.closed()