Logo Search packages:      
Sourcecode: ethereal version File versions

packet-bootp.c

/* packet-bootp.c
 * Routines for BOOTP/DHCP packet disassembly
 * Copyright 1998, Gilbert Ramirez <gram@alumni.rice.edu>
 * Copyright 2004, Thomas Anders <thomas.anders [AT] blue-cable.de>
 *
 * $Id: packet-bootp.c 13015 2005-01-13 07:49:31Z guy $
 *
 * The information used comes from:
 * RFC  951: Bootstrap Protocol
 * RFC 1497: BOOTP extensions
 * RFC 1542: Clarifications and Extensions for the Bootstrap Protocol
 * RFC 2131: Dynamic Host Configuration Protocol
 * RFC 2132: DHCP Options and BOOTP Vendor Extensions
 * RFC 2241: DHCP Options for Novell Directory Services
 * RFC 2242: NetWare/IP Domain Name and Information
 * RFC 2489: Procedure for Defining New DHCP Options
 * RFC 2610: DHCP Options for Service Location Protocol
 * RFC 3046: DHCP Relay Agent Information Option
 * RFC 3118: Authentication for DHCP Messages
 * RFC 3203: DHCP reconfigure extension
 * RFC 3495: DHCP Option (122) for CableLabs Client Configuration
 * RFC 3594: PacketCable Security Ticket Control Sub-Option (122.9)
 * draft-ietf-dhc-fqdn-option-07.txt
 * BOOTP and DHCP Parameters
 *     http://www.iana.org/assignments/bootp-dhcp-parameters
 * DOCSIS(TM) 2.0 Radio Frequency Interface Specification
 *     http://www.cablemodem.com/downloads/specs/CM-SP-RFIv2.0-I06-040804.pdf
 * PacketCable(TM) MTA Device Provisioning Specification
 *     http://www.packetcable.com/downloads/specs/PKT-SP-PROV-I10-040730.pdf
 *     http://www.cablelabs.com/specifications/archives/PKT-SP-PROV-I05-021127.pdf (superseded by above)
 * CableHome(TM) 1.1 Specification
 *     http://www.cablelabs.com/projects/cablehome/downloads/specs/CH-SP-CH1.1-I05-040806.pdf
 *
 * Ethereal - Network traffic analyzer
 * By Gerald Combs <gerald@ethereal.com>
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

/*
 * Some of the development of the BOOTP/DHCP protocol decoder was sponsored by
 * Cable Television Laboratories, Inc. ("CableLabs") based upon proprietary
 * CableLabs' specifications. Your license and use of this protocol decoder
 * does not mean that you are licensed to use the CableLabs'
 * specifications.  If you have questions about this protocol, contact
 * jf.mule [AT] cablelabs.com or c.stuart [AT] cablelabs.com for additional
 * information.
 */


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

#include <string.h>
#include <glib.h>
#include <epan/packet.h>
#include "packet-arp.h"
#include "packet-dns.h"                   /* for get_dns_name() */
#include <epan/addr_resolv.h>
#include <epan/prefs.h>
#include <epan/tap.h>
#include <epan/strutil.h>
#include <epan/arptypes.h>

static int bootp_dhcp_tap = -1;
static int proto_bootp = -1;
static int hf_bootp_type = -1;
static int hf_bootp_hw_type = -1;
static int hf_bootp_hw_len = -1;
static int hf_bootp_hops = -1;
static int hf_bootp_id = -1;
static int hf_bootp_secs = -1;
static int hf_bootp_flags = -1;
static int hf_bootp_flags_broadcast = -1;
static int hf_bootp_flags_reserved = -1;
static int hf_bootp_ip_client = -1;
static int hf_bootp_ip_your = -1;
static int hf_bootp_ip_server = -1;
static int hf_bootp_ip_relay = -1;
static int hf_bootp_hw_addr = -1;
static int hf_bootp_server = -1;
static int hf_bootp_file = -1;
static int hf_bootp_cookie = -1;
static int hf_bootp_vendor = -1;
static int hf_bootp_dhcp = -1;
static int hf_bootp_fqdn_s = -1;
static int hf_bootp_fqdn_o = -1;
static int hf_bootp_fqdn_e = -1;
static int hf_bootp_fqdn_n = -1;
static int hf_bootp_fqdn_mbz = -1;
static int hf_bootp_fqdn_rcode1 = -1;
static int hf_bootp_fqdn_rcode2 = -1;
static int hf_bootp_fqdn_name = -1;
static int hf_bootp_fqdn_asciiname = -1;
static int hf_bootp_pkt_mtacap_len = -1;
static int hf_bootp_docsis_cmcap_len = -1;
static int hf_bootp_hw_ether_addr = -1;

static gint ett_bootp = -1;
static gint ett_bootp_flags = -1;
static gint ett_bootp_option = -1;
static gint ett_bootp_fqdn = -1;

gboolean novell_string = FALSE;

#define UDP_PORT_BOOTPS  67
#define UDP_PORT_BOOTPC  68

#define BOOTP_BC  0x8000
#define BOOTP_MBZ 0x7FFF

/* FQDN stuff */
#define F_FQDN_S  0x01
#define F_FQDN_O  0x02
#define F_FQDN_E  0x04
#define F_FQDN_N  0x08
#define F_FQDN_MBZ      0xf0

static const true_false_string tfs_fqdn_s = {
  "Server",
  "Client"
};

static const true_false_string tfs_fqdn_o = {
  "Override",
  "No override"
};

static const true_false_string tfs_fqdn_e = {
  "Binary encoding",
  "ASCII encoding"
};

static const true_false_string tfs_fqdn_n = {
  "No server updates",
  "Some server updates"
};

enum field_type {
      special,
      none,
      presence,
      ipv4,             /* single IPv4 address */
      ipv4_list,        /* list of IPv4 addresses */
      string,
      bytes,
      opaque,
      val_boolean,
      val_u_byte,
      val_u_short,
      val_u_short_list,
      val_u_le_short,
      val_u_long,
      time_in_secs,
      fqdn,
      ipv4_or_fqdn
};

struct opt_info {
      char  *text;
      enum field_type ftype;
      const void *data;
};

static const true_false_string flag_set_broadcast = {
  "Broadcast",
  "Unicast"
};


/* PacketCable definitions */
#define PACKETCABLE_MTA_CAP10 "pktc1.0:"
#define PACKETCABLE_MTA_CAP15 "pktc1.5:"
#define PACKETCABLE_CM_CAP11  "docsis1.1:"
#define PACKETCABLE_CM_CAP20  "docsis2.0:"

#define PACKETCABLE_CCC_I05      1
#define PACKETCABLE_CCC_DRAFT5   2
#define PACKETCABLE_CCC_RFC_3495 3

static enum_val_t pkt_ccc_protocol_versions[] = {
      { "ccc_i05",     "PKT-SP-PROV-I05-021127", PACKETCABLE_CCC_I05 },
      { "ccc_draft_5", "IETF Draft 5",           PACKETCABLE_CCC_DRAFT5 },
      { "rfc_3495",    "RFC 3495",               PACKETCABLE_CCC_RFC_3495 },
      { NULL, NULL, 0 }
};

static gint pkt_ccc_protocol_version = PACKETCABLE_CCC_RFC_3495;
static gint pkt_ccc_option = 122;


static int dissect_vendor_pxeclient_suboption(proto_tree *v_tree, tvbuff_t *tvb,
    int optoff, int optend);
static int dissect_vendor_cablelabs_suboption(proto_tree *v_tree, tvbuff_t *tvb,
    int optoff, int optend);
static int dissect_netware_ip_suboption(proto_tree *v_tree, tvbuff_t *tvb,
    int optoff, int optend);
static int bootp_dhcp_decode_agent_info(proto_tree *v_tree, tvbuff_t *tvb,
    int optoff, int optend);
static void dissect_packetcable_mta_cap(proto_tree *v_tree, tvbuff_t *tvb,
       int voff, int len);
static void dissect_docsis_cm_cap(proto_tree *v_tree, tvbuff_t *tvb,
       int voff, int len);
static int dissect_packetcable_i05_ccc(proto_tree *v_tree, tvbuff_t *tvb,
    int optoff, int optend);
static int dissect_packetcable_ietf_ccc(proto_tree *v_tree, tvbuff_t *tvb,
    int optoff, int optend, int revision);


static const char *
get_dhcp_type(guint8 byte)
{
      static const char *opt53_text[] = {
            "Unknown Message Type",
            "Discover",
            "Offer",
            "Request",
            "Decline",
            "ACK",
            "NAK",
            "Release",
            "Inform",
            "Force Renew"
      };
      int i;

      if (byte > 0 && byte < (sizeof opt53_text / sizeof opt53_text[0]))
            i = byte;
      else
            i = 0;
      return opt53_text[i];
}

/* DHCP Authentication protocols */
#define AUTHEN_PROTO_CONFIG_TOKEN   0
#define AUTHEN_PROTO_DELAYED_AUTHEN 1

/* DHCP Authentication algorithms for delayed authentication */
#define AUTHEN_DELAYED_ALGO_HMAC_MD5      1

/* DHCP Authentication Replay Detection Methods */
#define AUTHEN_RDM_MONOTONIC_COUNTER      0x00

/* DHCP Option Overload (option code 52) */
#define OPT_OVERLOAD_FILE           1
#define OPT_OVERLOAD_SNAME          2
#define OPT_OVERLOAD_BOTH           3

/* Server name and boot file offsets and lengths */
#define SERVER_NAME_OFFSET          44
#define SERVER_NAME_LEN             64
#define FILE_NAME_OFFSET            108
#define FILE_NAME_LEN               128
#define VENDOR_INFO_OFFSET          236

static const true_false_string toggle_tfs = {
      "Enabled",
      "Disabled"
};

static const true_false_string yes_no_tfs = {
      "Yes",
      "No"
};

/* Returns the number of bytes consumed by this option. */
static int
bootp_option(tvbuff_t *tvb, proto_tree *bp_tree, int voff, int eoff,
    gboolean first_pass, gboolean *at_end, const char **dhcp_type_p,
    const guint8 **vendor_class_id_p)
{
      char              *text;
      enum field_type         ftype;
      guchar                  code = tvb_get_guint8(tvb, voff);
      int               optlen;
      const struct true_false_string *tfs;
      const value_string      *vs;
      guchar                  byte;
      int               i, consumed;
      int               optoff, optleft, optend;
      gulong                  time_secs;
      proto_tree        *v_tree, *o52tree, *flags_tree, *ft;
      proto_item        *vti;
      guint8                  protocol;
      guint8                  algorithm;
      guint8                  rdm;
      guint8                  fqdn_flags;
      int               o52voff, o52eoff;
      gboolean          o52at_end;
      gboolean          skip_opaque = FALSE;

      static const value_string nbnt_vals[] = {
          {0x1,   "B-node" },
          {0x2,   "P-node" },
          {0x4,   "M-node" },
          {0x8,   "H-node" },
          {0,     NULL     } };

      static const value_string slpda_vals[] = {
          {0x00,   "Dynamic Discovery" },
          {0x01,   "Static Discovery" },
          {0x80,   "Backwards compatibility" },
          {0,     NULL     } };

      static const value_string slp_scope_vals[] = {
          {0x00,   "Preferred Scope" },
          {0x01,   "Mandatory Scope" },
          {0,     NULL     } };

      static const value_string authen_protocol_vals[] = {
          {AUTHEN_PROTO_CONFIG_TOKEN,   "configuration token" },
          {AUTHEN_PROTO_DELAYED_AUTHEN, "delayed authentication" },
          {0,                           NULL     } };

      static const value_string authen_da_algo_vals[] = {
          {AUTHEN_DELAYED_ALGO_HMAC_MD5, "HMAC_MD5" },
          {0,                            NULL     } };

      static const value_string authen_rdm_vals[] = {
          {AUTHEN_RDM_MONOTONIC_COUNTER, "Monotonically-increasing counter" },
          {0,                            NULL     } };

      static const value_string opt_overload_vals[] = {
          { OPT_OVERLOAD_FILE,  "Boot file name holds options",                },
          { OPT_OVERLOAD_SNAME, "Server host name holds options",              },
          { OPT_OVERLOAD_BOTH,  "Boot file and server host names hold options" },
          { 0,                  NULL                                           } };

      static struct opt_info opt[] = {
            /*   0 */ { "Padding",                          none, NULL },
            /*   1 */ { "Subnet Mask",                      ipv4, NULL },
            /*   2 */ { "Time Offset",                      time_in_secs, NULL },
            /*   3 */ { "Router",                           ipv4_list, NULL },
            /*   4 */ { "Time Server",                      ipv4_list, NULL },
            /*   5 */ { "Name Server",                      ipv4_list, NULL },
            /*   6 */ { "Domain Name Server",               ipv4_list, NULL },
            /*   7 */ { "Log Server",                       ipv4_list, NULL },
            /*   8 */ { "Cookie Server",                    ipv4_list, NULL },
            /*   9 */ { "LPR Server",                       ipv4_list, NULL },
            /*  10 */ { "Impress Server",                   ipv4_list, NULL },
            /*  11 */ { "Resource Location Server",               ipv4_list, NULL },
            /*  12 */ { "Host Name",                        string, NULL },
            /*  13 */ { "Boot File Size",                   val_u_short, NULL },
            /*  14 */ { "Merit Dump File",                        string, NULL },
            /*  15 */ { "Domain Name",                      string, NULL },
            /*  16 */ { "Swap Server",                      ipv4, NULL },
            /*  17 */ { "Root Path",                        string, NULL },
            /*  18 */ { "Extensions Path",                        string, NULL },
            /*  19 */ { "IP Forwarding",                    val_boolean, TFS(&toggle_tfs) },
            /*  20 */ { "Non-Local Source Routing",               val_boolean, TFS(&toggle_tfs) },
            /*  21 */ { "Policy Filter",                    special, NULL },
            /*  22 */ { "Maximum Datagram Reassembly Size",       val_u_short, NULL },
            /*  23 */ { "Default IP Time-to-Live",                val_u_byte, NULL },
            /*  24 */ { "Path MTU Aging Timeout",                 time_in_secs, NULL },
            /*  25 */ { "Path MTU Plateau Table",                 val_u_short_list, NULL },
            /*  26 */ { "Interface MTU",                    val_u_short, NULL },
            /*  27 */ { "All Subnets are Local",                  val_boolean, TFS(&yes_no_tfs) },
            /*  28 */ { "Broadcast Address",                ipv4, NULL },
            /*  29 */ { "Perform Mask Discovery",                 val_boolean, TFS(&toggle_tfs) },
            /*  30 */ { "Mask Supplier",                    val_boolean, TFS(&yes_no_tfs) },
            /*  31 */ { "Perform Router Discover",                val_boolean, TFS(&toggle_tfs) },
            /*  32 */ { "Router Solicitation Address",            ipv4, NULL },
            /*  33 */ { "Static Route",                     special, NULL },
            /*  34 */ { "Trailer Encapsulation",                  val_boolean, TFS(&toggle_tfs) },
            /*  35 */ { "ARP Cache Timeout",                time_in_secs, NULL },
            /*  36 */ { "Ethernet Encapsulation",                 val_boolean, TFS(&toggle_tfs) },
            /*  37 */ { "TCP Default TTL",                        val_u_byte, NULL },
            /*  38 */ { "TCP Keepalive Interval",                 time_in_secs, NULL },
            /*  39 */ { "TCP Keepalive Garbage",                  val_boolean, TFS(&toggle_tfs) },
            /*  40 */ { "Network Information Service Domain",     string, NULL },
            /*  41 */ { "Network Information Service Servers",    ipv4_list, NULL },
            /*  42 */ { "Network Time Protocol Servers",          ipv4_list, NULL },
            /*  43 */ { "Vendor-Specific Information",            special, NULL },
            /*  44 */ { "NetBIOS over TCP/IP Name Server",        ipv4_list, NULL },
            /*  45 */ { "NetBIOS over TCP/IP Datagram Distribution Name Server", ipv4_list, NULL },
            /*  46 */ { "NetBIOS over TCP/IP Node Type",          val_u_byte, VALS(nbnt_vals) },
            /*  47 */ { "NetBIOS over TCP/IP Scope",        string, NULL },
            /*  48 */ { "X Window System Font Server",            ipv4_list, NULL },
            /*  49 */ { "X Window System Display Manager",        ipv4_list, NULL },
            /*  50 */ { "Requested IP Address",             ipv4, NULL },
            /*  51 */ { "IP Address Lease Time",                  time_in_secs, NULL },
            /*  52 */ { "Option Overload",                        special, NULL },
            /*  53 */ { "DHCP Message Type",                special, NULL },
            /*  54 */ { "Server Identifier",                ipv4, NULL },
            /*  55 */ { "Parameter Request List",                 special, NULL },
            /*  56 */ { "Message",                          string, NULL },
            /*  57 */ { "Maximum DHCP Message Size",        val_u_short, NULL },
            /*  58 */ { "Renewal Time Value",               time_in_secs, NULL },
            /*  59 */ { "Rebinding Time Value",             time_in_secs, NULL },
            /*  60 */ { "Vendor class identifier",                special, NULL },
            /*  61 */ { "Client identifier",                special, NULL },
            /*  62 */ { "Novell/Netware IP domain",               string, NULL },
            /*  63 */ { "Novell Options",                   special, NULL },
            /*  64 */ { "Network Information Service+ Domain",    string, NULL },
            /*  65 */ { "Network Information Service+ Servers",   ipv4_list, NULL },
            /*  66 */ { "TFTP Server Name",                       string, NULL },
            /*  67 */ { "Bootfile name",                    string, NULL },
            /*  68 */ { "Mobile IP Home Agent",             ipv4_list, NULL },
            /*  69 */ { "SMTP Server",                      ipv4_list, NULL },
            /*  70 */ { "POP3 Server",                      ipv4_list, NULL },
            /*  71 */ { "NNTP Server",                      ipv4_list, NULL },
            /*  72 */ { "Default WWW Server",               ipv4_list, NULL },
            /*  73 */ { "Default Finger Server",                  ipv4_list, NULL },
            /*  74 */ { "Default IRC Server",               ipv4_list, NULL },
            /*  75 */ { "StreetTalk Server",                ipv4_list, NULL },
            /*  76 */ { "StreetTalk Directory Assistance Server", ipv4_list, NULL },
            /*  77 */ { "User Class Information",                 opaque, NULL },
            /*  78 */ { "Directory Agent Information",            special, NULL },
            /*  79 */ { "Service Location Agent Scope",           special, NULL },
            /*  80 */ { "Naming Authority",                       opaque, NULL },
            /*  81 */ { "Client Fully Qualified Domain Name",     special, NULL },
            /*  82 */ { "Agent Information Option",                 special, NULL },
            /*  83 */ { "Unassigned",                       opaque, NULL },
            /*  84 */ { "Unassigned",                       opaque, NULL },
            /*  85 */ { "Novell Directory Services Servers",      special, NULL },
            /*  86 */ { "Novell Directory Services Tree Name",    string, NULL },
            /*  87 */ { "Novell Directory Services Context",      string, NULL },
            /*  88 */ { "IEEE 1003.1 POSIX Timezone",       opaque, NULL },
            /*  89 */ { "Fully Qualified Domain Name",            opaque, NULL },
            /*  90 */ { "Authentication",                   special, NULL },
            /*  91 */ { "Vines TCP/IP Server Option",       opaque, NULL },
            /*  92 */ { "Server Selection Option",                opaque, NULL },
            /*  93 */ { "Client System Architecture",       opaque, NULL },
            /*  94 */ { "Client Network Device Interface",        opaque, NULL },
            /*  95 */ { "Lightweight Directory Access Protocol",  opaque, NULL },
            /*  96 */ { "IPv6 Transitions",                       opaque, NULL },
            /*  97 */ { "UUID/GUID-based Client Identifier",      opaque, NULL },
            /*  98 */ { "Open Group's User Authentication",       opaque, NULL },
            /*  99 */ { "Unassigned",                       opaque, NULL },
            /* 100 */ { "Printer Name",                     opaque, NULL },
            /* 101 */ { "MDHCP multicast address",                opaque, NULL },
            /* 102 */ { "Removed/unassigned",               opaque, NULL },
            /* 103 */ { "Removed/unassigned",               opaque, NULL },
            /* 104 */ { "Removed/unassigned",               opaque, NULL },
            /* 105 */ { "Removed/unassigned",               opaque, NULL },
            /* 106 */ { "Removed/unassigned",               opaque, NULL },
            /* 107 */ { "Removed/unassigned",               opaque, NULL },
            /* 108 */ { "Swap Path Option",                       opaque, NULL },
            /* 109 */ { "Unassigned",                       opaque, NULL },
            /* 110 */ { "IPX Compability",                        opaque, NULL },
            /* 111 */ { "Unassigned",                       opaque, NULL },
            /* 112 */ { "NetInfo Parent Server Address",          ipv4_list, NULL },
            /* 113 */ { "NetInfo Parent Server Tag",        string, NULL },
            /* 114 */ { "URL",                              opaque, NULL },
            /* 115 */ { "DHCP Failover Protocol",                 opaque, NULL },
            /* 116 */ { "DHCP Auto-Configuration",                opaque, NULL },
            /* 117 */ { "Name Service Search",                    opaque, NULL },
            /* 118 */ { "Subnet Selection Option",                      opaque, NULL },
            /* 119 */ { "Domain Search",                    opaque, NULL },
            /* 120 */ { "SIP Servers",                      opaque, NULL },
            /* 121 */ { "Classless Static Route",                       opaque, NULL },
            /* 122 */ { "CableLabs Client Configuration",         opaque, NULL },
            /* 123 */ { "Unassigned",                       opaque, NULL },
            /* 124 */ { "Unassigned",                       opaque, NULL },
            /* 125 */ { "Unassigned",                       opaque, NULL },
            /* 126 */ { "Extension",                        opaque, NULL },
            /* 127 */ { "Extension",                        opaque, NULL },
            /* 128 */ { "Private",                          opaque, NULL },
            /* 129 */ { "Private",                          opaque, NULL },
            /* 130 */ { "Private",                          opaque, NULL },
            /* 131 */ { "Private",                          opaque, NULL },
            /* 132 */ { "Private",                          opaque, NULL },
            /* 133 */ { "Private",                          opaque, NULL },
            /* 134 */ { "Private",                          opaque, NULL },
            /* 135 */ { "Private",                          opaque, NULL },
            /* 136 */ { "Private",                          opaque, NULL },
            /* 137 */ { "Private",                          opaque, NULL },
            /* 138 */ { "Private",                          opaque, NULL },
            /* 139 */ { "Private",                          opaque, NULL },
            /* 140 */ { "Private",                          opaque, NULL },
            /* 141 */ { "Private",                          opaque, NULL },
            /* 142 */ { "Private",                          opaque, NULL },
            /* 143 */ { "Private",                          opaque, NULL },
            /* 144 */ { "Private",                          opaque, NULL },
            /* 145 */ { "Private",                          opaque, NULL },
            /* 146 */ { "Private",                          opaque, NULL },
            /* 147 */ { "Private",                          opaque, NULL },
            /* 148 */ { "Private",                          opaque, NULL },
            /* 149 */ { "Private",                          opaque, NULL },
            /* 150 */ { "Private",                          opaque, NULL },
            /* 151 */ { "Private",                          opaque, NULL },
            /* 152 */ { "Private",                          opaque, NULL },
            /* 153 */ { "Private",                          opaque, NULL },
            /* 154 */ { "Private",                          opaque, NULL },
            /* 155 */ { "Private",                          opaque, NULL },
            /* 156 */ { "Private",                          opaque, NULL },
            /* 157 */ { "Private",                          opaque, NULL },
            /* 158 */ { "Private",                          opaque, NULL },
            /* 159 */ { "Private",                          opaque, NULL },
            /* 160 */ { "Private",                          opaque, NULL },
            /* 161 */ { "Private",                          opaque, NULL },
            /* 162 */ { "Private",                          opaque, NULL },
            /* 163 */ { "Private",                          opaque, NULL },
            /* 164 */ { "Private",                          opaque, NULL },
            /* 165 */ { "Private",                          opaque, NULL },
            /* 166 */ { "Private",                          opaque, NULL },
            /* 167 */ { "Private",                          opaque, NULL },
            /* 168 */ { "Private",                          opaque, NULL },
            /* 169 */ { "Private",                          opaque, NULL },
            /* 170 */ { "Private",                          opaque, NULL },
            /* 171 */ { "Private",                          opaque, NULL },
            /* 172 */ { "Private",                          opaque, NULL },
            /* 173 */ { "Private",                          opaque, NULL },
            /* 174 */ { "Private",                          opaque, NULL },
            /* 175 */ { "Private",                          opaque, NULL },
            /* 176 */ { "Private",                          opaque, NULL },
            /* 177 */ { "Private",                          opaque, NULL },
            /* 178 */ { "Private",                          opaque, NULL },
            /* 179 */ { "Private",                          opaque, NULL },
            /* 180 */ { "Private",                          opaque, NULL },
            /* 181 */ { "Private",                          opaque, NULL },
            /* 182 */ { "Private",                          opaque, NULL },
            /* 183 */ { "Private",                          opaque, NULL },
            /* 184 */ { "Private",                          opaque, NULL },
            /* 185 */ { "Private",                          opaque, NULL },
            /* 186 */ { "Private",                          opaque, NULL },
            /* 187 */ { "Private",                          opaque, NULL },
            /* 188 */ { "Private",                          opaque, NULL },
            /* 189 */ { "Private",                          opaque, NULL },
            /* 190 */ { "Private",                          opaque, NULL },
            /* 191 */ { "Private",                          opaque, NULL },
            /* 192 */ { "Private",                          opaque, NULL },
            /* 193 */ { "Private",                          opaque, NULL },
            /* 194 */ { "Private",                          opaque, NULL },
            /* 195 */ { "Private",                          opaque, NULL },
            /* 196 */ { "Private",                          opaque, NULL },
            /* 197 */ { "Private",                          opaque, NULL },
            /* 198 */ { "Private",                          opaque, NULL },
            /* 199 */ { "Private",                          opaque, NULL },
            /* 200 */ { "Private",                          opaque, NULL },
            /* 201 */ { "Private",                          opaque, NULL },
            /* 202 */ { "Private",                          opaque, NULL },
            /* 203 */ { "Private",                          opaque, NULL },
            /* 204 */ { "Private",                          opaque, NULL },
            /* 205 */ { "Private",                          opaque, NULL },
            /* 206 */ { "Private",                          opaque, NULL },
            /* 207 */ { "Private",                          opaque, NULL },
            /* 208 */ { "Private",                          opaque, NULL },
            /* 209 */ { "Private",                          opaque, NULL },
            /* 210 */ { "Authentication",                   special, NULL }
      };

      /* Options whose length isn't "optlen + 2". */
      switch (code) {

      case 0:           /* Padding */
            /* check how much padding we have */
            for (i = voff + 1; i < eoff; i++ ) {
                  if (tvb_get_guint8(tvb, i) != 0) {
                        break;
                  }
            }
            i = i - voff;
            if (!first_pass) {
                  if (bp_tree != NULL) {
                        proto_tree_add_text(bp_tree, tvb, voff, i,
                            "Padding");
                  }
            }
            consumed = i;
            return consumed;
            break;

      case 255:   /* End Option */
            if (!first_pass) {
                  if (bp_tree != NULL) {
                        proto_tree_add_text(bp_tree, tvb, voff, 1,
                            "End Option");
                  }
            }
            *at_end = TRUE;
            consumed = 1;
            return consumed;
      }

      /*
       * Get the length of the option, and the number of bytes it
       * consumes (the length doesn't include the option code or
       * length bytes).
       *
       * On the first pass, check first whether we have the length
       * byte, so that we don't throw an exception; if we throw an
       * exception in the first pass, which is only checking for options
       * whose values we need in order to properly dissect the packet
       * on the second pass, we won't actually dissect the options, so
       * you won't be able to see which option had the problem.
       */
      if (first_pass) {
            if (!tvb_bytes_exist(tvb, voff+1, 1)) {
                  /*
                   * We don't have the length byte; just return 1
                   * as the number of bytes we consumed, to count
                   * the code byte.
                   */
                  return 1;
            }
      }
      optlen = tvb_get_guint8(tvb, voff+1);
      consumed = optlen + 2;

      /*
       * In the first pass, we don't put anything into the protocol
       * tree; we just check for some options we have to look at
       * in order to properly process the packet:
       *
       *    53 (DHCP message type) - if this is present, this is DHCP
       *
       *    60 (Vendor class identifier) - we need this in order to
       *       interpret the vendor-specific info
       *
       * We also check, before fetching anything, to make sure we
       * have the entire item we're fetching, so that we don't throw
       * an exception.
       */
      if (first_pass) {
            if (tvb_bytes_exist(tvb, voff+2, consumed-2)) {
                  switch (code) {

                  case 53:
                        *dhcp_type_p =
                            get_dhcp_type(tvb_get_guint8(tvb, voff+2));
                        break;

                  case 60:
                        *vendor_class_id_p =
                            tvb_get_ptr(tvb, voff+2, consumed-2);
                        break;
                  }
            }

            /*
             * We don't do anything else here.
             */
            return consumed;
      }

      /*
       * This is the second pass - if there's a protocol tree to be
       * built, we put stuff into it, otherwise we just return.
       */
      if (bp_tree == NULL) {
            /* Don't put anything in the protocol tree. */
            return consumed;
      }

      optoff = voff+2;

      text = opt[code].text;
      /* Special cases */
      switch (code) {

      case 21:    /* Policy Filter */
            if (optlen == 8) {
                  /* one IP address pair */
                  proto_tree_add_text(bp_tree, tvb, voff, consumed,
                        "Option %d: %s = %s/%s", code, text,
                        ip_to_str(tvb_get_ptr(tvb, optoff, 4)),
                        ip_to_str(tvb_get_ptr(tvb, optoff+4, 4)));
            } else {
                  /* > 1 IP address pair. Let's make a sub-tree */
                  vti = proto_tree_add_text(bp_tree, tvb, voff,
                        consumed, "Option %d: %s", code, text);
                  v_tree = proto_item_add_subtree(vti, ett_bootp_option);
                  for (i = optoff, optleft = optlen;
                      optleft > 0; i += 8, optleft -= 8) {
                        if (optleft < 8) {
                              proto_tree_add_text(v_tree, tvb, i, optleft,
                                  "Option length isn't a multiple of 8");
                              break;
                        }
                        proto_tree_add_text(v_tree, tvb, i, 8, "IP Address/Mask: %s/%s",
                              ip_to_str(tvb_get_ptr(tvb, i, 4)),
                              ip_to_str(tvb_get_ptr(tvb, i+4, 4)));
                  }
            }
            break;

      case 33:    /* Static Route */
            if (optlen == 8) {
                  /* one IP address pair */
                  proto_tree_add_text(bp_tree, tvb, voff, consumed,
                        "Option %d: %s = %s/%s", code, text,
                        ip_to_str(tvb_get_ptr(tvb, optoff, 4)),
                        ip_to_str(tvb_get_ptr(tvb, optoff+4, 4)));
            } else {
                  /* > 1 IP address pair. Let's make a sub-tree */
                  vti = proto_tree_add_text(bp_tree, tvb, voff,
                        consumed, "Option %d: %s", code, text);
                  v_tree = proto_item_add_subtree(vti, ett_bootp_option);
                  for (i = optoff, optleft = optlen; optleft > 0;
                      i += 8, optleft -= 8) {
                        if (optleft < 8) {
                              proto_tree_add_text(v_tree, tvb, i, optleft,
                                  "Option length isn't a multiple of 8");
                              break;
                        }
                        proto_tree_add_text(v_tree, tvb, i, 8,
                              "Destination IP Address/Router: %s/%s",
                              ip_to_str(tvb_get_ptr(tvb, i, 4)),
                              ip_to_str(tvb_get_ptr(tvb, i+4, 4)));
                  }
            }
            break;

      case 43:    /* Vendor-Specific Info */
            /* PXE protocol 2.1 as described in the intel specs */
            if (*vendor_class_id_p != NULL &&
                strncmp(*vendor_class_id_p, "PXEClient", strlen("PXEClient")) == 0) {
                  vti = proto_tree_add_text(bp_tree, tvb, voff,
                        consumed, "Option %d: %s (PXEClient)", code, text);
                  v_tree = proto_item_add_subtree(vti, ett_bootp_option);

                  optend = optoff + optlen;
                  while (optoff < optend) {
                        optoff = dissect_vendor_pxeclient_suboption(v_tree,
                              tvb, optoff, optend);
                  }
            } else if (*vendor_class_id_p != NULL &&
                     ((strncmp(*vendor_class_id_p, "pktc", strlen("pktc")) == 0) ||
                            (strncmp(*vendor_class_id_p, "docsis", strlen("docsis")) == 0) ||
                            (strncmp(*vendor_class_id_p, "CableHome", strlen("CableHome")) == 0))) {
                    /* CableLabs standard - see www.cablelabs.com/projects */
                    vti = proto_tree_add_text(bp_tree, tvb, voff,
                        consumed, "Option %d: %s (CableLabs)", code, text);
                  v_tree = proto_item_add_subtree(vti, ett_bootp_option);

                  optend = optoff + optlen;
                  while (optoff < optend) {
                          optoff = dissect_vendor_cablelabs_suboption(v_tree,
                              tvb, optoff, optend);
                  }
            } else {
                  proto_tree_add_text(bp_tree, tvb, voff, consumed,
                        "Option %d: %s (%d bytes)", code, text, optlen);
            }
            break;

      case 52:    /* Option Overload */
            if (optlen < 1) {
                  proto_tree_add_text(bp_tree, tvb, voff, consumed,
                        "Option %d: length isn't >= 1", code);
                  break;
            }
            byte = tvb_get_guint8(tvb, optoff);
            vti = proto_tree_add_text(bp_tree, tvb, voff, consumed,
                  "Option %d: %s = %s", code, text,
                  val_to_str(byte, opt_overload_vals,
                      "Unknown (0x%02x)"));

            /* Just in case we find an option 52 in sname or file */
            if (voff > VENDOR_INFO_OFFSET && byte >= 1 && byte <= 3) {
                  o52tree = proto_item_add_subtree(vti, ett_bootp_option);
                  if (byte == 1 || byte == 3) { /* 'file' */
                        vti = proto_tree_add_text (o52tree, tvb,
                              FILE_NAME_OFFSET, FILE_NAME_LEN,
                              "Boot file name option overload");
                        v_tree = proto_item_add_subtree(vti, ett_bootp_option);
                        o52voff = FILE_NAME_OFFSET;
                        o52eoff = FILE_NAME_OFFSET + FILE_NAME_LEN;
                        o52at_end = FALSE;
                        while (o52voff < o52eoff && !o52at_end) {
                              o52voff += bootp_option(tvb, v_tree, o52voff,
                                    o52eoff, FALSE, &o52at_end,
                                    dhcp_type_p, vendor_class_id_p);
                        }
                  }
                  if (byte == 2 || byte == 3) { /* 'sname' */
                        vti = proto_tree_add_text (o52tree, tvb,
                              SERVER_NAME_OFFSET, SERVER_NAME_LEN,
                              "Server host name option overload");
                        v_tree = proto_item_add_subtree(vti, ett_bootp_option);
                        o52voff = SERVER_NAME_OFFSET;
                        o52eoff = SERVER_NAME_OFFSET + SERVER_NAME_LEN;
                        o52at_end = FALSE;
                        while (o52voff < o52eoff && !o52at_end) {
                              o52voff += bootp_option(tvb, v_tree, o52voff,
                                    o52eoff, FALSE, &o52at_end,
                                    dhcp_type_p, vendor_class_id_p);
                        }
                  }
            }

/*          protocol = tvb_get_guint8(tvb, optoff);
            proto_tree_add_text(v_tree, tvb, optoff, 1, "Protocol: %s (%u)",
                            val_to_str(protocol, authen_protocol_vals, "Unknown"),
                            protocol); */
            break;

      case 53:    /* DHCP Message Type */
            if (optlen != 1) {
                  proto_tree_add_text(bp_tree, tvb, voff, consumed,
                        "Option %d: length isn't 1", code);
                  break;
            }
            proto_tree_add_text(bp_tree, tvb, voff, 3, "Option %d: %s = DHCP %s",
                  code, text, get_dhcp_type(tvb_get_guint8(tvb, optoff)));
            break;

      case 55:    /* Parameter Request List */
            vti = proto_tree_add_text(bp_tree, tvb, voff,
                  consumed, "Option %d: %s", code, text);
            v_tree = proto_item_add_subtree(vti, ett_bootp_option);
            for (i = 0; i < optlen; i++) {
                  byte = tvb_get_guint8(tvb, optoff+i);
                  if (byte < array_length(opt)) {
                        proto_tree_add_text(v_tree, tvb, optoff+i, 1, "%d = %s",
                                    byte, opt[byte].text);
                  } else {
                        proto_tree_add_text(vti, tvb, optoff+i, 1,
                              "Unknown Option Code: %d", byte);
                  }
            }
            break;

      case 60:    /* Vendor class identifier */
            /*
             * XXX - RFC 2132 says this is a string of octets;
             * should we check for non-printables?
             */
            vti = proto_tree_add_text(bp_tree, tvb, voff, consumed,
                  "Option %d: %s = \"%s\"", code, text,
                  tvb_format_stringzpad(tvb, optoff, consumed-2));
            if ((tvb_memeql(tvb, optoff, PACKETCABLE_MTA_CAP10, strlen(PACKETCABLE_MTA_CAP10)) == 0) ||
                  (tvb_memeql(tvb, optoff, PACKETCABLE_MTA_CAP15, strlen(PACKETCABLE_MTA_CAP10)) == 0)) {
                  v_tree = proto_item_add_subtree(vti, ett_bootp_option);
                  dissect_packetcable_mta_cap(v_tree, tvb, optoff, optlen);
            } else if (tvb_memeql(tvb, optoff, PACKETCABLE_CM_CAP11, strlen(PACKETCABLE_CM_CAP11)) == 0 ||
                        tvb_memeql(tvb, optoff, PACKETCABLE_CM_CAP20, strlen(PACKETCABLE_CM_CAP20)) == 0 ) {
                  v_tree = proto_item_add_subtree(vti, ett_bootp_option);
                  dissect_docsis_cm_cap(v_tree, tvb, optoff, optlen);
            }
            break;

      case 61:    /* Client Identifier */
            if (optlen > 0)
                  byte = tvb_get_guint8(tvb, optoff);
            else
                  byte = 0;

            /* We *MAY* use hwtype/hwaddr. If we have 7 bytes, I'll
               guess that the first is the hwtype, and the last 6
               are the hw addr */
            /* See http://www.iana.org/assignments/arp-parameters */
            /* RFC2132 9.14 Client-identifier has the following to say:
               A hardware type of 0 (zero) should be used when the value
               field contains an identifier other than a hardware address
               (e.g. a fully qualified domain name). */

            if (optlen == 7 && byte > 0 && byte < 48) {
                  vti = proto_tree_add_text(bp_tree, tvb, voff,
                        consumed, "Option %d: %s", code, text);
                  v_tree = proto_item_add_subtree(vti, ett_bootp_option);
                  proto_tree_add_text(v_tree, tvb, optoff, 1,
                        "Hardware type: %s",
                        arphrdtype_to_str(byte,
                              "Unknown (0x%02x)"));
                  if (byte == ARPHRD_ETHER || byte == ARPHRD_IEEE802)
                        proto_tree_add_item(v_tree,
                            hf_bootp_hw_ether_addr, tvb, optoff+1, 6,
                            FALSE);
                  else
                        proto_tree_add_text(v_tree, tvb, optoff+1, 6,
                              "Client hardware address: %s",
                              arphrdaddr_to_str(tvb_get_ptr(tvb, optoff+1, 6),
                              6, byte));
            } else {
                  /* otherwise, it's opaque data */
                  proto_tree_add_text(bp_tree, tvb, voff, consumed,
                        "Option %d: %s (%d bytes)", code, text, optlen);
            }
            break;

      case 63:    /* NetWare/IP options (RFC 2242) */
            vti = proto_tree_add_text(bp_tree, tvb, voff,
                consumed, "Option %d: %s", code, text);
            v_tree = proto_item_add_subtree(vti, ett_bootp_option);

            optend = optoff + optlen;
            while (optoff < optend)
                  optoff = dissect_netware_ip_suboption(v_tree, tvb, optoff, optend);
            break;

      case 78:    /* SLP Directory Agent Option RFC2610 Added by Greg Morris (gmorris@novell.com)*/
            if (optlen < 1) {
                  proto_tree_add_text(bp_tree, tvb, voff, consumed,
                        "Option %d: length isn't >= 1", code);
                  break;
            }
            optleft = optlen;
            byte = tvb_get_guint8(tvb, optoff);
            vti = proto_tree_add_text(bp_tree, tvb, voff, consumed,
                        "Option %d: %s = %s", code, text,
                        val_to_str(byte, slpda_vals,
                            "Unknown (0x%02x)"));
            optoff++;
            optleft--;
            if (byte == 0x80) {
                  if (optleft == 0)
                        break;
                  optoff++;
                  optleft--;
            }
            v_tree = proto_item_add_subtree(vti, ett_bootp_option);
            for (i = optoff; optleft > 0; i += 4, optleft -= 4) {
                  if (optleft < 4) {
                        proto_tree_add_text(v_tree, tvb, i, optleft,
                            "Option length isn't a multiple of 4");
                        break;
                  }
                  proto_tree_add_text(v_tree, tvb, i, 4, "SLPDA Address: %s",
                      ip_to_str(tvb_get_ptr(tvb, i, 4)));
            }
            break;

      case 79:    /* SLP Service Scope Option RFC2610 Added by Greg Morris (gmorris@novell.com)*/
            byte = tvb_get_guint8(tvb, optoff);
            vti = proto_tree_add_text(bp_tree, tvb, voff, consumed,
                        "Option %d: %s = %s", code, text,
                        val_to_str(byte, slp_scope_vals,
                            "Unknown (0x%02x)"));
            v_tree = proto_item_add_subtree(vti, ett_bootp_option);
            optoff++;
            optleft = optlen - 1;
            proto_tree_add_text(v_tree, tvb, optoff, optleft,
                "%s = \"%s\"", text,
                tvb_format_stringzpad(tvb, optoff, optleft));
            break;

      case 81:    /* Client Fully Qualified Domain Name */
            if (optlen < 3) {
                  proto_tree_add_text(bp_tree, tvb, voff, consumed,
                        "Option %d: length isn't >= 3", code);
                  break;
            }
            vti = proto_tree_add_text(bp_tree, tvb, voff, consumed,
                        "Option %d: FQDN", code);
            v_tree = proto_item_add_subtree(vti, ett_bootp_option);
            fqdn_flags = tvb_get_guint8(tvb, optoff);
            ft = proto_tree_add_text(v_tree, tvb, optoff, 1, "Flags: 0x%02x", fqdn_flags);
            flags_tree = proto_item_add_subtree(ft, ett_bootp_fqdn);
            proto_tree_add_item(flags_tree, hf_bootp_fqdn_mbz, tvb, optoff, 1, FALSE);
            proto_tree_add_item(flags_tree, hf_bootp_fqdn_n, tvb, optoff, 1, FALSE);
            proto_tree_add_item(flags_tree, hf_bootp_fqdn_e, tvb, optoff, 1, FALSE);
            proto_tree_add_item(flags_tree, hf_bootp_fqdn_o, tvb, optoff, 1, FALSE);
            proto_tree_add_item(flags_tree, hf_bootp_fqdn_s, tvb, optoff, 1, FALSE);
            /* XXX: use code from packet-dns for return code decoding */
            proto_tree_add_item(v_tree, hf_bootp_fqdn_rcode1, tvb, optoff+1, 1, FALSE);
            /* XXX: use code from packet-dns for return code decoding */
            proto_tree_add_item(v_tree, hf_bootp_fqdn_rcode2, tvb, optoff+2, 1, FALSE);
            if (optlen > 3) {
                  if (fqdn_flags & F_FQDN_E) {
                        /* XXX: use code from packet-dns for binary encoded name */
                        proto_tree_add_item(v_tree, hf_bootp_fqdn_name,
                            tvb, optoff+3, optlen-3, FALSE);

                  } else {
                        proto_tree_add_item(v_tree, hf_bootp_fqdn_asciiname,
                            tvb, optoff+3, optlen-3, FALSE);
                  }
            }
            break;

      case 82:        /* Relay Agent Information Option */
            vti = proto_tree_add_text(bp_tree, tvb, voff, consumed,
                                "Option %d: %s (%d bytes)",
                                code, text, optlen);
            v_tree = proto_item_add_subtree(vti, ett_bootp_option);
            optend = optoff + optlen;
            while (optoff < optend)
                  optoff = bootp_dhcp_decode_agent_info(v_tree, tvb, optoff, optend);
            break;

      case 85:        /* Novell Servers (RFC 2241) */
            /* Option 85 can be sent as a string */
            /* Added by Greg Morris (gmorris[AT]novell.com) */
            if (novell_string) {
                  proto_tree_add_text(bp_tree, tvb, voff, consumed,
                      "Option %d: %s = \"%s\"", code, text,
                      tvb_format_stringzpad(tvb, optoff, optlen));
            } else {
                  if (optlen == 4) {
                        /* one IP address */
                        proto_tree_add_text(bp_tree, tvb, voff, consumed,
                              "Option %d: %s = %s", code, text,
                              ip_to_str(tvb_get_ptr(tvb, optoff, 4)));
                  } else {
                        /* > 1 IP addresses. Let's make a sub-tree */
                        vti = proto_tree_add_text(bp_tree, tvb, voff,
                              consumed, "Option %d: %s", code, text);
                        v_tree = proto_item_add_subtree(vti, ett_bootp_option);
                        for (i = optoff, optleft = optlen; optleft > 0;
                            i += 4, optleft -= 4) {
                              if (optleft < 4) {
                                    proto_tree_add_text(v_tree, tvb, i, optleft,
                                        "Option length isn't a multiple of 4");
                                    break;
                              }
                              proto_tree_add_text(v_tree, tvb, i, 4, "IP Address: %s",
                                    ip_to_str(tvb_get_ptr(tvb, i, 4)));
                        }
                  }
            }
              break;

      case 90:    /* DHCP Authentication */
      case 210:   /* Was this used for authentication at one time? */
            if (optlen < 11) {
                  proto_tree_add_text(bp_tree, tvb, voff, consumed,
                        "Option %d: length isn't >= 11", code);
                  break;
            }
            vti = proto_tree_add_text(bp_tree, tvb, voff,
                  consumed, "Option %d: %s", code, text);
            v_tree = proto_item_add_subtree(vti, ett_bootp_option);

            optleft = optlen;
            protocol = tvb_get_guint8(tvb, optoff);
            proto_tree_add_text(v_tree, tvb, optoff, 1, "Protocol: %s (%u)",
                            val_to_str(protocol, authen_protocol_vals, "Unknown"),
                            protocol);
            optoff++;
            optleft--;

            algorithm = tvb_get_guint8(tvb, optoff);
            switch (protocol) {

            case AUTHEN_PROTO_DELAYED_AUTHEN:
                  proto_tree_add_text(v_tree, tvb, optoff, 1,
                            "Algorithm: %s (%u)",
                            val_to_str(algorithm, authen_da_algo_vals, "Unknown"),
                            algorithm);
                  break;

            default:
                  proto_tree_add_text(v_tree, tvb, optoff, 1,
                            "Algorithm: %u", algorithm);
                  break;
            }
            optoff++;
            optleft--;

            rdm = tvb_get_guint8(tvb, optoff);
            proto_tree_add_text(v_tree, tvb, optoff, 1,
                            "Replay Detection Method: %s (%u)",
                            val_to_str(rdm, authen_rdm_vals, "Unknown"),
                            rdm);
            optoff++;
            optleft--;

            switch (rdm) {

            case AUTHEN_RDM_MONOTONIC_COUNTER:
                  proto_tree_add_text(v_tree, tvb, optoff, 8,
                            "Replay Detection Value: %" PRIx64,
                            tvb_get_ntoh64(tvb, optoff));
                  break;

            default:
                  proto_tree_add_text(v_tree, tvb, optoff, 8,
                            "Replay Detection Value: %s",
                            tvb_bytes_to_str(tvb, optoff, 8));
                  break;
            }
            optoff += 8;
            optleft -= 8;

            switch (protocol) {

            case AUTHEN_PROTO_DELAYED_AUTHEN:
                  switch (algorithm) {

                  case AUTHEN_DELAYED_ALGO_HMAC_MD5:
                        if (optlen < 31) {
                              proto_tree_add_text(bp_tree, tvb, 0, 0,
                                    "Option %d: length isn't >= 31", code);
                              break;
                        }
                        proto_tree_add_text(v_tree, tvb, optoff, 4,
                              "Secret ID: 0x%08x",
                              tvb_get_ntohl(tvb, optoff));
                        optoff += 4;
                        optleft -= 4;
                        proto_tree_add_text(v_tree, tvb, optoff, 16,
                              "HMAC MD5 Hash: %s",
                              tvb_bytes_to_str(tvb, optoff, 16));
                        break;

                  default:
                        if (optleft == 0)
                              break;
                        proto_tree_add_text(v_tree, tvb, optoff, optleft,
                              "Authentication Information: %s",
                              tvb_bytes_to_str(tvb, optoff, optleft));
                        break;
                  }
                  break;

            default:
                  if (optleft == 0)
                        break;
                  proto_tree_add_text(v_tree, tvb, optoff, optleft,
                        "Authentication Information: %s",
                        tvb_bytes_to_str(tvb, optoff, optleft));
                  break;
            }
            break;

      default:    /* not special */
            /* The PacketCable CCC option number can vary.  If this is a CCC option,
               handle it and skip the "opaque" case below.
             */
            if (code == pkt_ccc_option) {
                  skip_opaque = TRUE;
                  vti = proto_tree_add_text(bp_tree, tvb, voff, consumed,
                                      "Option %d: CableLabs Client Configuration (%d bytes)",
                                      code, optlen);
                  v_tree = proto_item_add_subtree(vti, ett_bootp_option);
                  optend = optoff + optlen;
                  while (optoff < optend) {
                        switch (pkt_ccc_protocol_version) {
                              case PACKETCABLE_CCC_I05:
                                    optoff = dissect_packetcable_i05_ccc(v_tree, tvb, optoff, optend);
                                    break;
                              case PACKETCABLE_CCC_DRAFT5:
                              case PACKETCABLE_CCC_RFC_3495:
                                    optoff = dissect_packetcable_ietf_ccc(v_tree, tvb, optoff, optend, pkt_ccc_protocol_version);
                                    break;
                              default: /* XXX Should we do something here? */
                                    break;
                        }
                  }
            }

            break;
      }

      /* Normal cases */
      if (code < array_length(opt)) {
            text = opt[code].text;
            ftype = opt[code].ftype;

            if (ftype == special)
                  return consumed;
            if (ftype == opaque) {
                  if (skip_opaque) /* Currently used by PacketCable CCC */
                        return consumed;
            }

            vti = proto_tree_add_text(bp_tree, tvb, voff, consumed,
                "Option %d: %s", code, text);
            switch (ftype) {

            case ipv4:
                  if (optlen != 4) {
                        proto_item_append_string(vti,
                            " - length isn't 4");
                        break;
                  }
                  proto_item_append_text(vti, " = %s",
                        ip_to_str(tvb_get_ptr(tvb, optoff, 4)));
                  break;

            case ipv4_list:
                  if (optlen == 4) {
                        /* one IP address */
                        proto_item_append_text(vti, " = %s",
                              ip_to_str(tvb_get_ptr(tvb, optoff, 4)));
                  } else {
                        /* > 1 IP addresses. Let's make a sub-tree */
                        v_tree = proto_item_add_subtree(vti, ett_bootp_option);
                        for (i = optoff, optleft = optlen; optleft > 0;
                            i += 4, optleft -= 4) {
                              if (optleft < 4) {
                                    proto_tree_add_text(v_tree, tvb, i, voff + consumed - i,
                                        "Option length isn't a multiple of 4");
                                    break;
                              }
                              proto_tree_add_text(v_tree, tvb, i, 4, "IP Address: %s",
                                    ip_to_str(tvb_get_ptr(tvb, i, 4)));
                        }
                  }
                  break;

            case string:
                  /* Fix for non null-terminated string supplied by
                   * John Lines <John.Lines[AT]aeat.co.uk>
                   */
                  proto_item_append_text(vti, " = \"%s\"",
                              tvb_format_stringzpad(tvb, optoff, consumed-2));
                  break;

            case opaque:
                  proto_item_append_text(vti, " (%d bytes)", optlen);
                  break;

            case val_boolean:
                  if (optlen != 1) {
                        proto_item_append_text(vti,
                            " - length isn't 1");
                        break;
                  }
                  tfs = (const struct true_false_string *) opt[code].data;
                  i = tvb_get_guint8(tvb, optoff);
                  if (i != 0 && i != 1) {
                        proto_item_append_text(vti,
                            " = Invalid Value %d", i);
                  } else {
                        proto_item_append_text(vti, " = %s",
                            i == 0 ? tfs->false_string : tfs->true_string);
                  }
                  break;

            case val_u_byte:
                  if (optlen != 1) {
                        proto_item_append_text(vti,
                            " - length isn't 1");
                        break;
                  }
                  vs = (const value_string *) opt[code].data;
                  byte = tvb_get_guint8(tvb, optoff);
                  if (vs != NULL) {
                        proto_item_append_text(vti, " = %s",
                            val_to_str(byte, vs, "Unknown (%u)"));
                  } else
                        proto_item_append_text(vti, " = %u", byte);
                  break;

            case val_u_short:
                  if (optlen != 2) {
                        proto_item_append_text(vti,
                            " - length isn't 2");
                        break;
                  }
                  proto_item_append_text(vti, " = %u",
                      tvb_get_ntohs(tvb, optoff));
                  break;

            case val_u_short_list:
                  if (optlen == 2) {
                        /* one gushort */
                        proto_item_append_text(vti, " = %u",
                            tvb_get_ntohs(tvb, optoff));
                  } else {
                        /* > 1 gushort */
                        v_tree = proto_item_add_subtree(vti, ett_bootp_option);
                        for (i = optoff, optleft = optlen; optleft > 0;
                            i += 2, optleft -= 2) {
                              if (optleft < 2) {
                                    proto_tree_add_text(v_tree, tvb, i, voff + consumed - i,
                                        "Option length isn't a multiple of 2");
                                    break;
                              }
                              proto_tree_add_text(v_tree, tvb, i, 4, "Value: %u",
                                    tvb_get_ntohs(tvb, i));
                        }
                  }
                  break;

            case val_u_long:
                  if (optlen != 4) {
                        proto_item_append_text(vti,
                            " - length isn't 4");
                        break;
                  }
                  proto_item_append_text(vti, " = %u",
                      tvb_get_ntohl(tvb, optoff));
                  break;

            case time_in_secs:
                  if (optlen != 4) {
                        proto_item_append_text(vti,
                            " - length isn't 4");
                        break;
                  }
                  time_secs = tvb_get_ntohl(tvb, optoff);
                  proto_item_append_text(vti, " = %s",
                      ((time_secs == 0xffffffff) ?
                        "infinity" :
                        time_secs_to_str(time_secs)));
                  break;

            default:
                  proto_item_append_text(vti, " (%d bytes)", optlen);
                  break;
            }
      } else {
            proto_tree_add_text(bp_tree, tvb, voff, consumed,
                        "Unknown Option Code: %d (%d bytes)", code, optlen);
      }

      return consumed;
}

static int
bootp_dhcp_decode_agent_info(proto_tree *v_tree, tvbuff_t *tvb, int optoff,
    int optend)
{
      int suboptoff = optoff;
      guint8 subopt;
      guint8 subopt_len;

      subopt = tvb_get_guint8(tvb, optoff);
      suboptoff++;

      if (suboptoff >= optend) {
            proto_tree_add_text(v_tree, tvb, optoff, 1,
                  "Suboption %d: no room left in option for suboption length",
                  subopt);
            return (optend);
      }
      subopt_len = tvb_get_guint8(tvb, suboptoff);
      suboptoff++;

      if (suboptoff+subopt_len >= optend) {
            proto_tree_add_text(v_tree, tvb, optoff, optend-optoff,
                  "Suboption %d: no room left in option for suboption value",
                  subopt);
            return (optend);
      }
      switch (subopt) {
      case 1:
            proto_tree_add_text(v_tree, tvb, optoff, subopt_len + 2,
                            "Agent Circuit ID: %s",
                            tvb_bytes_to_str(tvb, suboptoff, subopt_len));
            break;
      case 2:
            proto_tree_add_text(v_tree, tvb, optoff, subopt_len + 2,
                            "Agent Remote ID: %s",
                            tvb_bytes_to_str(tvb, suboptoff, subopt_len));
            break;
      default:
            proto_tree_add_text(v_tree, tvb, optoff, subopt_len + 2,
                            "Invalid agent suboption %d (%d bytes)",
                            subopt, subopt_len);
            break;
      }
      optoff += (subopt_len + 2);
      return optoff;
}

static int
dissect_vendor_pxeclient_suboption(proto_tree *v_tree, tvbuff_t *tvb,
    int optoff, int optend)
{
      int suboptoff = optoff;
      guint8 subopt;
      guint8 subopt_len;
      int suboptleft;
      proto_tree *o43pxeclient_v_tree;
      proto_item *vti;

      static struct opt_info o43pxeclient_opt[]= {
            /* 0 */ {"nop", special, NULL},     /* dummy */
            /* 1 */ {"PXE mtftp IP", ipv4_list, NULL},
            /* 2 */ {"PXE mtftp client port", val_u_le_short, NULL},
            /* 3 */ {"PXE mtftp server port",val_u_le_short, NULL},
            /* 4 */ {"PXE mtftp timeout", val_u_byte, NULL},
            /* 5 */ {"PXE mtftp delay", val_u_byte, NULL},
            /* 6 */ {"PXE discovery control", val_u_byte, NULL},
                  /*
                   * Correct: b0 (lsb): disable broadcast discovery
                   *    b1: disable multicast discovery
                   *    b2: only use/accept servers in boot servers
                   *    b3: download bootfile without prompt/menu/disc
                   */
            /* 7 */ {"PXE multicast address", ipv4_list, NULL},
            /* 8 */ {"PXE boot servers", special, NULL},
            /* 9 */ {"PXE boot menu", special, NULL},
            /* 10 */ {"PXE menu prompt", special, NULL},
            /* 11 */ {"PXE multicast address alloc", special, NULL},
            /* 12 */ {"PXE credential types", special, NULL},
            /* 71 {"PXE boot item", special, NULL}, */
            /* 255 {"PXE end options", special, NULL} */
      };

      subopt = tvb_get_guint8(tvb, suboptoff);
      suboptoff++;

      if (subopt == 0) {
            proto_tree_add_text(v_tree, tvb, optoff, 1, "Padding");
                return (suboptoff);
      } else if (subopt == 255) {   /* End Option */
            proto_tree_add_text(v_tree, tvb, optoff, 1, "End PXEClient option");
            /* Make sure we skip any junk left this option */
            return (optend);
      }

      if (suboptoff >= optend) {
            proto_tree_add_text(v_tree, tvb, optoff, 1,
                  "Suboption %d: no room left in option for suboption length",
                  subopt);
            return (optend);
      }
      subopt_len = tvb_get_guint8(tvb, suboptoff);
      suboptoff++;

      if (suboptoff+subopt_len >= optend) {
            proto_tree_add_text(v_tree, tvb, optoff, optend-optoff,
                  "Suboption %d: no room left in option for suboption value",
                  subopt);
            return (optend);
      }
      if ( subopt == 71 ) {   /* 71 {"PXE boot item", special} */
            /* case special */
            /* I may need to decode that properly one day */
            proto_tree_add_text(v_tree, tvb, optoff, subopt_len+2,
                  "Suboption %d: %s (%d byte%s)" ,
                  subopt, "PXE boot item",
                  subopt_len, plurality(subopt_len, "", "s"));
      } else if ((subopt < 1) || (subopt > array_length(o43pxeclient_opt))) {
            proto_tree_add_text(v_tree, tvb, optoff, subopt_len+2,
                  "Unknown suboption %d (%d byte%s)", subopt, subopt_len,
                  plurality(subopt_len, "", "s"));
      } else {
            switch (o43pxeclient_opt[subopt].ftype) {

            case special:
                  /* I may need to decode that properly one day */
                  proto_tree_add_text(v_tree, tvb, optoff, subopt_len+2,
                        "Suboption %d: %s (%d byte%s)",
                        subopt, o43pxeclient_opt[subopt].text,
                        subopt_len, plurality(subopt_len, "", "s"));
                  break;

            case ipv4_list:
                  if (subopt_len == 4) {
                        /* one IP address */
                        proto_tree_add_text(v_tree, tvb, optoff, 6,
                            "Suboption %d : %s = %s",
                            subopt, o43pxeclient_opt[subopt].text,
                            ip_to_str(tvb_get_ptr(tvb, suboptoff, 4)));
                  } else {
                        /* > 1 IP addresses. Let's make a sub-tree */
                        vti = proto_tree_add_text(v_tree, tvb, optoff,
                            subopt_len+2, "Suboption %d: %s",
                            subopt, o43pxeclient_opt[subopt].text);
                        o43pxeclient_v_tree = proto_item_add_subtree(vti, ett_bootp_option);
                        for (suboptleft = subopt_len; suboptleft > 0;
                            suboptoff += 4, suboptleft -= 4) {
                              if (suboptleft < 4) {
                                    proto_tree_add_text(o43pxeclient_v_tree,
                                        tvb, suboptoff, suboptleft,
                                        "Suboption length isn't a multiple of 4");
                                    break;
                              }
                              proto_tree_add_text(o43pxeclient_v_tree,
                                  tvb, suboptoff, 4, "IP Address: %s",
                                  ip_to_str(tvb_get_ptr(tvb, suboptoff, 4)));
                        }
                  }
                  break;

/* XXX            case string:
                  proto_tree_add_text(v_tree, tvb, optoff, subopt_len+2,
                        "Suboption %d: %s", subopt, o43pxeclient_opt[subopt].text);
                  break;
   XXX */

            case val_u_byte:
                  if (subopt_len != 1) {
                        proto_tree_add_text(v_tree, tvb, optoff, subopt_len+2,
                              "Suboption %d: suboption length isn't 1", subopt);
                        break;
                  }
                  proto_tree_add_text(v_tree, tvb, optoff, 3, "Suboption %d: %s = %u",
                      subopt, o43pxeclient_opt[subopt].text,
                      tvb_get_guint8(tvb, suboptoff));
                  break;

            case val_u_le_short:
                  if (subopt_len != 2) {
                        proto_tree_add_text(v_tree, tvb, optoff, subopt_len+2,
                              "Suboption %d: suboption length isn't 2", subopt);
                        break;
                  }
                  proto_tree_add_text(v_tree, tvb, optoff, 4, "Suboption %d: %s = %u",
                      subopt, o43pxeclient_opt[subopt].text,
                      tvb_get_letohs(tvb, suboptoff));
                  break;

            default:
                  proto_tree_add_text(v_tree, tvb, optoff, subopt_len+2,"ERROR, please report: Unknown subopt type handler %d", subopt);
                  break;
            }
      }
      optoff += (subopt_len + 2);
      return optoff;
}


static int
dissect_vendor_cablelabs_suboption(proto_tree *v_tree, tvbuff_t *tvb,
    int optoff, int optend)
{
      int suboptoff = optoff;
      guint8 subopt, byte_val;
      guint8 subopt_len;

      static struct opt_info o43cablelabs_opt[]= {
            /* 0 */ {"nop", special, NULL},     /* dummy */
            /* 1 */ {"Suboption Request List", string, NULL},
            /* 2 */ {"Device Type", string, NULL},
            /* 3 */ {"eSAFE Types", string, NULL},
            /* 4 */ {"Serial Number", string, NULL},
            /* 5 */ {"Hardware Version", string, NULL},
            /* 6 */ {"Software Version", string, NULL},
            /* 7 */ {"Boot ROM version", string, NULL},
            /* 8 */ {"Organizationally Unique Identifier", special, NULL},
            /* 9 */ {"Model Number", string, NULL},
            /* 10 */ {"Vendor Name", string, NULL},
            /* *** 11-30: CableHome *** */
            /* 11 */ {"Address Realm", special, NULL},
            /* 12 */ {"CM/PS System Description", string, NULL},
            /* 13 */ {"CM/PS Firmware Revision", string, NULL},
            /* 14 */ {"Firewall Policy File Version", string, NULL},
            /* 15 */ {"Unassigned (CableHome)", special, NULL},
            /* 16 */ {"Unassigned (CableHome)", special, NULL},
            /* 17 */ {"Unassigned (CableHome)", special, NULL},
            /* 18 */ {"Unassigned (CableHome)", special, NULL},
            /* 19 */ {"Unassigned (CableHome)", special, NULL},
            /* 20 */ {"Unassigned (CableHome)", special, NULL},
            /* 21 */ {"Unassigned (CableHome)", special, NULL},
            /* 22 */ {"Unassigned (CableHome)", special, NULL},
            /* 23 */ {"Unassigned (CableHome)", special, NULL},
            /* 24 */ {"Unassigned (CableHome)", special, NULL},
            /* 25 */ {"Unassigned (CableHome)", special, NULL},
            /* 26 */ {"Unassigned (CableHome)", special, NULL},
            /* 27 */ {"Unassigned (CableHome)", special, NULL},
            /* 28 */ {"Unassigned (CableHome)", special, NULL},
            /* 29 */ {"Unassigned (CableHome)", special, NULL},
            /* 30 */ {"Unassigned (CableHome)", special, NULL},
            /* *** 31-50: PacketCable *** */
            /* 31 */ {"MTA MAC Address", special, NULL},
            /* 32 */ {"Correlation ID", string, NULL},
            /* 33-50 {"Unassigned (PacketCable)", special, NULL}, */
            /* *** 51-127: CableLabs *** */
            /* 51-127 {"Unassigned (CableLabs)", special, NULL}, */
            /* *** 128-254: Vendors *** */
            /* 128-254 {"Unassigned (Vendors)", special, NULL}, */
            /* 255 {"end options", special, NULL} */
      };

      static const value_string cablehome_subopt11_vals[] = {
            { 1, "PS WAN-Man" },
            { 2, "PS WAN-Data" },
            { 0, NULL }
      };

      subopt = tvb_get_guint8(tvb, suboptoff);
      suboptoff++;

      if (subopt == 0) {
            proto_tree_add_text(v_tree, tvb, optoff, 1, "Padding");
                return (suboptoff);
      } else if (subopt == 255) {   /* End Option */
            proto_tree_add_text(v_tree, tvb, optoff, 1, "End CableLabs option");
            /* Make sure we skip any junk left this option */
            return (optend);
      }

      if (suboptoff >= optend) {
            proto_tree_add_text(v_tree, tvb, optoff, 1,
                  "Suboption %d: no room left in option for suboption length",
                  subopt);
            return (optend);
      }
      subopt_len = tvb_get_guint8(tvb, suboptoff);
      suboptoff++;

      if (suboptoff+subopt_len >= optend) {
            proto_tree_add_text(v_tree, tvb, optoff, optend-optoff,
                  "Suboption %d: no room left in option for suboption value",
                  subopt);
            return (optend);
      }
      if ( (subopt < 1 ) || (subopt > array_length(o43cablelabs_opt)) ) {
            proto_tree_add_text(v_tree, tvb, optoff, subopt_len+2,
                  "Suboption %d: Unassigned (%d byte%s)", subopt, subopt_len,
                  plurality(subopt_len, "", "s"));
      } else {
            switch (o43cablelabs_opt[subopt].ftype) {

            case special:
                  if ( subopt == 8 ) {    /* OUI */
                        if (subopt_len != 3) {
                              proto_tree_add_text(v_tree, tvb, optoff, subopt_len+2,
                                    "Suboption %d: suboption length isn't 3", subopt);
                              break;
                        }
                        proto_tree_add_text(v_tree, tvb, optoff, subopt_len+2,
                              "Suboption %d: OUI = %s",
                              subopt, bytes_to_str_punct(tvb_get_ptr(tvb, suboptoff, 3), 3, ':'));
                  } else if ( subopt == 11 ) { /* Address Realm */
                        if (subopt_len != 1) {
                              proto_tree_add_text(v_tree, tvb, optoff, subopt_len+2,
                                    "Suboption %d: suboption length isn't 1", subopt);
                              break;
                        }
                        byte_val = tvb_get_guint8(tvb, suboptoff);
                        proto_tree_add_text(v_tree, tvb, optoff, subopt_len+2,
                              "Suboption %d: %s = %s (0x%02x)",
                              subopt, o43cablelabs_opt[subopt].text,
                              val_to_str(byte_val, cablehome_subopt11_vals, "Unknown"), byte_val);
                  } else if ( subopt == 31 ) { /* MTA MAC address */
                        if (subopt_len != 6) {
                              proto_tree_add_text(v_tree, tvb, optoff, subopt_len+2,
                                    "Suboption %d: suboption length isn't 6", subopt);
                              break;
                        }
                        proto_tree_add_text(v_tree, tvb, optoff, subopt_len+2,
                              "Suboption %d: %s = %s",
                              subopt,  o43cablelabs_opt[subopt].text,
                              bytes_to_str_punct(tvb_get_ptr(tvb, suboptoff, 6), 6, ':'));
                  } else {
                        proto_tree_add_text(v_tree, tvb, optoff, subopt_len+2,
                              "Suboption %d: %s (%d byte%s)" ,
                              subopt, o43cablelabs_opt[subopt].text,
                              subopt_len, plurality(subopt_len, "", "s"));
                  }
                  break;

            case string:
                  proto_tree_add_text(v_tree, tvb, optoff, subopt_len+2,
                        "Suboption %d: %s = \"%s\"", subopt,
                        o43cablelabs_opt[subopt].text,
                        tvb_format_stringzpad(tvb, optoff, subopt_len));
                  break;

            case bytes:
                  proto_tree_add_text(v_tree, tvb, optoff, subopt_len+2,
                        "Suboption %d: %s = 0x%s", subopt,
                        o43cablelabs_opt[subopt].text,
                        tvb_bytes_to_str(tvb, suboptoff, subopt_len));
                  break;

            default:
                  proto_tree_add_text(v_tree, tvb, optoff, subopt_len+2,"ERROR, please report: Unknown subopt type handler %d", subopt);
                  break;
            }
      }
      optoff += (subopt_len + 2);
      return optoff;
}



static int
dissect_netware_ip_suboption(proto_tree *v_tree, tvbuff_t *tvb,
    int optoff, int optend)
{
      int suboptoff = optoff;
      guint8 subopt;
      guint8 subopt_len;
      int suboptleft;
      const struct true_false_string *tfs;
      int i;
      proto_tree *o63_v_tree;
      proto_item *vti;

      static struct opt_info o63_opt[]= {
            /* 0 */ {"",none,NULL},
            /* 1 */ {"NWIP does not exist on subnet",presence,NULL},
            /* 2 */ {"NWIP exist in options area",presence,NULL},
            /* 3 */ {"NWIP exists in sname/file",presence,NULL},
            /* 4 */ {"NWIP exists,but too big",presence,NULL},
            /* 5 */ {"Broadcast for nearest Netware server",val_boolean,TFS(&yes_no_tfs)},
            /* 6 */ {"Preferred DSS server",ipv4_list,NULL},
            /* 7 */ {"Nearest NWIP server",ipv4_list,NULL},
            /* 8 */ {"Autoretries",val_u_byte,NULL},
            /* 9 */ {"Autoretry delay,secs",val_u_byte,NULL},
            /* 10*/ {"Support NetWare/IP v1.1",val_boolean,TFS(&yes_no_tfs)},
            /* 11*/ {"Primary DSS",ipv4,NULL}
      };

      subopt = tvb_get_guint8(tvb, optoff);
      suboptoff++;

      if (suboptoff >= optend) {
            proto_tree_add_text(v_tree, tvb, optoff, 1,
                  "Suboption %d: no room left in option for suboption length",
                  subopt);
            return (optend);
      }
      subopt_len = tvb_get_guint8(tvb, suboptoff);
      suboptoff++;

      if (subopt > array_length(o63_opt)) {
            proto_tree_add_text(v_tree, tvb, optoff, subopt_len + 2, "Unknown suboption %d", subopt);
      } else {
            switch (o63_opt[subopt].ftype) {

            case presence:
                  if (subopt_len != 0) {
                        proto_tree_add_text(v_tree, tvb, optoff, subopt_len + 2,
                              "Suboption %d: length isn't 0", subopt);
                        suboptoff += subopt_len;
                        break;
                  }
                  proto_tree_add_text(v_tree, tvb, optoff, 2, "Suboption %d: %s", subopt, o63_opt[subopt].text);
                  break;

            case ipv4:
                  if (subopt_len != 4) {
                        proto_tree_add_text(v_tree, tvb, optoff, subopt_len + 2,
                              "Suboption %d: length isn't 4", subopt);
                        suboptoff += subopt_len;
                        break;
                  }
                  if (suboptoff+4 > optend) {
                        proto_tree_add_text(v_tree, tvb, optoff, optend-optoff,
                            "Suboption %d: no room left in option for suboption value",
                            subopt);
                        return (optend);
                  }
                  proto_tree_add_text(v_tree, tvb, optoff, 6,
                      "Suboption %d: %s = %s" ,
                      subopt, o63_opt[subopt].text,
                      ip_to_str(tvb_get_ptr(tvb, suboptoff, 4)));
                  suboptoff += 6;
                  break;

            case ipv4_list:
                  if (subopt_len == 4) {
                        /* one IP address */
                        proto_tree_add_text(v_tree, tvb, optoff, 6,
                            "Suboption %d : %s = %s",
                            subopt, o63_opt[subopt].text,
                            ip_to_str(tvb_get_ptr(tvb, suboptoff, 4)));
                        suboptoff += 4;
                  } else {
                        /* > 1 IP addresses. Let's make a sub-tree */
                        vti = proto_tree_add_text(v_tree, tvb, optoff,
                            subopt_len+2, "Suboption %d: %s",
                            subopt, o63_opt[subopt].text);
                        o63_v_tree = proto_item_add_subtree(vti, ett_bootp_option);
                        for (suboptleft = subopt_len; suboptleft > 0;
                            suboptoff += 4, suboptleft -= 4) {
                              if (suboptleft < 4) {
                                    proto_tree_add_text(o63_v_tree,
                                        tvb, suboptoff, suboptleft,
                                        "Suboption length isn't a multiple of 4");
                                    suboptoff += suboptleft;
                                    break;
                              }
                              proto_tree_add_text(o63_v_tree, tvb, suboptoff, 4, "IP Address: %s",
                                  ip_to_str(tvb_get_ptr(tvb, suboptoff, 4)));
                        }
                  }
                  break;

            case val_boolean:
                  if (subopt_len != 1) {
                        proto_tree_add_text(v_tree, tvb, optoff, subopt_len + 2,
                              "Suboption %d: length isn't 1", subopt);
                        suboptoff += subopt_len;
                        break;
                  }
                  if (suboptoff+1 > optend) {
                        proto_tree_add_text(v_tree, tvb, optoff, optend-optoff,
                            "Suboption %d: no room left in option for suboption value",
                            subopt);
                        return (optend);
                  }
                  tfs = (const struct true_false_string *) o63_opt[subopt].data;
                  i = tvb_get_guint8(tvb, suboptoff);
                  if (i != 0 && i != 1) {
                        proto_tree_add_text(v_tree, tvb, optoff, 3,
                            "Subption %d: %s = Invalid Value %d",
                            subopt, o63_opt[subopt].text, i);
                  } else {
                        proto_tree_add_text(v_tree, tvb, optoff, 3,
                            "Subption %d: %s = %s", subopt,
                            o63_opt[subopt].text,
                            i == 0 ? tfs->false_string : tfs->true_string);
                  }
                  suboptoff += 3;
                  break;

            case val_u_byte:
                  if (subopt_len != 1) {
                        proto_tree_add_text(v_tree, tvb, optoff, subopt_len + 2,
                              "Suboption %d: length isn't 1", subopt);
                        suboptoff += subopt_len;
                        break;
                  }
                  if (suboptoff+1 > optend) {
                        proto_tree_add_text(v_tree, tvb, optoff, optend-optoff,
                            "Suboption %d: no room left in option for suboption value",
                            subopt);
                        return (optend);
                  }
                  proto_tree_add_text(v_tree, tvb, optoff, 3, "Suboption %d: %s = %u",
                      subopt, o63_opt[subopt].text,
                      tvb_get_guint8(tvb, suboptoff));
                  suboptoff += 1;
                  break;

            default:
                  proto_tree_add_text(v_tree, tvb, optoff, subopt_len + 2,"Unknown suboption %d", subopt);
                  suboptoff += subopt_len;
                  break;
            }
      }
      return suboptoff;
}


/* PacketCable Multimedia Terminal Adapter device capabilities (option 60).
   Ref: PKT-SP-I05-021127 sections 8.2 and 10 */

#define PKT_MDC_TLV_OFF 10


/* These are ASCII-encoded hexadecimal digits.  We use the raw hex equivalent for
   convenience. */
#define PKT_MDC_VERSION             0x3031  /* "01" */
#define PKT_MDC_TEL_END             0x3032  /* "02" */
#define PKT_MDC_TGT                 0x3033  /* "03" */
#define PKT_MDC_HTTP_ACC            0x3034  /* "04" */
#define PKT_MDC_SYSLOG              0x3035  /* "05" */
#define PKT_MDC_NCS                 0x3036  /* "06" */
#define PKT_MDC_PRI_LINE            0x3037  /* "07" */
#define PKT_MDC_VENDOR_TLV          0x3038  /* "08" */
#define PKT_MDC_NVRAM_STOR          0x3039  /* "09" */
#define PKT_MDC_PROV_REP            0x3041  /* "0A" */
#define PKT_MDC_PROV_REP_LC         0x3061  /* "0A" */
#define PKT_MDC_SUPP_CODECS         0x3042  /* "0B" */
#define PKT_MDC_SUPP_CODECS_LC            0x3062  /* "0b" */
#define PKT_MDC_SILENCE             0x3043  /* "0C" */
#define PKT_MDC_SILENCE_LC          0x3063  /* "0c" */
#define PKT_MDC_ECHO_CANCEL         0x3044  /* "0D" */
#define PKT_MDC_ECHO_CANCEL_LC            0x3064  /* "0d" */
#define PKT_MDC_RSVP                0x3045  /* "0E" */
#define PKT_MDC_RSVP_LC             0x3065  /* "0e" */
#define PKT_MDC_UGS_AD              0x3046  /* "0F" */
#define PKT_MDC_UGS_AD_LC           0x3066  /* "0f" */
#define PKT_MDC_IF_INDEX            0x3130  /* "10" */
#define PKT_MDC_FLOW_LOG            0x3131  /* "11" */
#define PKT_MDC_PROV_FLOWS          0x3132      /* "12" */
/* PacketCable 1.5: */
#define PKT_MDC_T38_VERSION         0x3133      /* "13" */
#define     PKT_MDC_T38_EC                0x3134      /* "14" */
#define     PKT_MDC_RFC2833_DTMF          0x3135      /* "15" */
#define PKT_MDC_VOICE_METRICS       0x3136      /* "16" */
#define     PKT_MDC_MIBS                  0x3137      /* "17" */
#define     PKT_MDC_MGPI                  0x3138      /* "18" */

static const value_string pkt_mdc_type_vals[] = {
      { PKT_MDC_VERSION,            "PacketCable Version" },
      { PKT_MDC_TEL_END,            "Number Of Telephony Endpoints" },
      { PKT_MDC_TGT,                "TGT Support" },
      { PKT_MDC_HTTP_ACC,           "HTTP Download File Access Method Support" },
      { PKT_MDC_SYSLOG,       "MTA-24 Event SYSLOG Notification Support" },
      { PKT_MDC_NCS,                "NCS Service Flow Support" },
      { PKT_MDC_PRI_LINE,           "Primary Line Support" },
      { PKT_MDC_VENDOR_TLV,         "Vendor Specific TLV Type(s)" },
      { PKT_MDC_NVRAM_STOR,         "NVRAM Ticket/Session Keys Storage Support" },
      { PKT_MDC_PROV_REP,           "Provisioning Event Reporting Support" },
      { PKT_MDC_PROV_REP_LC,        "Provisioning Event Reporting Support" },
      { PKT_MDC_SUPP_CODECS,        "Supported CODEC(s)" },
      { PKT_MDC_SUPP_CODECS_LC,     "Supported CODEC(s)" },
      { PKT_MDC_SILENCE,            "Silence Suppression Support" },
      { PKT_MDC_SILENCE_LC,         "Silence Suppression Support" },
      { PKT_MDC_ECHO_CANCEL,        "Echo Cancellation Support" },
      { PKT_MDC_ECHO_CANCEL_LC,     "Echo Cancellation Support" },
      { PKT_MDC_RSVP,               "RSVP Support/ Reserved" },
      { PKT_MDC_RSVP_LC,            "RSVP Support/ Reserved" },
      { PKT_MDC_UGS_AD,       "UGS-AD Support" },
      { PKT_MDC_UGS_AD_LC,          "UGS-AD Support" },
      { PKT_MDC_IF_INDEX,           "MTA's \"ifIndex\" starting number in \"ifTable\"" },
      { PKT_MDC_FLOW_LOG,           "Provisioning Flow Logging Support" },
      { PKT_MDC_PROV_FLOWS,         "Supported Provisioning Flows" },
      /* PacketCable 1.5: */
      { PKT_MDC_T38_VERSION,        "T38 Version Support" },
      { PKT_MDC_T38_EC,       "T38 Error Correction Support" },
      { PKT_MDC_RFC2833_DTMF,       "RFC 2833 DTMF Support" },
      { PKT_MDC_VOICE_METRICS,      "Voice Metrics Support" },
      { PKT_MDC_MIBS,               "MIB Support" },
      { PKT_MDC_MGPI,               "Multiple Grants Per Interval Support" },
      { 0,                          NULL }
};

static const value_string pkt_mdc_version_vals[] = {
      { 0x3030,   "PacketCable 1.0" },
      { 0x3031,   "PacketCable 1.1/1.5" }, /* 1.5 replaces 1.1-1.3 */
      { 0x3032,   "PacketCable 1.2" },
      { 0x3033,   "PacketCable 1.3" },
      { 0,        NULL }
};

static const value_string pkt_mdc_boolean_vals[] = {
      { 0x3030,   "No" },
      { 0x3031,   "Yes" },
      { 0,        NULL }
};

static const value_string pkt_mdc_codec_vals[] = {
      { 0x3031,   "other" },           /* "01" */
      { 0x3032,   "unknown" },
      { 0x3033,   "G.729" },
      { 0x3034,   "reserved" },
      { 0x3035,   "G.729E" },
      { 0x3036,   "PCMU" },
      { 0x3037,   "G.726-32" },
      { 0x3038,   "G.728" },
      { 0x3039,   "PCMA" },            /* "09" */
      { 0x3041,   "G.726-16" },        /* "0A" */
      { 0x3042,   "G.726-24" },
      { 0x3043,   "G.726-40" },
      { 0x3044,   "iLBC" },
      { 0x3045,   "BV16" },
      { 0x3046,   "telephone-event" }, /* "0F" */
      { 0,        NULL }
};

static const value_string pkt_mdc_t38_version_vals[] = {
      { 0x3030,   "Unsupported" },
      { 0x3031,   "T.38 Version Zero" }, /* default */
      { 0x3032,   "T.38 Version One" },
      { 0x3033,   "T.38 Version Two" },
      { 0x3035,   "T.38 Version Three" },
      { 0,        NULL }
};

static const value_string pkt_mdc_t38_ec_vals[] = {
      { 0x3030,   "None" },
      { 0x3031,   "Redundancy" }, /* default */
      { 0x3032,   "FEC" },
      { 0,        NULL }
};

static const value_string pkt_mdc_mibs_vals[] = {
      { 0x3030,   "PacketCable 1.0" },
      { 0x3031,   "PacketCable 1.5" },
      { 0x3032,   "Reserved" },
      { 0x3033,   "Reserved" },
      { 0x3034,   "Reserved" },
      { 0x3035,   "IETF" },
      { 0,        NULL }
};

/* DOCSIS Cable Modem device capabilities (option 60). */
/* XXX we should rename all PKT_CM_* variables to DOCSIS_CM_* */
#define PKT_CM_TLV_OFF 12

#define PKT_CM_CONCAT_SUP     0x3031  /* "01" */
#define PKT_CM_DOCSIS_VER     0x3032  /* "02" */
#define PKT_CM_FRAG_SUP       0x3033  /* "03" */
#define PKT_CM_PHS_SUP        0x3034  /* "04" */
#define PKT_CM_IGMP_SUP       0x3035  /* "05" */
#define PKT_CM_PRIV_SUP       0x3036  /* "06" */
#define PKT_CM_DSAID_SUP      0x3037  /* "07" */
#define PKT_CM_USID_SUP       0x3038  /* "08" */
#define PKT_CM_FILT_SUP       0x3039  /* "09" */
#define PKT_CM_TET_MI         0x3041  /* "0A" */
#define PKT_CM_TET_MI_LC      0x3061  /* "0a" */
#define PKT_CM_TET            0x3042  /* "0B" */
#define PKT_CM_TET_LC         0x3062  /* "0b" */
#define PKT_CM_DCC_SUP        0x3043  /* "0C" */
#define PKT_CM_DCC_SUP_LC     0x3063  /* "0c" */

static const value_string pkt_cm_type_vals[] = {
      { PKT_CM_CONCAT_SUP,    "Concatenation Support" },
      { PKT_CM_DOCSIS_VER,    "DOCSIS Version" },
      { PKT_CM_FRAG_SUP,      "Fragmentation Support" },
      { PKT_CM_PHS_SUP, "PHS Support" },
      { PKT_CM_IGMP_SUP,      "IGMP Support" },
      { PKT_CM_PRIV_SUP,      "Privacy Support" },
      { PKT_CM_DSAID_SUP,     "Downstream SAID Support" },
      { PKT_CM_USID_SUP,      "Upstream SID Support" },
      { PKT_CM_FILT_SUP,      "Optional Filtering Support" },
      { PKT_CM_TET_MI,  "Transmit Equalizer Taps per Modulation Interval" },
      { PKT_CM_TET_MI_LC,     "Transmit Equalizer Taps per Modulation Interval" },
      { PKT_CM_TET,           "Number of Transmit Equalizer Taps" },
      { PKT_CM_TET_LC,  "Number of Transmit Equalizer Taps" },
      { PKT_CM_DCC_SUP, "DCC Support" },
      { PKT_CM_DCC_SUP_LC,    "DCC Support" }
};

static const value_string pkt_cm_version_vals[] = {
      { 0x3030,   "DOCSIS 1.0" },
      { 0x3031,   "DOCSIS 1.1" },
      { 0x3032,   "DOCSIS 2.0" },
      { 0,        NULL }
};

static const value_string pkt_cm_privacy_vals[] = {
      { 0x3030,   "BPI Support" },
      { 0x3031,   "BPI Plus Support" },
      { 0,        NULL }
};


static const value_string pkt_mdc_supp_flow_vals[] = {
      { 1 << 0, "Secure Flow (Full Secure Provisioning Flow)" },
      { 1 << 1, "Hybrid Flow" },
      { 1 << 2, "Basic Flow" },
      { 0, NULL }
};


static void
dissect_packetcable_mta_cap(proto_tree *v_tree, tvbuff_t *tvb, int voff, int len)
{
      guint16 raw_val;
      unsigned long flow_val = 0;
      guint off = PKT_MDC_TLV_OFF + voff;
      guint tlv_len, i;
      guint8 asc_val[3] = "  ", flow_val_str[5];
      static GString *tlv_str = NULL;
      char bit_fld[64];
      proto_item *ti;
      proto_tree *subtree;

      if (! tlv_str)
            tlv_str = g_string_new("");

      tvb_memcpy (tvb, asc_val, off, 2);
      if (sscanf(asc_val, "%x", &tlv_len) != 1) {
            proto_tree_add_text(v_tree, tvb, off, len - off,
                  "Bogus length: %s", asc_val);
            return;
      } else {
            proto_tree_add_uint_format(v_tree, hf_bootp_pkt_mtacap_len, tvb, off, 2,
                        tlv_len, "MTA DC Length: %d", tlv_len);
            off += 2;

            while ((int) off - voff < len) {
                  /* Type */
                  raw_val = tvb_get_ntohs (tvb, off);
                  g_string_sprintf(tlv_str, "0x%.2s: %s = ",
                              tvb_get_ptr(tvb, off, 2),
                              val_to_str(raw_val, pkt_mdc_type_vals, "unknown"));

                  /* Length */
                  tvb_memcpy(tvb, asc_val, off + 2, 2);
                  if (sscanf(asc_val, "%x", &tlv_len) != 1) {
                        proto_tree_add_text(v_tree, tvb, off, len - off,
                                          "[Bogus length: %s]", asc_val);
                        return;
                  } else {
                        /* Value(s) */
                        /*g_string_sprintfa(tlv_str, "Length: %d, Value: ", tlv_len);*/

                        switch (raw_val) {
                              case PKT_MDC_VERSION:
                                    raw_val = tvb_get_ntohs(tvb, off + 4);
                                    g_string_sprintfa(tlv_str, "%s (%.2s)",
                                                val_to_str(raw_val, pkt_mdc_version_vals, "Reserved"),
                                                tvb_get_ptr(tvb, off + 4, 2) );
                                    break;
                              case PKT_MDC_TEL_END:
                              case PKT_MDC_IF_INDEX:
                                    g_string_sprintfa(tlv_str, "%.2s",
                                                tvb_get_ptr(tvb, off + 4, 2) );
                                    break;
                              case PKT_MDC_TGT:
                              case PKT_MDC_HTTP_ACC:
                              case PKT_MDC_SYSLOG:
                              case PKT_MDC_NCS:
                              case PKT_MDC_PRI_LINE:
                              case PKT_MDC_NVRAM_STOR:
                              case PKT_MDC_PROV_REP:
                              case PKT_MDC_PROV_REP_LC:
                              case PKT_MDC_SILENCE:
                              case PKT_MDC_SILENCE_LC:
                              case PKT_MDC_ECHO_CANCEL:
                              case PKT_MDC_ECHO_CANCEL_LC:
                              case PKT_MDC_RSVP:
                              case PKT_MDC_RSVP_LC:
                              case PKT_MDC_UGS_AD:
                              case PKT_MDC_UGS_AD_LC:
                              case PKT_MDC_FLOW_LOG:
                              case PKT_MDC_RFC2833_DTMF:
                              case PKT_MDC_VOICE_METRICS:
                                    raw_val = tvb_get_ntohs(tvb, off + 4);
                                    g_string_sprintfa(tlv_str, "%s (%.2s)",
                                                val_to_str(raw_val, pkt_mdc_boolean_vals, "unknown"),
                                                tvb_get_ptr(tvb, off + 4, 2) );
                                    break;
                              case PKT_MDC_SUPP_CODECS:
                              case PKT_MDC_SUPP_CODECS_LC:
                                    for (i = 0; i < tlv_len; i++) {
                                          raw_val = tvb_get_ntohs(tvb, off + 4 + (i * 2) );
                                          g_string_sprintfa(tlv_str, "%s%s (%.2s)",
                                                      plurality(i + 1, "", ", "),
                                                      val_to_str(raw_val, pkt_mdc_codec_vals, "unknown"),
                                                      tvb_get_ptr(tvb, off + 4 + (i * 2), 2) );
                                    }
                                    break;
                              case PKT_MDC_PROV_FLOWS:
                                    tvb_memcpy(tvb, flow_val_str, off + 4, 4);
                                    flow_val_str[4] = '\0';
                                    flow_val = strtoul(flow_val_str, NULL, 16);
                                    g_string_sprintfa(tlv_str, "0x%04lx", flow_val);
                                    break;
                              case PKT_MDC_T38_VERSION:
                                    raw_val = tvb_get_ntohs(tvb, off + 4);
                                    g_string_sprintfa(tlv_str, "%s (%.2s)",
                                                val_to_str(raw_val, pkt_mdc_t38_version_vals, "unknown"),
                                                tvb_get_ptr(tvb, off + 4, 2) );
                                    break;
                              case PKT_MDC_T38_EC:
                                    raw_val = tvb_get_ntohs(tvb, off + 4);
                                    g_string_sprintfa(tlv_str, "%s (%.2s)",
                                                val_to_str(raw_val, pkt_mdc_t38_ec_vals, "unknown"),
                                                tvb_get_ptr(tvb, off + 4, 2) );
                                    break;
                              case PKT_MDC_MIBS:
                                    raw_val = tvb_get_ntohs(tvb, off + 4);
                                    g_string_sprintfa(tlv_str, "%s (%.2s)",
                                                val_to_str(raw_val, pkt_mdc_mibs_vals, "unknown"),
                                                tvb_get_ptr(tvb, off + 4, 2) );
                                    break;
                              case PKT_MDC_VENDOR_TLV:
                              default:
                                    g_string_sprintfa(tlv_str, "%s",
                                                tvb_format_stringzpad(tvb, off + 4, tlv_len * 2) );
                                    break;
                        }
                  }
                  ti = proto_tree_add_text(v_tree, tvb, off, (tlv_len * 2) + 4, tlv_str->str);
                  subtree = proto_item_add_subtree(ti, ett_bootp_option);
                  if (raw_val == PKT_MDC_PROV_FLOWS) {
                        for (i = 0 ; i < 3; i++) {
                              if (flow_val & pkt_mdc_supp_flow_vals[i].value) {
                                    decode_bitfield_value(bit_fld, flow_val, pkt_mdc_supp_flow_vals[i].value, 16);
                                    proto_tree_add_text(ti, tvb, off + 4, 4, "%s%s",
                                          bit_fld, pkt_mdc_supp_flow_vals[i].strptr);
                              }
                        }
                  }
                  off += (tlv_len * 2) + 4;
            }
      }
}

static void
dissect_docsis_cm_cap(proto_tree *v_tree, tvbuff_t *tvb, int voff, int len)
{
      unsigned long raw_val;
      guint off = PKT_CM_TLV_OFF + voff;
      guint tlv_len, i;
      guint8 asc_val[3] = "  ";
      static GString *tlv_str = NULL;

      if (! tlv_str)
            tlv_str = g_string_new("");

      tvb_memcpy (tvb, asc_val, off, 2);
      if (sscanf(asc_val, "%x", &tlv_len) != 1) {
            proto_tree_add_text(v_tree, tvb, off, len - off,
                            "Bogus length: %s", asc_val);
            return;
      } else {
            proto_tree_add_uint_format(v_tree, hf_bootp_docsis_cmcap_len, tvb, off, 2,
                        tlv_len, "CM DC Length: %d", tlv_len);
            off += 2;

            while ((int) off - voff < len) {
                  /* Type */
                  raw_val = tvb_get_ntohs (tvb, off);
                  g_string_sprintf(tlv_str, "0x%.2s: %s = ",
                              tvb_get_ptr(tvb, off, 2),
                              val_to_str(raw_val, pkt_cm_type_vals, "unknown"));

                  /* Length */
                  tvb_memcpy(tvb, asc_val, off + 2, 2);
                  if (sscanf(asc_val, "%x", &tlv_len) != 1) {
                        proto_tree_add_text(v_tree, tvb, off, len - off,
                                          "[Bogus length: %s]", asc_val);
                        return;
                  } else {
                        /* Value(s) */
                        /*g_string_sprintfa(tlv_str, "Length: %d, Value%s: ", tlv_len,
                                    plurality(tlv_len, "", "s") );*/

                        switch (raw_val) {
                              case PKT_CM_CONCAT_SUP:
                              case PKT_CM_FRAG_SUP:
                              case PKT_CM_PHS_SUP:
                              case PKT_CM_IGMP_SUP:
                              case PKT_CM_DCC_SUP:
                              case PKT_CM_DCC_SUP_LC:
                                    for (i = 0; i < tlv_len; i++) {
                                          raw_val = tvb_get_ntohs(tvb, off + 4 + (i * 2) );
                                          g_string_sprintfa(tlv_str, "%s%s (%.2s)",
                                                      plurality(i + 1, "", ", "),
                                                      val_to_str(raw_val, pkt_mdc_boolean_vals, "unknown"),
                                                      tvb_get_ptr(tvb, off + 4 + (i * 2), 2) );
                                    }
                                    break;
                              case PKT_CM_DOCSIS_VER:
                                    raw_val = tvb_get_ntohs(tvb, off + 4);
                                    g_string_sprintfa(tlv_str, "%s (%.2s)",
                                                val_to_str(raw_val, pkt_cm_version_vals, "Reserved"),
                                                tvb_get_ptr(tvb, off + 4, 2) );
                                    break;
                              case PKT_CM_PRIV_SUP:
                                    raw_val = tvb_get_ntohs(tvb, off + 4);
                                    g_string_sprintfa(tlv_str, "%s (%.2s)",
                                                val_to_str(raw_val, pkt_cm_privacy_vals, "Reserved"),
                                                tvb_get_ptr(tvb, off + 4, 2) );
                                    break;
                              case PKT_CM_DSAID_SUP:
                              case PKT_CM_USID_SUP:
                              case PKT_CM_TET_MI:
                              case PKT_CM_TET_MI_LC:
                              case PKT_CM_TET:
                              case PKT_CM_TET_LC:
                                    tvb_memcpy (tvb, asc_val, off + 4, 2);
                                    raw_val = strtoul(asc_val, NULL, 16);
                                    g_string_sprintfa(tlv_str, "%lu", raw_val);
                                    break;
                              case PKT_CM_FILT_SUP:
                                    tvb_memcpy (tvb, asc_val, off + 4, 2);
                                    raw_val = strtoul(asc_val, NULL, 16);
                                    if (raw_val & 0x01)
                                          g_string_append(tlv_str, "802.1P filtering");
                                    if (raw_val & 0x02) {
                                          if (raw_val & 0x01)
                                                g_string_append(tlv_str, ", ");
                                          g_string_append(tlv_str, "802.1Q filtering");
                                    }
                                    if (! raw_val & 0x03)
                                          g_string_append(tlv_str, "None");
                                    g_string_sprintfa(tlv_str, " (0x%02lx)", raw_val);
                                    break;
                        }
                  }
                  proto_tree_add_text(v_tree, tvb, off, (tlv_len * 2) + 4, tlv_str->str);
                  off += (tlv_len * 2) + 4;
            }
      }
}


/* Definitions specific to PKT-SP-PROV-I05-021127 begin with "PKT_CCC_I05".
   Definitions specific to IETF draft 5 and RFC 3495 begin with "PKT_CCC_IETF".
   Shared definitions begin with "PKT_CCC".
 */
#define PKT_CCC_PRI_DHCP       1
#define PKT_CCC_SEC_DHCP       2
#define PKT_CCC_I05_SNMP       3
#define PKT_CCC_IETF_PROV_SRV  3
#define PKT_CCC_I05_PRI_DNS    4
#define PKT_CCC_IETF_AS_KRB    4
#define PKT_CCC_I05_SEC_DNS    5
#define PKT_CCC_IETF_AP_KRB    5
#define PKT_CCC_KRB_REALM      6
#define PKT_CCC_TGT_FLAG       7
#define PKT_CCC_PROV_TIMER     8
#define PKT_CCC_CMS_FQDN       9
#define PKT_CCC_IETF_SEC_TKT   9
#define PKT_CCC_AS_KRB        10
#define PKT_CCC_AP_KRB        11
#define PKT_CCC_MTA_KRB_CLEAR 12

static const value_string pkt_i05_ccc_opt_vals[] = {
      { PKT_CCC_PRI_DHCP,           "Primary DHCP Server" },
      { PKT_CCC_SEC_DHCP,           "Secondary DHCP Server" },
      { PKT_CCC_I05_SNMP,           "SNMP Entity" },
      { PKT_CCC_I05_PRI_DNS,        "Primary DNS Server" },
      { PKT_CCC_I05_SEC_DNS,        "Secondary DNS Server" },
      { PKT_CCC_KRB_REALM,          "Kerberos Realm" },
      { PKT_CCC_TGT_FLAG,           "MTA should fetch TGT?" },
      { PKT_CCC_PROV_TIMER,         "Provisioning Timer" },
      { PKT_CCC_CMS_FQDN,           "CMS FQDN" },
      { PKT_CCC_AS_KRB,       "AS-REQ/AS-REP Backoff and Retry" },
      { PKT_CCC_AP_KRB,       "AP-REQ/AP-REP Backoff and Retry" },
      { PKT_CCC_MTA_KRB_CLEAR,      "MTA should clear Kerberos tickets?" },
      { 0, NULL },
};

static const value_string pkt_draft5_ccc_opt_vals[] = {
      { PKT_CCC_PRI_DHCP,           "TSP's Primary DHCP Server" },
      { PKT_CCC_SEC_DHCP,           "TSP's Secondary DHCP Server" },
      { PKT_CCC_IETF_PROV_SRV,      "TSP's Provisioning Server" },
      { PKT_CCC_IETF_AS_KRB,        "TSP's AS-REQ/AS-REP Backoff and Retry" },
      { PKT_CCC_IETF_AP_KRB,        "TSP's AP-REQ/AP-REP Backoff and Retry" },
      { PKT_CCC_KRB_REALM,          "TSP's Kerberos Realm Name" },
      { PKT_CCC_TGT_FLAG,           "TSP's Ticket Granting Server Utilization" },
      { PKT_CCC_PROV_TIMER,         "TSP's Provisioning Timer Value" },
      { PKT_CCC_IETF_SEC_TKT,       "PacketCable Security Ticket Control" },
      { 0, NULL },
};

static const value_string pkt_i05_ccc_ticket_ctl_vals[] = {
      { 1, "Invalidate Provisioning Application Server's ticket" },
      { 2, "Invalidate all CMS Application Server tickets" },
      { 3, "Invalidate all Application Server tickets" },
      { 0, NULL },
};

static int
dissect_packetcable_i05_ccc(proto_tree *v_tree, tvbuff_t *tvb, int optoff,
    int optend)
{
      int suboptoff = optoff;
      guint8 subopt, subopt_len, fetch_tgt, timer_val, ticket_ctl;
      proto_tree *pkt_s_tree;
      proto_item *vti;
      static GString *opt_str = NULL;

      if (! opt_str)
            opt_str = g_string_new("");

      subopt = tvb_get_guint8(tvb, optoff);
      suboptoff++;

      if (suboptoff >= optend) {
            proto_tree_add_text(v_tree, tvb, optoff, 1,
                  "Suboption %d: no room left in option for suboption length",
                  subopt);
            return (optend);
      }
      subopt_len = tvb_get_guint8(tvb, optoff);
      suboptoff++;

      g_string_sprintf(opt_str, "Suboption %u: %s: ", subopt,
                  val_to_str(subopt, pkt_i05_ccc_opt_vals, "unknown/reserved") );

      switch (subopt) {
            case PKT_CCC_PRI_DHCP:  /* String values */
            case PKT_CCC_SEC_DHCP:
            case PKT_CCC_I05_SNMP:
            case PKT_CCC_I05_PRI_DNS:
            case PKT_CCC_I05_SEC_DNS:
            case PKT_CCC_KRB_REALM:
            case PKT_CCC_CMS_FQDN:
                  g_string_sprintfa(opt_str, "%s (%u byte%s)",
                              tvb_format_stringzpad(tvb, suboptoff, subopt_len),
                              subopt_len,
                              plurality(subopt_len, "", "s") );
                  proto_tree_add_text(v_tree, tvb, optoff, subopt_len + 2, opt_str->str);
                  suboptoff += subopt_len;
                  break;

            case PKT_CCC_TGT_FLAG:
                  if (suboptoff+1 > optend) {
                        proto_tree_add_text(v_tree, tvb, optoff, optend-optoff,
                            "Suboption %d: no room left in option for suboption value",
                            subopt);
                        return (optend);
                  }
                  fetch_tgt = tvb_get_guint8(tvb, suboptoff);
                  g_string_sprintfa(opt_str, "%s (%u byte%s%s)",
                              fetch_tgt ? "Yes" : "No",
                              subopt_len,
                              plurality(subopt_len, "", "s"),
                              subopt_len != 1 ? " [Invalid]" : "");
                  proto_tree_add_text(v_tree, tvb, optoff, subopt_len + 2, opt_str->str);
                  suboptoff += subopt_len;
                  break;

            case PKT_CCC_PROV_TIMER:
                  if (suboptoff+1 > optend) {
                        proto_tree_add_text(v_tree, tvb, optoff, optend-optoff,
                            "Suboption %d: no room left in option for suboption value",
                            subopt);
                        return (optend);
                  }
                  timer_val = tvb_get_guint8(tvb, suboptoff);
                  g_string_sprintfa(opt_str, "%u%s (%u byte%s%s)", timer_val,
                              timer_val > 30 ? " [Invalid]" : "",
                              subopt_len,
                              plurality(subopt_len, "", "s"),
                              subopt_len != 1 ? " [Invalid]" : "");
                  proto_tree_add_text(v_tree, tvb, optoff, subopt_len + 2, opt_str->str);
                  suboptoff += subopt_len;
                  break;

            case PKT_CCC_AS_KRB:
                  if (suboptoff+12 > optend) {
                        proto_tree_add_text(v_tree, tvb, optoff, optend-optoff,
                            "Suboption %d: no room left in option for suboption value",
                            subopt);
                        return (optend);
                  }
                  g_string_sprintfa(opt_str, "(%u byte%s%s)", subopt_len,
                              plurality(subopt_len, "", "s"),
                              subopt_len != 12 ? " [Invalid]" : "");
                  vti = proto_tree_add_text(v_tree, tvb, optoff, subopt_len + 2, opt_str->str);
                  if (subopt_len == 12) {
                        pkt_s_tree = proto_item_add_subtree(vti, ett_bootp_option);
                        proto_tree_add_text(pkt_s_tree, tvb, suboptoff, 4,
                                    "pktcMtaDevRealmUnsolicitedKeyNomTimeout: %u",
                                    tvb_get_ntohl(tvb, suboptoff));
                        proto_tree_add_text(pkt_s_tree, tvb, suboptoff + 4, 4,
                                    "pktcMtaDevRealmUnsolicitedKeyMaxTimeout: %u",
                                    tvb_get_ntohl(tvb, suboptoff + 4));
                        proto_tree_add_text(pkt_s_tree, tvb, suboptoff + 8, 4,
                                    "pktcMtaDevRealmUnsolicitedKeyMaxRetries: %u",
                                    tvb_get_ntohl(tvb, suboptoff + 8));
                  }
                  suboptoff += subopt_len;
                  break;

            case PKT_CCC_AP_KRB:
                  if (suboptoff+12 > optend) {
                        proto_tree_add_text(v_tree, tvb, optoff, optend-optoff,
                            "Suboption %d: no room left in option for suboption value",
                            subopt);
                        return (optend);
                  }
                  g_string_sprintfa(opt_str, "(%u byte%s%s)", subopt_len,
                              plurality(subopt_len, "", "s"),
                              subopt_len != 12 ? " [Invalid]" : "");
                  vti = proto_tree_add_text(v_tree, tvb, optoff, subopt_len + 2, opt_str->str);
                  if (subopt_len == 12) {
                        pkt_s_tree = proto_item_add_subtree(vti, ett_bootp_option);
                        proto_tree_add_text(pkt_s_tree, tvb, suboptoff, 4,
                                    "pktcMtaDevProvUnsolicitedKeyNomTimeout: %u",
                                    tvb_get_ntohl(tvb, suboptoff));
                        proto_tree_add_text(pkt_s_tree, tvb, suboptoff + 4, 4,
                                    "pktcMtaDevProvUnsolicitedKeyMaxTimeout: %u",
                                    tvb_get_ntohl(tvb, suboptoff + 4));
                        proto_tree_add_text(pkt_s_tree, tvb, suboptoff + 8, 4,
                                    "pktcMtaDevProvUnsolicitedKeyMaxRetries: %u",
                                    tvb_get_ntohl(tvb, suboptoff + 8));
                  }
                  suboptoff += subopt_len;
                  break;

            case PKT_CCC_MTA_KRB_CLEAR:
                  if (suboptoff+1 > optend) {
                        proto_tree_add_text(v_tree, tvb, optoff, optend-optoff,
                            "Suboption %d: no room left in option for suboption value",
                            subopt);
                        return (optend);
                  }
                  ticket_ctl = tvb_get_guint8(tvb, suboptoff);
                  g_string_sprintfa(opt_str, "%s (%u) (%u byte%s%s)",
                              val_to_str (ticket_ctl, pkt_i05_ccc_ticket_ctl_vals, "unknown/invalid"),
                              ticket_ctl,
                              subopt_len,
                              plurality(subopt_len, "", "s"),
                              subopt_len != 1 ? " [Invalid]" : "");
                  proto_tree_add_text(v_tree, tvb, optoff, subopt_len + 2, opt_str->str);
                  suboptoff += subopt_len;
                  break;

            default:
                  proto_tree_add_text(v_tree, tvb, optoff, subopt_len + 2, opt_str->str);
                  suboptoff += subopt_len;
                  break;

      }
      return suboptoff;
}


static const value_string sec_tcm_vals[] = {
      { 1 << 0, "PacketCable Provisioning Server" },
      { 1 << 1, "All PacketCable Call Management Servers" },
      { 0, NULL }
};

static int
dissect_packetcable_ietf_ccc(proto_tree *v_tree, tvbuff_t *tvb, int optoff,
    int optend, int revision)
{
      int suboptoff = optoff;
      guint8 subopt, subopt_len, ipv4_addr[4];
      guint8 prov_type, fetch_tgt, timer_val;
      guint16 sec_tcm;
      proto_tree *pkt_s_tree;
      proto_item *vti;
      static GString *opt_str = NULL;
      int max_timer_val = 255, i;
      char dns_name[255], bit_fld[24];

      if (! opt_str)
            opt_str = g_string_new("");

      subopt = tvb_get_guint8(tvb, suboptoff);
      suboptoff++;

      if (suboptoff >= optend) {
            proto_tree_add_text(v_tree, tvb, optoff, 1,
                  "Suboption %d: no room left in option for suboption length",
                  subopt);
            return (optend);
      }
      subopt_len = tvb_get_guint8(tvb, suboptoff);
      suboptoff++;

      g_string_sprintf(opt_str, "Suboption %u: %s: ", subopt,
                  val_to_str(subopt, pkt_draft5_ccc_opt_vals, "unknown/reserved") );

      switch (subopt) {
            case PKT_CCC_PRI_DHCP:  /* IPv4 values */
            case PKT_CCC_SEC_DHCP:
                  if (suboptoff+4 > optend) {
                        proto_tree_add_text(v_tree, tvb, optoff, optend-optoff,
                            "Suboption %d: no room left in option for suboption value",
                            subopt);
                        return (optend);
                  }
                  tvb_memcpy(tvb, ipv4_addr, suboptoff, 4);
                  g_string_sprintfa(opt_str, "%u.%u.%u.%u (%u byte%s%s)",
                              ipv4_addr[0], ipv4_addr[1], ipv4_addr[2], ipv4_addr[3],
                              subopt_len,
                              plurality(subopt_len, "", "s"),
                              subopt_len != 4 ? " [Invalid]" : "");
                  proto_tree_add_text(v_tree, tvb, optoff, subopt_len + 2, opt_str->str);
                  suboptoff += subopt_len;
                  break;

            case PKT_CCC_IETF_PROV_SRV:
                  if (suboptoff+1 > optend) {
                        proto_tree_add_text(v_tree, tvb, optoff, optend-optoff,
                            "Suboption %d: no room left in option for suboption value",
                            subopt);
                        return (optend);
                  }
                  prov_type = tvb_get_guint8(tvb, suboptoff);
                  suboptoff += 1;
                  switch (prov_type) {
                        case 0:
                              /* XXX - check suboption length */
                              get_dns_name(tvb, optoff, suboptoff, dns_name,
                                    sizeof(dns_name));
                              g_string_sprintfa(opt_str, "%s (%u byte%s)", dns_name,
                                          subopt_len - 1, plurality(subopt_len, "", "s") );
                              proto_tree_add_text(v_tree, tvb, optoff, subopt_len + 2, opt_str->str);
                              break;
                        case 1:
                              if (suboptoff+4 > optend) {
                                    proto_tree_add_text(v_tree, tvb, optoff, optend-optoff,
                                        "Suboption %d: no room left in option for suboption value",
                                        subopt);
                                    return (optend);
                              }
                              tvb_memcpy(tvb, ipv4_addr, suboptoff, 4);
                              g_string_sprintfa(opt_str, "%u.%u.%u.%u (%u byte%s%s)",
                                          ipv4_addr[0], ipv4_addr[1], ipv4_addr[2], ipv4_addr[3],
                                          subopt_len,
                                          plurality(subopt_len, "", "s"),
                                          subopt_len != 5 ? " [Invalid]" : "");
                              proto_tree_add_text(v_tree, tvb, optoff, subopt_len + 2, opt_str->str);
                              break;
                        default:
                              g_string_sprintfa(opt_str, "Invalid type: %u (%u byte%s)",
                                          prov_type,
                                          subopt_len,
                                          plurality(subopt_len, "", "s") );
                              proto_tree_add_text(v_tree, tvb, optoff, subopt_len + 2, opt_str->str);
                              break;
                  }
                  suboptoff += subopt_len - 1;
                  break;

            case PKT_CCC_IETF_AS_KRB:
                  if (suboptoff+12 > optend) {
                        proto_tree_add_text(v_tree, tvb, optoff, optend-optoff,
                            "Suboption %d: no room left in option for suboption value",
                            subopt);
                        return (optend);
                  }
                  g_string_sprintfa(opt_str, "(%u byte%s%s)", subopt_len,
                              plurality(subopt_len, "", "s"),
                              subopt_len != 12 ? " [Invalid]" : "");
                  vti = proto_tree_add_text(v_tree, tvb, optoff, subopt_len + 2, opt_str->str);
                  if (subopt_len == 12) {
                        pkt_s_tree = proto_item_add_subtree(vti, ett_bootp_option);
                        proto_tree_add_text(pkt_s_tree, tvb, suboptoff, 4,
                                    "pktcMtaDevRealmUnsolicitedKeyNomTimeout: %u",
                                    tvb_get_ntohl(tvb, suboptoff));
                        proto_tree_add_text(pkt_s_tree, tvb, suboptoff + 4, 4,
                                    "pktcMtaDevRealmUnsolicitedKeyMaxTimeout: %u",
                                    tvb_get_ntohl(tvb, suboptoff + 4));
                        proto_tree_add_text(pkt_s_tree, tvb, suboptoff + 8, 4,
                                    "pktcMtaDevRealmUnsolicitedKeyMaxRetries: %u",
                                    tvb_get_ntohl(tvb, suboptoff + 8));
                  }
                  suboptoff += subopt_len;
                  break;

            case PKT_CCC_IETF_AP_KRB:
                  g_string_sprintfa(opt_str, "(%u byte%s%s)", subopt_len,
                              plurality(subopt_len, "", "s"),
                              subopt_len != 12 ? " [Invalid]" : "");
                  vti = proto_tree_add_text(v_tree, tvb, optoff, subopt_len + 2, opt_str->str);
                  if (subopt_len == 12) {
                        pkt_s_tree = proto_item_add_subtree(vti, ett_bootp_option);
                        proto_tree_add_text(pkt_s_tree, tvb, suboptoff, 4,
                              "pktcMtaDevProvUnsolicitedKeyNomTimeout: %u",
                              tvb_get_ntohl(tvb, suboptoff));
                        proto_tree_add_text(pkt_s_tree, tvb, suboptoff + 4, 4,
                                    "pktcMtaDevProvUnsolicitedKeyMaxTimeout: %u",
                                    tvb_get_ntohl(tvb, suboptoff + 4));
                        proto_tree_add_text(pkt_s_tree, tvb, suboptoff + 8, 4,
                              "pktcMtaDevProvUnsolicitedKeyMaxRetries: %u",
                              tvb_get_ntohl(tvb, suboptoff + 8));
                  }
                  suboptoff += subopt_len;
                  break;

            case PKT_CCC_KRB_REALM: /* String values */
                  /* XXX - check suboption length */
                  get_dns_name(tvb, optoff, suboptoff, dns_name, sizeof(dns_name));
                  g_string_sprintfa(opt_str, "%s (%u byte%s)", dns_name,
                              subopt_len, plurality(subopt_len, "", "s") );
                  proto_tree_add_text(v_tree, tvb, optoff, subopt_len + 2, opt_str->str);
                  suboptoff += subopt_len;
                  break;

            case PKT_CCC_TGT_FLAG:
                  if (suboptoff+1 > optend) {
                        proto_tree_add_text(v_tree, tvb, optoff, optend-optoff,
                            "Suboption %d: no room left in option for suboption value",
                            subopt);
                        return (optend);
                  }
                  fetch_tgt = tvb_get_guint8(tvb, suboptoff);
                  g_string_sprintfa(opt_str, "%s (%u byte%s%s)",
                              fetch_tgt ? "Yes" : "No",
                              subopt_len,
                              plurality(subopt_len, "", "s"),
                              subopt_len != 1 ? " [Invalid]" : "");
                  proto_tree_add_text(v_tree, tvb, optoff, subopt_len + 2, opt_str->str);
                  suboptoff += 1;
                  break;

            case PKT_CCC_PROV_TIMER:
                  if (suboptoff+1 > optend) {
                        proto_tree_add_text(v_tree, tvb, optoff, optend-optoff,
                            "Suboption %d: no room left in option for suboption value",
                            subopt);
                        return (optend);
                  }
                  if (revision == PACKETCABLE_CCC_DRAFT5)
                        max_timer_val = 30;
                  timer_val = tvb_get_guint8(tvb, suboptoff);
                  g_string_sprintfa(opt_str, "%u%s (%u byte%s%s)", timer_val,
                              timer_val > max_timer_val ? " [Invalid]" : "",
                              subopt_len,
                              plurality(subopt_len, "", "s"),
                              subopt_len != 1 ? " [Invalid]" : "");
                  proto_tree_add_text(v_tree, tvb, optoff, subopt_len + 2, opt_str->str);
                  suboptoff += 1;
                  break;

            case PKT_CCC_IETF_SEC_TKT:
                  if (suboptoff+2 > optend) {
                        proto_tree_add_text(v_tree, tvb, optoff, optend-optoff,
                            "Suboption %d: no room left in option for suboption value",
                            subopt);
                        return (optend);
                  }
                  sec_tcm = tvb_get_ntohs(tvb, suboptoff);
                  g_string_sprintfa(opt_str, "0x%04x (%u byte%s%s)", sec_tcm, subopt_len,
                              plurality(subopt_len, "", "s"),
                              subopt_len != 2 ? " [Invalid]" : "");
                  vti = proto_tree_add_text(v_tree, tvb, optoff, subopt_len + 2, opt_str->str);
                  if (subopt_len == 2) {
                        pkt_s_tree = proto_item_add_subtree(vti, ett_bootp_option);
                        for (i = 0; i < 2; i++) {
                              if (sec_tcm & sec_tcm_vals[i].value) {
                                    decode_bitfield_value(bit_fld, sec_tcm, sec_tcm_vals[i].value, 16);
                                    proto_tree_add_text(pkt_s_tree, tvb, suboptoff, 2, "%sInvalidate %s",
                                          bit_fld, sec_tcm_vals[i].strptr);
                              }
                        }
                  }
                  suboptoff += subopt_len;
                  break;

            default:
                  proto_tree_add_text(v_tree, tvb, optoff, subopt_len + 2, opt_str->str);
                  suboptoff += subopt_len;
                  break;
      }
      return suboptoff;
}

#define BOOTREQUEST     1
#define BOOTREPLY 2

static const value_string op_vals[] = {
      { BOOTREQUEST,    "Boot Request" },
      { BOOTREPLY,      "Boot Reply" },
      { 0,        NULL }
};

static void
dissect_bootp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{
      proto_tree  *bp_tree = NULL;
      proto_item  *ti;
      proto_tree  *flag_tree = NULL;
      proto_item  *fi;
      guint8            op;
      guint8            htype, hlen;
      const guint8      *haddr;
      int         voff, eoff, tmpvoff; /* vendor offset, end offset */
      guint32           ip_addr;
      gboolean    at_end;
      const char  *dhcp_type = NULL;
      const guint8      *vendor_class_id = NULL;
      guint16           flags;

      if (check_col(pinfo->cinfo, COL_PROTOCOL))
            col_set_str(pinfo->cinfo, COL_PROTOCOL, "BOOTP");
      if (check_col(pinfo->cinfo, COL_INFO)) {
            /*
             * In case we throw an exception fetching the opcode, etc.
             */
            col_clear(pinfo->cinfo, COL_INFO);
      }

      op = tvb_get_guint8(tvb, 0);
      htype = tvb_get_guint8(tvb, 1);
      hlen = tvb_get_guint8(tvb, 2);
      if (check_col(pinfo->cinfo, COL_INFO)) {
            switch (op) {

            case BOOTREQUEST:
                  if ((htype == ARPHRD_ETHER || htype == ARPHRD_IEEE802)
                      && hlen == 6)
                        col_add_fstr(pinfo->cinfo, COL_INFO, "Boot Request from %s (%s)",
                            arphrdaddr_to_str(tvb_get_ptr(tvb, 28, hlen),
                                hlen, htype),
                            get_ether_name(tvb_get_ptr(tvb, 28, hlen)));
                  else
                        col_add_fstr(pinfo->cinfo, COL_INFO, "Boot Request from %s",
                            arphrdaddr_to_str(tvb_get_ptr(tvb, 28, hlen),
                              hlen, htype));
                  break;

            case BOOTREPLY:
                  col_set_str(pinfo->cinfo, COL_INFO, "Boot Reply");
                  break;

            default:
                  col_add_fstr(pinfo->cinfo, COL_INFO, "Unknown BOOTP message type (%u)",
                      op);
                  break;
            }
      }

      if (tree) {
            ti = proto_tree_add_item(tree, proto_bootp, tvb, 0, -1, FALSE);
            bp_tree = proto_item_add_subtree(ti, ett_bootp);

            proto_tree_add_uint(bp_tree, hf_bootp_type, tvb,
                                 0, 1,
                                 op);
            proto_tree_add_uint_format(bp_tree, hf_bootp_hw_type, tvb,
                                 1, 1,
                                 htype,
                                 "Hardware type: %s",
                                 arphrdtype_to_str(htype,
                                               "Unknown (0x%02x)"));
            proto_tree_add_uint(bp_tree, hf_bootp_hw_len, tvb,
                            2, 1, hlen);
            proto_tree_add_item(bp_tree, hf_bootp_hops, tvb,
                            3, 1, FALSE);
            proto_tree_add_item(bp_tree, hf_bootp_id, tvb,
                            4, 4, FALSE);
            proto_tree_add_item(bp_tree, hf_bootp_secs, tvb,
                            8, 2, FALSE);
            flags = tvb_get_ntohs(tvb, 10);
            fi = proto_tree_add_uint(bp_tree, hf_bootp_flags, tvb,
                            10, 2, flags);
            proto_item_append_text(fi, " (%s)",
                (flags & BOOTP_BC) ? "Broadcast" : "Unicast");
            flag_tree = proto_item_add_subtree(fi, ett_bootp_flags);
            proto_tree_add_boolean(flag_tree, hf_bootp_flags_broadcast, tvb,
                            10, 2, flags);
            proto_tree_add_uint(flag_tree, hf_bootp_flags_reserved, tvb,
                            10, 2, flags);
            proto_tree_add_item(bp_tree, hf_bootp_ip_client, tvb,
                            12, 4, FALSE);
            proto_tree_add_item(bp_tree, hf_bootp_ip_your, tvb,
                            16, 4, FALSE);
            proto_tree_add_item(bp_tree, hf_bootp_ip_server, tvb,
                            20, 4, FALSE);
            proto_tree_add_item(bp_tree, hf_bootp_ip_relay, tvb,
                            24, 4, FALSE);

            if (hlen > 0 && hlen <= 16) {
                  haddr = tvb_get_ptr(tvb, 28, hlen);
                  if ((htype == ARPHRD_ETHER || htype == ARPHRD_IEEE802)
                      && hlen == 6)
                        proto_tree_add_ether(bp_tree, hf_bootp_hw_ether_addr, tvb, 28, 6, haddr);
                  else
                        /* The chaddr element is 16 bytes in length,
                           although only the first hlen bytes are used */
                        proto_tree_add_bytes_format(bp_tree, hf_bootp_hw_addr, tvb,
                                       28, 16,
                                       haddr,
                                       "Client hardware address: %s",
                                       arphrdaddr_to_str(haddr,
                                                     hlen,
                                                     htype));
            }
            else {
                  proto_tree_add_text(bp_tree,  tvb,
                                       28, 16, "Client address not given");
            }

            /* The server host name is optional */
            if (tvb_get_guint8(tvb, 44) != '\0') {
                  proto_tree_add_item(bp_tree, hf_bootp_server, tvb,
                                       SERVER_NAME_OFFSET,
                                       SERVER_NAME_LEN, FALSE);
            }
            else {
                  proto_tree_add_string_format(bp_tree, hf_bootp_server, tvb,
                                       SERVER_NAME_OFFSET,
                                       SERVER_NAME_LEN,
                                       tvb_get_ptr(tvb, SERVER_NAME_OFFSET, 1),
                                       "Server host name not given");
            }

            /* Boot file */
            if (tvb_get_guint8(tvb, 108) != '\0') {
                  proto_tree_add_item(bp_tree, hf_bootp_file, tvb,
                                       FILE_NAME_OFFSET,
                                       FILE_NAME_LEN, FALSE);
            }
            else {
                  proto_tree_add_string_format(bp_tree, hf_bootp_file, tvb,
                                       FILE_NAME_OFFSET,
                                       FILE_NAME_LEN,
                                       tvb_get_ptr(tvb, FILE_NAME_OFFSET, 1),
                                       "Boot file name not given");
            }
      }

      voff = VENDOR_INFO_OFFSET;

      /* rfc2132 says it SHOULD exist, not that it MUST exist */
      if (tvb_bytes_exist(tvb, voff, 4)) {
            if (tvb_get_ntohl(tvb, voff) == 0x63825363) {
                  if (tree) {
                        tvb_memcpy(tvb, (void *)&ip_addr, voff, sizeof(ip_addr));
                        proto_tree_add_ipv4_format(bp_tree, hf_bootp_cookie, tvb,
                            voff, 4, ip_addr,
                            "Magic cookie: (OK)");
                  }
                  voff += 4;
            }
            else {
                  if (tree) {
                        proto_tree_add_text(bp_tree,  tvb,
                              voff, 64, "Bootp vendor specific options");
                  }
                  voff += 64;
            }
      }

      eoff = tvb_reported_length(tvb);

      /*
       * In the first pass, we just look for the DHCP message type
       * and Vendor class identifier options.
       */
      tmpvoff = voff;
      at_end = FALSE;
      while (tmpvoff < eoff && !at_end) {
            tmpvoff += bootp_option(tvb, 0, tmpvoff, eoff, TRUE, &at_end,
                &dhcp_type, &vendor_class_id);
      }

      /*
       * If there was a DHCP message type option, flag this packet
       * as DHCP.
       */
      if (dhcp_type != NULL) {
            /*
             * Yes, this is a DHCP packet, and "dhcp_type" is the
             * packet type.
             */
            if (check_col(pinfo->cinfo, COL_PROTOCOL))
                  col_set_str(pinfo->cinfo, COL_PROTOCOL, "DHCP");
            if (check_col(pinfo->cinfo, COL_INFO))
                  col_add_fstr(pinfo->cinfo, COL_INFO, "DHCP %-8s - Transaction ID 0x%x",
                      dhcp_type, tvb_get_ntohl(tvb, 4));
            if (tree)
                  proto_tree_add_boolean_hidden(bp_tree, hf_bootp_dhcp,
                      tvb, 0, 0, 1);
            tap_queue_packet( bootp_dhcp_tap, pinfo, dhcp_type);
      }

      /*
       * If we're not building the protocol tree, we don't need to
       * make a second pass.
       */
      if (tree == NULL)
            return;

      /*
       * OK, now build the protocol tree.
       */
      at_end = FALSE;
      while (voff < eoff && !at_end) {
            voff += bootp_option(tvb, bp_tree, voff, eoff, FALSE, &at_end,
                &dhcp_type, &vendor_class_id);
      }
      if (voff < eoff) {
            /*
             * Padding after the end option.
             */
            proto_tree_add_text(bp_tree, tvb, voff, eoff - voff, "Padding");
      }
}

void
proto_register_bootp(void)
{
  static hf_register_info hf[] = {
    { &hf_bootp_dhcp,
      { "Frame is DHCP",                "bootp.dhcp",    FT_BOOLEAN,
        BASE_NONE,                  NULL,        0x0,
        "", HFILL }},

    { &hf_bootp_type,
      { "Message type",             "bootp.type",      FT_UINT8,
         BASE_DEC,                  VALS(op_vals),   0x0,
            "", HFILL }},

    { &hf_bootp_hw_type,
      { "Hardware type",                  "bootp.hw.type", FT_UINT8,
        BASE_HEX,             NULL,        0x0,
            "", HFILL }},

    { &hf_bootp_hw_len,
      { "Hardware address length",  "bootp.hw.len",  FT_UINT8,
        BASE_DEC,             NULL,        0x0,
            "", HFILL }},

    { &hf_bootp_hops,
      { "Hops",                           "bootp.hops",      FT_UINT8,
        BASE_DEC,             NULL,        0x0,
            "", HFILL }},

    { &hf_bootp_id,
      { "Transaction ID",                 "bootp.id",  FT_UINT32,
        BASE_HEX,              NULL,             0x0,
            "", HFILL }},

    { &hf_bootp_secs,
      { "Seconds elapsed",                "bootp.secs",      FT_UINT16,
        BASE_DEC,              NULL,             0x0,
            "", HFILL }},

    { &hf_bootp_flags,
      { "Bootp flags",                    "bootp.flags",   FT_UINT16,
        BASE_HEX,             NULL,        0x0,
            "", HFILL }},

    { &hf_bootp_flags_broadcast,
      { "Broadcast flag",                 "bootp.flags.bc", FT_BOOLEAN,
        16,             TFS(&flag_set_broadcast), BOOTP_BC,
            "", HFILL }},

    { &hf_bootp_flags_reserved,
      { "Reserved flags",                 "bootp.flags.reserved", FT_UINT16,
        BASE_HEX,             NULL,       BOOTP_MBZ,
            "", HFILL }},

    { &hf_bootp_ip_client,
      { "Client IP address",              "bootp.ip.client",FT_IPv4,
        BASE_NONE,                  NULL,         0x0,
            "", HFILL }},

    { &hf_bootp_ip_your,
      { "Your (client) IP address", "bootp.ip.your",  FT_IPv4,
        BASE_NONE,                  NULL,         0x0,
            "", HFILL }},

    { &hf_bootp_ip_server,
      { "Next server IP address",   "bootp.ip.server",FT_IPv4,
        BASE_NONE,                  NULL,         0x0,
            "", HFILL }},

    { &hf_bootp_ip_relay,
      { "Relay agent IP address",   "bootp.ip.relay", FT_IPv4,
        BASE_NONE,                  NULL,         0x0,
            "", HFILL }},

    { &hf_bootp_hw_addr,
      { "Client hardware address",  "bootp.hw.addr", FT_BYTES,
        BASE_NONE,                  NULL,        0x0,
            "", HFILL }},

    { &hf_bootp_hw_ether_addr,
      { "Client MAC address",       "bootp.hw.mac_addr", FT_ETHER,
        BASE_NONE,                  NULL,        0x0,
            "", HFILL }},

    { &hf_bootp_server,
      { "Server host name",         "bootp.server",  FT_STRING,
        BASE_NONE,                  NULL,        0x0,
            "", HFILL }},

    { &hf_bootp_file,
      { "Boot file name",           "bootp.file",      FT_STRING,
        BASE_NONE,                  NULL,        0x0,
            "", HFILL }},

    { &hf_bootp_cookie,
      { "Magic cookie",             "bootp.cookie",    FT_IPv4,
         BASE_NONE,                 NULL,        0x0,
            "", HFILL }},

    { &hf_bootp_vendor,
      { "Bootp Vendor Options",           "bootp.vendor", FT_BYTES,
        BASE_NONE,                  NULL,        0x0,
            "", HFILL }},

    { &hf_bootp_fqdn_s,
      { "Server",       "bootp.fqdn.s",         FT_BOOLEAN,
        8,              TFS(&tfs_fqdn_s), F_FQDN_S,
            "If true, server should do DDNS update", HFILL }},

    { &hf_bootp_fqdn_o,
      { "Server overrides",   "bootp.fqdn.o",         FT_BOOLEAN,
        8,              TFS(&tfs_fqdn_o), F_FQDN_O,
            "If true, server insists on doing DDNS update", HFILL }},

    { &hf_bootp_fqdn_e,
      { "Encoding",     "bootp.fqdn.e",         FT_BOOLEAN,
        8,              TFS(&tfs_fqdn_e), F_FQDN_E,
            "If true, name is binary encoded", HFILL }},

    { &hf_bootp_fqdn_n,
      { "Server DDNS",  "bootp.fqdn.n",         FT_BOOLEAN,
        8,              TFS(&tfs_fqdn_n), F_FQDN_N,
            "If true, server should not do any DDNS updates", HFILL }},

    { &hf_bootp_fqdn_mbz,
      { "Reserved flags",     "bootp.fqdn.mbz", FT_UINT8,
        BASE_HEX,       NULL,             F_FQDN_MBZ,
            "", HFILL }},

    { &hf_bootp_fqdn_rcode1,
      { "A-RR result",              "bootp.fqdn.rcode1",     FT_UINT8,
        BASE_DEC,       NULL,              0x0,
            "Result code of A-RR update", HFILL }},

    { &hf_bootp_fqdn_rcode2,
      { "PTR-RR result",            "bootp.fqdn.rcode2",     FT_UINT8,
        BASE_DEC,       NULL,              0x0,
            "Result code of PTR-RR update", HFILL }},

    { &hf_bootp_fqdn_name,
      { "Client name",        "bootp.fqdn.name",      FT_BYTES,
        BASE_NONE,            NULL,             0x0,
            "Name to register via DDNS", HFILL }},

    { &hf_bootp_fqdn_asciiname,
      { "Client name",        "bootp.fqdn.name",      FT_STRING,
        BASE_NONE,            NULL,             0x0,
            "Name to register via DDNS", HFILL }},

    { &hf_bootp_pkt_mtacap_len,
      { "PacketCable MTA Device Capabilities Length", "bootp.vendor.pktc.mtacap_len",
        FT_UINT8, BASE_DEC, NULL, 0x0,
        "PacketCable MTA Device Capabilities Length", HFILL }},
    { &hf_bootp_docsis_cmcap_len,
      { "DOCSIS CM Device Capabilities Length", "bootp.vendor.docsis.cmcap_len",
        FT_UINT8, BASE_DEC, NULL, 0x0,
        "DOCSIS Cable Modem Device Capabilities Length", HFILL }},
  };

  static gint *ett[] = {
    &ett_bootp,
    &ett_bootp_flags,
    &ett_bootp_option,
    &ett_bootp_fqdn,
  };

  module_t *bootp_module;

  proto_bootp = proto_register_protocol("Bootstrap Protocol", "BOOTP/DHCP",
                              "bootp");
  proto_register_field_array(proto_bootp, hf, array_length(hf));
  proto_register_subtree_array(ett, array_length(ett));
  bootp_dhcp_tap = register_tap("bootp");

  bootp_module = prefs_register_protocol(proto_bootp, NULL);

  prefs_register_bool_preference(bootp_module, "novellserverstring",
    "Decode Option 85 as String",
    "Novell Servers option 85 can be configured as a string instead of address",
    &novell_string);

  prefs_register_enum_preference(bootp_module, "pkt.ccc.protocol_version",
    "PacketCable CCC protocol version",
    "The PacketCable CCC protocol version",
    &pkt_ccc_protocol_version,
    pkt_ccc_protocol_versions,
    FALSE);

  prefs_register_uint_preference(bootp_module, "pkt.ccc.option",
    "PacketCable CCC option",
    "Option Number for PacketCable CableLabs Client Configuration",
    10,
    &pkt_ccc_option);


}

void
proto_reg_handoff_bootp(void)
{
  dissector_handle_t bootp_handle;

  bootp_handle = create_dissector_handle(dissect_bootp, proto_bootp);
  dissector_add("udp.port", UDP_PORT_BOOTPS, bootp_handle);
  dissector_add("udp.port", UDP_PORT_BOOTPC, bootp_handle);
}

Generated by  Doxygen 1.6.0   Back to index