diff --git a/src/xpra/client/client_base.py b/src/xpra/client/client_base.py index 53281c0733..2a81f8bac8 100644 --- a/src/xpra/client/client_base.py +++ b/src/xpra/client/client_base.py @@ -349,6 +349,13 @@ def _process_disconnect(self, packet): self.warn_and_quit(e, "server requested disconnect: %s" % info) def _process_connection_lost(self, packet): + p = self._protocol + if p and p.input_raw_packetcount==0: + props = p.get_info() + c = props.get("compression", "unknown") + e = props.get("encoder", "unknown") + log.warn("disconnected without receiving a single packet, not an xpra server?") + log.warn("(maybe it does not support '%s' compression or '%s' packet encoding)", c, e) self.warn_and_quit(EXIT_CONNECTION_LOST, "Connection lost") def _process_challenge(self, packet): diff --git a/src/xpra/client/window_backing_base.py b/src/xpra/client/window_backing_base.py index 157d20a76a..820f94bb45 100644 --- a/src/xpra/client/window_backing_base.py +++ b/src/xpra/client/window_backing_base.py @@ -11,7 +11,7 @@ from threading import Lock from xpra.net.mmap_pipe import mmap_read -from xpra.net.protocol import has_lz4, LZ4_uncompress +from xpra.net import compression from xpra.util import typedict from xpra.codecs.codec_constants import get_colorspace_from_avutil_enum, get_PIL_decodings from xpra.codecs.loader import get_codec @@ -183,8 +183,8 @@ def process_delta(self, raw_data, width, height, rowstride, options): if options.intget("zlib", 0)>0: img_data = zlib.decompress(raw_data) elif options.boolget("lz4", False): - assert has_lz4 - img_data = LZ4_uncompress(raw_data) + assert compression.use_lz4 + img_data = compression.LZ4_uncompress(raw_data) if len(img_data)!=rowstride * height: log.error("invalid img data %s: %s", type(img_data), str(img_data)[:256]) raise Exception("expected %s bytes for %sx%s with rowstride=%s but received %s (%s compressed)" % diff --git a/src/xpra/net/compression.py b/src/xpra/net/compression.py index 5506ea760b..acce6d7740 100644 --- a/src/xpra/net/compression.py +++ b/src/xpra/net/compression.py @@ -10,12 +10,7 @@ from xpra.log import Logger log = Logger("network", "protocol") -debug = log.debug - - -ZLIB_FLAG = 0x00 -LZ4_FLAG = 0x10 -BZ2_FLAG = 0x20 +from xpra.net.header import LZ4_FLAG, ZLIB_FLAG, BZ2_FLAG try: @@ -64,6 +59,15 @@ def nocompress(packet, level): use_lz4 = has_lz4 +def get_compression_caps(): + return { + "lz4" : use_lz4, + "bz2" : use_bz2, + "zlib" : use_zlib, + "zlib.version" : zlib.__version__, + } + + class Compressed(object): def __init__(self, datatype, data): self.datatype = datatype @@ -94,3 +98,25 @@ def compressed_wrapper(datatype, data, level=5, lz4=False): algo = "zlib" cl, cdata = zcompress(data, level) return LevelCompressed(datatype, cdata, cl, algo) + + +def get_compression_type(level): + if level & LZ4_FLAG: + return "lz4" + elif level & BZ2_FLAG: + return "bz2" + else: + return "zlib" + +def decompress(data, level): + #log.info("decompress(%s bytes, %s) type=%s", len(data), get_compression_type(level)) + if level & LZ4_FLAG: + assert has_lz4, "lz4 is not available" + assert use_lz4, "lz4 is not enabled" + return LZ4_uncompress(data) + elif level & BZ2_FLAG: + assert use_bz2, "bz2 is not enabled" + return bz2.decompress(data) + else: + assert use_zlib, "zlib is not enabled" + return zlib.decompress(data) diff --git a/src/xpra/net/header.py b/src/xpra/net/header.py index 107bc5c062..3297b8baa0 100644 --- a/src/xpra/net/header.py +++ b/src/xpra/net/header.py @@ -7,6 +7,16 @@ import struct +ZLIB_FLAG = 0x0 #assume zlib if no other compression flag is set +FLAGS_RENCODE = 0x1 +FLAGS_CIPHER = 0x2 +FLAGS_YAML = 0x4 +#0x8 is free +LZ4_FLAG = 0x10 +BZ2_FLAG = 0x20 +FLAGS_NOHEADER = 0x40 +#0x80 is free + if sys.version_info[:2]>=(2,5): def unpack_header(buf): return struct.unpack_from('!cBBBL', buf) diff --git a/src/xpra/net/packet_encoding.py b/src/xpra/net/packet_encoding.py index 2f5b606ec1..aa69bd4499 100644 --- a/src/xpra/net/packet_encoding.py +++ b/src/xpra/net/packet_encoding.py @@ -9,7 +9,7 @@ from xpra.log import Logger log = Logger("network", "protocol") -debug = log.debug +from xpra.net.header import FLAGS_RENCODE, FLAGS_YAML #, FLAGS_BENCODE rencode_dumps, rencode_loads, rencode_version = None, None, None @@ -54,3 +54,43 @@ has_yaml = yaml_encode is not None and yaml_decode is not None use_yaml = has_yaml and os.environ.get("XPRA_USE_YAML", "1")=="1" log("packet encoding: has_yaml=%s, use_yaml=%s, version=%s", has_yaml, use_yaml, yaml_version) + + +def get_packet_encoding_caps(): + caps = { + "rencode" : use_rencode, + "bencode" : use_bencode, + "yaml" : use_yaml, + } + if has_rencode: + assert rencode_version is not None + caps["rencode.version"] = rencode_version + if has_bencode: + assert bencode_version is not None + caps["bencode.version"] = bencode_version + if has_yaml: + assert yaml_version is not None + caps["yaml.version"] = yaml_version + return caps + +def get_packet_encoding_type(protocol_flags): + if protocol_flags & FLAGS_RENCODE: + return "rencode" + elif protocol_flags & FLAGS_YAML: + return "yaml" + else: + return "bencode" + +def decode(data, protocol_flags): + if protocol_flags & FLAGS_RENCODE: + assert has_rencode, "we don't support rencode mode but the other end sent us a rencoded packet! not an xpra client?" + return list(rencode_loads(data)) + elif protocol_flags & FLAGS_YAML: + assert has_yaml, "we don't support yaml mode but the other end sent us a yaml packet! not an xpra client?" + return list(yaml_decode(data)) + else: + #if sys.version>='3': + # data = data.decode("latin1") + packet, l = bdecode(data) + assert l==len(data) + return packet diff --git a/src/xpra/net/protocol.py b/src/xpra/net/protocol.py index 0402475f84..a7e5af35dc 100644 --- a/src/xpra/net/protocol.py +++ b/src/xpra/net/protocol.py @@ -14,8 +14,6 @@ import threading import binascii from threading import Lock -import zlib -import bz2 from xpra.log import Logger @@ -25,13 +23,12 @@ from xpra.util import repr_ellipsized from xpra.net.bytestreams import ABORT from xpra.net import compression -from xpra.net.compression import nocompress, zcompress, bzcompress, BZ2_FLAG, has_lz4, LZ4_FLAG, lz4_compress, LZ4_uncompress, Compressed, LevelCompressed -from xpra.net.header import unpack_header, pack_header, pack_header_and_data +from xpra.net.compression import nocompress, zcompress, bzcompress, lz4_compress, Compressed, LevelCompressed, get_compression_caps +from xpra.net.header import unpack_header, pack_header, pack_header_and_data, FLAGS_RENCODE, FLAGS_YAML, FLAGS_CIPHER, FLAGS_NOHEADER from xpra.net.crypto import get_crypto_caps, get_cipher from xpra.net import packet_encoding -from xpra.net.packet_encoding import rencode_dumps, rencode_loads, rencode_version, has_rencode, \ - bencode, bdecode, bencode_version, has_bencode, \ - yaml_encode, yaml_decode, yaml_version, has_yaml +from xpra.net.packet_encoding import get_packet_encoding_caps, rencode_dumps, decode, has_rencode, \ + bencode, has_bencode, yaml_encode, has_yaml #stupid python version breakage: @@ -62,10 +59,6 @@ def get_network_caps(legacy=True): "rencode" : packet_encoding.use_rencode, "bencode" : packet_encoding.use_bencode, "yaml" : packet_encoding.use_yaml, - "lz4" : compression.use_lz4, - "bz2" : compression.use_bz2, - "zlib" : compression.use_zlib, - "zlib.version" : zlib.__version__, "mmap" : mmap, } if legacy: @@ -75,16 +68,8 @@ def get_network_caps(legacy=True): "chunked_compression" : True }) caps.update(get_crypto_caps()) - - if has_rencode: - assert rencode_version is not None - caps["rencode.version"] = rencode_version - if has_bencode: - assert bencode_version is not None - caps["bencode.version"] = bencode_version - if has_yaml: - assert yaml_version is not None - caps["yaml.version"] = yaml_version + caps.update(get_compression_caps()) + caps.update(get_packet_encoding_caps()) return caps @@ -96,11 +81,6 @@ class Protocol(object): CONNECTION_LOST = "connection-lost" GIBBERISH = "gibberish" - FLAGS_RENCODE = 0x1 - FLAGS_CIPHER = 0x2 - FLAGS_YAML = 0x4 - FLAGS_NOHEADER = 0x40 - def __init__(self, scheduler, conn, process_packet_cb, get_packet_cb=None): """ You must call this constructor and source_has_more() from the main thread. @@ -187,10 +167,10 @@ def restore_state(self, state): if state.get("rencode"): self.enable_rencode() - elif state.get("yaml"): - self.enable_yaml() elif state.get("bencode"): self.enable_bencode() + elif state.get("yaml"): + self.enable_yaml() else: raise Exception("invalid state: no encoder specified!") @@ -260,11 +240,13 @@ def get_info(self): try: info["encoder"] = self._encoder.__name__ except: - pass - try: - info.update(self._conn.get_info()) - except: - log.error("error collecting connection information on %s", self._conn, exc_info=True) + log.error("no __name__ defined on %s (type: %s)", self._encoder, type(self._encoder)) + c = self._conn + if c: + try: + info.update(self._conn.get_info()) + except: + log.error("error collecting connection information on %s", self._conn, exc_info=True) return info @@ -333,7 +315,7 @@ def _add_chunks_to_queue(self, chunks, proto_flags, start_send_cb=None, end_send payload_size = len(data) actual_size = payload_size if self.cipher_out: - proto_flags |= Protocol.FLAGS_CIPHER + proto_flags |= FLAGS_CIPHER #note: since we are padding: l!=len(data) padding = (self.cipher_out_block_size - len(data) % self.cipher_out_block_size) * " " if len(padding)==0: @@ -345,7 +327,7 @@ def _add_chunks_to_queue(self, chunks, proto_flags, start_send_cb=None, end_send data = self.cipher_out.encrypt(padded) assert len(data)==actual_size log("sending %s bytes encrypted with %s padding", payload_size, len(padding)) - if proto_flags & Protocol.FLAGS_NOHEADER: + if proto_flags & FLAGS_NOHEADER: #for plain/text packets (ie: gibberish response) items.append((data, scb, ecb)) elif pack_header_and_data is not None and actual_size0: try: - if compression_level & LZ4_FLAG: - ctype = "lz4" - assert has_lz4, "lz4 is not available" - assert compression.use_lz4, "lz4 is not enabled" - data = LZ4_uncompress(data) - elif compression_level & BZ2_FLAG: - ctype = "bz2" - assert compression.use_bz2, "bz2 is not enabled" - data = bz2.decompress(data) - else: - ctype = "zlib" - assert compression.use_zlib, "zlib is not enabled" - data = zlib.decompress(data) + data = compression.decompress(data, compression_level) except Exception, e: + ctype = compression.get_compression_type(compression_level) log("%s packet decompression failed", ctype, exc_info=True) if self.cipher_in: return self._call_connection_lost("%s packet decompression failed (invalid encryption key?): %s" % (ctype, e)) return self._call_connection_lost("%s packet decompression failed: %s" % (ctype, e)) - if self.cipher_in and not (protocol_flags & Protocol.FLAGS_CIPHER): + if self.cipher_in and not (protocol_flags & FLAGS_CIPHER): return self._call_connection_lost("unencrypted packet dropped: %s" % repr_ellipsized(data)) if self._closed: @@ -804,22 +777,13 @@ def debug_str(s): continue #final packet (packet_index==0), decode it: try: - if protocol_flags & Protocol.FLAGS_RENCODE: - assert has_rencode, "we don't support rencode mode but the other end sent us a rencoded packet! not an xpra client?" - packet = list(rencode_loads(data)) - elif protocol_flags & Protocol.FLAGS_YAML: - assert has_yaml, "we don't support yaml mode but the other end sent us a yaml packet! not an xpra client?" - packet = list(yaml_decode(data)) - else: - #if sys.version>='3': - # data = data.decode("latin1") - packet, l = bdecode(data) - assert l==len(data) + packet = decode(data, protocol_flags) except ValueError, e: - log.error("value error reading packet: %s", e, exc_info=True) + etype = packet_encoding.get_packet_encoding_type(protocol_flags) + log.error("value error parsing %s packet: %s", etype, e, exc_info=True) if self._closed: return - log("failed to parse packet: %s", binascii.hexlify(data)) + log("failed to parse %s packet: %s", etype, binascii.hexlify(data)) msg = "gibberish received: %s, packet index=%s, packet size=%s, buffer size=%s, error=%s" % (repr_ellipsized(data), packet_index, payload_size, bl, e) self.gibberish(msg, data) return diff --git a/src/xpra/server/server_core.py b/src/xpra/server/server_core.py index f5978e8445..888c211674 100644 --- a/src/xpra/server/server_core.py +++ b/src/xpra/server/server_core.py @@ -409,13 +409,22 @@ def _process_hello(self, proto, packet): capabilities = packet[1] c = typedict(capabilities) proto.set_compression_level(c.intget("compression_level", self.compression_level)) - proto.enable_encoder_from_caps(c) proto.enable_compressor_from_caps(c) + if not proto.enable_encoder_from_caps(c): + #we cannot continue without a packet encoder! + from xpra.net import packet_encoding + opts = [x for x,b in { + "bencode" : packet_encoding.use_bencode, + "rencode" : packet_encoding.use_rencode, + "yaml" : packet_encoding.use_yaml, + }.items() if b] + self.disconnect_client(proto, "failed to negotiate a packet encoder, try: %s" % (", ".join(opts))) + return log("process_hello: capabilities=%s", capabilities) if c.boolget("version_request"): self.send_version_info(proto) - return False + return auth_caps = self.verify_hello(proto, c) if auth_caps is not False: