From Lars Roland: Tethereal version of SIP statistics tap, and fixes to
[obnox/wireshark/wip.git] / gtk / sip_stat.c
1 /* sip_stat.c
2  * sip_stat   2004 Martin Mathieson
3  *
4  * $Id: sip_stat.c,v 1.3 2004/03/30 18:55:47 guy Exp $
5  * Copied from http_stat.c
6  *
7  * Ethereal - Network traffic analyzer
8  * By Gerald Combs <gerald@ethereal.com>
9  * Copyright 1998 Gerald Combs
10  *
11  * This program is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU General Public License
13  * as published by the Free Software Foundation; either version 2
14  * of the License, or (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
24  */
25
26 #ifdef HAVE_CONFIG_H
27 # include "config.h"
28 #endif
29
30 #include <string.h>
31
32 #include <gtk/gtk.h>
33
34 #include <epan/packet_info.h>
35 #include <epan/epan.h>
36
37 #include "tap_menu.h"
38 #include "simple_dialog.h"
39 #include "ui_util.h"
40 #include "dlg_utils.h"
41 #include "tap.h"
42 #include "../register.h"
43 #include "../packet-sip.h"
44 #include "../globals.h"
45 #include "compat_macros.h"
46 #include "../tap_dfilter_dlg.h"
47 #include "tap_dfilter_dlg.h"
48
49 #define SUM_STR_MAX     1024
50
51 /* Used to keep track of the statistics for an entire program interface */
52 typedef struct _sip_stats_t {
53     char        *filter;
54     GtkWidget   *win;
55     GHashTable  *hash_responses;
56     GHashTable  *hash_requests;
57     guint32         packets;        /* number of sip packets, including continuations */
58     GtkWidget   *packets_label;
59
60     GtkWidget   *request_box;           /* container for INVITE, ... */
61
62     GtkWidget   *informational_table;   /* Status code between 100 and 199 */
63     GtkWidget   *success_table;         /*   200 and 299 */
64     GtkWidget   *redirection_table;         /*   300 and 399 */
65     GtkWidget   *client_error_table;    /*   400 and 499 */
66     GtkWidget   *server_errors_table;   /*   500 and 599 */
67     GtkWidget   *global_failures_table; /*   600 and 699 */
68 } sipstat_t;
69
70 /* Used to keep track of the stats for a specific response code
71  * for example it can be { 3, 404, "Not Found" ,...}
72  * which means we captured 3 reply sip/1.1 404 Not Found */
73 typedef struct _sip_response_code_t {
74     guint32      packets;                   /* 3 */
75     guint                response_code;         /* 404 */
76     gchar               *name;                      /* "Not Found" */
77     GtkWidget   *widget;                    /* Label where we display it */
78     GtkWidget   *table;                     /* Table in which we put it,
79                                        e.g. client_error_table */
80     sipstat_t   *sp;                /* Pointer back to main struct */
81 } sip_response_code_t;
82
83 /* Used to keep track of the stats for a specific request string */
84 typedef struct _sip_request_method_t {
85     gchar               *response;              /* eg. : INVITE */
86     guint32              packets;
87     GtkWidget   *widget;
88     sipstat_t   *sp;                /* Pointer back to main struct */
89 } sip_request_method_t;
90
91 /* TODO: extra codes to be added from SIP extensions? */
92 static const value_string vals_status_code[] = {
93     { 100, "Trying"},
94     { 180, "Ringing"},
95     { 181, "Call Is Being Forwarded"},
96     { 182, "Queued"},
97     { 183, "Session Progress"},
98     { 199, "Informational - Others" },
99
100     { 200, "OK"},
101     { 202, "Accepted"},
102     { 299, "Success - Others"}, /* used to keep track of other Success packets */
103
104     { 300, "Multiple Choices"},
105     { 301, "Moved Permanently"},
106     { 302, "Moved Temporarily"},
107     { 305, "Use Proxy"},
108     { 380, "Alternative Service"},
109     { 399, "Redirection - Others"},
110
111     { 400, "Bad Request"},
112     { 401, "Unauthorized"},
113     { 402, "Payment Required"},
114     { 403, "Forbidden"},
115     { 404, "Not Found"},
116     { 405, "Method Not Allowed"},
117     { 406, "Not Acceptable"},
118     { 407, "Proxy Authentication Required"},
119     { 408, "Request Timeout"},
120     { 410, "Gone"},
121     { 413, "Request Entity Too Large"},
122     { 414, "Request-URI Too Long"},
123     { 415, "Unsupported Media Type"},
124     { 416, "Unsupported URI Scheme"},
125     { 420, "Bad Extension"},
126     { 421, "Extension Required"},
127     { 423, "Interval Too Brief"},
128     { 480, "Temporarily Unavailable"},
129     { 481, "Call/Transaction Does Not Exist"},
130     { 482, "Loop Detected"},
131     { 483, "Too Many Hops"},
132     { 484, "Address Incomplete"},
133     { 485, "Ambiguous"},
134     { 486, "Busy Here"},
135     { 487, "Request Terminated"},
136     { 488, "Not Acceptable Here"},
137     { 489, "Bad Event"},
138     { 491, "Request Pending"},
139     { 493, "Undecipherable"},
140     { 499, "Client Error - Others"},
141
142     { 500, "Server Internal Error"},
143     { 501, "Not Implemented"},
144     { 502, "Bad Gateway"},
145     { 503, "Service Unavailable"},
146     { 504, "Server Time-out"},
147     { 505, "Version Not Supported"},
148     { 513, "Message Too Large"},
149     { 599, "Server Error - Others"},
150
151     { 600, "Busy Everywhere"},
152     { 603, "Decline"},
153     { 604, "Does Not Exist Anywhere"},
154     { 606, "Not Acceptable"},
155     { 699, "Global Failure - Others"},
156
157     { 0,        NULL}
158 };
159
160 /* Create tables for responses and requests */
161 static void
162 sip_init_hash(sipstat_t *sp)
163 {
164     int i;
165
166     /* Create responses table */
167     sp->hash_responses = g_hash_table_new(g_int_hash, g_int_equal);
168
169     /* Add all response codes */
170     for (i=0 ; vals_status_code[i].strptr ; i++)
171     {
172         gint *key = g_malloc (sizeof(gint));
173         sip_response_code_t *sc = g_malloc (sizeof(sip_response_code_t));
174         *key = vals_status_code[i].value;
175         sc->packets=0;
176         sc->response_code =  *key;
177         sc->name=vals_status_code[i].strptr;
178         sc->widget=NULL;
179         sc->table=NULL;
180         sc->sp = sp;
181         g_hash_table_insert(sc->sp->hash_responses, key, sc);
182     }
183     
184     /* Create empty requests table */
185     sp->hash_requests = g_hash_table_new(g_str_hash, g_str_equal);
186 }
187
188 /* Draw the entry for an individual request message */
189 static void
190 sip_draw_hash_requests(gchar *key _U_ , sip_request_method_t *data, gchar * unused _U_)
191 {
192     gchar string_buff[SUM_STR_MAX];
193
194     if (data->packets==0)
195     {
196         return;
197     }
198     
199     /* Build string showing method and count */
200     g_snprintf(string_buff, sizeof(string_buff),
201                "     %-11s : %3d packets", data->response, data->packets);
202     if (data->widget==NULL)
203     {
204         /* Create new label */
205         data->widget=gtk_label_new(string_buff);
206         gtk_misc_set_alignment(GTK_MISC(data->widget), 0.0, 0.5);
207         gtk_box_pack_start(GTK_BOX(data->sp->request_box), data->widget,FALSE,FALSE, 0);
208         gtk_widget_show(data->widget);
209     }
210     else
211     {
212         /* Update existing label */
213         gtk_label_set(GTK_LABEL(data->widget), string_buff);
214     }
215 }
216
217 /* Draw an individual response entry */
218 static void
219 sip_draw_hash_responses(gint * key _U_ , sip_response_code_t *data, gchar * unused _U_)
220 {
221     gchar string_buff[SUM_STR_MAX];
222
223     if (data==NULL)
224     {
225         g_warning("C'est quoi ce borderl key=%d\n", *key);
226     }
227     if (data->packets==0)
228     {
229         return;
230     }
231
232     /* Create an entry in the relevant box of the window */
233     if (data->widget==NULL)
234     {
235         guint16 x;
236         GtkWidget *tmp;
237         guint i = data->response_code;
238
239         /* Out of valid range - ignore */
240         if ((i<100)||(i>=700))
241         {
242             return;
243         }
244
245         /* Find the table matching the code */
246         if (i<200)
247         {
248             data->table = data->sp->informational_table;
249         }
250         else if (i<300)
251         {
252             data->table = data->sp->success_table;
253         }
254         else if (i<400)
255         {
256             data->table = data->sp->redirection_table;
257         }
258         else if (i<500)
259         {
260             data->table = data->sp->client_error_table;
261         }
262         else if (i < 600)
263         {
264             data->table = data->sp->server_errors_table;
265         }
266         else
267         {
268             data->table = data->sp->global_failures_table;
269         }
270
271         /* Get number of rows in table */
272         x = GTK_TABLE(data->table)->nrows;
273
274         /* Create a new label with this response, e.g. "SIP 180 Ringing" */
275         g_snprintf(string_buff, sizeof(string_buff),
276                    "SIP %3d %s ", data->response_code, data->name);
277         tmp = gtk_label_new(string_buff);
278
279         /* Insert the label in the correct place in the table */
280         gtk_table_attach_defaults(GTK_TABLE(data->table), tmp,  0, 1, x, x+1);
281         gtk_label_set_justify(GTK_LABEL(tmp), GTK_JUSTIFY_LEFT);
282         gtk_widget_show(tmp);
283
284         /* Show number of packets */
285         g_snprintf(string_buff, sizeof(string_buff), "%9d", data->packets);
286         data->widget=gtk_label_new(string_buff);
287
288         /* Show this widget in the right place */
289         gtk_table_attach_defaults(GTK_TABLE(data->table), data->widget, 1, 2,x,x+1);
290         gtk_label_set_justify(GTK_LABEL(data->widget), GTK_JUSTIFY_RIGHT);
291         gtk_widget_show(data->widget);
292
293         gtk_table_resize(GTK_TABLE(data->table), x+1, 4);
294
295     } else
296     {
297         /* Just update the existing label string */
298         g_snprintf(string_buff, sizeof(string_buff), "%9d", data->packets);
299         gtk_label_set(GTK_LABEL(data->widget), string_buff);
300     }
301 }
302
303
304
305 static void
306 sip_free_hash(gpointer key, gpointer value, gpointer user_data _U_)
307 {
308     g_free(key);
309     g_free(value);
310 }
311
312 static void
313 sip_reset_hash_responses(gchar *key _U_ , sip_response_code_t *data, gpointer ptr _U_)
314 {
315     data->packets = 0;
316 }
317
318 static void
319 sip_reset_hash_requests(gchar *key _U_ , sip_request_method_t *data, gpointer ptr _U_)
320 {
321     data->packets = 0;
322 }
323
324 static void
325 sipstat_reset(void *psp)
326 {
327     sipstat_t *sp = psp;
328     if (sp)
329     {
330         sp->packets = 0;
331         g_hash_table_foreach(sp->hash_responses, (GHFunc)sip_reset_hash_responses, NULL);
332         g_hash_table_foreach(sp->hash_requests, (GHFunc)sip_reset_hash_requests, NULL);
333     }
334 }
335
336 /* Main entry point to SIP tap */
337 static int
338 sipstat_packet(void *psp, packet_info *pinfo _U_, epan_dissect_t *edt _U_, void *pri)
339 {
340     sip_info_value_t *value=pri;
341     sipstat_t *sp = (sipstat_t *)psp;
342
343     /* Total number of packets, including continuation packets */
344     sp->packets++;
345
346     /* Looking at both requests and responses */
347     if (value->response_code != 0)
348     {
349         /* Responses */
350         guint *key = g_malloc(sizeof(guint));
351         sip_response_code_t *sc;
352
353         /* Look up response code in hash table */
354         *key = value->response_code;
355         sc = g_hash_table_lookup(sp->hash_responses, key);
356         if (sc==NULL)
357         {
358             /* Non-standard status code ; we classify it as others
359              * in the relevant category
360              * (Informational,Success,Redirection,Client Error,Server Error,Global Failure)
361              */
362             int i = value->response_code;
363             if ((i<100) || (i>=700))
364             {
365                 /* Forget about crazy values */
366                 return 0;
367             }
368             else if (i<200)
369             {
370                 *key=199;       /* Hopefully, this status code will never be used */
371             }
372             else if (i<300)
373             {
374                 *key=299;
375             }
376             else if (i<400)
377             {
378                 *key=399;
379             }
380             else if (i<500)
381             {
382                 *key=499;
383             }
384             else if (i<600)
385             {
386                 *key=599;
387             }
388             else
389             {
390                 *key = 699;
391             }
392
393             /* Now look up this fallback code to get its text description */
394             sc = g_hash_table_lookup(sp->hash_responses, key);
395             if (sc==NULL)
396             {
397                 return 0;
398             }
399         }
400         sc->packets++;
401     }
402     else if (value->request_method)
403     {
404         /* Requests */
405         sip_request_method_t *sc;
406
407         /* Look up the request method in the table */
408         sc = g_hash_table_lookup(sp->hash_requests, value->request_method);
409         if (sc == NULL)
410         {
411             /* First of this type. Create structure and initialise */
412             sc=g_malloc(sizeof(sip_request_method_t));
413             sc->response = g_strdup(value->request_method);
414             sc->packets = 1;
415             sc->widget = NULL;
416             sc->sp = sp;
417             /* Insert it into request table */
418             g_hash_table_insert(sp->hash_requests, sc->response, sc);
419         }
420         else
421         {
422             /* Already existed, just update count for that method */
423             sc->packets++;
424         }
425         /* g_free(value->request_method); */
426     }
427     else
428     {
429         /* No request method set. Just ignore */
430         return 0;
431     }
432
433     return 1;
434 }
435
436 /* Redraw the whole stats window */
437 static void
438 sipstat_draw(void *psp)
439 {
440     gchar      string_buff[SUM_STR_MAX];
441     sipstat_t *sp=psp;
442
443     /* Set summary label */
444     g_snprintf(string_buff, sizeof(string_buff),
445                 "SIP stats (%d packets)", sp->packets);
446     gtk_label_set(GTK_LABEL(sp->packets_label), string_buff);
447
448     /* Draw responses and requests from their tables */
449     g_hash_table_foreach(sp->hash_responses, (GHFunc)sip_draw_hash_responses, NULL);
450     g_hash_table_foreach(sp->hash_requests,  (GHFunc)sip_draw_hash_requests, NULL);
451     
452     gtk_widget_show_all(sp->win);
453 }
454
455
456 /* since the gtk2 implementation of tap is multithreaded we must protect
457  * remove_tap_listener() from modifying the list while draw_tap_listener()
458  * is running.  the other protected block is in main.c
459  *
460  * there should not be any other critical regions in gtk2
461  */
462 void protect_thread_critical_region(void);
463 void unprotect_thread_critical_region(void);
464
465 /* When window is destroyed, clean up */
466 static void
467 win_destroy_cb(GtkWindow *win _U_, gpointer data)
468 {
469     sipstat_t *sp=(sipstat_t *)data;
470
471     protect_thread_critical_region();
472     remove_tap_listener(sp);
473     unprotect_thread_critical_region();
474
475     g_hash_table_foreach(sp->hash_responses, (GHFunc)sip_free_hash, NULL);
476     g_hash_table_destroy(sp->hash_responses);
477     g_hash_table_foreach(sp->hash_requests, (GHFunc)sip_free_hash, NULL);
478     g_hash_table_destroy(sp->hash_requests);
479     g_free(sp->filter);
480     g_free(sp);
481 }
482
483 /* Create a new instance of gtk_sipstat. */
484 static void
485 gtk_sipstat_init(char *optarg)
486 {
487     sipstat_t *sp;
488     char *filter = NULL;
489     GString     *error_string;
490     char *title = NULL;
491     GtkWidget  *main_vb, *separator,
492                *informational_fr, *success_fr, *redirection_fr,
493                *client_errors_fr, *server_errors_fr, *global_failures_fr,
494                *request_fr;
495
496
497     if (strncmp (optarg, "sip,stat,", 9) == 0)
498     {
499         /* Skip those characters from filter to display */
500         filter=optarg + 9;
501     }
502     else
503     {
504         /* No filter */
505         filter = NULL;
506     }
507
508     /* Create sip stats window structure */
509     sp = g_malloc(sizeof(sipstat_t));
510
511     /* Set title to include any filter given */
512     if (filter)
513     {
514         sp->filter = g_strdup(filter);
515         title = g_strdup_printf("SIP statistics with filter: %s", filter);
516     }
517     else
518     {
519         sp->filter = NULL;
520         title = g_strdup("SIP statistics");
521     }
522
523     /* Create underlying window */
524     sp->win = window_new(GTK_WINDOW_TOPLEVEL, title);
525     g_free(title);
526
527     /* Set destroy callback for underlying window */
528     SIGNAL_CONNECT(sp->win, "destroy", win_destroy_cb, sp);
529
530
531     /* Create container for all widgets */
532     main_vb = gtk_vbox_new(FALSE, 10);
533     gtk_container_border_width(GTK_CONTAINER(main_vb), 10);
534     gtk_container_add(GTK_CONTAINER(sp->win), main_vb);
535     gtk_widget_show(main_vb);
536
537     /* Initialise & show number of packets */
538     sp->packets = 0;
539     sp->packets_label = gtk_label_new("SIP stats (0 SIP packets)");
540     gtk_container_add(GTK_CONTAINER(main_vb), sp->packets_label);
541     gtk_widget_show(sp->packets_label);
542
543
544     /* Informational response frame */
545     informational_fr = gtk_frame_new("Informational  SIP 1xx");
546     gtk_container_add(GTK_CONTAINER(main_vb), informational_fr);
547     gtk_widget_show(informational_fr);
548
549     /* Information table (within that frame) */
550     sp->informational_table = gtk_table_new(0, 2, FALSE);
551     gtk_container_add(GTK_CONTAINER(informational_fr), sp->informational_table);
552     gtk_widget_show(sp->informational_table);
553
554
555     /* Success table and frame */
556     success_fr = gtk_frame_new  ("Success         SIP 2xx");
557     gtk_container_add(GTK_CONTAINER(main_vb), success_fr);
558     gtk_widget_show(success_fr);
559
560     sp->success_table = gtk_table_new(0, 2, FALSE);
561     gtk_container_add(GTK_CONTAINER(success_fr), sp->success_table);
562     gtk_widget_show(sp->success_table);
563
564
565     /* Redirection table and frame */
566     redirection_fr = gtk_frame_new      ("Redirection     SIP 3xx");
567     gtk_container_add(GTK_CONTAINER(main_vb), redirection_fr);
568     gtk_widget_show(redirection_fr);
569
570     sp->redirection_table = gtk_table_new(0, 2, FALSE);
571     gtk_container_add(GTK_CONTAINER(redirection_fr), sp->redirection_table);
572     gtk_widget_show(sp->redirection_table);
573
574
575     /* Client Errors table and frame */
576     client_errors_fr = gtk_frame_new("Client errors  SIP 4xx");
577     gtk_container_add(GTK_CONTAINER(main_vb), client_errors_fr);
578     gtk_widget_show(client_errors_fr);
579
580     sp->client_error_table = gtk_table_new(0, 2, FALSE);
581     gtk_container_add(GTK_CONTAINER(client_errors_fr), sp->client_error_table);
582     gtk_widget_show(sp->client_error_table);
583
584
585     /* Server Errors table and frame */
586     server_errors_fr = gtk_frame_new("Server errors  SIP 5xx");
587     gtk_container_add(GTK_CONTAINER(main_vb), server_errors_fr);
588     gtk_widget_show(server_errors_fr);
589
590     sp->server_errors_table = gtk_table_new(0, 2, FALSE);
591     gtk_container_add(GTK_CONTAINER(server_errors_fr), sp->server_errors_table);
592     gtk_widget_show(sp->server_errors_table);
593
594
595     /* Global Failures table and frame */
596     global_failures_fr = gtk_frame_new("Global failures  SIP 6xx");
597     gtk_container_add(GTK_CONTAINER(main_vb), global_failures_fr);
598     gtk_widget_show(global_failures_fr);
599
600     sp->global_failures_table = gtk_table_new(0, 2, FALSE);
601     gtk_container_add(GTK_CONTAINER(global_failures_fr), sp->global_failures_table);
602     gtk_widget_show(sp->global_failures_table);
603
604
605     /* Separator between requests and responses */
606     separator = gtk_hseparator_new();
607     gtk_container_add(GTK_CONTAINER(main_vb), separator);
608
609
610     /* Request table and frame */
611     request_fr = gtk_frame_new("List of request methods");
612     gtk_container_add(GTK_CONTAINER(main_vb), request_fr);
613     gtk_container_border_width(GTK_CONTAINER(request_fr), 0);
614     gtk_widget_show(request_fr);
615
616     sp->request_box = gtk_vbox_new(FALSE, 10);
617     gtk_container_add(GTK_CONTAINER(request_fr), sp->request_box);
618     gtk_widget_show(sp->request_box);
619
620
621     /* Register this tap listener now */
622     error_string = register_tap_listener("sip",
623                                          sp,
624                                          filter,
625                                          sipstat_reset,
626                                          sipstat_packet,
627                                          sipstat_draw);
628     if (error_string)
629     {
630         /* Error.  We failed to attach to the tap. Clean up */
631         simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, error_string->str);
632         g_free(sp->filter);
633         g_free(sp);
634         g_string_free(error_string, TRUE);
635         return;
636     }
637
638     /* Display up-to-date contents */
639     gtk_widget_show_all(sp->win);
640     sip_init_hash(sp);
641     retap_packets(&cfile);
642 }
643
644 static tap_dfilter_dlg sip_stat_dlg = {
645         "SIP Packet Counter",
646         "sip,stat",
647         gtk_sipstat_init,
648         -1
649 };
650
651 /* Register this tap listener and add menu item. */
652 void
653 register_tap_listener_gtksipstat(void)
654 {
655     register_ethereal_tap("sip,stat", gtk_sipstat_init);
656
657     register_tap_menu_item("SIP", REGISTER_TAP_GROUP_NONE,
658                            gtk_tap_dfilter_dlg_cb, NULL, NULL, &(sip_stat_dlg));
659 }