Ethereal-dev: [Ethereal-dev] Re: Armagetron Advanced dissector

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

From: Guillaume Chazarain <guichaz@xxxxxxxx>
Date: Sat, 14 May 2005 22:03:22 +0200


1,
dont use  armagetronad_descriptor,    use
a value_string as all other dissectors do.
That will remove the need for get_descriptor() as well since you would
use val_to_str() instead.
OK, Done. Anyway, it was a premature optimization, so not much is lost.

2,
add_message_data() : rewrite it completely.
I don't understand the motivation for this one.

Do not use tvb_memdup(), do not read directly from this buffer.
I thought tvb_memdup() was specifically created so as to read the packet data
directly from a pointer without worrying about the packet being too short.
And given the maximum size of a UDP packet I was not afraid of using
too much memory. Confused ...

IF the fields are of type little-endian 16 bit integers,   read them
one by one using tvb_get_letohs()
Here things start to complicate. This 'Data' field as its name implies has no precise syntax. Typically it contains 16 bit integers displayed perfectly in ethereal hex dump, but sometimes it contains a string with bytes swapped (don't ask me why) so I swap them to make the string readable. So basically, it's a char* with bytes swapped, and I was quite happy with my handling ...

3,
dissect_armagetronad()
change this dissector to become a "new" style dissector   i.e. first
doing some heuristics and testing if it really is armagetroinad or not
and return 0 if not.
see packet-snmp.c for how this is done for snmp over udp.
OK, done. Hopefully I got it right.

4,
Do you really need to specify it as a heuristic dissector as well?
No I don't really need it.

If it uses a fixed port normally, just leave it as a normal dissector
Yes the port is usually fixed.

attached to a port, then if the user wants to, he can always do a
DecodeAs and specify a different non-standard port for it.
I was targetting a different use case : an admin wants to know what the network is used for. Ethereal tells him people play Armagetronad trying to hide using a non-default port ;-) Another aspect is that it was easy to do. Anyway, I removed it since you seem to imply that it must be done only when it's needed because the port number is not well known.

Thanks for the review.

Regards.

--
Guillaume

Index: epan/dissectors/Makefile.common
===================================================================
--- epan/dissectors/Makefile.common	(revision 14363)
+++ epan/dissectors/Makefile.common	(working copy)
@@ -91,6 +91,7 @@
 	packet-ap1394.c	\
 	packet-app-pkix-cert.c	\
 	packet-arcnet.c	\
+	packet-armagetronad.c \
 	packet-arp.c	\
 	packet-aruba-adp.c	\
 	packet-asap.c	\
Index: epan/dissectors/packet-armagetronad.c
===================================================================
--- epan/dissectors/packet-armagetronad.c	(revision 0)
+++ epan/dissectors/packet-armagetronad.c	(revision 0)
@@ -0,0 +1,331 @@
+/* packet-armagetronad.c
+ * Routines for the Armagetronad packet dissection
+ * Copyright 2005, Guillaume Chazarain <guichaz@xxxxxxxx>
+ *
+ * $Id: README.developer 13227 2005-02-01 15:45:03Z jmayer $
+ *
+ * Ethereal - Network traffic analyzer
+ * By Gerald Combs <gerald@xxxxxxxxxxxx>
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <glib.h>
+
+#include <epan/packet.h>
+
+/* Initialize the protocol and registered fields */
+static int proto_armagetronad = -1;
+static int hf_armagetronad_descriptor_id = -1;
+static int hf_armagetronad_message_id = -1;
+static int hf_armagetronad_data_len = -1;
+static int hf_armagetronad_data = -1;
+static int hf_armagetronad_sender_id = -1;
+static int hf_armagetronad_msg_subtree = -1;
+
+/* Initialize the subtree pointers */
+static gint ett_armagetronad = -1;
+static gint ett_message = -1;
+
+#define UDP_PORT_ARMAGETRONAD 4534
+#define UDP_PORT_MASTER 4533
+
+/*
+ * The ACK packet is so common that we treat it
+ * differently: it has no MessageID
+ */
+#define ACK 1
+
+/*
+ * CVS as of 05/05/2005
+ * The list in src/network/Makefile.in does not seem up to date,
+ * so the numbers and names were retrieved at runtime using the
+ * 'nDescriptor* descriptors[MAXDESCRIPTORS]' array
+ */
+static value_string descriptors[] = {
+	{1, "ack"},
+	{2, "req_info"},
+	{3, "login_deny"},
+	{4, "login_ignore"},
+	{5, "login_accept"},
+	{6, "login1"},
+	{7, "logout"},
+	{8, "sn_ConsoleOut"},
+	{9, "client_cen"},
+	{10, "version"},
+	{11, "login2"},
+	{20, "req_id"},
+	{21, "id_req_handler"},
+	{22, "net_destroy"},
+	{23, "net_control"},
+	{24, "net_sync"},
+	{25, "ready to get objects"},
+	{26, "net_clear"},
+	{27, "sync_ack"},
+	{28, "sync_msg"},
+	{40, "password_request"},
+	{41, "password_answer"},
+	{50, "small_server"},
+	{51, "big_server"},
+	{52, "small_request"},
+	{53, "big_request"},
+	{60, "transfer config"},
+	{200, "Chat"},
+	{201, "ePlayerNetID"},
+	{202, "player_removed_from_game"},
+	{210, "eTimer"},
+	{220, "eTeam"},
+	{230, "vote cast"},
+	{231, "Chat"},
+	{300, "gNetPlayerWall"},
+	{310, "game"},
+	{311, "client_gamestate"},
+	{320, "cycle"},
+	{321, "destinaton"},
+	{330, "gAIPlayer"},
+	{331, "gAITeam"},
+	{340, "winzone"}
+};
+
+static gboolean is_armagetronad_packet(tvbuff_t * tvb)
+{
+	gint offset = 0;
+
+	/* For each message in the frame */
+	while (tvb_length_remaining(tvb, offset) > 2) {
+		guint16 data_len = tvb_get_ntohs(tvb, offset + 4) * 2;
+
+#if 0
+		/*
+		 * If the descriptor_id is not in the table it's possibly
+		 * because the protocol evoluated, losing synchronization
+		 * with the table, that's why we don't consider that as
+		 * a heuristic
+		 */
+		if (!match_strval(tvb_get_ntohs(tvb, offset), descriptors))
+			/* DescriptorID not found in the table */
+			return FALSE;
+#endif
+
+		if (!tvb_bytes_exist(tvb, offset + 6, data_len))
+			/* Advertised length too long */
+			return FALSE;
+
+		offset += 6 + data_len;
+	}
+
+	/* The packed should end with a 2 bytes ID */
+	return tvb_length_remaining(tvb, offset) == 2;
+}
+
+static void
+add_message_data(tvbuff_t * tvb, gint offset, guint16 data_len,
+		 proto_tree * tree)
+{
+	guint16 *data = NULL;
+
+	if (tree)
+		data = (guint16 *) tvb_memdup(tvb, offset, data_len);
+
+	if (data) {
+		guint16 *ptr, *end = &data[data_len / 2];
+		for (ptr = data; ptr != end; ptr++) {
+			/*
+			 * There must be a better way to tell
+			 * Ethereal not to stop on null bytes
+			 * as the length is known
+			 */
+			gchar *bptr = (gchar *) ptr;
+			if (!bptr[0])
+				bptr[0] = ' ';
+
+			if (!bptr[1])
+				bptr[1] = ' ';
+
+			/* Armagetronad swaps unconditionally */
+			*ptr = GUINT16_SWAP_LE_BE(*ptr);
+		}
+
+		proto_tree_add_string(tree, hf_armagetronad_data, tvb, offset,
+				      data_len, (gchar *) data);
+
+		g_free(data);
+		data = NULL;
+	} else
+		proto_tree_add_item(tree, hf_armagetronad_data, tvb, offset,
+				    data_len, FALSE);
+}
+
+static gint add_message(tvbuff_t * tvb, gint offset, proto_tree * tree,
+			GString * info)
+{
+	guint16 descriptor_id, message_id, data_len;
+	proto_item *msg;
+	proto_tree *msg_tree;
+	const gchar *descriptor;
+
+	descriptor_id = tvb_get_ntohs(tvb, offset);
+	message_id = tvb_get_ntohs(tvb, offset + 2);
+	data_len = tvb_get_ntohs(tvb, offset + 4) * 2;
+
+	/* Message subtree */
+	descriptor = val_to_str(descriptor_id, descriptors, "Unknown (%u)");
+	if (descriptor_id == ACK)
+		msg = proto_tree_add_none_format(tree,
+						 hf_armagetronad_msg_subtree,
+						 tvb, offset, data_len + 6,
+						 "ACK %d messages",
+						 data_len / 2);
+	else
+		msg = proto_tree_add_none_format(tree,
+						 hf_armagetronad_msg_subtree,
+						 tvb, offset, data_len + 6,
+						 "Message 0x%04x [%s]",
+						 message_id, descriptor);
+
+	msg_tree = proto_item_add_subtree(msg, ett_message);
+
+	/* DescriptorID field */
+	proto_tree_add_item(msg_tree, hf_armagetronad_descriptor_id, tvb,
+			    offset, 2, FALSE);
+	if (info)
+		g_string_sprintfa(info, "%s, ", descriptor);
+
+	/* MessageID field */
+	proto_tree_add_item(msg_tree, hf_armagetronad_message_id, tvb,
+			    offset + 2, 2, FALSE);
+
+	/* DataLen field */
+	proto_tree_add_item(msg_tree, hf_armagetronad_data_len, tvb,
+			    offset + 4, 2, FALSE);
+
+	/* Data field */
+	add_message_data(tvb, offset + 6, data_len, msg_tree);
+
+	return data_len + 6;
+}
+
+/* Code to actually dissect the packets */
+static gint
+dissect_armagetronad(tvbuff_t * tvb, packet_info * pinfo, proto_tree * tree)
+{
+	proto_item *ti;
+	proto_tree *armagetronad_tree;
+	guint16 sender;
+	gint offset = 0;
+	GString *info;
+
+	if (!is_armagetronad_packet(tvb))
+		return 0;
+
+	info = check_col(pinfo->cinfo, COL_INFO) ? g_string_new("") : NULL;
+
+	if (check_col(pinfo->cinfo, COL_PROTOCOL))
+		col_set_str(pinfo->cinfo, COL_PROTOCOL, "Armagetronad");
+
+	if (info)
+		col_clear(pinfo->cinfo, COL_INFO);
+
+	ti = proto_tree_add_item(tree, proto_armagetronad, tvb, 0, -1, FALSE);
+	armagetronad_tree = proto_item_add_subtree(ti, ett_armagetronad);
+
+	/* For each message in the frame */
+	while (tvb_length_remaining(tvb, offset) > 2)
+		offset += add_message(tvb, offset, armagetronad_tree, info);
+
+	/* After the messages, comes the SenderID */
+	sender = tvb_get_ntohs(tvb, offset);
+	proto_tree_add_item(ti, hf_armagetronad_sender_id, tvb, offset, 2,
+			    FALSE);
+
+	if (info) {
+		gsize new_len = info->len - 2;	/* Remove the trailing ", " */
+		if (new_len > 0)
+			g_string_truncate(info, new_len);
+		else
+			g_string_assign(info, "No message");
+
+		col_add_fstr(pinfo->cinfo, COL_INFO, "[%s] from 0x%04x",
+			     info->str, sender);
+		g_string_free(info, TRUE);
+	}
+
+	return offset + 2;
+}
+
+void proto_register_armagetronad(void)
+{
+	static hf_register_info hf[] = {
+		{&hf_armagetronad_descriptor_id,
+		 {"Descriptor", "armagetronad.descriptor_id",
+		  FT_UINT16, BASE_DEC, VALS(descriptors), 0x0,
+		  "The ID of the descriptor (the command)", HFILL}
+		 },
+		{&hf_armagetronad_message_id,
+		 {"MessageID", "armagetronad.message_id",
+		  FT_UINT16, BASE_HEX, NULL, 0x0,
+		  "The ID of the message (to ack it)", HFILL}
+		 },
+		{&hf_armagetronad_data_len,
+		 {"DataLen", "armagetronad.data_len",
+		  FT_UINT16, BASE_DEC, NULL, 0x0,
+		  "The length of the data (in shorts)", HFILL}
+		 },
+		{&hf_armagetronad_data,
+		 {"Data", "armagetronad.data",
+		  FT_STRING, BASE_HEX, NULL, 0x0,
+		  "The actual data (array of shorts in network order)", HFILL}
+		 },
+		{&hf_armagetronad_sender_id,
+		 {"SenderID", "armagetronad.sender_id",
+		  FT_UINT16, BASE_HEX, NULL, 0x0,
+		  "The ID of the sender (0x0000 for the server)", HFILL}
+		 },
+		{&hf_armagetronad_msg_subtree,
+		 {"Message", "armagetronad.message",
+		  FT_NONE, 0, NULL, 0x0,
+		  "A message", HFILL}
+		 }
+	};
+
+	static gint *ett[] = {
+		&ett_armagetronad,
+		&ett_message
+	};
+
+	proto_armagetronad =
+	    proto_register_protocol("The Armagetron Advanced OpenGL Tron clone",
+				    "Armagetronad", "armagetronad");
+
+	proto_register_field_array(proto_armagetronad, hf, array_length(hf));
+	proto_register_subtree_array(ett, array_length(ett));
+	new_register_dissector("armagetronad", dissect_armagetronad,
+			       proto_armagetronad);
+}
+
+void proto_reg_handoff_armagetronad(void)
+{
+	dissector_handle_t armagetronad_handle;
+
+	armagetronad_handle = find_dissector("armagetronad");
+
+	dissector_add("udp.port", UDP_PORT_ARMAGETRONAD, armagetronad_handle);
+	dissector_add("udp.port", UDP_PORT_MASTER, armagetronad_handle);
+}