/* packet-dvmrp.c   2001 Ronnie Sahlberg <See AUTHORS for email>
 * Routines for IGMP/DVMRP packet disassembly
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */
/*


			DVMRP	DVMRP
	code		v1	v3

	0x01		*	*
	0x02		*	*
	0x03		x
	0x04		x
	0x07			x
	0x08			x
	0x09			x


	* V3 has len>=8 and byte[6]==0xff and byte[7]==0x03


	DVMRP is defined in the following RFCs
	RFC1075 Version 1
	draft-ietf-idmr-dvmrp-v3-10.txt Version 3

	V1 and V3 can be distinguished by looking at bytes 6 and 7 in the
	IGMP/DVMRP header.
	If header[6]==0xff and header[7]==0x03 we have version 3.


	RFC1075 has typos in 3.12.2 and 3.12.4, see if you can spot them.
*/

#include "config.h"

#include <epan/packet.h>
#include <epan/prefs.h>
#include <epan/expert.h>
#include "packet-igmp.h"

void proto_register_dvmrp(void);
void proto_reg_handoff_dvmrp(void);

static int proto_dvmrp = -1;
static int hf_version = -1;
static int hf_type = -1;
static int hf_code_v1 = -1;
static int hf_checksum = -1;
static int hf_checksum_status = -1;
static int hf_reserved = -1;
static int hf_commands = -1;
static int hf_command = -1;
static int hf_count = -1;
static int hf_afi = -1;
static int hf_netmask = -1;
static int hf_metric = -1;
static int hf_dest_unr = -1;
static int hf_split_horiz = -1;
static int hf_infinity = -1;
static int hf_daddr = -1;
static int hf_maddr = -1;
static int hf_hold = -1;
static int hf_code_v3 = -1;
static int hf_capabilities = -1;
static int hf_cap_leaf = -1;
static int hf_cap_prune = -1;
static int hf_cap_genid = -1;
static int hf_cap_mtrace = -1;
static int hf_cap_snmp = -1;
static int hf_cap_netmask = -1;
static int hf_min_ver = -1;
static int hf_maj_ver = -1;
static int hf_genid = -1;
static int hf_route = -1;
static int hf_saddr = -1;
static int hf_life = -1;
static int hf_local = -1;
static int hf_threshold = -1;
static int hf_flags = -1;
static int hf_flag_tunnel = -1;
static int hf_flag_srcroute = -1;
static int hf_flag_down = -1;
static int hf_flag_disabled = -1;
static int hf_flag_querier = -1;
static int hf_flag_leaf = -1;
static int hf_ncount = -1;
static int hf_neighbor = -1;

static int ett_dvmrp = -1;
static int ett_commands = -1;
static int ett_capabilities = -1;
static int ett_flags = -1;
static int ett_route = -1;

static expert_field ei_checksum = EI_INIT;

static int strict_v3 = FALSE;

#define DVMRP_TYPE				0x13
static const value_string dvmrp_type[] = {
	{DVMRP_TYPE,	"DVMRP"	},
	{0,		NULL}
};

#define DVMRP_V1_RESPONSE			1
#define DVMRP_V1_REQUEST			2
#define DVMRP_V1_NON_MEMBERSHIP_REPORT		3
#define DVMRP_V1_NON_MEMBERSHIP_CANCELLATION	4
static const value_string code_v1[] = {
	{DVMRP_V1_RESPONSE,			"Response"			},
	{DVMRP_V1_REQUEST,			"Request"			},
	{DVMRP_V1_NON_MEMBERSHIP_REPORT,	"Non-membership report"		},
	{DVMRP_V1_NON_MEMBERSHIP_CANCELLATION,	"Non-membership cancellation"	},
	{0,					NULL}
};

#define DVMRP_V3_PROBE				0x1
#define DVMRP_V3_REPORT				0x2
#define DVMRP_V3_ASK_NEIGHBORS			0x3
#define DVMRP_V3_NEIGHBORS			0x4
#define DVMRP_V3_ASK_NEIGHBORS_2		0x5
#define DVMRP_V3_NEIGHBORS_2			0x6
#define DVMRP_V3_PRUNE				0x7
#define DVMRP_V3_GRAFT				0x8
#define DVMRP_V3_GRAFT_ACK			0x9
static const value_string code_v3[] = {
	{DVMRP_V3_PROBE,		"Probe"},
	{DVMRP_V3_REPORT,		"Report"},
	{DVMRP_V3_ASK_NEIGHBORS,	"Ask Neighbors"},
	{DVMRP_V3_NEIGHBORS,		"Neighbors"},
	{DVMRP_V3_ASK_NEIGHBORS_2,	"Ask Neighbors 2"},
	{DVMRP_V3_NEIGHBORS_2,		"Neighbors 2"},
	{DVMRP_V3_PRUNE,		"Prune"},
	{DVMRP_V3_GRAFT,		"Graft"},
	{DVMRP_V3_GRAFT_ACK,		"Graft ACK"},
	{0,				NULL}
};

#define DVMRP_V3_CAP_LEAF	0x01
#define DVMRP_V3_CAP_PRUNE	0x02
#define DVMRP_V3_CAP_GENID	0x04
#define DVMRP_V3_CAP_MTRACE	0x08
#define DVMRP_V3_CAP_SNMP	0x10
#define DVMRP_V3_CAP_NETMASK	0x20

#define DVMRP_V3_FLAG_TUNNEL	0x01
#define DVMRP_V3_FLAG_SRCROUTE	0x02
#define DVMRP_V3_FLAG_DOWN	0x10
#define DVMRP_V3_FLAG_DISABLED	0x20
#define DVMRP_V3_FLAG_QUERIER	0x40
#define DVMRP_V3_FLAG_LEAF	0x80


#define V1_COMMAND_NULL		0
#define V1_COMMAND_AFI		2
#define V1_COMMAND_SUBNETMASK	3
#define V1_COMMAND_METRIC	4
#define V1_COMMAND_FLAGS0	5
#define V1_COMMAND_INFINITY	6
#define V1_COMMAND_DA		7
#define V1_COMMAND_RDA		8
#define V1_COMMAND_NMR		9
#define V1_COMMAND_NMR_CANCEL	10
static const value_string command[] = {
	{V1_COMMAND_NULL,	"NULL"	},
	{V1_COMMAND_AFI,	"Address Family Indicator"},
	{V1_COMMAND_SUBNETMASK,	"Subnetmask"},
	{V1_COMMAND_METRIC,	"Metric"},
	{V1_COMMAND_FLAGS0,	"Flags0"},
	{V1_COMMAND_INFINITY,	"Infinity"},
	{V1_COMMAND_DA,		"Destination Address"},
	{V1_COMMAND_RDA,	"Requested Destination Address"},
	{V1_COMMAND_NMR,	"Non-Membership Report"},
	{V1_COMMAND_NMR_CANCEL,	"Non-Membership Report Cancel"},
	{0,			NULL}
};

#define V1_AFI_IP		2
static const value_string afi[] = {
	{V1_AFI_IP,	"IP v4 Family"},
	{0,		NULL}
};

static const true_false_string tfs_dest_unreach = {
	"Destination Unreachable",
	"NOT Destination Unreachable"
};

static const true_false_string tfs_split_horiz = {
	"Split Horizon concealed route",
	"NOT Split Horizon concealed route"
};

static const true_false_string tfs_cap_leaf = {
	"Leaf",
	"NOT Leaf"
};
static const true_false_string tfs_cap_prune = {
	"Prune capable",
	"NOT Prune capable"
};
static const true_false_string tfs_cap_genid = {
	"Genid capable",
	"NOT Genid capable"
};
static const true_false_string tfs_cap_mtrace = {
	"Multicast Traceroute capable",
	"NOT Multicast Traceroute capable"
};
static const true_false_string tfs_cap_snmp = {
	"SNMP capable",
	"NOT SNMP capable"
};
static const true_false_string tfs_cap_netmask = {
	"Netmask capable",
	"NOT Netmask capable"
};

static int
dissect_v3_report(tvbuff_t *tvb, proto_tree *parent_tree, int offset)
{
	guint8 m0,m1,m2,m3;
	guint8 s0,s1,s2,s3;
	guint8 metric;
	guint32 ip;

	while (tvb_reported_length_remaining(tvb, offset) > 0) {
		proto_tree *tree;
		proto_item *item;
		int old_offset_a = offset;

		item = proto_tree_add_item(parent_tree, hf_route,
				tvb, offset, -1, ENC_NA);
		tree = proto_item_add_subtree(item, ett_route);

		m0 = 0xff;
		/* read the mask */
		m1 = tvb_get_guint8(tvb, offset);
		m2 = tvb_get_guint8(tvb, offset+1);
		m3 = tvb_get_guint8(tvb, offset+2);

		ip = m3;
		ip = (ip<<8)|m2;
		ip = (ip<<8)|m1;
		ip = (ip<<8)|m0;
		proto_tree_add_ipv4(tree, hf_netmask, tvb, offset, 3, ip);

		offset += 3;

		/* read every srcnet, metric  pairs */
		do {
			int old_offset_b = offset;
			m0 = 0xff;

			s1 = 0;
			s2 = 0;
			s3 = 0;

			s0 = tvb_get_guint8(tvb, offset);
			offset += 1;
			if (m1) {
				s1 = tvb_get_guint8(tvb, offset);
				offset += 1;
			}
			if (m2) {
				s2 = tvb_get_guint8(tvb, offset);
				offset += 1;
			}
			if (m3) {
				s3 = tvb_get_guint8(tvb, offset);
				offset += 1;
			}

			/* handle special case for default route V3/3.4.3 */
			if ((!m1)&&(!m2)&&(!m3)&&(!s0)) {
				m0 = 0;
			}

			ip = s3;
			ip = (ip<<8)|s2;
			ip = (ip<<8)|s1;
			ip = (ip<<8)|s0;
			proto_tree_add_ipv4_format(tree, hf_saddr, tvb,
				old_offset_b, offset-old_offset_b, ip,
				"%s %d.%d.%d.%d (netmask %d.%d.%d.%d)",
				m0?"Source Network":"Default Route",
				s0,s1,s2,s3,m0,m1,m2,m3);

			metric = tvb_get_guint8(tvb, offset);
			proto_tree_add_uint(tree, hf_metric, tvb,
				offset, 1, metric&0x7f);
			offset += 1;


		} while (!(metric&0x80));

		proto_item_set_len(item, offset-old_offset_a);
	}

	return offset;
}

static int
dissect_dvmrp_v3(tvbuff_t *tvb, packet_info *pinfo, proto_tree *parent_tree, int offset)
{
	guint8 code;

	/* version */
	proto_tree_add_uint(parent_tree, hf_version, tvb, 0, 0, 3);

	/* type of command */
	proto_tree_add_uint(parent_tree, hf_type, tvb, offset, 1, 0x13);
	offset += 1;

	/* code */
	code = tvb_get_guint8(tvb, offset);
	proto_tree_add_uint(parent_tree, hf_code_v3, tvb, offset, 1, code);
	offset += 1;
	col_add_fstr(pinfo->cinfo, COL_INFO,
			"V%d %s",3 ,val_to_str(code, code_v3,
				"Unknown Type:0x%02x"));

	/* checksum */
	igmp_checksum(parent_tree, tvb, hf_checksum, hf_checksum_status, &ei_checksum, pinfo, 0);
	offset += 2;

	/* skip unused byte */
	proto_tree_add_item(parent_tree, hf_reserved, tvb, offset, 2, ENC_BIG_ENDIAN);
	offset += 1;

	/* PROBE and NEIGHBORS 2 packets have capabilities flags, unused
	   for other packets */
	if (code==DVMRP_V3_PROBE || code==DVMRP_V3_NEIGHBORS_2) {
		static int * const capabilities[] = {
			&hf_cap_netmask,
			&hf_cap_snmp,
			&hf_cap_mtrace,
			&hf_cap_genid,
			&hf_cap_prune,
			&hf_cap_leaf,
			NULL
		};

		proto_tree_add_bitmask(parent_tree, tvb, offset, hf_capabilities,
			       ett_capabilities, capabilities, ENC_NA);
	}
	offset += 1;

	/* minor version */
	proto_tree_add_item(parent_tree, hf_min_ver, tvb, offset, 1, ENC_BIG_ENDIAN);
	offset += 1;

	/* major version */
	proto_tree_add_item(parent_tree, hf_maj_ver, tvb, offset, 1, ENC_BIG_ENDIAN);
	offset += 1;

	switch (code) {
	case DVMRP_V3_PROBE:
		/* generation id */
		proto_tree_add_item(parent_tree, hf_genid, tvb,
			offset, 4, ENC_BIG_ENDIAN);
		offset += 4;
		while (tvb_reported_length_remaining(tvb, offset)>=4) {
			proto_tree_add_item(parent_tree, hf_neighbor,
				tvb, offset, 4, ENC_BIG_ENDIAN);
			offset += 4;
		}
		break;
	case DVMRP_V3_REPORT:
		offset = dissect_v3_report(tvb, parent_tree, offset);
		break;
	case DVMRP_V3_PRUNE:
		/* source address */
		proto_tree_add_item(parent_tree, hf_saddr,
			tvb, offset, 4, ENC_BIG_ENDIAN);
		offset += 4;
		/* group address */
		proto_tree_add_item(parent_tree, hf_maddr,
			tvb, offset, 4, ENC_BIG_ENDIAN);
		offset += 4;
		/* prune lifetime */
		proto_tree_add_item(parent_tree, hf_life,
			tvb, offset, 4, ENC_BIG_ENDIAN);
		offset += 4;
		/* source netmask */
		if (tvb_reported_length_remaining(tvb, offset)>=4) {
			proto_tree_add_item(parent_tree, hf_netmask,
				tvb, offset, 4, ENC_BIG_ENDIAN);
			offset += 4;
		}
		break;
	case DVMRP_V3_GRAFT:
		/* source address */
		proto_tree_add_item(parent_tree, hf_saddr,
			tvb, offset, 4, ENC_BIG_ENDIAN);
		offset += 4;
		/* group address */
		proto_tree_add_item(parent_tree, hf_maddr,
			tvb, offset, 4, ENC_BIG_ENDIAN);
		offset += 4;
		/* source netmask */
		if (tvb_reported_length_remaining(tvb, offset)>=4) {
			proto_tree_add_item(parent_tree, hf_netmask,
				tvb, offset, 4, ENC_BIG_ENDIAN);
			offset += 4;
		}
		break;
	case DVMRP_V3_GRAFT_ACK:
		/* source address */
		proto_tree_add_item(parent_tree, hf_saddr,
			tvb, offset, 4, ENC_BIG_ENDIAN);
		offset += 4;
		/* group address */
		proto_tree_add_item(parent_tree, hf_maddr,
			tvb, offset, 4, ENC_BIG_ENDIAN);
		offset += 4;
		/* source netmask */
		if (tvb_reported_length_remaining(tvb, offset)>=4) {
			proto_tree_add_item(parent_tree, hf_netmask,
				tvb, offset, 4, ENC_BIG_ENDIAN);
			offset += 4;
		}
		break;
	case DVMRP_V3_ASK_NEIGHBORS:
	case DVMRP_V3_NEIGHBORS:
		/* XXX - obsolete, and the draft doesn't describe them */
		break;
	case DVMRP_V3_ASK_NEIGHBORS_2:
		/* No data */
		break;
	case DVMRP_V3_NEIGHBORS_2:
		while (tvb_reported_length_remaining(tvb, offset)>=12) {
			guint8 neighbor_count;

			/* local address */
			proto_tree_add_item(parent_tree, hf_local,
				tvb, offset, 4, ENC_BIG_ENDIAN);
			offset += 4;
			/* Metric */
			proto_tree_add_item(parent_tree, hf_metric,
				tvb, offset, 1, ENC_BIG_ENDIAN);
			offset += 1;
			/* Threshold */
			proto_tree_add_item(parent_tree, hf_threshold,
				tvb, offset, 1, ENC_BIG_ENDIAN);
			offset += 1;
			/* Flags */
			{
				proto_tree *tree;
				proto_item *item;

				item = proto_tree_add_item(parent_tree, hf_flags,
					tvb, offset, 1, ENC_NA);
				tree = proto_item_add_subtree(item, ett_flags);

				proto_tree_add_item(tree, hf_flag_tunnel, tvb,
					offset, 1, ENC_BIG_ENDIAN);
				proto_tree_add_item(tree, hf_flag_srcroute, tvb,
					offset, 1, ENC_BIG_ENDIAN);
				proto_tree_add_item(tree, hf_flag_down, tvb,
					offset, 1, ENC_BIG_ENDIAN);
				proto_tree_add_item(tree, hf_flag_disabled, tvb,
					offset, 1, ENC_BIG_ENDIAN);
				proto_tree_add_item(tree, hf_flag_querier, tvb,
					offset, 1, ENC_BIG_ENDIAN);
				proto_tree_add_item(tree, hf_flag_leaf, tvb,
					offset, 1, ENC_BIG_ENDIAN);
			}
			offset += 1;
			/* Neighbor count */
			neighbor_count = tvb_get_guint8(tvb, offset);
			proto_tree_add_item(parent_tree, hf_ncount,
				tvb, offset, 1, ENC_BIG_ENDIAN);
			offset += 1;

			while ((tvb_reported_length_remaining(tvb, offset)>=4)
				&& (neighbor_count>0)) {
				proto_tree_add_item(parent_tree, hf_neighbor,
					tvb, offset, 4, ENC_BIG_ENDIAN);
				offset += 4;
				neighbor_count--;
			}
		}
		break;
	}

	return offset;
}


static int
dissect_dvmrp_v1(tvbuff_t *tvb, packet_info *pinfo, proto_tree *parent_tree, int offset)
{
	guint8 code;
	guint8 af=2; /* default */

	/* version */
	proto_tree_add_uint(parent_tree, hf_version, tvb, 0, 0, 1);

	/* type of command */
	proto_tree_add_uint(parent_tree, hf_type, tvb, offset, 1, 0x13);
	offset += 1;

	/* code */
	code = tvb_get_guint8(tvb, offset);
	proto_tree_add_uint(parent_tree, hf_code_v1, tvb, offset, 1, code);
	offset += 1;
	col_add_fstr(pinfo->cinfo, COL_INFO,
			"V%d %s",1 ,val_to_str(code, code_v1,
				"Unknown Type:0x%02x"));

	/* checksum */
	igmp_checksum(parent_tree, tvb, hf_checksum, hf_checksum_status, &ei_checksum, pinfo, 0);
	offset += 2;

	/* decode all the v1 commands */
	while (tvb_reported_length_remaining(tvb, offset) > 0) {
		proto_tree *tree;
		proto_item *item;
		guint8 cmd,count;
		int old_offset = offset;

		item = proto_tree_add_item(parent_tree, hf_commands,
				tvb, offset, -1, ENC_NA);
		tree = proto_item_add_subtree(item, ett_commands);

		cmd = tvb_get_guint8(tvb, offset);
		proto_tree_add_uint(tree, hf_command, tvb,
			offset, 1, cmd);
		offset += 1;

		switch (cmd){
		case V1_COMMAND_NULL:
			offset += 1; /* skip ignored/pad byte*/
			if (item) {
				proto_item_set_text(item, "Command: NULL");
			}
			break;
		case V1_COMMAND_AFI:
			af = tvb_get_guint8(tvb, offset);
			proto_tree_add_uint(tree, hf_afi, tvb,
				offset, 1, af);
			offset += 1;
			if (item) {
				proto_item_set_text(item, "%s: %s",
					val_to_str(cmd, command, "Unknown Command:0x%02x"),
					val_to_str(af, afi, "Unknown Family:0x%02x")
				);
			}
			break;
		case V1_COMMAND_SUBNETMASK:
			count = tvb_get_guint8(tvb, offset);
			proto_tree_add_uint(tree, hf_count, tvb,
				offset, 1, count);
			offset += 1;
			if (count) { /* must be 0 or 1 */
				proto_tree_add_item(tree, hf_netmask,
					tvb, offset, 4, ENC_BIG_ENDIAN);
				if (item) {
					proto_item_set_text(item, "%s: %d.%d.%d.%d",
						val_to_str(cmd, command, "Unknown Command:0x%02x"),
						tvb_get_guint8(tvb, offset),
						tvb_get_guint8(tvb, offset+1),
						tvb_get_guint8(tvb, offset+2),
						tvb_get_guint8(tvb, offset+3));
				}
				offset += 4;
			} else {
				if (item) {
					proto_item_set_text(item, "%s: <no mask supplied>",
						val_to_str(cmd, command, "Unknown Command:0x%02x"));
				}
			}
			break;
		case V1_COMMAND_METRIC:
			proto_tree_add_item(tree, hf_metric, tvb,
				offset, 1, ENC_BIG_ENDIAN);
			if (item) {
				proto_item_set_text(item, "%s: %d",
					val_to_str(cmd, command, "Unknown Command:0x%02x"),
					tvb_get_guint8(tvb, offset));
			}
			offset += 1;
			break;
		case V1_COMMAND_FLAGS0:
			count = tvb_get_guint8(tvb, offset);
			proto_tree_add_boolean(tree, hf_dest_unr, tvb, offset, 1, count);
			proto_tree_add_boolean(tree, hf_split_horiz, tvb, offset, 1, count);
			if (item) {
				proto_item_set_text(item, "%s: 0x%02x",
					val_to_str(cmd, command, "Unknown Command:0x%02x"), count);
			}
			offset += 1;
			break;
		case V1_COMMAND_INFINITY:
			proto_tree_add_item(tree, hf_infinity, tvb,
				offset, 1, ENC_BIG_ENDIAN);
			if (item) {
				proto_item_set_text(item, "%s: %d",
					val_to_str(cmd, command, "Unknown Command:0x%02x"), tvb_get_guint8(tvb, offset));
			}
			offset += 1;
			break;
		case V1_COMMAND_DA:
		case V1_COMMAND_RDA: /* same as DA */
			count = tvb_get_guint8(tvb, offset);
			proto_tree_add_uint(tree, hf_count, tvb,
				offset, 1, count);
			offset += 1;
			while (count--) {
				proto_tree_add_item(tree, hf_daddr,
					tvb, offset, 4, ENC_BIG_ENDIAN);
				offset += 4;
			}
			if (item) {
				proto_item_set_text(item, "%s",
					val_to_str(cmd, command, "Unknown Command:0x%02x"));
			}
			break;
		case V1_COMMAND_NMR:
			count = tvb_get_guint8(tvb, offset);
			proto_tree_add_uint(tree, hf_count, tvb,
				offset, 1, count);
			offset += 1;
			while (count--) {
				proto_tree_add_item(tree, hf_maddr,
					tvb, offset, 4, ENC_BIG_ENDIAN);
				offset += 4;
				proto_tree_add_item(tree, hf_hold, tvb,
					offset, 4, ENC_BIG_ENDIAN);
				offset += 4;
			}
			if (item) {
				proto_item_set_text(item, "%s",
					val_to_str(cmd, command, "Unknown Command:0x%02x"));
			}
			break;
		case V1_COMMAND_NMR_CANCEL:
			count = tvb_get_guint8(tvb, offset);
			proto_tree_add_uint(tree, hf_count, tvb,
				offset, 1, count);
			offset += 1;
			while (count--) {
				proto_tree_add_item(tree, hf_maddr,
					tvb, offset, 4, ENC_BIG_ENDIAN);
				offset += 4;
			}
			if (item) {
				proto_item_set_text(item, "%s",
					val_to_str(cmd, command, "Unknown Command:0x%02x"));
			}
			break;
		}

		proto_item_set_len(item, offset-old_offset);
	}

	return offset;
}

/* This function is only called from the IGMP dissector */
static int
dissect_dvmrp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *parent_tree, void* data _U_)
{
	proto_tree *tree;
	proto_item *item;
	int offset = 0;

	col_set_str(pinfo->cinfo, COL_PROTOCOL, "DVMRP");
	col_clear(pinfo->cinfo, COL_INFO);

	item = proto_tree_add_item(parent_tree, proto_dvmrp, tvb, offset, -1, ENC_NA);
	tree = proto_item_add_subtree(item, ett_dvmrp);

	if ((tvb_captured_length_remaining(tvb, offset)>=8)
	 && (((tvb_get_guint8(tvb, 6)==0xff)
	 && (tvb_get_guint8(tvb, 7)==0x03))
	     || !strict_v3)) {
		offset = dissect_dvmrp_v3(tvb, pinfo, tree, offset);
	} else {
		offset = dissect_dvmrp_v1(tvb, pinfo, tree, offset);
	}

	proto_item_set_len(item, offset);
	return offset;
}

void
proto_register_dvmrp(void)
{
	static hf_register_info hf[] = {
		{ &hf_version,
			{ "DVMRP Version", "dvmrp.version", FT_UINT8, BASE_DEC,
			  NULL, 0, NULL, HFILL }},

		{ &hf_type,
			{ "Type", "dvmrp.type", FT_UINT8, BASE_HEX,
			  VALS(dvmrp_type), 0, "DVMRP Packet Type", HFILL }},

		{ &hf_code_v1,
			{ "Code", "dvmrp.v1.code", FT_UINT8, BASE_HEX,
			  VALS(code_v1), 0, "DVMRP Packet Code", HFILL }},

		{ &hf_checksum,
			{ "Checksum", "dvmrp.checksum", FT_UINT16, BASE_HEX,
			  NULL, 0, "DVMRP Checksum", HFILL }},

		{ &hf_checksum_status,
			{ "Checksum Status", "dvmrp.checksum.status", FT_UINT8, BASE_NONE,
			  VALS(proto_checksum_vals), 0x0, NULL, HFILL }},

		{ &hf_reserved,
			{ "Reserved", "dvmrp.reserved", FT_UINT16, BASE_HEX,
			  NULL, 0, "DVMRP v3 Reserved", HFILL }},

		{ &hf_commands,
			{ "Commands", "dvmrp.commands", FT_NONE, BASE_NONE,
			  NULL, 0, "DVMRP V1 Commands", HFILL }},

		{ &hf_command,
			{ "Command", "dvmrp.command", FT_UINT8, BASE_HEX,
			  VALS(command), 0, "DVMRP V1 Command", HFILL }},

		{ &hf_afi,
			{ "Address Family", "dvmrp.afi", FT_UINT8, BASE_HEX,
			  VALS(afi), 0, "DVMRP Address Family Indicator", HFILL }},

		{ &hf_count,
			{ "Count", "dvmrp.count", FT_UINT8, BASE_HEX,
			  NULL, 0, NULL, HFILL }},

		{ &hf_netmask,
			{ "Netmask", "dvmrp.netmask", FT_IPv4, BASE_NETMASK,
			  NULL, 0, "DVMRP Netmask", HFILL }},

		{ &hf_metric,
			{ "Metric", "dvmrp.metric", FT_UINT8, BASE_DEC,
			  NULL, 0, "DVMRP Metric", HFILL }},

		{&hf_dest_unr,
			{ "Destination Unreachable", "dvmrp.dest_unreach", FT_BOOLEAN, 8,
			TFS(&tfs_dest_unreach), 0x01, NULL, HFILL }},

		{&hf_split_horiz,
			{ "Split Horizon", "dvmrp.split_horiz", FT_BOOLEAN, 8,
			TFS(&tfs_split_horiz), 0x02, "Split Horizon concealed route", HFILL }},

		{ &hf_infinity,
			{ "Infinity", "dvmrp.infinity", FT_UINT8, BASE_DEC,
			  NULL, 0, "DVMRP Infinity", HFILL }},

		{ &hf_daddr,
			{ "Dest Addr", "dvmrp.daddr", FT_IPv4, BASE_NONE,
			  NULL, 0, "DVMRP Destination Address", HFILL }},

		{ &hf_maddr,
			{ "Multicast Addr", "dvmrp.maddr", FT_IPv4, BASE_NONE,
			  NULL, 0, "DVMRP Multicast Address", HFILL }},

		{ &hf_hold,
			{ "Hold Time", "dvmrp.hold", FT_UINT32, BASE_DEC,
			  NULL, 0, "DVMRP Hold Time in seconds", HFILL }},

		{ &hf_code_v3,
			{ "Code", "dvmrp.v3.code", FT_UINT8, BASE_HEX,
			  VALS(code_v3), 0, "DVMRP Packet Code", HFILL }},

		{ &hf_capabilities,
			{ "Capabilities", "dvmrp.capabilities", FT_UINT8, BASE_HEX,
			  NULL, 0, "DVMRP V3 Capabilities", HFILL }},

		{&hf_cap_leaf,
			{ "Leaf", "dvmrp.cap.leaf", FT_BOOLEAN, 8,
			TFS(&tfs_cap_leaf), DVMRP_V3_CAP_LEAF, NULL, HFILL }},

		{&hf_cap_prune,
			{ "Prune", "dvmrp.cap.prune", FT_BOOLEAN, 8,
			TFS(&tfs_cap_prune), DVMRP_V3_CAP_PRUNE, "Prune capability", HFILL }},

		{&hf_cap_genid,
			{ "Genid", "dvmrp.cap.genid", FT_BOOLEAN, 8,
			TFS(&tfs_cap_genid), DVMRP_V3_CAP_GENID, "Genid capability", HFILL }},

		{&hf_cap_mtrace,
			{ "Mtrace", "dvmrp.cap.mtrace", FT_BOOLEAN, 8,
			TFS(&tfs_cap_mtrace), DVMRP_V3_CAP_MTRACE, "Mtrace capability", HFILL }},

		{&hf_cap_snmp,
			{ "SNMP", "dvmrp.cap.snmp", FT_BOOLEAN, 8,
			TFS(&tfs_cap_snmp), DVMRP_V3_CAP_SNMP, "SNMP capability", HFILL }},

		{&hf_cap_netmask,
			{ "Netmask", "dvmrp.cap.netmask", FT_BOOLEAN, 8,
			TFS(&tfs_cap_netmask), DVMRP_V3_CAP_NETMASK, "Netmask capability", HFILL }},

		{ &hf_min_ver,
			{ "Minor Version", "dvmrp.min_ver", FT_UINT8, BASE_HEX,
			  NULL, 0, "DVMRP Minor Version", HFILL }},

		{ &hf_maj_ver,
			{ "Major Version", "dvmrp.maj_ver", FT_UINT8, BASE_HEX,
			  NULL, 0, "DVMRP Major Version", HFILL }},

		{ &hf_genid,
			{ "Generation ID", "dvmrp.genid", FT_UINT32, BASE_DEC,
			  NULL, 0, "DVMRP Generation ID", HFILL }},

		{ &hf_route,
			{ "Route", "dvmrp.route", FT_NONE, BASE_NONE,
			  NULL, 0, "DVMRP V3 Route Report", HFILL }},

		{ &hf_saddr,
			{ "Source Addr", "dvmrp.saddr", FT_IPv4, BASE_NONE,
			  NULL, 0, "DVMRP Source Address", HFILL }},

		{ &hf_life,
			{ "Prune lifetime", "dvmrp.lifetime", FT_UINT32, BASE_DEC,
			  NULL, 0, "DVMRP Prune Lifetime", HFILL }},

		{ &hf_local,
			{ "Local Addr", "dvmrp.local", FT_IPv4, BASE_NONE,
			  NULL, 0, "DVMRP Local Address", HFILL }},

		{ &hf_threshold,
			{ "Threshold", "dvmrp.threshold", FT_UINT8, BASE_DEC,
			NULL, 0, "DVMRP Interface Threshold", HFILL }},

		{ &hf_flags,
			{ "Flags", "dvmrp.flags", FT_NONE, BASE_NONE,
			NULL, 0, "DVMRP Interface Flags", HFILL }},

		{ &hf_flag_tunnel,
			{ "Tunnel", "dvmrp.flag.tunnel", FT_BOOLEAN, 8,
			NULL, DVMRP_V3_FLAG_TUNNEL, "Neighbor reached via tunnel", HFILL }},

		{ &hf_flag_srcroute,
			{ "Source Route", "dvmrp.flag.srcroute", FT_BOOLEAN, 8,
			NULL, DVMRP_V3_FLAG_SRCROUTE, "Tunnel uses IP source routing", HFILL }},

		{ &hf_flag_down,
			{ "Down", "dvmrp.flag.down", FT_BOOLEAN, 8,
			NULL, DVMRP_V3_FLAG_DOWN, "Operational status down", HFILL }},

		{ &hf_flag_disabled,
			{ "Disabled", "dvmrp.flag.disabled", FT_BOOLEAN, 8,
			NULL, DVMRP_V3_FLAG_DISABLED, "Administrative status down", HFILL }},

		{ &hf_flag_querier,
			{ "Querier", "dvmrp.flag.querier", FT_BOOLEAN, 8,
			NULL, DVMRP_V3_FLAG_QUERIER, "Querier for interface", HFILL }},

		{ &hf_flag_leaf,
			{ "Leaf", "dvmrp.flag.leaf", FT_BOOLEAN, 8,
			NULL, DVMRP_V3_FLAG_LEAF, "No downstream neighbors on interface", HFILL }},

		{ &hf_ncount,
			{ "Neighbor Count", "dvmrp.ncount", FT_UINT8, BASE_DEC,
			NULL, 0, "DVMRP Neighbor Count", HFILL }},

		{ &hf_neighbor,
			{ "Neighbor Addr", "dvmrp.neighbor", FT_IPv4, BASE_NONE,
			  NULL, 0, "DVMRP Neighbor Address", HFILL }}
	};
	static gint *ett[] = {
		&ett_dvmrp,
		&ett_commands,
		&ett_capabilities,
		&ett_flags,
		&ett_route
	};

	static ei_register_info ei[] = {
		{ &ei_checksum, { "dvmrp.bad_checksum", PI_CHECKSUM, PI_ERROR, "Bad checksum", EXPFILL }},
	};

	expert_module_t* expert_dvmrp;

	module_t *module_dvmrp;

	proto_dvmrp = proto_register_protocol("Distance Vector Multicast Routing Protocol", "DVMRP", "dvmrp");
	proto_register_field_array(proto_dvmrp, hf, array_length(hf));
	proto_register_subtree_array(ett, array_length(ett));
	expert_dvmrp = expert_register_protocol(proto_dvmrp);
	expert_register_field_array(expert_dvmrp, ei, array_length(ei));

	module_dvmrp = prefs_register_protocol(proto_dvmrp, NULL);

	prefs_register_bool_preference(module_dvmrp, "strict_v3", "Allow strict DVMRP V3 only",
		"Allow only packets with Major=0x03//Minor=0xFF as DVMRP V3 packets",
		&strict_v3);
}

void
proto_reg_handoff_dvmrp(void)
{
	dissector_handle_t dvmrp_handle;

	dvmrp_handle = create_dissector_handle(dissect_dvmrp, proto_dvmrp);
	dissector_add_uint("igmp.type", IGMP_DVMRP, dvmrp_handle);
}


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