ANNOUNCEMENT: Live Wireshark University & Allegro Packets online APAC Wireshark Training Session
July 17th, 2024 | 10:00am-11:55am SGT (UTC+8) | Online

Ethereal-dev: [Ethereal-dev] New dissector Gnutella

Note: This archive is from the project's previous web site, ethereal.com. This list is no longer active.

From: ENDOH Akira <endoh@xxxxxxxxxxxxxx>
Date: Tue, 15 May 2001 20:08:56 +0900
Hello,

I wrote a new dissector for Gnutella Protocol.
I referred to the document `The Gnutella Protocol Specification v0.4'
written by Clip2 (http://www.clip2.com/).

Since Gnutella applications use HTTP to transfer files, I want to use
dissect_http() function in my dissector. But in packet-http.c,
dissect_http() is declared with `static' keyword, and I can't call
dissect_http() in my dissector. I use dissect_data() instead of
dissect_http(). If my dissector can call dissect_http(),
a few changes (in line 149, 225 and 226) make Ethereal to dissect
Gnutella file transfer as HTTP.

Regards,

----
endoh


------------------------
/* packet-gnutella.c
 * Routines for Gnutella Protocol dissection
 * By ENDOH Akira <endoh@xxxxxxxxxxxxxx>
 *
 *
 * Ethereal - Network traffic analyzer
 * By Gerald Combs <gerald@xxxxxxxxxx>
 * Copyright 1998 Gerald Combs
 *
 * Reference to `The Gnutella Protocol Specification v0.4': Clip2
 * Copied from doc/README.developer
 *
 * To do:
 *     - clean up the code
 *     - fix up the bug caused by the descriptor which has invalid(?) length
 * 
 * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <glib.h>

#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif

#ifdef HAVE_NETINET_IN_H
# include <netinet/in.h>
#endif

#ifdef NEED_SNPRINTF_H
# include "snprintf.h"
#endif

#include "strutil.h"
#include "packet.h"

#define Ping     0x00
#define Pong     0x01
#define Push     0x40
#define Query    0x80
#define QueryHit 0x81
#define DESC_PING_LEN          0
#define DESC_PONG_LEN         14
#define DESC_QUERY_MIN_LEN     4
#define DESC_QUERYHIT_MIN_LEN 27 /* sizeof(QueryHit w/o ResultSets & Trailler) */
#define DESC_PUSH_LEN         26
#define RSET_LEN              11

static const value_string payload_descriptor_val[] = {
    {Ping,     "Ping"},
    {Pong,     "Pong"},
    {Push,     "Push"},
    {Query,    "Query"},
    {QueryHit, "Query Hit"}
};

#define TCP_PORT_GNUTELLA 6346

static int proto_gnutella                   = -1;
static int hf_gnutella_descriptor_id        = -1;
static int hf_gnutella_payload_descriptor   = -1;
static int hf_gnutella_ttl                  = -1;
static int hf_gnutella_hops                 = -1;
static int hf_gnutella_payload_length       = -1;
static int hf_gnutella_pong_port            = -1;
static int hf_gnutella_pong_address         = -1;
static int hf_gnutella_pong_nfs             = -1;
static int hf_gnutella_pong_nks             = -1;
static int hf_gnutella_push_sid             = -1;
static int hf_gnutella_push_findex          = -1;
static int hf_gnutella_push_address         = -1;
static int hf_gnutella_push_port            = -1;
static int hf_gnutella_query_ms             = -1;
static int hf_gnutella_query_sc             = -1;
static int hf_gnutella_queryhit_noh         = -1;
static int hf_gnutella_queryhit_port        = -1;
static int hf_gnutella_queryhit_address     = -1;
static int hf_gnutella_queryhit_speed       = -1;
static int hf_gnutella_queryhit_sid         = -1;
static int hf_gnutella_resultset_findex     = -1;
static int hf_gnutella_resultset_fsize      = -1;
static int hf_gnutella_resultset_fname      = -1;
static int hf_gnutella_trailer_vcode        = -1;
static int hf_gnutella_trailer_odsize       = -1;
static int hf_gnutella_trailer_odata        = -1;
static int hf_gnutella_trailer_flag_upspeed = -1;
static int hf_gnutella_trailer_flag_huped   = -1;
static int hf_gnutella_trailer_flag_busy    = -1;
static int hf_gnutella_trailer_flag_push    = -1;
static int hf_gnutella_trailer_pdata        = -1;
static gint ett_gnutella      = -1;
static gint ett_gnutella_desc = -1;
static gint ett_pong          = -1;
static gint ett_push          = -1;
static gint ett_query         = -1;
static gint ett_queryhit      = -1;
static gint ett_resultset     = -1;
static gint ett_trailer       = -1;
static gint ett_odata         = -1;
static int  descriptor        = 0;
static const true_false_string flags_set_truth = {
  "Set",
  "Not set"
};

typedef enum _gnutella_proto_t {
    GNUTELLA_CONNECT,
    GNUTELLA_OK,
    GNUTELLA_GIV,
    GNUTELLA_DESC,
    HTTP
} gnutella_proto_t;


static guint dissect_desc_header(tvbuff_t*, proto_tree*, int);
static void  dissect_pong(tvbuff_t*, proto_tree*, guint);
static void  dissect_push(tvbuff_t*, proto_tree*, guint);
static void  dissect_query(tvbuff_t*, proto_tree*, guint, guint);
static void  dissect_queryhit(tvbuff_t*, proto_tree*, guint, guint);
static guint dissect_resultset(tvbuff_t*, proto_tree*, guint, int);
static guint dissect_trailer(tvbuff_t*, proto_tree*, guint, guint);
static void  dissect_odata(tvbuff_t*, proto_tree*, guint);
static gnutella_proto_t distinguish_protocol(tvbuff_t*);
static int is_descriptor(tvbuff_t*, guint);
/* static int ping_len_check(tvbuff_t*, guint); */
static int pong_len_check(tvbuff_t*, guint);
static int query_len_check(tvbuff_t*, guint);
static int queryhit_len_check(tvbuff_t*, guint);
static int push_len_check(tvbuff_t*, guint);
/* void dissect_http(tvbuff_t*, packet_info*, proto_tree*); */


static void
dissect_gnutella(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) {
    proto_item   *ti = NULL;
    proto_tree   *gnutella_tree;
    tvbuff_t     *next_tvb;
    guint        offset = 0, len = tvb_length(tvb), i;
    const gint8  *buf;

    CHECK_DISPLAY_AS_DATA(proto_gnutella, tvb, pinfo, tree);
    pinfo->current_proto = "Gnutella";

    if (check_col(pinfo->fd, COL_PROTOCOL)) 
	col_add_str(pinfo->fd, COL_PROTOCOL, "Gnutella");

    switch (distinguish_protocol(tvb)) {

    case GNUTELLA_CONNECT:
	buf = tvb_get_ptr(tvb, 0, len);
	if (check_col(pinfo->fd, COL_INFO))
	    col_add_str(pinfo->fd, COL_INFO, format_text(buf, len-2));
	if (tree) {
	    ti = proto_tree_add_item(tree, proto_gnutella, tvb, 0, len, FALSE);
	    gnutella_tree = proto_item_add_subtree(ti, ett_gnutella);
	    proto_tree_add_text(gnutella_tree, tvb, 0, len, format_text(buf, len));
	}
	break;

    case GNUTELLA_OK:
	if (check_col(pinfo->fd, COL_INFO))
	    col_add_str(pinfo->fd, COL_INFO, "GNUTELLA OK");
	offset = 13;
	if (tree) {
	    ti = proto_tree_add_item(tree, proto_gnutella, tvb, 0, len, FALSE);
	    gnutella_tree = proto_item_add_subtree(ti, ett_gnutella);
	    proto_tree_add_text(gnutella_tree, tvb, 0, offset, "GNUTELLA OK\\n\\n");
	}
	if (descriptor == 0) break;

    case GNUTELLA_DESC:
	if (check_col(pinfo->fd, COL_INFO)) {
	    if (offset == 0)
		col_add_fstr(pinfo->fd, COL_INFO, "Descriptor - Containing %d descriptor%s",
			     descriptor, descriptor>1?"s":"");
	    else
		col_append_fstr(pinfo->fd, COL_INFO, "; Descriptor - Containing %d descriptor%s",
				descriptor, descriptor>1?"s":"");
	}
	if (tree) {
	    if (offset == 0)
		ti = proto_tree_add_item(tree, proto_gnutella, tvb, 0, len, FALSE);
	    i = 0;
	    while (offset < len) {
		gnutella_tree = proto_item_add_subtree(ti, ett_gnutella);
		next_tvb = tvb_new_subset(tvb, offset, -1, -1);
		offset += dissect_desc_header(next_tvb, gnutella_tree, i);
		i++;
		tvb_free(next_tvb);
	    }
	}
	break;

    case GNUTELLA_GIV:
	buf = tvb_get_ptr(tvb, 0, len);
	if (check_col(pinfo->fd, COL_INFO))
	    col_add_str(pinfo->fd, COL_INFO, format_text(buf, len-2));
	if (tree) {
	    ti = proto_tree_add_item(tree, proto_gnutella, tvb, 0, len, FALSE);
	    gnutella_tree = proto_item_add_subtree(ti, ett_gnutella);
	    proto_tree_add_text(gnutella_tree, tvb, 0, len, format_text(buf, len));
	}
	break;

    case HTTP:
	/*dissect_http(tvb, pinfo, tree);*/
	dissect_data(tvb, 0, pinfo, tree);
    }
}


static guint
dissect_desc_header(tvbuff_t *tvb, proto_tree *tree, int descriptor_num) {
    proto_item    *ti;
    proto_tree    *gnutella_desc_tree;
    const guint8* ptr;
    guint32       desc_pay_len;
    guint         offset = 0;
    int           payload_desc;
 
    if (tree) {
	tvb_memcpy(tvb, (guint8*)&desc_pay_len, 19, 4);
	payload_desc = tvb_get_guint8(tvb, 16);
	ti = proto_tree_add_text(tree, tvb, offset, desc_pay_len + 23,
				 "Descriptor %d (%s - %d byte)", descriptor_num+1,
				 match_strval(payload_desc ,payload_descriptor_val),
				 desc_pay_len+23);
	gnutella_desc_tree = proto_item_add_subtree(ti, ett_gnutella_desc);
	ptr = tvb_get_ptr(tvb, offset, 23);
	proto_tree_add_bytes(gnutella_desc_tree, hf_gnutella_descriptor_id,
			     tvb, offset, 16, ptr);
	offset += 16;
	proto_tree_add_text(gnutella_desc_tree, tvb, offset, 1,
			    "Payload Descriptor: %2x (%s)", payload_desc,
			    match_strval(payload_desc ,payload_descriptor_val));
	proto_tree_add_uint_hidden(gnutella_desc_tree, hf_gnutella_payload_descriptor,
				   tvb, offset, 1, (guint32)payload_desc);
	offset += 1;
	proto_tree_add_uint(gnutella_desc_tree, hf_gnutella_ttl, tvb, offset, 1,
			    (guint32)tvb_get_guint8(tvb, offset));
	offset += 1;
	proto_tree_add_uint(gnutella_desc_tree, hf_gnutella_hops, tvb, offset, 1,
			    (guint32)tvb_get_guint8(tvb, offset));
	offset += 1;
	proto_tree_add_uint_format(gnutella_desc_tree, hf_gnutella_payload_length, tvb, 
				   offset, 4, desc_pay_len, "Payload Length: %d byte",
				   desc_pay_len);
	offset += 4;

	switch (payload_desc) {
	case 0x01:
	    dissect_pong(tvb, gnutella_desc_tree, offset);
	    break;
	case 0x40:
	    dissect_push(tvb, gnutella_desc_tree, offset);
	    break;
	case 0x80:
	    dissect_query(tvb, gnutella_desc_tree, offset, desc_pay_len);
	    break;
	case 0x81:
	    dissect_queryhit(tvb, gnutella_desc_tree, offset, desc_pay_len);
	}
    }
    offset += desc_pay_len;
    return offset;
}


static void
dissect_pong(tvbuff_t *tvb, proto_tree *tree, guint offset)
{
    proto_item *ti;
    proto_tree *pong_tree;
    guint32    i;

    if (tree) {
	if (!pong_len_check(tvb, offset)) {
	    ti = proto_tree_add_text(tree, tvb, offset, tvb_get_letohl(tvb, offset - 4),
				     "Descriptor Payload (Pong) - malformed descriptor");
	    return;
	}
	ti = proto_tree_add_text(tree, tvb, offset, 14, "Descriptor Payload (Pong)");
	pong_tree = proto_item_add_subtree(ti, ett_pong);
	i = tvb_get_letohs(tvb, offset);
	proto_tree_add_uint_format(pong_tree, hf_gnutella_pong_port, tvb,
				   offset, 2, i, "Port: %d", i);
	offset += 2;
	proto_tree_add_ipv4(pong_tree, hf_gnutella_pong_address, tvb,
			    offset, 4, tvb_get_letohl(tvb, offset));
	offset += 4;
	proto_tree_add_uint(pong_tree, hf_gnutella_pong_nfs, tvb, offset, 4,
			    tvb_get_letohl(tvb, offset));
	offset += 4;
	proto_tree_add_uint(pong_tree, hf_gnutella_pong_nks, tvb, offset, 4,
			    tvb_get_letohl(tvb, offset));
    }
}


static void
dissect_push(tvbuff_t *tvb, proto_tree *tree, guint offset)
{
    proto_item *ti;
    proto_tree *push_tree;

    if (tree) {
	if (!push_len_check(tvb, offset)) {
	    ti = proto_tree_add_text(tree, tvb, offset, tvb_get_letohl(tvb, offset-4),
				     "Descriptor Payload (Push) - malformed descriptor");
	    return;
	}
	ti = proto_tree_add_text(tree, tvb, offset, 26, "Descriptor Payload (Push)");

	push_tree = proto_item_add_subtree(ti, ett_push);
	proto_tree_add_bytes(push_tree, hf_gnutella_push_sid, tvb, offset,
			     16, tvb_get_ptr(tvb, offset, 16));
	offset += 16;
	proto_tree_add_uint(push_tree, hf_gnutella_push_findex, tvb, offset,
			    4, tvb_get_letohl(tvb, offset));
	offset += 4;
	proto_tree_add_ipv4(push_tree, hf_gnutella_push_address, tvb,
			    offset, 4, tvb_get_letohl(tvb, offset));
	offset += 4;
	proto_tree_add_uint(push_tree, hf_gnutella_push_port, tvb,
			    offset, 2, tvb_get_letohs(tvb, offset));
	offset += 2;
    }
}


static void
dissect_query(tvbuff_t *tvb, proto_tree *tree, guint offset, guint len)
{
    proto_item *ti;
    proto_tree *query_tree;
    guint32    i;

    if (tree) {
	if (!query_len_check(tvb, offset)) {
	    ti = proto_tree_add_text(tree, tvb, offset, tvb_get_letohl(tvb, offset - 4),
				     "Descriptor Payload (Query) - malformed descriptor");
	    return;
	}
	ti = proto_tree_add_text(tree, tvb, offset, len, "Descriptor Payload (Query)");
	query_tree = proto_item_add_subtree(ti, ett_query);
	i = (guint32)tvb_get_letohs(tvb, offset);
	proto_tree_add_uint_format(query_tree, hf_gnutella_query_ms, tvb,
				   offset, 2, i, "Mimum Speed: %d Kbytes / sec", i);
	offset += 2;
	proto_tree_add_string(query_tree, hf_gnutella_query_sc, tvb,
			      offset, len-2, tvb_get_ptr(tvb, offset, len-3));
    }
}


static void
dissect_queryhit(tvbuff_t *tvb, proto_tree *tree, guint offset, guint len)
{
    proto_item *ti;
    proto_tree *queryhit_tree;
    guint32    i;
    int        result_num;

    if (tree) {
	if (!queryhit_len_check(tvb, offset)) {
	    ti = proto_tree_add_text(tree, tvb, offset, tvb_get_letohl(tvb, offset - 4),
				     "Descriptor Payload (QueryHit) - malformed descriptor");
	    return;
	}
	ti = proto_tree_add_text(tree, tvb, offset, len, "Descriptor Payload (QueryHit)");
	queryhit_tree = proto_item_add_subtree(ti, ett_queryhit);
	proto_tree_add_uint(queryhit_tree, hf_gnutella_queryhit_noh,
			    tvb, offset, 1, result_num = tvb_get_guint8(tvb, offset));
	offset += 1;
	proto_tree_add_uint(queryhit_tree, hf_gnutella_queryhit_port,
			    tvb, offset, 2, tvb_get_letohs(tvb, offset));
	offset += 2;
	proto_tree_add_ipv4(queryhit_tree, hf_gnutella_queryhit_address,
			    tvb, offset, 4, tvb_get_letohl(tvb, offset));
	offset += 4;
	i = tvb_get_letohl(tvb, offset);
	proto_tree_add_uint_format(queryhit_tree, hf_gnutella_queryhit_speed,
				   tvb, offset, 4, i, "Speed: %d Kbytes / sec", i);
	offset += 4;
	for (i = 1; i <= result_num; i++) {
	    offset = dissect_resultset(tvb, queryhit_tree, offset, i);
	}
	if (offset+16 < len+23) {
	    offset = dissect_trailer(tvb, queryhit_tree, offset, len);
	}
	proto_tree_add_bytes(queryhit_tree, hf_gnutella_queryhit_sid, tvb,
			     offset, 16, tvb_get_ptr(tvb, offset, 16));
    }
}


static guint
dissect_resultset(tvbuff_t *tvb, proto_tree *tree, guint offset, int result_num)
{
    proto_item *ti;
    proto_tree *resultset_tree;
    guint32    fname_len = 0, fsize;
    guint16    gi;

    if (tree) {
	while ((gi = tvb_get_letohs(tvb, offset + 8 + fname_len)) != 0) fname_len++;
	fname_len += 2;
	ti = proto_tree_add_text(tree, tvb, offset, fname_len + 8, "Result Set %d", result_num);
	resultset_tree = proto_item_add_subtree(ti, ett_resultset);
	proto_tree_add_uint(resultset_tree, hf_gnutella_resultset_findex,
			    tvb, offset, 4, tvb_get_letohl(tvb, offset));
	offset += 4;
	fsize = tvb_get_letohl(tvb, offset);
	proto_tree_add_uint_format(resultset_tree, hf_gnutella_resultset_fsize,
				   tvb, offset, 4, fsize, "File Size: %d byte", fsize);
	offset += 4;
	proto_tree_add_string(resultset_tree, hf_gnutella_resultset_fname, tvb,
			      offset, fname_len, tvb_get_ptr(tvb, offset, fname_len));
	offset += fname_len;
    }
    return offset;
}


static guint
dissect_trailer(tvbuff_t *tvb, proto_tree *tree, guint offset, guint len)
{
    proto_item *ti;
    proto_tree *trailer_tree;
    guint8     datasize;
    guint      i;

    if (tree) {
	ti = proto_tree_add_text(tree, tvb, offset, len + 23 - offset - 16, "Trailer");
	trailer_tree = proto_item_add_subtree(ti, ett_trailer);
	proto_tree_add_uint(trailer_tree, hf_gnutella_trailer_vcode,
			    tvb, offset, 4, tvb_get_letohl(tvb, offset));
	offset += 4;
	datasize = tvb_get_guint8(tvb, offset);
	proto_tree_add_uint(trailer_tree, hf_gnutella_trailer_odsize,
			    tvb, offset, 1, datasize);
	offset += 1;
	for(i = 0; i < datasize; i++){
	    dissect_odata(tvb, trailer_tree, offset);
	    offset++;
	}
	datasize = len + 23 - offset - 16;
	proto_tree_add_bytes(trailer_tree, hf_gnutella_trailer_pdata,
			     tvb, offset, datasize, tvb_get_ptr(tvb, offset, datasize));
	offset += datasize;
    }
    return offset;
}


static void
dissect_odata(tvbuff_t *tvb, proto_tree *tree, guint offset)
{
    proto_item *ti;
    proto_tree *trailer_tree;
    guint8     flag;

    if (tree) {
	flag = tvb_get_guint8(tvb, offset);
	ti = proto_tree_add_text(tree, tvb, offset, 1, "Open Data (0x%02x)",flag);
	trailer_tree = proto_item_add_subtree(ti, ett_odata);
	proto_tree_add_boolean(trailer_tree, hf_gnutella_trailer_flag_upspeed,
			       tvb, offset, 1, flag);
	proto_tree_add_boolean(trailer_tree, hf_gnutella_trailer_flag_huped,
			       tvb, offset, 1, flag);
	proto_tree_add_boolean(trailer_tree, hf_gnutella_trailer_flag_busy,
			       tvb, offset, 1, flag);
	proto_tree_add_boolean(trailer_tree, hf_gnutella_trailer_flag_push,
			       tvb, offset, 1, flag);
    }
}


void
proto_register_gnutella(void)
{
    static hf_register_info hf[] = {
	{ &hf_gnutella_descriptor_id,
	  { "Descriptor ID", "gnutella.descriptor_id",
	    FT_BYTES, BASE_HEX, NULL, 0,
	    "Uniquerly identifying the descriptor on the network" }
	},
	{ &hf_gnutella_payload_descriptor,
	  { "Payload Descriptor", "gnutella.payload_descriptor",
	    FT_UINT8, BASE_HEX, NULL, 0,
	    "type of descriptor" }
	},
	{ &hf_gnutella_ttl,
	  { "TTL", "gnutella.ttl",
	    FT_UINT8, BASE_DEC, NULL, 0,
	    "The number of times this packet will be forwarded" }
	},
	{ &hf_gnutella_hops,
	  { "Hops", "gnutella.hops",
	    FT_UINT8, BASE_DEC, NULL, 0,
	    "The number of times this descriptor has been forwarded" }
	},
	{ &hf_gnutella_payload_length,
	  { "Payload Length", "gnutella.payload_length",
	    FT_UINT32, BASE_DEC, NULL, 0,
	    "The length of the descriptor following this field" }
	},
	{ &hf_gnutella_pong_port,
	  { " Port", "gnutella.pong_port",
	    FT_UINT16, BASE_DEC, NULL, 0,
	    "Port number on which the servent can accept incoming connections" }
	},
	{ &hf_gnutella_pong_address,
	  { "IP Address", "gnutella.pong_address",
	    FT_IPv4, BASE_NONE, NULL, 0,
	    "IP address of the servent" }
	},
	{ &hf_gnutella_pong_nfs,
	  { "Number of Files Shared", "gnutella.pong_nfs",
	    FT_UINT32, BASE_DEC, NULL, 0,
	    "Number of files which the servent is sharing" }
	},
	{ &hf_gnutella_pong_nks,
	  { "Number of Kilobytes Shared", "gnutella.pong_nks",
	    FT_UINT32, BASE_DEC, NULL, 0,
	    "Number of Kilobytes of data which the servent is sharing" }
	},
	{ &hf_gnutella_push_sid,
	  { "Servent Identifier", "gnutella.push_sid",
	    FT_BYTES, BASE_DEC, NULL, 0,
	    "Uniquerly identifying the descriptor on the network" }
	},
	{ &hf_gnutella_push_findex,
	  { "File Index", "gnutella.push_findex",
	    FT_UINT32, BASE_HEX, NULL, 0,
	    "Uniquely identifying the file to be pushed" }
	},
	{ &hf_gnutella_push_address,
	  { "IP Address", "gnutella.push_address",
	    FT_IPv4, BASE_NONE, NULL, 0,
	    "IP address of the servent" }
	},
	{ &hf_gnutella_push_port,
	  { "Port", "gnutella.push_port",
	    FT_UINT16, BASE_DEC, NULL, 0,
	    "Port number to which the file should be pushed" }
	},
	{ &hf_gnutella_query_ms,
	  { "Minimum Speed", "gnutella.query_ms",
	    FT_UINT16, BASE_DEC, NULL, 0,
	    "The minimum speed of servents that should respond to this query" }
	},
        { &hf_gnutella_query_sc,
          { "Search Criteria", "gnutella.query_sc",
            FT_STRING, BASE_NONE, NULL, 0,
            "Search Criteria" }
	},
	{ &hf_gnutella_queryhit_noh,
	  { "Number of Hits", "gnutella.queryhit_noh",
	    FT_UINT8, BASE_DEC, NULL, 0,
	    "Number of hits (result set) in this packet" }
	},
	{ &hf_gnutella_queryhit_port,
	  { "Port", "gnutella.queryhit_port",
	    FT_UINT16, BASE_DEC, NULL, 0,
	    "Port number on which the servent can accept incoming connections" }
	},
	{ &hf_gnutella_queryhit_address,
	  { "IP Address", "gnutella.queryhit_address",
	    FT_IPv4, BASE_NONE, NULL, 0,
	    "IP address of the servent" }
	},
	{ &hf_gnutella_queryhit_speed,
	  { "Speed", "gnutella.queryhit_speed",
	    FT_UINT32, BASE_DEC, NULL, 0,
	    "Speed of the responding servent" }
	},
	{ &hf_gnutella_queryhit_sid,
	  { "Servent Identifier", "gnutella.queryhit_sid",
	    FT_BYTES, BASE_HEX, NULL, 0,
	    "Uniquely identifying the servent on the network" }
	},
	{ &hf_gnutella_resultset_findex,
	  { "File Index", "gnutella.resultset_findex",
	    FT_UINT32, BASE_HEX, NULL, 0,
	    "Uniquery identifying the file on responding servent" }
	},
	{ &hf_gnutella_resultset_fsize,
	  { "File Size", "gnutella.resultset_fsize",
	    FT_UINT32, BASE_DEC, NULL, 0,
	    "Size of the file" }
	},
	{ &hf_gnutella_resultset_fname,
	  { "File Name", "gnutella.resultset_fname",
	    FT_STRING, BASE_NONE, NULL, 0,
	    "Name of the file" }
	},
	{ &hf_gnutella_trailer_vcode,
	  { "Vendor Code", "gnutella.trailer_vcode",
	    FT_UINT32, BASE_HEX, NULL, 0,
	    "Vendor code of the servent responding this packet" }
	},
	{ &hf_gnutella_trailer_odsize,
	  { "Open Data Size", "gnutella.trailer_odsize",
	    FT_UINT8, BASE_DEC, NULL, 0,
	    "Length of the Open Data field" }
	},
	{ &hf_gnutella_trailer_odata,
	  { "Open Data", "gnutella.trailer_odata",
	    FT_UINT8, BASE_DEC, NULL, 0,
	    "Trailler Open Data" }
	},
	{ &hf_gnutella_trailer_flag_upspeed,
	  { "flagUploadSpeed", "gnutella.trailer_flagupspeed",
	    FT_BOOLEAN, 8, TFS(&flags_set_truth), 16,
	    "" }
	},
	{ &hf_gnutella_trailer_flag_huped,
	  { "flagHaveUploaded", "gnutella.trailer_flaghuploaded",
	    FT_BOOLEAN, 8, TFS(&flags_set_truth), 8,
	    "" }
	},
	{ &hf_gnutella_trailer_flag_busy,
	  { "flagBusy", "gnutella.trailer_busy",
	    FT_BOOLEAN, 8, TFS(&flags_set_truth), 4,
	    "" }
	},
	{ &hf_gnutella_trailer_flag_push,
	  { "flagPush", "gnutella.trailer_push",
	    FT_BOOLEAN, 8, TFS(&flags_set_truth), 1,
	    "" }
	},
	{ &hf_gnutella_trailer_pdata,
	  { "Private Data", "gnutella.trailer_pdata",
	    FT_BYTES, BASE_HEX, NULL, 0,
	    "Trailer private data" }
	},
    };

    static gint *ett[] = {
	&ett_gnutella,
	&ett_gnutella_desc,
	&ett_pong,
	&ett_push,
	&ett_query,
	&ett_queryhit,
	&ett_resultset,
	&ett_trailer,
	&ett_odata
    };

    proto_gnutella = proto_register_protocol("Gnutella Protocol","Gnutella", "gnutella");
    proto_register_field_array(proto_gnutella, hf, array_length(hf));
    proto_register_subtree_array(ett, array_length(ett));
}


void
proto_reg_handoff_gnutella(void)
{
    dissector_add("tcp.port", TCP_PORT_GNUTELLA, dissect_gnutella, proto_gnutella);
}


static gnutella_proto_t
distinguish_protocol(tvbuff_t* tvb)
{
    guint  offset = 0, len = tvb_length(tvb);
    guint8 str_tmp[23];

    descriptor = 0;
    if (len >= 21) {
	strcpy(str_tmp, "GNUTELLA CONNECT");
	str_tmp[21] = str_tmp[22] = 0x0a;
	if (tvb_strneql(tvb, 0, str_tmp, 16) == 0 &&
	    tvb_strneql(tvb, len-2, str_tmp+21, 2) == 0)
	    return GNUTELLA_CONNECT;
    }
    if (len >= 13) {
	strcpy(str_tmp, "GNUTELLA OK");
	str_tmp[11] = str_tmp[12] = 0x0a;
	if (tvb_strneql(tvb, 0, str_tmp, 13) == 0){
	    if (len == 13 || (descriptor = is_descriptor(tvb, 13))) return GNUTELLA_OK;
	}
    }
    if (len >= 11) {
      if (tvb_strneql(tvb, 0, "GIV ", 4) == 0)
	  /*if (tvb_find_line_end(tvb, 0, -1, &eol) == len-1)
	      if (tvb_get_guint8(tvb, eol) == 0x0a)*/
	          return GNUTELLA_GIV;
    }
    descriptor = is_descriptor(tvb, offset);
    if (descriptor) return GNUTELLA_DESC;
    return HTTP;  /* probably.... */
}


static int
is_descriptor(tvbuff_t* tvb, guint offset)
{
    guint32 payload_length;
    guint   len = tvb_length(tvb);
    int     descriptor_num = 0;

    while (len > offset) {
	if (len >= offset + 23) {
	    payload_length = tvb_get_letohl(tvb, offset+19);
	    offset += (23 + payload_length);
	    descriptor_num++;
	}
	else break;
    }
    return (len == offset)?descriptor_num:0;
}


/*
static int
ping_len_check(tvbuff_t* tvb, guint offset)
{
    return (tvb_get_letohl(tvb, offset-4) == DESC_PING_LEN);
}
*/

static int
pong_len_check(tvbuff_t* tvb, guint offset)
{
    return (tvb_get_letohl(tvb, offset-4) == DESC_PONG_LEN);
}


static int
query_len_check(tvbuff_t* tvb, guint offset)
{
    return (tvb_get_letohl(tvb, offset - 4) >= DESC_QUERY_MIN_LEN);
}


static int
queryhit_len_check(tvbuff_t* tvb, guint offset)
{
    guint8 noh  = tvb_get_guint8(tvb, offset);
    int    rlen = tvb_get_letohl(tvb, offset - 4) - DESC_QUERYHIT_MIN_LEN;
    int    i, j, flag;

    for (j = 1; j <= noh; j++) {
	rlen -= 9;  /* sizeof(findex+fsize) + 1 = 9 */
	if(rlen <= 0) return 0;
	offset += 8;
	flag = 0;
	for (i = 1; i <= rlen-2; i++)
	    if (tvb_get_letohs(tvb, offset+i) == 0x0000) {
		flag = 1; break;
	    }
	if (!flag) return 0;  /* invalid ResultSet tailend */
	rlen   -= i + 1;
	if (rlen < 0) return 0;
	offset += i + 1;
    }
    if (rlen > 0)
	if (rlen < 7) return 0; /* too short Trailler */
    return 1;
}


static int
push_len_check(tvbuff_t* tvb, guint offset)
{
    return (tvb_get_letohl(tvb, offset-4) == DESC_PUSH_LEN);
}
------- end of file ---------