From 7ea96bebcc3e071b8b081cd7921c253a8758b998 Mon Sep 17 00:00:00 2001 From: Fantix King Date: Wed, 22 Aug 2018 12:03:09 +0800 Subject: [PATCH] Added SSL benchmark --- Dockerfile | 11 +- echo_client | 24 ++- run_benchmarks | 121 ++++++++++- servers/asyncioecho.py | 68 +++++- servers/gevecho.py | 9 +- servers/goecho.go | 46 ++++- servers/nodeecho.js | 49 ++++- servers/platinfo.py | 22 +- servers/requirements.txt | 11 +- servers/ssl_test.crt | 15 ++ servers/ssl_test_rsa | 16 ++ servers/sslbench.cc | 435 +++++++++++++++++++++++++++++++++++++++ servers/threadsslecho.py | 40 ++++ servers/twistedecho.py | 15 +- 14 files changed, 835 insertions(+), 47 deletions(-) create mode 100644 servers/ssl_test.crt create mode 100644 servers/ssl_test_rsa create mode 100644 servers/sslbench.cc create mode 100644 servers/threadsslecho.py diff --git a/Dockerfile b/Dockerfile index c9a3022..510829c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,9 @@ -FROM ubuntu:16.04 +FROM python:3.7 MAINTAINER elvis@magic.io RUN DEBIAN_FRONTEND=noninteractive \ apt-get update && apt-get install -y \ - language-pack-en + locales locales-all ENV LANG en_US.UTF-8 ENV WORKON_HOME /usr/local/python-venvs @@ -16,15 +16,16 @@ ENV GOPATH /usr/go/ RUN DEBIAN_FRONTEND=noninteractive \ apt-get update && apt-get install -y \ autoconf automake libtool build-essential \ - python3 python3-pip git nodejs golang gosu + python3 python3-pip git nodejs golang gosu libssl-dev RUN pip3 install vex -RUN vex --python=python3.5 -m bench pip install -U pip +RUN vex --python=python3 -m bench pip install -U pip RUN mkdir -p /var/lib/cache/pip ADD servers /usr/src/servers RUN cd /usr/src/servers && go build goecho.go && \ - go get github.com/golang/groupcache/lru && go build gohttp.go + go get github.com/golang/groupcache/lru && go build gohttp.go && \ + g++ -g -Wall -Werror -O2 sslbench.cc -lssl -lcrypto -ldl -lpthread -o sslbench RUN vex bench pip --cache-dir=/var/lib/cache/pip \ install -r /usr/src/servers/requirements.txt diff --git a/echo_client b/echo_client index 3bd1b9a..308ee0b 100755 --- a/echo_client +++ b/echo_client @@ -52,6 +52,7 @@ if __name__ == '__main__': help='server address') parser.add_argument('--output-format', default='text', type=str, help='output format', choices=['text', 'json']) + parser.add_argument('--ssl', action='store_true', help='enable SSL') args = parser.parse_args() unix = False @@ -78,6 +79,16 @@ if __name__ == '__main__': sock.settimeout(timeout / 1000) sock.connect(addr) + cipher = None + if args.ssl: + import ssl + ssl_context = ssl.create_default_context() + ssl_context.check_hostname = False + ssl_context.verify_mode = ssl.CERT_NONE + ssl_context.set_ciphers('ECDHE-RSA-AES128-GCM-SHA256') + sock = ssl_context.wrap_socket(sock) + sock.do_handshake() + cipher = '%s %s %dbits' % sock.cipher() n = 0 latency_stats = np.zeros((timeout * 100,)) @@ -106,7 +117,7 @@ if __name__ == '__main__': except OSError: pass - return n, latency_stats, min_latency, max_latency + return n, latency_stats, min_latency, max_latency, cipher N = args.concurrency DURATION = args.duration @@ -116,6 +127,7 @@ if __name__ == '__main__': messages = 0 latency_stats = None start = time.monotonic() + ciphers = set() with futures.ProcessPoolExecutor(max_workers=N) as e: fs = [] @@ -124,8 +136,9 @@ if __name__ == '__main__': res = futures.wait(fs) for fut in res.done: - t_messages, t_latency_stats, t_min_latency, t_max_latency = \ + t_messages, t_latency_stats, t_min_latency, t_max_latency, cip = \ fut.result() + ciphers.add(cip) messages += t_messages if latency_stats is None: latency_stats = t_latency_stats @@ -167,7 +180,8 @@ if __name__ == '__main__': latency_max=round(max_latency / 100, 3), latency_std=round(latency_std / 100, 3), latency_cv=round(latency_cv * 100, 2), - latency_percentiles=percentile_data + latency_percentiles=percentile_data, + ciphers=', '.join(ciphers), ) if args.output_format == 'json': @@ -183,7 +197,8 @@ if __name__ == '__main__': "latency_max": {latency_max}, "latency_std": {latency_std}, "latency_cv": {latency_cv}, - "latency_percentiles": {latency_percentiles} + "latency_percentiles": {latency_percentiles}, + "ciphers": "{ciphers}" }}'''.format(**data) else: data['latency_percentiles'] = '; '.join( @@ -196,6 +211,7 @@ std: {latency_std}ms ({latency_cv}%) Latency distribution: {latency_percentiles} Requests/sec: {rps} Transfer/sec: {transfer}MiB +Ciphers: {ciphers} '''.format(duration=DURATION, size=round(MSGSIZE / 1024, 2), **data) print(output) diff --git a/run_benchmarks b/run_benchmarks index e38641b..49a3ba9 100755 --- a/run_benchmarks +++ b/run_benchmarks @@ -26,6 +26,7 @@ server_base = ['docker', 'run', '--rm', '-t', '-p', '25000:25000', '-e', 'GID={}'.format(os.getegid()), '-v', '{_cache}:/var/lib/cache'.format(_cache=_cache), '-v', '{_socket}:/tmp/sockets'.format(_socket=_socket), + '-w', '/usr/src/servers', '--name', 'magicbench', 'magic/benchmark'] python = ['vex', 'bench', 'python'] @@ -41,6 +42,7 @@ unix_client = echo_client + ['--addr={}'.format(unix_address)] http_client = ['./http_client', '--output-format=json', '--addr={}'.format(tcp_address)] readline_client = tcp_client + ['--mpr=5'] +ssl_client = tcp_client + ['--ssl'] benchmarks = [{ 'name': 'tcpecho-gevent-sockets', @@ -213,6 +215,60 @@ benchmarks = [{ 'server': ['/usr/src/servers/gohttp'], 'server_address': tcp_address, 'client': http_client, +}, { + 'name': 'ssl-threaded', + 'title': 'SSL echo server (threaded)', + 'server': python + ['/usr/src/servers/threadsslecho.py'], + 'server_address': tcp_address, + 'client': ssl_client, +}, { + 'name': 'ssl-twisted', + 'title': 'SSL echo server (twisted)', + 'server': python + ['/usr/src/servers/twistedecho.py', '--ssl'], + 'server_address': tcp_address, + 'client': ssl_client, +}, { + 'name': 'ssl-gevent', + 'title': 'SSL echo server (gevent)', + 'server': python + ['/usr/src/servers/gevecho.py', '--ssl'], + 'server_address': tcp_address, + 'client': ssl_client, +}, { + 'name': 'ssl-golang-sockets', + 'title': 'SSL echo server (golang)', + 'server': ['/usr/src/servers/goecho', '--ssl'], + 'server_address': tcp_address, + 'client': ssl_client, +}, { + 'name': 'ssl-nodejs-streams', + 'title': 'SSL echo server (nodejs)', + 'server': nodejs + ['/usr/src/servers/nodeecho.js', '--ssl'], + 'server_address': tcp_address, + 'client': ssl_client, +}, { + 'name': 'ssl-asyncio-protocol', + 'title': 'SSL echo server (asyncio)', + 'server': python + ['/usr/src/servers/asyncioecho.py', + '--addr=0.0.0.0:25000', '--ssl', + '--proto'], + 'server_address': tcp_address, + 'client': ssl_client, +}, { + 'name': 'ssl-uvloop-protocol', + 'title': 'SSL echo server (uvloop)', + 'server': python + ['/usr/src/servers/asyncioecho.py', + '--addr=0.0.0.0:25000', '--ssl', + '--proto', '--uvloop'], + 'server_address': tcp_address, + 'client': ssl_client, +}, { + 'name': 'ssl-uvloop-buffered-protocol', + 'title': 'SSL echo server (uvloop buffered)', + 'server': python + ['/usr/src/servers/asyncioecho.py', + '--addr=0.0.0.0:25000', '--ssl', + '--proto', '--uvloop', '--buffered'], + 'server_address': tcp_address, + 'client': ssl_client, }] @@ -245,6 +301,13 @@ def start_and_wait_for_server(server_cmd, address, timeout=60): sock.settimeout(time.monotonic() - start) try: sock.connect(addr) + if 'ssl' in (' '.join(server_cmd)).lower(): + import ssl + ssl_context = ssl.create_default_context() + ssl_context.check_hostname = False + ssl_context.verify_mode = ssl.CERT_NONE + sock = ssl_context.wrap_socket(sock) + sock.do_handshake() sock.sendall(b'GET / HTTP/1.0\r\n\r\n') if sock.recv(4): print('Server is up and running.') @@ -297,7 +360,7 @@ def kill_server(): if server_container_exists(): print('Removing server container...') - subprocess.check_output(['docker', 'rm', 'magicbench']) + subprocess.check_output(['docker', 'rm', '-f', 'magicbench']) def format_report(data, target_file): @@ -348,6 +411,7 @@ def format_report(data, target_file): ('Mean latency', []), ('Max latency', []), ('Latency variation', []), + ('Ciphers', []) )) variations = benchmark['variations'] @@ -370,6 +434,7 @@ def format_report(data, target_file): '{}ms'.format(variation['latency_max'])) brecords['Latency variation'].append('{}ms ({}%)'.format( variation['latency_std'], variation['latency_cv'])) + brecords['Ciphers'].append(variation['ciphers']) vc = len(data['concurrency_levels']) * len(data['payload_size_levels']) @@ -538,6 +603,7 @@ def main(): Latency distribution: {latency_percentiles} Requests/sec: {rps} Transfer/sec: {transfer}MiB + Ciphers: {ciphers} ''').format(duration=duration, **format_data) print(output) @@ -548,6 +614,59 @@ def main(): print() + if 'ssl' in args.benchmarks: + title = 'ssl-raw' + print(title) + print('=' * len(title)) + print() + + benchmark_data = { + 'name': title, + 'variations': [] + } + + benchmarks_data.append(benchmark_data) + ssl_cmd = server_base + ['/usr/src/servers/sslbench', 'bulk', + 'ECDHE-RSA-AES128-GCM-SHA256'] + for variation in variations: + title = 'BENCHMARK: {}'.format(variation['title']) + print(title) + print('-' * len(title)) + msgsize = variation['payload_size'] + + cmd = ssl_cmd + [str(msgsize)] + print(' ' + ' '.join(cmd)) + output = subprocess.check_output(cmd, universal_newlines=True) + data = json.loads(output) + data['transfer'] = round(msgsize * data['messages'] / + data['duration'] / 1024 / 1024, 2) + data['rps'] = round(data['messages'] / data['duration'], 2) + avg = round(data['duration'] / data['messages'], 3) + data['latency_mean'] = data['latency_std'] = data['latency_cv'] = avg + data['latency_min'] = round(data['latency_min'], 3) + data['latency_max'] = round(data['latency_max'], 3) + data['latency_percentiles'] = [(p, avg) for p in [25, 50, 75, 90, 99, 99.99]] + + format_data = data.copy() + + format_data['latency_percentiles'] = '; '.join( + '{}% under {}ms'.format(*v) + for v in data['latency_percentiles']) + + output = textwrap.dedent('''\ + {messages} messages in {duration} seconds + Latency: min {latency_min}ms; max {latency_max}ms; mean {latency_mean}ms; std {latency_std}ms ({latency_cv}%); + Latency distribution: {latency_percentiles} + Requests/sec: {rps} + Transfer/sec: {transfer}MiB + Ciphers: {ciphers} + ''').format(**format_data) + + print(output) + benchmark_data['variations'].append(data) + + print() + if args.save_json or args.save_html: info_cmd = server_base + python + ['/usr/src/servers/platinfo.py'] print(' ' + ' '.join(info_cmd)) diff --git a/servers/asyncioecho.py b/servers/asyncioecho.py index f8e8d3b..9abd31a 100644 --- a/servers/asyncioecho.py +++ b/servers/asyncioecho.py @@ -10,7 +10,7 @@ PRINT = 0 -async def echo_server(loop, address, unix): +async def echo_server(loop, address, unix, ssl=None): if unix: sock = socket(AF_UNIX, SOCK_STREAM) else: @@ -23,10 +23,12 @@ async def echo_server(loop, address, unix): print('Server listening at', address) with sock: while True: - client, addr = await loop.sock_accept(sock) - if PRINT: - print('Connection from', addr) - loop.create_task(echo_client(loop, client)) + client, addr = await loop.sock_accept(sock) + if PRINT: + print('Connection from', addr) + if ssl: + client = ssl.wrap_socket(client, server_side=True) + loop.create_task(echo_client(loop, client)) async def echo_client(loop, client): @@ -79,6 +81,27 @@ def data_received(self, data): self.transport.write(data) +class EchoBufferedProtocol(asyncio.BufferedProtocol): + def connection_made(self, transport): + self.transport = transport + self.buffer = bytearray(256 * 1024) + self.view = memoryview(self.buffer) + sock = transport.get_extra_info('socket') + try: + sock.setsockopt(IPPROTO_TCP, TCP_NODELAY, 1) + except (OSError, NameError): + pass + + def connection_lost(self, exc): + self.transport = None + + def get_buffer(self, sizehint): + return self.buffer + + def buffer_updated(self, nbytes): + self.transport.write(self.view[:nbytes].tobytes()) + + async def print_debug(loop): while True: print(chr(27) + "[2J") # clear screen @@ -91,8 +114,10 @@ async def print_debug(loop): parser.add_argument('--uvloop', default=False, action='store_true') parser.add_argument('--streams', default=False, action='store_true') parser.add_argument('--proto', default=False, action='store_true') + parser.add_argument('--buffered', default=False, action='store_true') parser.add_argument('--addr', default='127.0.0.1:25000', type=str) parser.add_argument('--print', default=False, action='store_true') + parser.add_argument('--ssl', action='store_true', help='enable SSL') args = parser.parse_args() if args.uvloop: @@ -102,6 +127,17 @@ async def print_debug(loop): loop = asyncio.new_event_loop() print('using asyncio loop') + if args.ssl: + import ssl + path = os.path.dirname(os.path.abspath(__file__)) + KEYFILE = os.path.join(path, "ssl_test_rsa") + CERTFILE = os.path.join(path, "ssl_test.crt") + context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) + context.load_cert_chain(certfile=CERTFILE, keyfile=KEYFILE) + ssl = context + else: + ssl = None + asyncio.set_event_loop(loop) loop.set_debug(False) @@ -130,14 +166,18 @@ async def print_debug(loop): print('cannot use --stream and --proto simultaneously') exit(1) + if args.proto: + print('cannot use --stream and --buffered simultaneously') + exit(1) + print('using asyncio/streams') if unix: coro = asyncio.start_unix_server(echo_client_streams, - addr, loop=loop, + addr, loop=loop, ssl=ssl, limit=1024 * 1024) else: coro = asyncio.start_server(echo_client_streams, - *addr, loop=loop, + *addr, loop=loop, ssl=ssl, limit=1024 * 1024) srv = loop.run_until_complete(coro) elif args.proto: @@ -145,15 +185,21 @@ async def print_debug(loop): print('cannot use --stream and --proto simultaneously') exit(1) - print('using simple protocol') + if args.buffered: + print('using buffered protocol') + protocol = EchoBufferedProtocol + else: + print('using simple protocol') + protocol = EchoProtocol + if unix: - coro = loop.create_unix_server(EchoProtocol, addr) + coro = loop.create_unix_server(protocol, addr, ssl=ssl) else: - coro = loop.create_server(EchoProtocol, *addr) + coro = loop.create_server(protocol, *addr, ssl=ssl) srv = loop.run_until_complete(coro) else: print('using sock_recv/sock_sendall') - loop.create_task(echo_server(loop, addr, unix)) + loop.create_task(echo_server(loop, addr, unix, ssl=ssl)) try: loop.run_forever() finally: diff --git a/servers/gevecho.py b/servers/gevecho.py index 6577d16..19b5cb1 100644 --- a/servers/gevecho.py +++ b/servers/gevecho.py @@ -1,5 +1,7 @@ # Taken from curio: https://github.com/dabeaz/curio +import os +import sys from gevent.server import StreamServer from socket import * @@ -18,5 +20,10 @@ def echo(socket, address): socket.close() if __name__ == '__main__': - server = StreamServer(('0.0.0.0', 25000), echo) + kwargs = {} + if len(sys.argv) > 1 and sys.argv[1] == '--ssl': + path = os.path.dirname(os.path.abspath(__file__)) + kwargs['keyfile'] = os.path.join(path, "ssl_test_rsa") + kwargs['certfile'] = os.path.join(path, "ssl_test.crt") + server = StreamServer(('0.0.0.0', 25000), echo, **kwargs) server.serve_forever() diff --git a/servers/goecho.go b/servers/goecho.go index e4db9e0..7f7ef71 100644 --- a/servers/goecho.go +++ b/servers/goecho.go @@ -3,15 +3,58 @@ package main import ( + "crypto/tls" "fmt" "net" + "os" "strconv" ) const PORT = 25000 +const serverKey = `-----BEGIN PRIVATE KEY----- +MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBANtb0+YrKuxevGpm +LrjaUhZSgz6zFAmuGFmKmUbdjmfv9zSmmdsQIksK++jK0Be9LeZy20j6ahOfuVa0 +ufEmPoP7Fy4hXegKZR9cCWcIe/A6H2xWF1IIJLRTLaU8ol/I7T+um5HD5AwAwNPP +USNU0Eegmvp+xxWu3NX2m1Veot85AgMBAAECgYA3ZdZ673X0oexFlq7AAmrutkHt +CL7LvwrpOiaBjhyTxTeSNWzvtQBkIU8DOI0bIazA4UreAFffwtvEuPmonDb3F+Iq +SMAu42XcGyVZEl+gHlTPU9XRX7nTOXVt+MlRRRxL6t9GkGfUAXI3XxJDXW3c0vBK +UL9xqD8cORXOfE06rQJBAP8mEX1ERkR64Ptsoe4281vjTlNfIbs7NMPkUnrn9N/Y +BLhjNIfQ3HFZG8BTMLfX7kCS9D593DW5tV4Z9BP/c6cCQQDcFzCcVArNh2JSywOQ +ZfTfRbJg/Z5Lt9Fkngv1meeGNPgIMLN8Sg679pAOOWmzdMO3V706rNPzSVMME7E5 +oPIfAkEA8pDddarP5tCvTTgUpmTFbakm0KoTZm2+FzHcnA4jRh+XNTjTOv98Y6Ik +eO5d1ZnKXseWvkZncQgxfdnMqqpj5wJAcNq/RVne1DbYlwWchT2Si65MYmmJ8t+F +0mcsULqjOnEMwf5e+ptq5LzwbyrHZYq5FNk7ocufPv/ZQrcSSC+cFwJBAKvOJByS +x56qyGeZLOQlWS2JS3KJo59XuLFGqcbgN9Om9xFa41Yb4N9NvplFivsvZdw3m1Q/ +SPIXQuT8RMPDVNQ= +-----END PRIVATE KEY----- +` + +const serverCert = `-----BEGIN CERTIFICATE----- +MIICVDCCAb2gAwIBAgIJANfHOBkZr8JOMA0GCSqGSIb3DQEBBQUAMF8xCzAJBgNV +BAYTAlhZMRcwFQYDVQQHEw5DYXN0bGUgQW50aHJheDEjMCEGA1UEChMaUHl0aG9u +IFNvZnR3YXJlIEZvdW5kYXRpb24xEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xMDEw +MDgyMzAxNTZaFw0yMDEwMDUyMzAxNTZaMF8xCzAJBgNVBAYTAlhZMRcwFQYDVQQH +Ew5DYXN0bGUgQW50aHJheDEjMCEGA1UEChMaUHl0aG9uIFNvZnR3YXJlIEZvdW5k +YXRpb24xEjAQBgNVBAMTCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAw +gYkCgYEA21vT5isq7F68amYuuNpSFlKDPrMUCa4YWYqZRt2OZ+/3NKaZ2xAiSwr7 +6MrQF70t5nLbSPpqE5+5VrS58SY+g/sXLiFd6AplH1wJZwh78DofbFYXUggktFMt +pTyiX8jtP66bkcPkDADA089RI1TQR6Ca+n7HFa7c1fabVV6i3zkCAwEAAaMYMBYw +FAYDVR0RBA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3DQEBBQUAA4GBAHPctQBEQ4wd +BJ6+JcpIraopLn8BGhbjNWj40mmRqWB/NAWF6M5ne7KpGAu7tLeG4hb1zLaldK8G +lxy2GPSRF6LFS48dpEj2HbMv2nvv6xxalDMJ9+DicWgAKTQ6bcX2j3GUkCR0g/T1 +CRlNBAAlvhKzO7Clpf9l0YKBEfraJByX +-----END CERTIFICATE----- +` + func main() { - server, err := net.Listen("tcp", ":"+strconv.Itoa(PORT)) + if len(os.Args) > 1 && os.Args[1] == "--ssl" { + cer, err := tls.X509KeyPair([]byte(serverCert), []byte(serverKey)) + config := &tls.Config{Certificates: []tls.Certificate{cer}} + server, err := tls.Listen("tcp", ":"+strconv.Itoa(PORT), config) + } else { + server, err := net.Listen("tcp", ":"+strconv.Itoa(PORT)) + } if server == nil { panic("couldn't start listening: " + err.Error()) } @@ -41,3 +84,4 @@ func handleConn(client net.Conn) { } } } + diff --git a/servers/nodeecho.js b/servers/nodeecho.js index b424d6f..dfbbf52 100644 --- a/servers/nodeecho.js +++ b/servers/nodeecho.js @@ -4,9 +4,54 @@ // $ /usr/local/bin/node nodeecho.js var net = require('net'); -net.createServer(function(socket){ +var tls = require('tls'); +var options = { + key: `-----BEGIN PRIVATE KEY----- +MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBANtb0+YrKuxevGpm +LrjaUhZSgz6zFAmuGFmKmUbdjmfv9zSmmdsQIksK++jK0Be9LeZy20j6ahOfuVa0 +ufEmPoP7Fy4hXegKZR9cCWcIe/A6H2xWF1IIJLRTLaU8ol/I7T+um5HD5AwAwNPP +USNU0Eegmvp+xxWu3NX2m1Veot85AgMBAAECgYA3ZdZ673X0oexFlq7AAmrutkHt +CL7LvwrpOiaBjhyTxTeSNWzvtQBkIU8DOI0bIazA4UreAFffwtvEuPmonDb3F+Iq +SMAu42XcGyVZEl+gHlTPU9XRX7nTOXVt+MlRRRxL6t9GkGfUAXI3XxJDXW3c0vBK +UL9xqD8cORXOfE06rQJBAP8mEX1ERkR64Ptsoe4281vjTlNfIbs7NMPkUnrn9N/Y +BLhjNIfQ3HFZG8BTMLfX7kCS9D593DW5tV4Z9BP/c6cCQQDcFzCcVArNh2JSywOQ +ZfTfRbJg/Z5Lt9Fkngv1meeGNPgIMLN8Sg679pAOOWmzdMO3V706rNPzSVMME7E5 +oPIfAkEA8pDddarP5tCvTTgUpmTFbakm0KoTZm2+FzHcnA4jRh+XNTjTOv98Y6Ik +eO5d1ZnKXseWvkZncQgxfdnMqqpj5wJAcNq/RVne1DbYlwWchT2Si65MYmmJ8t+F +0mcsULqjOnEMwf5e+ptq5LzwbyrHZYq5FNk7ocufPv/ZQrcSSC+cFwJBAKvOJByS +x56qyGeZLOQlWS2JS3KJo59XuLFGqcbgN9Om9xFa41Yb4N9NvplFivsvZdw3m1Q/ +SPIXQuT8RMPDVNQ= +-----END PRIVATE KEY----- + `, + cert: `-----BEGIN CERTIFICATE----- +MIICVDCCAb2gAwIBAgIJANfHOBkZr8JOMA0GCSqGSIb3DQEBBQUAMF8xCzAJBgNV +BAYTAlhZMRcwFQYDVQQHEw5DYXN0bGUgQW50aHJheDEjMCEGA1UEChMaUHl0aG9u +IFNvZnR3YXJlIEZvdW5kYXRpb24xEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xMDEw +MDgyMzAxNTZaFw0yMDEwMDUyMzAxNTZaMF8xCzAJBgNVBAYTAlhZMRcwFQYDVQQH +Ew5DYXN0bGUgQW50aHJheDEjMCEGA1UEChMaUHl0aG9uIFNvZnR3YXJlIEZvdW5k +YXRpb24xEjAQBgNVBAMTCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAw +gYkCgYEA21vT5isq7F68amYuuNpSFlKDPrMUCa4YWYqZRt2OZ+/3NKaZ2xAiSwr7 +6MrQF70t5nLbSPpqE5+5VrS58SY+g/sXLiFd6AplH1wJZwh78DofbFYXUggktFMt +pTyiX8jtP66bkcPkDADA089RI1TQR6Ca+n7HFa7c1fabVV6i3zkCAwEAAaMYMBYw +FAYDVR0RBA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3DQEBBQUAA4GBAHPctQBEQ4wd +BJ6+JcpIraopLn8BGhbjNWj40mmRqWB/NAWF6M5ne7KpGAu7tLeG4hb1zLaldK8G +lxy2GPSRF6LFS48dpEj2HbMv2nvv6xxalDMJ9+DicWgAKTQ6bcX2j3GUkCR0g/T1 +CRlNBAAlvhKzO7Clpf9l0YKBEfraJByX +-----END CERTIFICATE----- +`, + + rejectUnauthorized: true, +}; +function handle(socket){ socket.setNoDelay(); socket.on('data', function(data){ socket.write(data) }); -}).listen(25000); +} +var server; +if (process.argv.length > 1 && process.argv[1] === '--ssl') { + server = tls.createServer(options, handle); +} else { + server = net.createServer(handle); +} +server.listen(25000); diff --git a/servers/platinfo.py b/servers/platinfo.py index 309b0c1..b909e93 100644 --- a/servers/platinfo.py +++ b/servers/platinfo.py @@ -1,7 +1,6 @@ import json import os.path import platform -import warnings if __name__ == '__main__': @@ -11,7 +10,7 @@ cpuinfo_f = '/proc/cpuinfo' - if (processor in {machine, 'unknown'} and os.path.exists(cpuinfo_f)): + if (processor in {machine, 'unknown', ''} and os.path.exists(cpuinfo_f)): with open(cpuinfo_f, 'rt') as f: for line in f: if line.startswith('model name'): @@ -19,22 +18,13 @@ processor = p.strip() break + distribution = None if 'Linux' in system: - with warnings.catch_warnings(): - # see issue #1322 for more information - warnings.filterwarnings( - 'ignore', - 'dist\(\) and linux_distribution\(\) ' - 'functions are deprecated .*', - PendingDeprecationWarning, - ) - distname, distversion, distid = platform.dist('') - - distribution = '{} {}'.format(distname, distversion).strip() - - else: - distribution = None + dist_f = '/etc/issue' + if os.path.exists(dist_f): + with open(dist_f, 'rt') as f: + distribution = f.read().strip() data = { 'cpu': processor, diff --git a/servers/requirements.txt b/servers/requirements.txt index 539b598..ed72307 100644 --- a/servers/requirements.txt +++ b/servers/requirements.txt @@ -1,7 +1,10 @@ curio==0.4 aiohttp==0.21.5 -gevent==1.1.1 +gevent==1.3.5 +greenlet==0.4.13 tornado==4.3 -Twisted==16.1.1 -httptools==0.0.9 -uvloop==0.4.28 +Twisted==18.7.0 +pyopenssl==18.0.0 +service-identity==17.0.0 +httptools==0.0.11 +uvloop==0.11.2 diff --git a/servers/ssl_test.crt b/servers/ssl_test.crt new file mode 100644 index 0000000..47a7d7e --- /dev/null +++ b/servers/ssl_test.crt @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICVDCCAb2gAwIBAgIJANfHOBkZr8JOMA0GCSqGSIb3DQEBBQUAMF8xCzAJBgNV +BAYTAlhZMRcwFQYDVQQHEw5DYXN0bGUgQW50aHJheDEjMCEGA1UEChMaUHl0aG9u +IFNvZnR3YXJlIEZvdW5kYXRpb24xEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xMDEw +MDgyMzAxNTZaFw0yMDEwMDUyMzAxNTZaMF8xCzAJBgNVBAYTAlhZMRcwFQYDVQQH +Ew5DYXN0bGUgQW50aHJheDEjMCEGA1UEChMaUHl0aG9uIFNvZnR3YXJlIEZvdW5k +YXRpb24xEjAQBgNVBAMTCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAw +gYkCgYEA21vT5isq7F68amYuuNpSFlKDPrMUCa4YWYqZRt2OZ+/3NKaZ2xAiSwr7 +6MrQF70t5nLbSPpqE5+5VrS58SY+g/sXLiFd6AplH1wJZwh78DofbFYXUggktFMt +pTyiX8jtP66bkcPkDADA089RI1TQR6Ca+n7HFa7c1fabVV6i3zkCAwEAAaMYMBYw +FAYDVR0RBA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3DQEBBQUAA4GBAHPctQBEQ4wd +BJ6+JcpIraopLn8BGhbjNWj40mmRqWB/NAWF6M5ne7KpGAu7tLeG4hb1zLaldK8G +lxy2GPSRF6LFS48dpEj2HbMv2nvv6xxalDMJ9+DicWgAKTQ6bcX2j3GUkCR0g/T1 +CRlNBAAlvhKzO7Clpf9l0YKBEfraJByX +-----END CERTIFICATE----- diff --git a/servers/ssl_test_rsa b/servers/ssl_test_rsa new file mode 100644 index 0000000..3fd3bbd --- /dev/null +++ b/servers/ssl_test_rsa @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBANtb0+YrKuxevGpm +LrjaUhZSgz6zFAmuGFmKmUbdjmfv9zSmmdsQIksK++jK0Be9LeZy20j6ahOfuVa0 +ufEmPoP7Fy4hXegKZR9cCWcIe/A6H2xWF1IIJLRTLaU8ol/I7T+um5HD5AwAwNPP +USNU0Eegmvp+xxWu3NX2m1Veot85AgMBAAECgYA3ZdZ673X0oexFlq7AAmrutkHt +CL7LvwrpOiaBjhyTxTeSNWzvtQBkIU8DOI0bIazA4UreAFffwtvEuPmonDb3F+Iq +SMAu42XcGyVZEl+gHlTPU9XRX7nTOXVt+MlRRRxL6t9GkGfUAXI3XxJDXW3c0vBK +UL9xqD8cORXOfE06rQJBAP8mEX1ERkR64Ptsoe4281vjTlNfIbs7NMPkUnrn9N/Y +BLhjNIfQ3HFZG8BTMLfX7kCS9D593DW5tV4Z9BP/c6cCQQDcFzCcVArNh2JSywOQ +ZfTfRbJg/Z5Lt9Fkngv1meeGNPgIMLN8Sg679pAOOWmzdMO3V706rNPzSVMME7E5 +oPIfAkEA8pDddarP5tCvTTgUpmTFbakm0KoTZm2+FzHcnA4jRh+XNTjTOv98Y6Ik +eO5d1ZnKXseWvkZncQgxfdnMqqpj5wJAcNq/RVne1DbYlwWchT2Si65MYmmJ8t+F +0mcsULqjOnEMwf5e+ptq5LzwbyrHZYq5FNk7ocufPv/ZQrcSSC+cFwJBAKvOJByS +x56qyGeZLOQlWS2JS3KJo59XuLFGqcbgN9Om9xFa41Yb4N9NvplFivsvZdw3m1Q/ +SPIXQuT8RMPDVNQ= +-----END PRIVATE KEY----- diff --git a/servers/sslbench.cc b/servers/sslbench.cc new file mode 100644 index 0000000..c1be3fe --- /dev/null +++ b/servers/sslbench.cc @@ -0,0 +1,435 @@ +// Copied from https://github.com/ctz/openssl-bench/blob/master/bench.cc +// with minimal changes + +#include +#include +#include + +#include + +#include +#include +#include + +#include + +static bool chkerr(int err) +{ + if (err == SSL_ERROR_SYSCALL) + { + ERR_print_errors_fp(stdout); + puts("error"); + exit(1); + return true; + } + return err == 0; +} + +class Context +{ + ssl_ctx_st *m_ctx; + +public: + Context(ssl_ctx_st *ctx) + : m_ctx(ctx) + { + SSL_CTX_set_options(m_ctx, + SSL_OP_NO_COMPRESSION | + SSL_R_NO_RENEGOTIATION); + } + + ~Context() + { + SSL_CTX_free(m_ctx); + } + + ssl_st * open() + { + return SSL_new(m_ctx); + } + + void load_server_creds() + { + int err; + err = SSL_CTX_use_certificate_chain_file(m_ctx, "./ssl_test.crt"); + assert(err == 1); + err = SSL_CTX_use_PrivateKey_file(m_ctx, "./ssl_test_rsa", SSL_FILETYPE_PEM); + assert(err == 1); + } + + void load_client_creds() + { + int err; + err = SSL_CTX_load_verify_locations(m_ctx, "./ssl_test.crt", NULL); + assert(err == 1); + } + + void set_ciphers(const char *ciphers) + { + SSL_CTX_set_cipher_list(m_ctx, ciphers); + } + + void enable_resume() + { + SSL_CTX_set_session_cache_mode(m_ctx, SSL_SESS_CACHE_BOTH); + SSL_CTX_set_session_id_context(m_ctx, (const uint8_t *) "localhost", strlen("localhost")); + } + + void disable_tickets() + { + long opts = SSL_CTX_get_options(m_ctx); + SSL_CTX_set_options(m_ctx, opts | SSL_OP_NO_TICKET); + } + + void dump_sess_stats() + { + printf("connects: %ld, connects-good: %ld, accepts: %ld, accepts-good: %ld\n", + SSL_CTX_sess_connect(m_ctx), + SSL_CTX_sess_connect_good(m_ctx), + SSL_CTX_sess_accept(m_ctx), + SSL_CTX_sess_accept_good(m_ctx)); + printf("sessions: %ld, hits: %ld, misses: %ld\n", + SSL_CTX_sess_number(m_ctx), + SSL_CTX_sess_hits(m_ctx), + SSL_CTX_sess_misses(m_ctx)); + } + + static Context server() + { + return Context(SSL_CTX_new(TLS_server_method())); + } + + static Context client() + { + return Context(SSL_CTX_new(TLS_client_method())); + } +}; + +class Conn +{ + ssl_st *m_ssl; + bio_st *m_reads_from; + bio_st *m_writes_to; + +public: + Conn(ssl_st *ssl) + : m_ssl(ssl), + m_reads_from(BIO_new(BIO_s_mem())), + m_writes_to(BIO_new(BIO_s_mem())) + { + SSL_set0_rbio(m_ssl, m_reads_from); + SSL_set0_wbio(m_ssl, m_writes_to); + } + + ~Conn() + { + SSL_free(m_ssl); + } + + void set_sni(const char *hostname) + { + int err; + err = SSL_set_tlsext_host_name(m_ssl, hostname); + assert(err == 1); + } + + void set_session(SSL_SESSION *sess) + { + int err = SSL_set_session(m_ssl, sess); + assert(err == 1); + } + + void ragged_close() + { + SSL_set_shutdown(m_ssl, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN); + } + + SSL_SESSION * get_session() + { + ragged_close(); + return SSL_get1_session(m_ssl); + } + + bool connect() + { + return chkerr(SSL_get_error(m_ssl, SSL_connect(m_ssl))); + } + + bool accept() + { + return chkerr(SSL_get_error(m_ssl, SSL_accept(m_ssl))); + } + + void transfer_to(Conn &other) + { + std::vector buf(256 * 1024, 0); + + while (true) + { + int err = BIO_read(m_writes_to, &buf[0], buf.size()); + + if (err == 0 || err == -1) + { + break; + } else if (err > 0) { + BIO_write(other.m_reads_from, &buf[0], err); + } + } + } + + void dump_cipher() + { + printf("\"ciphers\": \"%s %s\",", + SSL_get_cipher(m_ssl), + SSL_get_cipher_version(m_ssl)); + } + + void write(const uint8_t *buf, size_t n) + { + chkerr(SSL_get_error(m_ssl, + SSL_write(m_ssl, buf, n))); + } + + void read(uint8_t *buf, size_t n) + { + while (n) + { + int rd = SSL_read(m_ssl, buf, n); + + assert(rd >= 0); + buf += rd; + n -= rd; + } + } +}; + +static void do_handshake(Conn &client, Conn &server) +{ + client.set_sni("localhost"); + + while (true) + { + bool s_connected = server.accept(); + bool c_connected = client.connect(); + + if (s_connected && c_connected) + { + return; + } + + client.transfer_to(server); + server.transfer_to(client); + } +} + +static double get_time() +{ + timeval tv; + gettimeofday(&tv, NULL); + + double v = tv.tv_sec; + v += double(tv.tv_usec) / 1.e6; + return v; +} + +static void test_bulk(Context &server_ctx, Context &client_ctx, + const size_t plaintext_size) +{ + Conn server(server_ctx.open()); + Conn client(client_ctx.open()); + + do_handshake(client, server); + printf("{"); + client.dump_cipher(); + + std::vector plaintext(plaintext_size, 0); + double max_latency = 0; + double min_latency = 1024; + double req_start = 0; + double req_time = 0; + double total_time = 0; + const size_t total_data = plaintext_size < 8192 ? (64 * 1024 * 1024) : (1024 * 1024 * 1024); + const size_t rounds = total_data / plaintext_size; + + for (size_t i = 0; i < rounds; i++) + { + server.write(&plaintext[0], plaintext.size()); + server.transfer_to(client); + client.read(&plaintext[0], plaintext.size()); + } + + for (size_t i = 0; i < rounds; i++) + { + req_start = get_time(); + client.write(&plaintext[0], plaintext.size()); + client.transfer_to(server); + server.read(&plaintext[0], plaintext.size()); + server.write(&plaintext[0], plaintext.size()); + server.transfer_to(client); + client.read(&plaintext[0], plaintext.size()); + req_time = get_time() - req_start; + if (req_time > max_latency) { + max_latency = req_time; + } + if (req_time < min_latency) { + min_latency = req_time; + } + total_time += req_time; + } + + printf("\"messages\": %zu,", rounds); + printf("\"latency_min\": %g,", min_latency); + printf("\"latency_max\": %g,", max_latency); + printf("\"duration\": %g", total_time); + printf("}"); +} + +static void test_handshake(Context &server_ctx, Context &client_ctx) +{ + double time_client = 0; + double time_server = 0; + + const int handshakes = 2048; + + for (int i = 0; i < handshakes; i++) { + Conn server(server_ctx.open()); + Conn client(client_ctx.open()); + + client.set_sni("localhost"); + + double t; + + t = get_time(); + client.connect(); + client.transfer_to(server); + time_client += get_time() - t; + + t = get_time(); + server.accept(); + server.transfer_to(client); + time_server += get_time() - t; + + t = get_time(); + client.connect(); + client.transfer_to(server); + time_client += get_time() - t; + + t = get_time(); + server.accept(); + server.transfer_to(client); + time_server += get_time() - t; + + assert(server.accept()); + assert(client.accept()); + } + + printf("handshakes\tclient\t%g\thandshakes/s\n", + double(handshakes) / time_client); + printf("handshakes\tserver\t%g\thandshakes/s\n", + double(handshakes) / time_server); +} + +static void test_handshake_resume(Context &server_ctx, Context &client_ctx, + const bool with_tickets) +{ + double time_client = 0; + double time_server = 0; + + const int handshakes = 4096; + + server_ctx.enable_resume(); + client_ctx.enable_resume(); + + if (!with_tickets) { + server_ctx.disable_tickets(); + client_ctx.disable_tickets(); + } + + SSL_SESSION *client_session; + + { + Conn initial_server(server_ctx.open()); + Conn initial_client(client_ctx.open()); + initial_client.set_sni("localhost"); + do_handshake(initial_client, initial_server); + client_session = initial_client.get_session(); + initial_server.ragged_close(); + } + + for (int i = 0; i < handshakes; i++) { + Conn server(server_ctx.open()); + Conn client(client_ctx.open()); + + client.set_sni("localhost"); + client.set_session(client_session); + + double t; + + t = get_time(); + client.connect(); + client.transfer_to(server); + time_client += get_time() - t; + + t = get_time(); + server.accept(); + server.transfer_to(client); + time_server += get_time() - t; + + t = get_time(); + client.connect(); + client.transfer_to(server); + time_client += get_time() - t; + + t = get_time(); + server.accept(); + server.transfer_to(client); + time_server += get_time() - t; + + assert(server.accept()); + assert(client.connect()); + server.ragged_close(); + client.ragged_close(); + } + + server_ctx.dump_sess_stats(); + + printf("handshakes\tclient\t%g\thandshakes/s\n", + double(handshakes) / time_client); + printf("handshakes\tserver\t%g\thandshakes/s\n", + double(handshakes) / time_server); +} + +static int usage() +{ + puts("usage: bench "); + puts("usage: bench bulk "); + return 1; +} + +int main(int argc, char **argv) +{ + Context server_ctx = Context::server(); + Context client_ctx = Context::client(); + + if (argc < 3) { + return usage(); + } + + server_ctx.set_ciphers(argv[2]); + server_ctx.load_server_creds(); + client_ctx.load_client_creds(); + + if (!strcmp(argv[1], "bulk") && argc == 4) { + test_bulk(server_ctx, client_ctx, atoi(argv[3])); + } else if (!strcmp(argv[1], "handshake")) { + test_handshake(server_ctx, client_ctx); + } else if (!strcmp(argv[1], "handshake-resume")) { + test_handshake_resume(server_ctx, client_ctx, false); + } else if (!strcmp(argv[1], "handshake-ticket")) { + test_handshake_resume(server_ctx, client_ctx, true); + } else { + return usage(); + } + + return 0; +} diff --git a/servers/threadsslecho.py b/servers/threadsslecho.py new file mode 100644 index 0000000..9b29791 --- /dev/null +++ b/servers/threadsslecho.py @@ -0,0 +1,40 @@ +# Copied from https://github.com/dabeaz/curio/blob/master/examples/bench/threadecho.py +# A simple echo server with threads + +from socket import * +from threading import Thread +import ssl +import os + +path = os.path.dirname(os.path.abspath(__file__)) +KEYFILE = os.path.join(path, "ssl_test_rsa") # Private key +CERTFILE = os.path.join(path, "ssl_test.crt") # Certificate (self-signed) + + +def echo_server(addr): + sock = socket(AF_INET, SOCK_STREAM) + sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) + sock.bind(addr) + sock.listen(5) + while True: + client, addr = sock.accept() + Thread(target=echo_handler, args=(client, addr), daemon=True).start() + + +def echo_handler(client, addr): + print('Connection from', addr) + client.setsockopt(IPPROTO_TCP, TCP_NODELAY, 1) + ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) + ssl_context.load_cert_chain(certfile=CERTFILE, keyfile=KEYFILE) + client = ssl_context.wrap_socket(client, server_side=True) + with client: + while True: + data = client.recv(100000) + if not data: + break + client.sendall(data) + print('Connection closed') + + +if __name__ == '__main__': + echo_server(('', 25000)) diff --git a/servers/twistedecho.py b/servers/twistedecho.py index f5b5509..ce91a3f 100644 --- a/servers/twistedecho.py +++ b/servers/twistedecho.py @@ -2,7 +2,14 @@ # See LICENSE for details. -from twisted.internet import reactor, protocol +import os +import sys +from twisted.internet import reactor, protocol, ssl + + +path = os.path.dirname(os.path.abspath(__file__)) +KEYFILE = os.path.join(path, "ssl_test_rsa") # Private key +CERTFILE = os.path.join(path, "ssl_test.crt") # Certificate (self-signed) class Echo(protocol.Protocol): @@ -20,7 +27,11 @@ def main(): """This runs the protocol on port 25000""" factory = protocol.ServerFactory() factory.protocol = Echo - reactor.listenTCP(25000,factory) + if len(sys.argv) > 1 and sys.argv[1] == '--ssl': + reactor.listenSSL(25000, factory, + ssl.DefaultOpenSSLContextFactory(KEYFILE, CERTFILE)) + else: + reactor.listenTCP(25000,factory) reactor.run() # this only runs if the module was *not* imported