wireshark/epan/dissectors/packet-bt-tracker.c

605 lines
20 KiB
C

/* packet-bt-tracker.c
* Routines for BitTorrent Tracker over UDP dissection
* Copyright 2023, Ivan Nardi <nardi.ivan@gmail.com>
*
* Wireshark - Network traffic analyzer
* By Gerald Combs <gerald@wireshark.org>
* Copyright 1999 Gerald Combs
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "config.h"
#include <epan/packet.h>
#include <epan/conversation.h>
#include <epan/to_str.h>
#include <epan/prefs.h>
void proto_register_bt_tracker(void);
void proto_reg_handoff_bt_tracker(void);
/* Specifications:
* https://www.bittorrent.org/beps/bep_0015.html BEP 15 UDP Tracker Protocol for BitTorrent
* https://www.bittorrent.org/beps/bep_0041.html BEP 41 UDP Tracker Protocol Extensions
*/
enum {
ACTION_CONNECT = 0,
ACTION_ANNOUNCE = 1,
ACTION_SCRAPE = 2,
ACTION_ERROR = 3,
};
enum {
MSG_TYPE_CONNECT_REQUEST,
MSG_TYPE_CONNECT_RESPONSE,
MSG_TYPE_ANNOUNCE_REQUEST,
MSG_TYPE_ANNOUNCE_RESPONSE,
MSG_TYPE_SCRAPE_REQUEST,
MSG_TYPE_SCRAPE_RESPONSE,
MSG_TYPE_ERROR_RESPONSE,
MSG_TYPE_UNKNOWN,
};
static const value_string bt_tracker_msg_type_vals[] = {
{ MSG_TYPE_CONNECT_REQUEST, "Connection Request" },
{ MSG_TYPE_CONNECT_RESPONSE, "Connection Response" },
{ MSG_TYPE_ANNOUNCE_REQUEST, "Announce Request" },
{ MSG_TYPE_ANNOUNCE_RESPONSE, "Announce Response" },
{ MSG_TYPE_SCRAPE_REQUEST, "Scrape Request" },
{ MSG_TYPE_SCRAPE_RESPONSE, "Scrape Response" },
{ MSG_TYPE_ERROR_RESPONSE, "Error Response" },
{ 0, NULL }
};
static const value_string bt_tracker_event_vals[] = {
{ 0, "None" },
{ 1, "Completed" },
{ 2, "Started" },
{ 3, "Stopped" },
{ 0, NULL }
};
static const value_string bt_tracker_action_vals[] = {
{ ACTION_CONNECT, "Connect" },
{ ACTION_ANNOUNCE, "Announce" },
{ ACTION_SCRAPE, "Scrape" },
{ ACTION_ERROR, "Error" },
{ 0, NULL }
};
enum {
EXT_END_OF_OPTIONS = 0,
EXT_NOP = 1,
EXT_URLDATA = 2,
EXT_MAX
};
static const value_string bt_tracker_extension_type_vals[] = {
{ EXT_END_OF_OPTIONS, "End of Options" },
{ EXT_NOP, "NOP" },
{ EXT_URLDATA, "URL Data" },
{ 0, NULL }
};
static int proto_bt_tracker;
static dissector_handle_t bt_tracker_handle;
static int hf_bt_tracker_msg_type;
static int hf_bt_tracker_protocol_id;
static int hf_bt_tracker_action;
static int hf_bt_tracker_transaction_id;
static int hf_bt_tracker_connection_id;
static int hf_bt_tracker_info_hash;
static int hf_bt_tracker_peer_id;
static int hf_bt_tracker_downloaded;
static int hf_bt_tracker_left;
static int hf_bt_tracker_uploaded;
static int hf_bt_tracker_event;
static int hf_bt_tracker_ip_address;
static int hf_bt_tracker_key;
static int hf_bt_tracker_num_want;
static int hf_bt_tracker_port;
static int hf_bt_tracker_interval;
static int hf_bt_tracker_leechers;
static int hf_bt_tracker_seeders;
static int hf_bt_tracker_trackers;
static int hf_bt_tracker_tracker;
static int hf_bt_tracker_tr_ip;
static int hf_bt_tracker_tr_ip6;
static int hf_bt_tracker_tr_port;
static int hf_bt_tracker_completed;
static int hf_bt_tracker_error_msg;
static int hf_bt_tracker_extension;
static int hf_bt_tracker_extension_type;
static int hf_bt_tracker_extension_len;
static int hf_bt_tracker_extension_unknown;
static int hf_bt_tracker_extension_urldata;
static gint ett_bt_tracker;
static gint ett_bt_tracker_trackers;
static gint ett_bt_tracker_extension;
#define MAGIC_CONSTANT 0x41727101980
static guint
get_message_type(tvbuff_t *tvb)
{
if (tvb_get_ntoh64(tvb, 0) == MAGIC_CONSTANT &&
tvb_get_ntohl(tvb, 8) == ACTION_CONNECT)
return MSG_TYPE_CONNECT_REQUEST;
if (tvb_get_ntohl(tvb, 0) == ACTION_CONNECT)
return MSG_TYPE_CONNECT_RESPONSE;
if (tvb_get_ntohl(tvb, 8) == ACTION_ANNOUNCE)
return MSG_TYPE_ANNOUNCE_REQUEST;
if (tvb_get_ntohl(tvb, 0) == ACTION_ANNOUNCE)
return MSG_TYPE_ANNOUNCE_RESPONSE;
if (tvb_get_ntohl(tvb, 8) == ACTION_SCRAPE)
return MSG_TYPE_SCRAPE_REQUEST;
if (tvb_get_ntohl(tvb, 0) == ACTION_SCRAPE)
return MSG_TYPE_SCRAPE_RESPONSE;
if (tvb_get_ntohl(tvb, 0) == ACTION_ERROR)
return MSG_TYPE_ERROR_RESPONSE;
return MSG_TYPE_UNKNOWN;
}
static gboolean
is_ipv4_format(packet_info *pinfo)
{
wmem_list_frame_t *cur;
gint cur_proto;
const char *cur_name;
/* Format of Announce Response message depends on IPv4 vs IPv6
"Which format is used is determined by the address family of the underlying UDP packet.
I.e. packets from a v4 address use the v4 format, those from a v6 address use the v6 format."
Check the innermost IP layer, to take into account tunnels
*/
cur = wmem_list_frame_prev(wmem_list_tail(pinfo->layers));
while (cur != NULL) {
cur_proto = (gint)GPOINTER_TO_UINT(wmem_list_frame_data(cur));
cur_name = proto_get_protocol_filter_name(cur_proto);
if (!strcmp(cur_name, "ip"))
return TRUE;
if (!strcmp(cur_name, "ipv6"))
return FALSE;
cur = wmem_list_frame_prev(cur);
}
return TRUE;
}
static int
dissect_bt_tracker_extension(tvbuff_t *tvb, packet_info _U_*pinfo, proto_tree *tree, int offset)
{
proto_item *ti;
proto_tree *ext_tree;
guint8 extension_type;
guint32 extension_length;
gint32 tot_length;
while (offset < (int)tvb_reported_length(tvb)) {
extension_type = tvb_get_guint8(tvb, offset);
tot_length = 1;
if (extension_type == EXT_URLDATA) {
tot_length += 1 + tvb_get_guint8(tvb, offset + 1);
} else if (extension_type >= EXT_MAX) {
tot_length = -1;
}
ti = proto_tree_add_none_format(tree, hf_bt_tracker_extension, tvb, offset, tot_length, "Extension: %s", val_to_str_const(extension_type, bt_tracker_extension_type_vals, "Unknown"));
ext_tree = proto_item_add_subtree(ti, ett_bt_tracker_extension);
proto_tree_add_item(ext_tree, hf_bt_tracker_extension_type, tvb, offset, 1, ENC_BIG_ENDIAN);
offset += 1;
switch (extension_type) {
case EXT_END_OF_OPTIONS:
/* Option parsing continues until either the end of the packet is reached, or an EndOfOptions option is encountered, whichever happens first */
return offset;
case EXT_NOP:
/* A special case option that has a fixed-length of one byte. It is not followed by a length field, or associated data.
A NOP has no affect on option parsing. It is used only if optional padding is necessary in the future. */
break;
case EXT_URLDATA:
proto_tree_add_item_ret_uint(ext_tree, hf_bt_tracker_extension_len, tvb, offset, 1, ENC_BIG_ENDIAN, &extension_length);
offset += 1;
proto_tree_add_item(ext_tree, hf_bt_tracker_extension_urldata, tvb, offset, extension_length, ENC_ASCII);
offset += extension_length;
break;
default:
proto_tree_add_item(ext_tree, hf_bt_tracker_extension_unknown, tvb, offset, -1, ENC_NA);
return offset;
}
}
return offset;
}
static int
dissect_bt_tracker_msg(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, guint offset, guint msg_type)
{
guint node_index = 0;
gint stride_length;
proto_item *ti;
proto_tree *sub_tree;
gboolean is_ipv6;
ti = proto_tree_add_uint(tree, hf_bt_tracker_msg_type, tvb, 0, 0, msg_type);
proto_item_set_generated(ti);
switch (msg_type) {
case MSG_TYPE_CONNECT_REQUEST:
proto_tree_add_item(tree, hf_bt_tracker_protocol_id, tvb, offset, 8, ENC_BIG_ENDIAN);
offset += 8;
proto_tree_add_item(tree, hf_bt_tracker_action, tvb, offset, 4, ENC_BIG_ENDIAN);
offset += 4;
proto_tree_add_item(tree, hf_bt_tracker_transaction_id, tvb, offset, 4, ENC_BIG_ENDIAN);
offset += 4;
break;
case MSG_TYPE_CONNECT_RESPONSE:
proto_tree_add_item(tree, hf_bt_tracker_action, tvb, offset, 4, ENC_BIG_ENDIAN);
offset += 4;
proto_tree_add_item(tree, hf_bt_tracker_transaction_id, tvb, offset, 4, ENC_BIG_ENDIAN);
offset += 4;
proto_tree_add_item(tree, hf_bt_tracker_connection_id, tvb, offset, 8, ENC_BIG_ENDIAN);
offset += 8;
break;
case MSG_TYPE_ANNOUNCE_REQUEST:
proto_tree_add_item(tree, hf_bt_tracker_connection_id, tvb, offset, 8, ENC_BIG_ENDIAN);
offset += 8;
proto_tree_add_item(tree, hf_bt_tracker_action, tvb, offset, 4, ENC_BIG_ENDIAN);
offset += 4;
proto_tree_add_item(tree, hf_bt_tracker_transaction_id, tvb, offset, 4, ENC_BIG_ENDIAN);
offset += 4;
proto_tree_add_item(tree, hf_bt_tracker_info_hash, tvb, offset, 20, ENC_NA);
offset += 20;
proto_tree_add_item(tree, hf_bt_tracker_peer_id, tvb, offset, 20, ENC_NA);
offset += 20;
proto_tree_add_item(tree, hf_bt_tracker_downloaded, tvb, offset, 8, ENC_BIG_ENDIAN);
offset += 8;
proto_tree_add_item(tree, hf_bt_tracker_left, tvb, offset, 8, ENC_BIG_ENDIAN);
offset += 8;
proto_tree_add_item(tree, hf_bt_tracker_uploaded, tvb, offset, 8, ENC_BIG_ENDIAN);
offset += 8;
proto_tree_add_item(tree, hf_bt_tracker_event, tvb, offset, 4, ENC_BIG_ENDIAN);
offset += 4;
proto_tree_add_item(tree, hf_bt_tracker_ip_address, tvb, offset, 4, ENC_BIG_ENDIAN);
offset += 4;
proto_tree_add_item(tree, hf_bt_tracker_key, tvb, offset, 4, ENC_BIG_ENDIAN);
offset += 4;
proto_tree_add_item(tree, hf_bt_tracker_num_want, tvb, offset, 4, ENC_BIG_ENDIAN);
offset += 4;
proto_tree_add_item(tree, hf_bt_tracker_port, tvb, offset, 2, ENC_BIG_ENDIAN);
offset += 2;
offset = dissect_bt_tracker_extension(tvb, pinfo, tree, offset);
break;
case MSG_TYPE_ANNOUNCE_RESPONSE:
proto_tree_add_item(tree, hf_bt_tracker_action, tvb, offset, 4, ENC_BIG_ENDIAN);
offset += 4;
proto_tree_add_item(tree, hf_bt_tracker_transaction_id, tvb, offset, 4, ENC_BIG_ENDIAN);
offset += 4;
proto_tree_add_item(tree, hf_bt_tracker_interval, tvb, offset, 4, ENC_BIG_ENDIAN);
offset += 4;
proto_tree_add_item(tree, hf_bt_tracker_leechers, tvb, offset, 4, ENC_BIG_ENDIAN);
offset += 4;
proto_tree_add_item(tree, hf_bt_tracker_seeders, tvb, offset, 4, ENC_BIG_ENDIAN);
offset += 4;
if (tvb_captured_length_remaining(tvb, offset) > 0)
{
stride_length = 6;
is_ipv6 = !is_ipv4_format(pinfo);
if (is_ipv6)
stride_length = 18;
ti = proto_tree_add_item(tree, hf_bt_tracker_trackers, tvb, offset, -1, ENC_NA);
sub_tree = proto_item_add_subtree(ti, ett_bt_tracker_trackers);
while (tvb_captured_length_remaining(tvb, offset) >= stride_length)
{
proto_item *node_ti;
proto_tree *node_tree;
node_index += 1;
node_ti = proto_tree_add_item(sub_tree, hf_bt_tracker_tracker, tvb, offset, stride_length, ENC_NA);
proto_item_append_text(node_ti, " %d", node_index);
node_tree = proto_item_add_subtree(node_ti, ett_bt_tracker_trackers);
if (is_ipv6)
{
proto_tree_add_item( node_tree, hf_bt_tracker_tr_ip6, tvb, offset, 16, ENC_NA);
proto_item_append_text(node_ti, ", IPv6/Port: [%s]", tvb_ip6_to_str(pinfo->pool, tvb, offset));
proto_tree_add_item( node_tree, hf_bt_tracker_tr_port, tvb, offset + 16, 2, ENC_BIG_ENDIAN);
proto_item_append_text(node_ti, ":%u", tvb_get_ntohs( tvb, offset + 16 ));
}
else
{
proto_tree_add_item( node_tree, hf_bt_tracker_tr_ip, tvb, offset, 4, ENC_BIG_ENDIAN);
proto_item_append_text(node_ti, ", IPv4/Port: %s", tvb_ip_to_str(pinfo->pool, tvb, offset));
proto_tree_add_item( node_tree, hf_bt_tracker_tr_port, tvb, offset + 4, 2, ENC_BIG_ENDIAN);
proto_item_append_text(node_ti, ":%u", tvb_get_ntohs( tvb, offset + 4 ));
}
offset += stride_length;
}
proto_item_set_text(ti, "Trackers: %d trackers", node_index);
col_append_fstr(pinfo->cinfo, COL_INFO, ": %d trackers", node_index);
}
break;
case MSG_TYPE_SCRAPE_REQUEST:
proto_tree_add_item(tree, hf_bt_tracker_connection_id, tvb, offset, 8, ENC_BIG_ENDIAN);
offset += 8;
proto_tree_add_item(tree, hf_bt_tracker_action, tvb, offset, 4, ENC_BIG_ENDIAN);
offset += 4;
proto_tree_add_item(tree, hf_bt_tracker_transaction_id, tvb, offset, 4, ENC_BIG_ENDIAN);
offset += 4;
while (tvb_captured_length_remaining(tvb, offset) >= 20)
{
proto_tree_add_item(tree, hf_bt_tracker_info_hash, tvb, offset, 20, ENC_NA);
offset += 20;
}
break;
case MSG_TYPE_SCRAPE_RESPONSE:
proto_tree_add_item(tree, hf_bt_tracker_action, tvb, offset, 4, ENC_BIG_ENDIAN);
offset += 4;
proto_tree_add_item(tree, hf_bt_tracker_transaction_id, tvb, offset, 4, ENC_BIG_ENDIAN);
offset += 4;
while (tvb_captured_length_remaining(tvb, offset) >= 12)
{
proto_tree_add_item(tree, hf_bt_tracker_seeders, tvb, offset, 4, ENC_BIG_ENDIAN);
offset += 4;
proto_tree_add_item(tree, hf_bt_tracker_completed, tvb, offset, 4, ENC_BIG_ENDIAN);
offset += 4;
proto_tree_add_item(tree, hf_bt_tracker_leechers, tvb, offset, 4, ENC_BIG_ENDIAN);
offset += 4;
}
break;
case MSG_TYPE_ERROR_RESPONSE:
proto_tree_add_item(tree, hf_bt_tracker_action, tvb, offset, 4, ENC_BIG_ENDIAN);
offset += 4;
proto_tree_add_item(tree, hf_bt_tracker_transaction_id, tvb, offset, 4, ENC_BIG_ENDIAN);
offset += 4;
proto_tree_add_item(tree, hf_bt_tracker_error_msg, tvb, offset, -1, ENC_ASCII);
offset = tvb_captured_length(tvb);
break;
}
return offset;
}
static int
dissect_bt_tracker(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
{
guint msg_type;
proto_item *ti;
proto_tree *sub_tree;
col_set_str(pinfo->cinfo, COL_PROTOCOL, "BT-Tracker");
col_clear(pinfo->cinfo, COL_INFO);
msg_type = get_message_type(tvb);
col_append_str(pinfo->cinfo, COL_INFO, val_to_str_const(msg_type, bt_tracker_msg_type_vals, " Unknown Msg Type"));
ti = proto_tree_add_item(tree, proto_bt_tracker, tvb, 0, -1, ENC_NA);
sub_tree = proto_item_add_subtree(ti, ett_bt_tracker);
return dissect_bt_tracker_msg(tvb, pinfo, sub_tree, 0, msg_type);
}
static gboolean
dissect_bt_tracker_heur(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
{
conversation_t *conversation;
/* Look for a Connect Request */
if (tvb_captured_length_remaining(tvb, 0) < 16)
return FALSE;
if (tvb_get_ntoh64(tvb, 0) != MAGIC_CONSTANT)
return FALSE;
if (tvb_get_ntohl(tvb, 8) != ACTION_CONNECT)
return FALSE;
conversation = find_or_create_conversation(pinfo);
conversation_set_dissector_from_frame_number(conversation, pinfo->num, bt_tracker_handle);
dissect_bt_tracker(tvb, pinfo, tree, data);
return TRUE;
}
void
proto_register_bt_tracker(void)
{
static hf_register_info hf[] = {
{ &hf_bt_tracker_protocol_id,
{ "Protocol", "bt-tracker.proto_id",
FT_UINT64, BASE_HEX, NULL, 0x00,
NULL, HFILL }
},
{ &hf_bt_tracker_action,
{ "Action", "bt-tracker.action",
FT_UINT32, BASE_DEC, VALS(bt_tracker_action_vals), 0x0,
NULL, HFILL }
},
{ &hf_bt_tracker_transaction_id,
{ "Transaction Id", "bt-tracker.transaction_id",
FT_UINT32, BASE_DEC, NULL, 0x00,
NULL, HFILL }
},
{ &hf_bt_tracker_connection_id,
{ "Connection Id", "bt-tracker.connection_id",
FT_UINT64, BASE_DEC, NULL, 0x00,
NULL, HFILL }
},
{ &hf_bt_tracker_msg_type,
{ "Message Type", "bt-tracker.msg_type",
FT_UINT8, BASE_DEC, VALS(bt_tracker_msg_type_vals), 0x0,
NULL, HFILL }
},
{ &hf_bt_tracker_info_hash,
{ "Info Hash", "bt-tracker.info_hash",
FT_BYTES, BASE_NONE, NULL, 0x00,
NULL, HFILL }
},
{ &hf_bt_tracker_peer_id,
{ "Peer Id", "bt-tracker.peer_id",
FT_BYTES, BASE_NONE, NULL, 0x00,
NULL, HFILL }
},
{ &hf_bt_tracker_downloaded,
{ "Downloaded", "bt-tracker.downloaded",
FT_UINT64, BASE_DEC, NULL, 0x00,
NULL, HFILL }
},
{ &hf_bt_tracker_left,
{ "Left", "bt-tracker.left",
FT_UINT64, BASE_DEC, NULL, 0x00,
NULL, HFILL }
},
{ &hf_bt_tracker_uploaded,
{ "Uploaded", "bt-tracker.uploaded",
FT_UINT64, BASE_DEC, NULL, 0x00,
NULL, HFILL }
},
{ &hf_bt_tracker_event,
{ "Event", "bt-tracker.event",
FT_UINT32, BASE_DEC, VALS(bt_tracker_event_vals), 0x00,
NULL, HFILL }
},
{ &hf_bt_tracker_ip_address,
{ "IP Address", "bt-tracker.ip_address",
FT_IPv4, BASE_NONE, NULL, 0x00,
NULL, HFILL }
},
{ &hf_bt_tracker_key,
{ "Key", "bt-tracker.key",
FT_UINT32, BASE_DEC, NULL, 0x00,
NULL, HFILL }
},
{ &hf_bt_tracker_num_want,
{ "Num Want", "bt-tracker.num_want",
FT_INT32, BASE_DEC, NULL, 0x00,
NULL, HFILL }
},
{ &hf_bt_tracker_port,
{ "Port", "bt-tracker.port",
FT_UINT16, BASE_DEC, NULL, 0x00,
NULL, HFILL }
},
{ &hf_bt_tracker_interval,
{ "Interval", "bt-tracker.interval",
FT_INT32, BASE_DEC, NULL, 0x00,
NULL, HFILL }
},
{ &hf_bt_tracker_leechers,
{ "Leechers", "bt-tracker.leechers",
FT_INT32, BASE_DEC, NULL, 0x00,
NULL, HFILL }
},
{ &hf_bt_tracker_seeders,
{ "Seeders", "bt-tracker.seeders",
FT_INT32, BASE_DEC, NULL, 0x00,
NULL, HFILL }
},
{ &hf_bt_tracker_trackers,
{ "Trackers", "bt-tracker.trackers",
FT_NONE, BASE_NONE, NULL, 0x0,
NULL, HFILL }
},
{ &hf_bt_tracker_tracker,
{ "Tracker", "bt-tracker.tracker",
FT_NONE, BASE_NONE, NULL, 0x0,
NULL, HFILL }
},
{ &hf_bt_tracker_tr_ip,
{ "IP", "bt-tracker.tracker.ip",
FT_IPv4, BASE_NONE, NULL, 0x00,
NULL, HFILL }
},
{ &hf_bt_tracker_tr_ip6,
{ "IPv6", "bt-tracker.tracker.ip6",
FT_IPv6, BASE_NONE, NULL, 0x00,
NULL, HFILL }
},
{ &hf_bt_tracker_tr_port,
{ "(TCP) Port", "bt-tracker.tracker.port",
FT_UINT16, BASE_DEC, NULL, 0x00,
NULL, HFILL }
},
{ &hf_bt_tracker_completed,
{ "Completed", "bt-tracker.completed",
FT_INT32, BASE_DEC, NULL, 0x00,
NULL, HFILL }
},
{ &hf_bt_tracker_error_msg,
{ "Error message", "bt-tracker.error_msg",
FT_STRING, BASE_NONE, NULL, 0x00,
NULL, HFILL }
},
{ &hf_bt_tracker_extension,
{ "Extension", "bt-tracker.extension",
FT_NONE, BASE_NONE, NULL, 0x0,
NULL, HFILL }
},
{ &hf_bt_tracker_extension_type,
{ "Extension Type", "bt-tracker.extension_type",
FT_UINT8, BASE_HEX, VALS(bt_tracker_extension_type_vals), 0x0,
NULL, HFILL }
},
{ &hf_bt_tracker_extension_len,
{ "Extension Length", "bt-tracker.extension_len",
FT_UINT8, BASE_DEC|BASE_UNIT_STRING, &units_byte_bytes, 0x0,
NULL, HFILL }
},
{ &hf_bt_tracker_extension_unknown,
{ "Extension Unknown", "bt-tracker.extension_unknown",
FT_BYTES, BASE_NONE, NULL, 0x0,
NULL, HFILL }
},
{ &hf_bt_tracker_extension_urldata,
{ "URL Data", "bt-tracker.extension.urldata",
FT_STRING, BASE_NONE, NULL, 0x00,
NULL, HFILL }
},
};
/* Setup protocol subtree array */
static gint *ett[] = { &ett_bt_tracker, &ett_bt_tracker_trackers, &ett_bt_tracker_extension};
module_t *bt_tracker_module;
/* Register protocol */
proto_bt_tracker = proto_register_protocol ("BitTorrent Tracker", "BT-Tracker", "bt-tracker");
bt_tracker_module = prefs_register_protocol(proto_bt_tracker, NULL);
prefs_register_obsolete_preference(bt_tracker_module, "enable");
proto_register_field_array(proto_bt_tracker, hf, array_length(hf));
proto_register_subtree_array(ett, array_length(ett));
}
void
proto_reg_handoff_bt_tracker(void)
{
heur_dissector_add("udp", dissect_bt_tracker_heur, "BitTorrent Tracker over UDP", "bt_tracker_udp", proto_bt_tracker, HEURISTIC_ENABLE);
bt_tracker_handle = create_dissector_handle(dissect_bt_tracker, proto_bt_tracker);
dissector_add_for_decode_as_with_preference("udp.port", bt_tracker_handle);
}