/* packet-lwres.c
 * Routines for light weight reslover (lwres, part of BIND9) packet disassembly
 *
 * Copyright (c) 2003 by Oleg Terletsky <oleg.terletsky@comverse.com>
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1999 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 <epan/packet.h>
#include <epan/prefs.h>
#include <epan/to_str.h>

#include "packet-dns.h"

void proto_register_lwres(void);
void proto_reg_handoff_lwres(void);

#define LWRES_LWPACKET_LENGTH           (4 * 5 + 2 * 4)
#define LWRES_LWPACKETFLAG_RESPONSE     0x0001U /* if set, pkt is a response */
#define LWRES_LWPACKETVERSION_0         0

#define LW_LENGTH_OFFSET         0
#define LW_VERSION_OFFSET        4
#define LW_PKTFLASG_OFFSET       6
#define LW_SERIAL_OFFSET         8
#define LW_OPCODE_OFFSET        12
#define LW_RESULT_OFFSET        16
#define LW_RECVLEN_OFFSET       20
#define LW_AUTHTYPE_OFFSET      24
#define LW_AUTHLEN_OFFSET       26

#define LWRES_OPCODE_NOOP               0x00000000U
#define LWRES_OPCODE_GETADDRSBYNAME     0x00010001U
#define LWRES_OPCODE_GETNAMEBYADDR      0x00010002U
#define LWRES_OPCODE_GETRDATABYNAME     0x00010003U

static const value_string opcode_values[] = {
    { LWRES_OPCODE_NOOP,            "Noop" },
    { LWRES_OPCODE_GETADDRSBYNAME,  "getaddrbyname" },
    { LWRES_OPCODE_GETNAMEBYADDR,   "getnamebyaddr" },
    { LWRES_OPCODE_GETRDATABYNAME,  "getrdatabyname" },
    { 0, NULL },
};


#define LWRES_R_SUCCESS                  0
#define LWRES_R_NOMEMORY                 1
#define LWRES_R_TIMEOUT                  2
#define LWRES_R_NOTFOUND                 3
#define LWRES_R_UNEXPECTEDEND            4       /* unexpected end of input */
#define LWRES_R_FAILURE                  5       /* generic failure */
#define LWRES_R_IOERROR                  6
#define LWRES_R_NOTIMPLEMENTED           7
#define LWRES_R_UNEXPECTED               8
#define LWRES_R_TRAILINGDATA             9
#define LWRES_R_INCOMPLETE              10
#define LWRES_R_RETRY                   11
#define LWRES_R_TYPENOTFOUND            12
#define LWRES_R_TOOLARGE                13

#define T_A     1
#define T_NS    2
#define T_MX    15
#define T_SRV   33


static const value_string t_types[] = {
    { T_A,      "T_A" },
    { T_NS,     "T_NS" },
    { T_MX,     "T_MX" },
    { T_SRV,    "T_SRV" },
    { 0,        NULL },
};




static const value_string result_values[]  = {
    { LWRES_R_SUCCESS,          "Success" },
    { LWRES_R_NOMEMORY,         "No memory" },
    { LWRES_R_TIMEOUT,          "Timeout" },
    { LWRES_R_NOTFOUND,         "Not found" },
    { LWRES_R_UNEXPECTEDEND,    "Unexpected end of input" },
    { LWRES_R_FAILURE,          "Generic failure" },
    { LWRES_R_IOERROR,          "I/O Error" },
    { LWRES_R_UNEXPECTED,       "Unexpected" },
    { LWRES_R_TRAILINGDATA,     "Trailing data" },
    { LWRES_R_INCOMPLETE,       "Incomplete" },
    { LWRES_R_RETRY,            "Retry" },
    { LWRES_R_TYPENOTFOUND,     "Type not found" },
    { LWRES_R_TOOLARGE,         "Too large" },
    { 0,                        NULL },
};

static int hf_length = -1;
static int hf_version = -1;
static int hf_flags = -1;
static int hf_serial = -1;
static int hf_opcode = -1;
static int hf_result = -1;
static int hf_recvlen = -1;
static int hf_authtype = -1;
static int hf_authlen = -1;

static int hf_rflags = -1;
static int hf_rdclass = -1;
static int hf_rdtype = -1;
static int hf_namelen = -1;
static int hf_req_name = -1;

static int hf_ttl = -1;
static int hf_nrdatas = -1;
static int hf_nsigs = -1;
static int hf_realnamelen = -1;
static int hf_realname = -1;


static int hf_a_record = -1;
static int hf_a_rec_len = -1;
static int hf_srv_prio = -1;
static int hf_srv_weight = -1;
static int hf_srv_port = -1;
static int hf_srv_dname = -1;

static int hf_adn_flags = -1;
static int hf_adn_addrtype = -1;
static int hf_adn_namelen = -1;
static int hf_adn_name = -1;

static int hf_adn_realname = -1;
static int hf_adn_aliasname = -1;

static int hf_adn_naddrs = -1;
static int hf_adn_naliases = -1;
static int hf_adn_family = -1;
static int hf_adn_addr_len = -1;
static int hf_adn_addr_addr = -1;

static int hf_ns_dname = -1;

static int ett_lwres = -1;
static int ett_rdata_req = -1;
static int ett_rdata_resp = -1;
static int ett_a_rec = -1;
static int ett_a_rec_addr = -1;
static int ett_srv_rec = -1;
static int ett_srv_rec_item = -1;
static int ett_adn_request = -1;
static int ett_adn_resp = -1;
static int ett_adn_alias = -1;
static int ett_adn_addr = -1;
static int ett_nba_request = -1;
static int ett_nba_resp = -1;
static int ett_noop = -1;

static int ett_mx_rec = -1;
static int ett_mx_rec_item = -1;

static int ett_ns_rec = -1;
static int ett_ns_rec_item = -1;



#define LWRES_UDP_PORT 921

static guint global_lwres_port = LWRES_UDP_PORT;


/* Define the lwres proto */
static int proto_lwres = -1;


/* Define many many headers for mgcp */

static const value_string message_types_values[] = {
    { 1,        "REQUEST " },
    { 2,        "RESPONSE" },
    { 0,        NULL },
};

static void dissect_getnamebyaddr_request(tvbuff_t* tvb, proto_tree* lwres_tree)
{
    guint32 flags,family;
    guint   addrlen, slen;
    const char* addrs;

    proto_tree* nba_request_tree;

    flags = tvb_get_ntohl(tvb, LWRES_LWPACKET_LENGTH);
    family = tvb_get_ntohl(tvb, LWRES_LWPACKET_LENGTH + 4);
    addrlen = tvb_get_ntohs(tvb, LWRES_LWPACKET_LENGTH + 8);
    addrs = tvb_ip_to_str(tvb, LWRES_LWPACKET_LENGTH + 10);
    slen = (int)strlen(addrs);

    if (lwres_tree == NULL)
        return;

    nba_request_tree = proto_tree_add_subtree(lwres_tree,tvb,LWRES_LWPACKET_LENGTH,LWRES_LWPACKET_LENGTH+14,
                                        ett_nba_request,NULL,"getnamebyaddr parameters");

    proto_tree_add_uint(nba_request_tree, hf_adn_flags, tvb,
                LWRES_LWPACKET_LENGTH, 4, flags);

    proto_tree_add_uint(nba_request_tree, hf_adn_family, tvb,
                LWRES_LWPACKET_LENGTH + 4, 4, family);

    proto_tree_add_uint(nba_request_tree, hf_adn_addr_len, tvb,
                LWRES_LWPACKET_LENGTH + 8, 2, addrlen);

    proto_tree_add_string(nba_request_tree, hf_adn_addr_addr, tvb,
                  LWRES_LWPACKET_LENGTH + 10, slen, addrs);

}

static void dissect_getnamebyaddr_response(tvbuff_t* tvb, proto_tree* lwres_tree)
{
    guint32 i, offset;
    guint16 naliases,realnamelen,aliaslen;
    gchar *aliasname;

    proto_tree* nba_resp_tree;
    proto_tree* alias_tree;

    if(lwres_tree == NULL)
        return;

    nba_resp_tree = proto_tree_add_subtree(lwres_tree, tvb, LWRES_LWPACKET_LENGTH, 10, ett_nba_resp, NULL, "getnamebyaddr records");

    naliases = tvb_get_ntohs(tvb, LWRES_LWPACKET_LENGTH + 4);
    realnamelen = tvb_get_ntohs(tvb,LWRES_LWPACKET_LENGTH + 4 + 2);

    proto_tree_add_item(nba_resp_tree,
                        hf_adn_flags,
                        tvb,
                        LWRES_LWPACKET_LENGTH,
                        4,
                        ENC_BIG_ENDIAN);
    proto_tree_add_item(nba_resp_tree,
                        hf_adn_naliases,
                        tvb,
                        LWRES_LWPACKET_LENGTH + 4,
                        2,
                        ENC_BIG_ENDIAN);

    proto_tree_add_item(nba_resp_tree,
                        hf_adn_namelen,
                        tvb,
                        LWRES_LWPACKET_LENGTH + 6,
                        2,
                        ENC_BIG_ENDIAN);

    proto_tree_add_item(nba_resp_tree,
                        hf_adn_realname,
                        tvb,
                        LWRES_LWPACKET_LENGTH + 8,
                        realnamelen,
                        ENC_ASCII|ENC_NA);

    offset=LWRES_LWPACKET_LENGTH + 8 + realnamelen;

    if(naliases)
    {
        for(i=0; i<naliases; i++)
        {
            aliaslen = tvb_get_ntohs(tvb, offset);
            aliasname = tvb_get_string_enc(wmem_packet_scope(), tvb, offset + 2, aliaslen, ENC_ASCII);

            alias_tree = proto_tree_add_subtree_format(nba_resp_tree, tvb, offset, 2 + aliaslen,
                                ett_adn_alias, NULL, "Alias %s",aliasname);

            proto_tree_add_item(alias_tree,
                                hf_adn_namelen,
                                tvb,
                                offset,
                                2,
                                ENC_BIG_ENDIAN);

            proto_tree_add_item(alias_tree,
                                hf_adn_aliasname,
                                tvb,
                                offset + 2,
                                aliaslen,
                                ENC_ASCII|ENC_NA);

            offset+=(2 + aliaslen + 1);
        }
    }
}

static void dissect_getaddrsbyname_request(tvbuff_t* tvb, proto_tree* lwres_tree)
{
    guint16 namelen;

    proto_tree* adn_request_tree;

    namelen  = tvb_get_ntohs(tvb, LWRES_LWPACKET_LENGTH + 8);

    if(lwres_tree == NULL)
        return;

    adn_request_tree = proto_tree_add_subtree(lwres_tree,tvb,
                    LWRES_LWPACKET_LENGTH,10+namelen+1,
                    ett_adn_request, NULL,
                    "getaddrbyname parameters");

    proto_tree_add_item(adn_request_tree,
                hf_adn_flags,
                tvb,
                LWRES_LWPACKET_LENGTH+0,
                sizeof(guint32),
                ENC_BIG_ENDIAN);

    proto_tree_add_item(adn_request_tree,
                hf_adn_addrtype,
                tvb,
                LWRES_LWPACKET_LENGTH+4,
                sizeof(guint32),
                ENC_BIG_ENDIAN);

    proto_tree_add_item(adn_request_tree,
                hf_adn_namelen,
                tvb,
                LWRES_LWPACKET_LENGTH+8,
                sizeof(guint16),
                ENC_BIG_ENDIAN);

    proto_tree_add_item(adn_request_tree,
                hf_adn_name,
                tvb,
                LWRES_LWPACKET_LENGTH+10,
                namelen,
                ENC_ASCII|ENC_NA);

}


static void dissect_getaddrsbyname_response(tvbuff_t* tvb, proto_tree* lwres_tree)
{
    guint32 family ,i, offset;
    guint16 naliases, naddrs, realnamelen, length, aliaslen;
    const gchar* addrs;
    guint slen;
    gchar *aliasname;

    proto_tree *adn_resp_tree;
    proto_tree *alias_tree;
    proto_tree *addr_tree;



    if(lwres_tree == NULL)
        return;

    adn_resp_tree = proto_tree_add_subtree(lwres_tree, tvb, LWRES_LWPACKET_LENGTH, 10,
                                        ett_adn_resp, NULL, "getaddrbyname records");

    naliases = tvb_get_ntohs(tvb, LWRES_LWPACKET_LENGTH + 4);
    naddrs   = tvb_get_ntohs(tvb, LWRES_LWPACKET_LENGTH + 6);
    realnamelen = tvb_get_ntohs(tvb, LWRES_LWPACKET_LENGTH + 8);


    proto_tree_add_item(adn_resp_tree, hf_adn_flags, tvb,
                LWRES_LWPACKET_LENGTH, 4, ENC_BIG_ENDIAN);

    proto_tree_add_item(adn_resp_tree, hf_adn_naliases, tvb,
                LWRES_LWPACKET_LENGTH + 4, 2, ENC_BIG_ENDIAN);

    proto_tree_add_item(adn_resp_tree, hf_adn_naddrs, tvb,
                LWRES_LWPACKET_LENGTH + 6, 2, ENC_BIG_ENDIAN);

    proto_tree_add_item(adn_resp_tree, hf_adn_namelen, tvb,
                LWRES_LWPACKET_LENGTH + 8, 2, ENC_BIG_ENDIAN);

    proto_tree_add_item(adn_resp_tree, hf_adn_realname, tvb,
                LWRES_LWPACKET_LENGTH + 10, realnamelen, ENC_ASCII|ENC_NA);

    offset = LWRES_LWPACKET_LENGTH + 10 + realnamelen + 1;

    if(naliases)
    {
        for(i=0; i<naliases; i++)
        {
            aliaslen = tvb_get_ntohs(tvb, offset);
            aliasname = tvb_get_string_enc(wmem_packet_scope(), tvb, offset + 2, aliaslen, ENC_ASCII);

            alias_tree = proto_tree_add_subtree_format(adn_resp_tree, tvb, offset, 2 + aliaslen,
                                                        ett_adn_alias, NULL, "Alias %s",aliasname);

            proto_tree_add_uint(alias_tree, hf_adn_namelen, tvb,
                        offset, 2, aliaslen);

            proto_tree_add_item(alias_tree, hf_adn_aliasname, tvb,
                        offset + 2, aliaslen, ENC_ASCII|ENC_NA);

            offset+=(2 + aliaslen + 1);
        }
    }

    if(naddrs)
    {
        for(i=0; i < naddrs; i++)
        {
            family = tvb_get_ntohl(tvb, offset);
            length = tvb_get_ntohs(tvb, offset + 4);
            addrs = tvb_ip_to_str(tvb, offset + 6);
            slen = (int)strlen(addrs);

            addr_tree = proto_tree_add_subtree_format(adn_resp_tree,tvb, offset, 4+2+4, ett_adn_addr, NULL, "Address %s", addrs);

            proto_tree_add_uint(addr_tree, hf_adn_family, tvb,
                        offset, 4, family);

            proto_tree_add_uint(addr_tree, hf_adn_addr_len, tvb,
                        offset + 4, 2, length);

            proto_tree_add_string(addr_tree, hf_adn_addr_addr, tvb,
                          offset + 6, slen, addrs);

            offset+= 4 + 2 + 4;
        }
    }


}

static void dissect_a_records(tvbuff_t* tvb, proto_tree* tree,guint32 nrec,int offset)
{
    guint32 i, curr;
    const gchar* addrs;
    proto_tree* a_rec_tree;
    proto_tree* addr_tree;

    if(tree == NULL)
        return;

    a_rec_tree = proto_tree_add_subtree(tree,tvb,offset,
                (int)((sizeof(guint32) + sizeof(guint16)) * nrec),
                ett_a_rec, NULL, "A records");

    for(i=0; i<nrec; i++)
    {

        curr = offset + (int)((sizeof(guint32)+sizeof(guint16)) * i);

        addrs = tvb_ip_to_str(tvb, curr+2);

        addr_tree = proto_tree_add_subtree_format(a_rec_tree, tvb, curr,
                            6, ett_a_rec_addr, NULL, "Address %s", addrs);

        proto_tree_add_item(addr_tree, hf_a_rec_len, tvb, curr,
                    sizeof(guint16), ENC_BIG_ENDIAN);

        proto_tree_add_item(addr_tree, hf_a_record, tvb, curr + 2, 4, ENC_BIG_ENDIAN);
    }

}

static void dissect_srv_records(tvbuff_t* tvb, proto_tree* tree,guint32 nrec,int offset)
{
    guint32 i, curr;
    guint16 /*len, namelen,*/ priority, weight, port, dlen;
    const guchar *dname;

    proto_item* srv_rec_tree, *rec_tree;

    if(tree == NULL)
        return;

    srv_rec_tree = proto_tree_add_subtree_format(tree, tvb, offset, offset, ett_srv_rec, NULL, "SRV records (%d)", nrec);

    curr = offset;

    for(i=0; i < nrec; i++)
    {
        /*len =      tvb_get_ntohs(tvb, curr);*/
        priority = tvb_get_ntohs(tvb, curr + 2);
        weight   = tvb_get_ntohs(tvb, curr + 4);
        port     = tvb_get_ntohs(tvb, curr + 6);
        /*namelen = len - 8;*/

        dlen = get_dns_name(tvb, curr + 8, 0, curr + 8, &dname);

        rec_tree = proto_tree_add_subtree_format(srv_rec_tree, tvb, curr, 6,
                    ett_srv_rec_item, NULL,
                    "SRV record:pri=%d,w=%d,port=%d,dname=%s",
                    priority, weight, port, dname);

        proto_tree_add_uint(rec_tree,
                        hf_srv_prio,
                        tvb,
                        curr + 2,
                        2,
                        priority);

        proto_tree_add_uint(rec_tree,
                        hf_srv_weight,
                        tvb,
                        curr + 4,
                        2,
                        weight);

        proto_tree_add_uint(rec_tree,
                        hf_srv_port,
                        tvb,
                        curr + 6,
                        2,
                        port);


        proto_tree_add_string(rec_tree,
                            hf_srv_dname,
                            tvb,
                            curr + 8,
                            dlen,
                            dname);

        curr+=(int)((sizeof(short)*4) + dlen);

    }

}

static void dissect_mx_records(tvbuff_t* tvb, proto_tree* tree, guint32 nrec, int offset)
{

    guint i, curr;
    guint /*len, namelen,*/ priority, dlen;
    const guchar *dname;

    proto_tree* mx_rec_tree, *rec_tree;


    if(tree == NULL)
        return;

    mx_rec_tree = proto_tree_add_subtree_format(tree, tvb, offset, offset, ett_mx_rec, NULL, "MX records (%d)", nrec);

    curr = offset;
    for(i=0; i < nrec; i++)
    {
        /*len =       tvb_get_ntohs(tvb, curr);*/
        priority =  tvb_get_ntohs(tvb, curr + 2);
        /*namelen  =  len - 4;*/

        dlen  = get_dns_name(tvb, curr + 4, 0, curr + 4, &dname);

        rec_tree = proto_tree_add_subtree_format(mx_rec_tree, tvb, curr,6,ett_mx_rec_item,NULL,
                        "MX record: pri=%d,dname=%s", priority,dname);


        proto_tree_add_item(rec_tree,
                            hf_srv_prio,
                            tvb,
                            curr + 2,
                            2,
                            ENC_BIG_ENDIAN);

        proto_tree_add_string(rec_tree,
                            hf_srv_dname,
                            tvb,
                            curr + 4,
                            dlen,
                            dname);

        curr+=(int)((sizeof(short)*2) + dlen);


    }

}

static void dissect_ns_records(tvbuff_t* tvb, proto_tree* tree, guint32 nrec, int offset)
{
    guint i, curr;
    guint /*len, namelen,*/ dlen;
    const guchar *dname;

    proto_tree* ns_rec_tree, *rec_tree;

    if(tree == NULL)
        return;

    ns_rec_tree = proto_tree_add_subtree_format(tree, tvb, offset, offset, ett_ns_rec, NULL, "NS record (%d)", nrec);

    curr=offset;

    for(i=0;i<nrec;i++)
    {
        /*len = tvb_get_ntohs(tvb, curr);*/
        /*namelen = len - 2;*/

        dlen = get_dns_name(tvb, curr + 2, 0, curr + 2, &dname);

        rec_tree = proto_tree_add_subtree_format(ns_rec_tree, tvb, curr,4, ett_ns_rec_item, NULL, "NS record: dname=%s",dname);

        proto_tree_add_string(rec_tree,
                            hf_ns_dname,
                            tvb,
                            curr + 2,
                            dlen,
                            dname);
        curr+=(int)(sizeof(short) + dlen);

    }


}

static void dissect_rdata_request(tvbuff_t* tvb, proto_tree* lwres_tree)
{
    guint16 namelen;

    proto_tree* rdata_request_tree;

    namelen = tvb_get_ntohs(tvb, LWRES_LWPACKET_LENGTH+8);

    if(lwres_tree == NULL)
        return;

    rdata_request_tree =
            proto_tree_add_subtree(lwres_tree,tvb,LWRES_LWPACKET_LENGTH,10+namelen+1,ett_rdata_req,NULL,"RDATA request parameters");

    proto_tree_add_item(rdata_request_tree,
            hf_rflags,
            tvb,
            LWRES_LWPACKET_LENGTH+0,
            sizeof(guint32),
            ENC_BIG_ENDIAN);

    proto_tree_add_item(rdata_request_tree,
            hf_rdclass,
            tvb,
            LWRES_LWPACKET_LENGTH+4,
            sizeof(guint16),
            ENC_BIG_ENDIAN);

    proto_tree_add_item(rdata_request_tree,
            hf_rdtype,
            tvb,
            LWRES_LWPACKET_LENGTH+6,
            sizeof(guint16),
            ENC_BIG_ENDIAN);

    proto_tree_add_item(rdata_request_tree,
            hf_namelen,
            tvb,
            LWRES_LWPACKET_LENGTH+8,
            sizeof(guint16),
            ENC_BIG_ENDIAN);

    proto_tree_add_item(rdata_request_tree,
            hf_req_name,
            tvb,
            LWRES_LWPACKET_LENGTH+10,
            namelen,
                ENC_ASCII|ENC_NA);

}

static void dissect_rdata_response(tvbuff_t* tvb, proto_tree* lwres_tree)
{
    guint offset;
    guint rdtype, nrdatas, realnamelen;

    proto_tree* rdata_resp_tree;

    rdtype =  tvb_get_ntohs(tvb, LWRES_LWPACKET_LENGTH+6);
    nrdatas = tvb_get_ntohs(tvb, LWRES_LWPACKET_LENGTH+12);
    realnamelen = tvb_get_ntohs(tvb,LWRES_LWPACKET_LENGTH+16);

    offset = LWRES_LWPACKET_LENGTH + 18 + realnamelen + 1;

    if(lwres_tree == NULL)
        return;

    rdata_resp_tree = proto_tree_add_subtree(lwres_tree,tvb,LWRES_LWPACKET_LENGTH, 18+realnamelen+1,ett_rdata_resp,NULL,"RDATA response");

    proto_tree_add_item(rdata_resp_tree,
                        hf_rflags,
                        tvb,
                        LWRES_LWPACKET_LENGTH+0,
                        sizeof(guint32),
                        ENC_BIG_ENDIAN);

    proto_tree_add_item(rdata_resp_tree,
                        hf_rdclass,
                        tvb,
                        LWRES_LWPACKET_LENGTH+4,
                        sizeof(guint16),
                        ENC_BIG_ENDIAN);

    proto_tree_add_item(rdata_resp_tree,
                        hf_rdtype,
                        tvb,
                        LWRES_LWPACKET_LENGTH+6,
                        sizeof(guint16),
                        ENC_BIG_ENDIAN);

    proto_tree_add_item(rdata_resp_tree,
            hf_ttl,
            tvb,
            LWRES_LWPACKET_LENGTH+8,
            sizeof(guint32),
            ENC_BIG_ENDIAN);

    proto_tree_add_item(rdata_resp_tree,
            hf_nrdatas,
            tvb,
            LWRES_LWPACKET_LENGTH+12,
            sizeof(guint16),
            ENC_BIG_ENDIAN);

    proto_tree_add_item(rdata_resp_tree,
            hf_nsigs,
            tvb,
            LWRES_LWPACKET_LENGTH+14,
            sizeof(guint16),
            ENC_BIG_ENDIAN);

    proto_tree_add_item(rdata_resp_tree,
            hf_realnamelen,
            tvb,
            LWRES_LWPACKET_LENGTH+16,
            sizeof(guint16),
            ENC_BIG_ENDIAN);

    proto_tree_add_item(rdata_resp_tree,
                        hf_realname,
                        tvb,
                        LWRES_LWPACKET_LENGTH+18,
                        realnamelen,
                ENC_ASCII|ENC_NA);

    switch(rdtype)
    {
        case T_A:
            dissect_a_records(tvb,rdata_resp_tree,nrdatas,offset);
        break;

        case T_SRV:
            dissect_srv_records(tvb,rdata_resp_tree,nrdatas, offset);
        break;

        case T_MX:
            dissect_mx_records(tvb,rdata_resp_tree,nrdatas, offset);
        break;

        case T_NS:
            dissect_ns_records(tvb,rdata_resp_tree,nrdatas, offset);
        break;
    }

}

static void dissect_noop(tvbuff_t* tvb, proto_tree* lwres_tree)
{
    guint16 datalen;

    proto_tree* noop_tree;

    datalen = tvb_get_ntohs(tvb, LWRES_LWPACKET_LENGTH);

    if(lwres_tree == NULL)
        return;

    noop_tree = proto_tree_add_subtree(lwres_tree, tvb, LWRES_LWPACKET_LENGTH, 10, ett_noop, NULL, "Noop record");

    proto_tree_add_uint(noop_tree, hf_length, tvb,
                LWRES_LWPACKET_LENGTH, sizeof(guint16), datalen);

    tvb_ensure_bytes_exist(tvb, LWRES_LWPACKET_LENGTH, datalen);

}

static void dissect_getaddrsbyname(tvbuff_t* tvb, proto_tree* lwres_tree, int type)
{
    if(type == 1)
        dissect_getaddrsbyname_request(tvb, lwres_tree);
    else
        dissect_getaddrsbyname_response(tvb, lwres_tree);
}

static void dissect_getnamebyaddr(tvbuff_t* tvb, proto_tree* lwres_tree, int type)
{
    if(type == 1)
        dissect_getnamebyaddr_request(tvb, lwres_tree);
    else
        dissect_getnamebyaddr_response(tvb, lwres_tree);
}

static void dissect_getrdatabyname(tvbuff_t* tvb, proto_tree* lwres_tree, int type)
{
    if(type == 1)
        dissect_rdata_request(tvb, lwres_tree);
    else
        dissect_rdata_response(tvb, lwres_tree);
}

static int
dissect_lwres(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_)
{
    guint16 version, flags, authtype, authlength ;
    guint32 length, opcode, result, recvlength, serial;
    guint32 message_type;

    proto_item* lwres_item;
    proto_tree* lwres_tree;

    col_set_str(pinfo->cinfo, COL_PROTOCOL, "lw_res");
    length = tvb_get_ntohl(tvb, LW_LENGTH_OFFSET);
    version = tvb_get_ntohs(tvb, LW_VERSION_OFFSET);
    flags = tvb_get_ntohs(tvb, LW_PKTFLASG_OFFSET);
    serial = tvb_get_ntohl(tvb, LW_SERIAL_OFFSET);
    opcode = tvb_get_ntohl(tvb,LW_OPCODE_OFFSET);
    result = tvb_get_ntohl(tvb, LW_RESULT_OFFSET);
    recvlength = tvb_get_ntohl(tvb, LW_RECVLEN_OFFSET);
    authtype = tvb_get_ntohs(tvb, LW_AUTHTYPE_OFFSET);
    authlength = tvb_get_ntohs(tvb, LW_AUTHLEN_OFFSET);

    message_type = (flags & LWRES_LWPACKETFLAG_RESPONSE) ? 2 : 1;

    if(flags & LWRES_LWPACKETFLAG_RESPONSE)
    {
        col_add_fstr(pinfo->cinfo, COL_INFO,
            "%s, opcode=%s, serial=0x%x, result=%s",
                val_to_str_const((guint32)message_type,message_types_values,"unknown"),
                val_to_str_const(opcode, opcode_values, "unknown"),
                serial,
                val_to_str_const(result,result_values,"unknown"));
    }
    else
    {
        col_add_fstr(pinfo->cinfo, COL_INFO,
                "%s, opcode=%s, serial=0x%x",
                val_to_str_const((guint32)message_type,message_types_values,"unknown"),
                val_to_str_const(opcode, opcode_values, "unknown"),
        serial);
    }

    if(tree == NULL)
        return tvb_captured_length(tvb);

    lwres_item = proto_tree_add_item(tree,proto_lwres, tvb,0, -1, ENC_NA);
    lwres_tree = proto_item_add_subtree(lwres_item, ett_lwres);

    proto_tree_add_uint(lwres_tree,
            hf_length,
            tvb,
            LW_LENGTH_OFFSET,
            sizeof(guint32),
            length);


    proto_tree_add_uint(lwres_tree,
                hf_version,
                tvb,
                LW_VERSION_OFFSET,
                sizeof(guint16),
                version);




    proto_tree_add_uint(lwres_tree,
                hf_flags,
                tvb,
                LW_PKTFLASG_OFFSET,
                sizeof(guint16),
                flags);

    proto_tree_add_uint(lwres_tree,
                hf_serial,
                tvb,
                LW_SERIAL_OFFSET,
                sizeof(guint32),
                serial);

    proto_tree_add_uint(lwres_tree,
                hf_opcode,
                tvb,
                LW_OPCODE_OFFSET,
                sizeof(guint32),
                opcode);

    proto_tree_add_uint(lwres_tree,
                hf_result,
                tvb,
                LW_RESULT_OFFSET,
                sizeof(guint32),
                result);

    proto_tree_add_uint(lwres_tree,
                hf_recvlen,
                tvb,
                LW_RECVLEN_OFFSET,
                sizeof(guint32),
                recvlength);

    proto_tree_add_uint(lwres_tree,
                hf_authtype,
                tvb,
                LW_AUTHTYPE_OFFSET,
                sizeof(guint16),
                authtype);

    proto_tree_add_uint(lwres_tree,
                hf_authlen,
                tvb,
                LW_AUTHLEN_OFFSET,
                sizeof(guint16),
                authlength);

    if(!result)
    {
        switch(opcode)
        {
            case LWRES_OPCODE_NOOP:
                dissect_noop(tvb, lwres_tree);
            break;

            case LWRES_OPCODE_GETADDRSBYNAME:
                dissect_getaddrsbyname(tvb, lwres_tree, message_type);
            break;

            case LWRES_OPCODE_GETNAMEBYADDR:
                dissect_getnamebyaddr(tvb, lwres_tree, message_type);
            break;

            case LWRES_OPCODE_GETRDATABYNAME:
                dissect_getrdatabyname(tvb, lwres_tree, message_type);
            break;
        }
    }
    return tvb_captured_length(tvb);
}


void
proto_register_lwres(void)
{
    static hf_register_info hf[] = {
        { &hf_length,
          { "Length", "lwres.length", FT_UINT32, BASE_DEC, NULL, 0x0,
            "lwres length", HFILL }},

        { &hf_version,
          { "Version", "lwres.version", FT_UINT16, BASE_DEC, NULL, 0x0,
            "lwres version", HFILL }},

        { &hf_flags,
          { "Packet Flags", "lwres.flags", FT_UINT16, BASE_HEX, NULL, 0x0,
            "lwres flags", HFILL }},

        { &hf_serial,
          { "Serial", "lwres.serial", FT_UINT32, BASE_HEX, NULL, 0x0,
            "lwres serial", HFILL }},

        { &hf_opcode,
          { "Operation code", "lwres.opcode", FT_UINT32, BASE_DEC, VALS(opcode_values), 0x0,
            "lwres opcode", HFILL }},

        { &hf_result,
          { "Result", "lwres.result", FT_UINT32, BASE_DEC, VALS(result_values), 0x0,
            "lwres result", HFILL }},

        { &hf_recvlen,
          { "Received length", "lwres.recvlen", FT_UINT32, BASE_DEC, NULL, 0x0,
            "lwres recvlen", HFILL }},

        { &hf_authtype,
          { "Auth. type", "lwres.authtype", FT_UINT16, BASE_DEC, NULL, 0x0,
            "lwres authtype", HFILL }},

        { &hf_authlen,
          { "Auth. length", "lwres.authlen", FT_UINT16, BASE_DEC, NULL, 0x0,
            "lwres authlen", HFILL }},

        { &hf_rflags,
          { "Flags", "lwres.rflags", FT_UINT32, BASE_HEX, NULL, 0x0,
            "lwres rflags", HFILL }},
        { &hf_rdclass,
          { "Class", "lwres.class", FT_UINT16, BASE_DEC, NULL, 0x0,
            "lwres class", HFILL }},

        { &hf_rdtype,
          { "Type", "lwres.type", FT_UINT16, BASE_DEC, VALS(t_types), 0x0,
            "lwres type", HFILL }},

        { &hf_namelen,
          { "Name length", "lwres.namelen", FT_UINT16, BASE_DEC, NULL, 0x0,
            "lwres namelen", HFILL }},

        { &hf_req_name,
          { "Domain name", "lwres.reqdname", FT_STRING, BASE_NONE, NULL, 0x0,
            "lwres reqdname", HFILL }},

        { &hf_ttl,
          { "Time To Live", "lwres.ttl", FT_UINT32, BASE_DEC, NULL, 0x0,
            "lwres ttl", HFILL }},

        { &hf_nrdatas,
          { "Number of rdata records", "lwres.nrdatas", FT_UINT16, BASE_DEC, NULL, 0x0,
            "lwres nrdatas", HFILL }},

        { &hf_nsigs,
          { "Number of signature records", "lwres.nsigs", FT_UINT16, BASE_DEC, NULL, 0x0,
            "lwres nsigs", HFILL }},

        { &hf_realnamelen,
          { "Real name length", "lwres.realnamelen", FT_UINT16, BASE_DEC, NULL, 0x0,
            "lwres realnamelen", HFILL }},

        { &hf_realname,
          { "Real doname name", "lwres.realname", FT_STRING, BASE_NONE, NULL, 0x0,
            "lwres realname", HFILL }},

        { &hf_a_record,
          { "IPv4 Address", "lwres.arecord", FT_UINT32, BASE_DEC, NULL, 0x0,
            "lwres arecord", HFILL }},

        { &hf_a_rec_len,
          { "Length", "lwres.areclen", FT_UINT16, BASE_DEC, NULL, 0x0,
            "lwres areclen", HFILL }},

        { &hf_srv_prio,
          { "Priority", "lwres.srv.priority", FT_UINT16, BASE_DEC, NULL, 0x0,
            "lwres srv prio", HFILL }},

        { &hf_srv_weight,
          { "Weight", "lwres.srv.weight", FT_UINT16, BASE_DEC, NULL, 0x0,
            "lwres srv weight", HFILL }},

        { &hf_srv_port,
          { "Port" , "lwres.srv.port", FT_UINT16, BASE_DEC, NULL, 0x0,
            "lwres srv port", HFILL }},

        { &hf_srv_dname,
          { "DNAME" , "lwres.srv.dname", FT_STRING, BASE_NONE, NULL, 0x0,
            NULL, HFILL }},

        { &hf_adn_flags,
          { "Flags", "lwres.adn.flags", FT_UINT32, BASE_HEX, NULL, 0x0,
            "lwres adn flags", HFILL }},

        { &hf_adn_addrtype,
          { "Address type", "lwres.adn.addrtype", FT_UINT32, BASE_DEC, NULL, 0x0,
            "lwres adn addrtype", HFILL }},

        { &hf_adn_namelen,
          { "Name length", "lwres.adn.namelen", FT_UINT16, BASE_DEC, NULL, 0x0,
            "lwres adn namelen", HFILL }},

        { &hf_adn_name,
          { "Name", "lwres.adn.name", FT_STRING, BASE_NONE, NULL, 0x0,
            "lwres adn name", HFILL }},

        { &hf_adn_naliases,
          { "Number of aliases", "lwres.adn.naliases", FT_UINT16, BASE_DEC, NULL, 0x0,
            "lwres adn naliases", HFILL }},

        { &hf_adn_naddrs,
          { "Number of addresses", "lwres.adn.naddrs", FT_UINT16, BASE_DEC, NULL, 0x0,
            "lwres adn naddrs", HFILL }},

        { &hf_adn_realname,
          { "Real name", "lwres.adn.realname", FT_STRING, BASE_NONE, NULL, 0x0,
            "lwres adn realname", HFILL }},

        { &hf_adn_aliasname,
          { "Alias name", "lwres.adn.aliasname", FT_STRING, BASE_NONE, NULL, 0x0,
            "lwres adn aliasname", HFILL }},

        { &hf_adn_family,
          { "Address family", "lwres.adn.addr.family", FT_UINT32, BASE_DEC, NULL, 0x0,
            "lwres adn addr family", HFILL }},

        { &hf_adn_addr_len,
          { "Address length", "lwres.adn.addr.length", FT_UINT16, BASE_DEC, NULL, 0x0,
            "lwres adn addr length", HFILL }},

        { &hf_adn_addr_addr,
          { "IP Address", "lwres.adn.addr.addr", FT_STRING, BASE_NONE, NULL, 0x0,
            "lwres adn addr addr", HFILL }},

        { &hf_ns_dname,
          { "Name" , "lwres.ns.dname", FT_STRING, BASE_NONE, NULL, 0x0,
            NULL, HFILL }},

        /* Add more fields here */
    };

    static gint *ett[] = {
        &ett_lwres,
        &ett_rdata_req,
        &ett_rdata_resp,
        &ett_a_rec,
        &ett_a_rec_addr,
        &ett_srv_rec,
        &ett_srv_rec_item,
        &ett_adn_request,
        &ett_adn_resp,
        &ett_adn_alias,
        &ett_adn_addr,
        &ett_nba_request,
        &ett_nba_resp,
        &ett_mx_rec,
        &ett_mx_rec_item,
        &ett_ns_rec,
        &ett_ns_rec_item,
        &ett_noop,
    };


    module_t *lwres_module;

    proto_lwres = proto_register_protocol("Light Weight DNS RESolver (BIND9)",
                                          "LWRES", "lwres");

    proto_register_field_array(proto_lwres, hf, array_length(hf));
    proto_register_subtree_array(ett, array_length(ett));

    lwres_module = prefs_register_protocol(proto_lwres, proto_reg_handoff_lwres);

    prefs_register_uint_preference(lwres_module, "udp.lwres_port",
                                   "lwres listener UDP Port",
                                   "Set the UDP port for lwres daemon"
                                   "(if other than the default of 921)",
                                   10, &global_lwres_port);

}

/* The registration hand-off routine */
void
proto_reg_handoff_lwres(void)
{
    static gboolean lwres_prefs_initialized = FALSE;
    static dissector_handle_t lwres_handle;
    static guint lwres_port;

    if(!lwres_prefs_initialized) {
        lwres_handle = create_dissector_handle(dissect_lwres, proto_lwres);
        lwres_prefs_initialized = TRUE;
    }
    else {
        dissector_delete_uint("udp.port", lwres_port, lwres_handle);
    }

    dissector_add_uint("udp.port", global_lwres_port, lwres_handle);
    lwres_port = global_lwres_port;

}

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