diff options
author | Stephen Fisher <steve@stephen-fisher.com> | 2009-03-29 04:58:27 +0000 |
---|---|---|
committer | Stephen Fisher <steve@stephen-fisher.com> | 2009-03-29 04:58:27 +0000 |
commit | a7d48ed8b6948df2a0b9d295d97ce51fbc5fbfb8 (patch) | |
tree | 4462be6eafd50ffc5e0abca0a31882d6625fce8c /epan/dissectors/packet-ldss.c | |
parent | c023fe9066dd14069af3d7fc5fd5612005a8ccd9 (diff) |
Patch from enhancement bug #3265: LDSS (local download sharing service)
svn path=/trunk/; revision=27877
Diffstat (limited to 'epan/dissectors/packet-ldss.c')
-rw-r--r-- | epan/dissectors/packet-ldss.c | 1017 |
1 files changed, 1017 insertions, 0 deletions
diff --git a/epan/dissectors/packet-ldss.c b/epan/dissectors/packet-ldss.c new file mode 100644 index 0000000000..5b712ee800 --- /dev/null +++ b/epan/dissectors/packet-ldss.c @@ -0,0 +1,1017 @@ +/* packet-ldss.c + * Routines for Local Download Sharing Service dissection + * Copyright 2009, Vasantha Crabb <vcrabb@managesoft.com.au> + * and Chris Adams <cadams@managesoft.com.au> + * + * $Id$ + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * 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. + */ + +/* LDSS is a protocol for peers on a LAN to co-operatively download + * files from a WAN. The peers ask each other about files and can + * send files to each other, thus WAN use is minimized. However + * if no peer possesses a file, a peer can download it via the WAN. + * Usually the download uses HTTP, but WAN downloads are beyond + * the scope of this dissector. To avoid saturating the WAN link, + * peers also tell each other what they are fetching and how fast + * they're downloading. Files are identified only by digests. + * Broadcasts are sent via UDP and files transferred via TCP. Both + * UDP and TCP portions of the protocol are handled in this dissector. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <gmodule.h> +#include <ctype.h> +#include <time.h> +#include <math.h> +#include <glib.h> +#include <string.h> +#include <epan/packet.h> +#include <epan/conversation.h> +#include <epan/addr_resolv.h> +#include <epan/ipproto.h> +#include <epan/nstime.h> +#include <epan/prefs.h> +#include <epan/strutil.h> +#include <epan/dissectors/packet-tcp.h> + +/* The digest is up to 32 bytes long */ +#define DIGEST_LEN 32 + +#define MESSAGE_ID_NEEDFILE 0 +#define MESSAGE_ID_WILLSEND 1 + + +const value_string ldss_message_id_value[] = { + { MESSAGE_ID_NEEDFILE, "Need file" }, + { MESSAGE_ID_WILLSEND, "Will send" }, + { 0, NULL } +}; + +/* Message detail is inferred from various contents in the packet */ +#define INFERRED_PEERSHUTDOWN 0 +#define INFERRED_SEARCH 1 +#define INFERRED_OFFER 2 +#define INFERRED_PROMISE 3 +#define INFERRED_WANDOWNLOAD 4 +#define INFERRED_NONE 5 + +/* Displayed in the info column */ +const value_string ldss_inferred_info[] = { + { INFERRED_PEERSHUTDOWN, " - peer shutting down" }, + { INFERRED_SEARCH, " - search" }, + { INFERRED_OFFER, " - offer" }, + { INFERRED_PROMISE, " - promise" }, + { INFERRED_WANDOWNLOAD, " - WAN download start" }, + { INFERRED_NONE, "" }, + { 0, NULL } +}; + +/* Displayed in the tree as a generated item */ +const value_string ldss_inferred_value[] = { + { INFERRED_PEERSHUTDOWN, "Peer shutdown" }, + { INFERRED_SEARCH, "File search" }, + { INFERRED_OFFER, "File offer" }, + { INFERRED_PROMISE, "Promise (download in progress)" }, + { INFERRED_WANDOWNLOAD, "WAN download start" }, + { INFERRED_NONE, "" }, + { 0, NULL } +}; + + +#define DIGEST_TYPE_UNKNOWN 0 +#define DIGEST_TYPE_MD5 1 +#define DIGEST_TYPE_SHA1 2 +#define DIGEST_TYPE_SHA256 3 + + +const value_string ldss_digest_type_value[] = { + { DIGEST_TYPE_UNKNOWN, "Unknown" }, + { DIGEST_TYPE_MD5, "MD5" }, + { DIGEST_TYPE_SHA1, "SHA1" }, + { DIGEST_TYPE_SHA256, "SHA256" }, + { 0, NULL } +}; + + +#define COMPRESSION_NONE 0 +#define COMPRESSION_GZIP 1 + + +const value_string ldss_compression_value[] = { + { COMPRESSION_NONE, "None" }, + { COMPRESSION_GZIP, "gzip" }, + { 0, NULL } +}; + +/* Info about a broadcaster */ +typedef struct _ldss_broadcaster_t { + address addr; + guint16 port; +} ldss_broadcaster_t; + +/* Info about a file */ +typedef struct _ldss_file_t { + guint8 *digest; + guint8 digest_type; +} ldss_file_t; + +/* Info about a broadcast packet */ +typedef struct _ldss_broadcast_t { + guint32 num; + nstime_t ts; + guint16 message_id; + guint16 message_detail; + guint16 port; + guint64 size; + guint64 offset; + guint8 compression; + ldss_file_t *file; + ldss_broadcaster_t *broadcaster; +} ldss_broadcast_t; + +/* Info about a file as seen in a file request */ +typedef struct _ldss_file_req_t { + guint32 num; + nstime_t ts; + guint64 size; + guint64 offset; + guint8 compression; + ldss_file_t *file; +} ldss_file_request_t; + +/* Info attached to a file transfer conversation */ +typedef struct _ldss_transfer_info_t { + guint32 resp_num; + nstime_t resp_ts; + /* Refers either to the file in the request (for pull) + * or the file in the broadcast (for push) */ + ldss_file_t *file; + ldss_file_request_t *req; + ldss_broadcast_t *broadcast; +} ldss_transfer_info_t; + +/* Define udp_port for LDSS (IANA assigned) */ +#define UDP_PORT_LDSS 6087 + +void proto_register_ldss(void); +void proto_reg_handoff_ldss(void); + +/* Define the ldss proto */ +static int proto_ldss = -1; + +/* Define headers for ldss */ +static int hf_ldss_message_id = -1; +static int hf_ldss_message_detail = -1; +static int hf_ldss_digest_type = -1; +static int hf_ldss_compression = -1; +static int hf_ldss_cookie = -1; +static int hf_ldss_digest = -1; +static int hf_ldss_size = -1; +static int hf_ldss_offset = -1; +static int hf_ldss_target_time = -1; +static int hf_ldss_reserved_1 = -1; +static int hf_ldss_port = -1; +static int hf_ldss_rate = -1; +static int hf_ldss_priority = -1; +static int hf_ldss_property_count = -1; +static int hf_ldss_properties = -1; +static int hf_ldss_file_data = -1; +static int hf_ldss_response_in = -1; +static int hf_ldss_response_to = -1; +static int hf_ldss_initiated_by = -1; +static int hf_ldss_transfer_response_time = -1; +static int hf_ldss_transfer_completed_in = -1; + +/* Define the tree for ldss */ +static int ett_ldss_broadcast = -1; +static int ett_ldss_transfer = -1; +static int ett_ldss_transfer_req = -1; + +static dissector_handle_t ldss_handle; + +/* Global variables associated with the preferences for ldss */ +static guint global_udp_port_ldss = UDP_PORT_LDSS; + +/* Avoid creating conversations and data twice */ +static unsigned int highest_num_seen = 0; + +/* When seeing a broadcast talking about an open TCP port on a host, create + * a conversation to dissect anything sent/received at that address. Setup + * protocol data so the TCP dissection knows what broadcast triggered it. */ +static void +prepare_ldss_transfer_conv(ldss_broadcast_t *broadcast) +{ + conversation_t *transfer_conv; + ldss_transfer_info_t *transfer_info; + + transfer_info = se_alloc0(sizeof(ldss_transfer_info_t)); + transfer_info->broadcast = broadcast; + + /* Preparation for later push/pull dissection */ + transfer_conv = conversation_new (broadcast->num, &broadcast->broadcaster->addr, &broadcast->broadcaster->addr, + PT_TCP, broadcast->broadcaster->port, broadcast->broadcaster->port, NO_ADDR2|NO_PORT2); + conversation_add_proto_data(transfer_conv, proto_ldss, transfer_info); + conversation_set_dissector(transfer_conv, ldss_handle); +} + +/* Broadcasts are searches, offers or promises. + * + * Searches are sent by + * a peer when it needs a file (ie. while applying its policy, when it needs + * files such as installers to install software.) + * + * Each broadcast relates to one file and each file is identified only by its + * checksum - no file names are ever used. A search times out after 10 seconds + * (configurable) and the peer will then attempt to act on any offers by + * downloading (via push or pull - see dissect_ldss_transfer) from those peers. + * + * If no offers are received, the search fails and the peer fetches the file + * from a remote server, generally a HTTP server on the other side of a WAN. + * The protocol exists to minimize the number of WAN downloads needed. + * + * While downloading from WAN the peer sends promises to inform other peers + * when it will be available for them to download. This prevents multiple peers + * simultaneously downloading the same file. Promises also inform other peers + * how much download bandwidth is being used by their download. Other peers use + * this information and the configured knowledge of the WAN bandwidth to avoid + * saturating the WAN link, as file downloads are a non-time-critical and + * non-business-critical network function. LDSS is intended for networks of + * 5-20 machines connected by slow WAN link. The current implementation of the + * protocol allows administrator to configure "time windows" when WAN usage is + * throttled/unthrottled, though this isn't visible in LDSS. + * + * Once a WAN download or a LAN transfer (see below above dissect_ldss_transfer) + * has complete the peer will offer the file to other peers on the LAN so they + * don't need to download it themselves. + * + * Peers also notify when they shut down in case any other peer is waiting for + * a file. */ +int +dissect_ldss_broadcast(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) +{ + guint16 messageID; + guint8 digest_type; + guint8 compression; + guint32 cookie; + guint8 *digest; + guint64 size; + guint64 offset; + guint32 targetTime; + guint16 port; + guint16 rate; + guint16 messageDetail; + + proto_tree *ti, *ldss_tree; + + const gchar *packet_type, *packet_detail; + + messageID = tvb_get_ntohs (tvb, 0); + digest_type = tvb_get_guint8(tvb, 2); + compression = tvb_get_guint8(tvb, 3); + cookie = tvb_get_ntohl (tvb, 4); + digest = tvb_memdup(tvb, 8, DIGEST_LEN); + size = tvb_get_ntoh64 (tvb, 40); + offset = tvb_get_ntoh64 (tvb, 48); + targetTime = tvb_get_ntohl (tvb, 56); + port = tvb_get_ntohs (tvb, 64); + rate = tvb_get_ntohs (tvb, 66); + + packet_type = val_to_str(messageID, ldss_message_id_value, "unknown"); + + if (messageID == MESSAGE_ID_WILLSEND) { + if (cookie == 0) { + /* Shutdown: Dishonor promises from this peer. Current + * implementation abuses WillSend for this. */ + messageDetail = INFERRED_PEERSHUTDOWN; + } + else if (size == 0 && offset == 0) { + /* NeedFile search failed - going to WAN */ + messageDetail = INFERRED_WANDOWNLOAD; + } + else if (size > 0) { + /* Size is known (not always the case) */ + if (size == offset) { + /* File is available for pull on this peer's TCP port */ + messageDetail = INFERRED_OFFER; + } + else { + /* WAN download progress announcement from this peer */ + messageDetail = INFERRED_PROMISE; + } + } + } + else if (messageID == MESSAGE_ID_NEEDFILE) { + messageDetail = INFERRED_SEARCH; + } + packet_detail = val_to_str(messageDetail, ldss_inferred_info, "unknown"); + + /* Set the info column */ + if (check_col(pinfo->cinfo, COL_INFO)) { + col_add_fstr(pinfo->cinfo, COL_INFO, "LDSS Broadcast (%s%s)", + packet_type, + packet_detail); + } + + /* If we have a non-null tree (ie we are building the proto_tree + * instead of just filling out the columns), then give more detail. */ + if (tree) { + ti = proto_tree_add_item(tree, proto_ldss, + tvb, 0, (tvb_length(tvb) > 72) ? tvb_length(tvb) : 72, FALSE); + ldss_tree = proto_item_add_subtree(ti, ett_ldss_broadcast); + + ti = proto_tree_add_item(ldss_tree, hf_ldss_message_id, + tvb, 0, 2, FALSE); + ti = proto_tree_add_uint(ldss_tree, hf_ldss_message_detail, + tvb, 0, 0, messageDetail); + PROTO_ITEM_SET_GENERATED(ti); + ti = proto_tree_add_item(ldss_tree, hf_ldss_digest_type, + tvb, 2, 1, FALSE); + ti = proto_tree_add_item(ldss_tree, hf_ldss_compression, + tvb, 3, 1, FALSE); + ti = proto_tree_add_uint_format_value(ldss_tree, hf_ldss_cookie, + tvb, 4, 4, FALSE, + "0x%x%s", + cookie, + (cookie == 0) + ? " - shutdown (promises from this peer are no longer valid)" + : ""); + ti = proto_tree_add_item(ldss_tree, hf_ldss_digest, + tvb, 8, DIGEST_LEN, FALSE); + ti = proto_tree_add_item(ldss_tree, hf_ldss_size, + tvb, 40, 8, FALSE); + ti = proto_tree_add_item(ldss_tree, hf_ldss_offset, + tvb, 48, 8, FALSE); + ti = proto_tree_add_uint_format_value(ldss_tree, hf_ldss_target_time, + tvb, 56, 4, FALSE, + "%d:%02d:%02d", + (int)(targetTime / 3600), + (int)((targetTime / 60) % 60), + (int)(targetTime % 60)); + ti = proto_tree_add_item(ldss_tree, hf_ldss_reserved_1, + tvb, 60, 4, FALSE); + ti = proto_tree_add_uint_format_value(ldss_tree, hf_ldss_port, + tvb, 64, 2, FALSE, + "%d%s", + port, + (messageID == MESSAGE_ID_WILLSEND && + size > 0 && + size == offset) + ? " - file can be pulled at this TCP port" + : (messageID == MESSAGE_ID_NEEDFILE + ? " - file can be pushed to this TCP port" + : "")); + ti = proto_tree_add_uint_format_value(ldss_tree, hf_ldss_rate, + tvb, 66, 2, FALSE, + "%ld", + (rate > 0) + ? (long)floor(exp(rate * G_LN2 / 2048)) + : 0); + ti = proto_tree_add_item(ldss_tree, hf_ldss_priority, + tvb, 68, 2, FALSE); + ti = proto_tree_add_item(ldss_tree, hf_ldss_property_count, + tvb, 70, 2, FALSE); + if (tvb_length(tvb) > 72) { + ti = proto_tree_add_item(ldss_tree, hf_ldss_properties, + tvb, 72, tvb_length(tvb) - 72, FALSE); + } + } + + /* Finally, store the broadcast and register ourselves to dissect + * any pushes or pulls that result from this broadcast. All data + * is pushed/pulled over TCP using the port from the broadcast + * packet's port field. + * Track each by a TCP conversation with the remote end wildcarded. + * The TCP conv tracks back to a broadcast conv to determine what it + * is in response to. + * + * These steps only need to be done once per packet, so a variable + * tracks the highest frame number seen. Handles the case of first frame + * being frame zero. */ + if (messageDetail != INFERRED_PEERSHUTDOWN && + (highest_num_seen == 0 || + highest_num_seen < pinfo->fd->num)) { + + ldss_broadcast_t *data; + + /* Populate data from the broadcast */ + data = se_alloc0(sizeof(ldss_broadcast_t)); + data->num = pinfo->fd->num; + data->ts = pinfo->fd->abs_ts; + data->message_id = messageID; + data->message_detail = messageDetail; + data->port = port; + data->size = size; + data->offset = offset; + data->compression = compression; + + data->file = se_alloc0(sizeof(ldss_file_t)); + data->file->digest = digest; + data->file->digest_type = digest_type; + + data->broadcaster = se_alloc0(sizeof(ldss_broadcaster_t)); + COPY_ADDRESS(&data->broadcaster->addr, &pinfo->src); + data->broadcaster->port = port; + + /* Dissect any future pushes/pulls */ + if (port > 0) { + prepare_ldss_transfer_conv(data); + } + + /* Record that the frame was processed */ + highest_num_seen = pinfo->fd->num; + } + + return tvb_length(tvb); +} + +/* Transfers happen in response to broadcasts, they are always TCP and are + * used to send the file to the port mentioned in the broadcast. There are + * 2 types of transfers: Pushes, which are direct responses to searches, + * in which the peer that has the file connects to the peer that doesnt and + * sends it, then disconnects. The other type of transfer is a pull, where + * the peer that doesn't have the file connects to the peer that does and + * requests it be sent. + * + * Pulls have a file request which identifies the desired file, + * while pushes simply send the file. In practice this works because every + * file the implementation sends searches for is on a different TCP port + * on the searcher's machine. */ +int +dissect_ldss_transfer (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) +{ + conversation_t *transfer_conv; + ldss_transfer_info_t *transfer_info; + struct tcpinfo *transfer_tcpinfo; + + proto_tree *ti, *line_tree, *ldss_tree; + + nstime_t broadcast_response_time; + + /* Look for the transfer conversation; this was created during + * earlier broadcast dissection (see prepate_ldss_transfer_conv) */ + transfer_conv = find_conversation (pinfo->fd->num, &pinfo->src, &pinfo->dst, + PT_TCP, pinfo->srcport, pinfo->destport, 0); + transfer_info = conversation_get_proto_data(transfer_conv, proto_ldss); + transfer_tcpinfo = pinfo->private_data; + + /* For a pull, the first packet in the TCP connection is the file request. + * First packet is identified by relative seq/ack numbers of 1. + * File request only appears on a pull (triggered by an offer - see above + * about broadcasts) */ + if (transfer_tcpinfo->seq == 1 && + transfer_tcpinfo->lastackseq == 1 && + transfer_info->broadcast->message_id == MESSAGE_ID_WILLSEND) { + /* LDSS pull transfers look a lot like HTTP. + * Sample request: + * md5:01234567890123... + * Size: 2550 + * Start: 0 + * Compression: 0 + * (remote end sends the file identified by the digest) */ + guint offset = 0; + gboolean already_dissected = TRUE; + + if (check_col(pinfo->cinfo, COL_INFO)) { + col_add_str(pinfo->cinfo, COL_INFO, "LDSS File Transfer (Requesting file - pull)"); + } + + if (highest_num_seen == 0 || + highest_num_seen < pinfo->fd->num) { + + already_dissected = FALSE; + transfer_info->req = se_alloc0(sizeof(ldss_file_request_t)); + transfer_info->req->file = se_alloc0(sizeof(ldss_file_t)); + highest_num_seen = pinfo->fd->num; + } + + if (tree) { + ti = proto_tree_add_item(tree, proto_ldss, + tvb, 0, tvb_reported_length(tvb), FALSE); + ldss_tree = proto_item_add_subtree(ti, ett_ldss_transfer); + } + + /* Populate digest data into the file struct in the request */ + transfer_info->file = transfer_info->req->file; + + /* Grab each line from the packet, there should be 4 but lets + * not walk off the end looking for more. */ + while (offset < tvb_reported_length(tvb)) { + gint next_offset; + const guint8 *line; + int linelen; + gboolean is_digest_line; + guint digest_type_len; + + linelen = tvb_find_line_end(tvb, offset, + tvb_ensure_length_remaining(tvb, offset), &next_offset, + FALSE); + + /* Include new-line in line */ + line = tvb_memdup(tvb, offset, linelen+1); /* XXX - memory leak? */ + + if (tree) { + ti = proto_tree_add_text(ldss_tree, tvb, offset, linelen, + "%s", + tvb_format_text(tvb, offset, next_offset-offset)); + line_tree = proto_item_add_subtree(ti, ett_ldss_transfer_req); + } + + /* Reduce code duplication processing digest lines. + * There are too many locals to pass to a function - the signature + * looked pretty ugly when I tried! */ + is_digest_line = FALSE; + + if (strncmp(line,"md5:",4)==0) { + is_digest_line = TRUE; + digest_type_len = 4; + transfer_info->file->digest_type = DIGEST_TYPE_MD5; + } + else if (strncmp(line, "sha1:", 5)==0) { + is_digest_line = TRUE; + digest_type_len = 5; + transfer_info->file->digest_type = DIGEST_TYPE_SHA1; + } + else if (strncmp(line, "sha256:", 7)==0) { + is_digest_line = TRUE; + digest_type_len = 7; + transfer_info->file->digest_type = DIGEST_TYPE_SHA256; + } + else if (strncmp(line, "unknown:", 8)==0) { + is_digest_line = TRUE; + digest_type_len = 8; + transfer_info->file->digest_type = DIGEST_TYPE_UNKNOWN; + } + else if (strncmp(line, "Size: ", 6)==0) { + /* Sample size line: + * Size: 2550\n */ + transfer_info->req->size = g_ascii_strtoull(line+6, NULL, 10); + if (tree) { + ti = proto_tree_add_uint64(line_tree, hf_ldss_size, + tvb, offset+6, linelen-6, transfer_info->req->size); + PROTO_ITEM_SET_GENERATED(ti); + } + } + else if (strncmp(line, "Start: ", 7)==0) { + /* Sample offset line: + * Start: 0\n */ + transfer_info->req->offset = g_ascii_strtoull(line+7, NULL, 10); + if (tree) { + ti = proto_tree_add_uint64(line_tree, hf_ldss_offset, + tvb, offset+7, linelen-7, transfer_info->req->offset); + PROTO_ITEM_SET_GENERATED(ti); + } + } + else if (strncmp(line, "Compression: ", 13)==0) { + /* Sample compression line: + * Compression: 0\n */ + transfer_info->req->compression = (gint8)strtol(line+13, NULL, 10); /* XXX - bad cast */ + if (tree) { + ti = proto_tree_add_uint(line_tree, hf_ldss_compression, + tvb, offset+13, linelen-13, transfer_info->req->compression); + PROTO_ITEM_SET_GENERATED(ti); + } + } + else { + if (tree) { + ti = proto_tree_add_text(line_tree, tvb, offset, linelen, + "Unrecognized line ignored"); + PROTO_ITEM_SET_GENERATED(ti); + } + } + + if (is_digest_line) { + /* Sample digest-type/digest line: + * md5:0123456789ABCDEF\n */ + if (!already_dissected) { + GByteArray *digest_bytes; + + digest_bytes = g_byte_array_new(); + hex_str_to_bytes( + tvb_get_ptr(tvb, offset+digest_type_len, linelen-digest_type_len), + digest_bytes, FALSE); + + /* Ensure the digest is zero-padded */ + transfer_info->file->digest = se_alloc0(DIGEST_LEN); + memcpy(transfer_info->file->digest, digest_bytes->data, digest_bytes->len); + + g_byte_array_free(digest_bytes, TRUE); + } + if (tree) { + proto_item *ti = NULL; + + ti = proto_tree_add_uint(line_tree, hf_ldss_digest_type, + tvb, offset, digest_type_len, transfer_info->file->digest_type); + PROTO_ITEM_SET_GENERATED(ti); + ti = proto_tree_add_bytes(line_tree, hf_ldss_digest, + tvb, offset+digest_type_len, linelen-digest_type_len, + transfer_info->file->digest); + PROTO_ITEM_SET_GENERATED(ti); + } + } + + offset = next_offset; + } + + /* Link forwards to the response for this pull. */ + if (tree && transfer_info->resp_num != 0) { + ti = proto_tree_add_uint(ldss_tree, hf_ldss_response_in, + tvb, 0, 0, transfer_info->resp_num); + PROTO_ITEM_SET_GENERATED(ti); + } + + transfer_info->req->num = pinfo->fd->num; + transfer_info->req->ts = pinfo->fd->abs_ts; + } + /* Remaining packets are the file response */ + else { + guint64 size; + guint64 offset; + guint8 compression; + + /* size, digest, compression come from the file request for a pull but + * they come from the broadcast for a push. Pushes don't bother + * with a file request - they just send the data. We have to get file + * info from the offer broadcast which triggered this transfer. + * If we cannot find the file request, default to the broadcast. */ + if (transfer_info->broadcast->message_id == MESSAGE_ID_WILLSEND && + transfer_info->req != NULL) { + transfer_info->file = transfer_info->req->file; + size = transfer_info->req->size; + offset = transfer_info->req->offset; + compression = transfer_info->req->compression; + } + else { + transfer_info->file = transfer_info->broadcast->file; + size = transfer_info->broadcast->size; + offset = transfer_info->broadcast->offset; + compression = transfer_info->broadcast->compression; + } + + /* Remaining data in this TCP connection is all file data. + * Always desegment if the size is 0 (ie. unknown) + */ + if (pinfo->can_desegment) { + if (size == 0 || tvb_length(tvb) < size) { + pinfo->desegment_offset = 0; + pinfo->desegment_len = DESEGMENT_UNTIL_FIN; + return 0; + } + } + + /* OK. Now we have the whole file that was transferred. */ + transfer_info->resp_num = pinfo->fd->num; + transfer_info->resp_ts = pinfo->fd->abs_ts; + + if (check_col(pinfo->cinfo, COL_INFO)) { + col_add_fstr(pinfo->cinfo, COL_INFO, "LDSS File Transfer (Sending file - %s)", + transfer_info->broadcast->message_id == MESSAGE_ID_WILLSEND + ? "pull" + : "push"); + } + + if (tree) { + ti = proto_tree_add_item(tree, proto_ldss, + tvb, 0, tvb_reported_length(tvb), FALSE); + ldss_tree = proto_item_add_subtree(ti, ett_ldss_transfer); + ti = proto_tree_add_bytes_format(ldss_tree, hf_ldss_file_data, + tvb, 0, tvb_length(tvb), + tvb_get_ptr (tvb, 0, tvb_length(tvb)), + compression == COMPRESSION_GZIP + ? "Gzip compressed data: %d bytes" + : "File data: %d bytes", + tvb_length(tvb)); +#ifdef HAVE_LIBZ + /* Be nice and uncompress the file data. */ + if (compression == COMPRESSION_GZIP) { + tvbuff_t *uncomp_tvb = NULL; + uncomp_tvb = tvb_uncompress(tvb, 0, tvb_length(tvb)); + if (uncomp_tvb != NULL) { + ti = proto_tree_add_bytes_format_value(ldss_tree, hf_ldss_file_data, + uncomp_tvb, 0, tvb_length(uncomp_tvb), + tvb_get_ptr (uncomp_tvb, 0, tvb_length(uncomp_tvb)), + "Uncompressed data: %d bytes", + tvb_length(uncomp_tvb)); + } + } +#endif + ti = proto_tree_add_uint(ldss_tree, hf_ldss_digest_type, + tvb, 0, 0, transfer_info->file->digest_type); + PROTO_ITEM_SET_GENERATED(ti); + if (transfer_info->file->digest != NULL) { + /* This is ugly. You can't add bytes of nonzero length and have + * filtering work correctly unless you give a valid location in + * the packet. This hack pretends the first 32 bytes of the packet + * are the digest, which they aren't: they're actually the first 32 + * bytes of the file that was sent. */ + ti = proto_tree_add_bytes(ldss_tree, hf_ldss_digest, + tvb, 0, DIGEST_LEN, transfer_info->file->digest); + } + PROTO_ITEM_SET_GENERATED(ti); + ti = proto_tree_add_uint64(ldss_tree, hf_ldss_size, + tvb, 0, 0, size); + PROTO_ITEM_SET_GENERATED(ti); + ti = proto_tree_add_uint64(ldss_tree, hf_ldss_offset, + tvb, 0, 0, offset); + PROTO_ITEM_SET_GENERATED(ti); + ti = proto_tree_add_uint(ldss_tree, hf_ldss_compression, + tvb, 0, 0, compression); + PROTO_ITEM_SET_GENERATED(ti); + /* Link to the request for a pull. */ + if (transfer_info->broadcast->message_id == MESSAGE_ID_WILLSEND && + transfer_info->req != NULL && + transfer_info->req->num != 0) { + ti = proto_tree_add_uint(ldss_tree, hf_ldss_response_to, + tvb, 0, 0, transfer_info->req->num); + PROTO_ITEM_SET_GENERATED(ti); + } + } + } + + /* Print the pull response time */ + if (transfer_info->broadcast->message_id == MESSAGE_ID_WILLSEND && + transfer_info->req != NULL && + transfer_info->resp_num != 0) { + nstime_t pull_response_time; + nstime_delta(&pull_response_time, &transfer_info->resp_ts, + &transfer_info->req->ts); + ti = proto_tree_add_time(ldss_tree, hf_ldss_transfer_response_time, + tvb, 0, 0, &pull_response_time); + PROTO_ITEM_SET_GENERATED(ti); + } + + /* Link the transfer back to the initiating broadcast. Response time is + * calculated as the time from broadcast to completed transfer. */ + ti = proto_tree_add_uint(ldss_tree, hf_ldss_initiated_by, + tvb, 0, 0, transfer_info->broadcast->num); + PROTO_ITEM_SET_GENERATED(ti); + + if (transfer_info->resp_num != 0) { + nstime_delta(&broadcast_response_time, &transfer_info->resp_ts, + &transfer_info->broadcast->ts); + ti = proto_tree_add_time(ldss_tree, hf_ldss_transfer_completed_in, + tvb, 0, 0, &broadcast_response_time); + PROTO_ITEM_SET_GENERATED(ti); + } + + /* This conv got its addr2/port2 set by the TCP dissector because a TCP + * connection was established. Make a new one to handle future connections + * to the addr/port mentioned in the broadcast, because that socket is + * still open. */ + if (transfer_tcpinfo->seq == 1 && + transfer_tcpinfo->lastackseq == 1) { + + prepare_ldss_transfer_conv(transfer_info->broadcast); + } + + return tvb_length(tvb); +} + +gboolean +is_broadcast(address* addr) +{ + static address broadcast_addr; + static const guint8 broadcast_addr_bytes[6] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF + }; + + SET_ADDRESS(&broadcast_addr, AT_ETHER, 6, broadcast_addr_bytes); + return ADDRESSES_EQUAL(addr, &broadcast_addr); +} + +int +dissect_ldss (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) +{ + if (is_broadcast(&pinfo->dl_dst) && + pinfo->ipproto == IP_PROTO_UDP) { + + if(check_col(pinfo->cinfo,COL_PROTOCOL)){ + col_add_str(pinfo->cinfo,COL_PROTOCOL,"LDSS"); + } + return dissect_ldss_broadcast(tvb, pinfo, tree); + } + else if (pinfo->ipproto == IP_PROTO_TCP) { + + if(check_col(pinfo->cinfo,COL_PROTOCOL)){ + col_add_str(pinfo->cinfo,COL_PROTOCOL,"LDSS"); + } + return dissect_ldss_transfer(tvb, pinfo, tree); + } + else { + /* Definitely not LDSS */ + return 0; + } +} + +/* Initialize the highest num seen each time a + * new file is loaded or re-loaded in wireshark */ +static void +ldss_init_protocol(void) +{ + /* We haven't dissected anything yet. */ + highest_num_seen = 0; +} + +void +proto_register_ldss (void) { + static hf_register_info hf[] = + { { &hf_ldss_message_id, + { "LDSS Message ID", + "ldss.ldss_message_id", + FT_UINT16, BASE_DEC, ldss_message_id_value, 0x0, + "", HFILL + } + }, + { &hf_ldss_message_detail, + { "Inferred meaning", + "ldss.inferred_meaning", + FT_UINT16, BASE_DEC, ldss_inferred_value, 0x0, + "Inferred meaning of the packet", HFILL + } + }, + { &hf_ldss_digest_type, + { "Digest Type", + "ldss.digest_type", + FT_UINT8, BASE_DEC, ldss_digest_type_value, 0x0, + "", HFILL + } + }, + { &hf_ldss_compression, + { "Compressed Format", + "ldss.compression", + FT_UINT8, BASE_DEC, ldss_compression_value, 0x0, + "", HFILL + } + }, + { &hf_ldss_cookie, + { "Cookie", + "ldss.cookie", + FT_UINT32, BASE_HEX, NULL, 0x0, + "Random value used for duplicate rejection", HFILL + } + }, + { &hf_ldss_digest, + { "Digest", + "ldss.digest", + FT_BYTES, BASE_HEX, NULL, 0x0, + "Digest of file padded with 0x00", HFILL + } + }, + { &hf_ldss_size, + { "Size", + "ldss.size", + FT_UINT64, BASE_DEC, NULL, 0x0, + "Size of complete file", HFILL + } + }, + { &hf_ldss_offset, + { "Offset", + "ldss.offset", + FT_UINT64, BASE_DEC, NULL, 0x0, + "Size of currently available portion of file", HFILL + } + }, + { &hf_ldss_target_time, + { "Target time (relative)", + "ldss.target_time", + FT_UINT32, BASE_DEC, NULL, 0x0, + "Time until file will be needed/available", HFILL + } + }, + { &hf_ldss_reserved_1, + { "Reserved", + "ldss.reserved_1", + FT_UINT32, BASE_HEX, NULL, 0x0, + "Unused field - should be 0x00000000", HFILL + } + }, + { &hf_ldss_port, + { "Port", + "ldss.port", + FT_UINT16, BASE_DEC, NULL, 0x0, + "TCP port for push (Need file) or pull (Will send)", HFILL + } + }, + { &hf_ldss_rate, + { "Rate (B/s)", + "ldss.rate", + FT_UINT16, BASE_DEC, NULL, 0x0, + "Estimated current download rate", HFILL + } + }, + { &hf_ldss_priority, + { "Priority", + "ldss.priority", + FT_UINT16, BASE_DEC, NULL, 0x0, + "", HFILL + } + }, + { &hf_ldss_property_count, + { "Property Count", + "ldss.property_count", + FT_UINT16, BASE_DEC, NULL, 0x0, + "", HFILL + } + }, + { &hf_ldss_properties, + { "Properties", + "ldss.properties", + FT_BYTES, BASE_HEX, NULL, 0x0, + "", HFILL + } + }, + { &hf_ldss_file_data, + { "File data", + "ldss.file_data", + FT_BYTES, BASE_HEX, NULL, 0x0, + "", HFILL + } + }, + { &hf_ldss_response_in, + { "Response In", + "ldss.response_in", + FT_FRAMENUM, BASE_DEC, NULL, 0x0, + "The response to this file pull request is in this frame", HFILL } + }, + { &hf_ldss_response_to, + { "Request In", + "ldss.response_to", + FT_FRAMENUM, BASE_DEC, NULL, 0x0, + "This is a response to the file pull request in this frame", HFILL } + }, + { &hf_ldss_initiated_by, + { "Initiated by", + "ldss.initiated_by", + FT_FRAMENUM, BASE_DEC, NULL, 0x0, + "The broadcast that initiated this file transfer", HFILL } + }, + { &hf_ldss_transfer_response_time, + { "Transfer response time", + "ldss.transfer_response_time", + FT_RELATIVE_TIME, BASE_NONE, NULL, 0x0, + "The time between the request and the response for a pull transfer", HFILL } + }, + { &hf_ldss_transfer_completed_in, + { "Transfer completed in", + "ldss.transfer_completed_in", + FT_RELATIVE_TIME, BASE_NONE, NULL, 0x0, + "The time between requesting the file and completion of the file transfer", HFILL } + } + }; + + static gint *ett[] = { &ett_ldss_broadcast, &ett_ldss_transfer, &ett_ldss_transfer_req }; + + module_t *ldss_module; + + proto_ldss = proto_register_protocol( "Local Download Sharing Service", + "LDSS", "ldss"); + proto_register_field_array(proto_ldss, hf, array_length(hf)); + proto_register_subtree_array(ett, array_length(ett)); + + ldss_module = prefs_register_protocol( proto_ldss, + proto_reg_handoff_ldss); + prefs_register_uint_preference( ldss_module, "udp_port", + "LDSS UDP Port", + "The UDP port on which " + "Local Download Sharing Service " + "broadcasts will be sent", + 10, &global_udp_port_ldss); + + register_init_routine(&ldss_init_protocol); +} + + +/* The registration hand-off routine */ +void +proto_reg_handoff_ldss (void) +{ + static gboolean ldss_initialized = FALSE; + + if (!ldss_initialized) { + ldss_handle = new_create_dissector_handle(dissect_ldss, proto_ldss); + dissector_add("udp.port", global_udp_port_ldss, ldss_handle); + ldss_initialized = TRUE; + } +} |