diff options
author | Maximilian Hils <git@maximilianhils.com> | 2016-02-15 14:58:46 +0100 |
---|---|---|
committer | Maximilian Hils <git@maximilianhils.com> | 2016-02-15 14:58:46 +0100 |
commit | 33fa49277a821b9d38e8c9bf0bcf2adcfa2f6f04 (patch) | |
tree | 31914a601302579ff817504019296fd7e9e46765 /libmproxy/proxy | |
parent | 36f34f701991b5d474c005ec45e3b66e20f326a8 (diff) | |
download | mitmproxy-33fa49277a821b9d38e8c9bf0bcf2adcfa2f6f04.tar.gz mitmproxy-33fa49277a821b9d38e8c9bf0bcf2adcfa2f6f04.tar.bz2 mitmproxy-33fa49277a821b9d38e8c9bf0bcf2adcfa2f6f04.zip |
move mitmproxy
Diffstat (limited to 'libmproxy/proxy')
-rw-r--r-- | libmproxy/proxy/__init__.py | 11 | ||||
-rw-r--r-- | libmproxy/proxy/config.py | 208 | ||||
-rw-r--r-- | libmproxy/proxy/modes/__init__.py | 12 | ||||
-rw-r--r-- | libmproxy/proxy/modes/http_proxy.py | 28 | ||||
-rw-r--r-- | libmproxy/proxy/modes/reverse_proxy.py | 18 | ||||
-rw-r--r-- | libmproxy/proxy/modes/socks_proxy.py | 66 | ||||
-rw-r--r-- | libmproxy/proxy/modes/transparent_proxy.py | 25 | ||||
-rw-r--r-- | libmproxy/proxy/root_context.py | 139 | ||||
-rw-r--r-- | libmproxy/proxy/server.py | 157 |
9 files changed, 0 insertions, 664 deletions
diff --git a/libmproxy/proxy/__init__.py b/libmproxy/proxy/__init__.py deleted file mode 100644 index be7f5207..00000000 --- a/libmproxy/proxy/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import (absolute_import, print_function, division) - -from .server import ProxyServer, DummyServer -from .config import ProxyConfig -from .root_context import RootContext, Log - -__all__ = [ - "ProxyServer", "DummyServer", - "ProxyConfig", - "RootContext", "Log", -] diff --git a/libmproxy/proxy/config.py b/libmproxy/proxy/config.py deleted file mode 100644 index a635ab19..00000000 --- a/libmproxy/proxy/config.py +++ /dev/null @@ -1,208 +0,0 @@ -from __future__ import (absolute_import, print_function, division) -import collections -import os -import re -from OpenSSL import SSL - -from netlib import certutils, tcp -from netlib.http import authentication -from netlib.tcp import Address, sslversion_choices - -from .. import utils, platform - -CONF_BASENAME = "mitmproxy" -CA_DIR = "~/.mitmproxy" - -# We manually need to specify this, otherwise OpenSSL may select a non-HTTP2 cipher by default. -# https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=apache-2.2.15&openssl=1.0.2&hsts=yes&profile=old -DEFAULT_CLIENT_CIPHERS = "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA" - - -class HostMatcher(object): - - def __init__(self, patterns=tuple()): - self.patterns = list(patterns) - self.regexes = [re.compile(p, re.IGNORECASE) for p in self.patterns] - - def __call__(self, address): - if not address: - return False - address = tcp.Address.wrap(address) - host = "%s:%s" % (address.host, address.port) - if any(rex.search(host) for rex in self.regexes): - return True - else: - return False - - def __nonzero__(self): - return bool(self.patterns) - - -ServerSpec = collections.namedtuple("ServerSpec", "scheme address") - - -class ProxyConfig: - - def __init__( - self, - host='', - port=8080, - cadir=CA_DIR, - clientcerts=None, - no_upstream_cert=False, - body_size_limit=None, - mode="regular", - upstream_server=None, - authenticator=None, - ignore_hosts=tuple(), - tcp_hosts=tuple(), - http2=False, - rawtcp=False, - ciphers_client=DEFAULT_CLIENT_CIPHERS, - ciphers_server=None, - certs=tuple(), - ssl_version_client="secure", - ssl_version_server="secure", - ssl_verify_upstream_cert=False, - ssl_verify_upstream_trusted_cadir=None, - ssl_verify_upstream_trusted_ca=None, - ): - self.host = host - self.port = port - self.ciphers_client = ciphers_client - self.ciphers_server = ciphers_server - self.clientcerts = clientcerts - self.no_upstream_cert = no_upstream_cert - self.body_size_limit = body_size_limit - self.mode = mode - if upstream_server: - self.upstream_server = ServerSpec(upstream_server[0], Address.wrap(upstream_server[1])) - else: - self.upstream_server = None - - self.check_ignore = HostMatcher(ignore_hosts) - self.check_tcp = HostMatcher(tcp_hosts) - self.http2 = http2 - self.rawtcp = rawtcp - self.authenticator = authenticator - self.cadir = os.path.expanduser(cadir) - self.certstore = certutils.CertStore.from_store( - self.cadir, - CONF_BASENAME - ) - for spec, cert in certs: - self.certstore.add_cert_file(spec, cert) - - self.openssl_method_client, self.openssl_options_client = \ - sslversion_choices[ssl_version_client] - self.openssl_method_server, self.openssl_options_server = \ - sslversion_choices[ssl_version_server] - - if ssl_verify_upstream_cert: - self.openssl_verification_mode_server = SSL.VERIFY_PEER - else: - self.openssl_verification_mode_server = SSL.VERIFY_NONE - self.openssl_trusted_cadir_server = ssl_verify_upstream_trusted_cadir - self.openssl_trusted_ca_server = ssl_verify_upstream_trusted_ca - - -def process_proxy_options(parser, options): - body_size_limit = utils.parse_size(options.body_size_limit) - - c = 0 - mode, upstream_server = "regular", None - if options.transparent_proxy: - c += 1 - if not platform.resolver: - return parser.error("Transparent mode not supported on this platform.") - mode = "transparent" - if options.socks_proxy: - c += 1 - mode = "socks5" - if options.reverse_proxy: - c += 1 - mode = "reverse" - upstream_server = options.reverse_proxy - if options.upstream_proxy: - c += 1 - mode = "upstream" - upstream_server = options.upstream_proxy - if c > 1: - return parser.error( - "Transparent, SOCKS5, reverse and upstream proxy mode " - "are mutually exclusive. Read the docs on proxy modes to understand why." - ) - - if options.clientcerts: - options.clientcerts = os.path.expanduser(options.clientcerts) - if not os.path.exists(options.clientcerts): - return parser.error( - "Client certificate path does not exist: %s" % options.clientcerts - ) - - if options.auth_nonanonymous or options.auth_singleuser or options.auth_htpasswd: - - if options.transparent_proxy: - return parser.error("Proxy Authentication not supported in transparent mode.") - - if options.socks_proxy: - return parser.error( - "Proxy Authentication not supported in SOCKS mode. " - "https://github.com/mitmproxy/mitmproxy/issues/738" - ) - - if options.auth_singleuser: - if len(options.auth_singleuser.split(':')) != 2: - return parser.error( - "Invalid single-user specification. Please use the format username:password" - ) - username, password = options.auth_singleuser.split(':') - password_manager = authentication.PassManSingleUser(username, password) - elif options.auth_nonanonymous: - password_manager = authentication.PassManNonAnon() - elif options.auth_htpasswd: - try: - password_manager = authentication.PassManHtpasswd( - options.auth_htpasswd) - except ValueError as v: - return parser.error(v.message) - authenticator = authentication.BasicProxyAuth(password_manager, "mitmproxy") - else: - authenticator = authentication.NullProxyAuth(None) - - certs = [] - for i in options.certs: - parts = i.split("=", 1) - if len(parts) == 1: - parts = ["*", parts[0]] - parts[1] = os.path.expanduser(parts[1]) - if not os.path.exists(parts[1]): - parser.error("Certificate file does not exist: %s" % parts[1]) - certs.append(parts) - - if options.http2 and not tcp.HAS_ALPN: - raise RuntimeError("HTTP2 support requires OpenSSL 1.0.2 or above.") - - return ProxyConfig( - host=options.addr, - port=options.port, - cadir=options.cadir, - clientcerts=options.clientcerts, - no_upstream_cert=options.no_upstream_cert, - body_size_limit=body_size_limit, - mode=mode, - upstream_server=upstream_server, - ignore_hosts=options.ignore_hosts, - tcp_hosts=options.tcp_hosts, - http2=options.http2, - rawtcp=options.rawtcp, - authenticator=authenticator, - ciphers_client=options.ciphers_client, - ciphers_server=options.ciphers_server, - certs=tuple(certs), - ssl_version_client=options.ssl_version_client, - ssl_version_server=options.ssl_version_server, - ssl_verify_upstream_cert=options.ssl_verify_upstream_cert, - ssl_verify_upstream_trusted_cadir=options.ssl_verify_upstream_trusted_cadir, - ssl_verify_upstream_trusted_ca=options.ssl_verify_upstream_trusted_ca - ) diff --git a/libmproxy/proxy/modes/__init__.py b/libmproxy/proxy/modes/__init__.py deleted file mode 100644 index f014ed98..00000000 --- a/libmproxy/proxy/modes/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -from __future__ import (absolute_import, print_function, division) -from .http_proxy import HttpProxy, HttpUpstreamProxy -from .reverse_proxy import ReverseProxy -from .socks_proxy import Socks5Proxy -from .transparent_proxy import TransparentProxy - -__all__ = [ - "HttpProxy", "HttpUpstreamProxy", - "ReverseProxy", - "Socks5Proxy", - "TransparentProxy" -] diff --git a/libmproxy/proxy/modes/http_proxy.py b/libmproxy/proxy/modes/http_proxy.py deleted file mode 100644 index e19062b9..00000000 --- a/libmproxy/proxy/modes/http_proxy.py +++ /dev/null @@ -1,28 +0,0 @@ -from __future__ import (absolute_import, print_function, division) - -from ...protocol import Layer, ServerConnectionMixin - - -class HttpProxy(Layer, ServerConnectionMixin): - - def __call__(self): - layer = self.ctx.next_layer(self) - try: - layer() - finally: - if self.server_conn: - self.disconnect() - - -class HttpUpstreamProxy(Layer, ServerConnectionMixin): - - def __init__(self, ctx, server_address): - super(HttpUpstreamProxy, self).__init__(ctx, server_address=server_address) - - def __call__(self): - layer = self.ctx.next_layer(self) - try: - layer() - finally: - if self.server_conn: - self.disconnect() diff --git a/libmproxy/proxy/modes/reverse_proxy.py b/libmproxy/proxy/modes/reverse_proxy.py deleted file mode 100644 index c8e80a10..00000000 --- a/libmproxy/proxy/modes/reverse_proxy.py +++ /dev/null @@ -1,18 +0,0 @@ -from __future__ import (absolute_import, print_function, division) - -from ...protocol import Layer, ServerConnectionMixin - - -class ReverseProxy(Layer, ServerConnectionMixin): - - def __init__(self, ctx, server_address, server_tls): - super(ReverseProxy, self).__init__(ctx, server_address=server_address) - self.server_tls = server_tls - - def __call__(self): - layer = self.ctx.next_layer(self) - try: - layer() - finally: - if self.server_conn: - self.disconnect() diff --git a/libmproxy/proxy/modes/socks_proxy.py b/libmproxy/proxy/modes/socks_proxy.py deleted file mode 100644 index e2ce44ae..00000000 --- a/libmproxy/proxy/modes/socks_proxy.py +++ /dev/null @@ -1,66 +0,0 @@ -from __future__ import (absolute_import, print_function, division) - -from netlib import socks, tcp -from netlib.exceptions import TcpException - -from ...exceptions import Socks5ProtocolException -from ...protocol import Layer, ServerConnectionMixin - - -class Socks5Proxy(Layer, ServerConnectionMixin): - - def __init__(self, ctx): - super(Socks5Proxy, self).__init__(ctx) - - def __call__(self): - try: - # Parse Client Greeting - client_greet = socks.ClientGreeting.from_file(self.client_conn.rfile, fail_early=True) - client_greet.assert_socks5() - if socks.METHOD.NO_AUTHENTICATION_REQUIRED not in client_greet.methods: - raise socks.SocksError( - socks.METHOD.NO_ACCEPTABLE_METHODS, - "mitmproxy only supports SOCKS without authentication" - ) - - # Send Server Greeting - server_greet = socks.ServerGreeting( - socks.VERSION.SOCKS5, - socks.METHOD.NO_AUTHENTICATION_REQUIRED - ) - server_greet.to_file(self.client_conn.wfile) - self.client_conn.wfile.flush() - - # Parse Connect Request - connect_request = socks.Message.from_file(self.client_conn.rfile) - connect_request.assert_socks5() - if connect_request.msg != socks.CMD.CONNECT: - raise socks.SocksError( - socks.REP.COMMAND_NOT_SUPPORTED, - "mitmproxy only supports SOCKS5 CONNECT." - ) - - # We always connect lazily, but we need to pretend to the client that we connected. - connect_reply = socks.Message( - socks.VERSION.SOCKS5, - socks.REP.SUCCEEDED, - connect_request.atyp, - # dummy value, we don't have an upstream connection yet. - connect_request.addr - ) - connect_reply.to_file(self.client_conn.wfile) - self.client_conn.wfile.flush() - - except (socks.SocksError, TcpException) as e: - raise Socks5ProtocolException("SOCKS5 mode failure: %s" % repr(e)) - - # https://github.com/mitmproxy/mitmproxy/issues/839 - address_bytes = (connect_request.addr.host.encode("idna"), connect_request.addr.port) - self.server_conn.address = tcp.Address(address_bytes, connect_request.addr.use_ipv6) - - layer = self.ctx.next_layer(self) - try: - layer() - finally: - if self.server_conn: - self.disconnect() diff --git a/libmproxy/proxy/modes/transparent_proxy.py b/libmproxy/proxy/modes/transparent_proxy.py deleted file mode 100644 index 3fdda656..00000000 --- a/libmproxy/proxy/modes/transparent_proxy.py +++ /dev/null @@ -1,25 +0,0 @@ -from __future__ import (absolute_import, print_function, division) - -from ... import platform -from ...exceptions import ProtocolException -from ...protocol import Layer, ServerConnectionMixin - - -class TransparentProxy(Layer, ServerConnectionMixin): - - def __init__(self, ctx): - super(TransparentProxy, self).__init__(ctx) - self.resolver = platform.resolver() - - def __call__(self): - try: - self.server_conn.address = self.resolver.original_addr(self.client_conn.connection) - except Exception as e: - raise ProtocolException("Transparent mode failure: %s" % repr(e)) - - layer = self.ctx.next_layer(self) - try: - layer() - finally: - if self.server_conn: - self.disconnect() diff --git a/libmproxy/proxy/root_context.py b/libmproxy/proxy/root_context.py deleted file mode 100644 index f56aee6d..00000000 --- a/libmproxy/proxy/root_context.py +++ /dev/null @@ -1,139 +0,0 @@ -from __future__ import (absolute_import, print_function, division) -import string -import sys - -import six - -from libmproxy.exceptions import ProtocolException, TlsProtocolException -from netlib.exceptions import TcpException -from ..protocol import ( - RawTCPLayer, TlsLayer, Http1Layer, Http2Layer, is_tls_record_magic, ServerConnectionMixin, - UpstreamConnectLayer, TlsClientHello -) -from .modes import HttpProxy, HttpUpstreamProxy, ReverseProxy - - -class RootContext(object): - - """ - The outermost context provided to the root layer. - As a consequence, every layer has access to methods and attributes defined here. - - Attributes: - client_conn: - The :py:class:`client connection <libmproxy.models.ClientConnection>`. - channel: - A :py:class:`~libmproxy.controller.Channel` to communicate with the FlowMaster. - Provides :py:meth:`.ask() <libmproxy.controller.Channel.ask>` and - :py:meth:`.tell() <libmproxy.controller.Channel.tell>` methods. - config: - The :py:class:`proxy server's configuration <libmproxy.proxy.ProxyConfig>` - """ - - def __init__(self, client_conn, config, channel): - self.client_conn = client_conn - self.channel = channel - self.config = config - - def next_layer(self, top_layer): - """ - This function determines the next layer in the protocol stack. - - Arguments: - top_layer: the current innermost layer. - - Returns: - The next layer - """ - layer = self._next_layer(top_layer) - return self.channel.ask("next_layer", layer) - - def _next_layer(self, top_layer): - try: - d = top_layer.client_conn.rfile.peek(3) - except TcpException as e: - six.reraise(ProtocolException, ProtocolException(str(e)), sys.exc_info()[2]) - client_tls = is_tls_record_magic(d) - - # 1. check for --ignore - if self.config.check_ignore: - ignore = self.config.check_ignore(top_layer.server_conn.address) - if not ignore and client_tls: - try: - client_hello = TlsClientHello.from_client_conn(self.client_conn) - except TlsProtocolException as e: - self.log("Cannot parse Client Hello: %s" % repr(e), "error") - else: - ignore = self.config.check_ignore((client_hello.client_sni, 443)) - if ignore: - return RawTCPLayer(top_layer, logging=False) - - # 2. Always insert a TLS layer, even if there's neither client nor server tls. - # An inline script may upgrade from http to https, - # in which case we need some form of TLS layer. - if isinstance(top_layer, ReverseProxy): - return TlsLayer(top_layer, client_tls, top_layer.server_tls) - if isinstance(top_layer, ServerConnectionMixin) or isinstance(top_layer, UpstreamConnectLayer): - return TlsLayer(top_layer, client_tls, client_tls) - - # 3. In Http Proxy mode and Upstream Proxy mode, the next layer is fixed. - if isinstance(top_layer, TlsLayer): - if isinstance(top_layer.ctx, HttpProxy): - return Http1Layer(top_layer, "regular") - if isinstance(top_layer.ctx, HttpUpstreamProxy): - return Http1Layer(top_layer, "upstream") - - # 4. Check for other TLS cases (e.g. after CONNECT). - if client_tls: - return TlsLayer(top_layer, True, True) - - # 4. Check for --tcp - if self.config.check_tcp(top_layer.server_conn.address): - return RawTCPLayer(top_layer) - - # 5. Check for TLS ALPN (HTTP1/HTTP2) - if isinstance(top_layer, TlsLayer): - alpn = top_layer.client_conn.get_alpn_proto_negotiated() - if alpn == b'h2': - return Http2Layer(top_layer, 'transparent') - if alpn == b'http/1.1': - return Http1Layer(top_layer, 'transparent') - - # 6. Check for raw tcp mode - is_ascii = ( - len(d) == 3 and - # better be safe here and don't expect uppercase... - all(x in string.ascii_letters for x in d) - ) - if self.config.rawtcp and not is_ascii: - return RawTCPLayer(top_layer) - - # 7. Assume HTTP1 by default - return Http1Layer(top_layer, 'transparent') - - def log(self, msg, level, subs=()): - """ - Send a log message to the master. - """ - - full_msg = [ - "{}: {}".format(repr(self.client_conn.address), msg) - ] - for i in subs: - full_msg.append(" -> " + i) - full_msg = "\n".join(full_msg) - self.channel.tell("log", Log(full_msg, level)) - - @property - def layers(self): - return [] - - def __repr__(self): - return "RootContext" - - -class Log(object): - - def __init__(self, msg, level="info"): - self.msg = msg - self.level = level diff --git a/libmproxy/proxy/server.py b/libmproxy/proxy/server.py deleted file mode 100644 index d208cff5..00000000 --- a/libmproxy/proxy/server.py +++ /dev/null @@ -1,157 +0,0 @@ -from __future__ import (absolute_import, print_function, division) - -import traceback -import sys -import socket -import six - -from netlib import tcp -from netlib.exceptions import TcpException -from netlib.http.http1 import assemble_response -from ..exceptions import ProtocolException, ServerException, ClientHandshakeException -from ..protocol import Kill -from ..models import ClientConnection, make_error_response -from .modes import HttpUpstreamProxy, HttpProxy, ReverseProxy, TransparentProxy, Socks5Proxy -from .root_context import RootContext, Log - - -class DummyServer: - bound = False - - def __init__(self, config): - self.config = config - - def start_slave(self, *args): - pass - - def shutdown(self): - pass - - -class ProxyServer(tcp.TCPServer): - allow_reuse_address = True - bound = True - - def __init__(self, config): - """ - Raises ProxyServerError if there's a startup problem. - """ - self.config = config - try: - super(ProxyServer, self).__init__((config.host, config.port)) - except socket.error as e: - six.reraise( - ServerException, - ServerException('Error starting proxy server: ' + repr(e)), - sys.exc_info()[2] - ) - self.channel = None - - def start_slave(self, klass, channel): - slave = klass(channel, self) - slave.start() - - def set_channel(self, channel): - self.channel = channel - - def handle_client_connection(self, conn, client_address): - h = ConnectionHandler( - conn, - client_address, - self.config, - self.channel - ) - h.handle() - - -class ConnectionHandler(object): - - def __init__(self, client_conn, client_address, config, channel): - self.config = config - """@type: libmproxy.proxy.config.ProxyConfig""" - self.client_conn = ClientConnection( - client_conn, - client_address, - None) - """@type: libmproxy.proxy.connection.ClientConnection""" - self.channel = channel - """@type: libmproxy.controller.Channel""" - - def _create_root_layer(self): - root_context = RootContext( - self.client_conn, - self.config, - self.channel - ) - - mode = self.config.mode - if mode == "upstream": - return HttpUpstreamProxy( - root_context, - self.config.upstream_server.address - ) - elif mode == "transparent": - return TransparentProxy(root_context) - elif mode == "reverse": - server_tls = self.config.upstream_server.scheme == "https" - return ReverseProxy( - root_context, - self.config.upstream_server.address, - server_tls - ) - elif mode == "socks5": - return Socks5Proxy(root_context) - elif mode == "regular": - return HttpProxy(root_context) - elif callable(mode): # pragma: no cover - return mode(root_context) - else: # pragma: no cover - raise ValueError("Unknown proxy mode: %s" % mode) - - def handle(self): - self.log("clientconnect", "info") - - root_layer = self._create_root_layer() - root_layer = self.channel.ask("clientconnect", root_layer) - if root_layer == Kill: - def root_layer(): - raise Kill() - - try: - root_layer() - except Kill: - self.log("Connection killed", "info") - except ProtocolException as e: - - if isinstance(e, ClientHandshakeException): - self.log( - "Client Handshake failed. " - "The client may not trust the proxy's certificate for {}.".format(e.server), - "error" - ) - self.log(repr(e), "debug") - else: - self.log(repr(e), "info") - - self.log(traceback.format_exc(), "debug") - # If an error propagates to the topmost level, - # we send an HTTP error response, which is both - # understandable by HTTP clients and humans. - try: - error_response = make_error_response(502, repr(e)) - self.client_conn.send(assemble_response(error_response)) - except TcpException: - pass - except Exception: - self.log(traceback.format_exc(), "error") - print(traceback.format_exc(), file=sys.stderr) - print("mitmproxy has crashed!", file=sys.stderr) - print("Please lodge a bug report at: https://github.com/mitmproxy/mitmproxy", file=sys.stderr) - - self.log("clientdisconnect", "info") - self.channel.tell("clientdisconnect", root_layer) - self.client_conn.finish() - - def log(self, msg, level): - msg = "{}: {}".format(repr(self.client_conn.address), msg) - self.channel.tell("log", Log(msg, level)) |