xpra icon
Bug tracker and wiki

Ticket #639: udp-v9.patch

File udp-v9.patch, 194.3 KB (added by Antoine Martin, 3 years ago)

more asynchronous packets, rfb authentication integration, disabled delta, trim protocol classes, fix pydev warnings, etc.

  • xpra/client/client_base.py

     
    2525from xpra.net import compression
    2626from xpra.net.protocol import Protocol, sanity_checks
    2727from xpra.net.net_util import get_network_caps
    28 from xpra.net.crypto import crypto_backend_init, get_iterations, get_iv, get_salt, choose_padding, get_digest_module, \
     28from xpra.net.crypto import crypto_backend_init, get_iterations, get_iv, get_salt, choose_padding, get_hexdigest, \
    2929    ENCRYPTION_CIPHERS, ENCRYPT_FIRST_PACKET, DEFAULT_IV, DEFAULT_SALT, DEFAULT_ITERATIONS, INITIAL_PADDING, DEFAULT_PADDING, ALL_PADDING_OPTIONS, PADDING_OPTIONS
    3030from xpra.version_util import version_compat_check, get_version_info, XPRA_VERSION
    3131from xpra.platform.info import get_name
    32 from xpra.os_util import get_machine_id, get_user_uuid, load_binary_file, SIGNAMES, PYTHON3, PYTHON2, strtobytes, bytestostr, memoryview_to_bytes
     32from xpra.os_util import get_machine_id, get_user_uuid, load_binary_file, SIGNAMES, PYTHON3, PYTHON2, strtobytes, bytestostr
    3333from xpra.util import flatten_dict, typedict, updict, xor, repr_ellipsized, nonl, envbool, disconnect_is_an_error, dump_all_frames
    3434from xpra.net.file_transfer import FileTransferHandler
    3535
     
    165165
    166166
    167167    def install_signal_handlers(self):
    168         def deadly_signal(signum, frame):
     168        def deadly_signal(signum, _frame):
    169169            sys.stderr.write("\ngot deadly signal %s, exiting\n" % SIGNAMES.get(signum, signum))
    170170            sys.stderr.flush()
    171171            self.cleanup()
    172172            os._exit(128 + signum)
    173         def app_signal(signum, frame):
     173        def app_signal(signum, _frame):
    174174            sys.stderr.write("\ngot signal %s, exiting\n" % SIGNAMES.get(signum, signum))
    175175            sys.stderr.flush()
    176176            signal.signal(signal.SIGINT, deadly_signal)
     
    217217            log("disconnect_and_quit: protocol_closed()")
    218218            self.idle_add(self.quit, exit_code)
    219219        if p:
    220             p.flush_then_close(["disconnect", reason], done_callback=protocol_closed)
     220            p.send_disconnect([reason], done_callback=protocol_closed)
    221221        self.timeout_add(1000, self.quit, exit_code)
    222222
    223223    def exit(self):
     
    232232        raise NotImplementedError()
    233233
    234234    def setup_connection(self, conn):
    235         netlog("setup_connection(%s) timeout=%s", conn, conn.timeout)
    236         self._protocol = Protocol(self.get_scheduler(), conn, self.process_packet, self.next_packet)
     235        netlog.info("setup_connection(%s) timeout=%s, socktype=%s", conn, conn.timeout, conn.socktype)
     236        if conn.socktype in ("udp", "dtls"):
     237            from xpra.net.udp_protocol import UDPClientProtocol
     238            self._protocol = UDPClientProtocol(self.get_scheduler(), conn, self.process_packet, self.next_packet)
     239            #use a random uuid:
     240            import random
     241            self._protocol.uuid = random.randint(0, 2**64-1)
     242            self.set_packet_handlers(self._packet_handlers, {
     243                "udp-control"   : self._process_udp_control,
     244                })
     245        else:
     246            self._protocol = Protocol(self.get_scheduler(), conn, self.process_packet, self.next_packet)
    237247        self._protocol.large_packets.append("keymap-changed")
    238248        self._protocol.large_packets.append("server-settings")
    239249        self._protocol.large_packets.append("logging")
     
    254264            getChildReaper().add_process(proc, name, command, ignore=True, forget=False)
    255265        netlog("setup_connection(%s) protocol=%s", conn, self._protocol)
    256266
     267    def _process_udp_control(self, packet):
     268        self._protocol.process_control(*packet[1:])
    257269
     270
    258271    def remove_packet_handlers(self, *keys):
    259272        for k in keys:
    260273            for d in (self._packet_handlers, self._ui_packet_handlers):
     
    440453            p.source_has_more()
    441454
    442455    def next_packet(self):
     456        netlog("next_packet() packets in queues: priority=%i, ordinary=%i, mouse=%s", len(self._priority_packets), len(self._ordinary_packets), bool(self._mouse_position))
     457        synchronous = True
    443458        if self._priority_packets:
    444459            packet = self._priority_packets.pop(0)
    445460        elif self._ordinary_packets:
     
    446461            packet = self._ordinary_packets.pop(0)
    447462        elif self._mouse_position is not None:
    448463            packet = self._mouse_position
     464            synchronous = False
    449465            self._mouse_position = None
    450466        else:
    451467            packet = None
     
    452468        has_more = packet is not None and \
    453469                (bool(self._priority_packets) or bool(self._ordinary_packets) \
    454470                 or self._mouse_position is not None)
    455         return packet, None, None, has_more
     471        return packet, None, None, None, synchronous, has_more
    456472
    457473
    458474    def cleanup(self):
     
    513529            return
    514530        self.warn_and_quit(e, "server requested disconnect: %s" % s)
    515531
    516     def _process_connection_lost(self, packet):
     532    def _process_connection_lost(self, _packet):
    517533        p = self._protocol
    518534        if p and p.input_raw_packetcount==0:
    519535            props = p.get_info()
     
    555571        client_salt = get_salt(len(server_salt))
    556572        #TODO: use some key stretching algorigthm? (meh)
    557573        salt = xor(server_salt, client_salt)
    558         if digest.startswith(b"hmac"):
    559             import hmac
    560             digestmod = get_digest_module(digest)
    561             if not digestmod:
    562                 log("invalid digest module '%s': %s", digest)
    563                 warn_server_and_exit(EXIT_UNSUPPORTED, "server requested digest '%s' but it is not supported" % digest, "invalid digest")
    564                 return
    565             password = strtobytes(password)
    566             salt = memoryview_to_bytes(salt)
    567             challenge_response = hmac.HMAC(password, salt, digestmod=digestmod).hexdigest()
    568             authlog("hmac.HMAC(%s, %s)=%s", binascii.hexlify(password), binascii.hexlify(salt), challenge_response)
    569         elif digest==b"xor":
    570             #don't send XORed password unencrypted:
    571             encrypted = self._protocol.cipher_out or self._protocol.get_info().get("type")=="ssl"
     574
     575        #don't send XORed password unencrypted:
     576        if digest==b"xor":
     577            encrypted = self._protocol.cipher_out or self._protocol.get_info().get("type") in ("ssl", "wss")
    572578            local = self.display_desc.get("local", False)
    573579            authlog("xor challenge, encrypted=%s, local=%s", encrypted, local)
    574580            if local and ALLOW_LOCALHOST_PASSWORDS:
     
    576582            elif not encrypted and not ALLOW_UNENCRYPTED_PASSWORDS:
    577583                warn_server_and_exit(EXIT_ENCRYPTION, "server requested digest %s, cowardly refusing to use it without encryption" % digest, "invalid digest")
    578584                return
    579             salt = salt[:len(password)]
    580             challenge_response = memoryview_to_bytes(xor(password, salt))
    581         else:
    582             warn_server_and_exit(EXIT_PASSWORD_REQUIRED, "server requested an unsupported digest: %s" % digest, "invalid digest")
     585        challenge_response = get_hexdigest(digest, password, salt)
     586        if not challenge_response:
     587            log("invalid digest module '%s': %s", digest)
     588            warn_server_and_exit(EXIT_UNSUPPORTED, "server requested digest '%s' but it is not supported" % digest, "invalid digest")
    583589            return
    584         if digest:
    585             authlog("%s(%s, %s)=%s", digest, binascii.hexlify(password), binascii.hexlify(salt), challenge_response)
     590        authlog("%s(%s, %s)=%s", digest, binascii.hexlify(password), binascii.hexlify(salt), challenge_response)
    586591        self.password_sent = True
    587592        self.remove_packet_handlers("challenge")
    588593        self.send_hello(challenge_response, client_salt)
  • xpra/client/gtk_base/gtk_client_base.py

     
    468468               "configure.pointer"      : True,
    469469               "frame_sizes"            : self.get_window_frame_sizes()
    470470               })
    471         from xpra.client.window_backing_base import DELTA_BUCKETS
    472471        updict(capabilities, "encoding", {
    473472                    "icons.greedy"      : True,         #we don't set a default window icon any more
    474473                    "icons.size"        : (64, 64),     #size we want
    475474                    "icons.max_size"    : (128, 128),   #limit
    476                     "delta_buckets"     : DELTA_BUCKETS,
    477475                    })
     476        from xpra.client import window_backing_base
     477        if self._protocol._conn.socktype=="udp":
     478            #lossy protocol means we can't use delta regions:
     479            log("no delta buckets with udp, since we can drop paint packets")
     480            window_backing_base.DELTA_BUCKETS = 0
     481        updict(capabilities, "encoding", {
     482                    "delta_buckets"     : window_backing_base.DELTA_BUCKETS,
     483                    })
    478484        return capabilities
    479485
    480486
     
    487493        return get_screen_sizes(xscale, yscale)
    488494
    489495
    490     def reset_windows_cursors(self, *args):
     496    def reset_windows_cursors(self, *_args):
    491497        cursorlog("reset_windows_cursors() resetting cursors for: %s", self._cursors.keys())
    492498        for w,cursor_data in list(self._cursors.items()):
    493499            self.set_windows_cursor([w], cursor_data)
  • xpra/client/gtk_base/gtk_client_window_base.py

     
    550550        def do_set_shape():
    551551            xid = get_xid(self.get_window())
    552552            x_off, y_off = shape.get("x", 0), shape.get("y", 0)
    553             for kind, name in SHAPE_KIND.items():
     553            for kind, name in SHAPE_KIND.items():       #@UndefinedVariable
    554554                rectangles = shape.get("%s.rectangles" % name)      #ie: Bounding.rectangles = [(0, 0, 150, 100)]
    555555                if rectangles:
    556556                    #adjust for scaling:
  • xpra/client/ui_client_base.py

     
    3939webcamlog = Logger("webcam")
    4040notifylog = Logger("notify")
    4141cursorlog = Logger("cursor")
     42netlog = Logger("network")
    4243
    4344
    4445from xpra.gtk_common.gobject_util import no_arg_signal
     
    16971698        if FAKE_BROKEN_CONNECTION>0:
    16981699            self._server_ok = (int(monotonic_time()) % FAKE_BROKEN_CONNECTION) <= (FAKE_BROKEN_CONNECTION//2)
    16991700        else:
    1700             self._server_ok = not FAKE_BROKEN_CONNECTION and self.last_ping_echoed_time>=ping_sent_time
    1701         log("check_server_echo(%s) last=%s, server_ok=%s", ping_sent_time, last, self._server_ok)
     1701            self._server_ok = self.last_ping_echoed_time>=ping_sent_time
     1702        log("check_server_echo(%s) last=%s, server_ok=%s (last_ping_echoed_time=%s)", ping_sent_time, last, self._server_ok, self.last_ping_echoed_time)
    17021703        if last!=self._server_ok and not self._server_ok:
    17031704            log.info("server is not responding, drawing spinners over the windows")
    17041705            def timer_redraw():
     
    17241725                w.spinner(ok)
    17251726
    17261727    def check_echo_timeout(self, ping_time):
    1727         log("check_echo_timeout(%s) last_ping_echoed_time=%s", ping_time, self.last_ping_echoed_time)
     1728        netlog("check_echo_timeout(%s) last_ping_echoed_time=%s", ping_time, self.last_ping_echoed_time)
    17281729        if self.last_ping_echoed_time<ping_time:
    17291730            #no point trying to use disconnect_and_quit() to tell the server here..
    17301731            self.warn_and_quit(EXIT_TIMEOUT, "server ping timeout - waited %s seconds without a response" % PING_TIMEOUT)
     
    17381739            l = [x for _,x in list(self.server_ping_latency)]
    17391740            avg = sum(l) / len(l)
    17401741            wait = min(5, 1.0+avg*2.0)
    1741             log("average server latency=%.1f, using max wait %.2fs", 1000.0*avg, wait)
     1742            netlog("send_ping() timestamp=%s, average server latency=%.1f, using max wait %.2fs", now_ms, 1000.0*avg, wait)
    17421743        self.timeout_add(int(1000.0*wait), self.check_server_echo, now_ms)
    17431744        return True
    17441745
     
    17511752        self.server_load = l1, l2, l3
    17521753        if cl>=0:
    17531754            self.client_ping_latency.append((monotonic_time(), cl/1000.0))
    1754         log("ping echo server load=%s, measured client latency=%sms", self.server_load, cl)
     1755        netlog("ping echo server load=%s, measured client latency=%sms", self.server_load, cl)
    17551756
    17561757    def _process_ping(self, packet):
    17571758        echotime = packet[1]
  • xpra/clipboard/clipboard_base.py

     
    7878
    7979
    8080#may get overriden
    81 def nosanitize_gtkselectiondata(selectiondata):
     81def nosanitize_gtkselectiondata(_selectiondata):
    8282    return False
    8383sanitize_gtkselectiondata = nosanitize_gtkselectiondata
    8484
     
    776776                self._last_targets = targets or ()
    777777            self._clipboard.request_targets(got_targets)
    778778            return
    779         def unpack(clipboard, selection_data, user_data):
     779        def unpack(clipboard, selection_data, _user_data):
    780780            log("unpack %s: %s", clipboard, type(selection_data))
    781781            global sanitize_gtkselectiondata
    782782            if selection_data and sanitize_gtkselectiondata(selection_data):
  • xpra/log.py

     
    241241                ("protocol"     , "Packet input and output (formatting, parsing, sending and receiving)"),
    242242                ("websocket"    , "WebSocket layer"),
    243243                ("named-pipe"   , "Named pipe"),
     244                ("udp"          , "UDP"),
    244245                ("crypto"       , "Encryption"),
    245246                ("auth"         , "Authentication"),
    246247                ])),
  • xpra/net/bytestreams.py

     
    9898            return f(*a, **kw)
    9999        except Exception as e:
    100100            retry = can_retry(e)
     101            log("untilConcludes", exc_info=True)
    101102            log("untilConcludes(%s, %s, %s, %s, %s) %s, retry=%s", is_active_cb, can_retry, f, a, kw, e, retry)
    102103            if retry:
    103104                if wait>0:
     
    277278            i = s
    278279        log("%s.close() for socket=%s", self, i)
    279280        Connection.close(self)
    280         s.settimeout(0)
     281        try:
     282            s.settimeout(0)
     283        except:
     284            pass
    281285        #this is more proper but would break the proxy server:
    282286        #s.shutdown(socket.SHUT_RDWR)
    283287        s.close()
     
    306310        s = self._socket
    307311        if not s:
    308312            return None
    309         return {
    310                 #"class"         : str(type(s)),
    311                 "fileno"        : s.fileno(),
    312                 "timeout"       : int(1000*(s.gettimeout() or 0)),
     313        info = {
    313314                "family"        : FAMILY_STR.get(s.family, s.family),
    314315                "proto"         : s.proto,
    315316                "type"          : PROTOCOL_STR.get(s.type, s.type),
    316317                }
     318        try:
     319            info["timeout"] = int(1000*(s.gettimeout() or 0))
     320        except:
     321            pass
     322        if hasattr(s, "fileno"):
     323            info["fileno"] = s.fileno()
     324        return info
    317325
    318 
    319326try:
    320327    #this wrapper class allows us to override the normal ssl.Socket
    321328    #class so that we can fake peek() support by actually reading from the socket
  • xpra/net/crypto.py

     
    55# later version. See the file COPYING for details.
    66
    77import os
    8 from xpra.util import envint, envbool, csv
     8import hmac
     9import hashlib
     10import binascii
     11
     12from xpra.util import envint, envbool, csv, xor
    913from xpra.log import Logger
     14from xpra.os_util import strtobytes, memoryview_to_bytes
     15
    1016log = Logger("network", "crypto")
    1117
    1218ENABLE_CRYPTO = envbool("XPRA_ENABLE_CRYPTO", True)
     
    3339        PADDING_OPTIONS.append(x)
    3440
    3541
     42try:
     43    from xpra.codecs.xor.cyxor import xor_str           #@UnresolvedImport
     44    xor = xor_str
     45except Exception as e:
     46    log("no accelerated xor: %s", e)
     47
     48
    3649ENCRYPTION_CIPHERS = []
    3750backend = False
    3851def crypto_backend_init():
     
    5568    backend = None
    5669
    5770def validate_backend(try_backend):
    58     import binascii
    59     from xpra.os_util import strtobytes
    6071    try_backend.init()
    6172    message = b"some message1234"
    6273    password = "this is our secret"
     
    8293
    8394
    8495def get_digests():
    85     import hashlib
    86     return ["hmac", "xor"] + ["hmac+%s" % x for x in list(reversed(sorted(hashlib.algorithms_available)))]
     96    digests = ["hmac", "xor"] + ["hmac+%s" % x for x in list(reversed(sorted(hashlib.algorithms_available)))]
     97    try:
     98        from xpra.net import d3des
     99        assert d3des
     100        digests.append("des")
     101    except:
     102        pass
     103    return digests
    87104
    88105def get_digest_module(digest):
    89106    log("get_digest_module(%s)", digest)
    90107    if not digest or not digest.startswith("hmac"):
    91108        return None
    92     import hashlib
    93109    try:
    94110        digest_module = digest.split("+")[1]        #ie: "hmac+sha512" -> "sha512"
    95111    except:
     
    112128        return "hmac"
    113129    if "xor" in options:
    114130        return "xor"
     131    if "des" in options:
     132        return "des"
    115133    raise Exception("no known digest options found in '%s'" % csv(options))
    116134
     135def get_hexdigest(digest, password, salt):
     136    assert digest and password and salt
     137    if digest=="des":
     138        from xpra.net.d3des import generate_response
     139        password = password.ljust(8, "\x00")[:8]
     140        salt = salt.ljust(16, "\x00")[:16]
     141        v = generate_response(password, salt)
     142        return binascii.hexlify(v)
     143    elif digest=="xor":
     144        salt = salt.ljust(16, "\x00")[:len(password)]
     145        v = memoryview_to_bytes(xor(password, salt))
     146        return binascii.hexlify(v)
     147    digestmod = get_digest_module(digest)
     148    if not digestmod:
     149        log("invalid digest module '%s': %s", digest)
     150        return None
     151        #warn_server_and_exit(EXIT_UNSUPPORTED, "server requested digest '%s' but it is not supported" % digest, "invalid digest")
     152    password = strtobytes(password)
     153    salt = memoryview_to_bytes(salt)
     154    v = hmac.HMAC(password, salt, digestmod=digestmod).hexdigest()
     155    return v
    117156
     157def verify_digest(digest, password, salt, challenge_response):
     158    if not password or not salt or not challenge_response:
     159        return False
     160    verify = get_hexdigest(digest, password, salt)
     161    if not hmac.compare_digest(verify, challenge_response):
     162        log("expected '%s' but got '%s'", verify, challenge_response)
     163        return False
     164    return True
     165
     166
    118167def pad(padding, size):
    119168    if padding==PADDING_LEGACY:
    120169        return " "*size
  • xpra/net/d3des.py

     
     1#!/usr/bin/env python
     2##
     3##  d3des.py - DES implementation
     4##
     5##  Copyright (c) 2009 by Yusuke Shinyama
     6##
     7
     8# This is a Python rewrite of d3des.c by Richard Outerbridge.
     9#
     10# I referred to the original VNC viewer code for the changes that
     11# is necessary to maintain the exact behavior of the VNC protocol.
     12# Two constants and two functions were added to the original d3des
     13# code.  These added parts were written in Python and marked
     14# below.  I believe that the added parts do not make this program
     15# a "derivative work" of the VNC viewer (which is GPL'ed and
     16# written in C), but if there's any problem, let me know.
     17#
     18# Yusuke Shinyama (yusuke at cs dot nyu dot edu)
     19
     20
     21#  D3DES (V5.09) -
     22#
     23#  A portable, public domain, version of the Data Encryption Standard.
     24#
     25#  Written with Symantec's THINK (Lightspeed) C by Richard Outerbridge.
     26#  Thanks to: Dan Hoey for his excellent Initial and Inverse permutation
     27#  code;  Jim Gillogly & Phil Karn for the DES key schedule code; Dennis
     28#  Ferguson, Eric Young and Dana How for comparing notes; and Ray Lau,
     29#  for humouring me on.
     30#
     31#  Copyright (c) 1988,1989,1990,1991,1992 by Richard Outerbridge.
     32#  (GEnie : OUTER; CIS : [71755,204]) Graven Imagery, 1992.
     33#
     34
     35from struct import pack, unpack
     36
     37
     38###################################################
     39###
     40###  start: changes made for VNC.
     41###
     42
     43# This constant was taken from vncviewer/rfb/vncauth.c:
     44vnckey = [ 23,82,107,6,35,78,88,7 ]
     45
     46# This is a departure from the original code.
     47#bytebit = [ 0200, 0100, 040, 020, 010, 04, 02, 01 ] # original
     48bytebit = [ 01, 02, 04, 010, 020, 040, 0100, 0200 ] # VNC version
     49
     50# two password functions for VNC protocol.
     51def decrypt_passwd(data):
     52    dk = deskey(pack('8B', *vnckey), True)
     53    return desfunc(data, dk)
     54
     55def generate_response(passwd, challange):
     56    ek = deskey((passwd+'\x00'*8)[:8], False)
     57    return desfunc(challange[:8], ek) + desfunc(challange[8:], ek)
     58
     59###
     60###  end: changes made for VNC.
     61###
     62###################################################
     63
     64
     65bigbyte = [
     66  0x800000L,    0x400000L,      0x200000L,      0x100000L,
     67  0x80000L,     0x40000L,       0x20000L,       0x10000L,
     68  0x8000L,      0x4000L,        0x2000L,        0x1000L,
     69  0x800L,       0x400L,         0x200L,         0x100L,
     70  0x80L,        0x40L,          0x20L,          0x10L,
     71  0x8L,         0x4L,           0x2L,           0x1L
     72  ]
     73
     74# Use the key schedule specified in the Standard (ANSI X3.92-1981).
     75
     76pc1 = [
     77  56, 48, 40, 32, 24, 16,  8,    0, 57, 49, 41, 33, 25, 17,
     78   9,  1, 58, 50, 42, 34, 26,   18, 10,  2, 59, 51, 43, 35,
     79  62, 54, 46, 38, 30, 22, 14,    6, 61, 53, 45, 37, 29, 21,
     80  13,  5, 60, 52, 44, 36, 28,   20, 12,  4, 27, 19, 11,  3
     81  ]
     82
     83totrot = [ 1,2,4,6,8,10,12,14,15,17,19,21,23,25,27,28 ]
     84
     85pc2 = [
     86  13, 16, 10, 23,  0,  4,  2, 27, 14,  5, 20,  9,
     87  22, 18, 11,  3, 25,  7, 15,  6, 26, 19, 12,  1,
     88  40, 51, 30, 36, 46, 54, 29, 39, 50, 44, 32, 47,
     89  43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31
     90  ]
     91
     92def deskey(key, decrypt):      # Thanks to James Gillogly & Phil Karn!
     93    key = unpack('8B', key)
     94
     95    pc1m = [0]*56
     96    pcr = [0]*56
     97    kn = [0L]*32
     98
     99    for j in range(56):
     100        l = pc1[j]
     101        m = l & 07
     102        if key[l >> 3] & bytebit[m]:
     103            pc1m[j] = 1
     104        else:
     105            pc1m[j] = 0
     106
     107    for i in range(16):
     108        if decrypt:
     109            m = (15 - i) << 1
     110        else:
     111            m = i << 1
     112        n = m + 1
     113        kn[m] = kn[n] = 0L
     114        for j in range(28):
     115            l = j + totrot[i]
     116            if l < 28:
     117                pcr[j] = pc1m[l]
     118            else:
     119                pcr[j] = pc1m[l - 28]
     120        for j in range(28, 56):
     121            l = j + totrot[i]
     122            if l < 56:
     123                pcr[j] = pc1m[l]
     124            else:
     125                pcr[j] = pc1m[l - 28]
     126        for j in range(24):
     127            if pcr[pc2[j]]:
     128                kn[m] |= bigbyte[j]
     129            if pcr[pc2[j+24]]:
     130                kn[n] |= bigbyte[j]
     131
     132    return cookey(kn)
     133
     134def cookey(raw):
     135    key = []
     136    for i in range(0, 32, 2):
     137        (raw0, raw1) = (raw[i], raw[i+1])
     138        k  = (raw0 & 0x00fc0000L) << 6
     139        k |= (raw0 & 0x00000fc0L) << 10
     140        k |= (raw1 & 0x00fc0000L) >> 10
     141        k |= (raw1 & 0x00000fc0L) >> 6
     142        key.append(k)
     143        k  = (raw0 & 0x0003f000L) << 12
     144        k |= (raw0 & 0x0000003fL) << 16
     145        k |= (raw1 & 0x0003f000L) >> 4
     146        k |= (raw1 & 0x0000003fL)
     147        key.append(k)
     148    return key
     149
     150SP1 = [
     151  0x01010400L, 0x00000000L, 0x00010000L, 0x01010404L,
     152  0x01010004L, 0x00010404L, 0x00000004L, 0x00010000L,
     153  0x00000400L, 0x01010400L, 0x01010404L, 0x00000400L,
     154  0x01000404L, 0x01010004L, 0x01000000L, 0x00000004L,
     155  0x00000404L, 0x01000400L, 0x01000400L, 0x00010400L,
     156  0x00010400L, 0x01010000L, 0x01010000L, 0x01000404L,
     157  0x00010004L, 0x01000004L, 0x01000004L, 0x00010004L,
     158  0x00000000L, 0x00000404L, 0x00010404L, 0x01000000L,
     159  0x00010000L, 0x01010404L, 0x00000004L, 0x01010000L,
     160  0x01010400L, 0x01000000L, 0x01000000L, 0x00000400L,
     161  0x01010004L, 0x00010000L, 0x00010400L, 0x01000004L,
     162  0x00000400L, 0x00000004L, 0x01000404L, 0x00010404L,
     163  0x01010404L, 0x00010004L, 0x01010000L, 0x01000404L,
     164  0x01000004L, 0x00000404L, 0x00010404L, 0x01010400L,
     165  0x00000404L, 0x01000400L, 0x01000400L, 0x00000000L,
     166  0x00010004L, 0x00010400L, 0x00000000L, 0x01010004L
     167  ]
     168
     169SP2 = [
     170  0x80108020L, 0x80008000L, 0x00008000L, 0x00108020L,
     171  0x00100000L, 0x00000020L, 0x80100020L, 0x80008020L,
     172  0x80000020L, 0x80108020L, 0x80108000L, 0x80000000L,
     173  0x80008000L, 0x00100000L, 0x00000020L, 0x80100020L,
     174  0x00108000L, 0x00100020L, 0x80008020L, 0x00000000L,
     175  0x80000000L, 0x00008000L, 0x00108020L, 0x80100000L,
     176  0x00100020L, 0x80000020L, 0x00000000L, 0x00108000L,
     177  0x00008020L, 0x80108000L, 0x80100000L, 0x00008020L,
     178  0x00000000L, 0x00108020L, 0x80100020L, 0x00100000L,
     179  0x80008020L, 0x80100000L, 0x80108000L, 0x00008000L,
     180  0x80100000L, 0x80008000L, 0x00000020L, 0x80108020L,
     181  0x00108020L, 0x00000020L, 0x00008000L, 0x80000000L,
     182  0x00008020L, 0x80108000L, 0x00100000L, 0x80000020L,
     183  0x00100020L, 0x80008020L, 0x80000020L, 0x00100020L,
     184  0x00108000L, 0x00000000L, 0x80008000L, 0x00008020L,
     185  0x80000000L, 0x80100020L, 0x80108020L, 0x00108000L
     186  ]
     187
     188SP3 = [
     189  0x00000208L, 0x08020200L, 0x00000000L, 0x08020008L,
     190  0x08000200L, 0x00000000L, 0x00020208L, 0x08000200L,
     191  0x00020008L, 0x08000008L, 0x08000008L, 0x00020000L,
     192  0x08020208L, 0x00020008L, 0x08020000L, 0x00000208L,
     193  0x08000000L, 0x00000008L, 0x08020200L, 0x00000200L,
     194  0x00020200L, 0x08020000L, 0x08020008L, 0x00020208L,
     195  0x08000208L, 0x00020200L, 0x00020000L, 0x08000208L,
     196  0x00000008L, 0x08020208L, 0x00000200L, 0x08000000L,
     197  0x08020200L, 0x08000000L, 0x00020008L, 0x00000208L,
     198  0x00020000L, 0x08020200L, 0x08000200L, 0x00000000L,
     199  0x00000200L, 0x00020008L, 0x08020208L, 0x08000200L,
     200  0x08000008L, 0x00000200L, 0x00000000L, 0x08020008L,
     201  0x08000208L, 0x00020000L, 0x08000000L, 0x08020208L,
     202  0x00000008L, 0x00020208L, 0x00020200L, 0x08000008L,
     203  0x08020000L, 0x08000208L, 0x00000208L, 0x08020000L,
     204  0x00020208L, 0x00000008L, 0x08020008L, 0x00020200L
     205  ]
     206
     207SP4 = [
     208  0x00802001L, 0x00002081L, 0x00002081L, 0x00000080L,
     209  0x00802080L, 0x00800081L, 0x00800001L, 0x00002001L,
     210  0x00000000L, 0x00802000L, 0x00802000L, 0x00802081L,
     211  0x00000081L, 0x00000000L, 0x00800080L, 0x00800001L,
     212  0x00000001L, 0x00002000L, 0x00800000L, 0x00802001L,
     213  0x00000080L, 0x00800000L, 0x00002001L, 0x00002080L,
     214  0x00800081L, 0x00000001L, 0x00002080L, 0x00800080L,
     215  0x00002000L, 0x00802080L, 0x00802081L, 0x00000081L,
     216  0x00800080L, 0x00800001L, 0x00802000L, 0x00802081L,
     217  0x00000081L, 0x00000000L, 0x00000000L, 0x00802000L,
     218  0x00002080L, 0x00800080L, 0x00800081L, 0x00000001L,
     219  0x00802001L, 0x00002081L, 0x00002081L, 0x00000080L,
     220  0x00802081L, 0x00000081L, 0x00000001L, 0x00002000L,
     221  0x00800001L, 0x00002001L, 0x00802080L, 0x00800081L,
     222  0x00002001L, 0x00002080L, 0x00800000L, 0x00802001L,
     223  0x00000080L, 0x00800000L, 0x00002000L, 0x00802080L
     224  ]
     225
     226SP5 = [
     227  0x00000100L, 0x02080100L, 0x02080000L, 0x42000100L,
     228  0x00080000L, 0x00000100L, 0x40000000L, 0x02080000L,
     229  0x40080100L, 0x00080000L, 0x02000100L, 0x40080100L,
     230  0x42000100L, 0x42080000L, 0x00080100L, 0x40000000L,
     231  0x02000000L, 0x40080000L, 0x40080000L, 0x00000000L,
     232  0x40000100L, 0x42080100L, 0x42080100L, 0x02000100L,
     233  0x42080000L, 0x40000100L, 0x00000000L, 0x42000000L,
     234  0x02080100L, 0x02000000L, 0x42000000L, 0x00080100L,
     235  0x00080000L, 0x42000100L, 0x00000100L, 0x02000000L,
     236  0x40000000L, 0x02080000L, 0x42000100L, 0x40080100L,
     237  0x02000100L, 0x40000000L, 0x42080000L, 0x02080100L,
     238  0x40080100L, 0x00000100L, 0x02000000L, 0x42080000L,
     239  0x42080100L, 0x00080100L, 0x42000000L, 0x42080100L,
     240  0x02080000L, 0x00000000L, 0x40080000L, 0x42000000L,
     241  0x00080100L, 0x02000100L, 0x40000100L, 0x00080000L,
     242  0x00000000L, 0x40080000L, 0x02080100L, 0x40000100L
     243  ]
     244
     245SP6 = [
     246  0x20000010L, 0x20400000L, 0x00004000L, 0x20404010L,
     247  0x20400000L, 0x00000010L, 0x20404010L, 0x00400000L,
     248  0x20004000L, 0x00404010L, 0x00400000L, 0x20000010L,
     249  0x00400010L, 0x20004000L, 0x20000000L, 0x00004010L,
     250  0x00000000L, 0x00400010L, 0x20004010L, 0x00004000L,
     251  0x00404000L, 0x20004010L, 0x00000010L, 0x20400010L,
     252  0x20400010L, 0x00000000L, 0x00404010L, 0x20404000L,
     253  0x00004010L, 0x00404000L, 0x20404000L, 0x20000000L,
     254  0x20004000L, 0x00000010L, 0x20400010L, 0x00404000L,
     255  0x20404010L, 0x00400000L, 0x00004010L, 0x20000010L,
     256  0x00400000L, 0x20004000L, 0x20000000L, 0x00004010L,
     257  0x20000010L, 0x20404010L, 0x00404000L, 0x20400000L,
     258  0x00404010L, 0x20404000L, 0x00000000L, 0x20400010L,
     259  0x00000010L, 0x00004000L, 0x20400000L, 0x00404010L,
     260  0x00004000L, 0x00400010L, 0x20004010L, 0x00000000L,
     261  0x20404000L, 0x20000000L, 0x00400010L, 0x20004010L
     262  ]
     263
     264SP7 = [
     265  0x00200000L, 0x04200002L, 0x04000802L, 0x00000000L,
     266  0x00000800L, 0x04000802L, 0x00200802L, 0x04200800L,
     267  0x04200802L, 0x00200000L, 0x00000000L, 0x04000002L,
     268  0x00000002L, 0x04000000L, 0x04200002L, 0x00000802L,
     269  0x04000800L, 0x00200802L, 0x00200002L, 0x04000800L,
     270  0x04000002L, 0x04200000L, 0x04200800L, 0x00200002L,
     271  0x04200000L, 0x00000800L, 0x00000802L, 0x04200802L,
     272  0x00200800L, 0x00000002L, 0x04000000L, 0x00200800L,
     273  0x04000000L, 0x00200800L, 0x00200000L, 0x04000802L,
     274  0x04000802L, 0x04200002L, 0x04200002L, 0x00000002L,
     275  0x00200002L, 0x04000000L, 0x04000800L, 0x00200000L,
     276  0x04200800L, 0x00000802L, 0x00200802L, 0x04200800L,
     277  0x00000802L, 0x04000002L, 0x04200802L, 0x04200000L,
     278  0x00200800L, 0x00000000L, 0x00000002L, 0x04200802L,
     279  0x00000000L, 0x00200802L, 0x04200000L, 0x00000800L,
     280  0x04000002L, 0x04000800L, 0x00000800L, 0x00200002L
     281  ]
     282
     283SP8 = [
     284  0x10001040L, 0x00001000L, 0x00040000L, 0x10041040L,
     285  0x10000000L, 0x10001040L, 0x00000040L, 0x10000000L,
     286  0x00040040L, 0x10040000L, 0x10041040L, 0x00041000L,
     287  0x10041000L, 0x00041040L, 0x00001000L, 0x00000040L,
     288  0x10040000L, 0x10000040L, 0x10001000L, 0x00001040L,
     289  0x00041000L, 0x00040040L, 0x10040040L, 0x10041000L,
     290  0x00001040L, 0x00000000L, 0x00000000L, 0x10040040L,
     291  0x10000040L, 0x10001000L, 0x00041040L, 0x00040000L,
     292  0x00041040L, 0x00040000L, 0x10041000L, 0x00001000L,
     293  0x00000040L, 0x10040040L, 0x00001000L, 0x00041040L,
     294  0x10001000L, 0x00000040L, 0x10000040L, 0x10040000L,
     295  0x10040040L, 0x10000000L, 0x00040000L, 0x10001040L,
     296  0x00000000L, 0x10041040L, 0x00040040L, 0x10000040L,
     297  0x10040000L, 0x10001000L, 0x10001040L, 0x00000000L,
     298  0x10041040L, 0x00041000L, 0x00041000L, 0x00001040L,
     299  0x00001040L, 0x00040040L, 0x10000000L, 0x10041000L
     300  ]
     301
     302def desfunc(block, keys):
     303    (leftt, right) = unpack('>II', block)
     304
     305    work = ((leftt >> 4) ^ right) & 0x0f0f0f0fL
     306    right ^= work
     307    leftt ^= (work << 4)
     308    work = ((leftt >> 16) ^ right) & 0x0000ffffL
     309    right ^= work
     310    leftt ^= (work << 16)
     311    work = ((right >> 2) ^ leftt) & 0x33333333L
     312    leftt ^= work
     313    right ^= (work << 2)
     314    work = ((right >> 8) ^ leftt) & 0x00ff00ffL
     315    leftt ^= work
     316    right ^= (work << 8)
     317    right = ((right << 1) | ((right >> 31) & 1L)) & 0xffffffffL
     318    work = (leftt ^ right) & 0xaaaaaaaaL
     319    leftt ^= work
     320    right ^= work
     321    leftt = ((leftt << 1) | ((leftt >> 31) & 1L)) & 0xffffffffL
     322
     323    for i in range(0, 32, 4):
     324        work  = (right << 28) | (right >> 4)
     325        work ^= keys[i]
     326        fval  = SP7[ work            & 0x3fL]
     327        fval |= SP5[(work >>  8) & 0x3fL]
     328        fval |= SP3[(work >> 16) & 0x3fL]
     329        fval |= SP1[(work >> 24) & 0x3fL]
     330        work  = right ^ keys[i+1]
     331        fval |= SP8[ work            & 0x3fL]
     332        fval |= SP6[(work >>  8) & 0x3fL]
     333        fval |= SP4[(work >> 16) & 0x3fL]
     334        fval |= SP2[(work >> 24) & 0x3fL]
     335        leftt ^= fval
     336        work  = (leftt << 28) | (leftt >> 4)
     337        work ^= keys[i+2]
     338        fval  = SP7[ work            & 0x3fL]
     339        fval |= SP5[(work >>  8) & 0x3fL]
     340        fval |= SP3[(work >> 16) & 0x3fL]
     341        fval |= SP1[(work >> 24) & 0x3fL]
     342        work  = leftt ^ keys[i+3]
     343        fval |= SP8[ work            & 0x3fL]
     344        fval |= SP6[(work >>  8) & 0x3fL]
     345        fval |= SP4[(work >> 16) & 0x3fL]
     346        fval |= SP2[(work >> 24) & 0x3fL]
     347        right ^= fval
     348
     349    right = (right << 31) | (right >> 1)
     350    work = (leftt ^ right) & 0xaaaaaaaaL
     351    leftt ^= work
     352    right ^= work
     353    leftt = (leftt << 31) | (leftt >> 1)
     354    work = ((leftt >> 8) ^ right) & 0x00ff00ffL
     355    right ^= work
     356    leftt ^= (work << 8)
     357    work = ((leftt >> 2) ^ right) & 0x33333333L
     358    right ^= work
     359    leftt ^= (work << 2)
     360    work = ((right >> 16) ^ leftt) & 0x0000ffffL
     361    leftt ^= work
     362    right ^= (work << 16)
     363    work = ((right >> 4) ^ leftt) & 0x0f0f0f0fL
     364    leftt ^= work
     365    right ^= (work << 4)
     366
     367    leftt &= 0xffffffffL
     368    right &= 0xffffffffL
     369    return pack('>II', right, leftt)
     370
     371
     372# test
     373if __name__ == '__main__':
     374    key = '0123456789abcdef'.decode('hex')
     375    plain = '0123456789abcdef'.decode('hex')
     376    cipher = '6e09a37726dd560c'.decode('hex')
     377    ek = deskey(key, False)
     378    dk = deskey(key, True)
     379    assert desfunc(plain, ek) == cipher
     380    assert desfunc(desfunc(plain, ek), dk) == plain
     381    assert desfunc(desfunc(plain, dk), ek) == plain
     382    print 'test succeeded.'
  • xpra/net/protocol.py

     
    5959    packet_encoding_sanity_checks()
    6060
    6161
     62def exit_queue():
     63    queue = Queue()
     64    for _ in range(10):     #just 2 should be enough!
     65        queue.put(None)
     66    return queue
     67
     68def force_flush_queue(q):
     69    try:
     70        #discard all elements in the old queue and push the None marker:
     71        try:
     72            while q.qsize()>0:
     73                q.read(False)
     74        except:
     75            pass
     76        q.put_nowait(None)
     77    except:
     78        pass
     79
     80
    6281class Protocol(object):
     82    """
     83        This class handles sending and receiving packets,
     84        it will encode and compress them before sending,
     85        and decompress and decode when receiving.
     86    """
     87
    6388    CONNECTION_LOST = "connection-lost"
    6489    GIBBERISH = "gibberish"
    6590    INVALID = "invalid"
     
    7297        assert conn is not None
    7398        self.timeout_add = scheduler.timeout_add
    7499        self.idle_add = scheduler.idle_add
     100        self.source_remove = scheduler.source_remove
    75101        self._conn = conn
    76102        if FAKE_JITTER>0:
    77103            from xpra.net.fake_jitter import FakeJitter
     
    81107            self._process_packet_cb = process_packet_cb
    82108        self._write_queue = Queue(1)
    83109        self._read_queue = Queue(20)
     110        self._process_read = self.read_queue_put
    84111        self._read_queue_put = self.read_queue_put
    85112        # Invariant: if .source is None, then _source_has_more == False
    86113        self._get_packet_cb = get_packet_cb
     
    139166        self.enable_encoder(self.encoder)
    140167
    141168    def wait_for_io_threads_exit(self, timeout=None):
    142         for t in (self._read_thread, self._write_thread):
    143             if t and t.isAlive():
     169        io_threads = [x for x in (self._read_thread, self._write_thread) if x is not None]
     170        for t in io_threads:
     171            if t.isAlive():
    144172                t.join(timeout)
    145173        exited = True
    146174        cinfo = self._conn or "cleared connection"
    147         for t in (self._read_thread, self._write_thread):
    148             if t and t.isAlive():
     175        for t in io_threads:
     176            if t.isAlive():
    149177                log.warn("Warning: %s thread of %s is still alive (timeout=%s)", t.name, cinfo, timeout)
    150178                exited = False
    151179        return exited
     
    239267        if SEND_INVALID_PACKET:
    240268            self.timeout_add(SEND_INVALID_PACKET*1000, self.raw_write, SEND_INVALID_PACKET_DATA)
    241269
     270
     271    def send_disconnect(self, reasons, done_callback=None):
     272        self.flush_then_close(["disconnect"]+list(reasons), done_callback=done_callback)
     273
    242274    def send_now(self, packet):
    243275        if self._closed:
    244276            log("send_now(%s ...) connection is closed already, not sending", packet[0])
     
    279311                return
    280312            self._internal_error("error in network packet write/format", e, exc_info=True)
    281313
    282     def _add_packet_to_queue(self, packet, start_send_cb=None, end_send_cb=None, has_more=False):
     314    def _add_packet_to_queue(self, packet, start_send_cb=None, end_send_cb=None, fail_cb=None, synchronous=True, has_more=False):
    283315        if has_more:
    284316            self._source_has_more.set()
    285317        if packet is None:
     
    290322            if self._closed:
    291323                return
    292324            try:
    293                 self._add_chunks_to_queue(chunks, start_send_cb, end_send_cb)
     325                self._add_chunks_to_queue(chunks, start_send_cb, end_send_cb, fail_cb, synchronous)
    294326            except:
    295327                log.error("Error: failed to queue '%s' packet", packet[0])
    296                 log("add_chunks_to_queue%s", (chunks, start_send_cb, end_send_cb), exc_info=True)
     328                log("add_chunks_to_queue%s", (chunks, start_send_cb, end_send_cb, fail_cb), exc_info=True)
    297329                raise
    298330
    299     def _add_chunks_to_queue(self, chunks, start_send_cb=None, end_send_cb=None):
     331    def _add_chunks_to_queue(self, chunks, start_send_cb=None, end_send_cb=None, fail_cb=None, synchronous=True):
    300332        """ the write_lock must be held when calling this function """
    301333        counter = 0
    302334        items = []
    303335        for proto_flags,index,level,data in chunks:
    304             scb, ecb = None, None
    305336            #fire the start_send_callback just before the first packet is processed:
    306             if counter==0:
    307                 scb = start_send_cb
    308337            #fire the end_send callback when the last packet (index==0) makes it out:
    309             if index==0:
    310                 ecb = end_send_cb
    311338            payload_size = len(data)
    312339            actual_size = payload_size
    313340            if self.cipher_out:
     
    328355                assert not self.cipher_out
    329356                #for plain/text packets (ie: gibberish response)
    330357                log("sending %s bytes without header", payload_size)
    331                 items.append((data, scb, ecb))
     358                items.append(data)
    332359            elif actual_size<PACKET_JOIN_SIZE:
    333                 if type(data) not in JOIN_TYPES:
     360                if not isinstance(data, JOIN_TYPES):
    334361                    data = memoryview_to_bytes(data)
    335362                header_and_data = pack_header(proto_flags, level, index, payload_size) + data
    336                 items.append((header_and_data, scb, ecb))
     363                items.append(header_and_data)
    337364            else:
    338365                header = pack_header(proto_flags, level, index, payload_size)
    339                 items.append((header, scb, None))
    340                 items.append((data, None, ecb))
     366                items.append(header)
     367                items.append(data)
    341368            counter += 1
    342369        if self._write_thread is None:
    343370            self.start_write_thread()
    344         self._write_queue.put(items)
    345         self.output_packetcount += 1
     371        self._write_queue.put((items, start_send_cb, end_send_cb, fail_cb, synchronous))
    346372
     373
    347374    def start_write_thread(self):
    348375        self._write_thread = start_thread(self._write_thread_loop, "write", daemon=True)
    349376
    350     def raw_write(self, contents, start_cb=None, end_cb=None):
     377    def raw_write(self, contents, start_cb=None, end_cb=None, fail_cb=None, synchronous=True):
    351378        """ Warning: this bypasses the compression and packet encoder! """
    352379        if self._write_thread is None:
    353380            self.start_write_thread()
    354         self._write_queue.put(((contents, start_cb, end_cb), ))
     381        self._write_queue.put((contents, start_cb, end_cb, fail_cb, synchronous))
    355382
    356383    def verify_packet(self, packet):
    357384        """ look for None values which may have caused the packet to fail encoding """
     
    539566        assert level>=0 and level<=10, "invalid compression level: %s (must be between 0 and 10" % level
    540567        self.compression_level = level
    541568
     569
    542570    def _io_thread_loop(self, name, callback):
    543571        try:
    544572            log("io_thread_loop(%s, %s) loop starting", name, callback)
     
    559587                log.error("Error: %s on %s failed: %s", name, self._conn, type(e), exc_info=True)
    560588                self.close()
    561589
     590
    562591    def _write_thread_loop(self):
    563592        self._io_thread_loop("write", self._write)
    564593    def _write(self):
     
    568597            log("write thread: empty marker, exiting")
    569598            self.close()
    570599            return False
    571         for buf, start_cb, end_cb in items:
    572             con = self._conn
    573             if not con:
    574                 return False
    575             if start_cb:
    576                 try:
    577                     start_cb(con.output_bytecount)
    578                 except:
    579                     if not self._closed:
    580                         log.error("Error on write start callback %s", start_cb, exc_info=True)
     600        return self.write_items(*items)
     601   
     602    def write_items(self, buf_data, start_cb=None, end_cb=None, fail_cb=None, synchronous=True):
     603        con = self._conn
     604        if not con:
     605            return False
     606        if start_cb:
     607            try:
     608                start_cb(con.output_bytecount)
     609            except:
     610                if not self._closed:
     611                    log.error("Error on write start callback %s", start_cb, exc_info=True)
     612        self.write_buffers(buf_data, fail_cb, synchronous)
     613        if end_cb:
     614            try:
     615                end_cb(self._conn.output_bytecount)
     616            except:
     617                if not self._closed:
     618                    log.error("Error on write end callback %s", end_cb, exc_info=True)
     619        return True
     620
     621    def write_buffers(self, buf_data, _fail_cb, _synchronous):
     622        con = self._conn
     623        if not con:
     624            return 0
     625        for buf in buf_data:
    581626            while buf and not self._closed:
    582627                written = con.write(buf)
    583628                #example test code, for sending small chunks very slowly:
     
    587632                if written:
    588633                    buf = buf[written:]
    589634                    self.output_raw_packetcount += 1
    590             if end_cb:
    591                 try:
    592                     end_cb(self._conn.output_bytecount)
    593                 except:
    594                     if not self._closed:
    595                         log.error("Error on write end callback %s", end_cb, exc_info=True)
    596         return True
     635        self.output_packetcount += 1
    597636
     637
    598638    def _read_thread_loop(self):
    599639        self._io_thread_loop("read", self._read)
    600640    def _read(self):
     
    601641        buf = self._conn.read(READ_BUFFER_SIZE)
    602642        #log("read thread: got data of size %s: %s", len(buf), repr_ellipsized(buf))
    603643        #add to the read queue (or whatever takes its place - see steal_connection)
    604         self._read_queue_put(buf)
     644        self._process_read(buf)
    605645        if not buf:
    606646            log("read thread: eof")
    607647            #give time to the parse thread to call close itself
     
    646686    def _invalid_header(self, data, msg=""):
    647687        self.invalid_header(self, data, msg)
    648688
    649     def invalid_header(self, proto, data, msg="invalid packet header"):
     689    def invalid_header(self, _proto, data, msg="invalid packet header"):
    650690        err = "%s: '%s'" % (msg, binascii.hexlify(data[:8]))
    651691        if len(data)>1:
    652692            err += " read buffer=%s (%i bytes)" % (repr_ellipsized(data), len(data))
     
    653693        self.gibberish(err, data)
    654694
    655695
     696    def process_read(self, data):
     697        self._read_queue_put(data)
     698
    656699    def read_queue_put(self, data):
    657700        #start the parse thread if needed:
    658701        if not self._read_parser_thread and not self._closed:
     
    660703                log("empty marker in read queue, exiting")
    661704                self.idle_add(self.close)
    662705                return
    663             self._read_parser_thread = make_thread(self._read_parse_thread_loop, "parse", daemon=True)
    664             self._read_parser_thread.start()
     706            self.start_read_parser_thread()
    665707        self._read_queue.put(data)
    666708        #from now on, take shortcut:
    667709        if self._read_queue_put==self.read_queue_put:
    668710            self._read_queue_put = self._read_queue.put
    669711
     712    def start_read_parser_thread(self):
     713        self._read_parser_thread = start_thread(self._read_parse_thread_loop, "parse", daemon=True)
     714
    670715    def _read_parse_thread_loop(self):
    671716        log("read_parse_thread_loop starting")
    672717        try:
     
    911956                        close_and_release()
    912957                        return False
    913958                    return not self._closed     #run until we manage to close (here or via the timeout)
    914                 def packet_queued(*args):
     959                def packet_queued(*_args):
    915960                    #if we're here, we have the lock and the packet is in the write queue
    916961                    log("flush_then_close: packet_queued() closed=%s", self._closed)
    917962                    if wait_for_packet_sent():
    918963                        #check again every 100ms
    919964                        self.timeout_add(100, wait_for_packet_sent)
    920                 self._add_chunks_to_queue(chunks, start_send_cb=None, end_send_cb=packet_queued)
     965                self._add_chunks_to_queue(chunks, start_send_cb=None, end_send_cb=packet_queued, synchronous=False)
    921966                #just in case wait_for_packet_sent never fires:
    922967                self.timeout_add(5*1000, close_and_release)
    923968
     
    952997        self.idle_add(self._process_packet_cb, self, [Protocol.CONNECTION_LOST])
    953998        c = self._conn
    954999        if c:
     1000            self._conn = None
    9551001            try:
    9561002                log("Protocol.close() calling %s", c.close)
    9571003                c.close()
    958                 if self._log_stats is None and self._conn.input_bytecount==0 and self._conn.output_bytecount==0:
     1004                if self._log_stats is None and c.input_bytecount==0 and c.output_bytecount==0:
    9591005                    #no data sent or received, skip logging of stats:
    9601006                    self._log_stats = False
    9611007                if self._log_stats:
    9621008                    from xpra.simple_stats import std_unit, std_unit_dec
    9631009                    log.info("connection closed after %s packets received (%s bytes) and %s packets sent (%s bytes)",
    964                          std_unit(self.input_packetcount), std_unit_dec(self._conn.input_bytecount),
    965                          std_unit(self.output_packetcount), std_unit_dec(self._conn.output_bytecount)
     1010                         std_unit(self.input_packetcount), std_unit_dec(c.input_bytecount),
     1011                         std_unit(self.output_packetcount), std_unit_dec(c.output_bytecount)
    9661012                         )
    9671013            except:
    968                 log.error("error closing %s", self._conn, exc_info=True)
    969             self._conn = None
     1014                log.error("error closing %s", c, exc_info=True)
    9701015        self.terminate_queue_threads()
    9711016        self.idle_add(self.clean)
    9721017        log("Protocol.close() done")
     
    10071052        self._get_packet_cb = None
    10081053        self._source_has_more.set()
    10091054        #make all the queue based threads exit by adding the empty marker:
    1010         exit_queue = Queue()
    1011         for _ in range(10):     #just 2 should be enough!
    1012             exit_queue.put(None)
    1013         try:
    1014             owq = self._write_queue
    1015             self._write_queue = exit_queue
    1016             #discard all elements in the old queue and push the None marker:
    1017             try:
    1018                 while owq.qsize()>0:
    1019                     owq.read(False)
    1020             except:
    1021                 pass
    1022             owq.put_nowait(None)
    1023         except:
    1024             pass
    1025         try:
    1026             orq = self._read_queue
    1027             self._read_queue = exit_queue
    1028             #discard all elements in the old queue and push the None marker:
    1029             try:
    1030                 while orq.qsize()>0:
    1031                     orq.read(False)
    1032             except:
    1033                 pass
    1034             orq.put_nowait(None)
    1035         except:
    1036             pass
     1055        #write queue:
     1056        owq = self._write_queue
     1057        self._write_queue = exit_queue()
     1058        force_flush_queue(owq)
     1059        #read queue:
     1060        orq = self._read_queue
     1061        self._read_queue = exit_queue()
     1062        force_flush_queue(orq)
    10371063        #just in case the read thread is waiting again:
    10381064        self._source_has_more.set()
  • xpra/net/rfb.py

     
    1 # This file is part of Xpra.
    2 # Copyright (C) 2017 Antoine Martin <antoine@devloop.org.uk>
    3 # Xpra is released under the terms of the GNU GPL v2, or, at your option, any
    4 # later version. See the file COPYING for details.
    5 
    6 import struct
    7 from socket import error as socket_error
    8 import binascii
    9 from threading import Lock
    10 
    11 
    12 from xpra.log import Logger
    13 log = Logger("network", "protocol", "rfb")
    14 
    15 from xpra.os_util import Queue
    16 from xpra.util import repr_ellipsized, envint
    17 from xpra.make_thread import make_thread, start_thread
    18 from xpra.net.common import ConnectionClosedException          #@UndefinedVariable (pydev false positive)
    19 from xpra.net.bytestreams import ABORT
    20 
    21 READ_BUFFER_SIZE = envint("XPRA_READ_BUFFER_SIZE", 65536)
    22 #merge header and packet if packet is smaller than:
    23 PIXEL_FORMAT = "BBBBHHHBBBBBB"
    24 
    25 RFB_SETPIXELFORMAT = 0
    26 RFB_SETENCODINGS = 2
    27 RFB_FRAMEBUFFERUPDATEREQUEST = 3
    28 RFB_KEYEVENT = 4
    29 RFB_POINTEREVENT = 5
    30 RFB_CLIENTCUTTEXT = 6
    31 PACKET_TYPE = {
    32     RFB_SETPIXELFORMAT              : "SetPixelFormat",
    33     RFB_SETENCODINGS                : "SetEncodings",
    34     RFB_FRAMEBUFFERUPDATEREQUEST    : "FramebufferUpdateRequest",
    35     RFB_KEYEVENT                    : "KeyEvent",
    36     RFB_POINTEREVENT                : "PointerEvent",
    37     RFB_CLIENTCUTTEXT               : "ClientCutText",
    38     }
    39 PACKET_FMT = {
    40     RFB_SETPIXELFORMAT              : "!BBBB"+PIXEL_FORMAT,
    41     RFB_SETENCODINGS                : "!BBH",
    42     RFB_FRAMEBUFFERUPDATEREQUEST    : "!BBHHHH",
    43     RFB_KEYEVENT                    : "!BBBBi",
    44     RFB_POINTEREVENT                : "!BBHH",
    45     RFB_CLIENTCUTTEXT               : "!BBBBi",
    46     }
    47 PACKET_STRUCT = {}
    48 for ptype, fmt in PACKET_FMT.items():
    49     PACKET_STRUCT[ptype] = struct.Struct(fmt)
    50 
    51 
    52 class RFBProtocol(object):
    53     CONNECTION_LOST = "connection-lost"
    54     INVALID = "invalid"
    55 
    56     def __init__(self, scheduler, conn, process_packet_cb, get_rfb_pixelformat, session_name="Xpra"):
    57         """
    58             You must call this constructor and source_has_more() from the main thread.
    59         """
    60         assert scheduler is not None
    61         assert conn is not None
    62         self.timeout_add = scheduler.timeout_add
    63         self.idle_add = scheduler.idle_add
    64         self._conn = conn
    65         self._process_packet_cb = process_packet_cb
    66         self._get_rfb_pixelformat = get_rfb_pixelformat
    67         self.session_name = session_name
    68         self._write_queue = Queue(1)
    69         self._buffer = b""
    70         #counters:
    71         self.input_packetcount = 0
    72         self.input_raw_packetcount = 0
    73         self.output_packetcount = 0
    74         self.output_raw_packetcount = 0
    75         self._protocol_version = ()
    76         self._closed = False
    77         self._packet_parser = self._parse_protocol_handshake
    78         self._write_lock = Lock()
    79         self._write_thread = None
    80         self._read_thread = make_thread(self._read_thread_loop, "read", daemon=True)
    81 
    82 
    83     def send_protocol_handshake(self):
    84         self.raw_write(b"RFB 003.008\n")
    85 
    86     def _parse_invalid(self, packet):
    87         return len(packet)
    88 
    89     def _parse_protocol_handshake(self, packet):
    90         if len(packet)<12:
    91             return 0
    92         if not packet.startswith(b'RFB '):
    93             self._invalid_header(packet, "invalid RFB protocol handshake packet header")
    94             return 0
    95         #ie: packet==b'RFB 003.008\n'
    96         self._protocol_version = tuple(int(x) for x in packet[4:11].split("."))
    97         log.info("RFB version %s", b".".join(str(x) for x in self._protocol_version))
    98         #reply with Security Handshake:
    99         self._packet_parser = self._parse_security_handshake
    100         self.send(struct.pack("BB", 1, 1))
    101         return 12
    102 
    103     def _parse_security_handshake(self, packet):
    104         if packet!=b"\1":
    105             self._invalid_header(packet, "invalid security handshake response")
    106             return 0
    107         #Security Handshake, send SecurityResult Handshake
    108         self._packet_parser = self._parse_security_result
    109         self.send(struct.pack("BBBB", 0, 0, 0, 0))
    110         return 1
    111 
    112     def _parse_security_result(self, packet):
    113         if packet!=b"\0":
    114             self._invalid_header(packet, "invalid security result")
    115             return 0
    116         #send ClientInit
    117         self._packet_parser = self._parse_rfb
    118         w, h, bpp, depth, bigendian, truecolor, rmax, gmax, bmax, rshift, bshift, gshift = self._get_rfb_pixelformat()
    119         packet =  struct.pack("!HH"+PIXEL_FORMAT+"I", w, h, bpp, depth, bigendian, truecolor, rmax, gmax, bmax, rshift, bshift, gshift, 0, 0, 0, len(self.session_name))+self.session_name
    120         self.send(packet)
    121         self._process_packet_cb(self, [b"authenticated"])
    122         return 1
    123 
    124     def _parse_rfb(self, packet):
    125         try:
    126             ptype = ord(packet[0])
    127         except:
    128             ptype = packet[0]
    129         packet_type = PACKET_TYPE.get(ptype)
    130         if not packet_type:
    131             self.invalid("unknown RFB packet type: %#x" % ptype, packet)
    132             return 0
    133         s = PACKET_STRUCT[ptype]        #ie: Struct("!BBBB")
    134         if len(packet)<s.size:
    135             return 0
    136         size = s.size
    137         values = list(s.unpack(packet[:size]))
    138         values[0] = packet_type
    139         #some packets require parsing extra data:
    140         if ptype==RFB_SETENCODINGS:
    141             N = values[2]
    142             estruct = struct.Struct("!"+"i"*N)
    143             size += estruct.size
    144             if len(packet)<size:
    145                 return 0
    146             encodings = estruct.unpack(packet[s.size:size])
    147             values.append(encodings)
    148         elif ptype==RFB_CLIENTCUTTEXT:
    149             l = values[4]
    150             size += l
    151             if len(packet)<size:
    152                 return 0
    153             text = packet[s.size:size]
    154             values.append(text)
    155         self.input_packetcount += 1
    156         #log("RFB packet: %s", values)
    157         #now trigger the callback:
    158         self._process_packet_cb(self, values)
    159         #return part of packet not consumed:
    160         return size
    161 
    162 
    163     def wait_for_io_threads_exit(self, timeout=None):
    164         for t in (self._read_thread, self._write_thread):
    165             if t and t.isAlive():
    166                 t.join(timeout)
    167         exited = True
    168         cinfo = self._conn or "cleared connection"
    169         for t in (self._read_thread, self._write_thread):
    170             if t and t.isAlive():
    171                 log.warn("Warning: %s thread of %s is still alive (timeout=%s)", t.name, cinfo, timeout)
    172                 exited = False
    173         return exited
    174 
    175     def __repr__(self):
    176         return "RFBProtocol(%s)" % self._conn
    177 
    178     def get_threads(self):
    179         return  [x for x in [self._write_thread, self._read_thread] if x is not None]
    180 
    181 
    182     def get_info(self, *_args):
    183         info = {"protocol" : self._protocol_version}
    184         for t in (self._write_thread, self._read_thread):
    185             if t:
    186                 info.setdefault("thread", {})[t.name] = t.is_alive()
    187         return info
    188 
    189 
    190     def start(self):
    191         def start_network_read_thread():
    192             if not self._closed:
    193                 self._read_thread.start()
    194         self.idle_add(start_network_read_thread)
    195 
    196     def send(self, packet):
    197         if self._closed:
    198             log("send(%s ...) connection is closed already, not sending", packet[0])
    199             return
    200         log("send(%s ...)", packet[0])
    201         with self._write_lock:
    202             if self._closed:
    203                 return
    204             self.raw_write(packet)
    205 
    206     def start_write_thread(self):
    207         self._write_thread = start_thread(self._write_thread_loop, "write", daemon=True)
    208 
    209     def raw_write(self, contents):
    210         """ Warning: this bypasses the compression and packet encoder! """
    211         if self._write_thread is None:
    212             self.start_write_thread()
    213         self._write_queue.put(contents)
    214 
    215     def _io_thread_loop(self, name, callback):
    216         try:
    217             log("io_thread_loop(%s, %s) loop starting", name, callback)
    218             while not self._closed and callback():
    219                 pass
    220             log("io_thread_loop(%s, %s) loop ended, closed=%s", name, callback, self._closed)
    221         except ConnectionClosedException as e:
    222             log("%s closed", self._conn, exc_info=True)
    223             if not self._closed:
    224                 #ConnectionClosedException means the warning has been logged already
    225                 self._connection_lost("%s connection %s closed" % (name, self._conn))
    226         except (OSError, IOError, socket_error) as e:
    227             if not self._closed:
    228                 self._internal_error("%s connection %s reset" % (name, self._conn), e, exc_info=e.args[0] not in ABORT)
    229         except Exception as e:
    230             #can happen during close(), in which case we just ignore:
    231             if not self._closed:
    232                 log.error("Error: %s on %s failed: %s", name, self._conn, type(e), exc_info=True)
    233                 self.close()
    234 
    235     def _write_thread_loop(self):
    236         self._io_thread_loop("write", self._write)
    237     def _write(self):
    238         buf = self._write_queue.get()
    239         # Used to signal that we should exit:
    240         if buf is None:
    241             log("write thread: empty marker, exiting")
    242             self.close()
    243             return False
    244         con = self._conn
    245         if not con:
    246             return False
    247         while buf and not self._closed:
    248             written = con.write(buf)
    249             if written:
    250                 buf = buf[written:]
    251                 self.output_raw_packetcount += 1
    252         self.output_packetcount += 1
    253         return True
    254 
    255     def _read_thread_loop(self):
    256         self._io_thread_loop("read", self._read)
    257     def _read(self):
    258         buf = self._conn.read(READ_BUFFER_SIZE)
    259         #log("read()=%s", repr_ellipsized(buf))
    260         if not buf:
    261             log("read thread: eof")
    262             #give time to the parse thread to call close itself
    263             #so it has time to parse and process the last packet received
    264             self.timeout_add(1000, self.close)
    265             return False
    266         self.input_raw_packetcount += 1
    267         self._buffer += buf
    268         #log("calling %s(%s)", self._packet_parser, repr_ellipsized(self._buffer))
    269         while self._buffer:
    270             consumed = self._packet_parser(self._buffer)
    271             if consumed==0:
    272                 break
    273             self._buffer = self._buffer[consumed:]
    274         return True
    275 
    276     def _internal_error(self, message="", exc=None, exc_info=False):
    277         #log exception info with last log message
    278         if self._closed:
    279             return
    280         ei = exc_info
    281         if exc:
    282             ei = None   #log it separately below
    283         log.error("Error: %s", message, exc_info=ei)
    284         if exc:
    285             log.error(" %s", exc, exc_info=exc_info)
    286         self.idle_add(self._connection_lost, message)
    287 
    288     def _connection_lost(self, message="", exc_info=False):
    289         log("connection lost: %s", message, exc_info=exc_info)
    290         self.close()
    291         return False
    292 
    293 
    294     def invalid(self, msg, data):
    295         self._packet_parser = self._parse_invalid
    296         self.idle_add(self._process_packet_cb, self, [RFBProtocol.INVALID, msg, data])
    297         # Then hang up:
    298         self.timeout_add(1000, self._connection_lost, msg)
    299 
    300 
    301     #delegates to invalid_header()
    302     #(so this can more easily be intercepted and overriden
    303     # see tcp-proxy)
    304     def _invalid_header(self, data, msg=""):
    305         self.invalid_header(self, data, msg)
    306 
    307     def invalid_header(self, _proto, data, msg="invalid packet header"):
    308         self._packet_parser = self._parse_invalid
    309         err = "%s: '%s'" % (msg, binascii.hexlify(data[:8]))
    310         if len(data)>1:
    311             err += " read buffer=%s (%i bytes)" % (repr_ellipsized(data), len(data))
    312         self.invalid(err, data)
    313 
    314 
    315     def flush_then_close(self, _last_packet, done_callback=None):
    316         """ Note: this is best effort only
    317             the packet may not get sent.
    318 
    319             We try to get the write lock,
    320             we try to wait for the write queue to flush
    321             we queue our last packet,
    322             we wait again for the queue to flush,
    323             then no matter what, we close the connection and stop the threads.
    324         """
    325         log("flush_then_close(%s) closed=%s", done_callback, self._closed)
    326         def done():
    327             log("flush_then_close: done, callback=%s", done_callback)
    328             if done_callback:
    329                 done_callback()
    330         if self._closed:
    331             log("flush_then_close: already closed")
    332             return done()
    333         def wait_for_queue(timeout=10):
    334             #IMPORTANT: if we are here, we have the write lock held!
    335             if not self._write_queue.empty():
    336                 #write queue still has stuff in it..
    337                 if timeout<=0:
    338                     log("flush_then_close: queue still busy, closing without sending the last packet")
    339                     self._write_lock.release()
    340                     self.close()
    341                     done()
    342                 else:
    343                     log("flush_then_close: still waiting for queue to flush")
    344                     self.timeout_add(100, wait_for_queue, timeout-1)
    345             else:
    346                 log("flush_then_close: queue is now empty, sending the last packet and closing")
    347                 def close_and_release():
    348                     log("flush_then_close: wait_for_packet_sent() close_and_release()")
    349                     self.close()
    350                     try:
    351                         self._write_lock.release()
    352                     except:
    353                         pass
    354                     done()
    355                 def wait_for_packet_sent():
    356                     log("flush_then_close: wait_for_packet_sent() queue.empty()=%s, closed=%s", self._write_queue.empty(), self._closed)
    357                     if self._write_queue.empty() or self._closed:
    358                         #it got sent, we're done!
    359                         close_and_release()
    360                         return False
    361                     return not self._closed     #run until we manage to close (here or via the timeout)
    362                 self.timeout_add(100, wait_for_packet_sent)
    363                 #just in case wait_for_packet_sent never fires:
    364                 self.timeout_add(5*1000, close_and_release)
    365 
    366         def wait_for_write_lock(timeout=100):
    367             if not self._write_lock.acquire(False):
    368                 if timeout<=0:
    369                     log("flush_then_close: timeout waiting for the write lock")
    370                     self.close()
    371                     done()
    372                 else:
    373                     log("flush_then_close: write lock is busy, will retry %s more times", timeout)
    374                     self.timeout_add(10, wait_for_write_lock, timeout-1)
    375             else:
    376                 log("flush_then_close: acquired the write lock")
    377                 #we have the write lock - we MUST free it!
    378                 wait_for_queue()
    379         #normal codepath:
    380         # -> wait_for_write_lock
    381         # -> wait_for_queue
    382         # -> wait_for_packet_sent
    383         # -> close_and_release
    384         log("flush_then_close: wait_for_write_lock()")
    385         wait_for_write_lock()
    386 
    387 
    388     def close(self):
    389         log("Protocol.close() closed=%s, connection=%s", self._closed, self._conn)
    390         if self._closed:
    391             return
    392         self._closed = True
    393         #self.idle_add(self._process_packet_cb, self, [Protocol.CONNECTION_LOST])
    394         c = self._conn
    395         if c:
    396             try:
    397                 log("Protocol.close() calling %s", c.close)
    398                 c.close()
    399             except:
    400                 log.error("error closing %s", self._conn, exc_info=True)
    401             self._conn = None
    402         self.terminate_queue_threads()
    403         self.idle_add(self.clean)
    404         log("Protocol.close() done")
    405 
    406     def clean(self):
    407         #clear all references to ensure we can get garbage collected quickly:
    408         self._write_thread = None
    409         self._read_thread = None
    410         self._process_packet_cb = None
    411 
    412     def terminate_queue_threads(self):
    413         log("terminate_queue_threads()")
    414         #make all the queue based threads exit by adding the empty marker:
    415         exit_queue = Queue()
    416         for _ in range(10):     #just 2 should be enough!
    417             exit_queue.put(None)
    418         try:
    419             owq = self._write_queue
    420             self._write_queue = exit_queue
    421             #discard all elements in the old queue and push the None marker:
    422             try:
    423                 while owq.qsize()>0:
    424                     owq.read(False)
    425             except:
    426                 pass
    427             owq.put_nowait(None)
    428         except:
    429             pass
    430         try:
    431             orq = self._read_queue
    432             self._read_queue = exit_queue
    433             #discard all elements in the old queue and push the None marker:
    434             try:
    435                 while orq.qsize()>0:
    436                     orq.read(False)
    437             except:
    438                 pass
    439             orq.put_nowait(None)
    440         except:
    441             pass
  • xpra/net/udp_protocol.py

     
     1# This file is part of Xpra.
     2# Copyright (C) 2017 Antoine Martin <antoine@devloop.org.uk>
     3# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
     4# later version. See the file COPYING for details.
     5
     6import socket
     7import struct
     8import random
     9try:
     10    import errno
     11    EMSGSIZE = errno.EMSGSIZE
     12except ImportError as e:
     13    EMSGSIZE = None
     14
     15from xpra.log import Logger
     16log = Logger("network", "protocol", "udp")
     17
     18from xpra.os_util import LINUX, monotonic_time
     19from xpra.util import envint, repr_ellipsized
     20from xpra.make_thread import start_thread
     21from xpra.net.protocol import Protocol
     22from xpra.net.bytestreams import SocketConnection
     23
     24READ_BUFFER_SIZE = envint("XPRA_READ_BUFFER_SIZE", 65536)
     25DROP_PCT = envint("XPRA_UDP_DROP_PCT", 0)
     26
     27
     28#UUID, seqno, synchronous, chunk, chunks
     29_header_struct = struct.Struct('!QQHHH')
     30_header_size = _header_struct.size
     31
     32
     33class IncompletePacket(object):
     34    def __init__(self, seqno, start_time, chunks=None):
     35        self.seqno = seqno
     36        self.start_time = start_time
     37        self.last_time = start_time
     38        #todo: use numpy array of bytes
     39        self.chunks = chunks
     40    def __repr__(self):
     41        return ("IncompletePacket(%i: %s chunks)" % (self.seqno, len(self.chunks or [])))
     42
     43
     44class UDPListener(object):
     45    """
     46        This class is used by servers to receive UDP packets,
     47        it parses the header and then exposes the data received via process_packet_cb.
     48    """
     49
     50    def __init__(self, sock, process_packet_cb):
     51        assert sock is not None
     52        self._closed = False
     53        self._socket = sock
     54        self._process_packet_cb =  process_packet_cb
     55        self._read_thread = start_thread(self._read_thread_loop, "read", daemon=True)
     56
     57    def __repr__(self):
     58        return "UDPListener(%s)" % self._socket
     59
     60    def _read_thread_loop(self):
     61        log.info("udp read thread loop starting")
     62        try:
     63            while not self._closed:
     64                buf, bfrom = self._socket.recvfrom(READ_BUFFER_SIZE)
     65                if not buf:
     66                    log("read thread: eof")
     67                    break
     68                values = list(_header_struct.unpack_from(buf[:_header_size])) + [buf[_header_size:], bfrom]
     69                try:
     70                    self._process_packet_cb(self, *values)
     71                except Exception as e:
     72                    log("_read_thread_loop() buffer=%s, from=%s", repr_ellipsized(buf), bfrom, exc_info=True)
     73                    if not self._closed:
     74                        log.error("Error: UDP packet processing error:")
     75                        log.error(" %s", e)
     76        except Exception as e:
     77            #can happen during close(), in which case we just ignore:
     78            if not self._closed:
     79                log.error("Error: read on %s failed: %s", self._socket, type(e), exc_info=True)
     80        log("udp read thread loop ended")
     81        self.close()
     82
     83    def close(self):
     84        s = self._socket
     85        log("UDPListener.close() closed=%s, socket=%s", self._closed, s)
     86        if self._closed:
     87            return
     88        self._closed = True
     89        if s:
     90            try:
     91                log("Protocol.close() calling %s", s.close)
     92                s.close()
     93            except:
     94                log.error("error closing %s", s, exc_info=True)
     95            self._socket = None
     96        log("UDPListener.close() done")
     97
     98
     99class UDPProtocol(Protocol):
     100    """
     101        This class extends the Protocol class with UDP encapsulation.
     102        A single packet may end up being fragmented into multiple UDP frames
     103        to fit in the MTU.
     104        We keep track of the function which can be used to handle send failures
     105        (or the packet data if no function is supplied).
     106        "udp-control" packets are used to synchronize both ends.
     107    """
     108
     109    def __init__(self, *args):
     110        Protocol.__init__(self, *args)
     111        self.mtu = 0
     112        self.last_sequence = -1     #the most recent packet sequence we processed in full
     113        self.highest_sequence = -1
     114        self.jitter = 20            #20ms
     115        self.uuid = 0
     116        self.fail_cb = {}
     117        self.resend_cache = {}
     118        self.incomplete_packets = {}
     119        self.can_skip = set()       #processed already, or cancelled
     120        self.cancel = set()         #tell the other end to forget those
     121        self.control_timer = None
     122        self.control_timer_due = 0
     123        self._process_read = self.process_read
     124
     125    def close(self):
     126        Protocol.close(self)
     127        self.cancel_control_timer()
     128
     129
     130    def schedule_control(self, delay=1000):
     131        due = monotonic_time()+delay/1000.0
     132        if self.control_timer_due and self.control_timer_due<=due:
     133            #due already
     134            return
     135        ct = self.control_timer
     136        if ct:
     137            self.source_remove(ct)
     138        self.control_timer = self.timeout_add(delay, self.send_control)
     139        self.control_timer_due = due
     140
     141    def cancel_control_timer(self):
     142        ct = self.control_timer
     143        if ct:
     144            self.control_timer = None
     145            self.source_remove(ct)
     146
     147    def send_control(self):
     148        self.control_timer = None
     149        self.control_timer_due = 0
     150        if self._closed:
     151            return False
     152        missing = self._get_missing()
     153        packet = ("udp-control", self.mtu, self.last_sequence, self.highest_sequence, missing, tuple(self.cancel))
     154        log("send_control() packet(%s)=%s", self.incomplete_packets, packet)
     155        self.send_async(packet)
     156        self.cancel = set()
     157        self.schedule_control()
     158        return False
     159
     160    def _get_missing(self):
     161        """ the packets and chunks we are missing """
     162        if not self.incomplete_packets:
     163            return {}
     164        now = monotonic_time()
     165        max_start_time = now-self.jitter/1000.0
     166        late_start_time = now-2
     167        not_recent = now-0.5
     168        missing = {}
     169        for seqno, ip in self.incomplete_packets.items():
     170            st = ip.start_time
     171            if st>=max_start_time:
     172                continue        #too recent, may still arrive
     173            if st<late_start_time or ip.last_time<not_recent:
     174                if ip.chunks is None:
     175                    missing[seqno] = []
     176                else:
     177                    #TODO: use bitmap instead?
     178                    missing_chunks = [i for i,x in enumerate(ip.chunks) if x is None]
     179                    if missing_chunks:
     180                        missing[seqno] = missing_chunks
     181        return missing
     182
     183    def process_control(self, mtu, last_seq, high_seq, missing, cancel):
     184        log("process_control(%i, %i, %i, %s, %s)", mtu, last_seq, high_seq, missing, cancel)
     185        con = self._conn
     186        if not con:
     187            return
     188        if mtu and self.mtu==0:
     189            self.mtu = mtu
     190        #first, we can free all the packets that have been processed by the other end:
     191        if last_seq>=0:
     192            done = [x for x in self.fail_cb.keys() if x<=last_seq]
     193            for x in done:
     194                try:
     195                    del self.fail_cb[x]
     196                except:
     197                    pass
     198            done = [x for x in self.resend_cache.keys() if x<=last_seq]
     199            for x in done:
     200                try:
     201                    del self.resend_cache[x]
     202                except:
     203                    pass
     204        #next we can forget about sequence numbers that have been cancelled:
     205        if cancel:
     206            for seqno in cancel:
     207                if seqno>self.last_sequence:
     208                    self.can_skip.add(seqno)
     209                try:
     210                    del self.incomplete_packets[seqno]
     211                except:
     212                    pass
     213            #we may now be able to move forward a bit:
     214            if self.incomplete_packets and (self.last_sequence+1) in self.can_skip:
     215                self.process_incomplete()
     216        #re-send the missing ones:
     217        for seqno, missing_chunks in missing.items():
     218            resend_cache = self.resend_cache.get(seqno)
     219            fail_cb_seq = self.fail_cb.get(seqno)
     220            log("fail_cb[%i]=%s", seqno, repr_ellipsized(str(fail_cb_seq)))
     221            if fail_cb_seq is None and not resend_cache:
     222                log("cannot resend packet sequence %i - assuming we cancelled it already", seqno)
     223                #hope for the best, and tell the other end to stop asking:
     224                self.cancel.add(seqno)
     225                continue
     226            if len(missing_chunks)==0:
     227                #the other end only knows it is missing the seqno,
     228                #not how many chunks are missing, so send them all
     229                missing_chunks = resend_cache.keys()
     230            if fail_cb_seq:
     231                #we have a fail callback for this packet,
     232                #we have to decide if we send the missing chunks or use the callback,
     233                #resend if the other end is missing less than 25% of the chunks:
     234                #TODO: if the latency is low, resending is cheaper..
     235                if len(missing_chunks)>=len(resend_cache)//4:
     236                    #too many are missing, forget about it
     237                    try:
     238                        del self.resend_cache[seqno]
     239                    except:
     240                        pass
     241                    try:
     242                        del self.fail_cb[seqno]
     243                    except:
     244                        pass
     245                    self.cancel.add(seqno)
     246                    fail_cb_seq()
     247                    continue
     248            for c in missing_chunks:
     249                data = resend_cache.get(c)
     250                log("resend data[%i][%i]=%s", seqno, c, repr_ellipsized(str(data)))
     251                if data is None:
     252                    log.error("Error: cannot resend chunk %i of packet sequence %i", c, seqno)
     253                    log.error(" data missing from packet resend cache")
     254                    continue
     255                #send it again:
     256                #TODO: if the mtu is now lower, we should re-send the whole packet,
     257                # with the new chunk size..
     258                con.write(data)
     259
     260
     261    def send_async(self, packet):
     262        chunks = self.encode(packet)
     263        if len(chunks)>1:
     264            return Protocol.send_now(packet)
     265        proto_flags,index,level,data = chunks[0]
     266        from xpra.net.header import pack_header
     267        payload_size = len(data)
     268        header_and_data = pack_header(proto_flags, level, index, payload_size) + data
     269        with self._write_lock:
     270            if self._write_thread is None:
     271                self.start_write_thread()
     272            self._write_queue.put((header_and_data, None, None, None, False))
     273
     274    def process_udp_data(self, uuid, seqno, synchronous, chunk, chunks, data, bfrom):
     275        log("process_udp_data%s %i bytes", (uuid, seqno, synchronous, chunk, chunks, repr_ellipsized(data), bfrom), len(data))
     276        assert uuid==self.uuid
     277        if DROP_PCT>0:
     278            if random.randint(0, 100) <= DROP_PCT:
     279                log.warn("Warning: dropping udp packet %5i.%i", seqno, chunk)
     280                return
     281        if seqno<=self.last_sequence:
     282            #must be a duplicate, we've already processed it!
     283            return
     284        self.highest_sequence = max(self.highest_sequence, seqno)
     285        if self.incomplete_packets or (synchronous and seqno!=self.last_sequence+1) or chunk!=0 or chunks!=1:
     286            assert chunk>=0 and chunks>0 and chunk<chunks, "invalid chunk: %i/%i" % (chunk, chunks)
     287            #slow path: add chunk to incomplete packet
     288            now = monotonic_time()
     289            ip = self.incomplete_packets.get(seqno)
     290            if not ip or not ip.chunks:
     291                chunks_array = [None for _ in range(chunks)]
     292                ip = IncompletePacket(seqno, now, chunks_array)
     293                self.incomplete_packets[seqno] = ip
     294            else:
     295                ip.last_time = now
     296            ip.chunks[chunk] = data
     297            if seqno!=self.last_sequence+1:
     298                #we're waiting for a packet and this is not it,
     299                #make sure any gaps are marked as incomplete:
     300                for i in range(self.last_sequence+1, seqno):
     301                    if i not in self.incomplete_packets and i not in self.can_skip:
     302                        self.incomplete_packets[i] = IncompletePacket(i, now)
     303                #make sure we request the missing packets:
     304                self.schedule_control(self.jitter)
     305                if synchronous:
     306                    #we have to wait for the missing chunks / packets
     307                    log("process_udp_data: we're waiting for %i, not %i", self.last_sequence+1, seqno)
     308                    return
     309            if any(x is None for x in ip.chunks):
     310                #one of the chunks is still missing
     311                log("process_udp_data: sequence %i is still missing some chunks: %s", seqno, [i for i,x in enumerate(ip.chunks) if x is None])
     312                self.schedule_control(self.jitter)
     313                return
     314            #all the data is here!
     315            del self.incomplete_packets[seqno]
     316            data = b"".join(ip.chunks)
     317        #log("process_udp_data: adding packet sequence %i to read queue", seqno)
     318        if seqno==self.last_sequence+1:
     319            self.last_sequence = seqno
     320        else:
     321            assert not synchronous
     322            self.can_skip.add(seqno)
     323        self._read_queue_put(data)
     324        if self.incomplete_packets:
     325            self.process_incomplete()
     326
     327    def process_incomplete(self):
     328        #maybe we can send the next one(s) now?
     329        seqno = self.last_sequence
     330        log("process_incomplete() last_sequence=%i, can skip=%s", seqno, self.can_skip)
     331        while True:
     332            seqno += 1
     333            if seqno in self.can_skip:
     334                try:
     335                    del self.incomplete_packets[seqno]
     336                except KeyError:
     337                    pass
     338                self.can_skip.remove(seqno)
     339                self.last_sequence = seqno
     340                continue
     341            ip = self.incomplete_packets.get(seqno)
     342            if not ip or not ip.chunks:
     343                #it's missing, we just don't know how many chunks
     344                return
     345            if any(x is None for x in ip.chunks):
     346                #one of the chunks is still missing
     347                return
     348            #all the data is here!
     349            del self.incomplete_packets[seqno]
     350            data = b"".join(ip.chunks)
     351            log("process_incomplete: adding packet sequence %i to read queue", seqno)
     352            self.last_sequence = seqno
     353            self._read_queue_put(data)
     354
     355
     356    def write_buffers(self, buf_data, fail_cb, synchronous):
     357        buf = b"".join(buf_data)
     358        #if not isinstance(buf, JOIN_TYPES):
     359        #    buf = memoryview_to_bytes(buf)
     360        while True:
     361            try:
     362                seqno = self.output_packetcount
     363                return self.send_buf(seqno, buf, fail_cb, synchronous)
     364            except MTUExceeded as e:
     365                log.warn("%s: %s", e, self.mtu)
     366                if self.mtu>576:
     367                    self.mtu //= 2
     368                raise
     369
     370    def send_buf(self, seqno, data, fail_cb, synchronous):
     371        con = self._conn
     372        if not con:
     373            return 0
     374        #TODO: bump to 1280 for IPv6
     375        #mtu = max(576, self.mtu)
     376        mtu = max(1280, self.mtu)
     377        l = len(data)
     378        maxpayload = mtu-_header_size
     379        chunks = l // maxpayload
     380        if l % maxpayload > 0:
     381            chunks += 1
     382        #log("UDP.send_buf(%s, %i bytes, %s, %s) seq=%i, mtu=%s, maxpayload=%i, chunks=%i, data=%s", con, l, fail_cb, synchronous, seqno, mtu, maxpayload, chunks, repr_ellipsized(data))
     383        chunk = 0
     384        offset = 0
     385        if fail_cb:
     386            self.fail_cb[seqno] = fail_cb
     387        chunk_resend_cache = self.resend_cache.setdefault(seqno, {})
     388        while offset<l:
     389            assert chunk<chunks
     390            pl = min(maxpayload, l-offset)
     391            data_chunk = data[offset:offset+pl]
     392            udp_data = _header_struct.pack(self.uuid, seqno, synchronous, chunk, chunks) + data_chunk
     393            assert len(udp_data)<=mtu, "invalid payload size: %i greater than mtu %i" % (len(udp_data), mtu)
     394            con.write(udp_data)
     395            self.output_raw_packetcount += 1
     396            offset += pl
     397            if chunk_resend_cache is not None:
     398                chunk_resend_cache[chunk] = udp_data
     399            chunk += 1
     400        assert chunk==chunks, "wrote %i chunks but expected %i" % (chunk, chunks)
     401        self.output_packetcount += 1
     402        if not self.control_timer:
     403            self.control_timer = self.timeout_add(1000, self.send_control)
     404        return offset
     405
     406
     407    def get_info(self, alias_info=True):
     408        i = Protocol.get_info(self, alias_info)
     409        i["mtu"] = self.mtu
     410        return i
     411
     412
     413class UDPServerProtocol(UDPProtocol):
     414
     415    def _read_thread_loop(self):
     416        #server protocol is not used to read,
     417        #we rely on the listener to dispatch packets instead
     418        pass
     419
     420class UDPClientProtocol(UDPProtocol):
     421
     422    def con_write(self, data, fail_cb):
     423        """ After successfully writing some data, update the mtu value """
     424        r = UDPProtocol.con_write(self, data, fail_cb)
     425        if r>0 and LINUX:
     426            IP_MTU = 14
     427            con = self._conn
     428            if con:
     429                try:
     430                    self.mtu = min(32767, con._socket.getsockopt(socket.IPPROTO_IP, IP_MTU))
     431                    #log("mtu=%s", self.mtu)
     432                except IOError as e:
     433                    pass
     434        return r
     435
     436    def process_read(self, buf):
     437        """ Splits and parses the UDP frame header from the packet """
     438        #log.info("UDPClientProtocol.read_queue_put(%s)", repr_ellipsized(buf))
     439        uuid, seqno, synchronous, chunk, chunks = _header_struct.unpack_from(buf[:_header_size])
     440        data = buf[_header_size:]
     441        bfrom = None        #not available here..
     442        self.process_udp_data(uuid, seqno, synchronous, chunk, chunks, data, bfrom)
     443
     444
     445class UDPSocketConnection(SocketConnection):
     446    """
     447        This class extends SocketConnection to use socket.sendto
     448        to send data to the correct destination.
     449        (servers use a single socket to talk to multiple clients,
     450        they do not call connect() and so we have to specify the remote target every time)
     451    """
     452
     453    def __init__(self, *args):
     454        SocketConnection.__init__(self, *args)
     455
     456    def write(self, buf):
     457        #log("UDPSocketConnection: sending %i bytes to %s", len(buf), self.remote)
     458        try:
     459            return self._socket.sendto(buf, self.remote)
     460        except IOError as e:
     461            if e.errno==EMSGSIZE:
     462                raise MTUExceeded("invalid UDP payload size, cannot send %i bytes: %s" % (len(buf), e))
     463            raise
     464
     465    def close(self):
     466        """
     467            don't close the socket, we're don't own it
     468        """
     469        pass
     470
     471class MTUExceeded(IOError):
     472    pass
  • xpra/scripts/config.py

     
    437437                    "auth"              : str,
    438438                    "vsock-auth"        : str,
    439439                    "tcp-auth"          : str,
     440                    "udp-auth"          : str,
     441                    "dtls-auth"         : str,
    440442                    "ws-auth"           : str,
    441443                    "wss-auth"          : str,
    442444                    "ssl-auth"          : str,
     
    594596                    "bind"              : list,
    595597                    "bind-vsock"        : list,
    596598                    "bind-tcp"          : list,
     599                    "bind-udp"          : list,
     600                    "bind-dtls"         : list,
    597601                    "bind-ws"           : list,
    598602                    "bind-wss"          : list,
    599603                    "bind-ssl"          : list,
     
    615619    "start-after-connect", "start-child-after-connect",
    616620    "start-on-connect", "start-child-on-connect",
    617621    ]
    618 BIND_OPTIONS = ["bind", "bind-tcp", "bind-ssl", "bind-ws", "bind-wss", "bind-vsock"]
     622BIND_OPTIONS = ["bind", "bind-tcp", "bind-udp", "bind-ssl", "bind-ws", "bind-wss", "bind-vsock"]
    619623
    620624#keep track of the options added since v1,
    621625#so we can generate command lines that work with older supported versions:
     
    679683    "av-sync", "global-menus",
    680684    "printing", "file-transfer", "open-command", "open-files", "start-new-commands",
    681685    "mmap", "mmap-group", "mdns",
    682     "auth", "vsock-auth", "tcp-auth", "ws-auth", "wss-auth", "ssl-auth", "rfb-auth",
    683     "bind", "bind-vsock", "bind-tcp", "bind-ssl", "bind-ws", "bind-wss", "bind-rfb",
     686    "auth", "vsock-auth", "tcp-auth", "udp-auth", "dtls-auth", "ws-auth", "wss-auth", "ssl-auth", "rfb-auth",
     687    "bind", "bind-vsock", "bind-tcp", "bind-udp", "bind-dtls", "bind-ssl", "bind-ws", "bind-wss", "bind-rfb",
    684688    "start", "start-child",
    685689    "start-after-connect", "start-child-after-connect",
    686690    "start-on-connect", "start-child-on-connect",
     
    799803                    "auth"              : "",
    800804                    "vsock-auth"        : "",
    801805                    "tcp-auth"          : "",
     806                    "udp-auth"          : "",
     807                    "dtls-auth"         : "",
    802808                    "ws-auth"           : "",
    803809                    "wss-auth"          : "",
    804810                    "ssl-auth"          : "",
     
    946952                    "bind"              : bind_dirs,
    947953                    "bind-vsock"        : [],
    948954                    "bind-tcp"          : [],
     955                    "bind-udp"          : [],
     956                    "bind-dtls"         : [],
    949957                    "bind-ws"           : [],
    950958                    "bind-wss"          : [],
    951959                    "bind-ssl"          : [],
  • xpra/scripts/main.py

     
    534534                          metavar="[HOST]:[PORT]",
    535535                          help="Listen for connections over TCP (use --tcp-auth to secure it)."
    536536                            + " You may specify this option multiple times with different host and port combinations")
     537        group.add_option("--bind-udp", action="append",
     538                          dest="bind_udp", default=list(defaults.bind_udp or []),
     539                          metavar="[HOST]:[PORT]",
     540                          help="Listen for connections over UDP (use --udp-auth to secure it)."
     541                            + " You may specify this option multiple times with different host and port combinations")
     542        group.add_option("--bind-dtls", action="append",
     543                          dest="bind_dtls", default=list(defaults.bind_dtls or []),
     544                          metavar="[HOST]:[PORT]",
     545                          help="Listen for connections over UDP + DTLS (use --dtls-auth to secure it)."
     546                            + " You may specify this option multiple times with different host and port combinations")
    537547        group.add_option("--bind-ws", action="append",
    538548                          dest="bind_ws", default=list(defaults.bind_ws or []),
    539549                          metavar="[HOST]:[PORT]",
     
    558568        ignore({
    559569            "bind"      : defaults.bind,
    560570            "bind-tcp"  : defaults.bind_tcp,
     571            "bind-udp"  : defaults.bind_udp,
    561572            "bind-ws"   : defaults.bind_ws,
    562573            "bind-wss"  : defaults.bind_wss,
    563574            "bind-ssl"  : defaults.bind_ssl,
     
    974985    group.add_option("--tcp-auth", action="store",
    975986                      dest="tcp_auth", default=defaults.tcp_auth,
    976987                      help="The authentication module to use for TCP sockets (default: '%default')")
     988    group.add_option("--udp-auth", action="store",
     989                      dest="udp_auth", default=defaults.udp_auth,
     990                      help="The authentication module to use for UDP sockets (default: '%default')")
    977991    group.add_option("--ws-auth", action="store",
    978992                      dest="ws_auth", default=defaults.ws_auth,
    979993                      help="The authentication module to use for Websockets (default: '%default')")
     
    11631177    #    elif tcp_encryption:
    11641178    #        raise InitException("tcp-encryption %s should not use the same file as the password authentication file" % tcp_encryption)
    11651179
    1166 def dump_frames(*arsg):
     1180def dump_frames(*_args):
    11671181    frames = sys._current_frames()
    11681182    print("")
    11691183    print("found %s frames:" % len(frames))
     
    12411255
    12421256    #register posix signals for debugging:
    12431257    if POSIX:
    1244         def sigusr1(*args):
     1258        def sigusr1(*_args):
    12451259            dump_frames()
    12461260        signal.signal(signal.SIGUSR1, sigusr1)
    12471261
     
    16861700        if opts.socket_dir:
    16871701            desc["socket_dir"] = opts.socket_dir
    16881702        return desc
    1689     elif display_name.startswith("tcp:") or display_name.startswith("tcp/") or \
    1690             display_name.startswith("ssl:") or display_name.startswith("ssl/"):
    1691         ctype = display_name[:3]        #ie: "ssl" or "tcp"
    1692         separator = display_name[3]     # ":" or "/"
     1703    elif (
     1704        display_name.startswith("tcp:") or display_name.startswith("tcp/") or \
     1705        display_name.startswith("ssl:") or display_name.startswith("ssl/") or \
     1706        display_name.startswith("udp:") or display_name.startswith("udp/") or \
     1707        display_name.startswith("dtls:") or display_name.startswith("dtls/")
     1708        ):
     1709        ctype = display_name[:4].rstrip(":/")   #ie: "ssl" or "tcp"
     1710        separator = display_name[len(ctype)]     # ":" or "/"
    16931711        desc.update({
    16941712                     "type"     : ctype,
    16951713                     })
     
    20732091        from xpra.net.bytestreams import SocketConnection
    20742092        return SocketConnection(sock, "local", "host", (CID_TYPES.get(cid, cid), iport), dtype)
    20752093
    2076     elif dtype in ("tcp", "ssl", "ws", "wss"):
     2094    elif dtype in ("tcp", "ssl", "ws", "wss", "udp", "dtls"):
    20772095        if display_desc.get("ipv6"):
    20782096            assert socket.has_ipv6, "no IPv6 support"
    20792097            family = socket.AF_INET6
     
    20892107                socket.AF_INET  : "IPv4",
    20902108                }.get(family, family), (host, port), e))
    20912109        sockaddr = addrinfo[0][-1]
    2092         sock = socket.socket(family, socket.SOCK_STREAM)
    2093         sock.settimeout(SOCKET_TIMEOUT)
    2094         sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, TCP_NODELAY)
     2110        if dtype in ("udp", "dtls"):
     2111            opts.mmap = False
     2112            sock = socket.socket(family, socket.SOCK_DGRAM)
     2113        else:
     2114            sock = socket.socket(family, socket.SOCK_STREAM)
     2115            sock.settimeout(SOCKET_TIMEOUT)
     2116            sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, TCP_NODELAY)
    20952117        strict_host_check = display_desc.get("strict-host-check")
    20962118        if strict_host_check is False:
    20972119            opts.ssl_server_verify_mode = "none"
    20982120        conn = _socket_connect(sock, sockaddr, display_name, dtype)
    2099         if dtype in ("ssl", "wss"):
     2121        if dtype in ("ssl", "wss", "dtls"):
     2122            if dtype=="dtls":
     2123                from dtls import do_patch   #@UnresolvedImport
     2124                do_patch()
    21002125            wrap_socket = ssl_wrap_socket_fn(opts, server_side=False)
    21012126            sock = wrap_socket(sock)
    21022127            assert sock, "failed to wrap socket %s" % sock
     
    22712296        raise InitException("cannot check hostname with verify mode %s" % verify_mode)
    22722297    wrap_socket = context.wrap_socket
    22732298    del opts
    2274     def do_wrap_socket(tcp_socket):
     2299    def do_wrap_socket(tcp_socket, handshake=None):
    22752300        from xpra.log import Logger
    22762301        try:
    22772302            ssl_sock = wrap_socket(tcp_socket, **kwargs)
     
    22812306            if SSLEOFError and isinstance(e, SSLEOFError):
    22822307                return None
    22832308            raise InitExit(EXIT_SSL_FAILURE, "Cannot wrap socket %s: %s" (tcp_socket, e))
    2284         if not server_side:
     2309        if not server_side and handshake is not False:
    22852310            try:
    22862311                ssl_sock.do_handshake(True)
    22872312            except Exception as e:
     
    24012426                from xpra.codecs.loader import encodings_help
    24022427                encodings = ["auto"] + app.get_encodings()
    24032428                raise InitInfo(info+"%s xpra client supports the following encodings:\n * %s" % (app.client_toolkit(), "\n * ".join(encodings_help(encodings))))
    2404         def handshake_complete(*args):
     2429        def handshake_complete(*_args):
    24052430            from xpra.log import Logger
    24062431            log = Logger()
    24072432            log.info("Attached to %s (press Control-C to detach)\n", conn.target)
  • xpra/scripts/server.py

     
    357357    if opts.encoding=="help" or "help" in opts.encodings:
    358358        return show_encoding_help(opts)
    359359
    360     from xpra.server.socket_util import parse_bind_tcp, parse_bind_vsock
    361     bind_tcp = parse_bind_tcp(opts.bind_tcp)
    362     bind_ssl = parse_bind_tcp(opts.bind_ssl)
    363     bind_ws = parse_bind_tcp(opts.bind_ws)
    364     bind_wss = parse_bind_tcp(opts.bind_wss)
    365     bind_rfb = parse_bind_tcp(opts.bind_rfb)
     360    from xpra.server.socket_util import parse_bind_ip, parse_bind_vsock
     361    bind_tcp = parse_bind_ip(opts.bind_tcp)
     362    bind_udp = parse_bind_ip(opts.bind_udp)
     363    bind_dtls= parse_bind_ip(opts.bind_dtls)
     364    bind_ssl = parse_bind_ip(opts.bind_ssl)
     365    bind_ws  = parse_bind_ip(opts.bind_ws)
     366    bind_wss = parse_bind_ip(opts.bind_wss)
     367    bind_rfb = parse_bind_ip(opts.bind_rfb)
    366368    bind_vsock = parse_bind_vsock(opts.bind_vsock)
    367369
    368370    assert mode in ("start", "start-desktop", "upgrade", "shadow", "proxy")
     
    567569    sockets = []
    568570
    569571    #SSL sockets:
    570     wrap_socket_fn = None
     572    wrap_server_socket_fn = None
     573    wrap_client_socket_fn = None
    571574    need_ssl = False
    572575    ssl_opt = opts.ssl.lower()
    573576    if ssl_opt in TRUE_OPTIONS or bind_ssl or bind_wss:
     
    582585    if need_ssl:
    583586        from xpra.scripts.main import ssl_wrap_socket_fn
    584587        try:
    585             wrap_socket_fn = ssl_wrap_socket_fn(opts, server_side=True)
    586             netlog("wrap_socket_fn=%s", wrap_socket_fn)
     588            wrap_server_socket_fn = ssl_wrap_socket_fn(opts, server_side=True)
     589            wrap_client_socket_fn = ssl_wrap_socket_fn(opts, server_side=False)
     590            netlog("wrap socket functions: %s, %s", wrap_server_socket_fn, wrap_client_socket_fn)
    587591        except Exception as e:
    588592            netlog("SSL error", exc_info=True)
    589593            cpaths = csv("'%s'" % x for x in (opts.ssl_cert, opts.ssl_key) if x)
    590594            raise InitException("cannot create SSL socket, check your certificate paths (%s): %s" % (cpaths, e))
    591595
     596    from xpra.server.socket_util import setup_tcp_socket, setup_udp_socket, setup_vsock_socket, setup_local_sockets
    592597    def add_mdns(socktype, host, port):
    593598        recs = mdns_recs.setdefault(socktype.lower(), [])
    594599        rec = (host, port)
     
    598603        socket = setup_tcp_socket(host, iport, socktype)
    599604        sockets.append(socket)
    600605        add_mdns(socktype, host, iport)
    601 
     606    def add_udp_socket(socktype, host, iport):
     607        socket = setup_udp_socket(host, iport, socktype)
     608        if socktype=="dtls":
     609            from dtls import do_patch   #@UnresolvedImport
     610            do_patch()
     611        sockets.append(socket)
     612        add_mdns(socktype, host, iport)
    602613    # Initialize the TCP sockets before the display,
    603614    # That way, errors won't make us kill the Xvfb
    604615    # (which may not be ours to kill at that point)
    605     from xpra.server.socket_util import setup_tcp_socket, setup_vsock_socket, setup_local_sockets
    606616    netlog("setting up SSL sockets: %s", bind_ssl)
    607617    for host, iport in bind_ssl:
    608618        add_tcp_socket("SSL", host, iport)
     
    615625        add_tcp_socket("tcp", host, iport)
    616626        if tcp_ssl:
    617627            add_mdns("ssl", host, iport)
     628    netlog("setting up UDP sockets: %s", bind_udp)
     629    for host, iport in bind_udp:
     630        add_udp_socket("udp", host, iport)
     631    netlog("setting up UDP+DTLS sockets: %s", bind_dtls)
     632    for host, iport in bind_dtls:
     633        add_udp_socket("dtls", host, iport)
    618634    netlog("setting up http / ws (websockets): %s", bind_ws)
    619635    for host, iport in bind_ws:
    620636        add_tcp_socket("ws", host, iport)
     
    908924            mdns_publish(display_name, mode, listen_on, mdns_info)
    909925
    910926    try:
    911         app._ssl_wrap_socket = wrap_socket_fn
     927        app._ssl_wrap_server_socket = wrap_server_socket_fn
     928        app._ssl_wrap_client_socket = wrap_client_socket_fn
    912929        app.original_desktop_display = desktop_display
    913930        app.exec_cwd = opts.chdir or cwd
    914931        app.init(opts)
  • xpra/server/auth/allow_auth.py

     
    1515    def get_password(self):
    1616        return None
    1717
    18     def authenticate(self, challenge_response, client_salt):
     18    def authenticate(self, _challenge_response, _client_salt=None):
    1919        return True
  • xpra/server/auth/file_auth.py

     
    55
    66#authentication from a file containing just the password
    77
    8 import binascii
    9 import hmac
    10 
    11 from xpra.os_util import strtobytes
    12 from xpra.net.crypto import get_digest_module
     8from xpra.net.crypto import verify_digest
    139from xpra.server.auth.file_auth_base import FileAuthenticatorBase, init, log
    14 from xpra.util import xor, nonl
     10from xpra.util import xor
    1511
    1612
    1713#will be called when we init the module
     
    2016
    2117class Authenticator(FileAuthenticatorBase):
    2218
    23     def authenticate_hmac(self, challenge_response, client_salt):
     19    def authenticate_hmac(self, challenge_response, client_salt=None):
    2420        if not self.salt:
    2521            log.error("Error: illegal challenge response received - salt cleared or unset")
    2622            return None
     
    3531            log.error("Error: authentication failed")
    3632            log.error(" no password for '%s' in '%s'", self.username, self.password_filename)
    3733            return False
    38         digestmod = get_digest_module(self.digest)
    39         if not digestmod:
    40             log.error("Error: %s authentication failed", self)
    41             log.error(" digest module '%s' is invalid", self.digest)
    42             return False
    43         verify = hmac.HMAC(strtobytes(password), strtobytes(salt), digestmod=digestmod).hexdigest()
    44         log("file authenticate(%s) password='%s', salt=%s, hash=%s", nonl(challenge_response), nonl(password), binascii.hexlify(strtobytes(salt)), verify)
    45         if not hmac.compare_digest(verify, challenge_response):
    46             log("expected '%s' but got '%s'", verify, challenge_response)
     34        if not verify_digest(self.digest, password, salt, challenge_response):
    4735            log.error("Error: hmac password challenge for '%s' does not match", self.username)
    4836            return False
    4937        return True
  • xpra/server/auth/multifile_auth.py

     
    88
    99import os
    1010import binascii
    11 import hmac
    1211
    1312from xpra.server.auth.file_auth_base import log, FileAuthenticatorBase, init as file_init
    1413from xpra.os_util import strtobytes, POSIX
    1514from xpra.util import xor, parse_simple_dict
    16 from xpra.net.crypto import get_digest_module
     15from xpra.net.crypto import verify_digest
    1716
    1817
    1918socket_dir = None
     
    126125            return None
    127126        return entry[0]
    128127
    129     def authenticate_hmac(self, challenge_response, client_salt):
     128    def authenticate_hmac(self, challenge_response, client_salt=None):
    130129        log("authenticate_hmac(%s, %s)", challenge_response, client_salt)
    131130        self.sessions = None
    132131        if not self.salt:
     
    145144            return None
    146145        log("authenticate: auth-info(%s)=%s", self.username, entry)
    147146        fpassword, uid, gid, displays, env_options, session_options = entry
    148         digestmod = get_digest_module(self.digest)
    149         verify = hmac.HMAC(strtobytes(fpassword), strtobytes(salt), digestmod=digestmod).hexdigest()
    150147        log("multifile authenticate_hmac(%s) password='%s', hex(salt)=%s, hash=%s", challenge_response, fpassword, binascii.hexlify(strtobytes(salt)), verify)
    151         if not hmac.compare_digest(verify, challenge_response):
    152             log("expected '%s' but got '%s'", verify, challenge_response)
     148        if not verify_digest(self.digest, fpassword, salt, challenge_response):
    153149            log.error("Error: hmac password challenge for '%s' does not match", self.username)
    154150            return False
    155151        self.sessions = uid, gid, displays, env_options, session_options
  • xpra/server/auth/none_auth.py

     
    1717    def requires_challenge(self):
    1818        return False
    1919
    20     def get_challenge(self, digests):
     20    def get_challenge(self, _digests):
    2121        return None
    2222
    2323    def get_password(self):
    2424        return None
    2525
    26     def authenticate(self, challenge_response, client_salt):
     26    def authenticate(self, _challenge_response, _client_salt=None):
    2727        return True
    2828
    2929    def __repr__(self):
  • xpra/server/auth/peercred_auth.py

     
    9191        #pretend to require a challenge and fail it:
    9292        return self.uid<0
    9393
    94     def authenticate(self, challenge_response, client_salt):
     94    def authenticate(self, _challenge_response, _client_salt=None):
    9595        return False
    9696
    9797    def __repr__(self):
  • xpra/server/auth/reject_auth.py

     
    1111
    1212
    1313class Authenticator(object):
    14     def __init__(self, username, **kwargs):
     14    def __init__(self, username, **_kwargs):
    1515        self.username = username
    1616
    1717    def requires_challenge(self):
     
    2929    def get_password(self):
    3030        return None
    3131
    32     def authenticate(self, challenge_response, client_salt):
     32    def authenticate(self, _challenge_response, _client_salt=None):
    3333        return False
    3434
    3535    def get_sessions(self):
  • xpra/server/auth/sys_auth_base.py

     
    33# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
    44# later version. See the file COPYING for details.
    55
    6 import hmac, binascii
     6import binascii
    77
    88from xpra.platform.dotxpra import DotXpra
    99from xpra.util import xor
    10 from xpra.net.crypto import get_salt, choose_digest, get_digest_module
     10from xpra.net.crypto import get_salt, choose_digest, verify_digest
    1111from xpra.os_util import strtobytes
    1212from xpra.log import Logger
    1313log = Logger("auth")
     
    6565    def check(self, password):
    6666        raise NotImplementedError()
    6767
    68     def authenticate(self, challenge_response, client_salt):
     68    def authenticate(self, challenge_response, client_salt=None):
    6969        #this will call check(password)
    7070        return self.authenticate_check(challenge_response, client_salt)
    7171
    72     def authenticate_check(self, challenge_response, client_salt):
     72    def authenticate_check(self, challenge_response, client_salt=None):
    7373        if self.salt is None:
    7474            log.error("Error: illegal challenge response received - salt cleared or unset")
    7575            return False
     
    9191            return False
    9292        return ret
    9393
    94     def authenticate_hmac(self, challenge_response, client_salt):
     94    def authenticate_hmac(self, challenge_response, client_salt=None):
    9595        if not self.salt:
    9696            log.error("Error: illegal challenge response received - salt cleared or unset")
    9797            return None
     
    107107            log.error("Error: %s authentication failed", self)
    108108            log.error(" no password defined for '%s'", self.username)
    109109            return False
    110         digestmod = get_digest_module(self.digest)
    111         if not digestmod:
    112             log.error("Error: %s authentication failed", self)
    113             log.error(" digest module '%s' is invalid", self.digest)
    114             return False
    115         verify = hmac.HMAC(strtobytes(password), strtobytes(salt), digestmod=digestmod).hexdigest()
    116         log("%s auth: authenticate(%s) password=%s, hex(salt)=%s, hash=%s", self, challenge_response, password, binascii.hexlify(strtobytes(salt)), verify)
    117         if not hmac.compare_digest(verify, challenge_response):
    118             log("expected '%s' but got '%s'", verify, challenge_response)
     110        if not verify_digest(self.digest, password, salt, challenge_response):
    119111            log.error("Error: hmac password challenge for '%s' does not match", self.username)
    120112            return False
    121113        return True
  • xpra/server/proxy/proxy_instance_process.py

     
    359359                return
    360360        self.send_disconnect(proto, CONTROL_COMMAND_ERROR, "this socket only handles 'info', 'version' and 'stop' requests")
    361361
    362     def send_disconnect(self, proto, reason, *extra):
    363         log("send_disconnect(%s, %s, %s)", proto, reason, extra)
     362    def send_disconnect(self, proto, *reasons):
     363        log("send_disconnect(%s, %s)", proto, reasons)
    364364        if proto._closed:
    365365            return
    366         proto.send_now(["disconnect", reason]+list(extra))
     366        proto.send_disconnect(reasons)
    367367        self.timeout_add(1000, self.force_disconnect, proto)
    368368
    369369    def force_disconnect(self, proto):
     
    501501        for proto in (self.client_protocol, self.server_protocol):
    502502            if proto and proto!=skip_proto:
    503503                log("sending disconnect to %s", proto)
    504                 proto.flush_then_close(["disconnect", SERVER_SHUTDOWN, reason])
     504                proto.send_disconnect([SERVER_SHUTDOWN, reason])
    505505
    506506
    507507    def queue_client_packet(self, packet):
     
    639639            if packet[3]:
    640640                packet[3] = Compressed("file-chunk-data", packet[3])
    641641        elif packet_type=="challenge":
    642             from xpra.net.crypto import get_salt, get_digest_module
     642            from xpra.net.crypto import get_salt, get_hexdigest
    643643            #client may have already responded to the challenge,
    644644            #so we have to handle authentication from this end
    645645            salt = packet[1]
     
    646646            digest = packet[3]
    647647            client_salt = get_salt(len(salt))
    648648            salt = xor_str(salt, client_salt)
    649             digestmod = get_digest_module(digest)
    650             if not digestmod:
    651                 self.stop("digest mode '%s' not supported", std(digest))
    652                 return
    653649            password = self.session_options.get("password")
    654650            if not password:
    655651                self.stop("authentication requested by the server, but no password available for this session")
    656652                return
    657             import hmac
    658653            password = strtobytes(password)
    659654            salt = memoryview_to_bytes(salt)
    660             challenge_response = hmac.HMAC(password, salt, digestmod=digestmod).hexdigest()
     655            challenge_response = get_hexdigest(digest, password, salt)
    661656            log.info("sending %s challenge response", digest)
    662657            self.send_hello(challenge_response, client_salt)
    663658            return
  • xpra/server/proxy/proxy_server.py

     
    149149        if not protocol._closed:
    150150            self.send_disconnect(protocol, LOGIN_TIMEOUT)
    151151
    152     def hello_oked(self, proto, packet, c, auth_caps):
    153         if ServerCore.hello_oked(self, proto, packet, c, auth_caps):
     152    def do_hello_oked(self, proto, packet, c, auth_caps):
     153        if ServerCore.do_hello_oked(self, proto, packet, c, auth_caps):
    154154            #already handled in superclass
    155155            return
    156156        self.accept_client(proto, c)
  • xpra/server/rfb/__init__.py

     
     1# This file is part of Xpra.
     2# Copyright (C) 2017 Antoine Martin <antoine@devloop.org.uk>
     3# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
     4# later version. See the file COPYING for details.
  • xpra/server/rfb/rfb_const.py

     
     1# This file is part of Xpra.
     2# Copyright (C) 2017 Antoine Martin <antoine@devloop.org.uk>
     3# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
     4# later version. See the file COPYING for details.
     5
     6import struct
     7
     8#merge header and packet if packet is smaller than:
     9PIXEL_FORMAT = "BBBBHHHBBBBBB"
     10
     11
     12class RFBClientMessage(object):
     13    """ client to server messages """
     14    SETPIXELFORMAT = 0
     15    SETENCODINGS = 2
     16    FRAMEBUFFERUPDATEREQUEST = 3
     17    KEYEVENT = 4
     18    POINTEREVENT = 5
     19    CLIENTCUTTEXT = 6
     20    #optional:
     21    FILETRANSFER = 7
     22    SETSCALE = 8
     23    SETSERVERINPUT = 9
     24    SETSW = 10
     25    TEXTCHAT = 11
     26    KEYFRAMEREQUEST = 12
     27    KEEPALIVE = 13
     28    SETSCALEFACTOR = 15
     29    REQUESTSESSION = 20
     30    SETSESSION = 21
     31    NOTIFYPLUGINSTREAMING = 80
     32    VMWARE = 127
     33    CARCONNECTIVITY = 128
     34    ENABLECONTINUOUSUPDATES = 150
     35    CLIENTFENCE = 248
     36    OLIVECALLCONTROL = 249
     37    XVPCLIENTMESSAGE = 250
     38    SETDESKTOPSIZE = 251
     39    TIGHT = 252
     40    GIICLIENTMESSAGE = 253
     41    VMWARE = 254
     42    QEMUCLIENTMESSAGE = 255
     43
     44    PACKET_TYPE_STR = {
     45        SETPIXELFORMAT               : "SetPixelFormat",
     46        SETENCODINGS                 : "SetEncodings",
     47        FRAMEBUFFERUPDATEREQUEST     : "FramebufferUpdateRequest",
     48        KEYEVENT                     : "KeyEvent",
     49        POINTEREVENT                 : "PointerEvent",
     50        CLIENTCUTTEXT                : "ClientCutText",
     51        #optional:
     52        FILETRANSFER                 : "FileTransfer",
     53        SETSCALE                     : "SetScale",
     54        SETSERVERINPUT               : "SetServerInput",
     55        SETSW                        : "SetSW",
     56        TEXTCHAT                     : "TextChat",
     57        KEYFRAMEREQUEST              : "KeyFrameRequest",
     58        KEEPALIVE                    : "KeepAlive",
     59        SETSCALEFACTOR               : "SetScaleFactor",
     60        REQUESTSESSION               : "RequestSession",
     61        SETSESSION                   : "SetSession",
     62        NOTIFYPLUGINSTREAMING        : "NotifiyPluginStreaming",
     63        VMWARE                       : "VMWare",
     64        CARCONNECTIVITY              : "CarConnectivity",
     65        ENABLECONTINUOUSUPDATES      : "EnableContiniousUpdates",
     66        CLIENTFENCE                  : "ClientFence",
     67        OLIVECALLCONTROL             : "OliveCallControl",
     68        XVPCLIENTMESSAGE             : "XvpClientMessage",
     69        SETDESKTOPSIZE               : "SetDesktopSize",
     70        TIGHT                        : "Tight",
     71        GIICLIENTMESSAGE             : "GIIClientMessage",
     72        VMWARE                       : "VMWare",
     73        QEMUCLIENTMESSAGE            : "QEMUClientMessage",
     74    }
     75    PACKET_FMT = {
     76        SETPIXELFORMAT               : "!BBBB"+PIXEL_FORMAT,
     77        SETENCODINGS                 : "!BBH",
     78        FRAMEBUFFERUPDATEREQUEST     : "!BBHHHH",
     79        KEYEVENT                     : "!BBBBi",
     80        POINTEREVENT                 : "!BBHH",
     81        CLIENTCUTTEXT                : "!BBBBi",
     82        }
     83    PACKET_STRUCT = {}
     84    for ptype, fmt in PACKET_FMT.items():
     85        PACKET_STRUCT[ptype] = struct.Struct(fmt)
     86
     87
     88class RFBServerMessage(object):
     89    #server to client messages:
     90    FRAMEBUFFERUPDATE = 0
     91    SETCOLORMAPENTRIES = 1
     92    BELL = 2
     93    SERVERCUTTEXT = 3
     94    #optional:
     95    RESIZEFRAMEBUFFER1 = 4
     96    KEYFRAMEUPDATE = 4
     97    FILETRANSFER = 7
     98    TEXTCHAT = 11
     99    KEEPALIVE = 13
     100    RESIZEFRAMEBUFFER2 = 15
     101    VMWARE1 = 127
     102    CARCONNECTIVITY = 128
     103    ENDOFCONTINOUSUPDATES = 150
     104    SERVERSTATE = 173
     105    SERVERFENCE = 248
     106    OLIVECALLCONTROL = 249
     107    XVPSERVERMESSAGE = 250
     108    TIGHT = 252
     109    GIISERVERMESSAGE = 253
     110    VMWARE2 = 254
     111    QEMUSERVERMESSAGE = 255
     112
     113    PACKET_TYPE_STR = {
     114        FRAMEBUFFERUPDATE        : "FramebufferUpdate",
     115        SETCOLORMAPENTRIES       : "SetColorMapEntries",
     116        BELL                     : "Bell",
     117        SERVERCUTTEXT            : "ServerCutText",
     118        #optional:
     119        RESIZEFRAMEBUFFER1       : "ResizeFrameBuffer1",
     120        KEYFRAMEUPDATE           : "KeyFrameUpdate",
     121        FILETRANSFER             : "FileTransfer",
     122        TEXTCHAT                 : "TextChat",
     123        KEEPALIVE                : "KeepAlive",
     124        RESIZEFRAMEBUFFER2       : "ResizeFrameBuffer2",
     125        VMWARE1                  : "VMWare1",
     126        CARCONNECTIVITY          : "CarConnectivity",
     127        ENDOFCONTINOUSUPDATES    : "EndOfContinousUpdates",
     128        SERVERSTATE              : "ServerState",
     129        SERVERFENCE              : "ServerFence",
     130        OLIVECALLCONTROL         : "OliveCallControl",
     131        XVPSERVERMESSAGE         : "XvpServerMessage",
     132        TIGHT                    : "Tight",
     133        GIISERVERMESSAGE         : "GIIServerMessage",
     134        VMWARE2                  : "VMWare2",
     135        QEMUSERVERMESSAGE        : "QEMUServerMessage",
     136        }
     137
     138class RFBEncoding(object):
     139    RAW = 0
     140    COPYRECT = 1
     141    RRE = 2
     142    CORRE = 4
     143    HEXTILE = 5
     144    ZLIB = 6
     145    TIGHT = 7
     146    ZLIBHEX = 8
     147    TRLE = 15
     148    ZRLE = 16
     149    H264 = 20
     150    JPEG = 21
     151    JRLE = 22
     152    HITACHI_ZYWRLE = 17
     153    DESKTOPSIZE = -223
     154    LASTRECT = -224
     155    CURSOR = -239
     156    XCURSOR = -240
     157    QEMU_POINTER = -257
     158    QEMU_KEY = -258
     159    QEMU_AUDIO = -259
     160    GII = -305
     161    DESKTOPNAME = -307
     162    EXTENDEDDESKTOPSIZE = -308
     163    XVP = -309
     164    FENCE = -312
     165    CONTINUOUSUPDATES = -313
     166    CURSORWITHALPHA = -314
     167    VA_H264 = 0x48323634
     168
     169    #-23 to -32    JPEG Quality Level Pseudo-encoding
     170    #-247 to -256    Compression Level Pseudo-encoding
     171    #-412 to -512    JPEG Fine-Grained Quality Level Pseudo-encoding
     172    #-763 to -768    JPEG Subsampling Level Pseudo-encoding
     173   
     174    ENCODING_STR = {
     175        RAW                 : "Raw",
     176        COPYRECT            : "CopyRect",
     177        RRE                 : "RRE",
     178        CORRE               : "CoRRE",
     179        HEXTILE             : "Hextile",
     180        ZLIB                : "Zlib",
     181        TIGHT               : "Tight",
     182        ZLIBHEX             : "ZlibHex",
     183        TRLE                : "TRLE",
     184        ZRLE                : "ZRLE",
     185        H264                : "H264",
     186        JPEG                : "JPEG",
     187        JRLE                : "JRLE",
     188        HITACHI_ZYWRLE      : "HITACHI_ZYWRLE",
     189        DESKTOPSIZE         : "DesktopSize",
     190        LASTRECT            : "LastRect",
     191        CURSOR              : "Cursor",
     192        XCURSOR             : "XCursor",
     193        QEMU_POINTER        : "QEMU Pointer",
     194        QEMU_KEY            : "QEMU Key",
     195        QEMU_AUDIO          : "QEMU Audio",
     196        GII                 : "GII",
     197        DESKTOPNAME         : "DesktopName",
     198        EXTENDEDDESKTOPSIZE : "ExtendedDesktopSize",
     199        XVP                 : "Xvp",
     200        FENCE               : "Fence",
     201        CONTINUOUSUPDATES   : "ContinuousUpdates",
     202        CURSORWITHALPHA     : "CursorWithAlpha",
     203        VA_H264             : "VA_H264",
     204        }
     205
     206
     207class RFBAuth(object):
     208    INVALID = 0
     209    NONE = 1
     210    VNC = 2
     211    TIGHT = 16
     212    AUTH_STR = {
     213        INVALID    : "Invalid",
     214        NONE       : "None",
     215        VNC        : "VNC",
     216        TIGHT      : "Tight",
     217        5                   : "RA2",
     218        6                   : "RA2ne",
     219        17                  : "Ultra",
     220        18                  : "TLS",
     221        19                  : "VeNCrypt",
     222        20                  : "SASL",
     223        21                  : "MD5",
     224        22                  : "xvp",
     225        }
     226    for i in (3, 4):
     227        AUTH_STR[i] = "RealVNC"
     228    for i in range(7, 16):
     229        AUTH_STR[i] = "RealVNC"
     230    for i in range(128, 255):
     231        AUTH_STR[i] = "RealVNC"
     232    for i in range(30, 35):
     233        AUTH_STR[i] = "Apple"
     234
     235
     236RFB_KEYNAMES = {
     237    0xff08      : "BackSpace",
     238    0xff09      : "Tab",
     239    0xff0d      : "Return",
     240    0xff1b      : "Escape",
     241    0xff63      : "Insert",
     242    0xffff      : "Delete",
     243    0xff50      : "Home",
     244    0xff57      : "End",
     245    0xff55      : "PageUp",
     246    0xff56      : "PageDown",
     247    0xff51      : "Left",
     248    0xff52      : "Up",
     249    0xff53      : "Right",
     250    0xff54      : "Down",
     251    0xffe1      : "Shift_L",
     252    0xffe2      : "Shift_R",
     253    0xffe3      : "Control_L",
     254    0xffe4      : "Control_R",
     255    0xffe7      : "Meta_L",
     256    0xffe8      : "Meta_R",
     257    0xffe9      : "Alt_L",
     258    0xffea      : "Alt_R",
     259    }
     260for i in range(1, 13):
     261    RFB_KEYNAMES[0xffbe+(i-1)] = "F%i" % i
  • xpra/server/rfb/rfb_protocol.py

     
    1313log = Logger("network", "protocol", "rfb")
    1414
    1515from xpra.os_util import Queue
    16 from xpra.util import repr_ellipsized, envint
     16from xpra.util import repr_ellipsized, envint, nonl
    1717from xpra.make_thread import make_thread, start_thread
     18from xpra.net.protocol import force_flush_queue, exit_queue
    1819from xpra.net.common import ConnectionClosedException          #@UndefinedVariable (pydev false positive)
    1920from xpra.net.bytestreams import ABORT
     21from xpra.server.rfb.rfb_const import RFBClientMessage, RFBAuth, PIXEL_FORMAT
    2022
    2123READ_BUFFER_SIZE = envint("XPRA_READ_BUFFER_SIZE", 65536)
    2224#merge header and packet if packet is smaller than:
    23 PIXEL_FORMAT = "BBBBHHHBBBBBB"
    2425
    25 RFB_SETPIXELFORMAT = 0
    26 RFB_SETENCODINGS = 2
    27 RFB_FRAMEBUFFERUPDATEREQUEST = 3
    28 RFB_KEYEVENT = 4
    29 RFB_POINTEREVENT = 5
    30 RFB_CLIENTCUTTEXT = 6
    31 PACKET_TYPE = {
    32     RFB_SETPIXELFORMAT              : "SetPixelFormat",
    33     RFB_SETENCODINGS                : "SetEncodings",
    34     RFB_FRAMEBUFFERUPDATEREQUEST    : "FramebufferUpdateRequest",
    35     RFB_KEYEVENT                    : "KeyEvent",
    36     RFB_POINTEREVENT                : "PointerEvent",
    37     RFB_CLIENTCUTTEXT               : "ClientCutText",
    38     }
    39 PACKET_FMT = {
    40     RFB_SETPIXELFORMAT              : "!BBBB"+PIXEL_FORMAT,
    41     RFB_SETENCODINGS                : "!BBH",
    42     RFB_FRAMEBUFFERUPDATEREQUEST    : "!BBHHHH",
    43     RFB_KEYEVENT                    : "!BBBBi",
    44     RFB_POINTEREVENT                : "!BBHH",
    45     RFB_CLIENTCUTTEXT               : "!BBBBi",
    46     }
    47 PACKET_STRUCT = {}
    48 for ptype, fmt in PACKET_FMT.items():
    49     PACKET_STRUCT[ptype] = struct.Struct(fmt)
    5026
    51 
    5227class RFBProtocol(object):
    5328    CONNECTION_LOST = "connection-lost"
    5429    INVALID = "invalid"
    5530
    56     def __init__(self, scheduler, conn, process_packet_cb, get_rfb_pixelformat, session_name="Xpra"):
     31    def __init__(self, scheduler, conn, auth, process_packet_cb, get_rfb_pixelformat, session_name="Xpra"):
    5732        """
    5833            You must call this constructor and source_has_more() from the main thread.
    5934        """
     
    6237        self.timeout_add = scheduler.timeout_add
    6338        self.idle_add = scheduler.idle_add
    6439        self._conn = conn
     40        self._authenticator = auth
    6541        self._process_packet_cb = process_packet_cb
    6642        self._get_rfb_pixelformat = get_rfb_pixelformat
    6743        self.session_name = session_name
    6844        self._write_queue = Queue(1)
    6945        self._buffer = b""
     46        self._challenge = None
     47        self.share = False
    7048        #counters:
    7149        self.input_packetcount = 0
    7250        self.input_raw_packetcount = 0
     
    8765        return len(packet)
    8866
    8967    def _parse_protocol_handshake(self, packet):
     68        log("parse_protocol_handshake(%s)", nonl(packet))
    9069        if len(packet)<12:
    9170            return 0
    9271        if not packet.startswith(b'RFB '):
     
    9473            return 0
    9574        #ie: packet==b'RFB 003.008\n'
    9675        self._protocol_version = tuple(int(x) for x in packet[4:11].split("."))
    97         log.info("RFB version %s", b".".join(str(x) for x in self._protocol_version))
     76        log.info("RFB version %s connection from %s", b".".join(str(x) for x in self._protocol_version), self._conn.target)
     77        if self._protocol_version!=(3, 8):
     78            msg = "unsupported protocol version"
     79            log.error("Error: %s", msg)
     80            self.send(struct.pack("!BI", 0, len(msg))+msg)
     81            self.invalid(msg, packet)
     82            return 0
    9883        #reply with Security Handshake:
    9984        self._packet_parser = self._parse_security_handshake
    100         self.send(struct.pack("BB", 1, 1))
     85        if self._authenticator and self._authenticator.requires_challenge():
     86            security_types = [RFBAuth.VNC]
     87        else:
     88            security_types = [RFBAuth.NONE]
     89        packet = struct.pack("B", len(security_types))
     90        for x in security_types:
     91            packet += struct.pack("B", x)
     92        self.send(packet)
    10193        return 12
    10294
    10395    def _parse_security_handshake(self, packet):
    104         if packet!=b"\1":
    105             self._invalid_header(packet, "invalid security handshake response")
     96        log("parse_security_handshake(%s)", binascii.hexlify(packet))
     97        try:
     98            auth = struct.unpack("B", packet)[0]
     99        except:
     100            self._internal_error(packet, "cannot parse security handshake response '%s'" % binascii.hexlify(packet))
    106101            return 0
     102        auth_str = RFBAuth.AUTH_STR.get(auth, auth)
     103        if auth==RFBAuth.VNC:
     104            #send challenge:
     105            self._packet_parser = self._parse_challenge
     106            assert self._authenticator
     107            challenge, digest = self._authenticator.get_challenge("des")
     108            assert digest=="des"
     109            self._challenge = challenge[:16]
     110            log("sending RFB challenge value: %s", binascii.hexlify(self._challenge))
     111            self.send(self._challenge)
     112            return 1
     113        if self._authenticator and self._authenticator.requires_challenge():
     114            self._invalid_header(packet, "invalid security handshake response, authentication is required")
     115            return 0
     116        log("parse_security_handshake: auth=%s, sending SecurityResult", auth_str)
    107117        #Security Handshake, send SecurityResult Handshake
    108118        self._packet_parser = self._parse_security_result
    109         self.send(struct.pack("BBBB", 0, 0, 0, 0))
     119        self.send(struct.pack("!I", 0))
    110120        return 1
    111121
     122    def _parse_challenge(self, response):
     123        assert self._authenticator
     124        log("parse_challenge(%s)", binascii.hexlify(response))
     125        try:
     126            assert len(response)==16
     127            hex_response = binascii.hexlify(response)
     128            #log("padded password=%s", password)
     129            if self._authenticator.authenticate(hex_response):
     130                log("challenge authentication succeeded")
     131                self.send(struct.pack("!I", 0))
     132                self._packet_parser = self._parse_security_result
     133                return 16
     134            log.warn("Warning: authentication challenge response failure")
     135            log.warn(" password does not match")
     136        except Exception as e:
     137            log("parse_challenge(%s)", binascii.hexlify(response), exc_info=True)
     138            log.error("Error: authentication challenge failure:")
     139            log.error(" %s", e)
     140        def fail_challenge():
     141            self.send(struct.pack("!I", 1))
     142            self.close()
     143        self.timeout_add(1000, fail_challenge)
     144        return len(response)
     145
    112146    def _parse_security_result(self, packet):
    113         if packet!=b"\0":
    114             self._invalid_header(packet, "invalid security result")
    115             return 0
     147        self.share  = packet != b"\0"
     148        log("parse_security_result: sharing=%s, sending ClientInit", self.share)
    116149        #send ClientInit
    117150        self._packet_parser = self._parse_rfb
    118151        w, h, bpp, depth, bigendian, truecolor, rmax, gmax, bmax, rshift, bshift, gshift = self._get_rfb_pixelformat()
     
    126159            ptype = ord(packet[0])
    127160        except:
    128161            ptype = packet[0]
    129         packet_type = PACKET_TYPE.get(ptype)
     162        packet_type = RFBClientMessage.PACKET_TYPE_STR.get(ptype)
    130163        if not packet_type:
    131164            self.invalid("unknown RFB packet type: %#x" % ptype, packet)
    132165            return 0
    133         s = PACKET_STRUCT[ptype]        #ie: Struct("!BBBB")
     166        s = RFBClientMessage.PACKET_STRUCT.get(ptype)     #ie: Struct("!BBBB")
     167        if not s:
     168            self.invalid("RFB packet type '%s' is not supported" % packet_type, packet)
     169            return 0
    134170        if len(packet)<s.size:
    135171            return 0
    136172        size = s.size
     
    137173        values = list(s.unpack(packet[:size]))
    138174        values[0] = packet_type
    139175        #some packets require parsing extra data:
    140         if ptype==RFB_SETENCODINGS:
     176        if ptype==RFBClientMessage.SETENCODINGS:
    141177            N = values[2]
    142178            estruct = struct.Struct("!"+"i"*N)
    143179            size += estruct.size
     
    145181                return 0
    146182            encodings = estruct.unpack(packet[s.size:size])
    147183            values.append(encodings)
    148         elif ptype==RFB_CLIENTCUTTEXT:
     184        elif ptype==RFBClientMessage.CLIENTCUTTEXT:
    149185            l = values[4]
    150186            size += l
    151187            if len(packet)<size:
     
    153189            text = packet[s.size:size]
    154190            values.append(text)
    155191        self.input_packetcount += 1
    156         #log("RFB packet: %s", values)
     192        log("RFB packet: %s: %s", packet_type, values[1:])
    157193        #now trigger the callback:
    158194        self._process_packet_cb(self, values)
    159195        #return part of packet not consumed:
     
    160196        return size
    161197
    162198
    163     def wait_for_io_threads_exit(self, timeout=None):
    164         for t in (self._read_thread, self._write_thread):
    165             if t and t.isAlive():
    166                 t.join(timeout)
    167         exited = True
    168         cinfo = self._conn or "cleared connection"
    169         for t in (self._read_thread, self._write_thread):
    170             if t and t.isAlive():
    171                 log.warn("Warning: %s thread of %s is still alive (timeout=%s)", t.name, cinfo, timeout)
    172                 exited = False
    173         return exited
    174 
    175199    def __repr__(self):
    176200        return "RFBProtocol(%s)" % self._conn
    177201
     
    181205
    182206    def get_info(self, *_args):
    183207        info = {"protocol" : self._protocol_version}
    184         for t in (self._write_thread, self._read_thread):
    185             if t:
    186                 info.setdefault("thread", {})[t.name] = t.is_alive()
     208        for t in self.get_threads():
     209            info.setdefault("thread", {})[t.name] = t.is_alive()
    187210        return info
    188211
    189212
     
    193216                self._read_thread.start()
    194217        self.idle_add(start_network_read_thread)
    195218
     219
     220    def send_disconnect(self, *_args, **_kwargs):
     221        #no such packet in RFB, just close
     222        self.close()
     223
     224
    196225    def send(self, packet):
    197226        if self._closed:
    198             log("send(%s ...) connection is closed already, not sending", packet[0])
     227            log("connection is closed already, not sending packet")
    199228            return
    200         log("send(%s ...)", packet[0])
     229        log("send(%i bytes: %s..)", len(packet), binascii.hexlify(packet[:16]))
    201230        with self._write_lock:
    202231            if self._closed:
    203232                return
     
    255284    def _read_thread_loop(self):
    256285        self._io_thread_loop("read", self._read)
    257286    def _read(self):
    258         buf = self._conn.read(READ_BUFFER_SIZE)
     287        c = self._conn
     288        if not c:
     289            return None
     290        buf = c.read(READ_BUFFER_SIZE)
    259291        #log("read()=%s", repr_ellipsized(buf))
    260292        if not buf:
    261293            log("read thread: eof")
     
    312344        self.invalid(err, data)
    313345
    314346
    315     def flush_then_close(self, _last_packet, done_callback=None):
    316         """ Note: this is best effort only
    317             the packet may not get sent.
    318 
    319             We try to get the write lock,
    320             we try to wait for the write queue to flush
    321             we queue our last packet,
    322             we wait again for the queue to flush,
    323             then no matter what, we close the connection and stop the threads.
    324         """
    325         log("flush_then_close(%s) closed=%s", done_callback, self._closed)
    326         def done():
    327             log("flush_then_close: done, callback=%s", done_callback)
    328             if done_callback:
    329                 done_callback()
    330         if self._closed:
    331             log("flush_then_close: already closed")
    332             return done()
    333         def wait_for_queue(timeout=10):
    334             #IMPORTANT: if we are here, we have the write lock held!
    335             if not self._write_queue.empty():
    336                 #write queue still has stuff in it..
    337                 if timeout<=0:
    338                     log("flush_then_close: queue still busy, closing without sending the last packet")
    339                     self._write_lock.release()
    340                     self.close()
    341                     done()
    342                 else:
    343                     log("flush_then_close: still waiting for queue to flush")
    344                     self.timeout_add(100, wait_for_queue, timeout-1)
    345             else:
    346                 log("flush_then_close: queue is now empty, sending the last packet and closing")
    347                 def close_and_release():
    348                     log("flush_then_close: wait_for_packet_sent() close_and_release()")
    349                     self.close()
    350                     try:
    351                         self._write_lock.release()
    352                     except:
    353                         pass
    354                     done()
    355                 def wait_for_packet_sent():
    356                     log("flush_then_close: wait_for_packet_sent() queue.empty()=%s, closed=%s", self._write_queue.empty(), self._closed)
    357                     if self._write_queue.empty() or self._closed:
    358                         #it got sent, we're done!
    359                         close_and_release()
    360                         return False
    361                     return not self._closed     #run until we manage to close (here or via the timeout)
    362                 self.timeout_add(100, wait_for_packet_sent)
    363                 #just in case wait_for_packet_sent never fires:
    364                 self.timeout_add(5*1000, close_and_release)
    365 
    366         def wait_for_write_lock(timeout=100):
    367             if not self._write_lock.acquire(False):
    368                 if timeout<=0:
    369                     log("flush_then_close: timeout waiting for the write lock")
    370                     self.close()
    371                     done()
    372                 else:
    373                     log("flush_then_close: write lock is busy, will retry %s more times", timeout)
    374                     self.timeout_add(10, wait_for_write_lock, timeout-1)
    375             else:
    376                 log("flush_then_close: acquired the write lock")
    377                 #we have the write lock - we MUST free it!
    378                 wait_for_queue()
    379         #normal codepath:
    380         # -> wait_for_write_lock
    381         # -> wait_for_queue
    382         # -> wait_for_packet_sent
    383         # -> close_and_release
    384         log("flush_then_close: wait_for_write_lock()")
    385         wait_for_write_lock()
    386 
    387 
    388347    def close(self):
    389         log("Protocol.close() closed=%s, connection=%s", self._closed, self._conn)
     348        log("RFBProtocol.close() closed=%s, connection=%s", self._closed, self._conn)
    390349        if self._closed:
    391350            return
    392351        self._closed = True
     
    394353        c = self._conn
    395354        if c:
    396355            try:
    397                 log("Protocol.close() calling %s", c.close)
     356                log("RFBProtocol.close() calling %s", c.close)
    398357                c.close()
    399358            except:
    400359                log.error("error closing %s", self._conn, exc_info=True)
     
    401360            self._conn = None
    402361        self.terminate_queue_threads()
    403362        self.idle_add(self.clean)
    404         log("Protocol.close() done")
     363        log("RFBProtocol.close() done")
    405364
    406365    def clean(self):
    407366        #clear all references to ensure we can get garbage collected quickly:
     
    412371    def terminate_queue_threads(self):
    413372        log("terminate_queue_threads()")
    414373        #make all the queue based threads exit by adding the empty marker:
    415         exit_queue = Queue()
    416         for _ in range(10):     #just 2 should be enough!
    417             exit_queue.put(None)
    418         try:
    419             owq = self._write_queue
    420             self._write_queue = exit_queue
    421             #discard all elements in the old queue and push the None marker:
    422             try:
    423                 while owq.qsize()>0:
    424                     owq.read(False)
    425             except:
    426                 pass
    427             owq.put_nowait(None)
    428         except:
    429             pass
    430         try:
    431             orq = self._read_queue
    432             self._read_queue = exit_queue
    433             #discard all elements in the old queue and push the None marker:
    434             try:
    435                 while orq.qsize()>0:
    436                     orq.read(False)
    437             except:
    438                 pass
    439             orq.put_nowait(None)
    440         except:
    441             pass
     374        owq = self._write_queue
     375        self._write_queue = exit_queue()
     376        force_flush_queue(owq)
  • xpra/server/rfb/rfb_server.py

     
     1# coding=utf8
     2# This file is part of Xpra.
     3# Copyright (C) 2017 Antoine Martin <antoine@devloop.org.uk>
     4# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
     5# later version. See the file COPYING for details.
     6
     7
     8from xpra.util import nonl, csv
     9from xpra.os_util import POSIX
     10from xpra.server.rfb.rfb_const import RFBEncoding, RFB_KEYNAMES
     11from xpra.server.rfb.rfb_protocol import RFBProtocol
     12from xpra.server.rfb.rfb_source import RFBSource
     13 
     14from xpra.log import Logger
     15log = Logger("rfb")
     16
     17
     18"""
     19    Adds RFB packet handler to a server.
     20"""
     21class RFBServer(object):
     22
     23    def init(self):
     24        self.rfb_buttons = 0
     25        self.x11_keycodes_for_keysym = {}
     26        if POSIX:
     27            from xpra.x11.bindings.keyboard_bindings import X11KeyboardBindings #@UnresolvedImport
     28            X11Keyboard = X11KeyboardBindings()
     29            x11_keycodes = X11Keyboard.get_keycode_mappings()
     30            for keycode, keysyms in x11_keycodes.items():
     31                for keysym in keysyms:
     32                    self.x11_keycodes_for_keysym.setdefault(keysym, []).append(keycode)
     33            log("x11_keycodes_for_keysym=%s", self.x11_keycodes_for_keysym)
     34
     35
     36    def _get_rfb_desktop_model(self):
     37        models = self._window_to_id.keys()
     38        if len(models)!=1:
     39            log.error("RFB can only handle a single desktop window, found %i", len(self._window_to_id))
     40            return None
     41        return models[0]
     42
     43    def _get_rfb_desktop_wid(self):
     44        ids = self._window_to_id.values()
     45        if len(ids)!=1:
     46            log.error("RFB can only handle a single desktop window, found %i", len(self._window_to_id))
     47            return None
     48        return ids[0]
     49
     50
     51    def handle_rfb_connection(self, conn):
     52        model = self._get_rfb_desktop_model()
     53        if not model:
     54            conn.close()
     55            return
     56        def rfb_protocol_class(conn):
     57            auth = self.make_authenticator("rfb", "rfb", conn)
     58            return RFBProtocol(self, conn, auth, self.process_rfb_packet, self.get_rfb_pixelformat, self.session_name or "Xpra Server")
     59        p = self.do_make_protocol("rfb", conn, rfb_protocol_class)
     60        p.send_protocol_handshake()
     61
     62    def process_rfb_packet(self, proto, packet):
     63        #log("RFB packet: '%s'", nonl(packet))
     64        fn_name = "_process_rfb_%s" % packet[0]
     65        fn = getattr(self, fn_name, None)
     66        if not fn:
     67            log.warn("Warning: no RFB handler for %s", fn_name)
     68            return
     69        self.idle_add(fn, proto, packet)
     70
     71
     72    def get_rfb_pixelformat(self):
     73        model = self._get_rfb_desktop_model()
     74        w, h = model.get_dimensions()
     75        #w, h, bpp, depth, bigendian, truecolor, rmax, gmax, bmax, rshift, bshift, gshift
     76        return w, h, 32, 32, False, True, 255, 255, 255, 16, 8, 0
     77
     78    def _process_rfb_invalid(self, proto, packet):
     79        self.disconnect_protocol(proto, "invalid packet: %s" % (packet[1:]))
     80
     81    def _process_rfb_authenticated(self, proto, _packet):
     82        model = self._get_rfb_desktop_model()
     83        if not model:
     84            proto.close()
     85            return
     86        self.accept_protocol(proto)
     87        #use blocking sockets from now on:
     88        from xpra.net.bytestreams import set_socket_timeout
     89        set_socket_timeout(proto._conn, None)
     90        server_exit, share_count, disconnected = self.handle_sharing(proto, share=proto.share)
     91        log("rfb handle sharing: server exit=%s, share count=%s, disconnected=%s", server_exit, share_count, disconnected)
     92        if server_exit:
     93            return
     94        source = RFBSource(proto, self._get_rfb_desktop_model(), proto.share)
     95        self._server_sources[proto] = source
     96        w, h = model.get_dimensions()
     97        source.damage(self._window_to_id[model], model, 0, 0, w, h)
     98
     99    def _process_rfb_PointerEvent(self, _proto, packet):
     100        buttons, x, y = packet[1:4]
     101        wid = self._get_rfb_desktop_wid()
     102        self._move_pointer(wid, (x, y))
     103        if buttons!=self.rfb_buttons:
     104            #figure out which buttons have changed:
     105            for button in range(8):
     106                mask = 2**button
     107                if buttons & mask != self.rfb_buttons & mask:
     108                    pressed = bool(buttons & mask)
     109                    self.button_action(1+button, pressed, -1)
     110            self.rfb_buttons = buttons
     111
     112    def _process_rfb_KeyEvent(self, _proto, packet):
     113        pressed, _, _, key = packet[1:5]
     114        wid = self._get_rfb_desktop_wid()
     115        keyval = 0
     116        name = RFB_KEYNAMES.get(key) or chr(key)
     117        keycode = 0
     118        keycodes = self.x11_keycodes_for_keysym.get(name, 0)
     119        log("keycodes(%s)=%s", name, keycodes)
     120        if keycodes:
     121            keycode = keycodes[0]
     122            modifiers = []
     123            self._handle_key(wid, bool(pressed), name, keyval, keycode, modifiers)
     124
     125    def _process_rfb_SetEncodings(self, _proto, packet):
     126        n, encodings = packet[2:4]
     127        known_encodings = [RFBEncoding.ENCODING_STR.get(x) for x in encodings if x in RFBEncoding.ENCODING_STR]
     128        log("%i encodings: %s", n, csv(known_encodings))
     129        unknown_encodings = [x for x in encodings if x not in RFBEncoding.ENCODING_STR]
     130        if unknown_encodings:
     131            log("%i unknown encodings: %s", len(unknown_encodings), csv(unknown_encodings))
     132
     133    def _process_rfb_SetPixelFormat(self, _proto, packet):
     134        log("RFB: SetPixelFormat %s", packet)
     135        #w, h, bpp, depth, bigendian, truecolor, rmax, gmax, bmax, rshift, bshift, gshift = packet
     136
     137    def _process_rfb_FramebufferUpdateRequest(self, _proto, packet):
     138        #pressed, _, _, keycode = packet[1:5]
     139        inc, x, y, w, h = packet[1:6]
     140        if not inc:
     141            model = self._get_rfb_desktop_model()
     142            self._damage(model, x, y, w, h)
     143
     144    def _process_rfb_ClientCutText(self, _proto, packet):
     145        #l = packet[4]
     146        text = packet[5]
     147        log("got rfb clipboard text: %s", nonl(text))
  • xpra/server/rfb/rfb_source.py

     
     1# coding=utf8
     2# This file is part of Xpra.
     3# Copyright (C) 2017 Antoine Martin <antoine@devloop.org.uk>
     4# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
     5# later version. See the file COPYING for details.
     6
     7import struct
     8from threading import Event
     9
     10from xpra.os_util import memoryview_to_bytes
     11from xpra.log import Logger
     12log = Logger("rfb")
     13
     14
     15class RFBSource(object):
     16
     17    def __init__(self, protocol, desktop, share=False):
     18        self.protocol = protocol
     19        self.desktop = desktop
     20        self.close_event = Event()
     21        self.log_disconnect = True
     22        self.ui_client = True
     23        self.counter = 0
     24        self.share = share
     25        self.uuid = "todo: use protocol?"
     26
     27    def get_info(self):
     28        return {
     29            "protocol"  : "rfb",
     30            "uuid"      : self.uuid,
     31            "share"     : self.share,
     32            }
     33
     34    def get_window_info(self, _wids):
     35        return {}
     36
     37    def is_closed(self):
     38        return self.close_event.isSet()
     39
     40    def close(self):
     41        pass
     42
     43    def ping(self):
     44        pass
     45
     46    def keys_changed(self):
     47        pass
     48
     49
     50    def send_server_event(self, *_args):
     51        pass
     52
     53    def send_cursor(self):
     54        pass
     55
     56
     57    def damage(self, _wid, window, x, y, w, h, _options=None):
     58        from xpra.net.protocol import PACKET_JOIN_SIZE
     59        img = window.get_image(x, y, w, h)
     60        log("damage: %s", img)
     61        fbupdate = struct.pack("!BBH", 0, 0, 1)
     62        encoding = 0    #Raw
     63        rect = struct.pack("!HHHHi", x, y, w, h, encoding)
     64        if img.get_rowstride()!=w*4:
     65            img.restride(w*4)
     66        pixels = img.get_pixels()
     67        assert len(pixels)>=4*w*h
     68        pixels = pixels[:4*w*h]
     69        if len(pixels)<=PACKET_JOIN_SIZE:
     70            self.send(fbupdate+rect+memoryview_to_bytes(pixels))
     71        else:
     72            self.send(fbupdate+rect)
     73            self.send(pixels)
     74
     75    def send_clipboard(self, text):
     76        nocr = text.replace("\r", "")
     77        msg = struct.pack("!BBBBI", 3, 0, 0, 0, len(nocr))+nocr
     78        self.send(msg)
     79
     80    def bell(self, *_args):
     81        msg = struct.pack("!B", 2)
     82        self.send(msg)
     83
     84    def send(self, msg):
     85        p = self.protocol
     86        if p:
     87            p.send(msg)
  • xpra/server/server_base.py

     
    11001100        self.cleanup_protocol(proto)
    11011101
    11021102
    1103     def hello_oked(self, proto, packet, c, auth_caps):
    1104         if ServerCore.hello_oked(self, proto, packet, c, auth_caps):
    1105             #has been handled
    1106             return
    1107         if not c.boolget("steal", True) and self._server_sources:
    1108             self.disconnect_client(proto, SESSION_BUSY, "this session is already active")
    1109             return
    1110         if c.boolget("screenshot_request"):
    1111             self.send_screenshot(proto)
    1112             return
    1113         detach_request  = c.boolget("detach_request", False)
    1114         stop_request    = c.boolget("stop_request", False)
    1115         exit_request    = c.boolget("exit_request", False)
    1116         event_request   = c.boolget("event_request", False)
    1117         print_request   = c.boolget("print_request", False)
    1118         is_request = detach_request or stop_request or exit_request or event_request or print_request
    1119         if not is_request:
    1120             #"normal" connection, so log welcome message:
    1121             log.info("Handshake complete; enabling connection")
    1122         self.server_event("handshake-complete")
    1123 
    1124         # Things are okay, we accept this connection, and may disconnect previous one(s)
    1125         # (but only if this is going to be a UI session - control sessions can co-exist)
    1126         ui_client = c.boolget("ui_client", True)
     1103    def handle_sharing(self, proto, ui_client=True, detach_request=False, share=False):
     1104        server_exit = False
    11271105        share_count = 0
    11281106        disconnected = 0
    11291107        for p,ss in self._server_sources.items():
     
    11351113                if not self.sharing:
    11361114                    self.disconnect_client(p, NEW_CLIENT, "this session does not allow sharing")
    11371115                    disconnected += 1
    1138                 elif not c.boolget("share"):
     1116                elif not share:
    11391117                    self.disconnect_client(p, NEW_CLIENT, "the new client does not wish to share")
    11401118                    disconnected += 1
    11411119                elif not ss.share:
     
    11471125        #don't accept this connection if we're going to exit-with-client:
    11481126        if disconnected>0 and share_count==0 and self.exit_with_client:
    11491127            self.disconnect_client(proto, SERVER_EXIT, "last client has exited")
     1128            server_exit = True
     1129        return server_exit, share_count, disconnected
     1130
     1131    def do_hello_oked(self, proto, packet, c, auth_caps):
     1132        if ServerCore.do_hello_oked(self, proto, packet, c, auth_caps):
     1133            #has been handled
    11501134            return
     1135        if not c.boolget("steal", True) and self._server_sources:
     1136            self.disconnect_client(proto, SESSION_BUSY, "this session is already active")
     1137            return
     1138        if c.boolget("screenshot_request"):
     1139            self.send_screenshot(proto)
     1140            return
     1141        detach_request  = c.boolget("detach_request", False)
     1142        stop_request    = c.boolget("stop_request", False)
     1143        exit_request    = c.boolget("exit_request", False)
     1144        event_request   = c.boolget("event_request", False)
     1145        print_request   = c.boolget("print_request", False)
     1146        is_request = detach_request or stop_request or exit_request or event_request or print_request
     1147        if not is_request:
     1148            #"normal" connection, so log welcome message:
     1149            log.info("Handshake complete; enabling connection")
     1150        self.server_event("handshake-complete")
    11511151
     1152        # Things are okay, we accept this connection, and may disconnect previous one(s)
     1153        # (but only if this is going to be a UI session - control sessions can co-exist)
     1154        ui_client = c.boolget("ui_client", True)
     1155        share = c.boolget("share")
     1156        server_exit, share_count, disconnected = self.handle_sharing(proto, ui_client, detach_request, share)
     1157        if server_exit:
     1158            return
     1159
    11521160        if detach_request:
    11531161            self.disconnect_client(proto, DONE, "%i other clients have been disconnected" % disconnected)
    11541162            return
     
    33203328                handlers = self._authenticated_packet_handlers
    33213329                ui_handlers = self._authenticated_ui_packet_handlers
    33223330            else:
    3323                 handlers = {}
    3324                 ui_handlers = self._default_packet_handlers
     3331                handlers = self._default_packet_handlers
     3332                ui_handlers = {}
    33253333            handler = handlers.get(packet_type)
    33263334            if handler:
    33273335                netlog("process non-ui packet %s", packet_type)
  • xpra/server/server_core.py

     
    141141    def __init__(self):
    142142        log("ServerCore.__init__()")
    143143        self.start_time = monotonic_time()
    144         self.auth_class = None
    145         self.tcp_auth_class = None
    146         self.ws_auth_class = None
    147         self.wss_auth_class = None
    148         self.ssl_auth_class = None
    149         self.rfb_auth_class = None
    150         self.vsock_auth_class = None
     144        self.auth_classes = {}
    151145        self._when_ready = []
    152146        self.child_reaper = None
    153147        self.original_desktop_display = None
     
    157151        #networking bits:
    158152        self._socket_info = []
    159153        self._potential_protocols = []
     154        self._udp_listeners = []
     155        self._udp_protocols = {}
    160156        self._tcp_proxy_clients = []
    161157        self._tcp_proxy = ""
    162         self._ssl_wrap_socket = None
     158        self._ssl_wrap_server_socket = None
     159        self._ssl_wrap_client_socket = None
    163160        self._accept_timeout = SOCKET_TIMEOUT + 1
    164161        self.ssl_mode = None
    165162        self._html = False
     
    286283            self._tcp_proxy = False
    287284
    288285    def init_auth(self, opts):
    289         self.auth_class = self.get_auth_module("unix-domain", opts.auth, opts)
    290         self.tcp_auth_class = self.get_auth_module("tcp", opts.tcp_auth or opts.auth, opts)
    291         self.ws_auth_class = self.get_auth_module("ws", opts.ws_auth, opts)
    292         self.wss_auth_class = self.get_auth_module("wss", opts.wss_auth, opts)
    293         self.ssl_auth_class = self.get_auth_module("ssl", opts.ssl_auth or opts.tcp_auth or opts.auth, opts)
    294         self.rfb_auth_class = self.get_auth_module("rfb", opts.rfb_auth, opts)
    295         self.vsock_auth_class = self.get_auth_module("vsock", opts.vsock_auth, opts)
    296         authlog("init_auth(..) auth=%s, tcp auth=%s, ws auth=%s, wss auth=%s, ssl auth=%s, vsock auth=%s",
    297                 self.auth_class, self.tcp_auth_class, self.ws_auth_class, self.wss_auth_class, self.ssl_auth_class, self.vsock_auth_class)
     286        auth = self.get_auth_module("local-auth", opts.auth, opts)
     287        if WIN32:
     288            self.auth_classes["named-pipes"] = auth
     289        else:
     290            self.auth_classes["unix-domain"] = auth
     291        for x in ("tcp", "ws", "wss", "ssl", "rfb", "vsock", "udp"):
     292            opts_value = getattr(opts, "%s_auth" % x)
     293            self.auth_classes[x] = self.get_auth_module(x, opts_value, opts)
     294        authlog("init_auth(..) auth=%s", self.auth_classes)
    298295
    299296    def get_auth_module(self, socket_type, auth_str, opts):
    300297        authlog("get_auth_module(%s, %s, {..})", socket_type, auth_str)
     
    383380        self._default_packet_handlers = {
    384381            "hello":                                self._process_hello,
    385382            "disconnect":                           self._process_disconnect,
     383            "udp-control":                          self._process_udp_control,
    386384            Protocol.CONNECTION_LOST:               self._process_connection_lost,
    387385            Protocol.GIBBERISH:                     self._process_gibberish,
    388386            Protocol.INVALID:                       self._process_invalid,
     
    538536        self.do_cleanup()
    539537        self.cleanup_protocols(protocols, reason, True)
    540538        self._potential_protocols = []
     539        self.cleanup_udp_listeners()
    541540
    542541    def do_cleanup(self):
    543542        #allow just a bit of time for the protocol packet flush
     
    544543        sleep(0.1)
    545544
    546545
     546    def cleanup_udp_listeners(self):
     547        for udpl in self._udp_listeners:
     548            udpl.close()
     549        self._udp_listeners = []
     550
    547551    def cleanup_all_protocols(self, reason):
    548552        protocols = self.get_all_protocols()
    549553        self.cleanup_protocols(protocols, reason)
     
    569573            #named pipe listener uses a thread:
    570574            sock.new_connection_cb = self._new_connection
    571575            sock.start()
     576        elif socktype in ("udp", "dtls"):
     577            #socket_info = self.socket_info.get(sock)
     578            from xpra.net.udp_protocol import UDPListener
     579            udpl = UDPListener(sock, self.process_udp_packet)
     580            self._udp_listeners.append(udpl)
    572581        else:
    573582            from xpra.gtk_common.gobject_compat import import_glib
    574583            glib = import_glib()
     
    673682        peek_data, line1 = self.peek_connection(conn)
    674683
    675684        def ssl_wrap():
    676             ssl_sock = self._ssl_wrap_socket(sock)
     685            ssl_sock = self._ssl_wrap_server_socket(sock)
    677686            netlog("ssl wrapped socket(%s)=%s", sock, ssl_sock)
    678687            if ssl_sock is None:
    679688                #None means EOF! (we don't want to import ssl bits here)
     
    729738            self.handle_rfb_connection(conn)
    730739            return
    731740
    732         elif socktype=="tcp" and peek_data and (self._html or self._tcp_proxy or self._ssl_wrap_socket):
     741        elif peek_data and (
     742            (socktype=="tcp" and  (self._html or self._tcp_proxy or self._ssl_wrap_server_socket)) or
     743            (socktype=="udp" and self._ssl_wrap_server_socket and self._ssl_wrap_client_socket)
     744            ):
    733745            #see if the packet data is actually xpra or something else
    734746            #that we need to handle via a tcp proxy, ssl wrapper or the websockify adapter:
    735747            try:
     
    763775        netlog("make_protocol(%s, %s)", socktype, conn)
    764776        socktype = socktype.lower()
    765777        protocol = protocol_class(conn)
     778        protocol.socket_type = socktype
    766779        self._potential_protocols.append(protocol)
    767780        protocol.challenge_sent = False
    768781        protocol.authenticator = None
     
    769782        protocol.encryption = None
    770783        protocol.keyfile = None
    771784        if socktype=="tcp":
    772             protocol.auth_class = self.tcp_auth_class
     785            #special case for legacy encryption code:
    773786            protocol.encryption = self.tcp_encryption
    774787            protocol.keyfile = self.tcp_encryption_keyfile
    775788            if protocol.encryption and ENCRYPT_FIRST_PACKET:
     
    776789                authlog("encryption=%s, keyfile=%s", protocol.encryption, protocol.keyfile)
    777790                password = self.get_encryption_key(None, protocol.keyfile)
    778791                protocol.set_cipher_in(protocol.encryption, DEFAULT_IV, password, DEFAULT_SALT, DEFAULT_ITERATIONS, INITIAL_PADDING)
    779         elif socktype=="ssl":
    780             protocol.auth_class = self.ssl_auth_class
    781         elif socktype=="ws":
    782             protocol.auth_class = self.ws_auth_class
    783         elif socktype=="wss":
    784             protocol.auth_class = self.wss_auth_class
    785         elif socktype=="rfb":
    786             protocol.auth_class = self.rfb_auth_class
    787         elif socktype=="vsock":
    788             protocol.auth_class = self.vsock_auth_class
    789         else:
    790             protocol.auth_class = self.auth_class
    791         protocol.socket_type = socktype
    792792        protocol.invalid_header = self.invalid_header
    793         authlog("socktype=%s, auth class=%s, encryption=%s, keyfile=%s", socktype, protocol.auth_class, protocol.encryption, protocol.keyfile)
     793        authlog("socktype=%s, encryption=%s, keyfile=%s", socktype, protocol.encryption, protocol.keyfile)
    794794        protocol.start()
    795795        self.timeout_add(self._accept_timeout*1000, self.verify_connection_accepted, protocol)
    796796        return protocol
     
    811811            #xpra packet header, no need to wrap this connection
    812812            return True, conn, peek_data
    813813        frominfo = pretty_socket(conn.remote)
    814         if self._ssl_wrap_socket and peek_data[0] in (chr(0x16), 0x16):
    815             socktype = "SSL"
     814        if self._ssl_wrap_server_socket and peek_data[0] in (chr(0x16), 0x16):
     815            socktype = {
     816                "udp"   : "dtls",
     817                "tcp"   : "SSL",
     818                }[socktype]
    816819            sock, sockname, address, target = conn._socket, conn.local, conn.remote, conn.target
    817             sock = self._ssl_wrap_socket(sock)
     820            sock = self._ssl_wrap_server_socket(sock)
    818821            if sock is None:
    819822                #None means EOF! (we don't want to import ssl bits here)
    820823                netlog("ignoring SSL EOF error")
     
    850853        return True, conn, peek_data
    851854
    852855    def invalid_header(self, proto, data, msg=""):
    853         netlog("invalid_header(%s, %s bytes: '%s', %s) input_packetcount=%s, tcp_proxy=%s, html=%s, ssl=%s", proto, len(data or ""), msg, repr_ellipsized(data), proto.input_packetcount, self._tcp_proxy, self._html, bool(self._ssl_wrap_socket))
     856        netlog("invalid_header(%s, %s bytes: '%s', %s) input_packetcount=%s, tcp_proxy=%s, html=%s, ssl=%s",
     857               proto, len(data or ""), msg, repr_ellipsized(data), proto.input_packetcount, self._tcp_proxy, self._html, bool(self._ssl_wrap_server_socket))
    854858        err = "invalid packet format, %s" % self.guess_header_protocol(data)
    855859        proto.gibberish(err, data)
    856860
     
    9991003            log.error("connection timedout: %s", protocol)
    10001004            self.send_disconnect(protocol, LOGIN_TIMEOUT)
    10011005
    1002     def send_disconnect(self, proto, reason, *extra):
    1003         netlog("send_disconnect(%s, %s, %s)", proto, reason, extra)
     1006    def send_disconnect(self, proto, *reasons):
     1007        netlog("send_disconnect(%s, %s)", proto, reasons)
    10041008        if proto._closed:
    10051009            return
    1006         proto.send_now(["disconnect", reason]+list(extra))
     1010        proto.send_disconnect(*reasons)
    10071011        self.timeout_add(1000, self.force_disconnect, proto)
    10081012
    10091013    def force_disconnect(self, proto):
     
    10151019        if protocol and not protocol._closed:
    10161020            self.disconnect_protocol(protocol, reason, *extra)
    10171021
    1018     def disconnect_protocol(self, protocol, reason, *extra):
    1019         netlog("disconnect_protocol(%s, %s, %s)", protocol, reason, extra)
    1020         i = str(reason)
    1021         if extra:
    1022             i += " (%s)" % extra
     1022    def disconnect_protocol(self, protocol, *reasons):
     1023        netlog("disconnect_protocol(%s, %s)", protocol, reasons)
     1024        i = str(reasons[0])
     1025        if len(reasons)>1:
     1026            i += " (%s)" % csv(reasons[1:])
    10231027        try:
    10241028            proto_info = " %s" % protocol._conn.get_info().get("endpoint")
    10251029        except:
     
    10261030            proto_info = " %s" % protocol
    10271031        self._log_disconnect(protocol, "Disconnecting client%s:", proto_info)
    10281032        self._log_disconnect(protocol, " %s", i)
    1029         protocol.flush_then_close(["disconnect", reason]+list(extra))
     1033        protocol.send_disconnect(reasons)
    10301034
    10311035
    10321036    def _process_disconnect(self, proto, packet):
     
    10381042        self._log_disconnect(proto, "client%s has requested disconnection: %s", proto_info, info)
    10391043        self.disconnect_protocol(proto, CLIENT_REQUEST)
    10401044
    1041     def _log_disconnect(self, proto, *args):
     1045    def _log_disconnect(self, _proto, *args):
    10421046        netlog.info(*args)
    10431047
    1044     def _disconnect_proto_info(self, proto):
     1048    def _disconnect_proto_info(self, _proto):
    10451049        #overriden in server_base in case there is more than one protocol
    10461050        return ""
    10471051
     
    10511055            if not proto._closed:
    10521056                self._log_disconnect(proto, "Connection lost")
    10531057            self._potential_protocols.remove(proto)
     1058        if proto.uuid in self._udp_protocols:
     1059            del self._udp_protocols[proto.uuid]
    10541060
    10551061    def _process_gibberish(self, proto, packet):
    10561062        (_, message, data) = packet
     
    10971103                #call from UI thread:
    10981104                self.idle_add(self.handle_command_request, proto, *command_req)
    10991105                return
    1100             #continue processing hello packet:
    1101             try:
    1102                 if SIMULATE_SERVER_HELLO_ERROR:
    1103                     raise Exception("Simulating a server error")
    1104                 self.hello_oked(proto, packet, c, auth_caps)
    1105             except ClientException as e:
    1106                 log.error("Error setting up new connection for")
    1107                 log.error(" %s:", proto)
    1108                 log.error(" %s", e)
    1109                 self.disconnect_client(proto, SERVER_ERROR, str(e))
    1110             except Exception as e:
    1111                 #log exception but don't disclose internal details to the client
    1112                 log.error("server error processing new connection from %s: %s", proto, e, exc_info=True)
    1113                 self.disconnect_client(proto, SERVER_ERROR, "error accepting new connection")
     1106            #continue processing hello packet in UI thread:
     1107            self.idle_add(self.hello_oked, proto, packet, c, auth_caps)
    11141108
     1109    def make_authenticator(self, socktype, username, conn):
     1110        authlog("make_authenticator%s", (socktype, username, conn))
     1111        authenticator = None
     1112        auth_class = self.auth_classes[socktype]
     1113        if auth_class:
     1114            authlog("creating authenticator %s for %s, with username=%s, connection=%s", auth_class, socktype, username, conn)
     1115            auth, aclass, options = auth_class
     1116            opts = dict(options)
     1117            opts["connection"] = conn
     1118            authenticator = aclass(username, **opts)
     1119            authlog("authenticator: %s(%s, %s)=%s", auth, username, opts, authenticator)
     1120        return authenticator
    11151121
    11161122    def verify_hello(self, proto, c):
    11171123        remote_version = c.strget("version")
     
    11191125        if verr is not None:
    11201126            self.disconnect_client(proto, VERSION_ERROR, "incompatible version: %s" % verr)
    11211127            proto.close()
    1122             return  False
     1128            return False
    11231129
    11241130        def auth_failed(msg):
    11251131            authlog.error("Error: authentication failed")
     
    11271133            self.timeout_add(1000, self.disconnect_client, proto, msg)
    11281134
    11291135        #authenticator:
    1130         username = c.strget("username")
    1131         if proto.authenticator is None and proto.auth_class:
    1132             authlog("creating authenticator %s with username=%s", proto.auth_class, username)
     1136        if not proto.authenticator:
     1137            username = c.strget("username")
    11331138            try:
    1134                 auth, aclass, options = proto.auth_class
    1135                 opts = dict(options)
    1136                 opts["connection"] = proto._conn
    1137                 ainstance = aclass(username, **opts)
    1138                 proto.authenticator = ainstance
    1139                 authlog("authenticator: %s(%s, %s)=%s", auth, username, opts, ainstance)
     1139                proto.authenticator = self.make_authenticator(proto.socket_type, username, proto._conn)
    11401140            except Exception as e:
    1141                 authlog("instantiating authenticator for %s", proto, exc_info=True)
    1142                 authlog.error("Error instantiating %s:", proto.auth_class)
     1141                authlog("instantiating authenticator for %s", proto.socket_type, exc_info=True)
     1142                authlog.error("Error instantiating authenticator for %s:", proto.socket_type)
    11431143                authlog.error(" %s", e)
    11441144                auth_failed("authentication failed")
    11451145                return False
     
    12471247        return v
    12481248
    12491249    def hello_oked(self, proto, packet, c, auth_caps):
     1250        try:
     1251            if SIMULATE_SERVER_HELLO_ERROR:
     1252                raise Exception("Simulating a server error")
     1253            self.do_hello_oked(proto, packet, c, auth_caps)
     1254        except ClientException as e:
     1255            log.error("Error setting up new connection for")
     1256            log.error(" %s:", proto)
     1257            log.error(" %s", e)
     1258            self.disconnect_client(proto, SERVER_ERROR, str(e))
     1259        except Exception as e:
     1260            #log exception but don't disclose internal details to the client
     1261            log.error("server error processing new connection from %s: %s", proto, e, exc_info=True)
     1262            self.disconnect_client(proto, SERVER_ERROR, "error accepting new connection")
     1263
     1264    def do_hello_oked(self, proto, _packet, c, _auth_caps):
    12501265        ctr = c.strget("connect_test_request")
    12511266        if ctr:
    12521267            response = {"connect_test_response" : ctr}
     
    14411456        for socktype, _, info in self._socket_info:
    14421457            if info:
    14431458                si.setdefault(socktype, {}).setdefault("listeners", []).append(info)
    1444         for socktype, auth_class in {
    1445                                      "tcp"          : self.tcp_auth_class,
    1446                                      "ssl"          : self.ssl_auth_class,
    1447                                      "unix-domain"  : self.auth_class,
    1448                                      "vsock"        : self.vsock_auth_class,
    1449                                      }.items():
     1459        for socktype, auth_class in self.auth_classes.items():
    14501460            if auth_class:
    14511461                si.setdefault(socktype, {})["authenticator"] = auth_class[0], auth_class[2]
    14521462        return si
     
    14711481        except:
    14721482            netlog.error("Unhandled error while processing a '%s' packet from peer using %s", packet_type, handler, exc_info=True)
    14731483
     1484
    14741485    def handle_rfb_connection(self, conn):
    14751486        log.error("Error: RFB protocol is not supported by this server")
    14761487        conn.close()
     1488
     1489
     1490    def _process_udp_control(self, proto, packet):
     1491        proto.process_control(*packet[1:])
     1492
     1493    def process_udp_packet(self, udp_listener, uuid, seqno, synchronous, chunk, chunks, data, bfrom):
     1494        #log.info("process_udp_packet%s", (udp_listener, uuid, seqno, synchronous, chunk, chunks, len(data), bfrom))
     1495        protocol = self._udp_protocols.get(uuid)
     1496        if not protocol:
     1497            from xpra.net.udp_protocol import UDPServerProtocol, UDPSocketConnection
     1498            def udp_protocol_class(conn):
     1499                protocol = UDPServerProtocol(self, conn, self.process_packet)
     1500                protocol.uuid = uuid
     1501                protocol.large_packets.append("info-response")
     1502                protocol.receive_aliases.update(self._aliases)
     1503                return protocol
     1504            socktype = "udp"        #breaks dtls...
     1505            host, port = bfrom
     1506            sock = udp_listener._socket
     1507            sockname = sock.getsockname()
     1508            conn = UDPSocketConnection(sock, sockname, (host, port), (host, port), socktype)
     1509            conn.timeout = SOCKET_TIMEOUT
     1510            protocol = self.do_make_protocol(socktype, conn, udp_protocol_class)
     1511            self._udp_protocols[uuid] = protocol
     1512        else:
     1513            #update remote address in case the client is roaming:
     1514            protocol._conn.remote = bfrom
     1515        #assert packetsize==datalen, "expected datalen=packetsize, but got %i!=%i" % (datalen, packetsize)
     1516        #assert len(data)==datalen, "expected %i bytes but got %i" % (datalen, len(data))
     1517        protocol.process_udp_data(uuid, seqno, synchronous, chunk, chunks, data, bfrom)
  • xpra/server/shadow/shadow_server_base.py

     
    1212from xpra.net.compression import Compressed
    1313from xpra.server.window.batch_config import DamageBatchConfig
    1414from xpra.server.shadow.root_window_model import RootWindowModel
     15from xpra.server.rfb.rfb_server import RFBServer
    1516from xpra.util import envint, DONE
    1617
    1718REFRESH_DELAY = envint("XPRA_SHADOW_REFRESH_DELAY", 50)
    1819
    1920
    20 class ShadowServerBase(object):
     21class ShadowServerBase(RFBServer):
    2122
    2223    def __init__(self, root_window):
    2324        self.root = root_window
     
    2930        self.timer = None
    3031        DamageBatchConfig.ALWAYS = True             #always batch
    3132        DamageBatchConfig.MIN_DELAY = 50            #never lower than 50ms
     33        RFBServer.init(self)
    3234
    3335    def cleanup(self):
    3436        self.stop_refresh()
     
    5254        else:
    5355            log.info(" on display of size %ix%i", w, h)
    5456
    55     def make_hello(self, source):
     57    def make_hello(self, _source):
    5658        return {"shadow" : True}
    5759
    58     def get_info(self, proto=None):
     60    def get_info(self, _proto=None):
    5961        if self.root_window_model:
    6062            return {"root-window" : self.root_window_model.get_info()}
    6163        return {}
    6264
    6365
    64     def get_window_position(self, window):
     66    def get_window_position(self, _window):
    6567        #we export the whole desktop as a window:
    6668        return 0, 0
    6769
     
    113115
    114116    ############################################################################
    115117
    116     def sanity_checks(self, proto, c):
     118    def sanity_checks(self, _proto, c):
    117119        server_uuid = c.strget("server_uuid")
    118120        if server_uuid:
    119121            if server_uuid==self.uuid:
     
    144146        """ don't override the existing desktop """
    145147        pass
    146148
    147     def set_keymap(self, server_source, force=False):
     149    def set_keymap(self, server_source, _force=False):
    148150        log.info("shadow server: setting default keymap translation")
    149151        self.keyboard_config = server_source.set_default_keymap()
    150152
  • xpra/server/socket_util.py

     
    108108        log("create_tcp_socket%s", (host, iport), exc_info=True)
    109109        raise InitException("failed to setup %s socket on %s:%s %s" % (socktype, host, iport, e))
    110110    def cleanup_tcp_socket():
    111         log.info("closing %s socket %s:%s", socktype, host, iport)
     111        log.info("closing %s socket %s:%s", socktype.lower(), host, iport)
    112112        try:
    113113            tcp_socket.close()
    114114        except:
     
    117117    log("%s: %s:%s : %s", socktype, host, iport, socket)
    118118    return socktype, tcp_socket, (host, iport)
    119119
     120def create_udp_socket(host, iport):
     121    if host.find(":")<0:
     122        listener = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
     123        sockaddr = (host, iport)
     124    else:
     125        assert socket.has_ipv6, "specified an IPv6 address but this is not supported"
     126        res = socket.getaddrinfo(host, iport, socket.AF_INET6, socket.SOCK_DGRAM)
     127        listener = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
     128        sockaddr = res[0][-1]
     129    listener.bind(sockaddr)
     130    return listener
    120131
    121 def parse_bind_tcp(bind_tcp):
    122     tcp_sockets = set()
    123     if bind_tcp:
    124         for spec in bind_tcp:
     132def setup_udp_socket(host, iport, socktype="udp"):
     133    from xpra.log import Logger
     134    log = Logger("network")
     135    try:
     136        udp_socket = create_udp_socket(host, iport)
     137    except Exception as e:
     138        log("create_udp_socket%s", (host, iport), exc_info=True)
     139        raise InitException("failed to setup %s socket on %s:%s %s" % (socktype, host, iport, e))
     140    def cleanup_udp_socket():
     141        log.info("closing %s socket %s:%s", socktype, host, iport)
     142        try:
     143            udp_socket.close()
     144        except:
     145            pass
     146    add_cleanup(cleanup_udp_socket)
     147    log("%s: %s:%s : %s", socktype, host, iport, socket)
     148    return socktype, udp_socket, (host, iport)
     149
     150
     151def parse_bind_ip(bind_ip):
     152    ip_sockets = set()
     153    if bind_ip:
     154        for spec in bind_ip:
    125155            if ":" not in spec:
    126                 raise InitException("TCP port must be specified as [HOST]:PORT")
     156                raise InitException("port must be specified as [HOST]:PORT")
    127157            host, port = spec.rsplit(":", 1)
    128158            if host == "":
    129159                host = "127.0.0.1"
     
    135165                    assert iport>0 and iport<2**16
    136166                except:
    137167                    raise InitException("invalid port number: %s" % port)
    138             tcp_sockets.add((host, iport))
    139     return tcp_sockets
     168            ip_sockets.add((host, iport))
     169    return ip_sockets
    140170
    141171def setup_vsock_socket(cid, iport):
    142172    from xpra.log import Logger
  • xpra/server/source.py

     
    11201120        self.update_av_sync_delay_total()
    11211121
    11221122    def new_sound_buffer(self, sound_source, data, metadata, packet_metadata=[]):
    1123         soundlog("new_sound_buffer(%s, %s, %s, %s) suspended=%s",
    1124                  sound_source, len(data or []), metadata, [len(x) for x in packet_metadata], self.suspended)
     1123        soundlog("new_sound_buffer(%s, %s, %s, %s) info=%s, suspended=%s",
     1124                 sound_source, len(data or []), metadata, [len(x) for x in packet_metadata], sound_source.info, self.suspended)
    11251125        if self.sound_source!=sound_source or self.is_closed():
    11261126            soundlog("sound buffer dropped: from old source or closed")
    11271127            return
     
    11371137            else:
    11381138                #the packet metadata is compressed already:
    11391139                packet_metadata = Compressed("packet metadata", packet_metadata, can_inline=True)
    1140         self.send_sound_data(sound_source, data, metadata, packet_metadata)
     1140        #don't drop the first 10 buffers
     1141        can_drop_packet = (sound_source.info or {}).get("buffer_count", 0)>10
     1142        self.send_sound_data(sound_source, data, metadata, packet_metadata, can_drop_packet)
    11411143
    1142     def send_sound_data(self, sound_source, data, metadata={}, packet_metadata=()):
     1144    def send_sound_data(self, sound_source, data, metadata={}, packet_metadata=(), can_drop_packet=False):
    11431145        packet_data = [sound_source.codec, Compressed(sound_source.codec, data), metadata]
    11441146        if packet_metadata:
    11451147            assert self.sound_bundle_metadata
    11461148            packet_data.append(packet_metadata)
    1147         if sound_source.sequence>=0:
    1148             metadata["sequence"] = sound_source.sequence
    1149         self.send("sound-data", *packet_data)
     1149        sequence = sound_source.sequence
     1150        if sequence>=0:
     1151            metadata["sequence"] = sequence
     1152        fail_cb = None
     1153        if can_drop_packet:
     1154            def sound_data_fail_cb():
     1155                #ideally we would tell gstreamer to send an audio "key frame"
     1156                #or synchronization point to ensure the stream recovers
     1157                soundlog("a sound data buffer was not received and will not be resent")
     1158            fail_cb = sound_data_fail_cb
     1159        self._send(fail_cb, False, "sound-data", *packet_data)
    11501160
    11511161    def stop_receiving_sound(self):
    11521162        ss = self.sound_sink
     
    13951405            return
    13961406        if self.mouse_last_position!=(x, y, rx, ry):
    13971407            self.mouse_last_position = (x, y, rx, ry)
    1398             self.send("pointer-position", wid, x, y, rx, ry)
     1408            self.send_async("pointer-position", wid, x, y, rx, ry)
    13991409
    14001410#
    14011411# Functions for interacting with the network layer:
     
    14021412#
    14031413    def next_packet(self):
    14041414        """ Called by protocol.py when it is ready to send the next packet """
    1405         packet, start_send_cb, end_send_cb, have_more = None, None, None, False
     1415        packet, start_send_cb, end_send_cb, fail_cb, synchronous, have_more = None, None, None, None, True, False
    14061416        if not self.is_closed():
    14071417            if len(self.ordinary_packets)>0:
    1408                 packet = self.ordinary_packets.pop(0)
     1418                packet, synchronous, fail_cb = self.ordinary_packets.pop(0)
    14091419            elif len(self.packet_queue)>0:
    1410                 packet, _, _, start_send_cb, end_send_cb = self.packet_queue.popleft()
     1420                packet, _, _, start_send_cb, end_send_cb, fail_cb = self.packet_queue.popleft()
    14111421            have_more = packet is not None and (len(self.ordinary_packets)>0 or len(self.packet_queue)>0)
    1412         return packet, start_send_cb, end_send_cb, have_more
     1422        return packet, start_send_cb, end_send_cb, fail_cb, synchronous, have_more
    14131423
    14141424    def send(self, *parts):
    14151425        """ This method queues non-damage packets (higher priority) """
    1416         self.ordinary_packets.append(parts)
     1426        self._send(None, True, *parts)
     1427
     1428    def send_async(self, *parts):
     1429        self._send(None, False, *parts)
     1430
     1431    def _send(self, fail_cb=None, synchronous=True, *parts):
     1432        """ This method queues non-damage packets (higher priority) """
     1433        #log.info("_send%s", (fail_cb, synchronous, parts))
    14171434        p = self.protocol
    14181435        if p:
     1436            self.ordinary_packets.append((parts, synchronous, fail_cb))
    14191437            p.source_has_more()
    14201438
     1439
    14211440#
    14221441# Functions used by the server to request something
    14231442# (window events, stats, user requests, etc)
     
    15101529        if self.last_ping_echoed_time>0:
    15111530            lpe = int(monotonic_time()*1000-self.last_ping_echoed_time)
    15121531        info = {
     1532                "protocol"          : "xpra",
    15131533                "version"           : self.client_version or "unknown",
    15141534                "revision"          : self.client_revision or "unknown",
    15151535                "platform_name"     : platform_name(self.client_platform, self.client_release),
     
    16911711            v = notypedict(info)
    16921712        else:
    16931713            v = flatten_dict(info)
    1694         self.send("info-response", v)
     1714        self.send_async("info-response", v)
    16951715
    16961716
    16971717    def send_server_event(self, *args):
     
    17021722    def send_clipboard_enabled(self, reason=""):
    17031723        if not self.hello_sent:
    17041724            return
    1705         self.send("set-clipboard-enabled", self.clipboard_enabled, reason)
     1725        self.send_async("set-clipboard-enabled", self.clipboard_enabled, reason)
    17061726
    17071727    def send_clipboard_progress(self, count):
    17081728        if not self.clipboard_notifications or not self.hello_sent:
     
    18231843    def bell(self, wid, device, percent, pitch, duration, bell_class, bell_id, bell_name):
    18241844        if not self.send_bell or self.suspended or not self.hello_sent:
    18251845            return
    1826         self.send("bell", wid, device, percent, pitch, duration, bell_class, bell_id, bell_name)
     1846        self.send_async("bell", wid, device, percent, pitch, duration, bell_class, bell_id, bell_name)
    18271847
    18281848    def notify(self, dbus_id, nid, app_name, replaces_nid, app_icon, summary, body, expire_timeout):
    18291849        if not self.send_notifications:
     
    18331853            notifylog("client %s is suspended, notification not sent", self)
    18341854            return False
    18351855        if self.hello_sent:
    1836             self.send("notify_show", dbus_id, int(nid), str(app_name), int(replaces_nid), str(app_icon), str(summary), str(body), int(expire_timeout))
     1856            self.send_async("notify_show", dbus_id, int(nid), str(app_name), int(replaces_nid), str(app_icon), str(summary), str(body), int(expire_timeout))
    18371857        return True
    18381858
    18391859    def notify_close(self, nid):
     
    18471867
    18481868    def send_webcam_ack(self, device, frame, *args):
    18491869        if self.hello_sent:
    1850             self.send("webcam-ack", device, frame, *args)
     1870            self.send_async("webcam-ack", device, frame, *args)
    18511871
    18521872    def send_webcam_stop(self, device, message):
    18531873        if self.hello_sent:
    1854             self.send("webcam-stop", device, message)
     1874            self.send_async("webcam-stop", device, message)
    18551875
    18561876
    18571877    def set_printers(self, printers, password_file, auth, encryption, encryption_keyfile):
     
    19721992        #NOTE: all ping time/echo time/load avg values are in milliseconds
    19731993        now_ms = int(1000*monotonic_time())
    19741994        log("sending ping to %s with time=%s", self.protocol, now_ms)
    1975         self.send("ping", now_ms)
     1995        self.send_async("ping", now_ms)
    19761996        timeout = 60
    19771997        def check_echo_timeout():
    19781998            if self.last_ping_echoed_time<now_ms and not self.is_closed():
     
    19922012            cl = int(1000.0*cl)
    19932013        else:
    19942014            cl = -1
    1995         self.send("ping_echo", time_to_echo, l1, l2, l3, cl)
     2015        self.send_async("ping_echo", time_to_echo, l1, l2, l3, cl)
    19962016        #if the client is pinging us, ping it too:
    19972017        self.timeout_add(500, self.ping)
    19982018
     
    20192039
    20202040    def show_desktop(self, show):
    20212041        if self.show_desktop_allowed and self.hello_sent:
    2022             self.send("show-desktop", show)
     2042            self.send_async("show-desktop", show)
    20232043
    20242044    def initiate_moveresize(self, wid, window, x_root, y_root, direction, button, source_indication):
    20252045        if not self.can_send_window(window) or not self.window_initiate_moveresize:
     
    20872107        metadata = {}
    20882108        for propname in list(window.get_property_names()):
    20892109            metadata.update(self._make_metadata(window, propname))
    2090         self.send("new-tray", wid, w, h, metadata)
     2110        self.send_async("new-tray", wid, w, h, metadata)
    20912111
    20922112    def new_window(self, ptype, wid, window, x, y, w, h, client_properties):
    20932113        if not self.can_send_window(window):
     
    21052125                metalog("make_metadata(%s, %s, %s)=%s", wid, window, prop, v)
    21062126            metadata.update(v)
    21072127        log("new_window(%s, %s, %s, %s, %s, %s, %s, %s) metadata(%s)=%s", ptype, window, wid, x, y, w, h, client_properties, send_props, metadata)
    2108         self.send(ptype, wid, x, y, w, h, metadata, client_properties or {})
     2128        self.send_async(ptype, wid, x, y, w, h, metadata, client_properties or {})
    21092129        if send_raw_icon:
    21102130            self.send_window_icon(wid, window)
    21112131
     
    21552175    def raise_window(self, wid, window):
    21562176        if not self.can_send_window(window):
    21572177            return
    2158         self.send("raise-window", wid)
     2178        self.send_async("raise-window", wid)
    21592179
    21602180    def remove_window(self, wid, window):
    21612181        """ The given window is gone, ensure we free all the related resources """
     
    22942314        self.statistics.compression_work_qsizes.append((monotonic_time(), self.encode_work_queue.qsize()))
    22952315        self.encode_work_queue.put(fn_and_args)
    22962316
    2297     def queue_packet(self, packet, wid=0, pixels=0, start_send_cb=None, end_send_cb=None):
     2317    def queue_packet(self, packet, wid=0, pixels=0, start_send_cb=None, end_send_cb=None, fail_cb=None):
    22982318        """
    22992319            Add a new 'draw' packet to the 'packet_queue'.
    23002320            Note: this code runs in the non-ui thread
     
    23032323        self.statistics.packet_qsizes.append((now, len(self.packet_queue)))
    23042324        if wid>0:
    23052325            self.statistics.damage_packet_qpixels.append((now, wid, sum(x[2] for x in list(self.packet_queue) if x[1]==wid)))
    2306         self.packet_queue.append((packet, wid, pixels, start_send_cb, end_send_cb))
     2326        self.packet_queue.append((packet, wid, pixels, start_send_cb, end_send_cb, fail_cb))
    23072327        p = self.protocol
    23082328        if p:
    23092329            p.source_has_more()
  • xpra/server/window/window_source.py

     
    15001500        """
    15011501        self.statistics.encoding_pending[sequence] = (damage_time, w, h)
    15021502        try:
    1503             packet = self.make_data_packet(damage_time, process_damage_time, image, coding, sequence, options, flush)
     1503            packet = self.make_data_packet(image, coding, sequence, options, flush)
    15041504        finally:
    15051505            self.free_image_wrapper(image)
    15061506            del image
     
    17261726            now = monotonic_time()
    17271727            damage_in_latency = now-process_damage_time
    17281728            self.statistics.damage_in_latency.append((now, width*height, actual_batch_delay, damage_in_latency))
    1729         self.queue_packet(packet, self.wid, width*height, start_send, damage_packet_sent)
     1729        fail_cb = self.get_fail_cb(packet)
     1730        #log.info("queuing %s packet with fail_cb=%s", coding, fail_cb)
     1731        self.queue_packet(packet, self.wid, width*height, start_send, damage_packet_sent, fail_cb)
    17301732
     1733    def get_fail_cb(self, packet):
     1734        def resend():
     1735            log.warn("paint packet failure, resending")
     1736            x,y,width,height = packet[2:6]
     1737            damage_packet_sequence = packet[8]
     1738            self.damage_packet_acked(damage_packet_sequence, width, height, 0, "")
     1739            self.idle_add(self.damage, x, y, width, height)
     1740        return resend
     1741
    17311742    def damage_packet_acked(self, damage_packet_sequence, width, height, decode_time, message):
    17321743        """
    17331744            The client is acknowledging a damage packet,
     
    17791790            self.timeout_add(250, self.full_quality_refresh)
    17801791
    17811792
    1782     def make_data_packet(self, damage_time, process_damage_time, image, coding, sequence, options, flush):
     1793    def make_data_packet(self, image, coding, sequence, options, flush):
    17831794        """
    17841795            Picture encoding - non-UI thread.
    17851796            Converts a damage item picked from the 'compression_work_queue'
  • xpra/server/window/window_video_source.py

     
    14261426        return {}
    14271427
    14281428
     1429    def get_fail_cb(self, packet):
     1430        coding = packet[6]
     1431        if coding in self.common_video_encodings:
     1432            return None
     1433        return WindowSource.get_fail_cb(self, packet)
     1434
     1435
    14291436    def make_draw_packet(self, x, y, w, h, coding, data, outstride, client_options={}, options={}):
    14301437        #overriden so we can invalidate the scroll data:
    14311438        #log.error("make_draw_packet%s", (x, y, w, h, coding, "..", outstride, client_options)
  • xpra/x11/desktop_server.py

     
    88import gtk.gdk
    99import gobject
    1010import socket
    11 import struct
    12 from threading import Event
    1311
    14 from xpra.util import updict, log_screen_sizes, envbool, nonl
    15 from xpra.os_util import get_generic_os_name, memoryview_to_bytes
     12from xpra.util import updict, log_screen_sizes, envbool
     13from xpra.os_util import get_generic_os_name
    1614from xpra.platform.paths import get_icon
    1715from xpra.platform.gui import get_wm_name
     16from xpra.server.rfb.rfb_server import RFBServer
    1817from xpra.gtk_common.gobject_util import one_arg_signal, no_arg_signal
    1918from xpra.gtk_common.gobject_compat import import_glib
    2019from xpra.gtk_common.error import xswallow
     
    4746metadatalog = Logger("x11", "metadata")
    4847screenlog = Logger("screen")
    4948iconlog = Logger("icon")
    50 rfblog = Logger("rfb")
    5149
    5250glib = import_glib()
    5351
     
    274272    A server class for RFB / VNC-like desktop displays,
    275273    used with the "start-desktop" subcommand.
    276274"""
    277 class XpraDesktopServer(gobject.GObject, X11ServerBase):
     275class XpraDesktopServer(gobject.GObject, RFBServer, X11ServerBase):
    278276    __gsignals__ = {
    279277        "xpra-xkb-event"        : one_arg_signal,
    280278        "xpra-cursor-event"     : one_arg_signal,
     
    285283        gobject.GObject.__init__(self)
    286284        X11ServerBase.__init__(self)
    287285
     286    def init(self, opts):
     287        X11ServerBase.init(self, opts)
     288        RFBServer.init(self)
     289
    288290    def x11_init(self):
    289291        X11ServerBase.x11_init(self)
    290292        assert init_x11_filter() is True
     
    567569            offset_y += 0
    568570        return self.make_screenshot_packet_from_regions(regions)
    569571
    570 
    571     def _get_rfb_desktop_model(self):
    572         models = self._window_to_id.keys()
    573         if len(models)!=1:
    574             rfblog.error("RFB can only handle a single desktop window, found %i", len(self._window_to_id))
    575             return None
    576         return models[0]
    577 
    578     def _get_rfb_desktop_wid(self):
    579         ids = self._window_to_id.values()
    580         if len(ids)!=1:
    581             rfblog.error("RFB can only handle a single desktop window, found %i", len(self._window_to_id))
    582             return None
    583         return ids[0]
    584 
    585     def handle_rfb_connection(self, conn):
    586         model = self._get_rfb_desktop_model()
    587         if not model:
    588             conn.close()
    589             return
    590         from xpra.net.rfb import RFBProtocol
    591         def rfb_protocol_class(conn):
    592             return RFBProtocol(self, conn, self.process_rfb_packet, self.get_rfb_pixelformat, self.session_name or "Xpra Server")
    593         p = self.do_make_protocol("rfb", conn, rfb_protocol_class)
    594         p.send_protocol_handshake()
    595 
    596     def get_rfb_pixelformat(self):
    597         model = self._get_rfb_desktop_model()
    598         w, h = model.get_dimensions()
    599         return w, h, 32, 32, False, True, 255, 255, 255, 16, 8, 0
    600 
    601     def process_rfb_packet(self, proto, packet):
    602         rfblog("RFB packet: '%s'", nonl(packet))
    603         fn_name = "_process_rfb_%s" % packet[0]
    604         fn = getattr(self, fn_name, None)
    605         if not fn:
    606             rfblog.warn("Warning: no RFB handler for %s", fn_name)
    607             return
    608         self.idle_add(fn, proto, packet)
    609 
    610     def _process_rfb_invalid(self, proto, packet):
    611         self.disconnect_protocol(proto, "invalid packet: %s" % (packet[1:]))
    612 
    613     def _process_rfb_gibberish(self, proto, packet):
    614         self.disconnect_protocol(proto, "invalid packet: %s" % (packet[1:]))
    615 
    616     def _process_rfb_authenticated(self, proto, _packet):
    617         model = self._get_rfb_desktop_model()
    618         if not model:
    619             proto.close()
    620             return
    621         self.rfb_init()
    622         self.accept_protocol(proto)
    623         #use blocking sockets from now on:
    624         from xpra.net.bytestreams import set_socket_timeout
    625         set_socket_timeout(proto._conn, None)
    626         source = RFBSource(proto, self._window_to_id.keys()[0])
    627         self._server_sources[proto] = source
    628         w, h = model.get_dimensions()
    629         source.damage(self._window_to_id[model], model, 0, 0, w, h)
    630 
    631     def rfb_init(self):
    632         self.rfb_buttons = 0
    633         self.x11_keycodes_for_keysym = {}
    634         x11_keycodes = X11Keyboard.get_keycode_mappings()
    635         for keycode, keysyms in x11_keycodes.items():
    636             for keysym in keysyms:
    637                 self.x11_keycodes_for_keysym.setdefault(keysym, []).append(keycode)
    638         rfblog("x11_keycodes_for_keysym=%s", self.x11_keycodes_for_keysym)
    639 
    640     def _process_rfb_PointerEvent(self, _proto, packet):
    641         buttons, x, y = packet[1:4]
    642         wid = self._get_rfb_desktop_wid()
    643         self._move_pointer(wid, (x, y))
    644         if buttons!=self.rfb_buttons:
    645             #figure out which buttons have changed:
    646             for button in range(8):
    647                 mask = 2**button
    648                 if buttons & mask != self.rfb_buttons & mask:
    649                     pressed = bool(buttons & mask)
    650                     self.button_action(1+button, pressed, -1)
    651             self.rfb_buttons = buttons
    652 
    653     def _process_rfb_KeyEvent(self, _proto, packet):
    654         pressed, _, _, key = packet[1:5]
    655         wid = self._get_rfb_desktop_wid()
    656         keyval = 0
    657         name = RFB_KEYNAMES.get(key) or chr(key)
    658         keycode = 0
    659         keycodes = self.x11_keycodes_for_keysym.get(name, 0)
    660         rfblog("keycodes(%s)=%s", name, keycodes)
    661         if keycodes:
    662             keycode = keycodes[0]
    663             modifiers = []
    664             self._handle_key(wid, bool(pressed), name, keyval, keycode, modifiers)
    665 
    666     def _process_rfb_FramebufferUpdateRequest(self, _proto, packet):
    667         #pressed, _, _, keycode = packet[1:5]
    668         inc, x, y, w, h = packet[1:6]
    669         if not inc:
    670             model = self._get_rfb_desktop_model()
    671             self._damage(model, x, y, w, h)
    672 
    673     def _process_rfb_ClientCutText(self, _proto, packet):
    674         #l = packet[4]
    675         text = packet[5]
    676         rfblog("got rfb clipboard text: %s", nonl(text))
    677 
    678 
    679 RFB_KEYNAMES = {
    680     0xff08      : "BackSpace",
    681     0xff09      : "Tab",
    682     0xff0d      : "Return",
    683     0xff1b      : "Escape",
    684     0xff63      : "Insert",
    685     0xffff      : "Delete",
    686     0xff50      : "Home",
    687     0xff57      : "End",
    688     0xff55      : "PageUp",
    689     0xff56      : "PageDown",
    690     0xff51      : "Left",
    691     0xff52      : "Up",
    692     0xff53      : "Right",
    693     0xff54      : "Down",
    694     0xffe1      : "Shift_L",
    695     0xffe2      : "Shift_R",
    696     0xffe3      : "Control_L",
    697     0xffe4      : "Control_R",
    698     0xffe7      : "Meta_L",
    699     0xffe8      : "Meta_R",
    700     0xffe9      : "Alt_L",
    701     0xffea      : "Alt_R",
    702     }
    703 for i in range(1, 13):
    704     RFB_KEYNAMES[0xffbe+(i-1)] = "F%i" % i
    705 
    706 
    707 class RFBSource(object):
    708 
    709     def __init__(self, protocol, desktop):
    710         self.protocol = protocol
    711         self.desktop = desktop
    712         self.close_event = Event()
    713         self.log_disconnect = True
    714         self.ui_client = True
    715         self.counter = 0
    716         self.uuid = "todo: use protocol?"
    717 
    718     def is_closed(self):
    719         return self.close_event.isSet()
    720 
    721     def close(self):
    722         pass
    723 
    724     def ping(self):
    725         pass
    726 
    727     def keys_changed(self):
    728         pass
    729 
    730     def send_server_event(self, *_args):
    731         pass
    732 
    733     def send_cursor(self):
    734         pass
    735 
    736 
    737     def damage(self, _wid, window, x, y, w, h, _options=None):
    738         img = window.get_image(x, y, w, h)
    739         rfblog("damage: %s", img)
    740         fbupdate = struct.pack("!BBH", 0, 0, 1)
    741         encoding = 0    #Raw
    742         rect = struct.pack("!HHHHi", x, y, w, h, encoding)
    743         if img.get_rowstride()!=w*4:
    744             img.restride(w*4)
    745         pixels = img.get_pixels()
    746         assert len(pixels)>=4*w*h
    747         pixels = pixels[:4*w*h]
    748         if len(pixels)<=4096:
    749             self.send(fbupdate+rect+memoryview_to_bytes(pixels))
    750         else:
    751             self.send(fbupdate+rect)
    752             self.send(pixels)
    753 
    754     def send_clipboard(self, text):
    755         nocr = text.replace("\r", "")
    756         msg = struct.pack("!BBBBI", 3, 0, 0, 0, len(nocr))+nocr
    757         self.send(msg)
    758 
    759     def bell(self, *_args):
    760         msg = struct.pack("!B", 2)
    761         self.send(msg)
    762 
    763     def send(self, msg):
    764         p = self.protocol
    765         if p:
    766             p.send(msg)
    767 
    768 
    769572gobject.type_register(XpraDesktopServer)
  • xpra/x11/x11_server_core.py

     
    720720            self.do_process_mouse_common(proto, wid, pointer)
    721721            self.pointer_device.wheel_motion(button, distance/1000.0)
    722722
    723     def _move_pointer(self, wid, pos, deviceid, *args):
     723    def _move_pointer(self, wid, pos, deviceid=-1, *args):
    724724        #(this is called within an xswallow context)
    725725        screen_no = self.get_screen_number(wid)
    726726        device = self.pointer_device