nexmon – Rev 1

Subversion Repositories:
Rev:
/* packet-dcc.c
 * Routines for Distributed Checksum Clearinghouse packet dissection
 * DCC Home: http://www.rhyolite.com/anti-spam/dcc/
 *
 * Copyright 1999, Nathan Neulinger <nneul@umr.edu>
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * Copied from packet-tftp.c
 *
 * 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 <epan/packet.h>
#include <epan/expert.h>

#include "packet-dcc.h"

void proto_register_dcc(void);
void proto_reg_handoff_dcc(void);

static int proto_dcc = -1;
static int hf_dcc_len = -1;
static int hf_dcc_pkt_vers = -1;
static int hf_dcc_op = -1;
static int hf_dcc_clientid = -1;
static int hf_dcc_opnums_host = -1;
static int hf_dcc_opnums_pid = -1;
static int hf_dcc_opnums_report = -1;
static int hf_dcc_opnums_retrans = -1;

static int hf_dcc_signature = -1;
static int hf_dcc_max_pkt_vers = -1;
static int hf_dcc_qdelay_ms = -1;
static int hf_dcc_brand = -1;

static int hf_dcc_ck_type = -1;
static int hf_dcc_ck_len = -1;
static int hf_dcc_ck_sum = -1;

static int hf_dcc_date = -1;

static int hf_dcc_target = -1;
static int hf_dcc_response_text = -1;

static int hf_dcc_adminop = -1;
static int hf_dcc_adminval = -1;
static int hf_dcc_floodop = -1;
static int hf_dcc_trace = -1;
static int hf_dcc_trace_admin = -1;
static int hf_dcc_trace_anon = -1;
static int hf_dcc_trace_client = -1;
static int hf_dcc_trace_rlim = -1;
static int hf_dcc_trace_query = -1;
static int hf_dcc_trace_ridc = -1;
static int hf_dcc_trace_flood = -1;

static int hf_dcc_addr = -1;
static int hf_dcc_id = -1;
static int hf_dcc_last_used = -1;
static int hf_dcc_requests = -1;
static int hf_dcc_pad = -1;
static int hf_dcc_unused = -1;

static gint ett_dcc = -1;
static gint ett_dcc_opnums = -1;
static gint ett_dcc_op = -1;
static gint ett_dcc_ck = -1;
static gint ett_dcc_trace = -1;

static expert_field ei_dcc_len = EI_INIT;

/* Utility macros */
#define D_SIGNATURE() \
        proto_tree_add_item(dcc_optree, hf_dcc_signature, tvb, \
                offset, (int)sizeof(DCC_SIGNATURE), ENC_NA); \
        offset += (int)sizeof(DCC_SIGNATURE);

#define D_LABEL(hf_label, len, encoding) \
        proto_tree_add_item(dcc_optree, hf_label, tvb, offset, len, encoding); \
        offset += len;

#define D_TEXT(hf_label, endpad) { \
        int next_offset,left; \
        while (tvb_offset_exists(tvb, offset+endpad)) { \
                left = tvb_reported_length_remaining(tvb,offset) - endpad; \
                tvb_find_line_end(tvb, offset, left, &next_offset, \
                    FALSE); \
                proto_tree_add_item(dcc_optree, hf_label, tvb, offset, \
                        next_offset - offset, ENC_ASCII|ENC_NA); \
                offset = next_offset; \
        } \
}


#define D_TARGET() \
        proto_tree_add_item(dcc_tree, hf_dcc_target, tvb, \
                offset, (int)sizeof(DCC_TGTS), ENC_BIG_ENDIAN); \
        offset += (int)sizeof(DCC_TGTS);

#define D_DATE() { \
        nstime_t ts; \
        ts.nsecs = 0; \
        ts.secs = tvb_get_ntohl(tvb,offset); \
        proto_tree_add_time(dcc_optree, hf_dcc_date, tvb, offset, 4, &ts); \
        offset += 4; \
}


#define D_CHECKSUM() { \
        proto_tree *cktree; \
        cktree = proto_tree_add_subtree_format(dcc_optree, tvb, offset, (int)sizeof(DCC_CK), \
                ett_dcc_ck, NULL, "Checksum - %s", val_to_str(tvb_get_guint8(tvb,offset), \
                dcc_cktype_vals, \
                "Unknown Type: %u")); \
        proto_tree_add_item(cktree, hf_dcc_ck_type, tvb, offset, 1, ENC_BIG_ENDIAN); \
        offset += 1; \
        proto_tree_add_item(cktree, hf_dcc_ck_len, tvb, offset, 1, ENC_BIG_ENDIAN); \
        offset += 1; \
        proto_tree_add_item(cktree, hf_dcc_ck_sum, tvb, offset, \
                (int)sizeof(DCC_SUM), ENC_NA); \
        offset += (int)sizeof(DCC_SUM); \
}


/* Lookup string tables */
static const value_string dcc_op_vals[] = {
        {DCC_OP_INVALID,    "Invalid Op"},
        {DCC_OP_NOP,        "No-Op"},
        {DCC_OP_REPORT,     "Report and Query"},
        {DCC_OP_QUERY,      "Query"},
        {DCC_OP_QUERY_RESP, "Server Response"},
        {DCC_OP_ADMN,       "Admin"},
        {DCC_OP_OK,         "Ok"},
        {DCC_OP_ERROR,      "Server Failing"},
        {DCC_OP_DELETE,     "Delete Checksum(s)"},
        {0, NULL}
};

static const value_string dcc_cktype_vals[] = {
        {DCC_CK_INVALID,    "Invalid/Deleted from DB when seen"},
        {DCC_CK_IP,         "MD5 of binary source IPv6 address"},
        {DCC_CK_ENV_FROM,   "MD5 of envelope Mail From value"},
        {DCC_CK_FROM,       "MD5 of header From: line"},
        {DCC_CK_SUB,        "MD5 of substitute header line"},
        {DCC_CK_MESSAGE_ID, "MD5 of header Message-ID: line"},
        {DCC_CK_RECEIVED,   "MD5 of last header Received: line"},
        {DCC_CK_BODY,       "MD5 of body"},
        {DCC_CK_FUZ1,       "MD5 of filtered body - FUZ1"},
        {DCC_CK_FUZ2,       "MD5 of filtered body - FUZ2"},
        {DCC_CK_FUZ3,       "MD5 of filtered body - FUZ3"},
        {DCC_CK_FUZ4,       "MD5 of filtered body - FUZ4"},
        {DCC_CK_SRVR_ID,    "hostname for server-ID check "},
        {DCC_CK_ENV_TO,     "MD5 of envelope Rcpt To value"},
        {0, NULL},
};

static const value_string dcc_adminop_vals[] = {
        {DCC_AOP_OK,          "Never sent"},
        {DCC_AOP_STOP,        "Stop Gracefully"},
        {DCC_AOP_NEW_IDS,     "Load keys and client IDs"},
        {DCC_AOP_FLOD,        "Flood control"},
        {DCC_AOP_DB_UNLOCK,   "Start Switch to new database"},
        {DCC_AOP_DB_NEW,      "Finish Switch to new database"},
        {DCC_AOP_STATS,       "Return counters"},
        {DCC_AOP_STATS_CLEAR, "Return and zero counters"},
        {DCC_AOP_TRACE_ON,    "Enable tracing"},
        {DCC_AOP_TRACE_OFF,   "Disable tracing"},
        {DCC_AOP_CUR_CLIENTS, "List clients"},
        {0, NULL},
};

static const value_string dcc_target_vals[] = {
        {DCC_TGTS_TOO_MANY, "Targets (>= 16777200)"},
        {DCC_TGTS_OK,       "Certified not spam"},
        {DCC_TGTS_OK2,      "Half certified not spam"},
        {DCC_TGTS_DEL,      "Deleted checksum"},
        {DCC_TGTS_INVALID,  "Invalid"},
        {0, NULL},
};

static const value_string dcc_floodop_vals[] = {
        {DCC_AOP_FLOD_CHECK,       "Check"},
        {DCC_AOP_FLOD_SHUTDOWN,    "Shutdown"},
        {DCC_AOP_FLOD_HALT,        "Halt"},
        {DCC_AOP_FLOD_RESUME,      "Resume"},
        {DCC_AOP_FLOD_REWIND,      "Rewind"},
        {DCC_AOP_FLOD_LIST,        "List"},
        {DCC_AOP_FLOD_STATS,       "Stats"},
        {DCC_AOP_FLOD_STATS_CLEAR, "Clear Stats"},
        {0,NULL},
};

static gboolean
dissect_dcc(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
{
        proto_tree *dcc_tree, *dcc_optree, *dcc_opnumtree, *ti;
        proto_tree *dcc_tracetree;
        proto_item *len_item;
        int offset = 0;
        int client_is_le = 0;
        int op = 0;
        int i, is_response;

        if (pinfo->srcport != DCC_PORT && pinfo->destport != DCC_PORT) {
                /* Not the right port - not a DCC packet. */
                return FALSE;
        }

        /* get at least a full packet structure */
        if ( tvb_reported_length(tvb) < sizeof(DCC_HDR) ) {
                /* Doesn't have enough bytes to contain packet header. */
                return FALSE;
        }

        col_set_str(pinfo->cinfo, COL_PROTOCOL, "DCC");

        offset = 0;
        is_response = pinfo->srcport == DCC_PORT;

        col_add_fstr(pinfo->cinfo, COL_INFO,
                "%s: %s",
                is_response ? "Response" : "Request",
                val_to_str(tvb_get_guint8(tvb, offset+3),
                         dcc_op_vals, "Unknown Op: %u"));

        ti = proto_tree_add_item(tree, proto_dcc, tvb, offset, -1,
                ENC_NA);
        dcc_tree = proto_item_add_subtree(ti, ett_dcc);

        len_item = proto_tree_add_item(dcc_tree, hf_dcc_len, tvb,
                offset, 2, ENC_BIG_ENDIAN);

        if ( tvb_reported_length(tvb) < tvb_get_ntohs(tvb, offset)) {
                /* Doesn't have number of bytes that header claims. */
                expert_add_info(pinfo, len_item, &ei_dcc_len);
        }
        offset += 2;

        if (tree) {
                proto_tree_add_item(dcc_tree, hf_dcc_pkt_vers, tvb,
                        offset, 1, ENC_BIG_ENDIAN);
                offset += 1;

                op = tvb_get_guint8(tvb, offset);
                proto_tree_add_item(dcc_tree, hf_dcc_op, tvb,
                        offset, 1, ENC_BIG_ENDIAN);
                offset += 1;

                proto_tree_add_item(dcc_tree, hf_dcc_clientid, tvb,
                        offset, 4, ENC_BIG_ENDIAN);
                offset += 4;

                dcc_opnumtree = proto_tree_add_subtree(dcc_tree, tvb, offset, -1, ett_dcc_opnums, NULL, "Operation Numbers (Opaque to Server)");

                /* Note - these are indeterminate - they are sortof considered opaque to the client */
                /* Make some attempt to figure out if this data is little endian, not guaranteed to be
                correct if connection went through a firewall or similar. */

                /* Very hokey check - if all three of pid/report/retrans look like little-endian
                        numbers, host is probably little endian. Probably innacurate on super-heavily-used
                        DCC clients though. This should be good enough for now. */
                client_is_le = ( (tvb_get_guint8(tvb, offset+4) | tvb_get_guint8(tvb, offset+5)) &&
                                                 (tvb_get_guint8(tvb, offset+8) | tvb_get_guint8(tvb, offset+9)) &&
                                                 (tvb_get_guint8(tvb, offset+12) | tvb_get_guint8(tvb, offset+13)) );

                proto_tree_add_item(dcc_opnumtree, hf_dcc_opnums_host, tvb,
                        offset, 4, client_is_le);
                offset += 4;

                proto_tree_add_item(dcc_opnumtree, hf_dcc_opnums_pid, tvb,
                        offset, 4, client_is_le);
                offset += 4;

                proto_tree_add_item(dcc_opnumtree, hf_dcc_opnums_report, tvb,
                        offset, 4, client_is_le);
                offset += 4;

                proto_tree_add_item(dcc_opnumtree, hf_dcc_opnums_retrans, tvb,
                        offset, 4, client_is_le);
                offset += 4;

                dcc_optree = proto_tree_add_subtree_format(dcc_tree, tvb, offset, -1, ett_dcc_op, NULL,
                        "Operation: %s", val_to_str(op, dcc_op_vals, "Unknown Op: %u"));

                switch(op) {
                        case DCC_OP_NOP:
                                D_SIGNATURE();
                                break;

                        case DCC_OP_REPORT:
                                D_TARGET();
                                for (i=0; i<=DCC_QUERY_MAX &&
                                        tvb_bytes_exist(tvb, offset+(int)sizeof(DCC_SIGNATURE),1); i++)
                                {
                                        D_CHECKSUM();
                                }
                                D_SIGNATURE();
                                break;

                        case DCC_OP_QUERY_RESP:
                                for (i=0; i<=DCC_QUERY_MAX &&
                                        tvb_bytes_exist(tvb, offset+(int)sizeof(DCC_SIGNATURE),1); i++)
                                {
                                        D_TARGET();
                                }
                                D_SIGNATURE();
                                break;

                        case DCC_OP_ADMN:
                                if ( is_response )
                                {
                                        int left_local = tvb_reported_length_remaining(tvb, offset) -
                                                (int)sizeof(DCC_SIGNATURE);
                                        if ( left_local == sizeof(DCC_ADMN_RESP_CLIENTS) )
                                        {
                                                D_LABEL(hf_dcc_addr, 16, ENC_NA);
                                                D_LABEL(hf_dcc_id, (int)sizeof(DCC_CLNT_ID), ENC_BIG_ENDIAN);
                                                D_LABEL(hf_dcc_last_used, 4, ENC_BIG_ENDIAN);
                                                D_LABEL(hf_dcc_requests, 4, ENC_BIG_ENDIAN);
                                        }
                                        else
                                        {
                                                D_TEXT(hf_dcc_response_text, (int)sizeof(DCC_SIGNATURE));
                                        }
                                        D_SIGNATURE();
                                }
                                else
                                {
                                        int aop;

                                        D_DATE();

                                        aop = tvb_get_guint8(tvb, offset+4);
                                        proto_tree_add_item(dcc_optree, hf_dcc_adminop, tvb, offset+4,
                                                1, ENC_BIG_ENDIAN);
                                        col_append_fstr(pinfo->cinfo, COL_INFO, ", %s",
                                                val_to_str(tvb_get_guint8(tvb,offset+4),
                                                dcc_adminop_vals, "Unknown (%u)"));

                                        if (aop == DCC_AOP_TRACE_ON || aop == DCC_AOP_TRACE_OFF )
                                        {
                                                ti = proto_tree_add_item(dcc_optree, hf_dcc_trace, tvb, offset,
                                                        4, ENC_BIG_ENDIAN);
                                                dcc_tracetree = proto_item_add_subtree(ti, ett_dcc_trace);
                                                proto_tree_add_item(dcc_tracetree, hf_dcc_trace_admin, tvb, offset, 4, ENC_BIG_ENDIAN);
                                                proto_tree_add_item(dcc_tracetree, hf_dcc_trace_anon, tvb, offset, 4, ENC_BIG_ENDIAN);
                                                proto_tree_add_item(dcc_tracetree, hf_dcc_trace_client, tvb, offset, 4, ENC_BIG_ENDIAN);
                                                proto_tree_add_item(dcc_tracetree, hf_dcc_trace_rlim, tvb, offset, 4, ENC_BIG_ENDIAN);
                                                proto_tree_add_item(dcc_tracetree, hf_dcc_trace_query, tvb, offset, 4, ENC_BIG_ENDIAN);
                                                proto_tree_add_item(dcc_tracetree, hf_dcc_trace_ridc, tvb, offset, 4, ENC_BIG_ENDIAN);
                                                proto_tree_add_item(dcc_tracetree, hf_dcc_trace_flood, tvb, offset, 4, ENC_BIG_ENDIAN);
                                        }
                                        else if ( aop == DCC_AOP_FLOD )
                                        {
                                                proto_tree_add_item(dcc_optree, hf_dcc_floodop,
                                                        tvb, offset, 4, ENC_BIG_ENDIAN);
                                                col_append_fstr(pinfo->cinfo, COL_INFO, ", %s",
                                                        val_to_str(tvb_get_ntohl(tvb,offset),
                                                        dcc_floodop_vals, "Unknown (%u)"));
                                        }
                                        else
                                        {
                                                proto_tree_add_item(dcc_optree, hf_dcc_adminval,
                                                        tvb, offset, 4, ENC_BIG_ENDIAN);
                                        }
                                        offset += 4;

                                        offset += 1; /* admin op we did in reverse order */
                                        D_LABEL(hf_dcc_pad, 3, ENC_NA);
                                        D_SIGNATURE();
                                }
                                break;

                        case DCC_OP_OK:
                                proto_tree_add_item(dcc_optree, hf_dcc_max_pkt_vers, tvb,
                                        offset, 1, ENC_BIG_ENDIAN);
                                offset += 1;

                                D_LABEL(hf_dcc_unused, 1, ENC_NA);

                                proto_tree_add_item(dcc_optree, hf_dcc_qdelay_ms, tvb,
                                        offset, 2, ENC_BIG_ENDIAN);
                                offset += 2;

                                proto_tree_add_item(dcc_optree, hf_dcc_brand, tvb,
                                        offset, (int)sizeof(DCC_BRAND), ENC_ASCII|ENC_NA);
                                offset += (int)sizeof(DCC_BRAND);

                                D_SIGNATURE();
                                break;

                        default:
                                /* do nothing */
                                break;
                }
        }

        return TRUE;
}

void
proto_register_dcc(void)
{
        static hf_register_info hf[] = {
                        { &hf_dcc_len, {
                                "Packet Length", "dcc.len", FT_UINT16, BASE_DEC,
                                NULL, 0, NULL, HFILL }},

                        { &hf_dcc_pkt_vers, {
                                "Packet Version", "dcc.pkt_vers", FT_UINT16, BASE_DEC,
                                NULL, 0, NULL, HFILL }},

                        { &hf_dcc_op, {
                                "Operation Type", "dcc.op", FT_UINT8, BASE_DEC,
                                VALS(dcc_op_vals), 0, NULL, HFILL }},

                        { &hf_dcc_clientid, {
                                "Client ID", "dcc.clientid", FT_UINT32, BASE_DEC,
                                NULL, 0, NULL, HFILL }},

                        { &hf_dcc_opnums_host, {
                                "Host", "dcc.opnums.host", FT_UINT32, BASE_DEC,
                                NULL, 0, NULL, HFILL }},

                        { &hf_dcc_opnums_pid, {
                                "Process ID", "dcc.opnums.pid", FT_UINT32, BASE_DEC,
                                NULL, 0, NULL, HFILL }},

                        { &hf_dcc_opnums_report, {
                                "Report", "dcc.opnums.report", FT_UINT32, BASE_DEC,
                                NULL, 0, NULL, HFILL }},

                        { &hf_dcc_opnums_retrans, {
                                "Retransmission", "dcc.opnums.retrans", FT_UINT32, BASE_DEC,
                                NULL, 0, NULL, HFILL }},

                        { &hf_dcc_signature, {
                                "Signature", "dcc.signature", FT_BYTES, BASE_NONE,
                                NULL, 0, NULL, HFILL }},

                        { &hf_dcc_max_pkt_vers, {
                                "Maximum Packet Version", "dcc.max_pkt_vers", FT_UINT8, BASE_DEC,
                                NULL, 0, NULL, HFILL }},

                        { &hf_dcc_qdelay_ms, {
                                "Client Delay", "dcc.qdelay_ms", FT_UINT16, BASE_DEC,
                                NULL, 0, NULL, HFILL }},

                        { &hf_dcc_brand, {
                                "Server Brand", "dcc.brand", FT_STRING, BASE_NONE,
                                NULL, 0, NULL, HFILL }},

                        { &hf_dcc_ck_type, {
                                "Type", "dcc.checksum.type", FT_UINT8, BASE_DEC,
                                VALS(dcc_cktype_vals), 0, "Checksum Type", HFILL }},

                        { &hf_dcc_ck_len, {
                                "Length", "dcc.checksum.length", FT_UINT8, BASE_DEC,
                                NULL, 0, "Checksum Length", HFILL }},

                        { &hf_dcc_ck_sum, {
                                "Sum", "dcc.checksum.sum", FT_BYTES, BASE_NONE,
                                NULL, 0, "Checksum", HFILL }},

                        { &hf_dcc_target, {
                                "Target", "dcc.target", FT_UINT32, BASE_HEX,
                                VALS(dcc_target_vals), 0, NULL, HFILL }},

                        { &hf_dcc_response_text, {
                                "Response Text", "dcc.response_text", FT_BYTES, BASE_NONE,
                                NULL, 0, NULL, HFILL }},

                        { &hf_dcc_date, {
                                "Date", "dcc.date", FT_ABSOLUTE_TIME, ABSOLUTE_TIME_LOCAL,
                                NULL, 0, NULL, HFILL }},

                        { &hf_dcc_adminop, {
                                "Admin Op", "dcc.adminop", FT_UINT8, BASE_DEC,
                                VALS(dcc_adminop_vals), 0, NULL, HFILL }},

                        { &hf_dcc_adminval, {
                                "Admin Value", "dcc.adminval", FT_UINT32, BASE_DEC,
                                NULL, 0, NULL, HFILL }},

                        { &hf_dcc_trace, {
                                "Trace Bits", "dcc.trace", FT_UINT32, BASE_HEX,
                                NULL, 0, NULL, HFILL }},

                        { &hf_dcc_trace_admin, {
                                "Admin Requests", "dcc.trace.admin", FT_BOOLEAN, 32,
                                NULL, 0x00000001, NULL, HFILL }},

                        { &hf_dcc_trace_anon, {
                                "Anonymous Requests", "dcc.trace.anon", FT_BOOLEAN, 32,
                                NULL, 0x00000002, NULL, HFILL }},

                        { &hf_dcc_trace_client, {
                                "Authenticated Client Requests", "dcc.trace.client", FT_BOOLEAN, 32,
                                NULL, 0x00000004, NULL, HFILL }},

                        { &hf_dcc_trace_rlim, {
                                "Rate-Limited Requests", "dcc.trace.rlim", FT_BOOLEAN, 32,
                                NULL, 0x00000008, NULL, HFILL }},

                        { &hf_dcc_trace_query, {
                                "Queries and Reports", "dcc.trace.query", FT_BOOLEAN, 32,
                                NULL, 0x00000010, NULL, HFILL }},

                        { &hf_dcc_trace_ridc, {
                                "RID Cache Messages", "dcc.trace.ridc", FT_BOOLEAN, 32,
                                NULL, 0x00000020, NULL, HFILL }},

                        { &hf_dcc_trace_flood, {
                                "Input/Output Flooding", "dcc.trace.flood", FT_BOOLEAN, 32,
                                NULL, 0x00000040, NULL, HFILL }},

                        { &hf_dcc_floodop, {
                                "Flood Control Operation", "dcc.floodop", FT_UINT32, BASE_DEC,
                                VALS(dcc_floodop_vals), 0, NULL, HFILL }},

                        { &hf_dcc_id, {
                                "Id", "dcc.id", FT_UINT32, BASE_DEC,
                                NULL, 0, NULL, HFILL }},

                        { &hf_dcc_last_used, {
                                "Last Used", "dcc.last_used", FT_UINT32, BASE_DEC,
                                NULL, 0, NULL, HFILL }},

                        { &hf_dcc_requests, {
                                "Requests", "dcc.requests", FT_UINT32, BASE_DEC,
                                NULL, 0, NULL, HFILL }},

                        { &hf_dcc_addr, {
                                "Addr", "dcc.addr", FT_BYTES, BASE_NONE,
                                NULL, 0, NULL, HFILL }},

                        { &hf_dcc_pad, {
                                "Pad", "dcc.pad", FT_BYTES, BASE_NONE,
                                NULL, 0, NULL, HFILL }},

                        { &hf_dcc_unused, {
                                "Unused", "dcc.unused", FT_BYTES, BASE_NONE,
                                NULL, 0, NULL, HFILL }},
                };

        static gint *ett[] = {
                &ett_dcc,
                &ett_dcc_op,
                &ett_dcc_ck,
                &ett_dcc_opnums,
                &ett_dcc_trace,
        };

        static ei_register_info ei[] = {
                { &ei_dcc_len, { "dcc.len.short", PI_MALFORMED, PI_ERROR, "Error - packet is shorter than header claims!", EXPFILL }},
        };

        expert_module_t* expert_dcc;

        proto_dcc = proto_register_protocol("Distributed Checksum Clearinghouse protocol", "DCC", "dcc");

        proto_register_field_array(proto_dcc, hf, array_length(hf));
        proto_register_subtree_array(ett, array_length(ett));
        expert_dcc = expert_register_protocol(proto_dcc);
        expert_register_field_array(expert_dcc, ei, array_length(ei));
}

void
proto_reg_handoff_dcc(void)
{
        heur_dissector_add("udp", dissect_dcc, "Distributed Checksum Clearinghouse over UDP", "dcc_udp", proto_dcc, HEURISTIC_ENABLE);
}

/*
 * Editor modelines  -  http://www.wireshark.org/tools/modelines.html
 *
 * Local variables:
 * c-basic-offset: 8
 * tab-width: 8
 * indent-tabs-mode: t
 * End:
 *
 * vi: set shiftwidth=8 tabstop=8 noexpandtab:
 * :indentSize=8:tabSize=8:noTabs=false:
 */