From ab69b8f86f321d4c9ea7e36f0d4ce64c2edc775c Mon Sep 17 00:00:00 2001 From: Marcel Hellkamp Date: Sun, 20 Apr 2014 02:01:49 +0200 Subject: [PATCH] Fixed too-many-open-files ddos --- brain.py | 42 +++++++++++++++++++++----------- pixelflut.py | 69 ++++++++++++++++++++++++++++++++-------------------- 2 files changed, 71 insertions(+), 40 deletions(-) diff --git a/brain.py b/brain.py index f986984..56d5b20 100644 --- a/brain.py +++ b/brain.py @@ -2,6 +2,8 @@ import pixelflut import os import time +pixelcount = 0 + def guess_IP(): import socket s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) @@ -17,13 +19,14 @@ port = 1234 text = 'P1XELFLUT! v%s (%d)\n' % ( pixelflut.__version__, os.stat(__file__).st_mtime) -text += 'Connect to %s:%d\n\n' % (IP, port) -text += '>>> HELP\n' -text += '>>> SIZE\n' -text += '>>> TEXT x y text\n' -text += '>>> PX x y [RRGGBB (hex)]\n' -text += '... and more ...\n\n' -text += 'H A C K O N\n' +text += '$ echo "HELP" | netcat %s %d\n' % (IP, port) +text += 'https://github.com/defnull/pixelflut' + +help = 'Commands:' +help += '>>> HELP\n' +help += '>>> SIZE\n' +help += '>>> TEXT x y text\n' +help += '>>> PX x y [RRGGBB (hex)]\n' @on('LOAD') def callback(c): @@ -41,7 +44,7 @@ def on_resize(c): @on('CONNECT') def on_connect(c, client): pass - #print c.clients.keys() + #print client @on('KEYDOWN-c') def on_key_c(c): @@ -60,12 +63,13 @@ def on_key_s(c): @on('COMMAND-HELP') def on_help(canvas, client): - client.send(text) + client.send(help) @on('COMMAND-TEXT') def on_text(canvas, client, x, y, *words): x, y = int(x), int(y) - canvas.text(x, y, ' '.join(words), delay=0.1) + text = ' '.join(words)[:200] + canvas.text(x, y, text, delay=0.5) @on('COMMAND-SIZE') def on_size(canvas, client): @@ -77,20 +81,24 @@ def on_quit(canvas, client): @on('COMMAND-PX') def on_px(canvas, client, x, y, color=None): + global pixelcount + pixelcount += 1 client.last_pixel = time.time() x, y = int(x), int(y) if color: c = int(color, 16) - if c <= 16777215: + if len(color) == 6: r = (c & 0xff0000) >> 16 g = (c & 0x00ff00) >> 8 b = c & 0x0000ff a = 0xff - else: + elif len(color) == 8: r = (c & 0xff000000) >> 24 g = (c & 0x00ff0000) >> 16 b = (c & 0x0000ff00) >> 8 a = c & 0x000000ff + else: + return canvas.set_pixel(x, y, r, g, b, a) else: r,g,b,a = canvas.get_pixel(x,y) @@ -99,10 +107,16 @@ def on_px(canvas, client, x, y, color=None): last_save = 0 + @on('TICK') def on_tick(canvas, dt): - global last_save + global last_save, pixelcount + canvas.text(5, 5, text, delay=0) + if time.time() > last_save: last_save = time.time() + 5 canvas.save_as('save/mov_%d.png' % last_save) - canvas.text(5, 5, text, delay=0) + print len(canvas.clients) + canvas.text(5, 200, 'px/s %d' % (pixelcount/5), delay=0) + canvas.text(5, 208, 'Connections %d' % len([c for c in canvas.clients.values() if c.socket]), delay=0) + pixelcount = 0 diff --git a/pixelflut.py b/pixelflut.py index 227ba8d..244522b 100755 --- a/pixelflut.py +++ b/pixelflut.py @@ -1,11 +1,11 @@ #coding: utf8 -__version__ = '0.5' +__version__ = '0.6' import time from gevent import spawn, sleep as gsleep -from gevent.server import StreamServer -from gevent.coros import Semaphore +from gevent.socket import socket +from gevent.coros import Semaphore, RLock from gevent.queue import Queue from collections import deque import pygame @@ -33,6 +33,7 @@ class Client(object): # And this is used to limit clients to X messages per tick # We start at 0 (instead of x) to add a reconnect-penalty. self.limit = Semaphore(0) + self.lock = RLock() def send(self, line): self.sendbuffer.append(line.strip() + '\n') @@ -42,16 +43,21 @@ class Client(object): self.sendbuffer.append(line.strip() + '\n') def disconnect(self): - if self.socket: - self.socket.close() - self.socket = None + with self.lock: + if self.socket: + socket = self.socket + self.socket = None + socket.close() + log.info('Disconnect') def serve(self, socket): - self.socket = socket - sendall = self.socket.sendall - readline = self.socket.makefile().readline + with self.lock: + self.socket = socket + sendall = self.socket.sendall + readline = self.socket.makefile().readline + try: - while True: + while self.socket: self.limit.acquire() # Idea: Send first, receive later. If the client is to # slow to get the send-buffer empty, he cannot send. @@ -64,8 +70,9 @@ class Client(object): command = arguments.pop(0) try: self.canvas.fire('COMMAND-%s' % command.upper(), self, *arguments) - except TypeError, e: - self.nospam('ERROR %r :(' % e) + except Exception, e: + socket.send('ERROR %r :(' % e) + break finally: self.disconnect() @@ -79,7 +86,7 @@ class Client(object): class Canvas(object): - size = 640,480 + size = 640, 480 flags = pygame.RESIZABLE#|pygame.FULLSCREEN def __init__(self): @@ -95,24 +102,34 @@ class Canvas(object): self.font = pygame.font.Font(None, 17) def serve(self, host, port): - self.server = StreamServer((host, port), self.make_client) - self.server.start() - return spawn(self._loop) + self.host = host + self.port = port + + spawn(self._loop) + + self.socket = socket() + self.socket.bind((host, port)) + self.socket.listen(500) + while True: + sock, addr = self.socket.accept() + ip, port = addr + + log.info('Connect %s:%d', ip, port) + + client = self.clients.get(ip) + if not client: + client = self.clients[ip] = Client(self) - def make_client(self, socket, address): - ip = address[0] - client = self.clients.get(ip) - if client: client.disconnect() - else: - self.clients[ip] = client = Client(self) + spawn(self.handle_client, client, sock) + def handle_client(self, client, sock): try: self.fire('CONNECT', client) - client.serve(socket) # This blocks until ready + client.serve(sock) # This blocks until ready self.fire('DISCONNECT', client) finally: - client.disconnect() + client.disconnect() def _loop(self): while True: @@ -214,7 +231,7 @@ class Canvas(object): if __name__ == '__main__': - logging.basicConfig() + logging.basicConfig(level=logging.DEBUG) import optparse parser = optparse.OptionParser("usage: %prog [options] brain_script") @@ -229,7 +246,7 @@ if __name__ == '__main__': parser.error("incorrect number of arguments") canvas = Canvas() - task = canvas.serve(options.hostname, options.portnum) + task = spawn(canvas.serve, options.hostname, options.portnum) brainfile = args[0] mtime = 0