Esempio n. 1
0
    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]))
Esempio n. 2
0
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()
Esempio n. 3
0
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()
Esempio n. 4
0
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()