/* packet-tftp.c * Routines for tftp packet dissection * * Richard Sharpe * Craig Newell * RFC2347 TFTP Option Extension * Joerg Mayer (see AUTHORS file) * RFC2348 TFTP Blocksize Option * * Wireshark - Network traffic analyzer * By Gerald Combs * Copyright 1998 Gerald Combs * * Copied from packet-bootp.c * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ /* Documentation: * RFC 1350: THE TFTP PROTOCOL (REVISION 2) * RFC 2090: TFTP Multicast Option * (not yet implemented) * RFC 2347: TFTP Option Extension * RFC 2348: TFTP Blocksize Option * RFC 2349: TFTP Timeout Interval and Transfer Size Options * (not yet implemented) */ #include "config.h" #include #include #include #include #include #include #include #include "packet-tftp.h" void proto_register_tftp(void); /* Things we may want to remember for a whole conversation */ typedef struct _tftp_conv_info_t { guint16 blocksize; const guint8 *source_file, *destination_file; /* Sequence analysis */ guint next_block_num; gboolean blocks_missing; /* When exporting file object, build up list of data blocks here */ guint next_tap_block_num; GSList *block_list; guint file_length; } tftp_conv_info_t; static int proto_tftp = -1; static int hf_tftp_opcode = -1; static int hf_tftp_source_file = -1; static int hf_tftp_destination_file = -1; static int hf_tftp_transfer_type = -1; static int hf_tftp_blocknum = -1; static int hf_tftp_error_code = -1; static int hf_tftp_error_string = -1; static int hf_tftp_option_name = -1; static int hf_tftp_option_value = -1; static int hf_tftp_data = -1; static gint ett_tftp = -1; static gint ett_tftp_option = -1; static expert_field ei_tftp_blocksize_range = EI_INIT; static dissector_handle_t tftp_handle; #define UDP_PORT_TFTP_RANGE "69" void proto_reg_handoff_tftp (void); /* User definable values */ static range_t *global_tftp_port_range = NULL; /* minimum length is an ACK message of 4 bytes */ #define MIN_HDR_LEN 4 #define TFTP_RRQ 1 #define TFTP_WRQ 2 #define TFTP_DATA 3 #define TFTP_ACK 4 #define TFTP_ERROR 5 #define TFTP_OACK 6 #define TFTP_INFO 255 static const value_string tftp_opcode_vals[] = { { TFTP_RRQ, "Read Request" }, { TFTP_WRQ, "Write Request" }, { TFTP_DATA, "Data Packet" }, { TFTP_ACK, "Acknowledgement" }, { TFTP_ERROR, "Error Code" }, { TFTP_OACK, "Option Acknowledgement" }, { TFTP_INFO, "Information (MSDP)" }, { 0, NULL } }; #define TFTP_ERR_NOT_DEF 0 #define TFTP_ERR_NOT_FOUND 1 #define TFTP_ERR_NOT_ALLOWED 2 #define TFTP_ERR_DISK_FULL 3 #define TFTP_ERR_BAD_OP 4 #define TFTP_ERR_BAD_ID 5 #define TFTP_ERR_EXISTS 6 #define TFTP_ERR_NO_USER 7 #define TFTP_ERR_OPT_FAIL 8 static const value_string tftp_error_code_vals[] = { { TFTP_ERR_NOT_DEF, "Not defined" }, { TFTP_ERR_NOT_FOUND, "File not found" }, { TFTP_ERR_NOT_ALLOWED, "Access violation" }, { TFTP_ERR_DISK_FULL, "Disk full or allocation exceeded" }, { TFTP_ERR_BAD_OP, "Illegal TFTP Operation" }, { TFTP_ERR_BAD_ID, "Unknown transfer ID" }, /* Does not cause termination */ { TFTP_ERR_EXISTS, "File already exists" }, { TFTP_ERR_NO_USER, "No such user" }, { TFTP_ERR_OPT_FAIL, "Option negotiation failed" }, { 0, NULL } }; static int tftp_eo_tap = -1; /* A list of block list entries to delete from cleanup callback when window is closed. */ typedef struct eo_info_dynamic_t { gchar *filename; GSList *block_list; } eo_info_dynamic_t; static GSList *s_dynamic_info_list = NULL; /* Used for TFTP Export Object feature */ typedef struct _tftp_eo_t { guint32 pkt_num; gchar *filename; guint32 payload_len; GSList *block_list; } tftp_eo_t; /* Tap function */ static gboolean tftp_eo_packet(void *tapdata, packet_info *pinfo, epan_dissect_t *edt _U_, const void *data) { export_object_list_t *object_list = (export_object_list_t *)tapdata; const tftp_eo_t *eo_info = (const tftp_eo_t *)data; export_object_entry_t *entry; GSList *block_iterator; guint payload_data_offset = 0; eo_info_dynamic_t *dynamic_info; /* These values will be freed when the Export Object window is closed. */ entry = g_new(export_object_entry_t, 1); /* Remember which frame had the last block of the file */ entry->pkt_num = pinfo->num; /* Copy filename */ entry->filename = g_path_get_basename(eo_info->filename); /* Iterate over list of blocks and concatenate into contiguous memory */ entry->payload_len = eo_info->payload_len; entry->payload_data = (guint8 *)g_try_malloc((gsize)entry->payload_len); for (block_iterator = eo_info->block_list; block_iterator; block_iterator = block_iterator->next) { file_block_t *block = (file_block_t*)block_iterator->data; memcpy(entry->payload_data + payload_data_offset, block->data, block->length); payload_data_offset += block->length; } /* These 2 fields not used */ entry->hostname = NULL; entry->content_type = NULL; /* Add to list of entries to be cleaned up. eo_info is only packet scope, so need to make list only of block list now */ dynamic_info = g_new(eo_info_dynamic_t, 1); dynamic_info->filename = eo_info->filename; dynamic_info->block_list = eo_info->block_list; s_dynamic_info_list = g_slist_append(s_dynamic_info_list, (eo_info_dynamic_t*)dynamic_info); /* Pass out entry to the GUI */ object_list->add_entry(object_list->gui_data, entry); return TRUE; /* State changed - window should be redrawn */ } /* Clean up the stored parts of a single tapped entry */ static void cleanup_tftp_eo(eo_info_dynamic_t *dynamic_info) { GSList *block_iterator; /* Free the filename */ g_free(dynamic_info->filename); /* Walk list of block items */ for (block_iterator = dynamic_info->block_list; block_iterator; block_iterator = block_iterator->next) { file_block_t *block = (file_block_t*)(block_iterator->data); /* Free block data */ wmem_free(NULL, block->data); /* Free block itself */ g_free(block); } } /* Callback for freeing up data supplied with taps. The taps themselves only have packet scope, so only store/free dynamic memory pointers */ static void tftp_eo_cleanup(void) { /* Cleanup each entry in the global list */ GSList *dynamic_iterator; for (dynamic_iterator = s_dynamic_info_list; dynamic_iterator; dynamic_iterator = dynamic_iterator->next) { eo_info_dynamic_t *dynamic_info = (eo_info_dynamic_t*)dynamic_iterator->data; cleanup_tftp_eo(dynamic_info); } /* List is empty again */ s_dynamic_info_list = NULL; } static void tftp_dissect_options(tvbuff_t *tvb, packet_info *pinfo, int offset, proto_tree *tree, guint16 opcode, tftp_conv_info_t *tftp_info) { int option_len, value_len; int value_offset; const char *optionname; const char *optionvalue; proto_tree *opt_tree; while (tvb_offset_exists(tvb, offset)) { /* option_len and value_len include the trailing 0 byte */ option_len = tvb_strsize(tvb, offset); value_offset = offset + option_len; value_len = tvb_strsize(tvb, value_offset); /* use xxx_len-1 to exclude the trailing 0 byte, it would be displayed as nonprinting character tvb_format_text() creates a temporary 0-terminated buffer */ optionname = tvb_format_text(tvb, offset, option_len-1); optionvalue = tvb_format_text(tvb, value_offset, value_len-1); opt_tree = proto_tree_add_subtree_format(tree, tvb, offset, option_len+value_len, ett_tftp_option, NULL, "Option: %s = %s", optionname, optionvalue); proto_tree_add_item(opt_tree, hf_tftp_option_name, tvb, offset, option_len, ENC_ASCII|ENC_NA); proto_tree_add_item(opt_tree, hf_tftp_option_value, tvb, value_offset, value_len, ENC_ASCII|ENC_NA); offset += option_len + value_len; col_append_fstr(pinfo->cinfo, COL_INFO, ", %s=%s", optionname, optionvalue); /* Special code to handle individual options */ if (!g_ascii_strcasecmp((const char *)optionname, "blksize") && opcode == TFTP_OACK) { gint blocksize = (gint)strtol((const char *)optionvalue, NULL, 10); if (blocksize < 8 || blocksize > 65464) { expert_add_info(pinfo, NULL, &ei_tftp_blocksize_range); } else { tftp_info->blocksize = blocksize; } } } } static void cleanup_tftp_blocks(tftp_conv_info_t *conv) { GSList *block_iterator; /* Walk list of block items */ for (block_iterator = conv->block_list; block_iterator; block_iterator = block_iterator->next) { file_block_t *block = (file_block_t*)block_iterator->data; /* Free block data */ wmem_free(NULL, block->data); /* Free block itself */ g_free(block); } conv->block_list = NULL; conv->file_length = 0; } static void dissect_tftp_message(tftp_conv_info_t *tftp_info, tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) { proto_tree *tftp_tree; proto_item *ti; gint offset = 0; guint16 opcode; guint16 bytes; guint16 blocknum; guint i1; guint16 error; tvbuff_t *data_tvb = NULL; col_set_str(pinfo->cinfo, COL_PROTOCOL, "TFTP"); /* Protocol root */ ti = proto_tree_add_item(tree, proto_tftp, tvb, offset, -1, ENC_NA); tftp_tree = proto_item_add_subtree(ti, ett_tftp); /* Opcode */ opcode = tvb_get_ntohs(tvb, offset); proto_tree_add_uint(tftp_tree, hf_tftp_opcode, tvb, offset, 2, opcode); col_add_str(pinfo->cinfo, COL_INFO, val_to_str(opcode, tftp_opcode_vals, "Unknown (0x%04x)")); offset += 2; /* read and write requests contain file names for other messages, we add the filenames from the conversation */ if (opcode!=TFTP_RRQ && opcode!=TFTP_WRQ) { if (tftp_info->source_file) { ti = proto_tree_add_string(tftp_tree, hf_tftp_source_file, tvb, 0, 0, tftp_info->source_file); PROTO_ITEM_SET_GENERATED(ti); } if (tftp_info->destination_file) { ti = proto_tree_add_string(tftp_tree, hf_tftp_destination_file, tvb, 0, 0, tftp_info->destination_file); PROTO_ITEM_SET_GENERATED(ti); } } switch (opcode) { case TFTP_RRQ: i1 = tvb_strsize(tvb, offset); proto_tree_add_item_ret_string(tftp_tree, hf_tftp_source_file, tvb, offset, i1, ENC_ASCII|ENC_NA, wmem_file_scope(), &tftp_info->source_file); /* we either have a source file name (for read requests) or a destination file name (for write requests) when we set one of the names, we clear the other */ tftp_info->destination_file = NULL; col_append_fstr(pinfo->cinfo, COL_INFO, ", File: %s", tvb_format_stringzpad(tvb, offset, i1)); offset += i1; i1 = tvb_strsize(tvb, offset); proto_tree_add_item(tftp_tree, hf_tftp_transfer_type, tvb, offset, i1, ENC_ASCII|ENC_NA); col_append_fstr(pinfo->cinfo, COL_INFO, ", Transfer type: %s", tvb_format_stringzpad(tvb, offset, i1)); offset += i1; tftp_dissect_options(tvb, pinfo, offset, tftp_tree, opcode, tftp_info); break; case TFTP_WRQ: i1 = tvb_strsize(tvb, offset); proto_tree_add_item_ret_string(tftp_tree, hf_tftp_destination_file, tvb, offset, i1, ENC_ASCII|ENC_NA, wmem_file_scope(), &tftp_info->destination_file); tftp_info->source_file = NULL; /* see above */ col_append_fstr(pinfo->cinfo, COL_INFO, ", File: %s", tvb_format_stringzpad(tvb, offset, i1)); offset += i1; i1 = tvb_strsize(tvb, offset); proto_tree_add_item(tftp_tree, hf_tftp_transfer_type, tvb, offset, i1, ENC_ASCII|ENC_NA); col_append_fstr(pinfo->cinfo, COL_INFO, ", Transfer type: %s", tvb_format_stringzpad(tvb, offset, i1)); offset += i1; tftp_dissect_options(tvb, pinfo, offset, tftp_tree, opcode, tftp_info); break; case TFTP_INFO: tftp_dissect_options(tvb, pinfo, offset, tftp_tree, opcode, tftp_info); break; case TFTP_DATA: blocknum = tvb_get_ntohs(tvb, offset); proto_tree_add_uint(tftp_tree, hf_tftp_blocknum, tvb, offset, 2, blocknum); /* Sequence analysis on blocknums (first pass only) */ if (!pinfo->fd->flags.visited) { if (blocknum > tftp_info->next_block_num) { /* There is a gap. Don't try to recover from this. */ tftp_info->next_block_num = blocknum + 1; tftp_info->blocks_missing = TRUE; /* TODO: add info to a result table for showing expert info in later passes */ } else if (blocknum == tftp_info->next_block_num) { /* OK, inc what we expect next */ tftp_info->next_block_num++; } } offset += 2; /* Show number of bytes in this block, and whether it is the end of the file */ bytes = tvb_reported_length_remaining(tvb, offset); col_append_fstr(pinfo->cinfo, COL_INFO, ", Block: %i%s", blocknum, (bytes < tftp_info->blocksize)?" (last)":"" ); /* Show data in tree */ if (bytes > 0) { data_tvb = tvb_new_subset_length_caplen(tvb, offset, -1, bytes); call_data_dissector(data_tvb, pinfo, tree); } /* If Export Object tap is listening, need to accumulate blocks info list to send to tap. But if already know there are blocks missing, there is no point in trying. */ if (have_tap_listener(tftp_eo_tap) && !tftp_info->blocks_missing) { file_block_t *block; if (blocknum == 1) { /* Reset data for this conversation, freeing any accumulated blocks! */ cleanup_tftp_blocks(tftp_info); tftp_info->next_tap_block_num = 1; } if (blocknum != tftp_info->next_tap_block_num) { /* Ignore. Could be missing frames, or just clicking previous frame */ return; } if (bytes > 0) { /* Create a block for this block */ block = (file_block_t*)g_malloc(sizeof(file_block_t)); block->length = bytes; block->data = tvb_memdup(NULL, data_tvb, 0, bytes); /* Add to the end of the list (does involve traversing whole list..) */ tftp_info->block_list = g_slist_append(tftp_info->block_list, block); tftp_info->file_length += bytes; /* Look for next blocknum next time */ tftp_info->next_tap_block_num++; } /* Tap export object only when reach end of file */ if (bytes < tftp_info->blocksize) { tftp_eo_t *eo_info; /* If don't have a filename, won't tap file info */ if ((tftp_info->source_file == NULL) && (tftp_info->destination_file == NULL)) { cleanup_tftp_blocks(tftp_info); return; } /* Create the eo_info to pass to the listener */ eo_info = wmem_new(wmem_packet_scope(), tftp_eo_t); /* Set filename */ if (tftp_info->source_file) { eo_info->filename = g_strdup(tftp_info->source_file); } else if (tftp_info->destination_file) { eo_info->filename = g_strdup(tftp_info->destination_file); } /* Send block list, which will be combined and freed at tap. */ eo_info->payload_len = tftp_info->file_length; eo_info->pkt_num = blocknum; eo_info->block_list = tftp_info->block_list; /* Send to tap */ tap_queue_packet(tftp_eo_tap, pinfo, eo_info); /* Have sent, so forget list of blocks, and only pay attention if we get back to the first block again. */ tftp_info->block_list = NULL; tftp_info->next_tap_block_num = 1; } } break; case TFTP_ACK: blocknum = tvb_get_ntohs(tvb, offset); proto_tree_add_uint(tftp_tree, hf_tftp_blocknum, tvb, offset, 2, blocknum); col_append_fstr(pinfo->cinfo, COL_INFO, ", Block: %i", blocknum); break; case TFTP_ERROR: error = tvb_get_ntohs(tvb, offset); proto_tree_add_uint(tftp_tree, hf_tftp_error_code, tvb, offset, 2, error); col_append_fstr(pinfo->cinfo, COL_INFO, ", Code: %s", val_to_str(error, tftp_error_code_vals, "Unknown (%u)")); offset += 2; i1 = tvb_strsize(tvb, offset); proto_tree_add_item(tftp_tree, hf_tftp_error_string, tvb, offset, i1, ENC_ASCII|ENC_NA); col_append_fstr(pinfo->cinfo, COL_INFO, ", Message: %s", tvb_format_stringzpad(tvb, offset, i1)); expert_add_info(pinfo, NULL, &ei_tftp_blocksize_range); break; case TFTP_OACK: tftp_dissect_options(tvb, pinfo, offset, tftp_tree, opcode, tftp_info); break; default: proto_tree_add_item(tftp_tree, hf_tftp_data, tvb, offset, -1, ENC_NA); break; } } static gboolean dissect_embeddedtftp_heur(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { /* Used to dissect TFTP packets where one can not assume that the TFTP is the only protocol used by that port, and that TFTP may not be carried by UDP */ conversation_t *conversation = NULL; guint16 opcode; tftp_conv_info_t *tftp_info; /* * We need to verify it could be a TFTP message before creating a conversation */ if (tvb_captured_length(tvb) < MIN_HDR_LEN) return FALSE; opcode = tvb_get_ntohs(tvb, 0); switch (opcode) { case TFTP_RRQ: case TFTP_WRQ: /* These 2 opcodes have a NULL-terminated source file name after opcode. Verify */ { gint char_offset = 1; while (tvb_captured_length_remaining(tvb, char_offset)) { gchar c = (gchar)tvb_get_guint8(tvb, char_offset++); if (c == '\0') { /* NULL termination found - continue with dissection */ break; } else if (!g_ascii_isprint(c)) { /* Not part of a file name - give up now */ return FALSE; } } /* Would have to have a short capture length to not include the whole filename, but fall through here anyway rather than returning FALSE */ } /* Intentionally dropping through here... */ case TFTP_DATA: case TFTP_ACK: case TFTP_OACK: case TFTP_INFO: break; case TFTP_ERROR: /* for an error, we can verify the error code is legit */ switch (tvb_get_ntohs(tvb, 2)) { case TFTP_ERR_NOT_DEF: case TFTP_ERR_NOT_FOUND: case TFTP_ERR_NOT_ALLOWED: case TFTP_ERR_DISK_FULL: case TFTP_ERR_BAD_OP: case TFTP_ERR_BAD_ID: case TFTP_ERR_EXISTS: case TFTP_ERR_NO_USER: case TFTP_ERR_OPT_FAIL: break; default: return FALSE; } break; default: return FALSE; } conversation = find_or_create_conversation(pinfo); tftp_info = (tftp_conv_info_t *)conversation_get_proto_data(conversation, proto_tftp); if (!tftp_info) { tftp_info = wmem_new(wmem_file_scope(), tftp_conv_info_t); tftp_info->blocksize = 512; /* TFTP default block size */ tftp_info->source_file = NULL; tftp_info->destination_file = NULL; tftp_info->next_block_num = 1; tftp_info->blocks_missing = FALSE; tftp_info->next_tap_block_num = 1; tftp_info->block_list = NULL; tftp_info->file_length = 0; conversation_add_proto_data(conversation, proto_tftp, tftp_info); } dissect_tftp_message(tftp_info, tvb, pinfo, tree); return TRUE; } static int dissect_tftp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_) { conversation_t *conversation = NULL; tftp_conv_info_t *tftp_info; /* * The first TFTP packet goes to the TFTP port; the second one * comes from some *other* port, but goes back to the same * IP address and port as the ones from which the first packet * came; all subsequent packets go between those two IP addresses * and ports. * * If this packet went to the TFTP port (either to one of the ports * set in the preferences or to a port set via Decode As), we check * to see if there's already a conversation with one address/port pair * matching the source IP address and port of this packet, * the other address matching the destination IP address of this * packet, and any destination port. * * If not, we create one, with its address 1/port 1 pair being * the source address/port of this packet, its address 2 being * the destination address of this packet, and its port 2 being * wildcarded, and give it the TFTP dissector as a dissector. */ if (value_is_in_range(global_tftp_port_range, pinfo->destport) || (pinfo->match_uint == pinfo->destport)) { conversation = find_conversation(pinfo->num, &pinfo->src, &pinfo->dst, ENDPOINT_UDP, pinfo->srcport, 0, NO_PORT_B); if( (conversation == NULL) || (conversation_get_dissector(conversation, pinfo->num) != tftp_handle) ){ conversation = conversation_new(pinfo->num, &pinfo->src, &pinfo->dst, ENDPOINT_UDP, pinfo->srcport, 0, NO_PORT2); conversation_set_dissector(conversation, tftp_handle); } } else { conversation = find_conversation_pinfo(pinfo, 0); if( (conversation == NULL) || (conversation_get_dissector(conversation, pinfo->num) != tftp_handle) ){ conversation = conversation_new(pinfo->num, &pinfo->src, &pinfo->dst, ENDPOINT_UDP, pinfo->destport, pinfo->srcport, 0); conversation_set_dissector(conversation, tftp_handle); } else if (conversation->options & NO_PORT_B) { if (pinfo->destport == conversation_key_port1(conversation->key_ptr)) conversation_set_port2(conversation, pinfo->srcport); else return 0; } } tftp_info = (tftp_conv_info_t *)conversation_get_proto_data(conversation, proto_tftp); if (!tftp_info) { tftp_info = wmem_new(wmem_file_scope(), tftp_conv_info_t); tftp_info->blocksize = 512; /* TFTP default block size */ tftp_info->source_file = NULL; tftp_info->destination_file = NULL; tftp_info->next_block_num = 1; tftp_info->blocks_missing = FALSE; tftp_info->next_tap_block_num = 1; tftp_info->block_list = NULL; tftp_info->file_length = 0; conversation_add_proto_data(conversation, proto_tftp, tftp_info); } dissect_tftp_message(tftp_info, tvb, pinfo, tree); return tvb_captured_length(tvb); } static void apply_tftp_prefs(void) { global_tftp_port_range = prefs_get_range_value("tftp", "udp.port"); } void proto_register_tftp(void) { static hf_register_info hf[] = { { &hf_tftp_opcode, { "Opcode", "tftp.opcode", FT_UINT16, BASE_DEC, VALS(tftp_opcode_vals), 0x0, "TFTP message type", HFILL }}, { &hf_tftp_source_file, { "Source File", "tftp.source_file", FT_STRINGZ, BASE_NONE, NULL, 0x0, "TFTP source file name", HFILL }}, { &hf_tftp_destination_file, { "DESTINATION File", "tftp.destination_file", FT_STRINGZ, BASE_NONE, NULL, 0x0, "TFTP source file name", HFILL }}, { &hf_tftp_transfer_type, { "Type", "tftp.type", FT_STRINGZ, BASE_NONE, NULL, 0x0, "TFTP transfer type", HFILL }}, { &hf_tftp_blocknum, { "Block", "tftp.block", FT_UINT16, BASE_DEC, NULL, 0x0, "Block number", HFILL }}, { &hf_tftp_error_code, { "Error code", "tftp.error.code", FT_UINT16, BASE_DEC, VALS(tftp_error_code_vals), 0x0, "Error code in case of TFTP error message", HFILL }}, { &hf_tftp_error_string, { "Error message", "tftp.error.message", FT_STRINGZ, BASE_NONE, NULL, 0x0, "Error string in case of TFTP error message", HFILL }}, { &hf_tftp_option_name, { "Option name", "tftp.option.name", FT_STRINGZ, BASE_NONE, NULL, 0x0, NULL, HFILL }}, { &hf_tftp_option_value, { "Option value", "tftp.option.value", FT_STRINGZ, BASE_NONE, NULL, 0x0, NULL, HFILL }}, { &hf_tftp_data, { "Data", "tftp.data", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL }}, }; static gint *ett[] = { &ett_tftp, &ett_tftp_option, }; static ei_register_info ei[] = { { &ei_tftp_blocksize_range, { "tftp.blocksize_range", PI_RESPONSE_CODE, PI_WARN, "TFTP blocksize out of range", EXPFILL }}, }; expert_module_t* expert_tftp; proto_tftp = proto_register_protocol("Trivial File Transfer Protocol", "TFTP", "tftp"); proto_register_field_array(proto_tftp, hf, array_length(hf)); proto_register_subtree_array(ett, array_length(ett)); expert_tftp = expert_register_protocol(proto_tftp); expert_register_field_array(expert_tftp, ei, array_length(ei)); tftp_handle = register_dissector("tftp", dissect_tftp, proto_tftp); prefs_register_protocol(proto_tftp, apply_tftp_prefs); /* Register the tap for the "Export Object" function */ tftp_eo_tap = register_export_object(proto_tftp, tftp_eo_packet, tftp_eo_cleanup); } void proto_reg_handoff_tftp(void) { heur_dissector_add("stun", dissect_embeddedtftp_heur, "TFTP over TURN", "tftp_stun", proto_tftp, HEURISTIC_ENABLE); dissector_add_uint_range_with_preference("udp.port", UDP_PORT_TFTP_RANGE, tftp_handle); } /* * Editor modelines - http://www.wireshark.org/tools/modelines.html * * Local variables: * c-basic-offset: 2 * tab-width: 8 * indent-tabs-mode: nil * End: * * vi: set shiftwidth=2 tabstop=8 expandtab: * :indentSize=2:tabSize=8:noTabs=true: */