aboutsummaryrefslogtreecommitdiffstats
path: root/epan
diff options
context:
space:
mode:
authorMichael Mann <mmann78@netscape.net>2014-07-23 13:38:55 -0400
committerGerald Combs <gerald@wireshark.org>2014-07-26 20:59:42 +0000
commit31ecdf5b06bff3bb2e706e840c28c519698e6f67 (patch)
tree71b3e59889e862560412d317df71113f66b094b6 /epan
parent507d07eda6ad562d4567cf0ee83aa9b03997beca (diff)
Refactor "common" Conversation table functionality.
Refactor (non-GUI) conversation table functionality from gtk/Qt to epan. Also refactor "common GUI" conversation table functionality. The idea is to not have to modify the GUI when a dissector adds a new "conversation type" Change-Id: I11f08d0d7edd631218663ba4b902c4a4c849acda Reviewed-on: https://code.wireshark.org/review/3113 Reviewed-by: Gerald Combs <gerald@wireshark.org>
Diffstat (limited to 'epan')
-rw-r--r--epan/CMakeLists.txt1
-rw-r--r--epan/Makefile.common2
-rw-r--r--epan/conversation_table.c581
-rw-r--r--epan/conversation_table.h272
-rw-r--r--epan/dissectors/packet-eth.c31
-rw-r--r--epan/dissectors/packet-fc.c29
-rw-r--r--epan/dissectors/packet-fddi.c28
-rw-r--r--epan/dissectors/packet-ieee80211.c29
-rw-r--r--epan/dissectors/packet-ip.c28
-rw-r--r--epan/dissectors/packet-ipv6.c38
-rw-r--r--epan/dissectors/packet-ipx.c30
-rw-r--r--epan/dissectors/packet-jxta.c31
-rw-r--r--epan/dissectors/packet-ncp.c30
-rw-r--r--epan/dissectors/packet-rsvp.c23
-rw-r--r--epan/dissectors/packet-sctp.c31
-rw-r--r--epan/dissectors/packet-tcp.c31
-rw-r--r--epan/dissectors/packet-tr.c30
-rw-r--r--epan/dissectors/packet-udp.c29
-rw-r--r--epan/dissectors/packet-usb.c37
-rw-r--r--epan/proto.c22
-rw-r--r--epan/proto.h5
-rw-r--r--epan/stat_cmd_args.h7
22 files changed, 1336 insertions, 9 deletions
diff --git a/epan/CMakeLists.txt b/epan/CMakeLists.txt
index 3998a7a497..3ecaf480da 100644
--- a/epan/CMakeLists.txt
+++ b/epan/CMakeLists.txt
@@ -1527,6 +1527,7 @@ set(LIBWIRESHARK_FILES
column.c
column-utils.c
conversation.c
+ conversation_table.c
crc16-tvb.c
crc32-tvb.c
crc8-tvb.c
diff --git a/epan/Makefile.common b/epan/Makefile.common
index 1707635e29..675d740428 100644
--- a/epan/Makefile.common
+++ b/epan/Makefile.common
@@ -35,6 +35,7 @@ LIBWIRESHARK_SRC = \
column.c \
column-utils.c \
conversation.c \
+ conversation_table.c \
crc16-tvb.c \
crc32-tvb.c \
crc8-tvb.c \
@@ -163,6 +164,7 @@ LIBWIRESHARK_INCLUDES = \
column-utils.h \
conversation.h \
conversation_debug.h \
+ conversation_table.h \
conv_id.h \
crc16-tvb.h \
crc32-tvb.h \
diff --git a/epan/conversation_table.c b/epan/conversation_table.c
new file mode 100644
index 0000000000..50f1954ea9
--- /dev/null
+++ b/epan/conversation_table.c
@@ -0,0 +1,581 @@
+/* conversations_table.c
+ * conversations_table 2003 Ronnie Sahlberg
+ * Helper routines common to all endpoint conversations tap.
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include "packet_info.h"
+#include "proto.h"
+#include "conversation_table.h"
+#include "addr_resolv.h"
+#include "emem.h"
+
+#include "stat_cmd_args.h"
+
+GList *cmd_string_list_ = NULL;
+
+struct register_ct {
+ gboolean hide_ports; /* hide TCP / UDP port columns */
+ int proto_id; /* protocol id (0-indexed) */
+ tap_packet_cb packet_func; /* function to be called for new incoming packets */
+ conv_gui_init_cb gui_init_cb; /* GUI specific function to initialize conversation */
+};
+
+gboolean get_conversation_hide_ports(register_ct_t* ct)
+{
+ return ct->hide_ports;
+}
+
+int get_conversation_proto_id(register_ct_t* ct)
+{
+ if (!ct) {
+ return -1;
+ }
+ return ct->proto_id;
+}
+
+tap_packet_cb get_conversation_packet_func(register_ct_t* ct)
+{
+ return ct->packet_func;
+}
+
+static GSList *registered_ct_tables = NULL;
+
+void
+dissector_conversation_init(const char *opt_arg, void* userdata)
+{
+ register_ct_t *table = (register_ct_t*)userdata;
+ GString *cmd_str = g_string_new("conv,");
+ const char *filter=NULL;
+
+ g_string_append(cmd_str, proto_get_protocol_filter_name(table->proto_id));
+ if(!strncmp(opt_arg, cmd_str->str, cmd_str->len)){
+ filter = opt_arg + cmd_str->len;
+ } else {
+ filter = NULL;
+ }
+ g_string_free(cmd_str, TRUE);
+
+ if (table->gui_init_cb)
+ table->gui_init_cb(table, filter);
+}
+
+/** get conversation from protocol ID
+ *
+ * @param proto_id protocol ID
+ * @return tap function handler of conversation
+ */
+register_ct_t* get_conversation_by_proto_id(int proto_id)
+{
+ GSList *ct;
+ register_ct_t *table;
+
+ for(ct = registered_ct_tables; ct != NULL; ct = g_slist_next(ct)){
+ table = (register_ct_t*)ct->data;
+ if ((table) && (table->proto_id == proto_id))
+ return table;
+ }
+
+ return NULL;
+}
+
+static gint
+insert_sorted_by_table_name(gconstpointer aparam, gconstpointer bparam)
+{
+ const register_ct_t *a = (register_ct_t *)aparam;
+ const register_ct_t *b = (register_ct_t *)bparam;
+
+ return g_ascii_strcasecmp(proto_get_protocol_short_name(find_protocol_by_id(a->proto_id)), proto_get_protocol_short_name(find_protocol_by_id(b->proto_id)));
+}
+
+void
+register_conversation_table(const int proto_id, gboolean hide_ports, tap_packet_cb packet_func)
+{
+ register_ct_t *table;
+ GString *cmd_str = g_string_new("conv,");
+
+ table = g_new(register_ct_t,1);
+
+ table->hide_ports = hide_ports;
+ table->proto_id = proto_id;
+ table->packet_func = packet_func;
+ table->gui_init_cb = NULL;
+
+ registered_ct_tables = g_slist_insert_sorted(registered_ct_tables, table, insert_sorted_by_table_name);
+
+ g_string_append(cmd_str, proto_get_protocol_filter_name(table->proto_id));
+ cmd_string_list_ = g_list_append(cmd_string_list_, cmd_str->str);
+ register_stat_cmd_arg(cmd_str->str, dissector_conversation_init, table);
+ g_string_free(cmd_str, FALSE);
+}
+
+/* Set GUI fields for register_ct list */
+static void
+set_gui_data(gpointer data, gpointer user_data)
+{
+ register_ct_t *table = (register_ct_t*)data;
+ table->gui_init_cb = (conv_gui_init_cb)user_data;
+}
+
+void conversation_table_set_gui_info(conv_gui_init_cb init_cb)
+{
+ g_slist_foreach(registered_ct_tables, set_gui_data, init_cb);
+}
+
+void conversation_table_iterate_tables(GFunc func, gpointer user_data)
+{
+ g_slist_foreach(registered_ct_tables, func, user_data);
+}
+
+guint conversation_table_get_num(void)
+{
+ return g_slist_length(registered_ct_tables);
+}
+
+
+register_ct_t *get_conversation_table_by_num(guint table_num)
+{
+ return (register_ct_t *) g_slist_nth_data(registered_ct_tables, table_num);
+}
+
+/** Compute the hash value for two given address/port pairs.
+ * (Parameter type is gconstpointer for GHashTable compatibility.)
+ *
+ * @param key Conversation. MUST point to a conv_key_t struct.
+ * @return Computed key hash.
+ */
+static guint
+conversation_hash(gconstpointer v)
+{
+ const conv_key_t *key = (const conv_key_t *)v;
+ guint hash_val;
+
+ hash_val = 0;
+ ADD_ADDRESS_TO_HASH(hash_val, &key->addr1);
+ hash_val += key->port1;
+ ADD_ADDRESS_TO_HASH(hash_val, &key->addr2);
+ hash_val += key->port2;
+ hash_val ^= key->conv_id;
+
+ return hash_val;
+}
+
+/** Compare two conversation keys for an exact match.
+ * (Parameter types are gconstpointer for GHashTable compatibility.)
+ *
+ * @param key1 First conversation. MUST point to a conv_key_t struct.
+ * @param key2 Second conversation. MUST point to a conv_key_t struct.
+ * @return TRUE if conversations are equal, FALSE otherwise.
+ */
+static gboolean
+conversation_equal(gconstpointer key1, gconstpointer key2)
+{
+ const conv_key_t *ck1 = (const conv_key_t *)key1;
+ const conv_key_t *ck2 = (const conv_key_t *)key2;
+
+ if (ck1->conv_id == ck2->conv_id)
+ {
+ if (ck1->port1 == ck2->port1 &&
+ ck1->port2 == ck2->port2 &&
+ ADDRESSES_EQUAL(&ck1->addr1, &ck2->addr1) &&
+ ADDRESSES_EQUAL(&ck1->addr2, &ck2->addr2)) {
+ return TRUE;
+ }
+
+ if (ck1->port2 == ck2->port1 &&
+ ck1->port1 == ck2->port2 &&
+ ADDRESSES_EQUAL(&ck1->addr2, &ck2->addr1) &&
+ ADDRESSES_EQUAL(&ck1->addr1, &ck2->addr2)) {
+ return TRUE;
+ }
+ }
+
+ /*
+ * The addresses, ports, or conversation IDs don't match.
+ */
+ return FALSE;
+}
+
+void
+reset_conversation_table_data(conv_hash_t *ch)
+{
+ if (!ch) {
+ return;
+ }
+
+ if (ch->conv_array != NULL) {
+ guint i;
+ for(i = 0; i < ch->conv_array->len; i++){
+ conv_item_t *conv = &g_array_index(ch->conv_array, conv_item_t, i);
+ g_free((gpointer)conv->src_address.data);
+ g_free((gpointer)conv->dst_address.data);
+ }
+
+ g_array_free(ch->conv_array, TRUE);
+ }
+
+ if (ch->hashtable != NULL) {
+ g_hash_table_destroy(ch->hashtable);
+ }
+
+ ch->conv_array=NULL;
+ ch->hashtable=NULL;
+}
+
+const char *get_conversation_address(address *addr, gboolean resolve_names)
+{
+ if (resolve_names) {
+ return ep_address_to_display(addr);
+ } else {
+ return ep_address_to_str(addr);
+ }
+}
+
+const char *get_conversation_port(guint32 port, port_type ptype, gboolean resolve_names)
+{
+
+ if(!resolve_names) ptype = PT_NONE;
+
+ switch(ptype) {
+ case(PT_TCP):
+ return ep_tcp_port_to_display(port);
+ case(PT_UDP):
+ return ep_udp_port_to_display(port);
+ case(PT_SCTP):
+ return ep_sctp_port_to_display(port);
+ default:
+ return ep_strdup_printf("%d", port);
+ }
+}
+
+/* given an address (to distinguish between ipv4 and ipv6 for tcp/udp),
+ a port_type and a name_type (FN_...)
+ return a string for the filter name.
+
+ Some addresses, like AT_ETHER may actually be any of multiple types
+ of protocols, either ethernet, tokenring, fddi, wlan etc so we must be
+ more specific there; that's why we need specific_addr_type.
+*/
+static const char *
+conversation_get_filter_name(conv_item_t *conv_item, conv_filter_type_e filter_type)
+{
+
+ if ((conv_item == NULL) || (conv_item->dissector_info == NULL) || (conv_item->dissector_info->get_filter_type == NULL)) {
+ return CONV_FILTER_INVALID;
+ }
+
+ return conv_item->dissector_info->get_filter_type(conv_item, filter_type);
+}
+
+/* Convert a port number into a string or NULL */
+static char *
+ct_port_to_str(port_type ptype, guint32 port)
+{
+ switch(ptype){
+ case PT_TCP:
+ case PT_UDP:
+ case PT_SCTP:
+ case PT_NCP:
+ return g_strdup_printf("%d", port);
+ default:
+ break;
+ }
+ return NULL;
+}
+
+const char *get_conversation_filter(conv_item_t *conv_item, conv_direction_e direction)
+{
+ char *sport, *dport;
+ const char *str = "INVALID";
+
+ sport = ct_port_to_str(conv_item->ptype, conv_item->src_port);
+ dport = ct_port_to_str(conv_item->ptype, conv_item->dst_port);
+
+ switch(direction){
+ case CONV_DIR_A_TO_FROM_B:
+ /* A <-> B */
+ str = ep_strdup_printf("%s==%s%s%s%s%s && %s==%s%s%s%s%s",
+ conversation_get_filter_name(conv_item, CONV_FT_ANY_ADDRESS),
+ ep_address_to_str(&conv_item->src_address),
+ sport?" && ":"",
+ sport?conversation_get_filter_name(conv_item, CONV_FT_ANY_PORT):"",
+ sport?"==":"",
+ sport?sport:"",
+ conversation_get_filter_name(conv_item, CONV_FT_ANY_ADDRESS),
+ ep_address_to_str(&conv_item->dst_address),
+ dport?" && ":"",
+ dport?conversation_get_filter_name(conv_item, CONV_FT_ANY_PORT):"",
+ dport?"==":"",
+ dport?dport:""
+ );
+ break;
+ case CONV_DIR_A_TO_B:
+ /* A --> B */
+ str = ep_strdup_printf("%s==%s%s%s%s%s && %s==%s%s%s%s%s",
+ conversation_get_filter_name(conv_item, CONV_FT_SRC_ADDRESS),
+ ep_address_to_str(&conv_item->src_address),
+ sport?" && ":"",
+ sport?conversation_get_filter_name(conv_item, CONV_FT_SRC_PORT):"",
+ sport?"==":"",
+ sport?sport:"",
+ conversation_get_filter_name(conv_item, CONV_FT_DST_ADDRESS),
+ ep_address_to_str(&conv_item->dst_address),
+ dport?" && ":"",
+ dport?conversation_get_filter_name(conv_item, CONV_FT_DST_PORT):"",
+ dport?"==":"",
+ dport?dport:""
+ );
+ break;
+ case CONV_DIR_A_FROM_B:
+ /* A <-- B */
+ str = ep_strdup_printf("%s==%s%s%s%s%s && %s==%s%s%s%s%s",
+ conversation_get_filter_name(conv_item, CONV_FT_DST_ADDRESS),
+ ep_address_to_str(&conv_item->src_address),
+ sport?" && ":"",
+ sport?conversation_get_filter_name(conv_item, CONV_FT_DST_PORT):"",
+ sport?"==":"",
+ sport?sport:"",
+ conversation_get_filter_name(conv_item, CONV_FT_SRC_ADDRESS),
+ ep_address_to_str(&conv_item->dst_address),
+ dport?" && ":"",
+ dport?conversation_get_filter_name(conv_item, CONV_FT_SRC_PORT):"",
+ dport?"==":"",
+ dport?dport:""
+ );
+ break;
+ case CONV_DIR_A_TO_FROM_ANY:
+ /* A <-> ANY */
+ str = ep_strdup_printf("%s==%s%s%s%s%s",
+ conversation_get_filter_name(conv_item, CONV_FT_ANY_ADDRESS),
+ ep_address_to_str(&conv_item->src_address),
+ sport?" && ":"",
+ sport?conversation_get_filter_name(conv_item, CONV_FT_ANY_PORT):"",
+ sport?"==":"",
+ sport?sport:""
+ );
+ break;
+ case CONV_DIR_A_TO_ANY:
+ /* A --> ANY */
+ str = ep_strdup_printf("%s==%s%s%s%s%s",
+ conversation_get_filter_name(conv_item, CONV_FT_SRC_ADDRESS),
+ ep_address_to_str(&conv_item->src_address),
+ sport?" && ":"",
+ sport?conversation_get_filter_name(conv_item, CONV_FT_SRC_PORT):"",
+ sport?"==":"",
+ sport?sport:""
+ );
+ break;
+ case CONV_DIR_A_FROM_ANY:
+ /* A <-- ANY */
+ str = ep_strdup_printf("%s==%s%s%s%s%s",
+ conversation_get_filter_name(conv_item, CONV_FT_DST_ADDRESS),
+ ep_address_to_str(&conv_item->src_address),
+ sport?" && ":"",
+ sport?conversation_get_filter_name(conv_item, CONV_FT_DST_PORT):"",
+ sport?"==":"",
+ sport?sport:""
+ );
+ break;
+ case CONV_DIR_ANY_TO_FROM_B:
+ /* ANY <-> B */
+ str = ep_strdup_printf("%s==%s%s%s%s%s",
+ conversation_get_filter_name(conv_item, CONV_FT_ANY_ADDRESS),
+ ep_address_to_str(&conv_item->dst_address),
+ dport?" && ":"",
+ dport?conversation_get_filter_name(conv_item, CONV_FT_ANY_PORT):"",
+ dport?"==":"",
+ dport?dport:""
+ );
+ break;
+ case CONV_DIR_ANY_FROM_B:
+ /* ANY <-- B */
+ str = ep_strdup_printf("%s==%s%s%s%s%s",
+ conversation_get_filter_name(conv_item, CONV_FT_SRC_ADDRESS),
+ ep_address_to_str(&conv_item->dst_address),
+ dport?" && ":"",
+ dport?conversation_get_filter_name(conv_item, CONV_FT_SRC_PORT):"",
+ dport?"==":"",
+ dport?dport:""
+ );
+ break;
+ case CONV_DIR_ANY_TO_B:
+ /* ANY --> B */
+ str = ep_strdup_printf("%s==%s%s%s%s%s",
+ conversation_get_filter_name(conv_item, CONV_FT_DST_ADDRESS),
+ ep_address_to_str(&conv_item->dst_address),
+ dport?" && ":"",
+ dport?conversation_get_filter_name(conv_item, CONV_FT_DST_PORT):"",
+ dport?"==":"",
+ dport?dport:""
+ );
+ break;
+ default:
+ break;
+ }
+ g_free(sport);
+ g_free(dport);
+ return str;
+}
+
+void
+add_conversation_table_data(conv_hash_t *ch, const address *src, const address *dst, guint32 src_port, guint32 dst_port, int num_frames, int num_bytes, nstime_t *ts, ct_dissector_info_t *ct_info, port_type ptype)
+{
+ add_conversation_table_data_with_conv_id(ch, src, dst, src_port, dst_port, CONV_ID_UNSET, num_frames, num_bytes, ts, ct_info, ptype);
+}
+
+void
+add_conversation_table_data_with_conv_id(
+ conv_hash_t *ch,
+ const address *src,
+ const address *dst,
+ guint32 src_port,
+ guint32 dst_port,
+ conv_id_t conv_id,
+ int num_frames,
+ int num_bytes,
+ nstime_t *ts,
+ ct_dissector_info_t *ct_info,
+ port_type ptype)
+{
+ const address *addr1, *addr2;
+ guint32 port1, port2;
+ conv_item_t *conv_item = NULL;
+ unsigned int conversation_idx = 0;
+
+ if (src_port > dst_port) {
+ addr1 = src;
+ addr2 = dst;
+ port1 = src_port;
+ port2 = dst_port;
+ } else if (src_port < dst_port) {
+ addr2 = src;
+ addr1 = dst;
+ port2 = src_port;
+ port1 = dst_port;
+ } else if (CMP_ADDRESS(src, dst) < 0) {
+ addr1 = src;
+ addr2 = dst;
+ port1 = src_port;
+ port2 = dst_port;
+ } else {
+ addr2 = src;
+ addr1 = dst;
+ port2 = src_port;
+ port1 = dst_port;
+ }
+
+ /* if we dont have any entries at all yet */
+ if (ch->conv_array == NULL) {
+ ch->conv_array = g_array_sized_new(FALSE, FALSE, sizeof(conv_item_t), 10000);
+
+ ch->hashtable = g_hash_table_new_full(conversation_hash,
+ conversation_equal, /* key_equal_func */
+ g_free, /* key_destroy_func */
+ NULL); /* value_destroy_func */
+
+ } else {
+ /* try to find it among the existing known conversations */
+ conv_key_t existing_key;
+
+ existing_key.addr1 = *addr1;
+ existing_key.addr2 = *addr2;
+ existing_key.port1 = port1;
+ existing_key.port2 = port2;
+ existing_key.conv_id = conv_id;
+ if (g_hash_table_lookup_extended(ch->hashtable, &existing_key, NULL, (gpointer *) &conversation_idx)) {
+ conv_item = &g_array_index(ch->conv_array, conv_item_t, conversation_idx);
+ }
+ }
+
+ /* if we still dont know what conversation this is it has to be a new one
+ and we have to allocate it and append it to the end of the list */
+ if (conv_item == NULL) {
+ conv_key_t *new_key;
+ conv_item_t new_conv_item;
+
+ COPY_ADDRESS(&new_conv_item.src_address, addr1);
+ COPY_ADDRESS(&new_conv_item.dst_address, addr2);
+ new_conv_item.dissector_info = ct_info;
+ new_conv_item.ptype = ptype;
+ new_conv_item.src_port = port1;
+ new_conv_item.dst_port = port2;
+ new_conv_item.conv_id = conv_id;
+ new_conv_item.rx_frames = 0;
+ new_conv_item.tx_frames = 0;
+ new_conv_item.rx_bytes = 0;
+ new_conv_item.tx_bytes = 0;
+ new_conv_item.modified = TRUE;
+
+ if (ts) {
+ memcpy(&new_conv_item.start_time, ts, sizeof(new_conv_item.start_time));
+ memcpy(&new_conv_item.stop_time, ts, sizeof(new_conv_item.stop_time));
+ } else {
+ nstime_set_unset(&new_conv_item.start_time);
+ nstime_set_unset(&new_conv_item.stop_time);
+ }
+ g_array_append_val(ch->conv_array, new_conv_item);
+ conversation_idx = ch->conv_array->len - 1;
+ conv_item = &g_array_index(ch->conv_array, conv_item_t, conversation_idx);
+
+ /* ct->conversations address is not a constant but src/dst_address.data are */
+ new_key = g_new(conv_key_t, 1);
+ SET_ADDRESS(&new_key->addr1, conv_item->src_address.type, conv_item->src_address.len, conv_item->src_address.data);
+ SET_ADDRESS(&new_key->addr2, conv_item->dst_address.type, conv_item->dst_address.len, conv_item->dst_address.data);
+ new_key->port1 = port1;
+ new_key->port2 = port2;
+ new_key->conv_id = conv_id;
+ g_hash_table_insert(ch->hashtable, new_key, GUINT_TO_POINTER(conversation_idx));
+ }
+
+ /* update the conversation struct */
+ conv_item->modified = TRUE;
+ if ( (!CMP_ADDRESS(src, addr1)) && (!CMP_ADDRESS(dst, addr2)) && (src_port==port1) && (dst_port==port2) ) {
+ conv_item->tx_frames += num_frames;
+ conv_item->tx_bytes += num_bytes;
+ } else {
+ conv_item->rx_frames += num_frames;
+ conv_item->rx_bytes += num_bytes;
+ }
+
+ if (ts) {
+ if (nstime_cmp(ts, &conv_item->stop_time) > 0) {
+ memcpy(&conv_item->stop_time, ts, sizeof(conv_item->stop_time));
+ } else if (nstime_cmp(ts, &conv_item->start_time) < 0) {
+ memcpy(&conv_item->start_time, ts, sizeof(conv_item->start_time));
+ }
+ }
+}
+
+/*
+ * Editor modelines
+ *
+ * Local Variables:
+ * c-basic-offset: 4
+ * tab-width: 8
+ * indent-tabs-mode: nil
+ * End:
+ *
+ * ex: set shiftwidth=4 tabstop=8 expandtab:
+ * :indentSize=4:tabSize=8:noTabs=true:
+ */
diff --git a/epan/conversation_table.h b/epan/conversation_table.h
new file mode 100644
index 0000000000..d9e6f2bd01
--- /dev/null
+++ b/epan/conversation_table.h
@@ -0,0 +1,272 @@
+/* conversation_table.h
+ * GUI independent helper routines common to all conversations taps.
+ * Refactored original conversations_table by Ronnie Sahlberg
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef __CONVERSATION_TABLE_H__
+#define __CONVERSATION_TABLE_H__
+
+#include "conv_id.h"
+#include "tap.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/** @file
+ * Conversation definitions.
+ */
+
+typedef enum {
+ CONV_FT_SRC_ADDRESS,
+ CONV_FT_DST_ADDRESS,
+ CONV_FT_ANY_ADDRESS,
+ CONV_FT_SRC_PORT,
+ CONV_FT_DST_PORT,
+ CONV_FT_ANY_PORT
+} conv_filter_type_e;
+
+/* Filter direction */
+typedef enum {
+ CONV_DIR_A_TO_FROM_B,
+ CONV_DIR_A_TO_B,
+ CONV_DIR_A_FROM_B,
+ CONV_DIR_A_TO_FROM_ANY,
+ CONV_DIR_A_TO_ANY,
+ CONV_DIR_A_FROM_ANY,
+ CONV_DIR_ANY_TO_FROM_B,
+ CONV_DIR_ANY_TO_B,
+ CONV_DIR_ANY_FROM_B
+} conv_direction_e;
+
+/** Conversation hash + value storage
+ * Hash table keys are conv_key_t. Hash table values are indexes into conv_array.
+ */
+typedef struct _conversation_hash_t {
+ GHashTable *hashtable; /**< conversations hash table */
+ GArray *conv_array; /**< array of conversation values */
+ void *user_data; /**< "GUI" specifics (if necessary) */
+} conv_hash_t;
+
+/** Key for hash lookups */
+typedef struct _conversation_key_t {
+ address addr1;
+ address addr2;
+ guint32 port1;
+ guint32 port2;
+ conv_id_t conv_id;
+} conv_key_t;
+
+struct _conversation_item_t;
+typedef const char* (*conv_get_filter_type)(struct _conversation_item_t* item, conv_filter_type_e filter);
+typedef const char* (*conv_get_conversation_filter_type)(struct _conversation_item_t* item, conv_direction_e direction);
+
+
+
+typedef struct _ct_dissector_info {
+ conv_get_filter_type get_filter_type;
+} ct_dissector_info_t;
+
+#define CONV_FILTER_INVALID "INVALID"
+
+
+struct register_ct;
+typedef void (*conv_gui_init_cb)(struct register_ct* ct, const char *filter);
+
+/** Structure for information about a registered conversation */
+typedef struct register_ct register_ct_t;
+
+/** Conversation information */
+typedef struct _conversation_item_t {
+ ct_dissector_info_t *dissector_info; /**< conversation information provided by dissector */
+ address src_address; /**< source address */
+ address dst_address; /**< destination address */
+ port_type ptype; /**< port_type (e.g. PT_TCP) */
+ guint32 src_port; /**< source port */
+ guint32 dst_port; /**< destination port */
+ conv_id_t conv_id; /**< conversation id */
+
+ guint64 rx_frames; /**< number of received packets */
+ guint64 tx_frames; /**< number of transmitted packets */
+ guint64 rx_bytes; /**< number of received bytes */
+ guint64 tx_bytes; /**< number of transmitted bytes */
+
+ nstime_t start_time; /**< start time for the conversation */
+ nstime_t stop_time; /**< stop time for the conversation */
+
+ gboolean modified; /**< new to redraw the row (only used in GTK+) */
+} conv_item_t;
+
+/** Register the conversation table for the multiple conversation window.
+ *
+ * @param hide_ports hide the port columns
+ * @param table_name the table name to be displayed
+ * @param tap_name the registered tap name
+ * @param packet_func the function to be called for each incoming packet
+ */
+extern void register_conversation_table(const int proto_id, gboolean hide_ports, tap_packet_cb packet_func);
+
+/** Should port columns be hidden?
+ *
+ * @param ct Registered conversation
+ * @return TRUE if port columns should be hidden for this conversation type.
+ */
+WS_DLL_PUBLIC gboolean get_conversation_hide_ports(register_ct_t* ct);
+
+/** Get protocol ID from conversation
+ *
+ * @param ct Registered conversation
+ * @return protocol id of conversation
+ */
+WS_DLL_PUBLIC int get_conversation_proto_id(register_ct_t* ct);
+
+/** Get tap function handler from conversation
+ *
+ * @param ct Registered conversation
+ * @return tap function handler of conversation
+ */
+WS_DLL_PUBLIC tap_packet_cb get_conversation_packet_func(register_ct_t* ct);
+
+/** get conversation from protocol ID
+ *
+ * @param proto_id protocol ID
+ * @return tap function handler of conversation
+ */
+WS_DLL_PUBLIC register_ct_t* get_conversation_by_proto_id(int proto_id);
+
+/** Register "initialization function" used by the GUI to create conversation
+ * table display in GUI
+ *
+ * @param init_cb callback function that will be called when converation table "display
+ * is instantiated in GUI
+ */
+WS_DLL_PUBLIC void conversation_table_set_gui_info(conv_gui_init_cb init_cb);
+
+/** Interator to walk converation tables and execute func
+ * a GUI menu (only used in GTK)
+ *
+ * @param func action to be performed on all converation tables
+ * @param user_data any data needed to help perform function
+ */
+WS_DLL_PUBLIC void conversation_table_iterate_tables(GFunc func, gpointer user_data);
+
+/** Total number of converation tables
+ */
+WS_DLL_PUBLIC guint conversation_table_get_num(void);
+
+/** Get conversation table by its number
+ * Tables are ordered alphabetically by title.
+ *
+ * @param table_num Item to fetch.
+ * @return table pointer or NULL.
+ */
+WS_DLL_PUBLIC register_ct_t* get_conversation_table_by_num(guint table_num);
+
+/** Remove all entries from the conversation table.
+ *
+ * @param ch the table to reset
+ */
+WS_DLL_PUBLIC void reset_conversation_table_data(conv_hash_t *ch);
+
+/** Initialize dissector converation for stats and (possibly) GUI.
+ *
+ * @param opt_arg filter string to compare with dissector
+ * @param userdata register_ct_t* for dissector conversation
+ */
+WS_DLL_PUBLIC void dissector_conversation_init(const char *opt_arg, void* userdata);
+
+/** Get the string representation of an address.
+ *
+ * @param addr The address.
+ * @param resolve_names Enable name resolution.
+ * @return An ep_allocated string representing the address.
+ */
+WS_DLL_PUBLIC const char *get_conversation_address(address *addr, gboolean resolve_names);
+
+/** Get the string representation of a port.
+ *
+ * @param port The port number.
+ * @param ptype The port type.
+ * @param resolve_names Enable name resolution.
+ * @return An ep_allocated string representing the port.
+ */
+WS_DLL_PUBLIC const char *get_conversation_port(guint32 port, port_type ptype, gboolean resolve_names);
+
+/** Get a display filter for the given conversation and direction.
+ *
+ * @param conv_item The conversation.
+ * @param direction The desired direction.
+ * @return An ep_allocated string representing the conversation.
+ */
+WS_DLL_PUBLIC const char *get_conversation_filter(conv_item_t *conv_item, conv_direction_e direction);
+
+/** Add some data to the conversation table.
+ *
+ * @param ch the table to add the data to
+ * @param src source address
+ * @param dst destination address
+ * @param src_port source port
+ * @param dst_port destination port
+ * @param num_frames number of packets
+ * @param num_bytes number of bytes
+ * @param ts timestamp
+ * @param ct_info callback handlers from the dissector
+ * @param ptype the port type (e.g. PT_TCP)
+ */
+extern void add_conversation_table_data(conv_hash_t *ch, const address *src, const address *dst,
+ guint32 src_port, guint32 dst_port, int num_frames, int num_bytes, nstime_t *ts,
+ ct_dissector_info_t *ct_info, port_type ptype);
+
+/** Add some data to the conversation table, passing a value to be used in
+ * addition to the address and port quadruple to uniquely identify the
+ * conversation.
+ *
+ * @param ch the table to add the data to
+ * @param src source address
+ * @param dst destination address
+ * @param src_port source port
+ * @param dst_port destination port
+ * @param num_frames number of packets
+ * @param num_bytes number of bytes
+ * @param ts timestamp
+ * @param ct_info callback handlers from the dissector
+ * @param ptype the port type (e.g. PT_TCP)
+ * @param conv_id a value to help differentiate the conversation in case the address and port quadruple is not sufficiently unique
+ */
+extern void
+add_conversation_table_data_with_conv_id(
+ conv_hash_t *ch,
+ const address *src,
+ const address *dst,
+ guint32 src_port,
+ guint32 dst_port,
+ conv_id_t conv_id,
+ int num_frames,
+ int num_bytes,
+ nstime_t *ts,
+ ct_dissector_info_t *ct_info,
+ port_type ptype);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __CONVERSATION_TABLE_H__ */
diff --git a/epan/dissectors/packet-eth.c b/epan/dissectors/packet-eth.c
index 21e7e1195e..f1974631c9 100644
--- a/epan/dissectors/packet-eth.c
+++ b/epan/dissectors/packet-eth.c
@@ -30,6 +30,7 @@
#include <epan/etypes.h>
#include <epan/addr_resolv.h>
#include <epan/expert.h>
+#include <epan/conversation_table.h>
#include <wsutil/pint.h>
#include "packet-eth.h"
#include "packet-ieee8023.h"
@@ -106,6 +107,34 @@ static const true_false_string lg_tfs = {
"Globally unique address (factory default)"
};
+
+static const char* eth_conv_get_filter_type(conv_item_t* conv, conv_filter_type_e filter)
+{
+ if ((filter == CONV_FT_SRC_ADDRESS) && (conv->src_address.type == AT_ETHER))
+ return "eth.src";
+
+ if ((filter == CONV_FT_DST_ADDRESS) && (conv->dst_address.type == AT_ETHER))
+ return "eth.dst";
+
+ if ((filter == CONV_FT_ANY_ADDRESS) && (conv->src_address.type == AT_ETHER))
+ return "eth.addr";
+
+ return CONV_FILTER_INVALID;
+}
+
+static ct_dissector_info_t eth_ct_dissector_info = {&eth_conv_get_filter_type};
+
+static int
+eth_conversation_packet(void *pct, packet_info *pinfo, epan_dissect_t *edt _U_, const void *vip)
+{
+ conv_hash_t *hash = (conv_hash_t*) pct;
+ const eth_hdr *ehdr=(const eth_hdr *)vip;
+
+ add_conversation_table_data(hash, &ehdr->src, &ehdr->dst, 0, 0, 1, pinfo->fd->pkt_len, &pinfo->rel_ts, &eth_ct_dissector_info, PT_NONE);
+
+ return 1;
+}
+
/* These are the Netware-ish names for the different Ethernet frame types.
EthernetII: The ethernet with a Type field instead of a length field
Ethernet802.2: An 802.3 header followed by an 802.2 header
@@ -941,6 +970,8 @@ proto_register_eth(void)
register_dissector("eth_withfcs", dissect_eth_withfcs, proto_eth);
register_dissector("eth", dissect_eth_maybefcs, proto_eth);
eth_tap = register_tap("eth");
+
+ register_conversation_table(proto_eth, TRUE, eth_conversation_packet);
}
void
diff --git a/epan/dissectors/packet-fc.c b/epan/dissectors/packet-fc.c
index 737c7d1162..2ca116f2b5 100644
--- a/epan/dissectors/packet-fc.c
+++ b/epan/dissectors/packet-fc.c
@@ -34,6 +34,7 @@
#include <wiretap/wtap.h>
#include <epan/reassemble.h>
#include <epan/conversation.h>
+#include <epan/conversation_table.h>
#include <epan/etypes.h>
#include "packet-fc.h"
#include "packet-fclctl.h"
@@ -205,6 +206,32 @@ fc_exchange_init_protocol(void)
fcseq_req_hash = g_hash_table_new(fcseq_hash, fcseq_equal);
}
+static const char* fc_conv_get_filter_type(conv_item_t* conv, conv_filter_type_e filter)
+{
+ if ((filter == CONV_FT_SRC_ADDRESS) && (conv->src_address.type == AT_FC))
+ return "fc.s_id";
+
+ if ((filter == CONV_FT_DST_ADDRESS) && (conv->dst_address.type == AT_FC))
+ return "fc.d_id";
+
+ if ((filter == CONV_FT_ANY_ADDRESS) && (conv->src_address.type == AT_FC))
+ return "fc.id";
+
+ return CONV_FILTER_INVALID;
+}
+
+static ct_dissector_info_t fc_ct_dissector_info = {&fc_conv_get_filter_type};
+
+static int
+fc_conversation_packet(void *pct, packet_info *pinfo, epan_dissect_t *edt _U_, const void *vip)
+{
+ conv_hash_t *hash = (conv_hash_t*) pct;
+ const fc_hdr *fchdr=(const fc_hdr *)vip;
+
+ add_conversation_table_data(hash, &fchdr->s_id, &fchdr->d_id, 0, 0, 1, pinfo->fd->pkt_len, &pinfo->rel_ts, &fc_ct_dissector_info, PT_NONE);
+
+ return 1;
+}
const value_string fc_fc4_val[] = {
{FC_TYPE_BLS, "Basic Link Svc"},
@@ -1579,6 +1606,8 @@ proto_register_fc(void)
proto_register_subtree_array(sof_ett, array_length(sof_ett));
fcsof_handle = register_dissector("fcsof", dissect_fcsof, proto_fcsof);
+
+ register_conversation_table(proto_fc, TRUE, fc_conversation_packet);
}
diff --git a/epan/dissectors/packet-fddi.c b/epan/dissectors/packet-fddi.c
index 50856084da..76a2fda692 100644
--- a/epan/dissectors/packet-fddi.c
+++ b/epan/dissectors/packet-fddi.c
@@ -33,6 +33,7 @@
#include <epan/packet.h>
#include <wsutil/bitswap.h>
#include <epan/prefs.h>
+#include <epan/conversation_table.h>
#include "packet-fddi.h"
#include "packet-llc.h"
#include <epan/tap.h>
@@ -142,6 +143,32 @@ swap_mac_addr(guint8 *swapped_addr, tvbuff_t *tvb, gint offset)
bitswap_buf_inplace(swapped_addr, 6);
}
+static const char* fddi_conv_get_filter_type(conv_item_t* conv, conv_filter_type_e filter)
+{
+ if ((filter == CONV_FT_SRC_ADDRESS) && (conv->src_address.type == AT_ETHER))
+ return "fddi.src";
+
+ if ((filter == CONV_FT_DST_ADDRESS) && (conv->dst_address.type == AT_ETHER))
+ return "fddi.dst";
+
+ if ((filter == CONV_FT_ANY_ADDRESS) && (conv->src_address.type == AT_ETHER))
+ return "fddi.addr";
+
+ return CONV_FILTER_INVALID;
+}
+
+static ct_dissector_info_t fddi_ct_dissector_info = {&fddi_conv_get_filter_type};
+
+static int
+fddi_conversation_packet(void *pct, packet_info *pinfo, epan_dissect_t *edt _U_, const void *vip)
+{
+ conv_hash_t *hash = (conv_hash_t*) pct;
+ const fddi_hdr *ehdr=(const fddi_hdr *)vip;
+
+ add_conversation_table_data(hash, &ehdr->src, &ehdr->dst, 0, 0, 1, pinfo->fd->pkt_len, &pinfo->rel_ts, &fddi_ct_dissector_info, PT_NONE);
+
+ return 1;
+}
void
capture_fddi(const guchar *pd, int len, packet_counts *ld)
@@ -478,6 +505,7 @@ proto_register_fddi(void)
&fddi_padding);
fddi_tap = register_tap("fddi");
+ register_conversation_table(proto_fddi, TRUE, fddi_conversation_packet);
}
void
diff --git a/epan/dissectors/packet-ieee80211.c b/epan/dissectors/packet-ieee80211.c
index a24f53b591..7e7f24606d 100644
--- a/epan/dissectors/packet-ieee80211.c
+++ b/epan/dissectors/packet-ieee80211.c
@@ -102,6 +102,7 @@
#include <epan/wmem/wmem.h>
#include <epan/crypt/wep-wpadefs.h>
#include <epan/expert.h>
+#include <epan/conversation_table.h>
#include <epan/uat.h>
#include <epan/eapol_keydes_types.h>
@@ -5172,6 +5173,33 @@ static const value_string ff_psmp_sta_info_flags[] = {
{0, NULL}
};
+static const char* wlan_conv_get_filter_type(conv_item_t* conv, conv_filter_type_e filter)
+{
+ if ((filter == CONV_FT_SRC_ADDRESS) && (conv->src_address.type == AT_ETHER))
+ return "wlan.sa";
+
+ if ((filter == CONV_FT_DST_ADDRESS) && (conv->dst_address.type == AT_ETHER))
+ return "wlan.da";
+
+ if ((filter == CONV_FT_ANY_ADDRESS) && (conv->src_address.type == AT_ETHER))
+ return "wlan.addr";
+
+ return CONV_FILTER_INVALID;
+}
+
+static ct_dissector_info_t wlan_ct_dissector_info = {&wlan_conv_get_filter_type};
+
+static int
+wlan_conversation_packet(void *pct, packet_info *pinfo, epan_dissect_t *edt _U_, const void *vip)
+{
+ conv_hash_t *hash = (conv_hash_t*) pct;
+ const wlan_hdr *whdr=(const wlan_hdr *)vip;
+
+ add_conversation_table_data(hash, &whdr->src, &whdr->dst, 0, 0, 1, pinfo->fd->pkt_len, &pinfo->rel_ts, &wlan_ct_dissector_info, PT_NONE);
+
+ return 1;
+}
+
static void
beacon_interval_base_custom(gchar *result, guint32 beacon_interval)
{
@@ -25898,6 +25926,7 @@ proto_register_ieee80211 (void)
register_init_routine(ieee80211_gas_reassembly_init);
wlan_tap = register_tap("wlan");
+ register_conversation_table(proto_wlan, TRUE, wlan_conversation_packet);
/* Register configuration options */
wlan_module = prefs_register_protocol(proto_wlan, init_wepkeys);
diff --git a/epan/dissectors/packet-ip.c b/epan/dissectors/packet-ip.c
index c9e16d315a..ec61d204b8 100644
--- a/epan/dissectors/packet-ip.c
+++ b/epan/dissectors/packet-ip.c
@@ -36,6 +36,7 @@
#include <epan/expert.h>
#include <epan/ip_opts.h>
#include <epan/prefs.h>
+#include <epan/conversation_table.h>
#include <epan/reassemble.h>
#include <epan/etypes.h>
#include <epan/greproto.h>
@@ -473,6 +474,32 @@ static gpointer ip_value(packet_info *pinfo)
return GUINT_TO_POINTER(pinfo->ipproto);
}
+static const char* ip_conv_get_filter_type(conv_item_t* conv, conv_filter_type_e filter)
+{
+ if ((filter == CONV_FT_SRC_ADDRESS) && (conv->src_address.type == AT_IPv4))
+ return "ip.src";
+
+ if ((filter == CONV_FT_DST_ADDRESS) && (conv->dst_address.type == AT_IPv4))
+ return "ip.dst";
+
+ if ((filter == CONV_FT_ANY_ADDRESS) && (conv->src_address.type == AT_IPv4))
+ return "ip.addr";
+
+ return CONV_FILTER_INVALID;
+}
+
+static ct_dissector_info_t ip_ct_dissector_info = {&ip_conv_get_filter_type};
+
+static int
+ip_conversation_packet(void *pct, packet_info *pinfo, epan_dissect_t *edt _U_, const void *vip)
+{
+ conv_hash_t *hash = (conv_hash_t*) pct;
+ const ws_ip *iph=(const ws_ip *)vip;
+
+ add_conversation_table_data(hash, &iph->ip_src, &iph->ip_dst, 0, 0, 1, pinfo->fd->pkt_len, &pinfo->rel_ts, &ip_ct_dissector_info, PT_NONE);
+
+ return 1;
+}
/*
* defragmentation of IPv4
@@ -3050,6 +3077,7 @@ proto_register_ip(void)
ip_tap = register_tap("ip");
register_decode_as(&ip_da);
+ register_conversation_table(proto_ip, TRUE, ip_conversation_packet);
}
void
diff --git a/epan/dissectors/packet-ipv6.c b/epan/dissectors/packet-ipv6.c
index 22a2eeaf0c..7191c2ca3d 100644
--- a/epan/dissectors/packet-ipv6.c
+++ b/epan/dissectors/packet-ipv6.c
@@ -33,6 +33,7 @@
#include <epan/ip_opts.h>
#include <epan/addr_resolv.h>
#include <epan/prefs.h>
+#include <epan/conversation_table.h>
#include <epan/reassemble.h>
#include <epan/ipproto.h>
#include <epan/ipv6-utils.h>
@@ -347,6 +348,41 @@ static gpointer ipv6_next_header_value(packet_info *pinfo)
return p_get_proto_data(pinfo->pool, pinfo, proto_ipv6, IPV6_PROTO_NXT_HDR);
}
+static const char* ipv6_conv_get_filter_type(conv_item_t* conv, conv_filter_type_e filter)
+{
+ if ((filter == CONV_FT_SRC_ADDRESS) && (conv->src_address.type == AT_IPv6))
+ return "ipv6.src";
+
+ if ((filter == CONV_FT_DST_ADDRESS) && (conv->dst_address.type == AT_IPv6))
+ return "ipv6.dst";
+
+ if ((filter == CONV_FT_ANY_ADDRESS) && (conv->src_address.type == AT_IPv6))
+ return "ipv6.addr";
+
+ return CONV_FILTER_INVALID;
+}
+
+static ct_dissector_info_t ipv6_ct_dissector_info = {&ipv6_conv_get_filter_type};
+
+static int
+ipv6_conversation_packet(void *pct, packet_info *pinfo, epan_dissect_t *edt _U_, const void *vip)
+{
+ conv_hash_t *hash = (conv_hash_t*) pct;
+ const struct ip6_hdr *ip6h = (const struct ip6_hdr *)vip;
+ address src;
+ address dst;
+
+ /* Addresses aren't implemented as 'address' type in struct ip6_hdr */
+ src.type = dst.type = AT_IPv6;
+ src.len = dst.len = sizeof(struct e_in6_addr);
+ src.data = &ip6h->ip6_src;
+ dst.data = &ip6h->ip6_dst;
+
+ add_conversation_table_data(hash, &src, &dst, 0, 0, 1, pinfo->fd->pkt_len, &pinfo->rel_ts, &ipv6_ct_dissector_info, PT_NONE);
+
+ return 1;
+}
+
static const fragment_items ipv6_frag_items = {
&ett_ipv6_fragment,
&ett_ipv6_fragments,
@@ -2908,6 +2944,8 @@ proto_register_ipv6(void)
register_decode_as(&ipv6_da);
register_decode_as(&ipv6_next_header_da);
+
+ register_conversation_table(proto_ipv6, TRUE, ipv6_conversation_packet);
}
void
diff --git a/epan/dissectors/packet-ipx.c b/epan/dissectors/packet-ipx.c
index b845bdf3c9..74b0e2f3cd 100644
--- a/epan/dissectors/packet-ipx.c
+++ b/epan/dissectors/packet-ipx.c
@@ -38,6 +38,7 @@
#include <epan/aftypes.h>
#include <epan/arcnet_pids.h>
#include <epan/conversation.h>
+#include <epan/conversation_table.h>
#include <epan/tap.h>
#include <epan/wmem/wmem.h>
@@ -146,6 +147,33 @@ dissect_ipxmsg(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree);
#define IPX_HEADER_LEN 30 /* It's *always* 30 bytes */
+static const char* ipx_conv_get_filter_type(conv_item_t* conv, conv_filter_type_e filter)
+{
+ if ((filter == CONV_FT_SRC_ADDRESS) && (conv->src_address.type == AT_IPX))
+ return "ipx.src";
+
+ if ((filter == CONV_FT_DST_ADDRESS) && (conv->dst_address.type == AT_IPX))
+ return "ipx.dst";
+
+ if ((filter == CONV_FT_ANY_ADDRESS) && (conv->src_address.type == AT_IPX))
+ return "ipx.addr";
+
+ return CONV_FILTER_INVALID;
+}
+
+static ct_dissector_info_t ipx_ct_dissector_info = {&ipx_conv_get_filter_type};
+
+static int
+ipx_conversation_packet(void *pct, packet_info *pinfo, epan_dissect_t *edt _U_, const void *vip)
+{
+ conv_hash_t *hash = (conv_hash_t*) pct;
+ const ipxhdr_t *ipxh=(const ipxhdr_t *)vip;
+
+ add_conversation_table_data(hash, &ipxh->ipx_src, &ipxh->ipx_dst, 0, 0, 1, pinfo->fd->pkt_len, &pinfo->rel_ts, &ipx_ct_dissector_info, PT_NONE);
+
+ return 1;
+}
+
/* ================================================================= */
/* IPX */
/* ================================================================= */
@@ -1539,6 +1567,8 @@ proto_register_ipx(void)
register_init_routine(&spx_init_protocol);
register_postseq_cleanup_routine(&spx_postseq_cleanup);
ipx_tap=register_tap("ipx");
+
+ register_conversation_table(proto_ipx, TRUE, ipx_conversation_packet);
}
void
diff --git a/epan/dissectors/packet-jxta.c b/epan/dissectors/packet-jxta.c
index 09a2cfc23a..bc39290d59 100644
--- a/epan/dissectors/packet-jxta.c
+++ b/epan/dissectors/packet-jxta.c
@@ -39,6 +39,7 @@
#include <epan/packet.h>
#include <epan/conversation.h>
+#include <epan/conversation_table.h>
#include <epan/strutil.h>
#include <epan/prefs.h>
#include <epan/tap.h>
@@ -194,6 +195,34 @@ struct jxta_stream_conversation_data {
typedef struct jxta_stream_conversation_data jxta_stream_conversation_data;
+static const char* jxta_conv_get_filter_type(conv_item_t* conv, conv_filter_type_e filter)
+{
+ if ((filter == CONV_FT_SRC_ADDRESS) && (conv->src_address.type == AT_URI))
+ return "jxta.message.src";
+
+ if ((filter == CONV_FT_DST_ADDRESS) && (conv->dst_address.type == AT_URI))
+ return "jxta.message.dst";
+
+ if ((filter == CONV_FT_ANY_ADDRESS) && (conv->src_address.type == AT_URI))
+ return "jxta.message.address";
+
+ return CONV_FILTER_INVALID;
+}
+
+static ct_dissector_info_t jxta_ct_dissector_info = {&jxta_conv_get_filter_type};
+
+static int
+jxta_conversation_packet(void *pct, packet_info *pinfo _U_, epan_dissect_t *edt _U_, const void *vip)
+{
+ conv_hash_t *hash = (conv_hash_t*) pct;
+ const jxta_tap_header *jxtahdr = (const jxta_tap_header *) vip;
+
+ add_conversation_table_data(hash, &jxtahdr->src_address, &jxtahdr->dest_address,
+ 0, 0, 1, jxtahdr->size, NULL, &jxta_ct_dissector_info, PT_NONE);
+
+ return 1;
+}
+
/**
* Prototypes
**/
@@ -2322,6 +2351,8 @@ void proto_register_jxta(void)
prefs_register_bool_preference(jxta_module, "sctp.heuristic", "Try to discover JXTA in SCTP connections",
"Enable to inspect SCTP connections for JXTA conversations.", &gSCTP_HEUR);
+
+ register_conversation_table(proto_jxta, TRUE, jxta_conversation_packet);
}
diff --git a/epan/dissectors/packet-ncp.c b/epan/dissectors/packet-ncp.c
index 8398dc8264..c75c2430ee 100644
--- a/epan/dissectors/packet-ncp.c
+++ b/epan/dissectors/packet-ncp.c
@@ -54,6 +54,7 @@
#include "packet-ncp-int.h"
#include <epan/reassemble.h>
#include <epan/conversation.h>
+#include <epan/conversation_table.h>
#include <epan/tap.h>
void proto_register_ncp(void);
@@ -295,6 +296,31 @@ mncp_hash_lookup(conversation_t *conversation, guint32 nwconnection, guint8 nwta
return (mncp_rhash_value *)g_hash_table_lookup(mncp_rhash, &key);
}
+static const char* ncp_conv_get_filter_type(conv_item_t* conv _U_, conv_filter_type_e filter)
+{
+ if ((filter == CONV_FT_SRC_PORT) || (filter == CONV_FT_DST_PORT) || (filter == CONV_FT_ANY_PORT))
+ return "ncp.connection";
+
+ return CONV_FILTER_INVALID;
+}
+
+static ct_dissector_info_t ncp_ct_dissector_info = {&ncp_conv_get_filter_type};
+
+static int
+ncp_conversation_packet(void *pct, packet_info *pinfo, epan_dissect_t *edt _U_, const void *vip)
+{
+ conv_hash_t *hash = (conv_hash_t*) pct;
+ const struct ncp_common_header *ncph=(const struct ncp_common_header *)vip;
+ guint32 connection;
+
+ connection = (ncph->conn_high * 256)+ncph->conn_low;
+ if (connection < 65535) {
+ add_conversation_table_data(hash, &pinfo->src, &pinfo->dst, connection, connection, 1, pinfo->fd->pkt_len, &pinfo->rel_ts, &ncp_ct_dissector_info, PT_NCP);
+ }
+
+ return 1;
+}
+
/*
* Burst packet system flags.
*/
@@ -1098,8 +1124,10 @@ proto_register_ncp(void)
&ncp_echo_file);
register_init_routine(&mncp_init_protocol);
ncp_tap.stat=register_tap("ncp_srt");
- ncp_tap.hdr=register_tap("ncp_hdr");
+ ncp_tap.hdr=register_tap("ncp");
register_postseq_cleanup_routine(&mncp_postseq_cleanup);
+
+ register_conversation_table(proto_ncp, FALSE, ncp_conversation_packet);
}
void
diff --git a/epan/dissectors/packet-rsvp.c b/epan/dissectors/packet-rsvp.c
index 1eabc28539..a4c3363aa2 100644
--- a/epan/dissectors/packet-rsvp.c
+++ b/epan/dissectors/packet-rsvp.c
@@ -108,6 +108,7 @@
#include <epan/etypes.h>
#include <epan/ipproto.h>
#include <epan/conversation.h>
+#include <epan/conversation_table.h>
#include <epan/tap.h>
#include <epan/sminmpec.h>
#include <epan/wmem/wmem.h>
@@ -1871,6 +1872,26 @@ rsvp_init_protocol(void)
rsvp_request_hash = g_hash_table_new(rsvp_hash, rsvp_equal);
}
+static const char* rsvp_conv_get_filter_type(conv_item_t* conv _U_, conv_filter_type_e filter _U_)
+{
+ /* XXX - Not sure about this */
+ return CONV_FILTER_INVALID;
+}
+
+static ct_dissector_info_t rsvp_ct_dissector_info = {&rsvp_conv_get_filter_type};
+
+static int
+rsvp_conversation_packet(void *pct, packet_info *pinfo, epan_dissect_t *edt _U_, const void *vip)
+{
+ conv_hash_t *hash = (conv_hash_t*) pct;
+ const rsvp_conversation_info *rsvph = (const rsvp_conversation_info *)vip;
+
+ add_conversation_table_data(hash, &rsvph->source, &rsvph->destination,
+ 0, 0, 1, pinfo->fd->pkt_len, &pinfo->rel_ts, &rsvp_ct_dissector_info, PT_NONE);
+
+ return 1;
+}
+
static inline int
rsvp_class_to_filter_num(int classnum)
{
@@ -9156,6 +9177,8 @@ proto_register_rsvp(void)
/* Initialization routine for RSVP conversations */
register_init_routine(&rsvp_init_protocol);
+
+ register_conversation_table(proto_rsvp, TRUE, rsvp_conversation_packet);
}
void
diff --git a/epan/dissectors/packet-sctp.c b/epan/dissectors/packet-sctp.c
index b88ec19b0c..28f01b8335 100644
--- a/epan/dissectors/packet-sctp.c
+++ b/epan/dissectors/packet-sctp.c
@@ -64,6 +64,7 @@
#include <epan/uat.h>
#include <epan/wmem/wmem.h>
#include <epan/expert.h>
+#include <epan/conversation_table.h>
#include <epan/show_exception.h>
#include <epan/decode_as.h>
#include <wsutil/crc32.h>
@@ -774,6 +775,34 @@ sctp_ppi_value2(packet_info *pinfo)
return p_get_proto_data(pinfo->pool, pinfo, proto_sctp, 1);
}
+static const char* sctp_conv_get_filter_type(conv_item_t* conv _U_, conv_filter_type_e filter)
+{
+ if (filter == CONV_FT_SRC_PORT)
+ return "sctp.srcport";
+
+ if (filter == CONV_FT_DST_PORT)
+ return "sctp.dstport";
+
+ if (filter == CONV_FT_ANY_PORT)
+ return "sctp.port";
+
+ return CONV_FILTER_INVALID;
+}
+
+static ct_dissector_info_t sctp_ct_dissector_info = {&sctp_conv_get_filter_type};
+
+static int
+sctp_conversation_packet(void *pct, packet_info *pinfo, epan_dissect_t *edt _U_, const void *vip)
+{
+ conv_hash_t *hash = (conv_hash_t*) pct;
+ const struct _sctp_info *sctphdr=(const struct _sctp_info *)vip;
+
+ add_conversation_table_data(hash, &sctphdr->ip_src, &sctphdr->ip_dst,
+ sctphdr->sport, sctphdr->dport, 1, pinfo->fd->pkt_len, &pinfo->rel_ts, &sctp_ct_dissector_info, PT_SCTP);
+
+
+ return 1;
+}
static unsigned int
sctp_adler32(const unsigned char *buf, unsigned int len)
@@ -4834,6 +4863,8 @@ proto_register_sctp(void)
register_decode_as(&sctp_da_port);
register_decode_as(&sctp_da_ppi);
+
+ register_conversation_table(proto_sctp, FALSE, sctp_conversation_packet);
}
void
diff --git a/epan/dissectors/packet-tcp.c b/epan/dissectors/packet-tcp.c
index 2d69807b96..871825e86d 100644
--- a/epan/dissectors/packet-tcp.c
+++ b/epan/dissectors/packet-tcp.c
@@ -38,6 +38,7 @@
#include <epan/wmem/wmem.h>
#include <epan/show_exception.h>
#include <epan/conversation.h>
+#include <epan/conversation_table.h>
#include <epan/reassemble.h>
#include <epan/tap.h>
#include <epan/decode_as.h>
@@ -523,6 +524,34 @@ tcp_both_prompt(packet_info *pinfo, gchar *result)
g_snprintf(result, MAX_DECODE_AS_PROMPT_LEN, "both (%u%s%u)", pinfo->srcport, UTF8_LEFT_RIGHT_ARROW, pinfo->destport);
}
+static const char* tcp_conv_get_filter_type(conv_item_t* conv _U_, conv_filter_type_e filter)
+{
+ if (filter == CONV_FT_SRC_PORT)
+ return "tcp.srcport";
+
+ if (filter == CONV_FT_DST_PORT)
+ return "tcp.dstport";
+
+ if (filter == CONV_FT_ANY_PORT)
+ return "tcp.port";
+
+ return CONV_FILTER_INVALID;
+}
+
+static ct_dissector_info_t tcp_ct_dissector_info = {&tcp_conv_get_filter_type};
+
+static int
+tcpip_conversation_packet(void *pct, packet_info *pinfo, epan_dissect_t *edt _U_, const void *vip)
+{
+ conv_hash_t *hash = (conv_hash_t*) pct;
+ const struct tcpheader *tcphdr=(const struct tcpheader *)vip;
+
+ add_conversation_table_data_with_conv_id(hash, &tcphdr->ip_src, &tcphdr->ip_dst, tcphdr->th_sport, tcphdr->th_dport, (conv_id_t) tcphdr->th_stream, 1, pinfo->fd->pkt_len,
+ &pinfo->rel_ts, &tcp_ct_dissector_info, PT_TCP);
+
+ return 1;
+}
+
/* TCP structs and definitions */
/* **************************************************************************
@@ -5818,6 +5847,8 @@ proto_register_tcp(void)
register_init_routine(tcp_init);
register_decode_as(&tcp_da);
+
+ register_conversation_table(proto_tcp, FALSE, tcpip_conversation_packet);
}
void
diff --git a/epan/dissectors/packet-tr.c b/epan/dissectors/packet-tr.c
index 172b30af2c..30d7698897 100644
--- a/epan/dissectors/packet-tr.c
+++ b/epan/dissectors/packet-tr.c
@@ -27,6 +27,7 @@
#include <glib.h>
#include <epan/packet.h>
#include <epan/exceptions.h>
+#include <epan/conversation_table.h>
#include <wsutil/pint.h>
#include "packet-tr.h"
#include "packet-llc.h"
@@ -127,6 +128,33 @@ static dissector_handle_t trmac_handle;
static dissector_handle_t llc_handle;
static dissector_handle_t data_handle;
+static const char* tr_conv_get_filter_type(conv_item_t* conv, conv_filter_type_e filter)
+{
+ if ((filter == CONV_FT_SRC_ADDRESS) && (conv->src_address.type == AT_ETHER))
+ return "tr.src";
+
+ if ((filter == CONV_FT_DST_ADDRESS) && (conv->dst_address.type == AT_ETHER))
+ return "tr.dst";
+
+ if ((filter == CONV_FT_ANY_ADDRESS) && (conv->src_address.type == AT_ETHER))
+ return "tr.addr";
+
+ return CONV_FILTER_INVALID;
+}
+
+static ct_dissector_info_t tr_ct_dissector_info = {&tr_conv_get_filter_type};
+
+static int
+tr_conversation_packet(void *pct, packet_info *pinfo, epan_dissect_t *edt _U_, const void *vip)
+{
+ conv_hash_t *hash = (conv_hash_t*) pct;
+ const tr_hdr *trhdr=(const tr_hdr *)vip;
+
+ add_conversation_table_data(hash, &trhdr->src, &trhdr->dst, 0, 0, 1, pinfo->fd->pkt_len, &pinfo->rel_ts, &tr_ct_dissector_info, PT_NONE);
+
+ return 1;
+}
+
/*
* DODGY LINUX HACK DODGY LINUX HACK
* Linux 2.0.x always passes frames to the Token Ring driver for transmission with
@@ -734,6 +762,8 @@ proto_register_tr(void)
register_dissector("tr", dissect_tr, proto_tr);
tr_tap=register_tap("tr");
+
+ register_conversation_table(proto_tr, TRUE, tr_conversation_packet);
}
void
diff --git a/epan/dissectors/packet-udp.c b/epan/dissectors/packet-udp.c
index 1def80f2d3..4a730dcd3e 100644
--- a/epan/dissectors/packet-udp.c
+++ b/epan/dissectors/packet-udp.c
@@ -43,6 +43,7 @@
#include "packet-ip.h"
#include <epan/conversation.h>
+#include <epan/conversation_table.h>
#include <epan/tap.h>
#include <epan/decode_as.h>
@@ -280,6 +281,33 @@ get_udp_conversation_data(conversation_t *conv, packet_info *pinfo)
return udpd;
}
+static const char* udp_conv_get_filter_type(conv_item_t* conv _U_, conv_filter_type_e filter)
+{
+ if (filter == CONV_FT_SRC_PORT)
+ return "udp.srcport";
+
+ if (filter == CONV_FT_DST_PORT)
+ return "udp.dstport";
+
+ if (filter == CONV_FT_ANY_PORT)
+ return "udp.port";
+
+ return CONV_FILTER_INVALID;
+}
+
+static ct_dissector_info_t udp_ct_dissector_info = {&udp_conv_get_filter_type};
+
+static int
+udpip_conversation_packet(void *pct, packet_info *pinfo, epan_dissect_t *edt _U_, const void *vip)
+{
+ conv_hash_t *hash = (conv_hash_t*) pct;
+ const e_udphdr *udphdr=(const e_udphdr *)vip;
+
+ add_conversation_table_data(hash, &udphdr->ip_src, &udphdr->ip_dst, udphdr->uh_sport, udphdr->uh_dport, 1, pinfo->fd->pkt_len, &pinfo->rel_ts, &udp_ct_dissector_info, PT_UDP);
+
+ return 1;
+}
+
/* Attach process info to a flow */
/* XXX - We depend on the UDP dissector finding the conversation first */
void
@@ -891,6 +919,7 @@ proto_register_udp(void)
&udplite_check_checksum);
register_decode_as(&udp_da);
+ register_conversation_table(proto_udp, FALSE, udpip_conversation_packet);
register_init_routine(udp_init);
diff --git a/epan/dissectors/packet-usb.c b/epan/dissectors/packet-usb.c
index e94cf8d9d7..aebf750bfe 100644
--- a/epan/dissectors/packet-usb.c
+++ b/epan/dissectors/packet-usb.c
@@ -34,6 +34,7 @@
#include <epan/wmem/wmem.h>
#include <epan/tap.h>
#include <epan/conversation.h>
+#include <epan/conversation_table.h>
#include <epan/expert.h>
#include <epan/prefs.h>
#include <epan/decode_as.h>
@@ -1101,6 +1102,30 @@ get_usb_iface_conv_info(packet_info *pinfo, guint8 interface_num)
return get_usb_conv_info(conversation);
}
+static const char* usb_conv_get_filter_type(conv_item_t* conv, conv_filter_type_e filter)
+{
+ if ((filter == CONV_FT_SRC_ADDRESS) && (conv->src_address.type == AT_USB))
+ return "usb.sa";
+
+ if ((filter == CONV_FT_DST_ADDRESS) && (conv->dst_address.type == AT_USB))
+ return "usb.da";
+
+ if ((filter == CONV_FT_ANY_ADDRESS) && (conv->src_address.type == AT_USB))
+ return "usb.addr";
+
+ return CONV_FILTER_INVALID;
+}
+
+static ct_dissector_info_t usb_ct_dissector_info = {&usb_conv_get_filter_type};
+
+static int
+usb_conversation_packet(void *pct, packet_info *pinfo, epan_dissect_t *edt _U_, const void *vip _U_)
+{
+ conv_hash_t *hash = (conv_hash_t*) pct;
+ add_conversation_table_data(hash, &pinfo->src, &pinfo->dst, 0, 0, 1, pinfo->fd->pkt_len, &pinfo->rel_ts, &usb_ct_dissector_info, PT_NONE);
+
+ return 1;
+}
/* SETUP dissectors */
@@ -4087,6 +4112,11 @@ proto_register_usb(void)
expert_module_t* expert_usb;
+ proto_usb = proto_register_protocol("USB", "USB", "usb");
+ proto_register_field_array(proto_usb, hf, array_length(hf));
+ proto_register_subtree_array(usb_subtrees, array_length(usb_subtrees));
+ linux_usb_handle = register_dissector("usb", dissect_linux_usb, proto_usb);
+
expert_usb = expert_register_protocol(proto_usb);
expert_register_field_array(expert_usb, ei, array_length(ei));
@@ -4096,11 +4126,6 @@ proto_register_usb(void)
protocol_to_dissector = register_dissector_table("usb.protocol", "USB protocol", FT_UINT32, BASE_HEX);
product_to_dissector = register_dissector_table("usb.product", "USB product", FT_UINT32, BASE_HEX);
- proto_usb = proto_register_protocol("USB", "USB", "usb");
- proto_register_field_array(proto_usb, hf, array_length(hf));
- proto_register_subtree_array(usb_subtrees, array_length(usb_subtrees));
- linux_usb_handle = register_dissector("usb", dissect_linux_usb, proto_usb);
-
usb_bulk_dissector_table = register_dissector_table("usb.bulk",
"USB bulk endpoint", FT_UINT8, BASE_DEC);
register_heur_dissector_list("usb.bulk", &heur_bulk_subdissector_list);
@@ -4125,6 +4150,8 @@ proto_register_usb(void)
register_decode_as(&usb_protocol_da);
register_decode_as(&usb_product_da);
register_decode_as(&usb_device_da);
+
+ register_conversation_table(proto_usb, TRUE, usb_conversation_packet);
}
void
diff --git a/epan/proto.c b/epan/proto.c
index 05d7c9f119..059e18a495 100644
--- a/epan/proto.c
+++ b/epan/proto.c
@@ -4827,12 +4827,11 @@ proto_register_protocol(const char *name, const char *short_name,
}
g_hash_table_insert(proto_names, key, (gpointer)name);
- existing_name = (const char *)g_hash_table_lookup(proto_short_names, (gpointer)short_name);
- if (existing_name != NULL) {
+ existing_protocol = (const protocol_t *)g_hash_table_lookup(proto_short_names, (gpointer)short_name);
+ if (existing_protocol != NULL) {
g_error("Duplicate protocol short_name \"%s\"!"
" This might be caused by an inappropriate plugin or a development error.", short_name);
}
- g_hash_table_insert(proto_short_names, (gpointer)short_name, (gpointer)short_name);
found_invalid = FALSE;
for (i = 0; filter_name[i]; i++) {
@@ -4865,6 +4864,7 @@ proto_register_protocol(const char *name, const char *short_name,
/* list will be sorted later by name, when all protocols completed registering */
protocols = g_list_prepend(protocols, protocol);
g_hash_table_insert(proto_filter_names, (gpointer)filter_name, protocol);
+ g_hash_table_insert(proto_short_names, (gpointer)short_name, protocol);
/* Here we allocate a new header_field_info struct */
hfinfo = g_slice_new(header_field_info);
@@ -5012,6 +5012,22 @@ int proto_get_id_by_filter_name(const gchar* filter_name)
return protocol->proto_id;
}
+int proto_get_id_by_short_name(const gchar* short_name)
+{
+ const protocol_t *protocol = NULL;
+
+ if(!short_name){
+ fprintf(stderr, "No short name present");
+ DISSECTOR_ASSERT(short_name);
+ }
+
+ protocol = (const protocol_t *)g_hash_table_lookup(proto_short_names, (gpointer)short_name);
+
+ if (protocol == NULL)
+ return -1;
+ return protocol->proto_id;
+}
+
const char *
proto_get_protocol_name(const int proto_id)
{
diff --git a/epan/proto.h b/epan/proto.h
index cb456f8ebc..dada8a938e 100644
--- a/epan/proto.h
+++ b/epan/proto.h
@@ -1984,6 +1984,11 @@ WS_DLL_PUBLIC header_field_info *proto_get_next_protocol_field(const int proto_i
@return proto_id */
WS_DLL_PUBLIC int proto_get_id_by_filter_name(const gchar* filter_name);
+/** Given a protocol's short name.
+ @param short_name the protocol short name to search for
+ @return proto_id */
+WS_DLL_PUBLIC int proto_get_id_by_short_name(const gchar* short_name);
+
/** Can item # n decoding be disabled?
@param proto_id protocol id (0-indexed)
@return TRUE if it's a protocol, FALSE if it's not */
diff --git a/epan/stat_cmd_args.h b/epan/stat_cmd_args.h
index a8bfaf60a2..499bf32a5d 100644
--- a/epan/stat_cmd_args.h
+++ b/epan/stat_cmd_args.h
@@ -30,6 +30,13 @@
extern "C" {
#endif /* __cplusplus */
+/** Register a stat ("-z") command line argument.
+ *
+ * @param cmd The command name without arguments, e.g. "conv,tcp" or "io,stat".
+ * MUST be valid when other stat_cmd routines below are called.
+ * @param func Callbak to be invoked when the CLI argument is supplied.
+ * @param userdata Additional data for the callback.
+ */
WS_DLL_PUBLIC void register_stat_cmd_arg(const char *cmd,
void (*func)(const char *arg,void* userdata), void* userdata);
WS_DLL_PUBLIC gboolean process_stat_cmd_arg(char *optstr);