summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--test/weercd.conf12
-rwxr-xr-xtest/weercd.py346
2 files changed, 358 insertions, 0 deletions
diff --git a/test/weercd.conf b/test/weercd.conf
new file mode 100644
index 000000000..20748a0e5
--- /dev/null
+++ b/test/weercd.conf
@@ -0,0 +1,12 @@
+#
+# weercd configuration
+#
+host=
+port=7777
+debug=off
+wait=0
+sleep=0
+maxchans=5
+maxnicks=100
+usernotices=on
+channotices=on
diff --git a/test/weercd.py b/test/weercd.py
new file mode 100755
index 000000000..e734ab16f
--- /dev/null
+++ b/test/weercd.py
@@ -0,0 +1,346 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2011 Sebastien Helleu <flashcode@flashtux.org>
+#
+# This file is part of WeeChat, the extensible chat client.
+#
+# WeeChat is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# WeeChat is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with WeeChat. If not, see <http://www.gnu.org/licenses/>.
+#
+
+#
+# weercd - the WeeChat flood IRC server
+#
+# weercd is an IRC server that is designed to test client resistance and memory
+# usage (quickly detect memory leaks, for example with client scripts).
+# Various IRC commands are sent in a short time (privmsg, notice, join/quit, ..)
+#
+# This script works with Python 2.x and 3.x.
+#
+# It is *STRONGLY RECOMMENDED* to connect this server with a client in a test
+# environment:
+# - for WeeChat, another home with: `weechat-curses --dir /tmp/weechat`
+# - on a test machine, because CPU will be used a lot by client to display
+# messages from weercd
+# - if possible locally (ie server and client on same machine), to speed up data
+# exchange between server and client.
+#
+# Instructions to use this server with WeeChat:
+# 1. open a terminal and run server:
+# python weercd.py
+# 2. open another terminal and run WeeChat with home in /tmp:
+# weechat-curses --dir /tmp/weechat
+# 3. optional: install script(s) in /tmp/weechat/<language>/autoload/
+# 4. add server and connect to it:
+# /server add weercd 127.0.0.1/7777
+# /connect weercd
+# 5. wait some months.....
+# WeeChat still not crashed and does not use 200 TB of RAM ?
+# Yeah, it's stable \o/
+#
+
+import sys, socket, select, time, random, string
+
+NAME = 'weercd'
+VERSION = '0.1'
+
+options = {
+ 'host' : ['', 'Host for socket bind'],
+ 'port' : ['7777', 'Port for socket bind'],
+ 'debug' : ['off', 'Debug (on/off)'],
+ 'wait' : ['0', 'Time to wait before flooding client (float, in seconds)'],
+ 'sleep' : ['0', 'Sleep for select, delay between 2 messages sent to client (float, in seconds)'],
+ 'maxchans' : ['5', 'Max channels to join'],
+ 'maxnicks' : ['100', 'Max nicks per channel'],
+ 'usernotices': ['on', 'Send notices to user (on/off)'],
+ 'channotices': ['on', 'Send notices to channels (on/off)'],
+}
+
+def usage():
+ ''' Display usage. '''
+ global options
+ print('\nUsage: %s [option=value [option=value ...]] | [-h | --help]\n' % sys.argv[0])
+ print(' option=value option with value (see table below)')
+ print(' -h, --help display this help\n')
+ print('Options (name, default value, description):\n')
+ for key in options:
+ v = '"%s"' % options[key][0]
+ print(' %-12s %-10s %s' % (key, v, options[key][1]))
+ print('\nOptions can be written in %s.conf, with format for each line: option=value\n' % NAME)
+ sys.exit(0)
+
+def setoption(string):
+ ''' Set option with string using format "option=value". '''
+ global options
+ items = string.strip().split('=', 1)
+ if len(items) == 2:
+ key = items[0].strip()
+ if not key.startswith('#'):
+ value = items[1].strip()
+ if key in options:
+ options[key][0] = value
+ else:
+ print('WARNING: unknown option "%s"' % key)
+
+def getoption(option):
+ ''' Get value of an option. '''
+ global options
+ if option in options:
+ return options[option][0]
+ return None
+
+def getdictoptions():
+ ''' Get dict with options and values. '''
+ global options
+ d = {}
+ for key in options:
+ d[key] = options[key][0]
+ return d
+
+def readconfig(filename):
+ ''' Read configuration file. '''
+ try:
+ lines = open(filename, 'rb').readlines()
+ for line in lines:
+ setoption(line)
+ except:
+ pass
+
+def strrand(minlength=1, maxlength=50, spaces=False):
+ ''' Return string with random lenght and content. '''
+ length = random.randint(minlength, maxlength)
+ strspace = ''
+ if spaces:
+ strspace = ' '
+ return ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits + strspace) for x in range(length))
+
+class Client:
+ def __init__(self, sock, addr, **kwargs):
+ self.sock, self.addr = sock, addr
+ self.nick = ''
+ self.nicknumber = 0
+ self.channels = {}
+ self.lastbuf = ''
+ self.incount, self.outcount, self.inbytes, self.outbytes = 0, 0, 0, 0
+ self.quit, self.endmsg, self.endexcept = False, '', None
+ self.sleep = float(getoption('sleep'))
+ self.maxchans = int(getoption('maxchans'))
+ self.maxnicks = int(getoption('maxnicks'))
+ self.usernotices = (getoption('usernotices') == 'on')
+ self.channotices = (getoption('channotices') == 'on')
+ self.starttime = time.time()
+ self.connect()
+ if not self.quit:
+ self.flood()
+
+ def send(self, data):
+ ''' Send one message to client. '''
+ if getoption('debug') == 'on':
+ print('<-- %s' % data)
+ msg = '%s\r\n' % data
+ self.outbytes += len(msg)
+ self.sock.send(msg.encode('UTF-8'))
+ self.outcount += 1
+
+ def recv(self, data):
+ ''' Read one message from client. '''
+ if getoption('debug') == 'on':
+ print('--> %s' % data)
+ if data.startswith('PING '):
+ args = data[5:]
+ if args[0] == ':':
+ args = args[1:]
+ self.send('PONG :%s' % args)
+ elif data.startswith('NICK '):
+ self.nick = data[5:]
+ elif data.startswith('QUIT '):
+ self.quit = True
+ self.incount += 1
+
+ def read(self, timeout):
+ ''' Read data from client. '''
+ inr, outr, exceptr = select.select([self.sock], [], [], timeout)
+ if inr:
+ data = self.sock.recv(4096)
+ if data:
+ data = data.decode('UTF-8')
+ self.inbytes += len(data)
+ data = self.lastbuf + data
+ while 1:
+ pos = data.find('\r\n')
+ if pos < 0:
+ break
+ self.recv(data[0:pos])
+ data = data[pos+2:]
+ self.lastbuf = data
+
+ def connect(self):
+ ''' Tell client that connection is ok. '''
+ try:
+ while self.nick == '':
+ self.read(0.1)
+ self.send(':%s 001 %s :Welcome to the WeeChat IRC flood server' % (NAME, self.nick))
+ self.send(':%s 002 %s :Your host is %s, running version %s' % (NAME, self.nick, NAME, VERSION))
+ self.send(':%s 003 %s :Are you solid like a rock?' % (NAME, self.nick))
+ self.send(':%s 004 %s :Let\'s see!' % (NAME, self.nick))
+ except KeyboardInterrupt:
+ self.quit = True
+ self.endmsg = 'interrupted'
+ return
+
+ def chan_randnick(self, channel):
+ if len(self.channels[channel]) < 2:
+ return None
+ rnick = self.nick
+ while rnick == self.nick:
+ rnick = self.channels[channel][random.randint(0, len(self.channels[channel])-1)]
+ return rnick
+
+ def flood(self):
+ ''' Yay, funny stuff here! Flood client! '''
+ wait = int(getoption('wait'))
+ if wait > 0:
+ print('Wait %d seconds' % wait)
+ time.sleep(wait)
+ sys.stdout.write('Flooding client..')
+ sys.stdout.flush()
+ try:
+ while not self.quit:
+ self.read(self.sleep)
+ # global actions
+ action = random.randint(1, 2)
+ if action == 1:
+ # join
+ if len(self.channels) < self.maxchans:
+ channel = '#%s' % strrand(1, 25)
+ if not channel in self.channels:
+ self.send(':%s!%s JOIN :%s' % (self.nick, self.addr[0], channel))
+ self.send(':%s 353 %s = %s :@%s' % (NAME, self.nick, channel, self.nick))
+ self.send(':%s 366 %s %s :End of /NAMES list.' % (NAME, self.nick, channel))
+ self.channels[channel] = [self.nick]
+ elif action == 2 and self.usernotices:
+ # notice
+ self.send(':%s!%s@%s NOTICE %s :%s' % (
+ strrand(1, 10), strrand(1, 10), strrand(1, 10), self.nick, strrand(1, 400, True)))
+ # actions for each channel
+ for channel in self.channels:
+ action = random.randint(1, 50)
+ if action >= 1 and action <= 10:
+ # join
+ if len(self.channels[channel]) < self.maxnicks:
+ self.nicknumber += 1
+ newnick = '%s%d' % (strrand(1, 5), self.nicknumber)
+ self.send(':%s!%s@%s JOIN :%s' % (newnick, strrand(1, 10), strrand(1, 10), channel))
+ self.channels[channel].append(newnick)
+ elif action == 11:
+ # part/quit
+ if len(self.channels[channel]) > 0:
+ rnick = self.chan_randnick(channel)
+ if rnick:
+ command = 'QUIT :%s' % strrand(1, 30)
+ if random.randint(1, 2) == 1:
+ command = 'PART %s' % channel
+ self.send(':%s!%s@%s %s' % (rnick, strrand(1, 10), strrand(1, 10), command))
+ self.channels[channel].remove(rnick)
+ elif action == 12:
+ # kick
+ if len(self.channels[channel]) > 0:
+ rnick1 = self.chan_randnick(channel)
+ rnick2 = self.chan_randnick(channel)
+ if rnick1 and rnick2 and rnick1 != rnick2:
+ self.send(':%s!%s@%s KICK %s %s :%s' % (rnick1, strrand(1, 10), strrand(1, 10), channel, rnick2, strrand (1, 50)))
+ self.channels[channel].remove(rnick2)
+ else:
+ # message
+ if len(self.channels[channel]) > 0:
+ rnick = self.chan_randnick(channel)
+ if rnick:
+ msg = strrand(1, 400, True)
+ if self.channotices and random.randint(1,100) == 100:
+ # notice
+ self.send(':%s!%s@%s NOTICE %s :%s' % (rnick, strrand(1, 10), strrand (1, 10), channel, msg))
+ else:
+ # add random highlight
+ if random.randint(1, 100) == 100:
+ msg = '%s: %s' % (self.nick, msg)
+ action2 = random.randint(1,50)
+ if action2 == 1:
+ # action (/me)
+ msg = '\x01ACTION %s\x01' % msg
+ elif action2 == 2:
+ # version
+ msg = '\x01VERSION\x01'
+ self.send(':%s!%s@%s PRIVMSG %s :%s' % (rnick, strrand(1, 10), strrand(1, 10), channel, msg))
+ # display progress
+ if self.outcount % 1000 == 0:
+ sys.stdout.write('.')
+ sys.stdout.flush()
+ except Exception as e:
+ if self.quit:
+ self.endmsg = 'quit received'
+ else:
+ self.endmsg = 'connection lost'
+ self.endexcept = e
+ return
+ except KeyboardInterrupt:
+ self.endmsg = 'interrupted'
+ return
+ else:
+ self.endmsg = 'quit received'
+ return
+
+ def stats(self):
+ msgexcept = ''
+ if self.endexcept:
+ msgexcept = '(%s)' % self.endexcept
+ print(' %s %s' % (self.endmsg, msgexcept))
+ elapsed = time.time() - self.starttime
+ print('Elapsed: %.1fs - packets: in:%d, out:%d (%d/s) - bytes: in:%d, out: %d (%d/s)' % (
+ elapsed, self.incount, self.outcount, self.outcount / elapsed,
+ self.inbytes, self.outbytes, self.outbytes / elapsed))
+ if self.endmsg == 'connection lost':
+ print('Uh-oh! No quit received, client has crashed? Ahah \o/')
+
+ def __del__(self):
+ self.stats()
+ print('Closing connection with %s' % str(self.addr))
+ self.sock.close()
+
+def main():
+ if len(sys.argv) > 1 and (sys.argv[1] == '-h' or sys.argv[1] == '--help'):
+ usage()
+ readconfig('%s.conf' % NAME)
+ for arg in sys.argv:
+ setoption(arg)
+ print('%s %s - WeeChat flood IRC server' % (NAME, VERSION))
+ while 1:
+ print('Options: %s' % getdictoptions())
+ print('Listening on port %s (ctrl-C to exit)' % getoption('port'))
+ servsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ servsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ servsock.bind((getoption('host'), int(getoption('port'))))
+ servsock.listen(1)
+ clientsock = None
+ addr = None
+ try:
+ clientsock, addr = servsock.accept()
+ except KeyboardInterrupt:
+ servsock.close()
+ return
+ print('Connection from %s' % str(addr))
+ client = Client(clientsock, addr)
+ del client
+
+if __name__ == '__main__':
+ main()