Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
* move bytestreams "can_retry" function to a method of each connection, allowing us to move the platform code to where it belongs
* don't inject ssl info into the generic SocketConnection object, just always use a SSLSocketConnection object when we wrap a connection
* add new switches: bind-ws, bind-wss and auth-ws, auth-wss for http(s) / (secure)websockets
* refactor socket setup: cleaner, smaller code
* keep track of socket_info to show where clients are actually connecting to, reliably
* more detailed logging of socket peek data for debugging, wrapping decision making
* when wrapping sockets, verify that the peek_data isn't just plain wrong - bail out early if it is

git-svn-id: https://xpra.org/svn/Xpra/trunk@16599 3bb7dfac-3a0b-4e04-842a-767bc560f471
  • Loading branch information
totaam committed Aug 3, 2017
1 parent e03ac20 commit 8a48dd4
Show file tree
Hide file tree
Showing 8 changed files with 302 additions and 181 deletions.
149 changes: 63 additions & 86 deletions src/xpra/net/bytestreams.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,9 @@
# later version. See the file COPYING for details.

import time
import sys
import os
import errno
import socket
import types

from xpra.log import Logger
log = Logger("network", "protocol")
Expand All @@ -27,7 +25,7 @@
#raises an IOError but we should continue if the error code is EINTR
#this wrapper takes care of it.
#EWOULDBLOCK can also be hit with the proxy server when we handover the socket
CONTINUE = {
CONTINUE_ERRNO = {
errno.EINTR : "EINTR",
errno.EWOULDBLOCK : "EWOULDBLOCK"
}
Expand All @@ -44,6 +42,16 @@
OS_WRITE = os.write
TTY_READ = os.read
TTY_WRITE = os.write
if WIN32 and PYTHON2:
#win32 has problems writing more than 32767 characters to stdout!
#see: http://bugs.python.org/issue11395
#(this is fixed in python 3.2 and we don't care about 3.0 or 3.1)
def win32ttywrite(fd, buf):
#this awful limitation only applies to tty devices:
if len(buf)>32767:
buf = buf[:32767]
return os.write(fd, buf)
TTY_WRITE = win32ttywrite


PROTOCOL_STR = {}
Expand All @@ -60,30 +68,6 @@
pass


if WIN32:
#on win32, we have to deal with a few more odd error codes:
CONTINUE[errno.WSAEWOULDBLOCK] = "WSAEWOULDBLOCK" #@UndefinedVariable

#some of these may be redundant or impossible to hit? (does not hurt I think)
for x in ("WSAENETDOWN", "WSAENETUNREACH", "WSAECONNABORTED", "WSAECONNRESET",
"WSAENOTCONN", "WSAESHUTDOWN", "WSAETIMEDOUT", "WSAETIMEDOUT",
"WSAEHOSTUNREACH", "WSAEDISCON"):
ABORT[getattr(errno, x)] = x
#duplicated from winerror module:
ERROR_BROKEN_PIPE = 109
ERROR_PIPE_NOT_CONNECTED = 233
ABORT[ERROR_BROKEN_PIPE] = "BROKENPIPE"
ABORT[ERROR_PIPE_NOT_CONNECTED] = "PIPE_NOT_CONNECTED"
if PYTHON2:
#win32 has problems writing more than 32767 characters to stdout!
#see: http://bugs.python.org/issue11395
#(this is fixed in python 3.2 and we don't care about 3.0 or 3.1)
def win32ttywrite(fd, buf):
#this awful limitation only applies to tty devices:
if len(buf)>32767:
buf = buf[:32767]
return os.write(fd, buf)
TTY_WRITE = win32ttywrite

def set_continue_wait(v):
global continue_wait
Expand All @@ -95,42 +79,33 @@ def set_continue_wait(v):
socket.timeout : "socket.timeout",
}

def init_ssl():
import ssl
assert ssl
global CONTINUE_EXCEPTIONS
CONTINUE_EXCEPTIONS[ssl.SSLError] = "SSLError"
CONTINUE_EXCEPTIONS[ssl.SSLWantReadError] = "SSLWantReadError"
CONTINUE_EXCEPTIONS[ssl.SSLWantWriteError] = "SSLWantWriteError"
return ssl


def can_retry(e):
continue_exception = CONTINUE_EXCEPTIONS.get(type(e))
if continue_exception:
return continue_exception
if isinstance(e, (IOError, OSError)):
global CONTINUE
global CONTINUE_ERRNO
code = e.args[0]
can_continue = CONTINUE.get(code)
can_continue = CONTINUE_ERRNO.get(code)
if can_continue:
return can_continue

abort = ABORT.get(code, code)
if abort is not None:
log("untilConcludes: %s, args=%s, code=%s, abort=%s", type(e), e.args, code, abort)
log("can_retry: %s, args=%s, code=%s, abort=%s", type(e), e.args, code, abort)
raise ConnectionClosedException(e)
return False

def untilConcludes(is_active_cb, f, *a, **kw):
def untilConcludes(is_active_cb, can_retry, f, *a, **kw):
global continue_wait
wait = 0
while is_active_cb():
try:
return f(*a, **kw)
except Exception as e:
retry = can_retry(e)
log("untilConcludes(%s, %s, %s, %s) %s, retry=%s", is_active_cb, f, a, kw, e, retry)
log("untilConcludes(%s, %s, %s, %s, %s) %s, retry=%s", is_active_cb, can_retry, f, a, kw, e, retry)
if retry:
if wait>0:
time.sleep(wait/1000.0) #wait is in milliseconds, sleep takes seconds
Expand Down Expand Up @@ -173,8 +148,11 @@ def set_active(self, active):
def close(self):
self.set_active(False)

def can_retry(self, e):
return can_retry(e)

def untilConcludes(self, *args):
return untilConcludes(self.is_active, *args)
return untilConcludes(self.is_active, self.can_retry, *args)

def peek(self, n):
#not implemented
Expand Down Expand Up @@ -345,6 +323,43 @@ def do_get_socket_info(self):
}


class SSLSocketConnection(SocketConnection):

def can_retry(self, e):
if getattr(e, "library", None)=="SSL":
reason = getattr(e, "reason", None)
if reason in ("WRONG_VERSION_NUMBER", "UNEXPECTED_RECORD"):
return False
#log.info("can_retry(%s) %s", e, type(e))
#for x in ('args', 'errno', 'filename', 'library', 'message', 'reason', 'strerror'):
# log.info("%s=%s", x, getattr(e, x, None))
return SocketConnection.can_retry(self, e)

def get_info(self):
i = SocketConnection.get_info(self)
i["ssl"] = True
for k,fn in {
"compression" : "compression",
"alpn-protocol" : "selected_alpn_protocol",
"npn-protocol" : "selected_npn_protocol",
"version" : "version",
}.items():
sfn = getattr(self._socket, fn, None)
if sfn:
v = sfn()
if v is not None:
i[k] = v
cipher_fn = getattr(self._socket, "cipher", None)
if cipher_fn:
cipher = cipher_fn()
if cipher:
i["cipher"] = {
"name" : cipher[0],
"protocol" : cipher[1],
"bits" : cipher[2],
}
return i


def set_socket_timeout(conn, timeout=None):
#FIXME: this is ugly, but less intrusive than the alternative?
Expand All @@ -353,48 +368,7 @@ def set_socket_timeout(conn, timeout=None):
conn._socket.settimeout(timeout)


def inject_ssl_socket_info(conn):
"""
If the socket is an SSLSocket,
we patch the Connection's get_info method
to return additional ssl data.
This method does not load the 'ssl' module.
"""
sock = conn._socket
ssl = sys.modules.get("ssl")
log("ssl=%s, socket class=%s", ssl, type(sock))
if ssl and isinstance(sock, ssl.SSLSocket):
#inject extra ssl info into the socket class:
def get_ssl_socket_info(sock):
d = sock.do_get_socket_info()
d["ssl"] = True
s = sock._socket
if not s:
return d
for k,fn in {
"compression" : "compression",
"alpn-protocol" : "selected_alpn_protocol",
"npn-protocol" : "selected_npn_protocol",
"version" : "version",
}.items():
sfn = getattr(s, fn, None)
if sfn:
v = sfn()
if v is not None:
d[k] = v
cipher_fn = getattr(s, "cipher", None)
if cipher_fn:
cipher = cipher_fn()
if cipher:
d["cipher"] = {
"name" : cipher[0],
"protocol" : cipher[1],
"bits" : cipher[2],
}
return d
conn.get_socket_info = types.MethodType(get_ssl_socket_info, conn)

def log_new_connection(conn):
def log_new_connection(conn, socket_info=""):
""" logs the new connection message """
sock = conn._socket
address = conn.remote
Expand All @@ -404,14 +378,17 @@ def log_new_connection(conn):
except:
peername = str(address)
sockname = sock.getsockname()
log("log_new_connection(%s) sock=%s, sockname=%s, address=%s, peername=%s", conn, sock, sockname, address, peername)
log("log_new_connection(%s, %s) type=%s, sock=%s, sockname=%s, address=%s, peername=%s", conn, socket_info, type(conn), sock, sockname, address, peername)
if peername:
frominfo = pretty_socket(peername)
info_msg = "New %s connection received from %s" % (socktype, frominfo)
if socket_info:
info_msg += " on %s" % (pretty_socket(socket_info),)
elif socktype=="unix-domain":
frominfo = sockname
info_msg = "New %s connection received on %s" % (socktype, frominfo)
else:
frominfo = ""
info_msg = "New %s connection received"
if socket_info:
info_msg += " on %s" % (pretty_socket(socket_info),)
log.info(info_msg)
1 change: 1 addition & 0 deletions src/xpra/platform/win32/namedpipes/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
INFINITE = 65535
INVALID_HANDLE_VALUE = -1

ERROR_BROKEN_PIPE = 109
ERROR_PIPE_NOT_CONNECTED = 233
ERROR_MORE_DATA = 234
ERROR_BROKEN_PIPE = 109
Expand Down
25 changes: 24 additions & 1 deletion src/xpra/platform/win32/namedpipes/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@

#@PydevCodeAnalysisIgnore

import errno
from ctypes import WinDLL, addressof, byref, c_ulong, c_char_p, c_char, c_void_p, cast, string_at

from xpra.net.bytestreams import Connection
from xpra.net.common import ConnectionClosedException
from xpra.platform.win32.namedpipes.common import OVERLAPPED, WAIT_STR, INVALID_HANDLE_VALUE, ERROR_PIPE_BUSY, ERROR_PIPE_NOT_CONNECTED, INFINITE, ERROR_STR, ERROR_BROKEN_PIPE, ERROR_IO_PENDING
from xpra.platform.win32.constants import FILE_FLAG_OVERLAPPED, GENERIC_READ, GENERIC_WRITE, OPEN_EXISTING, PIPE_READMODE_BYTE

Expand All @@ -31,6 +33,16 @@

BUFSIZE = 65536

CONNECTION_CLOSED_ERRORS = {
ERROR_BROKEN_PIPE : "BROKENPIPE",
ERROR_PIPE_NOT_CONNECTED : "PIPE_NOT_CONNECTED",
}
#some of these may be redundant or impossible to hit? (does not hurt I think)
for x in ("WSAENETDOWN", "WSAENETUNREACH", "WSAECONNABORTED", "WSAECONNRESET",
"WSAENOTCONN", "WSAESHUTDOWN", "WSAETIMEDOUT", "WSAETIMEDOUT",
"WSAEHOSTUNREACH", "WSAEDISCON"):
CONNECTION_CLOSED_ERRORS[getattr(errno, x)] = x


class NamedPipeConnection(Connection):
def __init__(self, name, pipe_handle):
Expand All @@ -52,9 +64,20 @@ def __init__(self, name, pipe_handle):
self.write_overlapped.InternalHigh = None
self.write_overlapped.union.Pointer = None

def can_retry(self, e):
code = e.args[0]
if code==errno.WSAEWOULDBLOCK: #@UndefinedVariable
return "WSAEWOULDBLOCK"
#convert those to a connection closed:
closed = CONNECTION_CLOSED_ERRORS.get(code)
if closed:
raise ConnectionClosedException(e)
return False


def untilConcludes(self, fn, *args, **kwargs):
try:
return Connection.untilConcludes(self, fn, *args, **kwargs)
return Connection.untilConcludes(self, self.can_retry, fn, *args, **kwargs)
except Exception as e:
code = GetLastError()
log("untilConcludes(%s, ) exception: %s, error code=%s", fn, e, code, exc_info=True)
Expand Down
14 changes: 11 additions & 3 deletions src/xpra/scripts/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,8 @@ def may_create_user_config(xpra_conf_filename=DEFAULT_XPRA_CONF_FILENAME):
"auth" : str,
"vsock-auth" : str,
"tcp-auth" : str,
"ws-auth" : str,
"wss-auth" : str,
"ssl-auth" : str,
"wm-name" : str,
"session-name" : str,
Expand Down Expand Up @@ -590,6 +592,8 @@ def may_create_user_config(xpra_conf_filename=DEFAULT_XPRA_CONF_FILENAME):
"bind" : list,
"bind-vsock" : list,
"bind-tcp" : list,
"bind-ws" : list,
"bind-wss" : list,
"bind-ssl" : list,
"start-env" : list,
"env" : list,
Expand All @@ -608,7 +612,7 @@ def may_create_user_config(xpra_conf_filename=DEFAULT_XPRA_CONF_FILENAME):
"start-after-connect", "start-child-after-connect",
"start-on-connect", "start-child-on-connect",
]
BIND_OPTIONS = ["bind", "bind-tcp", "bind-ssl", "bind-vsock"]
BIND_OPTIONS = ["bind", "bind-tcp", "bind-ssl", "bind-ws", "bind-wss", "bind-vsock"]

#keep track of the options added since v1,
#so we can generate command lines that work with older supported versions:
Expand Down Expand Up @@ -672,8 +676,8 @@ def may_create_user_config(xpra_conf_filename=DEFAULT_XPRA_CONF_FILENAME):
"av-sync", "global-menus",
"printing", "file-transfer", "open-command", "open-files", "start-new-commands",
"mmap", "mmap-group", "mdns",
"auth", "vsock-auth", "tcp-auth", "ssl-auth",
"bind", "bind-vsock", "bind-tcp", "bind-ssl",
"auth", "vsock-auth", "tcp-auth", "ws-auth", "wss-auth", "ssl-auth",
"bind", "bind-vsock", "bind-tcp", "bind-ssl", "bind-ws", "bind-wss",
"start", "start-child",
"start-after-connect", "start-child-after-connect",
"start-on-connect", "start-child-on-connect",
Expand Down Expand Up @@ -792,6 +796,8 @@ def addtrailingslash(v):
"auth" : "",
"vsock-auth" : "",
"tcp-auth" : "",
"ws-auth" : "",
"wss-auth" : "",
"ssl-auth" : "",
"wm-name" : DEFAULT_NET_WM_NAME,
"session-name" : "",
Expand Down Expand Up @@ -935,6 +941,8 @@ def addtrailingslash(v):
"bind" : bind_dirs,
"bind-vsock" : [],
"bind-tcp" : [],
"bind-ws" : [],
"bind-wss" : [],
"bind-ssl" : [],
"start" : [],
"start-child" : [],
Expand Down
Loading

0 comments on commit 8a48dd4

Please sign in to comment.