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