Fix for bug 848. Make a scrollable window when more than 10 interfaces are listed.
[obnox/wireshark/wip.git] / gtk / capture_if_dlg.c
1 /* capture_if_dlg.c
2  * Routines for the capture interface dialog
3  *
4  * $Id$
5  *
6  * Ethereal - Network traffic analyzer
7  * By Gerald Combs <gerald@ethereal.com>
8  * Copyright 1998 Gerald Combs
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License
12  * as published by the Free Software Foundation; either version 2
13  * of the License, or (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
23  */
24
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
28
29 #ifdef HAVE_LIBPCAP
30
31 #ifdef HAVE_SYS_WAIT_H
32 # include <sys/wait.h>
33 #endif
34
35
36 #include <gtk/gtk.h>
37
38 #include "globals.h"
39 #include "capture-pcap-util.h"
40
41 #ifdef _WIN32
42 #include "capture-wpcap.h"
43 #endif
44
45 #include "compat_macros.h"
46 #include "simple_dialog.h"
47 #include "capture.h"
48 #include "capture_dlg.h"
49 #include "capture_if_details_dlg.h"
50 #include "capture_errs.h"
51
52 #include "gui_utils.h"
53 #include "dlg_utils.h"
54
55 #include "wtap.h"
56 #include "main.h"
57 #include "help_dlg.h"
58 #include "toolbar.h"
59
60 /*
61  * Keep a static pointer to the current "Capture Interfaces" window, if
62  * any, so that if somebody tries to do "Capture:Start" while there's
63  * already a "Capture Interfaces" window up, we just pop up the existing
64  * one, rather than creating a new one.
65  */
66 static GtkWidget *cap_if_w;
67
68 GList           *if_data = NULL;
69
70 guint           timer_id;
71
72 GtkWidget       *stop_bt;
73
74 GList           *if_list;
75
76 /*
77  * Timeout, in milliseconds, for reads from the stream of captured packets.
78  */
79 #define CAP_READ_TIMEOUT        250
80
81
82 /* the "runtime" data of one interface */
83 typedef struct if_dlg_data_s {
84     pcap_t      *pch;
85     GtkWidget   *device_lb;
86     GtkWidget   *descr_lb;
87     GtkWidget   *ip_lb;
88     GtkWidget   *curr_lb;
89     GtkWidget   *last_lb;
90     GtkWidget   *capture_bt;
91     GtkWidget   *prepare_bt;
92 #ifdef _WIN32
93     GtkWidget   *details_bt;
94 #endif
95     guint32     last_packets;
96     gchar       *device;
97 } if_dlg_data_t;
98
99 void update_if(if_dlg_data_t *if_dlg_data);
100
101
102 /* start capture button was pressed */
103 static void
104 capture_do_cb(GtkWidget *capture_bt _U_, gpointer if_data)
105 {
106   if_dlg_data_t *if_dlg_data = if_data;
107
108   if (capture_opts->iface)
109     g_free(capture_opts->iface);
110
111   capture_opts->iface = g_strdup(if_dlg_data->device);
112
113   /* XXX - remove this? */
114   if (capture_opts->save_file) {
115     g_free(capture_opts->save_file);
116     capture_opts->save_file = NULL;
117   }
118
119   /* stop capturing from all interfaces, we are going to do real work now ... */
120   window_destroy(cap_if_w);
121
122   capture_start_cb(NULL, NULL);
123 }
124
125
126 /* prepare capture button was pressed */
127 static void
128 capture_prepare_cb(GtkWidget *prepare_bt _U_, gpointer if_data)
129 {
130   if_dlg_data_t *if_dlg_data = if_data;
131
132   if (capture_opts->iface)
133     g_free(capture_opts->iface);
134
135   capture_opts->iface = g_strdup(if_dlg_data->device);
136
137   /* stop capturing from all interfaces, we are going to do real work now ... */
138   window_destroy(cap_if_w);
139
140   capture_prep_cb(NULL, NULL);
141 }
142
143
144 #ifdef _WIN32
145 /* capture details button was pressed */
146 static void
147 capture_details_cb(GtkWidget *details_bt _U_, gpointer if_data)
148 {
149   if_dlg_data_t *if_dlg_data = if_data;
150
151
152   capture_if_details_open(if_dlg_data->device);
153 }
154 #endif
155
156
157 /* open a single interface */
158 static void
159 open_if(gchar *name, if_dlg_data_t *if_dlg_data)
160 {
161   gchar       open_err_str[CAPTURE_PCAP_ERRBUF_SIZE];
162
163   /*
164    * XXX - on systems with BPF, the number of BPF devices limits the
165    * number of devices on which you can capture simultaneously.
166    *
167    * This means that
168    *
169    *    1) this might fail if you run out of BPF devices
170    *
171    * and
172    *
173    *    2) opening every interface could leave too few BPF devices
174    *       for *other* programs.
175    *
176    * It also means the system could end up getting a lot of traffic
177    * that it has to pass through the networking stack and capture
178    * mechanism, so opening all the devices and presenting packet
179    * counts might not always be a good idea.
180    */
181   if_dlg_data->pch = pcap_open_live(name,
182                        MIN_PACKET_SIZE,
183                        capture_opts->promisc_mode, CAP_READ_TIMEOUT,
184                        open_err_str);
185
186   if (if_dlg_data->pch != NULL) {
187     update_if(if_dlg_data);
188   } else {
189     printf("open_if: %s\n", open_err_str);
190     gtk_label_set_text(GTK_LABEL(if_dlg_data->curr_lb), "error");
191     gtk_label_set_text(GTK_LABEL(if_dlg_data->last_lb), "error");
192   }
193 }
194
195 /* update a single interface */
196 void
197 update_if(if_dlg_data_t *if_dlg_data)
198 {
199   struct pcap_stat stats;
200   gchar *str;
201   guint diff;
202
203
204   /* pcap_stats() stats values differ on libpcap and winpcap!
205    * libpcap: returns the number of packets since pcap_open_live
206    * winpcap: returns the number of packets since the last pcap_stats call
207    * XXX - if that's true, that's a bug, and should be fixed; "pcap_stats()"
208    * is supposed to work the same way on all platforms, including Windows.
209    * Note that the WinPcap 3.0 documentation says "The values represent
210    * packet statistics from the start of the run to the time of the call."
211    * (Note also that some versions of libpcap, on some versions of UN*X,
212    * have the same bug.)
213    */
214   if (if_dlg_data->pch) {
215     if(pcap_stats(if_dlg_data->pch, &stats) >= 0) {
216 #ifdef _WIN32
217       diff = stats.ps_recv - if_dlg_data->last_packets;
218       if_dlg_data->last_packets = stats.ps_recv;
219 #else
220       diff = stats.ps_recv;
221       if_dlg_data->last_packets = stats.ps_recv + if_dlg_data->last_packets;
222 #endif    
223
224       str = g_strdup_printf("%u", if_dlg_data->last_packets);
225       gtk_label_set_text(GTK_LABEL(if_dlg_data->curr_lb), str);
226       g_free(str);
227       str = g_strdup_printf("%u", diff);
228       gtk_label_set_text(GTK_LABEL(if_dlg_data->last_lb), str);
229       g_free(str);
230
231       gtk_widget_set_sensitive(if_dlg_data->curr_lb, diff);
232       gtk_widget_set_sensitive(if_dlg_data->last_lb, diff);
233     } else {
234       gtk_label_set_text(GTK_LABEL(if_dlg_data->curr_lb), "error");
235       gtk_label_set_text(GTK_LABEL(if_dlg_data->last_lb), "error");
236     }
237   }
238 }
239
240
241 /* close a single interface */
242 static void
243 close_if(if_dlg_data_t *if_dlg_data)
244 {
245     if(if_dlg_data->pch)
246         pcap_close(if_dlg_data->pch);
247 }
248
249
250
251 /* update all interfaces */
252 static gboolean
253 update_all(gpointer data)
254 {
255     GList *curr;
256     int ifs;
257
258
259     if(!cap_if_w) {
260         return FALSE;
261     }
262
263     for(ifs = 0; (curr = g_list_nth(data, ifs)); ifs++) {
264         update_if(curr->data);
265     }
266
267     return TRUE;
268 }
269
270
271 /* a live capture has started or stopped */
272 void
273 set_capture_if_dialog_for_capture_in_progress(gboolean capture_in_progress)
274 {
275     GList *curr;
276     int ifs;
277
278     if(cap_if_w) {
279         gtk_widget_set_sensitive(stop_bt, capture_in_progress);
280         
281         for(ifs = 0; (curr = g_list_nth(if_data, ifs)); ifs++) {
282             if_dlg_data_t *if_dlg_data = curr->data;
283
284             gtk_widget_set_sensitive(if_dlg_data->capture_bt, !capture_in_progress);
285             gtk_widget_set_sensitive(if_dlg_data->prepare_bt, !capture_in_progress);
286         }        
287     }
288 }
289
290
291 /* the window was closed, cleanup things */
292 static void
293 capture_if_destroy_cb(GtkWidget *win _U_, gpointer user_data _U_)
294 {
295     GList *curr;
296     int ifs;
297
298     gtk_timeout_remove(timer_id);
299
300     for(ifs = 0; (curr = g_list_nth(if_data, ifs)); ifs++) {
301         if_dlg_data_t *if_dlg_data = curr->data;
302
303         close_if(if_dlg_data);
304         g_free(curr->data);
305     }
306
307     if_data = NULL;
308
309     free_interface_list(if_list);
310
311     /* Note that we no longer have a "Capture Options" dialog box. */
312     cap_if_w = NULL;
313 }
314
315
316 /* start getting capture stats from all interfaces */
317 void
318 capture_if_cb(GtkWidget *w _U_, gpointer d _U_)
319 {
320   GtkWidget     *main_vb, *main_sw, *bbox, *close_bt, *help_bt;
321
322   GtkWidget     *if_tb;
323   GtkWidget     *if_lb;
324 #if GTK_MAJOR_VERSION < 2
325   GtkAccelGroup *accel_group;
326 #endif
327   GtkTooltips   *tooltips;
328   int           err;
329   char          err_str[CAPTURE_PCAP_ERRBUF_SIZE];
330   gchar         *cant_get_if_list_errstr;
331   GtkRequisition requisition;
332   int           row, height;
333   if_dlg_data_t *if_dlg_data;
334   int           ifs;
335   GList         *curr;
336   if_info_t     *if_info;
337   GSList        *curr_ip;
338   if_addr_t     *ip_addr;
339   GString       *if_tool_str = g_string_new("");
340   gchar         *tmp_str;
341
342   if (cap_if_w != NULL) {
343     /* There's already a "Capture Interfaces" dialog box; reactivate it. */
344     reactivate_window(cap_if_w);
345     return;
346   }
347
348 #ifdef _WIN32
349   /* Is WPcap loaded? */
350   if (!has_wpcap) {
351     char *detailed_err;
352
353     detailed_err = cant_load_winpcap_err("Ethereal");
354     simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, "%s", detailed_err);
355     g_free(detailed_err);
356     return;
357   }
358 #endif
359
360   if_list = get_interface_list(&err, err_str);
361   if (if_list == NULL && err == CANT_GET_INTERFACE_LIST) {
362     cant_get_if_list_errstr = cant_get_if_list_error_message(err_str);
363     simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, "%s",
364                   cant_get_if_list_errstr);
365     g_free(cant_get_if_list_errstr);
366     return;
367   }
368
369   cap_if_w = window_new(GTK_WINDOW_TOPLEVEL, "Ethereal: Capture Interfaces");
370
371   tooltips = gtk_tooltips_new();
372
373 #if GTK_MAJOR_VERSION < 2
374   /* Accelerator group for the accelerators (or, as they're called in
375      Windows and, I think, in Motif, "mnemonics"; Alt+<key> is a mnemonic,
376      Ctrl+<key> is an accelerator). */
377   accel_group = gtk_accel_group_new();
378   gtk_window_add_accel_group(GTK_WINDOW(cap_if_w), accel_group);
379 #endif
380
381   main_sw = gtk_scrolled_window_new(NULL, NULL);
382   gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(main_sw), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
383   gtk_container_add(GTK_CONTAINER(cap_if_w), main_sw);
384
385   main_vb = gtk_vbox_new(FALSE, 0);
386   gtk_container_border_width(GTK_CONTAINER(main_vb), 5);
387   gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(main_sw), main_vb);
388
389
390   if_tb = gtk_table_new(6,1, FALSE);
391   gtk_table_set_row_spacings(GTK_TABLE(if_tb), 3);
392   gtk_table_set_col_spacings(GTK_TABLE(if_tb), 3);
393   gtk_box_pack_start(GTK_BOX(main_vb), if_tb, FALSE, FALSE, 0);
394
395   row = 0;
396   height = 0;
397
398 #ifndef _WIN32
399   /*
400    * On Windows, device names are generally not meaningful - NT 5
401    * uses long blobs with GUIDs in them, for example - so we don't
402    * bother showing them.
403    */
404   if_lb = gtk_label_new("Device");
405   gtk_table_attach_defaults(GTK_TABLE(if_tb), if_lb, 0, 1, row, row+1);
406 #endif
407
408   if_lb = gtk_label_new("Description");
409   gtk_table_attach_defaults(GTK_TABLE(if_tb), if_lb, 1, 2, row, row+1);
410
411   if_lb = gtk_label_new(" IP ");
412   gtk_table_attach_defaults(GTK_TABLE(if_tb), if_lb, 2, 3, row, row+1);
413
414   if_lb = gtk_label_new("Packets");
415   gtk_table_attach_defaults(GTK_TABLE(if_tb), if_lb, 3, 4, row, row+1);
416
417   if_lb = gtk_label_new(" Packets/s ");
418   gtk_table_attach_defaults(GTK_TABLE(if_tb), if_lb, 4, 5, row, row+1);
419
420   stop_bt = BUTTON_NEW_FROM_STOCK(GTK_STOCK_STOP);
421   gtk_tooltips_set_tip(tooltips, stop_bt, 
422           "Stop a running capture.", NULL);
423 #ifdef _WIN32
424   gtk_table_attach_defaults(GTK_TABLE(if_tb), stop_bt, 5, 8, row, row+1);
425 #else
426   gtk_table_attach_defaults(GTK_TABLE(if_tb), stop_bt, 5, 7, row, row+1);
427 #endif
428   SIGNAL_CONNECT(stop_bt, "clicked", capture_stop_cb, NULL);
429
430   row++;
431   gtk_widget_size_request(stop_bt, &requisition);
432   height += requisition.height + 15;
433
434   for(ifs = 0; (curr = g_list_nth(if_list, ifs)); ifs++) {
435       g_string_assign(if_tool_str, "");
436       if_info = curr->data;
437       if_dlg_data = g_malloc0(sizeof(if_dlg_data_t));
438
439       /* device name */
440       if_dlg_data->device_lb = gtk_label_new(if_info->name);
441       if_dlg_data->device = if_info->name;
442 #ifndef _WIN32
443       gtk_misc_set_alignment(GTK_MISC(if_dlg_data->device_lb), 0.0, 0.5);
444       gtk_table_attach_defaults(GTK_TABLE(if_tb), if_dlg_data->device_lb, 0, 1, row, row+1);
445 #endif
446       g_string_append(if_tool_str, "Device: ");
447       g_string_append(if_tool_str, if_info->name);
448       g_string_append(if_tool_str, "\n");
449
450       /* description */
451       if (if_info->description != NULL)
452         if_dlg_data->descr_lb = gtk_label_new(if_info->description);
453       else
454         if_dlg_data->descr_lb = gtk_label_new("");
455       gtk_misc_set_alignment(GTK_MISC(if_dlg_data->descr_lb), 0.0, 0.5);
456       gtk_table_attach_defaults(GTK_TABLE(if_tb), if_dlg_data->descr_lb, 1, 2, row, row+1);
457
458       if (if_info->description) {
459         g_string_append(if_tool_str, "Description: ");
460         g_string_append(if_tool_str, if_info->description);
461         g_string_append(if_tool_str, "\n");
462       }
463
464       /* IP address */
465       /* only the first IP address will be shown */
466       g_string_append(if_tool_str, "IP: ");
467       curr_ip = g_slist_nth(if_info->ip_addr, 0);
468       if(curr_ip) {
469         ip_addr = (if_addr_t *)curr_ip->data;
470         switch (ip_addr->type) {
471
472         case AT_IPv4:
473           tmp_str = ip_to_str((guint8 *)&ip_addr->ip_addr.ip4_addr);
474           break;
475
476         case AT_IPv6:
477           tmp_str = ip6_to_str((struct e_in6_addr *)&ip_addr->ip_addr.ip6_addr);
478           break;
479
480         default:
481           g_assert_not_reached();
482           tmp_str = NULL;
483         }
484         if_dlg_data->ip_lb = gtk_label_new(tmp_str);
485         gtk_widget_set_sensitive(if_dlg_data->ip_lb, TRUE);
486         g_string_append(if_tool_str, tmp_str);
487       } else {
488         if_dlg_data->ip_lb = gtk_label_new("unknown");
489         gtk_widget_set_sensitive(if_dlg_data->ip_lb, FALSE);
490         g_string_append(if_tool_str, "unknown");
491       }
492       gtk_table_attach_defaults(GTK_TABLE(if_tb), if_dlg_data->ip_lb, 2, 3, row, row+1);
493       g_string_append(if_tool_str, "\n");
494
495       /* packets */
496       if_dlg_data->curr_lb = gtk_label_new("-");
497       gtk_table_attach_defaults(GTK_TABLE(if_tb), if_dlg_data->curr_lb, 3, 4, row, row+1);
498
499       /* packets/s */
500       if_dlg_data->last_lb = gtk_label_new("-");
501       gtk_table_attach_defaults(GTK_TABLE(if_tb), if_dlg_data->last_lb, 4, 5, row, row+1);
502
503       /* capture button */
504       if_dlg_data->capture_bt = gtk_button_new_with_label("Capture");
505       SIGNAL_CONNECT(if_dlg_data->capture_bt, "clicked", capture_do_cb, if_dlg_data);
506       tmp_str = g_strdup_printf("Immediately start a capture from this interface:\n\n%s", if_tool_str->str);
507       gtk_tooltips_set_tip(tooltips, if_dlg_data->capture_bt, 
508           tmp_str, NULL);
509       g_free(tmp_str);
510       gtk_table_attach_defaults(GTK_TABLE(if_tb), if_dlg_data->capture_bt, 5, 6, row, row+1);
511
512       /* prepare button */
513       if_dlg_data->prepare_bt = gtk_button_new_with_label("Prepare");
514       SIGNAL_CONNECT(if_dlg_data->prepare_bt, "clicked", capture_prepare_cb, if_dlg_data);
515       gtk_tooltips_set_tip(tooltips, if_dlg_data->prepare_bt, 
516           "Open the capture options dialog with this interface selected.", NULL);
517       gtk_table_attach_defaults(GTK_TABLE(if_tb), if_dlg_data->prepare_bt, 6, 7, row, row+1);
518
519       /* details button */
520 #ifdef _WIN32
521       if_dlg_data->details_bt = gtk_button_new_with_label("Details");
522       SIGNAL_CONNECT(if_dlg_data->details_bt, "clicked", capture_details_cb, if_dlg_data);
523       gtk_tooltips_set_tip(tooltips, if_dlg_data->details_bt, 
524           "Open the capture details dialog of this interface.", NULL);
525       gtk_table_attach_defaults(GTK_TABLE(if_tb), if_dlg_data->details_bt, 7, 8, row, row+1);
526 #endif
527
528       open_if(if_info->name, if_dlg_data);
529
530       if_data = g_list_append(if_data, if_dlg_data);
531
532       row++;
533       if (row <= 10) {
534           /* Lets add up 10 rows of interfaces, otherwise the window may become too high */
535           gtk_widget_size_request(GTK_WIDGET(if_dlg_data->prepare_bt), &requisition);
536           height += requisition.height;
537       }
538   }
539
540   g_string_free(if_tool_str, TRUE);
541
542   /* Button row: close button */
543   if(topic_available(HELP_CAPTURE_INTERFACES_DIALOG)) {
544     bbox = dlg_button_row_new(GTK_STOCK_CLOSE, GTK_STOCK_HELP, NULL);
545   } else {
546     bbox = dlg_button_row_new(GTK_STOCK_CLOSE, NULL);
547   }
548   gtk_box_pack_start(GTK_BOX(main_vb), bbox, FALSE, FALSE, 5);
549
550   close_bt = OBJECT_GET_DATA(bbox, GTK_STOCK_CLOSE);
551   window_set_cancel_button(cap_if_w, close_bt, window_cancel_button_cb);
552   gtk_tooltips_set_tip(tooltips, close_bt, "Close this window.", NULL);
553
554   if(topic_available(HELP_CAPTURE_INTERFACES_DIALOG)) {
555     help_bt = OBJECT_GET_DATA(bbox, GTK_STOCK_HELP);
556     SIGNAL_CONNECT(help_bt, "clicked", topic_cb, HELP_CAPTURE_INTERFACES_DIALOG);
557   }
558
559   gtk_widget_size_request(GTK_WIDGET(close_bt), &requisition);
560   height += requisition.height + 25;
561   gtk_window_set_default_size(GTK_WINDOW(cap_if_w), -1, height);
562
563   gtk_widget_grab_default(close_bt);
564
565   SIGNAL_CONNECT(cap_if_w, "delete_event", window_delete_event_cb, NULL);
566   SIGNAL_CONNECT(cap_if_w, "destroy", capture_if_destroy_cb, NULL);
567
568   gtk_widget_show_all(cap_if_w);
569   window_present(cap_if_w);
570
571   set_capture_if_dialog_for_capture_in_progress(is_capture_in_progress());
572
573     /* update the interface list every 1000ms */
574   timer_id = gtk_timeout_add(1000, update_all, if_data);
575 }
576
577
578 #endif /* HAVE_LIBPCAP */