/* packet-ldss.c * Routines for Local Download Sharing Service dissection * Copyright 2009, Vasantha Crabb * and Chris Adams * * Wireshark - Network traffic analyzer * By Gerald Combs * Copyright 1998 Gerald Combs * * SPDX-License-Identifier: GPL-2.0-or-later */ /* LDSS is a protocol for peers on a LAN to cooperatively 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. */ #include "config.h" #include #include #include #include #include #include "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 static 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 */ static 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 */ static 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 static 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 static 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; uint16_t port; } ldss_broadcaster_t; /* Info about a file */ typedef struct _ldss_file_t { uint8_t *digest; uint8_t digest_type; } ldss_file_t; /* Info about a broadcast packet */ typedef struct _ldss_broadcast_t { uint32_t num; nstime_t ts; uint16_t message_id; uint16_t message_detail; uint16_t port; uint64_t size; uint64_t offset; uint8_t 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 { uint32_t num; nstime_t ts; uint64_t size; uint64_t offset; uint8_t compression; ldss_file_t *file; } ldss_file_request_t; /* Info attached to a file transfer conversation */ typedef struct _ldss_transfer_info_t { uint32_t 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; /* Define headers for ldss */ static int hf_ldss_message_id; static int hf_ldss_message_detail; static int hf_ldss_digest_type; static int hf_ldss_compression; static int hf_ldss_cookie; static int hf_ldss_digest; static int hf_ldss_size; static int hf_ldss_offset; static int hf_ldss_target_time; static int hf_ldss_reserved_1; static int hf_ldss_port; static int hf_ldss_rate; static int hf_ldss_priority; static int hf_ldss_property_count; static int hf_ldss_properties; static int hf_ldss_file_data; static int hf_ldss_response_in; static int hf_ldss_response_to; static int hf_ldss_initiated_by; static int hf_ldss_transfer_response_time; static int hf_ldss_transfer_completed_in; /* Define the tree for ldss */ static int ett_ldss_broadcast; static int ett_ldss_transfer; static int ett_ldss_transfer_req; static expert_field ei_ldss_unrecognized_line; static dissector_handle_t ldss_udp_handle; static dissector_handle_t ldss_tcp_handle; /* 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) { if (!find_conversation(broadcast->num, &broadcast->broadcaster->addr, &broadcast->broadcaster->addr, CONVERSATION_TCP, broadcast->broadcaster->port, broadcast->broadcaster->port, NO_ADDR_B|NO_PORT_B)) { conversation_t *transfer_conv; ldss_transfer_info_t *transfer_info; transfer_info = wmem_new0(wmem_file_scope(), 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, CONVERSATION_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_tcp_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. */ static int dissect_ldss_broadcast(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) { uint16_t messageID; uint8_t digest_type; uint8_t compression; uint32_t cookie; uint8_t *digest; uint64_t size; uint64_t offset; uint32_t targetTime; uint16_t port; uint16_t rate; uint16_t messageDetail = INFERRED_NONE; proto_tree *ti, *ldss_tree; const char *packet_type, *packet_detail; messageID = tvb_get_ntohs (tvb, 0); digest_type = tvb_get_uint8 (tvb, 2); compression = tvb_get_uint8 (tvb, 3); cookie = tvb_get_ntohl (tvb, 4); digest = (uint8_t *)tvb_memdup (wmem_file_scope(), 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_const(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_const(messageDetail, ldss_inferred_info, "unknown"); /* Set the info column */ 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. */ ti = proto_tree_add_item(tree, proto_ldss, tvb, 0, (tvb_captured_length(tvb) > 72) ? tvb_captured_length(tvb) : 72, ENC_NA); ldss_tree = proto_item_add_subtree(ti, ett_ldss_broadcast); proto_tree_add_item(ldss_tree, hf_ldss_message_id, tvb, 0, 2, ENC_BIG_ENDIAN); ti = proto_tree_add_uint(ldss_tree, hf_ldss_message_detail, tvb, 0, 0, messageDetail); proto_item_set_generated(ti); proto_tree_add_item(ldss_tree, hf_ldss_digest_type, tvb, 2, 1, ENC_BIG_ENDIAN); proto_tree_add_item(ldss_tree, hf_ldss_compression, tvb, 3, 1, ENC_BIG_ENDIAN); 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)" : ""); proto_tree_add_item(ldss_tree, hf_ldss_digest, tvb, 8, DIGEST_LEN, ENC_NA); proto_tree_add_item(ldss_tree, hf_ldss_size, tvb, 40, 8, ENC_BIG_ENDIAN); proto_tree_add_item(ldss_tree, hf_ldss_offset, tvb, 48, 8, ENC_BIG_ENDIAN); 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)); proto_tree_add_item(ldss_tree, hf_ldss_reserved_1, tvb, 60, 4, ENC_BIG_ENDIAN); 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" : "")); 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); proto_tree_add_item(ldss_tree, hf_ldss_priority, tvb, 68, 2, ENC_BIG_ENDIAN); proto_tree_add_item(ldss_tree, hf_ldss_property_count, tvb, 70, 2, ENC_BIG_ENDIAN); if (tvb_reported_length(tvb) > 72) { proto_tree_add_item(ldss_tree, hf_ldss_properties, tvb, 72, tvb_captured_length(tvb) - 72, ENC_NA); } /* 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) && !PINFO_FD_VISITED(pinfo)) { ldss_broadcast_t *data; /* Populate data from the broadcast */ data = wmem_new0(wmem_file_scope(), ldss_broadcast_t); data->num = pinfo->num; data->ts = pinfo->abs_ts; data->message_id = messageID; data->message_detail = messageDetail; data->port = port; data->size = size; data->offset = offset; data->compression = compression; data->file = wmem_new0(wmem_file_scope(), ldss_file_t); data->file->digest = digest; data->file->digest_type = digest_type; data->broadcaster = wmem_new0(wmem_file_scope(), ldss_broadcaster_t); copy_address_wmem(wmem_file_scope(), &data->broadcaster->addr, &pinfo->src); data->broadcaster->port = port; /* Dissect any future pushes/pulls */ if (port > 0) { prepare_ldss_transfer_conv(data); } } return tvb_captured_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 doesn't 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. */ static int dissect_ldss_transfer (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data) { conversation_t *transfer_conv; ldss_transfer_info_t *transfer_info; struct tcpinfo *transfer_tcpinfo; proto_tree *ti, *line_tree = NULL, *ldss_tree = NULL; nstime_t broadcast_response_time; /* Reject the packet if data is NULL */ if (data == NULL) return 0; transfer_tcpinfo = (struct tcpinfo *)data; col_set_str(pinfo->cinfo, COL_PROTOCOL, "LDSS"); /* Look for the transfer conversation; this was created during * earlier broadcast dissection (see prepare_ldss_transfer_conv) */ transfer_conv = find_conversation (pinfo->num, &pinfo->src, &pinfo->dst, CONVERSATION_TCP, pinfo->srcport, pinfo->destport, 0); DISSECTOR_ASSERT(transfer_conv); transfer_info = (ldss_transfer_info_t *)conversation_get_proto_data(transfer_conv, proto_ldss); DISSECTOR_ASSERT(transfer_info); /* 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) */ unsigned offset = 0; col_set_str(pinfo->cinfo, COL_INFO, "LDSS File Transfer (Requesting file - pull)"); if (transfer_info->req == NULL) { transfer_info->req = wmem_new0(wmem_file_scope(), ldss_file_request_t); transfer_info->req->file = wmem_new0(wmem_file_scope(), ldss_file_t); } ti = proto_tree_add_item(tree, proto_ldss, tvb, 0, tvb_reported_length(tvb), ENC_NA); 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 (tvb_offset_exists(tvb, offset)) { int next_offset; const uint8_t *line; int linelen; unsigned digest_type_len = 0; linelen = tvb_find_line_end(tvb, offset, -1, &next_offset, false); /* Include new-line in line */ line = tvb_get_string_enc(pinfo->pool, tvb, offset, linelen, ENC_ASCII); line_tree = proto_tree_add_subtree(ldss_tree, tvb, offset, linelen, ett_ldss_transfer_req, NULL, tvb_format_text(pinfo->pool, tvb, offset, next_offset-offset)); if (strncmp(line,"md5:",4)==0) { digest_type_len = 4; transfer_info->file->digest_type = DIGEST_TYPE_MD5; } else if (strncmp(line, "sha1:", 5)==0) { digest_type_len = 5; transfer_info->file->digest_type = DIGEST_TYPE_SHA1; } else if (strncmp(line, "sha256:", 7)==0) { digest_type_len = 7; transfer_info->file->digest_type = DIGEST_TYPE_SHA256; } else if (strncmp(line, "unknown:", 8)==0) { 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); 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); 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 = (int8_t)strtol(line+13, NULL, 10); /* XXX - bad cast */ 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 { proto_tree_add_expert(line_tree, pinfo, &ei_ldss_unrecognized_line, tvb, offset, linelen); } if (digest_type_len > 0) { proto_item *tii = NULL; /* Sample digest-type/digest line: * md5:0123456789ABCDEF\n */ if (!transfer_info->file->digest) { 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); if(digest_bytes->len >= DIGEST_LEN) digest_bytes->len = (DIGEST_LEN-1); /* Ensure the digest is zero-padded */ transfer_info->file->digest = (uint8_t *)wmem_alloc0(wmem_file_scope(), DIGEST_LEN); memcpy(transfer_info->file->digest, digest_bytes->data, digest_bytes->len); g_byte_array_free(digest_bytes, true); } tii = proto_tree_add_uint(line_tree, hf_ldss_digest_type, tvb, offset, digest_type_len, transfer_info->file->digest_type); proto_item_set_generated(tii); tii = proto_tree_add_bytes(line_tree, hf_ldss_digest, tvb, offset+digest_type_len, MIN(linelen-digest_type_len, DIGEST_LEN), transfer_info->file->digest); proto_item_set_generated(tii); } offset = next_offset; } /* Link forwards to the response for this pull. */ if (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->num; transfer_info->req->ts = pinfo->abs_ts; } /* Remaining packets are the file response */ else { uint64_t size; uint64_t offset; uint8_t 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_captured_length(tvb) < size) { pinfo->desegment_offset = 0; pinfo->desegment_len = DESEGMENT_UNTIL_FIN; return -1; } } /* OK. Now we have the whole file that was transferred. */ transfer_info->resp_num = pinfo->num; transfer_info->resp_ts = pinfo->abs_ts; col_add_fstr(pinfo->cinfo, COL_INFO, "LDSS File Transfer (Sending file - %s)", transfer_info->broadcast->message_id == MESSAGE_ID_WILLSEND ? "pull" : "push"); ti = proto_tree_add_item(tree, proto_ldss, tvb, 0, tvb_reported_length(tvb), ENC_NA); ldss_tree = proto_item_add_subtree(ti, ett_ldss_transfer); proto_tree_add_bytes_format(ldss_tree, hf_ldss_file_data, tvb, 0, tvb_captured_length(tvb), NULL, compression == COMPRESSION_GZIP ? "Gzip compressed data: %d bytes" : "File data: %d bytes", tvb_captured_length(tvb)); #if defined (HAVE_ZLIB) || defined (HAVE_ZLIBNG) /* Be nice and uncompress the file data. */ if (compression == COMPRESSION_GZIP) { tvbuff_t *uncomp_tvb; uncomp_tvb = tvb_child_uncompress_zlib(tvb, tvb, 0, tvb_captured_length(tvb)); if (uncomp_tvb != NULL) { /* XXX: Maybe not a good idea to add a data_source for what may very well be a large buffer since then the full uncompressed buffer will be shown in a tab in the hex bytes pane ? However, if we don't, bytes in an unrelated tab will be highlighted. */ add_new_data_source(pinfo, uncomp_tvb, "Uncompressed Data"); proto_tree_add_bytes_format_value(ldss_tree, hf_ldss_file_data, uncomp_tvb, 0, tvb_captured_length(uncomp_tvb), NULL, "Uncompressed data: %d bytes", tvb_captured_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_captured_length(tvb); } static bool is_broadcast(address* addr) { static const uint8_t broadcast_addr_bytes[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; static const address broadcast_addr = ADDRESS_INIT(AT_ETHER, 6, broadcast_addr_bytes); return addresses_equal(addr, &broadcast_addr); } static int dissect_ldss (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { if (is_broadcast(&pinfo->dl_dst)) { col_set_str(pinfo->cinfo, COL_PROTOCOL, "LDSS"); return dissect_ldss_broadcast(tvb, pinfo, tree); } /* Definitely not LDSS */ return 0; } void proto_register_ldss (void) { static hf_register_info hf[] = { { &hf_ldss_message_id, { "LDSS Message ID", "ldss.message_id", FT_UINT16, BASE_DEC, VALS(ldss_message_id_value), 0x0, NULL, HFILL } }, { &hf_ldss_message_detail, { "Inferred meaning", "ldss.inferred_meaning", FT_UINT16, BASE_DEC, VALS(ldss_inferred_value), 0x0, "Inferred meaning of the packet", HFILL } }, { &hf_ldss_digest_type, { "Digest Type", "ldss.digest_type", FT_UINT8, BASE_DEC, VALS(ldss_digest_type_value), 0x0, NULL, HFILL } }, { &hf_ldss_compression, { "Compressed Format", "ldss.compression", FT_UINT8, BASE_DEC, VALS(ldss_compression_value), 0x0, NULL, 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_NONE, 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, NULL, HFILL } }, { &hf_ldss_property_count, { "Property Count", "ldss.property_count", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL } }, { &hf_ldss_properties, { "Properties", "ldss.properties", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_ldss_file_data, { "File data", "ldss.file_data", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_ldss_response_in, { "Response In", "ldss.response_in", FT_FRAMENUM, BASE_NONE, 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_NONE, 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_NONE, 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 int *ett[] = { &ett_ldss_broadcast, &ett_ldss_transfer, &ett_ldss_transfer_req }; static ei_register_info ei[] = { { &ei_ldss_unrecognized_line, { "ldss.unrecognized_line", PI_PROTOCOL, PI_WARN, "Unrecognized line ignored", EXPFILL }}, }; expert_module_t* expert_ldss; 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)); expert_ldss = expert_register_protocol(proto_ldss); expert_register_field_array(expert_ldss, ei, array_length(ei)); ldss_udp_handle = register_dissector("ldss", dissect_ldss, proto_ldss); ldss_tcp_handle = register_dissector("ldss_transfer", dissect_ldss_transfer, proto_ldss); } /* The registration hand-off routine */ void proto_reg_handoff_ldss (void) { dissector_add_uint_with_preference("udp.port", UDP_PORT_LDSS, ldss_udp_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: */