Fixed too-many-open-files ddos

This commit is contained in:
Marcel Hellkamp 2014-04-20 02:01:49 +02:00
parent 5a4cb85bea
commit ab69b8f86f
2 changed files with 71 additions and 40 deletions

View file

@ -2,6 +2,8 @@ import pixelflut
import os import os
import time import time
pixelcount = 0
def guess_IP(): def guess_IP():
import socket import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
@ -17,13 +19,14 @@ port = 1234
text = 'P1XELFLUT! v%s (%d)\n' % ( text = 'P1XELFLUT! v%s (%d)\n' % (
pixelflut.__version__, pixelflut.__version__,
os.stat(__file__).st_mtime) os.stat(__file__).st_mtime)
text += 'Connect to %s:%d\n\n' % (IP, port) text += '$ echo "HELP" | netcat %s %d\n' % (IP, port)
text += '>>> HELP\n' text += 'https://github.com/defnull/pixelflut'
text += '>>> SIZE\n'
text += '>>> TEXT x y text\n' help = 'Commands:'
text += '>>> PX x y [RRGGBB (hex)]\n' help += '>>> HELP\n'
text += '... and more ...\n\n' help += '>>> SIZE\n'
text += 'H A C K O N\n' help += '>>> TEXT x y text\n'
help += '>>> PX x y [RRGGBB (hex)]\n'
@on('LOAD') @on('LOAD')
def callback(c): def callback(c):
@ -41,7 +44,7 @@ def on_resize(c):
@on('CONNECT') @on('CONNECT')
def on_connect(c, client): def on_connect(c, client):
pass pass
#print c.clients.keys() #print client
@on('KEYDOWN-c') @on('KEYDOWN-c')
def on_key_c(c): def on_key_c(c):
@ -60,12 +63,13 @@ def on_key_s(c):
@on('COMMAND-HELP') @on('COMMAND-HELP')
def on_help(canvas, client): def on_help(canvas, client):
client.send(text) client.send(help)
@on('COMMAND-TEXT') @on('COMMAND-TEXT')
def on_text(canvas, client, x, y, *words): def on_text(canvas, client, x, y, *words):
x, y = int(x), int(y) 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') @on('COMMAND-SIZE')
def on_size(canvas, client): def on_size(canvas, client):
@ -77,20 +81,24 @@ def on_quit(canvas, client):
@on('COMMAND-PX') @on('COMMAND-PX')
def on_px(canvas, client, x, y, color=None): def on_px(canvas, client, x, y, color=None):
global pixelcount
pixelcount += 1
client.last_pixel = time.time() client.last_pixel = time.time()
x, y = int(x), int(y) x, y = int(x), int(y)
if color: if color:
c = int(color, 16) c = int(color, 16)
if c <= 16777215: if len(color) == 6:
r = (c & 0xff0000) >> 16 r = (c & 0xff0000) >> 16
g = (c & 0x00ff00) >> 8 g = (c & 0x00ff00) >> 8
b = c & 0x0000ff b = c & 0x0000ff
a = 0xff a = 0xff
else: elif len(color) == 8:
r = (c & 0xff000000) >> 24 r = (c & 0xff000000) >> 24
g = (c & 0x00ff0000) >> 16 g = (c & 0x00ff0000) >> 16
b = (c & 0x0000ff00) >> 8 b = (c & 0x0000ff00) >> 8
a = c & 0x000000ff a = c & 0x000000ff
else:
return
canvas.set_pixel(x, y, r, g, b, a) canvas.set_pixel(x, y, r, g, b, a)
else: else:
r,g,b,a = canvas.get_pixel(x,y) 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 last_save = 0
@on('TICK') @on('TICK')
def on_tick(canvas, dt): def on_tick(canvas, dt):
global last_save global last_save, pixelcount
canvas.text(5, 5, text, delay=0)
if time.time() > last_save: if time.time() > last_save:
last_save = time.time() + 5 last_save = time.time() + 5
canvas.save_as('save/mov_%d.png' % last_save) 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

View file

@ -1,11 +1,11 @@
#coding: utf8 #coding: utf8
__version__ = '0.5' __version__ = '0.6'
import time import time
from gevent import spawn, sleep as gsleep from gevent import spawn, sleep as gsleep
from gevent.server import StreamServer from gevent.socket import socket
from gevent.coros import Semaphore from gevent.coros import Semaphore, RLock
from gevent.queue import Queue from gevent.queue import Queue
from collections import deque from collections import deque
import pygame import pygame
@ -33,6 +33,7 @@ class Client(object):
# And this is used to limit clients to X messages per tick # And this is used to limit clients to X messages per tick
# We start at 0 (instead of x) to add a reconnect-penalty. # We start at 0 (instead of x) to add a reconnect-penalty.
self.limit = Semaphore(0) self.limit = Semaphore(0)
self.lock = RLock()
def send(self, line): def send(self, line):
self.sendbuffer.append(line.strip() + '\n') self.sendbuffer.append(line.strip() + '\n')
@ -42,16 +43,21 @@ class Client(object):
self.sendbuffer.append(line.strip() + '\n') self.sendbuffer.append(line.strip() + '\n')
def disconnect(self): def disconnect(self):
if self.socket: with self.lock:
self.socket.close() if self.socket:
self.socket = None socket = self.socket
self.socket = None
socket.close()
log.info('Disconnect')
def serve(self, socket): def serve(self, socket):
self.socket = socket with self.lock:
sendall = self.socket.sendall self.socket = socket
readline = self.socket.makefile().readline sendall = self.socket.sendall
readline = self.socket.makefile().readline
try: try:
while True: while self.socket:
self.limit.acquire() self.limit.acquire()
# Idea: Send first, receive later. If the client is to # Idea: Send first, receive later. If the client is to
# slow to get the send-buffer empty, he cannot send. # slow to get the send-buffer empty, he cannot send.
@ -64,8 +70,9 @@ class Client(object):
command = arguments.pop(0) command = arguments.pop(0)
try: try:
self.canvas.fire('COMMAND-%s' % command.upper(), self, *arguments) self.canvas.fire('COMMAND-%s' % command.upper(), self, *arguments)
except TypeError, e: except Exception, e:
self.nospam('ERROR %r :(' % e) socket.send('ERROR %r :(' % e)
break
finally: finally:
self.disconnect() self.disconnect()
@ -79,7 +86,7 @@ class Client(object):
class Canvas(object): class Canvas(object):
size = 640,480 size = 640, 480
flags = pygame.RESIZABLE#|pygame.FULLSCREEN flags = pygame.RESIZABLE#|pygame.FULLSCREEN
def __init__(self): def __init__(self):
@ -95,24 +102,34 @@ class Canvas(object):
self.font = pygame.font.Font(None, 17) self.font = pygame.font.Font(None, 17)
def serve(self, host, port): def serve(self, host, port):
self.server = StreamServer((host, port), self.make_client) self.host = host
self.server.start() self.port = port
return spawn(self._loop)
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() client.disconnect()
else: spawn(self.handle_client, client, sock)
self.clients[ip] = client = Client(self)
def handle_client(self, client, sock):
try: try:
self.fire('CONNECT', client) self.fire('CONNECT', client)
client.serve(socket) # This blocks until ready client.serve(sock) # This blocks until ready
self.fire('DISCONNECT', client) self.fire('DISCONNECT', client)
finally: finally:
client.disconnect() client.disconnect()
def _loop(self): def _loop(self):
while True: while True:
@ -214,7 +231,7 @@ class Canvas(object):
if __name__ == '__main__': if __name__ == '__main__':
logging.basicConfig() logging.basicConfig(level=logging.DEBUG)
import optparse import optparse
parser = optparse.OptionParser("usage: %prog [options] brain_script") parser = optparse.OptionParser("usage: %prog [options] brain_script")
@ -229,7 +246,7 @@ if __name__ == '__main__':
parser.error("incorrect number of arguments") parser.error("incorrect number of arguments")
canvas = Canvas() canvas = Canvas()
task = canvas.serve(options.hostname, options.portnum) task = spawn(canvas.serve, options.hostname, options.portnum)
brainfile = args[0] brainfile = args[0]
mtime = 0 mtime = 0