Merged silc_1_0_branch to trunk.
[silc.git] / apps / silcstress / silcstress.c
1 /*
2
3   silcstress.c
4
5   Author: Pekka Riikonen <priikone@silcnet.org>
6
7   Copyright (C) 2005 Pekka Riikonen
8
9   This program is free software; you can redistribute it and/or modify
10   it under the terms of the GNU General Public License as published by
11   the Free Software Foundation; version 2 of the License.
12
13   This program is distributed in the hope that it will be useful,
14   but WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16   GNU General Public License for more details.
17
18 */
19
20 /*
21   TODO:
22
23   - join to created clients with another client, to get the sent messages
24     as reply (channel message).
25   - create x clients
26
27 */
28
29 /*
30   Results:
31
32   SILC Server 0.9.20 (release), Linux 2.6.11, RAM 512 MB:
33
34     - 3000 channels == silcd size 6 MB (-c 3000 -n)
35     - 10000 channels == silcd size 17 MB (-c 10000 -n)
36     - 30000 channels == silcd size 48 MB (-c 30000 -n)
37       (creating 30000 channels take 14 minutes, because JOIN is LAG_STRICT
38        command, which prevents one client flooding server with commands +
39        Qos was enabled too)
40
41
42   SILC Server 0.9.20 (debug, stack-trace), Linux 2.6.11, RAM 512 MB:
43
44     - 3000 channels == silcd size 10 MB (-c 3000 -n)
45     - 10000 channels == silcd size 37 MB (-c 10000 -n)
46
47     - 1 channel, default data flood, QoS == silcd load < 1.0%
48     - 10 channels, default data flood, QoS == silcd load < 4.0%
49     - 100 channels, default data flood, QoS == silcd load < 5.0%
50       (Qos: rate=20, bytes_limit=500B, usec_limit=200000)
51
52 */
53
54 #include "silcincludes.h"
55 #include "silcclient.h"
56
57 typedef struct {
58   SilcClient client;
59   SilcClientConnection conn;
60   int msize;
61   int loops;
62   int flood;
63   int channels;
64   int threads;
65   bool nosend;
66   SilcMutex m;
67 } *SilcStress;
68
69 typedef struct {
70   SilcStress sc;
71   SilcDList channels;
72 } *SilcStressWorker;
73
74 SilcClientOperations ops;
75
76 /* Long command line options */
77 static struct option long_opts[] =
78 {
79   { "server", 1, NULL,'s' },
80   { "port", 1, NULL,'p' },
81   { "channels", 1, NULL,'c' },
82   { "msize", 1, NULL,'m' },
83   { "loops", 1, NULL,'l' },
84   { "flood", 1, NULL,'f' },
85   { "nosend", 0, NULL,'n' },
86   { "threads", 1, NULL,'t' },
87   { "debug", 2, NULL, 'd' },
88   { "help", 0, NULL, 'h' },
89   { "version", 0, NULL,'V' },
90
91   { NULL, 0, NULL, 0 }
92 };
93
94 static void silc_stress_usage(void)
95 {
96   printf(""
97 "Usage: silcstress [options]\n"
98 "\n"
99 "  Generic Options:\n"
100 "  -s  --server=server           Server to connect\n"
101 "  -p  --port=NUMBER             Server port to connect (def: 706)\n"
102 "  -c  --channels=NUMBER         Number of channels to create (def: 1)\n"
103 "  -m  --msize=NUMBER            Size of message in bytes (def: 512)\n"
104 "  -l  --loops=NUMBER            Number of loops to send data (def: 1024)\n"
105 "  -f  --flood=NUMBER            Send message in every usec (def: 50000)\n"
106 "  -n  --nosend                  Don't send any data\n"
107 "  -t  --threads=NUMBER          Number of threads to use (def: 1)\n"
108 "  -d  --debug=string            Enable debugging\n"
109 "  -h  --help                    Display this message and exit\n"
110 "  -V  --version                 Display version and exit\n"
111 "\n");
112   exit(0);
113 }
114
115 int main(int argc, char **argv)
116 {
117   int opt, option_index;
118   int c = 1, port = 706, b = 512, l = 1024, f = 50000, n = FALSE, t = 1;
119   char *server = NULL;
120   SilcStress sc;
121
122   if (argc > 1) {
123     while ((opt = getopt_long(argc, argv, "d:hVc:s:p:m:l:f:nt:",
124                               long_opts, &option_index)) != EOF) {
125       switch(opt) {
126         case 'h':
127           silc_stress_usage();
128           break;
129         case 'V':
130           printf("SILC Stress, version %s\n", silc_dist_version);
131           printf("(c) 2005 Pekka Riikonen <priikone@silcnet.org>\n");
132           exit(0);
133           break;
134         case 'd':
135 #ifdef SILC_DEBUG
136           silc_log_debug(TRUE);
137           silc_log_debug_hexdump(TRUE);
138           silc_log_quick(TRUE);
139           if (optarg)
140             silc_log_set_debug_string(optarg);
141 #else
142           fprintf(stderr,
143                   "Run-time debugging is not enabled. To enable it recompile\n"
144                   "the server with --enable-debug configuration option.\n");
145 #endif
146           break;
147         case 'c':
148           c = atoi(optarg);
149           break;
150         case 'l':
151           l = atoi(optarg);
152           break;
153         case 'f':
154           f = atoi(optarg);
155           break;
156         case 'm':
157           b = atoi(optarg);
158           break;
159         case 't':
160           t = atoi(optarg);
161           break;
162         case 'p':
163           port = atoi(optarg);
164           break;
165         case 's':
166           server = strdup(optarg);
167           break;
168         case 'n':
169           n = TRUE;
170           break;
171         default:
172           silc_stress_usage();
173           break;
174       }
175     }
176   }
177
178   if (!server)
179     silc_stress_usage();
180
181   sc = silc_calloc(1, sizeof(*sc));
182   if (!sc)
183     return 1;
184   sc->channels = c;
185   sc->msize = b;
186   sc->loops = l;
187   sc->flood = f;
188   sc->nosend = n;
189   sc->threads = t;
190
191   sc->client = silc_client_alloc(&ops, NULL, sc, NULL);
192   if (!sc->client)
193     return 1;
194
195   sc->client->username = silc_get_username();
196   sc->client->hostname = silc_net_localhost();
197   sc->client->realname = strdup("SILC STRESS");
198
199   if (!silc_client_init(sc->client))
200     return 1;
201
202   if (!silc_load_key_pair("silcstress.pub", "silcstress.prv", "",
203                           &sc->client->pkcs,
204                           &sc->client->public_key,
205                           &sc->client->private_key)) {
206     if (!silc_create_key_pair("rsa", 2048, "silcstress.pub",
207                               "silcstress.prv", NULL, "",
208                               &sc->client->pkcs,
209                               &sc->client->public_key,
210                               &sc->client->private_key, FALSE)) {
211       return 1;
212     }
213   }
214
215   silc_mutex_alloc(&sc->m);
216
217   silc_client_connect_to_server(sc->client, NULL, port, server, sc);
218
219   silc_client_run(sc->client);
220
221   silc_mutex_free(sc->m);
222   silc_client_free(sc->client);
223   silc_free(sc);
224   silc_free(server);
225
226   return 0;
227 }
228
229 /* Worker thread */
230
231 static void *
232 silc_stress_worker(void *context)
233 {
234   SilcStressWorker w = context;
235   SilcClient client = w->sc->client;
236   SilcClientConnection conn = w->sc->conn;
237   SilcChannelEntry channel;
238   char *tmp;
239   int i;
240
241   tmp = silc_calloc(w->sc->msize, sizeof(*tmp));
242   if (!tmp) {
243     fprintf(stderr, "out of mem\n");
244     silc_dlist_uninit(w->channels);
245     return NULL;
246   }
247
248   memset(tmp, 'M', w->sc->msize);
249
250   for (i = 0; i < w->sc->loops; i++) {
251     silc_dlist_start(w->channels);
252     while ((channel = silc_dlist_get(w->channels)) != SILC_LIST_END) {
253       /* Our packet routines don't like threads, so let's lock :( */
254       silc_mutex_lock(w->sc->m);
255       if (!w->sc->conn) {
256         silc_dlist_uninit(w->channels);
257         return NULL;
258       }
259       silc_client_send_channel_message(client, conn, channel, NULL, 0,
260                                        tmp, w->sc->msize, TRUE);
261       silc_mutex_unlock(w->sc->m);
262     }
263     usleep(w->sc->flood);
264   }
265
266   silc_dlist_uninit(w->channels);
267   silc_free(tmp);
268
269   return NULL;
270 }
271
272
273 /* "say" client operation is a message from the client library to the
274    application.  It may include error messages or something else.  We
275    just dump them to screen. */
276
277 static void
278 silc_say(SilcClient client, SilcClientConnection conn,
279          SilcClientMessageType type, char *msg, ...)
280 {
281   char str[200];
282   va_list va;
283   va_start(va, msg);
284   vsnprintf(str, sizeof(str) - 1, msg, va);
285   fprintf(stdout, "%s\n", str);
286   va_end(va);
287 }
288
289
290 /* Message for a channel. The `sender' is the sender of the message
291    The `channel' is the channel. The `message' is the message.  Note
292    that `message' maybe NULL.  The `flags' indicates message flags
293    and it is used to determine how the message can be interpreted
294    (like it may tell the message is multimedia message). */
295
296 static void
297 silc_channel_message(SilcClient client, SilcClientConnection conn,
298                      SilcClientEntry sender, SilcChannelEntry channel,
299                      SilcMessagePayload payload,
300                      SilcChannelPrivateKey key,
301                      SilcMessageFlags flags, const unsigned char *message,
302                      SilcUInt32 message_len)
303 {
304
305 }
306
307
308 /* Private message to the client. The `sender' is the sender of the
309    message. The message is `message'and maybe NULL.  The `flags'
310    indicates message flags  and it is used to determine how the message
311    can be interpreted (like it may tell the message is multimedia
312    message). */
313
314 static void
315 silc_private_message(SilcClient client, SilcClientConnection conn,
316                      SilcClientEntry sender, SilcMessagePayload payload,
317                      SilcMessageFlags flags,
318                      const unsigned char *message,
319                      SilcUInt32 message_len)
320 {
321 }
322
323
324 /* Notify message to the client. The notify arguments are sent in the
325    same order as servers sends them. The arguments are same as received
326    from the server except for ID's.  If ID is received application receives
327    the corresponding entry to the ID. For example, if Client ID is received
328    application receives SilcClientEntry.  Also, if the notify type is
329    for channel the channel entry is sent to application (even if server
330    does not send it because client library gets the channel entry from
331    the Channel ID in the packet's header). */
332
333 static void
334 silc_notify(SilcClient client, SilcClientConnection conn,
335             SilcNotifyType type, ...)
336 {
337
338 }
339
340
341 /* Command handler. This function is called always in the command function.
342    If error occurs it will be called as well. `conn' is the associated
343    client connection. `cmd_context' is the command context that was
344    originally sent to the command. `success' is FALSE if error occurred
345    during command. `command' is the command being processed. It must be
346    noted that this is not reply from server. This is merely called just
347    after application has called the command. Just to tell application
348    that the command really was processed. */
349
350 static void
351 silc_command(SilcClient client, SilcClientConnection conn,
352              SilcClientCommandContext cmd_context, bool success,
353              SilcCommand command, SilcStatus status)
354 {
355   /* If error occurred in client library with our command, print the error */
356   if (status != SILC_STATUS_OK)
357     fprintf(stderr, "COMMAND %s: %s\n",
358             silc_get_command_name(command),
359             silc_get_status_message(status));
360 }
361
362
363 /* Command reply handler. This function is called always in the command reply
364    function. If error occurs it will be called as well. Normal scenario
365    is that it will be called after the received command data has been parsed
366    and processed. The function is used to pass the received command data to
367    the application.
368
369    `conn' is the associated client connection. `cmd_payload' is the command
370    payload data received from server and it can be ignored. It is provided
371    if the application would like to re-parse the received command data,
372    however, it must be noted that the data is parsed already by the library
373    thus the payload can be ignored. `success' is FALSE if error occurred.
374    In this case arguments are not sent to the application. The `status' is
375    the command reply status server returned. The `command' is the command
376    reply being processed. The function has variable argument list and each
377    command defines the number and type of arguments it passes to the
378    application (on error they are not sent). */
379
380 static void
381 silc_command_reply(SilcClient client, SilcClientConnection conn,
382                    SilcCommandPayload cmd_payload, bool success,
383                    SilcCommand command, SilcStatus status, ...)
384 {
385   SilcStress sc = client->application;
386   va_list va;
387
388   /* If error occurred in client library with our command, print the error */
389   if (status != SILC_STATUS_OK)
390     fprintf(stderr, "COMMAND REPLY %s: %s\n",
391             silc_get_command_name(command),
392             silc_get_status_message(status));
393
394   va_start(va, status);
395
396   /* Check for successful JOIN */
397   if (command == SILC_COMMAND_JOIN && sc->nosend == FALSE &&
398       silc_hash_table_count(conn->local_entry->channels) == sc->channels) {
399     /* Create worker threads for data sending */
400     SilcStressWorker w;
401     SilcHashTableList htl;
402     SilcChannelUser chu;
403     int i, k;
404
405     printf("Creating %d threads (%d channels/thread)\n",
406            sc->threads, silc_hash_table_count(conn->local_entry->channels) /
407            sc->threads);
408
409     silc_hash_table_list(conn->local_entry->channels, &htl);
410     for (i = 0; i < sc->threads; i++) {
411
412       w = silc_calloc(1, sizeof(*w));
413       if (!w) {
414         fprintf(stderr, "out of mem\n");
415         exit(1);
416       }
417
418       w->sc = sc;
419       w->channels = silc_dlist_init();
420       if (!w->channels) {
421         fprintf(stderr, "out of mem\n");
422         exit(1);
423       }
424
425       for (k = 0; k < (silc_hash_table_count(conn->local_entry->channels) /
426                        sc->threads); k++)
427         while (silc_hash_table_get(&htl, NULL, (void *)&chu))
428           silc_dlist_add(w->channels, chu->channel);
429
430       silc_thread_create(silc_stress_worker, w, FALSE);
431     }
432     silc_hash_table_list_reset(&htl);
433   }
434
435   va_end(va);
436 }
437
438
439 /* Called to indicate that connection was either successfully established
440    or connecting failed.  This is also the first time application receives
441    the SilcClientConnection objecet which it should save somewhere.
442    If the `success' is FALSE the application must always call the function
443    silc_client_close_connection. */
444
445 static void
446 silc_connected(SilcClient client, SilcClientConnection conn,
447                SilcClientConnectionStatus status)
448 {
449   SilcStress sc = client->application;
450   char tmp[16];
451   int i;
452
453   if (status != SILC_CLIENT_CONN_SUCCESS) {
454     fprintf(stderr, "Could not connect to server\n");
455     silc_client_close_connection(client, conn);
456     return;
457   }
458
459   fprintf(stdout, "Connected to server.\n");
460
461   /* Save the connection context */
462   sc->conn = conn;
463
464   /* Join channels */
465   for (i = 0; i < sc->channels; i++) {
466     memset(tmp, 0, sizeof(tmp));
467     snprintf(tmp, sizeof(tmp) - 1, "JOIN %d", i);
468
469     /* Our packet routines don't like threads, so let's lock :( */
470     silc_mutex_lock(sc->m);
471     silc_client_command_call(client, conn, tmp);
472     silc_mutex_unlock(sc->m);
473     usleep(50000);
474   }
475 }
476
477
478 /* Called to indicate that connection was disconnected to the server.
479    The `status' may tell the reason of the disconnection, and if the
480    `message' is non-NULL it may include the disconnection message
481    received from server. */
482
483 static void
484 silc_disconnected(SilcClient client, SilcClientConnection conn,
485                   SilcStatus status, const char *message)
486 {
487   SilcStress sc = client->application;
488
489   /* We got disconnected from server */
490   sc->conn = NULL;
491   fprintf(stdout, "%s:%s\n", silc_get_status_message(status),
492           message);
493 }
494
495
496 /* Find authentication method and authentication data by hostname and
497    port. The hostname may be IP address as well. When the authentication
498    method has been resolved the `completion' callback with the found
499    authentication method and authentication data is called. The `conn'
500    may be NULL. */
501
502 static void
503 silc_get_auth_method(SilcClient client, SilcClientConnection conn,
504                      char *hostname, SilcUInt16 port,
505                      SilcGetAuthMeth completion,
506                      void *context)
507 {
508   completion(TRUE, SILC_AUTH_NONE, NULL, 0, context);
509 }
510
511
512 /* Verifies received public key. The `conn_type' indicates which entity
513    (server, client etc.) has sent the public key. If user decides to trust
514    the application may save the key as trusted public key for later
515    use. The `completion' must be called after the public key has been
516    verified. */
517
518 static void
519 silc_verify_public_key(SilcClient client, SilcClientConnection conn,
520                        SilcSocketType conn_type, unsigned char *pk,
521                        SilcUInt32 pk_len, SilcSKEPKType pk_type,
522                        SilcVerifyPublicKey completion, void *context)
523 {
524   completion(TRUE, context);
525 }
526
527
528 /* Ask (interact, that is) a passphrase from user. The passphrase is
529    returned to the library by calling the `completion' callback with
530    the `context'. The returned passphrase SHOULD be in UTF-8 encoded,
531    if not then the library will attempt to encode. */
532
533 static void
534 silc_ask_passphrase(SilcClient client, SilcClientConnection conn,
535                     SilcAskPassphrase completion, void *context)
536 {
537   completion(NULL, 0, context);
538 }
539
540
541 /* Notifies application that failure packet was received.  This is called
542    if there is some protocol active in the client.  The `protocol' is the
543    protocol context.  The `failure' is opaque pointer to the failure
544    indication.  Note, that the `failure' is protocol dependant and
545    application must explicitly cast it to correct type.  Usually `failure'
546    is 32 bit failure type (see protocol specs for all protocol failure
547    types). */
548
549 static void
550 silc_failure(SilcClient client, SilcClientConnection conn,
551              SilcProtocol protocol, void *failure)
552 {
553   fprintf(stderr, "Connecting failed (protocol failure)\n");
554 }
555
556
557 /* Asks whether the user would like to perform the key agreement protocol.
558    This is called after we have received an key agreement packet or an
559    reply to our key agreement packet. This returns TRUE if the user wants
560    the library to perform the key agreement protocol and FALSE if it is not
561    desired (application may start it later by calling the function
562    silc_client_perform_key_agreement). If TRUE is returned also the
563    `completion' and `context' arguments must be set by the application. */
564
565 static bool
566 silc_key_agreement(SilcClient client, SilcClientConnection conn,
567                    SilcClientEntry client_entry, const char *hostname,
568                    SilcUInt16 port, SilcKeyAgreementCallback *completion,
569                    void **context)
570 {
571   return FALSE;
572 }
573
574
575 /* Notifies application that file transfer protocol session is being
576    requested by the remote client indicated by the `client_entry' from
577    the `hostname' and `port'. The `session_id' is the file transfer
578    session and it can be used to either accept or reject the file
579    transfer request, by calling the silc_client_file_receive or
580    silc_client_file_close, respectively. */
581
582 static void
583 silc_ftp(SilcClient client, SilcClientConnection conn,
584          SilcClientEntry client_entry, SilcUInt32 session_id,
585          const char *hostname, SilcUInt16 port)
586 {
587
588 }
589
590
591 /* Delivers SILC session detachment data indicated by `detach_data' to the
592    application.  If application has issued SILC_COMMAND_DETACH command
593    the client session in the SILC network is not quit.  The client remains
594    in the network but is detached.  The detachment data may be used later
595    to resume the session in the SILC Network.  The appliation is
596    responsible of saving the `detach_data', to for example in a file.
597
598    The detachment data can be given as argument to the functions
599    silc_client_connect_to_server, or silc_client_add_connection when
600    creating connection to remote server, inside SilcClientConnectionParams
601    structure.  If it is provided the client library will attempt to resume
602    the session in the network.  After the connection is created
603    successfully, the application is responsible of setting the user
604    interface for user into the same state it was before detaching (showing
605    same channels, channel modes, etc).  It can do this by fetching the
606    information (like joined channels) from the client library. */
607
608 static void
609 silc_detach(SilcClient client, SilcClientConnection conn,
610             const unsigned char *detach_data, SilcUInt32 detach_data_len)
611 {
612
613 }
614
615 SilcClientOperations ops = {
616   silc_say,
617   silc_channel_message,
618   silc_private_message,
619   silc_notify,
620   silc_command,
621   silc_command_reply,
622   silc_connected,
623   silc_disconnected,
624   silc_get_auth_method,
625   silc_verify_public_key,
626   silc_ask_passphrase,
627   silc_failure,
628   silc_key_agreement,
629   silc_ftp,
630   silc_detach
631 };