/* Routines for LTE RLC disassembly * * Martin Mathieson * * Wireshark - Network traffic analyzer * By Gerald Combs * Copyright 1998 Gerald Combs * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "config.h" #include #include #include #include #include #include #include "packet-mac-lte.h" #include "packet-rlc-lte.h" #include "packet-rlc-3gpp-common.h" #include "packet-pdcp-lte.h" /* Described in: * 3GPP TS 36.322 Evolved Universal Terrestrial Radio Access (E-UTRA) * Radio Link Control (RLC) Protocol specification v14.0.0 */ /* TODO: - add intermediate results to segments leading to final reassembly - use multiple active rlc_channel_reassembly_info's per channel - sequence analysis gets confused when we change cells and skip back to SN 0. Maybe add cell-id to context and add to channel/result key? */ void proto_register_rlc_lte(void); void proto_reg_handoff_rlc_lte(void); /********************************/ /* Preference settings */ #define SEQUENCE_ANALYSIS_MAC_ONLY 1 #define SEQUENCE_ANALYSIS_RLC_ONLY 2 /* By default do try to analyse the sequence of messages for AM/UM channels using MAC PDUs */ static int global_rlc_lte_am_sequence_analysis = SEQUENCE_ANALYSIS_MAC_ONLY; static int global_rlc_lte_um_sequence_analysis = SEQUENCE_ANALYSIS_MAC_ONLY; /* By default do call PDCP/RRC dissectors for SDU data */ static bool global_rlc_lte_call_pdcp_for_srb = true; enum pdcp_for_drb { PDCP_drb_off, PDCP_drb_SN_7, PDCP_drb_SN_12, PDCP_drb_SN_signalled, PDCP_drb_SN_15, PDCP_drb_SN_18}; static const enum_val_t pdcp_drb_col_vals[] = { {"pdcp-drb-off", "Off", PDCP_drb_off}, {"pdcp-drb-sn-7", "7-bit SN", PDCP_drb_SN_7}, {"pdcp-drb-sn-12", "12-bit SN", PDCP_drb_SN_12}, {"pdcp-drb-sn-15", "15-bit SN", PDCP_drb_SN_15}, {"pdcp-drb-sn-18", "18-bit SN", PDCP_drb_SN_18}, {"pdcp-drb-sn-signalling", "Use signalled value", PDCP_drb_SN_signalled}, {NULL, NULL, -1} }; static int global_rlc_lte_call_pdcp_for_drb = (int)PDCP_drb_SN_signalled; static bool global_rlc_lte_call_rrc_for_ccch = true; static bool global_rlc_lte_call_rrc_for_mcch; static bool global_rlc_lte_call_ip_for_mtch; /* Preference to expect RLC headers without payloads */ static bool global_rlc_lte_headers_expected; /* Re-assembly of segments */ static bool global_rlc_lte_reassembly = true; /* Tree storing UE related parameters */ #define NO_EXT_LI 0x0 #define UL_EXT_LI 0x1 #define DL_EXT_LI 0x2 typedef struct rlc_ue_parameters { uint32_t id; uint8_t ext_li_field; uint8_t pdcp_sn_bits; } rlc_ue_parameters; static wmem_tree_t *ue_parameters_tree; /**************************************************/ /* Initialize the protocol and registered fields. */ int proto_rlc_lte; extern int proto_mac_lte; extern int proto_pdcp_lte; static dissector_handle_t pdcp_lte_handle; static dissector_handle_t ip_handle; static dissector_handle_t lte_rrc_mcch; static dissector_handle_t lte_rrc_ul_ccch; static dissector_handle_t lte_rrc_dl_ccch; static dissector_handle_t lte_rrc_bcch_bch; static dissector_handle_t lte_rrc_bcch_dl_sch; static dissector_handle_t lte_rrc_pcch; static dissector_handle_t lte_rrc_ul_ccch_nb; static dissector_handle_t lte_rrc_dl_ccch_nb; static dissector_handle_t lte_rrc_bcch_bch_nb; static dissector_handle_t lte_rrc_bcch_dl_sch_nb; static dissector_handle_t lte_rrc_pcch_nb; static int rlc_lte_tap; /* Decoding context */ static int hf_rlc_lte_context; static int hf_rlc_lte_context_mode; static int hf_rlc_lte_context_direction; static int hf_rlc_lte_context_priority; static int hf_rlc_lte_context_ueid; static int hf_rlc_lte_context_channel_type; static int hf_rlc_lte_context_channel_id; static int hf_rlc_lte_context_pdu_length; static int hf_rlc_lte_context_um_sn_length; static int hf_rlc_lte_context_am_sn_length; /* Transparent mode fields */ static int hf_rlc_lte_tm; static int hf_rlc_lte_tm_data; /* Unacknowledged mode fields */ static int hf_rlc_lte_um; static int hf_rlc_lte_um_header; static int hf_rlc_lte_um_fi; static int hf_rlc_lte_um_fixed_e; static int hf_rlc_lte_um_sn; static int hf_rlc_lte_um_fixed_reserved; static int hf_rlc_lte_um_data; static int hf_rlc_lte_extension_part; /* Extended header (common to UM and AM) */ static int hf_rlc_lte_extension_e; static int hf_rlc_lte_extension_li; static int hf_rlc_lte_extension_padding; /* Acknowledged mode fields */ static int hf_rlc_lte_am; static int hf_rlc_lte_am_header; static int hf_rlc_lte_am_data_control; static int hf_rlc_lte_am_rf; static int hf_rlc_lte_am_p; static int hf_rlc_lte_am_fi; static int hf_rlc_lte_am_fixed_e; static int hf_rlc_lte_am_fixed_sn; static int hf_rlc_lte_am_fixed_reserved; static int hf_rlc_lte_am_segment_lsf16; static int hf_rlc_lte_am_fixed_reserved2; static int hf_rlc_lte_am_fixed_sn16; static int hf_rlc_lte_am_segment_lsf; static int hf_rlc_lte_am_segment_so; static int hf_rlc_lte_am_segment_so16; static int hf_rlc_lte_am_data; /* Control fields */ static int hf_rlc_lte_am_cpt; static int hf_rlc_lte_am_ack_sn; static int hf_rlc_lte_am_e1; static int hf_rlc_lte_am_e2; static int hf_rlc_lte_am_nack_sn; static int hf_rlc_lte_am_nacks; static int hf_rlc_lte_am_so_start; static int hf_rlc_lte_am_so_end; static int hf_rlc_lte_predefined_pdu; static int hf_rlc_lte_header_only; /* Sequence Analysis */ static int hf_rlc_lte_sequence_analysis; static int hf_rlc_lte_sequence_analysis_ok; static int hf_rlc_lte_sequence_analysis_previous_frame; static int hf_rlc_lte_sequence_analysis_next_frame; static int hf_rlc_lte_sequence_analysis_expected_sn; static int hf_rlc_lte_sequence_analysis_framing_info_correct; static int hf_rlc_lte_sequence_analysis_mac_retx; static int hf_rlc_lte_sequence_analysis_retx; static int hf_rlc_lte_sequence_analysis_repeated; static int hf_rlc_lte_sequence_analysis_skipped; static int hf_rlc_lte_sequence_analysis_repeated_nack; static int hf_rlc_lte_sequence_analysis_repeated_nack_original_frame; static int hf_rlc_lte_sequence_analysis_ack_out_of_range; static int hf_rlc_lte_sequence_analysis_ack_out_of_range_opposite_frame; /* Reassembly */ static int hf_rlc_lte_reassembly_source; static int hf_rlc_lte_reassembly_source_number_of_segments; static int hf_rlc_lte_reassembly_source_total_length; static int hf_rlc_lte_reassembly_source_segment; static int hf_rlc_lte_reassembly_source_segment_sn; static int hf_rlc_lte_reassembly_source_segment_framenum; static int hf_rlc_lte_reassembly_source_segment_length; /* Subtrees. */ static int ett_rlc_lte; static int ett_rlc_lte_context; static int ett_rlc_lte_um_header; static int ett_rlc_lte_am_header; static int ett_rlc_lte_extension_part; static int ett_rlc_lte_sequence_analysis; static int ett_rlc_lte_reassembly_source; static int ett_rlc_lte_reassembly_source_segment; static expert_field ei_rlc_lte_context_mode; static expert_field ei_rlc_lte_am_nack_sn; static expert_field ei_rlc_lte_am_nack_sn_ahead_ack; static expert_field ei_rlc_lte_um_sn_repeated; static expert_field ei_rlc_lte_am_nack_sn_ack_same; static expert_field ei_rlc_lte_am_cpt; static expert_field ei_rlc_lte_am_data_no_data; static expert_field ei_rlc_lte_sequence_analysis_last_segment_complete; static expert_field ei_rlc_lte_sequence_analysis_mac_retx; static expert_field ei_rlc_lte_am_nack_sn_partial; static expert_field ei_rlc_lte_sequence_analysis_repeated_nack; static expert_field ei_rlc_lte_bytes_after_status_pdu_complete; static expert_field ei_rlc_lte_sequence_analysis_repeated; static expert_field ei_rlc_lte_wrong_sequence_number; static expert_field ei_rlc_lte_sequence_analysis_retx; static expert_field ei_rlc_lte_am_sn_missing; static expert_field ei_rlc_lte_um_sn; static expert_field ei_rlc_lte_header_only; static expert_field ei_rlc_lte_am_data_no_data_beyond_extensions; static expert_field ei_rlc_lte_um_sn_missing; static expert_field ei_rlc_lte_sequence_analysis_ack_out_of_range_opposite_frame; static expert_field ei_rlc_lte_sequence_analysis_last_segment_not_continued; static expert_field ei_rlc_lte_reserved_bits_not_zero; static expert_field ei_rlc_lte_no_per_frame_info; static expert_field ei_rlc_lte_unknown_udp_framing_tag; static expert_field ei_rlc_lte_missing_udp_framing_tag; /* Value-strings */ static const value_string direction_vals[] = { { DIRECTION_UPLINK, "Uplink"}, { DIRECTION_DOWNLINK, "Downlink"}, { 0, NULL } }; static const value_string rlc_mode_short_vals[] = { { RLC_TM_MODE, "TM"}, { RLC_UM_MODE, "UM"}, { RLC_AM_MODE, "AM"}, { RLC_PREDEF, "PREDEFINED"}, /* For data testing */ { 0, NULL } }; static const value_string rlc_mode_vals[] = { { RLC_TM_MODE, "Transparent Mode"}, { RLC_UM_MODE, "Unacknowledged Mode"}, { RLC_AM_MODE, "Acknowledged Mode"}, { 0, NULL } }; static const value_string rlc_channel_type_vals[] = { { CHANNEL_TYPE_CCCH, "CCCH"}, { CHANNEL_TYPE_BCCH_BCH, "BCCH_BCH"}, { CHANNEL_TYPE_PCCH, "PCCH"}, { CHANNEL_TYPE_SRB, "SRB"}, { CHANNEL_TYPE_DRB, "DRB"}, { CHANNEL_TYPE_BCCH_DL_SCH, "BCCH_DL_SCH"}, { CHANNEL_TYPE_MCCH, "MCCH"}, { CHANNEL_TYPE_MTCH, "MTCH"}, { 0, NULL } }; static const value_string framing_info_vals[] = { { 0, "First byte begins a RLC SDU and last byte ends a RLC SDU"}, { 1, "First byte begins a RLC SDU and last byte does not end a RLC SDU"}, { 2, "First byte does not begin a RLC SDU and last byte ends a RLC SDU"}, { 3, "First byte does not begin a RLC SDU and last byte does not end a RLC SDU"}, { 0, NULL } }; static const value_string fixed_extension_vals[] = { { 0, "Data field follows from the octet following the fixed part of the header"}, { 1, "A set of E field and LI field follows from the octet following the fixed part of the header"}, { 0, NULL } }; static const value_string extension_extension_vals[] = { { 0, "Data field follows from the octet following the LI field following this E field"}, { 1, "A set of E field and LI field follows from the bit following the LI field following this E field"}, { 0, NULL } }; static const value_string resegmentation_flag_vals[] = { { 0, "AMD PDU"}, { 1, "AMD PDU segment"}, { 0, NULL } }; static const value_string polling_bit_vals[] = { { 0, "Status report not requested"}, { 1, "Status report is requested"}, { 0, NULL } }; static const value_string lsf_vals[] = { { 0, "Last byte of the AMD PDU segment does not correspond to the last byte of an AMD PDU"}, { 1, "Last byte of the AMD PDU segment corresponds to the last byte of an AMD PDU"}, { 0, NULL } }; static const value_string control_pdu_type_vals[] = { { 0, "STATUS PDU"}, { 0, NULL } }; static const value_string am_e1_vals[] = { { 0, "A set of NACK_SN, E1 and E2 does not follow"}, { 1, "A set of NACK_SN, E1 and E2 follows"}, { 0, NULL } }; static const value_string am_e2_vals[] = { { 0, "A set of SOstart and SOend does not follow for this NACK_SN"}, { 1, "A set of SOstart and SOend follows for this NACK_SN"}, { 0, NULL } }; static const value_string header_only_vals[] = { { 0, "RLC PDU Headers and body present"}, { 1, "RLC PDU Headers only"}, { 0, NULL } }; /**********************************************************************************/ /* These are for keeping track of UM/AM extension headers, and the lengths found */ /* in them */ static uint8_t s_number_of_extensions; #define MAX_RLC_SDUS 192 static uint16_t s_lengths[MAX_RLC_SDUS]; /*********************************************************************/ /* UM/AM sequence analysis */ /* Types for RLC channel hash table */ /* This table is maintained during initial dissection of RLC */ /* frames, mapping from channel_hash_key -> sequence_analysis_report */ /* Channel key */ typedef struct { unsigned ueId : 16; unsigned channelType : 3; unsigned channelId : 5; unsigned direction : 1; } channel_hash_key; /******************************************************************/ /* State maintained for AM/UM reassembly */ typedef struct rlc_segment { uint32_t frameNum; uint16_t SN; uint8_t *data; uint16_t length; } rlc_segment; typedef struct rlc_channel_reassembly_info { uint16_t number_of_segments; #define RLC_MAX_SEGMENTS 100 rlc_segment segments[RLC_MAX_SEGMENTS]; } rlc_channel_reassembly_info; /*******************************************************************/ /* Conversation-type status for sequence analysis on channel */ typedef struct { uint8_t rlcMode; /* For UM, we always expect the SN to keep advancing, and these fields keep track of this. For AM, these correspond to new data */ uint16_t previousSequenceNumber; uint32_t previousFrameNum; bool previousSegmentIncomplete; /* Accumulate info about current segmented SDU */ struct rlc_channel_reassembly_info *reassembly_info; } channel_sequence_analysis_status; /* The sequence analysis channel hash table (channel_hash_key* -> channel_sequence_analysis_status*) */ static wmem_map_t *sequence_analysis_channel_hash; /* Types for sequence analysis frame report hash table */ /* This is a table from framenum -> state_report_in_frame */ /* This is necessary because the per-packet info is already being used */ /* for context information before the dissector is called */ /* Info to attach to frame when first read, recording what to show about sequence */ typedef enum { SN_OK, SN_Repeated, SN_MAC_Retx, SN_Retx, SN_Missing, ACK_Out_of_Window, SN_Error } sequence_analysis_state; typedef struct { bool sequenceExpectedCorrect; uint16_t sequenceExpected; uint32_t previousFrameNum; bool previousSegmentIncomplete; uint32_t nextFrameNum; uint16_t firstSN; uint16_t lastSN; /* AM/UM */ sequence_analysis_state state; } sequence_analysis_report; /* The sequence analysis frame report hash table instance itself, lookup when visited */ /* rlc_result_hash_key* -> sequence_analysis_report* */ static wmem_map_t *sequence_analysis_report_hash; static void *get_report_hash_key(uint16_t SN, uint32_t frameNumber, rlc_lte_info *p_rlc_lte_info, bool do_persist); /* The reassembly result hash table */ static wmem_map_t *reassembly_report_hash; /* Create a new struct for reassembly */ static void reassembly_reset(channel_sequence_analysis_status *status) { status->reassembly_info = wmem_new0(wmem_file_scope(), rlc_channel_reassembly_info); } /* Hide previous one */ static void reassembly_destroy(channel_sequence_analysis_status *status) { /* Just "leak" it. There seems to be no way to free this memory... */ status->reassembly_info = NULL; } /* Add a new segment to the accumulating segmented SDU */ static void reassembly_add_segment(channel_sequence_analysis_status *status, uint16_t SN, uint32_t frame, tvbuff_t *tvb, int offset, int length) { int segment_number = status->reassembly_info->number_of_segments; uint8_t *segment_data; /* Give up if reach segment limit */ if (segment_number >= (RLC_MAX_SEGMENTS-1)) { reassembly_destroy(status); return; } segment_data = (uint8_t *)tvb_memdup(wmem_file_scope(),tvb, offset, length); /* Add new segment */ status->reassembly_info->segments[segment_number].frameNum = frame; status->reassembly_info->segments[segment_number].SN = SN; status->reassembly_info->segments[segment_number].data = segment_data; status->reassembly_info->segments[segment_number].length = length; status->reassembly_info->number_of_segments++; } /* Record the current & complete segmented SDU by mapping from this frame number to struct with segment info. */ static void reassembly_record(channel_sequence_analysis_status *status, packet_info *pinfo, uint16_t SN, rlc_lte_info *p_rlc_lte_info) { /* Just store existing info in hash table */ wmem_map_insert(reassembly_report_hash, get_report_hash_key(SN, pinfo->num, p_rlc_lte_info, true), status->reassembly_info); } /* Create and return a tvb based upon contents of reassembly info */ static tvbuff_t* reassembly_get_reassembled_tvb(rlc_channel_reassembly_info *reassembly_info, tvbuff_t *parent_tvb, packet_info *pinfo) { int n; unsigned combined_length = 0; uint8_t *combined_data; unsigned combined_offset = 0; tvbuff_t *reassembled_tvb; /* Allocate buffer big enough to hold re-assembled data */ for (n=0; n < reassembly_info->number_of_segments; n++) { combined_length += reassembly_info->segments[n].length; } combined_data = (uint8_t *)wmem_alloc(pinfo->pool, combined_length); /* Copy data into contiguous buffer */ for (n=0; n < reassembly_info->number_of_segments; n++) { uint8_t *data = reassembly_info->segments[n].data; int length = reassembly_info->segments[n].length; memcpy(combined_data+combined_offset, data, length); combined_offset += length; } /* Create and return tvb with this data */ reassembled_tvb = tvb_new_child_real_data(parent_tvb, combined_data, combined_offset, combined_offset); add_new_data_source(pinfo, reassembled_tvb, "Reassembled SDU"); return reassembled_tvb; } /* Show where the segments came from for a reassembled SDU */ static void reassembly_show_source(rlc_channel_reassembly_info *reassembly_info, proto_tree *tree, tvbuff_t *tvb, int offset) { int n; proto_item *source_ti, *ti; proto_tree *source_tree; proto_item *segment_ti; proto_tree *segment_tree; unsigned total_length=0; /* Create root of source info */ source_ti = proto_tree_add_item(tree, hf_rlc_lte_reassembly_source, tvb, 0, 0, ENC_ASCII); source_tree = proto_item_add_subtree(source_ti, ett_rlc_lte_reassembly_source); proto_item_set_generated(source_ti); for (n=0; n < reassembly_info->number_of_segments; n++) { total_length += reassembly_info->segments[n].length; } proto_item_append_text(source_ti, " %u segments, %u bytes", reassembly_info->number_of_segments, total_length); /* Number of segments */ ti = proto_tree_add_uint(source_tree, hf_rlc_lte_reassembly_source_number_of_segments, tvb, 0, 0, reassembly_info->number_of_segments); proto_item_set_generated(ti); /* Total length */ ti = proto_tree_add_uint(source_tree, hf_rlc_lte_reassembly_source_total_length, tvb, 0, 0, total_length); proto_item_set_generated(ti); /* Now add info about each segment in turn */ for (n=0; n < reassembly_info->number_of_segments; n++) { /* Add next segment as a subtree */ rlc_segment *segment = &(reassembly_info->segments[n]); proto_item_append_text(source_ti, " (SN=%u frame=%u len=%u)", segment->SN, segment->frameNum, segment->length); /* N.B. assume last segment from passed-in tvb! */ segment_ti = proto_tree_add_item(source_tree, hf_rlc_lte_reassembly_source_segment, tvb, (n == reassembly_info->number_of_segments-1) ? offset : 0, (n == reassembly_info->number_of_segments-1) ? segment->length : 0, ENC_NA); segment_tree = proto_item_add_subtree(segment_ti, ett_rlc_lte_reassembly_source_segment); proto_item_append_text(segment_ti, " (SN=%u frame=%u length=%u)", segment->SN, segment->frameNum, segment->length); proto_item_set_generated(segment_ti); /* Add details to segment tree */ ti = proto_tree_add_uint(segment_tree, hf_rlc_lte_reassembly_source_segment_sn, tvb, 0, 0, segment->SN); proto_item_set_generated(ti); ti = proto_tree_add_uint(segment_tree, hf_rlc_lte_reassembly_source_segment_framenum, tvb, 0, 0, segment->frameNum); proto_item_set_generated(ti); ti = proto_tree_add_uint(segment_tree, hf_rlc_lte_reassembly_source_segment_length, tvb, 0, 0, segment->length); proto_item_set_generated(ti); } } /******************************************************************/ /* Conversation-type status for repeated NACK checking on channel */ typedef struct { uint16_t noOfNACKs; uint16_t NACKs[MAX_NACKs]; uint32_t frameNum; } channel_repeated_nack_status; static wmem_map_t *repeated_nack_channel_hash; typedef struct { uint16_t noOfNACKsRepeated; uint16_t repeatedNACKs[MAX_NACKs]; uint32_t previousFrameNum; } channel_repeated_nack_report; static wmem_map_t *repeated_nack_report_hash; /********************************************************/ /* Forward declarations & functions */ static void dissect_rlc_lte_common(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, bool is_udp_framing); /* Write the given formatted text to: - the info column - the top-level RLC PDU item - another subtree item (if supplied) */ static void write_pdu_label_and_info(proto_item *pdu_ti, proto_item *sub_ti, packet_info *pinfo, const char *format, ...) G_GNUC_PRINTF(4, 5); static void write_pdu_label_and_info(proto_item *pdu_ti, proto_item *sub_ti, packet_info *pinfo, const char *format, ...) { #define MAX_INFO_BUFFER 256 static char info_buffer[MAX_INFO_BUFFER]; va_list ap; va_start(ap, format); vsnprintf(info_buffer, MAX_INFO_BUFFER, format, ap); va_end(ap); /* Add to indicated places */ col_append_str(pinfo->cinfo, COL_INFO, info_buffer); proto_item_append_text(pdu_ti, "%s", info_buffer); if (sub_ti != NULL) { proto_item_append_text(sub_ti, "%s", info_buffer); } } /* Version of function above, where no vsnprintf() call needed - the info column - the top-level RLC PDU item - another subtree item (if supplied) */ static void write_pdu_label_and_info_literal(proto_item *pdu_ti, proto_item *sub_ti, packet_info *pinfo, const char *info_buffer) { /* Add to indicated places */ col_append_str(pinfo->cinfo, COL_INFO, info_buffer); proto_item_append_text(pdu_ti, "%s", info_buffer); if (sub_ti != NULL) { proto_item_append_text(sub_ti, "%s", info_buffer); } } /* Dissect extension headers (common to both UM and AM) */ static int dissect_rlc_lte_extension_header(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree, int offset, rlc_lte_info *p_rlc_lte_info) { uint8_t isOdd; uint64_t extension = 1; uint64_t length; /* Reset this count */ s_number_of_extensions = 0; while (extension && (s_number_of_extensions < MAX_RLC_SDUS)) { proto_tree *extension_part_tree; proto_item *extension_part_ti; /* Extension part subtree */ extension_part_ti = proto_tree_add_string_format(tree, hf_rlc_lte_extension_part, tvb, offset, 2, "", "Extension Part"); extension_part_tree = proto_item_add_subtree(extension_part_ti, ett_rlc_lte_extension_part); if (p_rlc_lte_info->extendedLiField == false) { isOdd = (s_number_of_extensions % 2); /* Read next extension */ proto_tree_add_bits_ret_val(extension_part_tree, hf_rlc_lte_extension_e, tvb, (offset*8) + ((isOdd) ? 4 : 0), 1, &extension, ENC_BIG_ENDIAN); /* Read length field */ proto_tree_add_bits_ret_val(extension_part_tree, hf_rlc_lte_extension_li, tvb, (offset*8) + ((isOdd) ? 5 : 1), 11, &length, ENC_BIG_ENDIAN); /* Move on to byte of next extension */ if (isOdd) { offset += 2; } else { offset++; } } else { /* Read next extension */ proto_tree_add_bits_ret_val(extension_part_tree, hf_rlc_lte_extension_e, tvb, (offset*8), 1, &extension, ENC_BIG_ENDIAN); /* Read length field */ proto_tree_add_bits_ret_val(extension_part_tree, hf_rlc_lte_extension_li, tvb, (offset*8) + 1, 15, &length, ENC_BIG_ENDIAN); /* Move on to byte of next extension */ offset += 2; } proto_item_append_text(extension_part_tree, " (length=%u)", (uint16_t)length); s_lengths[s_number_of_extensions++] = (uint16_t)length; } /* May need to skip padding after last extension part */ isOdd = (s_number_of_extensions % 2); if (isOdd && (p_rlc_lte_info->extendedLiField == false)) { proto_tree_add_item(tree, hf_rlc_lte_extension_padding, tvb, offset++, 1, ENC_BIG_ENDIAN); } return offset; } /* Show in the info column how many bytes are in the UM/AM PDU, and indicate whether or not the beginning and end are included in this packet */ static void show_PDU_in_info(packet_info *pinfo, proto_item *top_ti, int32_t length, bool first_includes_start, bool last_includes_end) { /* Reflect this PDU in the info column */ if (length > 0) { write_pdu_label_and_info(top_ti, NULL, pinfo, " %s%u-byte%s%s", (first_includes_start) ? "[" : "..", length, (length > 1) ? "s" : "", (last_includes_end) ? "]" : ".."); } else { write_pdu_label_and_info(top_ti, NULL, pinfo, " %sunknown-bytes%s", (first_includes_start) ? "[" : "..", (last_includes_end) ? "]" : ".."); } } /* Show an SDU. If configured, pass to PDCP/RRC/IP dissector */ static void show_PDU_in_tree(packet_info *pinfo, proto_tree *tree, tvbuff_t *tvb, int offset, int length, rlc_lte_info *rlc_info, bool whole_pdu, rlc_channel_reassembly_info *reassembly_info, sequence_analysis_state state) { wmem_tree_key_t key[3]; uint32_t id; rlc_ue_parameters *params; /* Add raw data (according to mode) */ proto_item *data_ti = proto_tree_add_item(tree, (rlc_info->rlcMode == RLC_AM_MODE) ? hf_rlc_lte_am_data : hf_rlc_lte_um_data, tvb, offset, length, ENC_NA); if (whole_pdu || (reassembly_info != NULL)) { if (((global_rlc_lte_call_pdcp_for_srb) && (rlc_info->channelType == CHANNEL_TYPE_SRB)) || ((global_rlc_lte_call_pdcp_for_drb != PDCP_drb_off) && (rlc_info->channelType == CHANNEL_TYPE_DRB))) { /* Send whole PDU to PDCP */ /* TODO: made static to avoid compiler warning... */ static tvbuff_t *pdcp_tvb = NULL; struct pdcp_lte_info *p_pdcp_lte_info; /* Get tvb for passing to LTE PDCP dissector */ if (reassembly_info == NULL) { pdcp_tvb = tvb_new_subset_length(tvb, offset, length); } else { /* Get combined tvb. */ pdcp_tvb = reassembly_get_reassembled_tvb(reassembly_info, tvb, pinfo); reassembly_show_source(reassembly_info, tree, tvb, offset); } /* Reuse or allocate struct */ p_pdcp_lte_info = (pdcp_lte_info *)p_get_proto_data(wmem_file_scope(), pinfo, proto_pdcp_lte, 0); if (p_pdcp_lte_info == NULL) { p_pdcp_lte_info = wmem_new0(wmem_file_scope(), pdcp_lte_info); /* Store info in packet */ p_add_proto_data(wmem_file_scope(), pinfo, proto_pdcp_lte, 0, p_pdcp_lte_info); } p_pdcp_lte_info->ueid = rlc_info->ueid; if (rlc_info->nbMode == rlc_nb_mode) { p_pdcp_lte_info->channelType = Channel_DCCH_NB; } else { p_pdcp_lte_info->channelType = Channel_DCCH; } p_pdcp_lte_info->channelId = rlc_info->channelId; p_pdcp_lte_info->direction = rlc_info->direction; p_pdcp_lte_info->is_retx = (state != SN_OK); /* Set plane and sequence number length */ p_pdcp_lte_info->no_header_pdu = false; if (rlc_info->channelType == CHANNEL_TYPE_SRB) { p_pdcp_lte_info->plane = SIGNALING_PLANE; if ((rlc_info->nbMode == rlc_nb_mode) && (rlc_info->channelId == 3)) { p_pdcp_lte_info->no_header_pdu = true; p_pdcp_lte_info->seqnum_length = 0; } else { p_pdcp_lte_info->seqnum_length = 5; } } else { p_pdcp_lte_info->plane = USER_PLANE; switch (global_rlc_lte_call_pdcp_for_drb) { case PDCP_drb_SN_7: p_pdcp_lte_info->seqnum_length = 7; break; case PDCP_drb_SN_12: p_pdcp_lte_info->seqnum_length = 12; break; case PDCP_drb_SN_15: p_pdcp_lte_info->seqnum_length = 15; break; case PDCP_drb_SN_18: p_pdcp_lte_info->seqnum_length = 18; break; case PDCP_drb_SN_signalled: /* Use whatever was signalled (e.g. in RRC) */ id = (rlc_info->channelId << 16) | rlc_info->ueid; key[0].length = 1; key[0].key = &id; key[1].length = 1; key[1].key = &pinfo->num; key[2].length = 0; key[2].key = NULL; params = (rlc_ue_parameters *)wmem_tree_lookup32_array_le(ue_parameters_tree, key); if (params && (params->id != id)) { params = NULL; } if (params) { p_pdcp_lte_info->seqnum_length = params->pdcp_sn_bits; } else if (rlc_info->nbMode == rlc_nb_mode) { p_pdcp_lte_info->seqnum_length = 7; } else { p_pdcp_lte_info->seqnum_length = 12; } break; default: DISSECTOR_ASSERT(false); break; } } TRY { call_dissector_only(pdcp_lte_handle, pdcp_tvb, pinfo, tree, NULL); } CATCH_ALL { } ENDTRY proto_item_set_hidden(data_ti); } else if (global_rlc_lte_call_rrc_for_mcch && (rlc_info->channelType == CHANNEL_TYPE_MCCH)) { /* Send whole PDU to RRC */ static tvbuff_t *rrc_tvb = NULL; /* Get tvb for passing to LTE RRC dissector */ if (reassembly_info == NULL) { rrc_tvb = tvb_new_subset_length(tvb, offset, length); } else { /* Get combined tvb. */ rrc_tvb = reassembly_get_reassembled_tvb(reassembly_info, tvb, pinfo); reassembly_show_source(reassembly_info, tree, tvb, offset); } TRY { call_dissector_only(lte_rrc_mcch, rrc_tvb, pinfo, tree, NULL); } CATCH_ALL { } ENDTRY proto_item_set_hidden(data_ti); } else if (global_rlc_lte_call_ip_for_mtch && (rlc_info->channelType == CHANNEL_TYPE_MTCH)) { /* Send whole PDU to IP */ static tvbuff_t *ip_tvb = NULL; /* Get tvb for passing to IP dissector */ if (reassembly_info == NULL) { ip_tvb = tvb_new_subset_length(tvb, offset, length); } else { /* Get combined tvb. */ ip_tvb = reassembly_get_reassembled_tvb(reassembly_info, tvb, pinfo); reassembly_show_source(reassembly_info, tree, tvb, offset); } TRY { call_dissector_only(ip_handle, ip_tvb, pinfo, tree, NULL); } CATCH_ALL { } ENDTRY proto_item_set_hidden(data_ti); } } } /* Hash table functions for RLC channels */ /* Equal keys */ static int rlc_channel_equal(const void *v, const void *v2) { const channel_hash_key* val1 = (const channel_hash_key *)v; const channel_hash_key* val2 = (const channel_hash_key *)v2; /* All fields must match */ /* N.B. Currently fits into one word, so could return (*v == *v2) if we're sure they're initialised to 0... */ return ((val1->ueId == val2->ueId) && (val1->channelType == val2->channelType) && (val1->channelId == val2->channelId) && (val1->direction == val2->direction)); } /* Compute a hash value for a given key. */ static unsigned rlc_channel_hash_func(const void *v) { const channel_hash_key* val1 = (const channel_hash_key *)v; /* TODO: check/reduce multipliers */ return ((val1->ueId * 1024) + (val1->channelType*64) + (val1->channelId*2) + val1->direction); } /*************************************************************************/ /* Result hash */ typedef struct { uint32_t frameNumber; uint32_t SN : 10; uint32_t channelType : 2; uint32_t channelId: 5; uint32_t direction: 1; } rlc_result_hash_key; /* Compare 2 rlc_result_hash_key structs */ static int rlc_result_hash_equal(const void *v, const void *v2) { const rlc_result_hash_key *val1 = (const rlc_result_hash_key *)v; const rlc_result_hash_key *val2 = (const rlc_result_hash_key *)v2; /* All fields (and any padding...) must match */ return (memcmp(val1, val2, sizeof(rlc_result_hash_key)) == 0); } /* Compute a hash value for a given key. */ static unsigned rlc_result_hash_func(const void *v) { const rlc_result_hash_key* val1 = (const rlc_result_hash_key *)v; /* Got rid of multipliers - no evidence that they reduced collisions */ return val1->frameNumber + val1->SN + val1->channelType + val1->channelId + val1->direction; } /* Convenience function to get a pointer for the hash_func to work with */ static void *get_report_hash_key(uint16_t SN, uint32_t frameNumber, rlc_lte_info *p_rlc_lte_info, bool do_persist) { static rlc_result_hash_key key; rlc_result_hash_key *p_key; /* Only allocate a struct when will be adding entry */ if (do_persist) { p_key = wmem_new0(wmem_file_scope(), rlc_result_hash_key); } else { memset(&key, 0, sizeof(rlc_result_hash_key)); p_key = &key; } /* Fill in details, and return pointer */ p_key->frameNumber = frameNumber; p_key->SN = SN; p_key->channelType = p_rlc_lte_info->channelType; p_key->channelId = p_rlc_lte_info->channelId; p_key->direction = p_rlc_lte_info->direction; return p_key; } static void checkFIconsistency(sequence_analysis_report *p, rlc_lte_info *p_rlc_lte_info, bool newSegmentStarted, proto_tree *seqnum_tree, packet_info *pinfo, tvbuff_t *tvb) { proto_item *ti; if (p->previousSegmentIncomplete) { /* Previous segment was incomplete, so this PDU should continue it */ if (newSegmentStarted) { ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_framing_info_correct, tvb, 0, 0, false); expert_add_info_format(pinfo, ti, &ei_rlc_lte_sequence_analysis_last_segment_not_continued, "Last segment of previous PDU was not continued for UE %u (%s-%u)", p_rlc_lte_info->ueid, val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"), p_rlc_lte_info->channelId); } else { ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_framing_info_correct, tvb, 0, 0, true); proto_item_set_hidden(ti); } } else { /* Previous segment was complete, so this PDU should start a new one */ if (!newSegmentStarted) { ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_framing_info_correct, tvb, 0, 0, false); expert_add_info_format(pinfo, ti, &ei_rlc_lte_sequence_analysis_last_segment_complete, "Last segment of previous PDU was complete, but new segment was not started on UE %u (%s-%u)", p_rlc_lte_info->ueid, val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"), p_rlc_lte_info->channelId); } else { ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_framing_info_correct, tvb, 0, 0, true); proto_item_set_hidden(ti); } } proto_item_set_generated(ti); } /* Add to the tree values associated with sequence analysis for this frame */ static void addChannelSequenceInfo(sequence_analysis_report *p, bool isControlFrame, rlc_lte_info *p_rlc_lte_info, uint16_t sequenceNumber, bool newSegmentStarted, rlc_3gpp_tap_info *tap_info, packet_info *pinfo, proto_tree *tree, tvbuff_t *tvb) { proto_tree *seqnum_tree; proto_item *seqnum_ti; proto_item *ti; /* Create subtree */ seqnum_ti = proto_tree_add_string_format(tree, hf_rlc_lte_sequence_analysis, tvb, 0, 0, "", "Sequence Analysis"); seqnum_tree = proto_item_add_subtree(seqnum_ti, ett_rlc_lte_sequence_analysis); proto_item_set_generated(seqnum_ti); if (p->previousFrameNum != 0) { ti = proto_tree_add_uint(seqnum_tree, hf_rlc_lte_sequence_analysis_previous_frame, tvb, 0, 0, p->previousFrameNum); proto_item_set_generated(ti); } switch (p_rlc_lte_info->rlcMode) { case RLC_AM_MODE: /********************************************/ /* AM */ /********************************************/ switch (p->state) { case SN_OK: if (isControlFrame) { return; } ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok, tvb, 0, 0, true); proto_item_set_generated(ti); proto_item_append_text(seqnum_ti, " - OK"); /* Link to next SN in channel (if known) */ if (p->nextFrameNum != 0) { proto_tree_add_uint(seqnum_tree, hf_rlc_lte_sequence_analysis_next_frame, tvb, 0, 0, p->nextFrameNum); } /* Correct sequence number, so check frame indication bits consistent */ /* Deactivated for now as it gets confused by resegmentation */ /* checkFIconsistency(p, p_rlc_lte_info, newSegmentStarted, seqnum_tree, pinfo, tvb); */ break; case SN_MAC_Retx: if (isControlFrame) { return; } ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok, tvb, 0, 0, false); proto_item_set_generated(ti); ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_mac_retx, tvb, 0, 0, true); proto_item_set_generated(ti); expert_add_info_format(pinfo, ti, &ei_rlc_lte_sequence_analysis_mac_retx, "AM Frame retransmitted for %s on UE %u - due to MAC retx! (%s-%u)", val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"), p_rlc_lte_info->ueid, val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"), p_rlc_lte_info->channelId); proto_item_append_text(seqnum_ti, " - MAC retx of SN %u", p->firstSN); break; case SN_Retx: if (isControlFrame) { return; } ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok, tvb, 0, 0, false); proto_item_set_generated(ti); ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_retx, tvb, 0, 0, true); proto_item_set_generated(ti); expert_add_info_format(pinfo, ti, &ei_rlc_lte_sequence_analysis_retx, "AM Frame retransmitted for %s on UE %u - most likely in response to NACK (%s-%u)", val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"), p_rlc_lte_info->ueid, val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"), p_rlc_lte_info->channelId); proto_item_append_text(seqnum_ti, " - SN %u retransmitted", p->firstSN); break; case SN_Repeated: if (isControlFrame) { return; } ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok, tvb, 0, 0, false); proto_item_set_generated(ti); ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_repeated, tvb, 0, 0, true); proto_item_set_generated(ti); expert_add_info_format(pinfo, ti, &ei_rlc_lte_sequence_analysis_repeated, "AM SN Repeated for %s for UE %u - probably because didn't receive Status PDU? (%s-%u)", val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"), p_rlc_lte_info->ueid, val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"), p_rlc_lte_info->channelId); proto_item_append_text(seqnum_ti, "- SN %u Repeated", p->firstSN); break; case SN_Missing: if (isControlFrame) { return; } ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok, tvb, 0, 0, false); proto_item_set_generated(ti); ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_skipped, tvb, 0, 0, true); proto_item_set_generated(ti); if (p->lastSN != p->firstSN) { expert_add_info_format(pinfo, ti, &ei_rlc_lte_am_sn_missing, "AM SNs (%u to %u) missing for %s on UE %u (%s-%u)", p->firstSN, p->lastSN, val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"), p_rlc_lte_info->ueid, val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"), p_rlc_lte_info->channelId); proto_item_append_text(seqnum_ti, " - SNs missing (%u to %u)", p->firstSN, p->lastSN); if (p_rlc_lte_info->sequenceNumberLength == AM_SN_LENGTH_16_BITS) { tap_info->missingSNs = ((65536 + (uint32_t)p->lastSN - (uint32_t)p->firstSN) % 65536) + 1; } else { tap_info->missingSNs = ((1024 + p->lastSN - p->firstSN) % 1024) + 1; } } else { expert_add_info_format(pinfo, ti, &ei_rlc_lte_am_sn_missing, "AM SN (%u) missing for %s on UE %u (%s-%u)", p->firstSN, val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"), p_rlc_lte_info->ueid, val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"), p_rlc_lte_info->channelId); proto_item_append_text(seqnum_ti, " - SN missing (%u)", p->firstSN); tap_info->missingSNs = 1; } break; case ACK_Out_of_Window: if (!isControlFrame) { return; } /* Not OK */ ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok, tvb, 0, 0, false); /* Out of range */ proto_item_set_generated(ti); ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ack_out_of_range, tvb, 0, 0, true); proto_item_set_generated(ti); /* Link back to last seen SN in other direction */ ti = proto_tree_add_uint(seqnum_tree, hf_rlc_lte_sequence_analysis_ack_out_of_range_opposite_frame, tvb, 0, 0, p->previousFrameNum); proto_item_set_generated(ti); /* Expert error */ expert_add_info_format(pinfo, ti, &ei_rlc_lte_sequence_analysis_ack_out_of_range_opposite_frame, "AM ACK for SN %u - but last received SN in other direction is %u for UE %u (%s-%u)", p->firstSN, p->sequenceExpected, p_rlc_lte_info->ueid, val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"), p_rlc_lte_info->channelId); proto_item_append_text(seqnum_ti, "- ACK SN %u Outside Rx Window - last received SN is %u", p->firstSN, p->sequenceExpected); break; default: return; } break; case RLC_UM_MODE: /********************************************/ /* UM */ /********************************************/ /* Expected sequence number */ ti = proto_tree_add_uint(seqnum_tree, hf_rlc_lte_sequence_analysis_expected_sn, tvb, 0, 0, p->sequenceExpected); proto_item_set_generated(ti); if (p->sequenceExpectedCorrect) { proto_item_set_hidden(ti); } if (!p->sequenceExpectedCorrect) { /* Work out SN wrap (in case needed below) */ uint16_t snLimit; if (p_rlc_lte_info->sequenceNumberLength == UM_SN_LENGTH_5_BITS) { snLimit = 32; } else { snLimit = 1024; } switch (p->state) { case SN_Missing: ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok, tvb, 0, 0, false); proto_item_set_generated(ti); ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_skipped, tvb, 0, 0, true); proto_item_set_generated(ti); if (p->lastSN != p->firstSN) { expert_add_info_format(pinfo, ti, &ei_rlc_lte_um_sn_missing, "UM SNs (%u to %u) missing for %s on UE %u (%s-%u)", p->firstSN, p->lastSN, val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"), p_rlc_lte_info->ueid, val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"), p_rlc_lte_info->channelId); proto_item_append_text(seqnum_ti, " - SNs missing (%u to %u)", p->firstSN, p->lastSN); tap_info->missingSNs = ((snLimit + p->lastSN - p->firstSN) % snLimit) + 1; } else { expert_add_info_format(pinfo, ti, &ei_rlc_lte_um_sn_missing, "UM SN (%u) missing for %s on UE %u (%s-%u)", p->firstSN, val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"), p_rlc_lte_info->ueid, val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"), p_rlc_lte_info->channelId); proto_item_append_text(seqnum_ti, " - SN missing (%u)", p->firstSN); tap_info->missingSNs = 1; } break; case SN_Repeated: ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok, tvb, 0, 0, false); proto_item_set_generated(ti); ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_repeated, tvb, 0, 0, true); proto_item_set_generated(ti); expert_add_info_format(pinfo, ti, &ei_rlc_lte_um_sn_repeated, "UM SN (%u) repeated for %s for UE %u (%s-%u)", p->firstSN, val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"), p_rlc_lte_info->ueid, val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"), p_rlc_lte_info->channelId); proto_item_append_text(seqnum_ti, "- SN %u Repeated", p->firstSN); break; case SN_MAC_Retx: ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok, tvb, 0, 0, false); proto_item_set_generated(ti); ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_mac_retx, tvb, 0, 0, true); proto_item_set_generated(ti); expert_add_info_format(pinfo, ti, &ei_rlc_lte_sequence_analysis_mac_retx, "UM Frame retransmitted for %s on UE %u - due to MAC retx! (%s-%u)", val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"), p_rlc_lte_info->ueid, val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"), p_rlc_lte_info->channelId); break; default: /* Incorrect sequence number */ expert_add_info_format(pinfo, ti, &ei_rlc_lte_wrong_sequence_number, "Wrong Sequence Number for %s on UE %u - got %u, expected %u (%s-%u)", val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"), p_rlc_lte_info->ueid, sequenceNumber, p->sequenceExpected, val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"), p_rlc_lte_info->channelId); break; } } else { /* Correct sequence number, so check frame indication bits consistent */ checkFIconsistency(p, p_rlc_lte_info, newSegmentStarted, seqnum_tree, pinfo, tvb); /* Set OK here! */ ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok, tvb, 0, 0, true); proto_item_set_generated(ti); proto_item_append_text(seqnum_ti, " - OK"); } /* Next channel frame */ if (p->nextFrameNum != 0) { ti = proto_tree_add_uint(seqnum_tree, hf_rlc_lte_sequence_analysis_next_frame, tvb, 0, 0, p->nextFrameNum); proto_item_set_generated(ti); } } } /* Update the channel status and set report for this frame */ static sequence_analysis_state checkChannelSequenceInfo(packet_info *pinfo, tvbuff_t *tvb, rlc_lte_info *p_rlc_lte_info, bool isControlFrame, uint8_t number_of_segments, uint16_t firstSegmentOffset, uint16_t firstSegmentLength, uint16_t lastSegmentOffset, uint16_t sequenceNumber, bool first_includes_start, bool last_includes_end, bool is_resegmented _U_, rlc_3gpp_tap_info *tap_info, proto_tree *tree) { channel_hash_key channel_key; channel_hash_key *p_channel_key; channel_sequence_analysis_status *p_channel_status; sequence_analysis_report *p_report_in_frame = NULL; bool createdChannel = false; uint16_t expectedSequenceNumber = 0; uint32_t snLimit = 0; /* If find stat_report_in_frame already, use that and get out */ if (pinfo->fd->visited) { p_report_in_frame = (sequence_analysis_report*)wmem_map_lookup(sequence_analysis_report_hash, get_report_hash_key(sequenceNumber, pinfo->num, p_rlc_lte_info, false)); if (p_report_in_frame != NULL) { addChannelSequenceInfo(p_report_in_frame, isControlFrame, p_rlc_lte_info, sequenceNumber, first_includes_start, tap_info, pinfo, tree, tvb); return p_report_in_frame->state; } /* Don't just give up here... */ } /**************************************************/ /* Create or find an entry for this channel state */ channel_key.ueId = p_rlc_lte_info->ueid; channel_key.channelType = p_rlc_lte_info->channelType; channel_key.channelId = p_rlc_lte_info->channelId; channel_key.direction = p_rlc_lte_info->direction; /* Do the table lookup */ p_channel_status = (channel_sequence_analysis_status*)wmem_map_lookup(sequence_analysis_channel_hash, &channel_key); /* Create table entry if necessary */ if (p_channel_status == NULL) { createdChannel = true; /* Allocate a new value and duplicate key contents */ p_channel_status = wmem_new0(wmem_file_scope(), channel_sequence_analysis_status); p_channel_key = (channel_hash_key *)wmem_memdup(wmem_file_scope(), &channel_key, sizeof(channel_hash_key)); /* Set mode */ p_channel_status->rlcMode = p_rlc_lte_info->rlcMode; /* Add entry */ wmem_map_insert(sequence_analysis_channel_hash, p_channel_key, p_channel_status); } /* Create space for frame state_report */ p_report_in_frame = wmem_new0(wmem_file_scope(), sequence_analysis_report); /* Deal with according to channel mode */ switch (p_channel_status->rlcMode) { case RLC_UM_MODE: if (p_rlc_lte_info->sequenceNumberLength == UM_SN_LENGTH_5_BITS) { snLimit = 32; } else { snLimit = 1024; } /* Work out expected sequence number */ if (!createdChannel) { expectedSequenceNumber = (p_channel_status->previousSequenceNumber + 1) % snLimit; } else { /* Whatever we got is fine.. */ expectedSequenceNumber = sequenceNumber; } if ((sequenceNumber == 0) && ((p_rlc_lte_info->channelType == CHANNEL_TYPE_MCCH) || (p_rlc_lte_info->channelType == CHANNEL_TYPE_MTCH))) { /* With eMBMS, SN restarts to 0 at each MCH Scheduling Period so we cannot deduce easily whether there was a PDU loss or not without analysing the Frame Indicator; assume no loss when seeing 0 */ expectedSequenceNumber = 0; } /* Set report for this frame */ /* For UM, sequence number is always expectedSequence number */ p_report_in_frame->sequenceExpectedCorrect = (sequenceNumber == expectedSequenceNumber); /* For wrong sequence number... */ if (!p_report_in_frame->sequenceExpectedCorrect) { /* Don't get confused by MAC (HARQ) retx */ if (is_mac_lte_frame_retx(pinfo, p_rlc_lte_info->direction)) { p_report_in_frame->state = SN_MAC_Retx; p_report_in_frame->firstSN = sequenceNumber; /* No channel state to update */ break; } /* Frames are not missing if we get an earlier sequence number again */ /* TODO: taking time into account would give better idea of whether missing or repeated... */ else if ((p_rlc_lte_info->channelType == CHANNEL_TYPE_MCCH) || (p_rlc_lte_info->channelType == CHANNEL_TYPE_MTCH) || (((snLimit + sequenceNumber - expectedSequenceNumber) % snLimit) < 10)) { reassembly_destroy(p_channel_status); p_report_in_frame->state = SN_Missing; tap_info->missingSNs = (snLimit + sequenceNumber - expectedSequenceNumber) % snLimit; p_report_in_frame->firstSN = expectedSequenceNumber; p_report_in_frame->lastSN = (snLimit + sequenceNumber - 1) % snLimit; p_report_in_frame->sequenceExpected = expectedSequenceNumber; p_report_in_frame->previousFrameNum = p_channel_status->previousFrameNum; p_report_in_frame->previousSegmentIncomplete = p_channel_status->previousSegmentIncomplete; /* Update channel status to remember *this* frame */ p_channel_status->previousFrameNum = pinfo->num; p_channel_status->previousSequenceNumber = sequenceNumber; p_channel_status->previousSegmentIncomplete = !last_includes_end; } else { /* An SN has been repeated */ p_report_in_frame->state = SN_Repeated; p_report_in_frame->firstSN = sequenceNumber; p_report_in_frame->sequenceExpected = expectedSequenceNumber; p_report_in_frame->previousFrameNum = p_channel_status->previousFrameNum; } } else { /* SN was OK */ p_report_in_frame->sequenceExpected = expectedSequenceNumber; p_report_in_frame->previousFrameNum = p_channel_status->previousFrameNum; p_report_in_frame->previousSegmentIncomplete = p_channel_status->previousSegmentIncomplete; /* Update channel status to remember *this* frame */ p_channel_status->previousFrameNum = pinfo->num; p_channel_status->previousSequenceNumber = sequenceNumber; p_channel_status->previousSegmentIncomplete = !last_includes_end; if (p_channel_status->reassembly_info) { /* Add next segment to reassembly info */ reassembly_add_segment(p_channel_status, sequenceNumber, pinfo->num, tvb, firstSegmentOffset, firstSegmentLength); /* The end of existing reassembly? */ if (!first_includes_start && ((number_of_segments > 1) || last_includes_end)) { reassembly_record(p_channel_status, pinfo, sequenceNumber, p_rlc_lte_info); reassembly_destroy(p_channel_status); } } /* The start of a new reassembly? */ if (!last_includes_end && ((number_of_segments > 1) || first_includes_start)) { uint16_t lastSegmentLength = tvb_reported_length(tvb)-lastSegmentOffset; if (global_rlc_lte_reassembly) { reassembly_reset(p_channel_status); reassembly_add_segment(p_channel_status, sequenceNumber, pinfo->num, tvb, lastSegmentOffset, lastSegmentLength); } } if (p_report_in_frame->previousFrameNum != 0) { /* Get report for previous frame */ sequence_analysis_report *p_previous_report; if (p_rlc_lte_info->sequenceNumberLength == UM_SN_LENGTH_5_BITS) { snLimit = 32; } else { snLimit = 1024; } /* Look up report for previous SN */ p_previous_report = (sequence_analysis_report*)wmem_map_lookup(sequence_analysis_report_hash, get_report_hash_key((sequenceNumber+snLimit-1) % snLimit, p_report_in_frame->previousFrameNum, p_rlc_lte_info, false)); /* It really shouldn't be NULL... */ if (p_previous_report != NULL) { /* Point it forward to this one */ p_previous_report->nextFrameNum = pinfo->num; } } } break; case RLC_AM_MODE: if (p_rlc_lte_info->sequenceNumberLength == AM_SN_LENGTH_16_BITS) { snLimit = 65536; } else { snLimit = 1024; } /* Work out expected sequence number */ if (!createdChannel) { expectedSequenceNumber = (p_channel_status->previousSequenceNumber + 1) % snLimit; } else { /* Whatever we got is fine.. */ expectedSequenceNumber = sequenceNumber; } /* For AM, may be: - expected Sequence number OR - previous frame repeated - old SN being sent (in response to NACK) - new SN, but with frames missed out Assume window whose front is at expectedSequenceNumber */ /* First of all, check to see whether frame is judged to be MAC Retx */ if (is_mac_lte_frame_retx(pinfo, p_rlc_lte_info->direction)) { /* Just report that this is a MAC Retx */ p_report_in_frame->state = SN_MAC_Retx; p_report_in_frame->firstSN = sequenceNumber; /* No channel state to update */ break; } if (sequenceNumber != expectedSequenceNumber) { /* Don't trash reassembly info if this looks like a close retx... */ if (((snLimit + sequenceNumber - expectedSequenceNumber) % snLimit) < 50) { reassembly_destroy(p_channel_status); } } /* Expected? */ if (sequenceNumber == expectedSequenceNumber) { /* Set report for this frame */ p_report_in_frame->sequenceExpectedCorrect = true; p_report_in_frame->sequenceExpected = expectedSequenceNumber; p_report_in_frame->previousFrameNum = p_channel_status->previousFrameNum; p_report_in_frame->previousSegmentIncomplete = p_channel_status->previousSegmentIncomplete; p_report_in_frame->state = SN_OK; /* Update channel status */ p_channel_status->previousSequenceNumber = sequenceNumber; p_channel_status->previousFrameNum = pinfo->num; p_channel_status->previousSegmentIncomplete = !last_includes_end; if (p_channel_status->reassembly_info) { /* Add next segment to reassembly info */ reassembly_add_segment(p_channel_status, sequenceNumber, pinfo->num, tvb, firstSegmentOffset, firstSegmentLength); /* The end of existing reassembly? */ if (!first_includes_start && ((number_of_segments > 1) || last_includes_end)) { reassembly_record(p_channel_status, pinfo, sequenceNumber, p_rlc_lte_info); reassembly_destroy(p_channel_status); } } /* The start of a new reassembly? */ if (!last_includes_end && ((number_of_segments > 1) || first_includes_start)) { uint16_t lastSegmentLength = tvb_reported_length(tvb)-lastSegmentOffset; if (global_rlc_lte_reassembly) { reassembly_reset(p_channel_status); reassembly_add_segment(p_channel_status, sequenceNumber, pinfo->num, tvb, lastSegmentOffset, lastSegmentLength); } } if (p_report_in_frame->previousFrameNum != 0) { /* Get report for previous frame */ sequence_analysis_report *p_previous_report; p_previous_report = (sequence_analysis_report*)wmem_map_lookup(sequence_analysis_report_hash, get_report_hash_key((sequenceNumber+snLimit-1) % snLimit, p_report_in_frame->previousFrameNum, p_rlc_lte_info, false)); /* It really shouldn't be NULL... */ if (p_previous_report != NULL) { /* Point it forward to this one */ p_previous_report->nextFrameNum = pinfo->num; } } } /* Previous subframe repeated? */ else if (((sequenceNumber+1) % snLimit) == expectedSequenceNumber) { p_report_in_frame->state = SN_Repeated; /* Set report for this frame */ p_report_in_frame->sequenceExpectedCorrect = false; p_report_in_frame->sequenceExpected = expectedSequenceNumber; p_report_in_frame->firstSN = sequenceNumber; p_report_in_frame->previousFrameNum = p_channel_status->previousFrameNum; p_report_in_frame->previousSegmentIncomplete = p_channel_status->previousSegmentIncomplete; /* Really should be nothing to update... */ p_channel_status->previousSequenceNumber = sequenceNumber; p_channel_status->previousFrameNum = pinfo->num; p_channel_status->previousSegmentIncomplete = !last_includes_end; } else { /* Need to work out if new (with skips, or likely a retx (due to NACK)) */ int delta = (snLimit + expectedSequenceNumber - sequenceNumber) % snLimit; /* Rx window is 512/32768, so check to see if this is a retx */ if (delta < (int)(snLimit>>1)) { /* Probably a retx due to receiving NACK */ p_report_in_frame->state = SN_Retx; p_report_in_frame->firstSN = sequenceNumber; /* Don't update anything in channel state */ } else { /* Ahead of expected SN. Assume frames have been missed */ p_report_in_frame->state = SN_Missing; p_report_in_frame->firstSN = expectedSequenceNumber; p_report_in_frame->lastSN = (snLimit + sequenceNumber-1) % snLimit; /* Update channel state - forget about missed SNs */ p_report_in_frame->sequenceExpected = expectedSequenceNumber; p_channel_status->previousSequenceNumber = sequenceNumber; p_channel_status->previousFrameNum = pinfo->num; p_channel_status->previousSegmentIncomplete = !last_includes_end; } } break; default: /* Shouldn't get here! */ return SN_Error; } /* Associate with this frame number */ wmem_map_insert(sequence_analysis_report_hash, get_report_hash_key(sequenceNumber, pinfo->num, p_rlc_lte_info, true), p_report_in_frame); /* Add state report for this frame into tree */ addChannelSequenceInfo(p_report_in_frame, isControlFrame, p_rlc_lte_info, sequenceNumber, first_includes_start, tap_info, pinfo, tree, tvb); return p_report_in_frame->state; } /* Add to the tree values associated with sequence analysis for this frame */ static void addChannelRepeatedNACKInfo(channel_repeated_nack_report *p, rlc_lte_info *p_rlc_lte_info, packet_info *pinfo, proto_tree *tree, tvbuff_t *tvb) { proto_tree *seqnum_tree; proto_item *seqnum_ti; proto_item *ti; int n; /* Create subtree */ seqnum_ti = proto_tree_add_string_format(tree, hf_rlc_lte_sequence_analysis, tvb, 0, 0, "", "Sequence Analysis"); seqnum_tree = proto_item_add_subtree(seqnum_ti, ett_rlc_lte_sequence_analysis); proto_item_set_generated(seqnum_ti); /* OK = false */ ti = proto_tree_add_boolean(seqnum_tree, hf_rlc_lte_sequence_analysis_ok, tvb, 0, 0, false); proto_item_set_generated(ti); /* Add each repeated NACK as item & expert info */ for (n=0; n < p->noOfNACKsRepeated; n++) { ti = proto_tree_add_uint(seqnum_tree, hf_rlc_lte_sequence_analysis_repeated_nack, tvb, 0, 0, p->repeatedNACKs[n]); proto_item_set_generated(ti); expert_add_info_format(pinfo, ti, &ei_rlc_lte_sequence_analysis_repeated_nack, "Same SN (%u) NACKd for %s on UE %u in successive Status PDUs", p->repeatedNACKs[n], val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"), p_rlc_lte_info->ueid); } /* Link back to previous status report */ ti = proto_tree_add_uint(seqnum_tree, hf_rlc_lte_sequence_analysis_repeated_nack_original_frame, tvb, 0, 0, p->previousFrameNum); proto_item_set_generated(ti); /* Append count to sequence analysis root */ proto_item_append_text(seqnum_ti, " - %u SNs repeated from previous Status PDU", p->noOfNACKsRepeated); } /* Update the channel repeated NACK status and set report for this frame */ static void checkChannelRepeatedNACKInfo(packet_info *pinfo, rlc_lte_info *p_rlc_lte_info, rlc_3gpp_tap_info *tap_info, proto_tree *tree, tvbuff_t *tvb) { channel_hash_key channel_key; channel_hash_key *p_channel_key; channel_repeated_nack_status *p_channel_status; channel_repeated_nack_report *p_report_in_frame = NULL; uint16_t noOfNACKsRepeated = 0; uint16_t repeatedNACKs[MAX_NACKs]; int n, i, j; /* If find state_report_in_frame already, use that and get out */ if (pinfo->fd->visited) { p_report_in_frame = (channel_repeated_nack_report*)wmem_map_lookup(repeated_nack_report_hash, get_report_hash_key(0, pinfo->num, p_rlc_lte_info, false)); if (p_report_in_frame != NULL) { addChannelRepeatedNACKInfo(p_report_in_frame, p_rlc_lte_info, pinfo, tree, tvb); return; } else { /* Give up - we must have tried already... */ return; } } /**************************************************/ /* Create or find an entry for this channel state */ channel_key.ueId = p_rlc_lte_info->ueid; channel_key.channelType = p_rlc_lte_info->channelType; channel_key.channelId = p_rlc_lte_info->channelId; channel_key.direction = p_rlc_lte_info->direction; memset(repeatedNACKs, 0, sizeof(repeatedNACKs)); /* Do the table lookup */ p_channel_status = (channel_repeated_nack_status*)wmem_map_lookup(repeated_nack_channel_hash, &channel_key); /* Create table entry if necessary */ if (p_channel_status == NULL) { /* Allocate a new key and value */ p_channel_key = wmem_new(wmem_file_scope(), channel_hash_key); p_channel_status = wmem_new0(wmem_file_scope(), channel_repeated_nack_status); /* Copy key contents */ memcpy(p_channel_key, &channel_key, sizeof(channel_hash_key)); /* Add entry to table */ wmem_map_insert(repeated_nack_channel_hash, p_channel_key, p_channel_status); } /* Compare NACKs in channel status with NACKs in tap_info. Note any that are repeated */ for (i=0; i < p_channel_status->noOfNACKs; i++) { for (j=0; j < MIN(tap_info->noOfNACKs, MAX_NACKs); j++) { if (tap_info->NACKs[j] == p_channel_status->NACKs[i]) { /* Don't add the same repeated NACK twice! */ if ((noOfNACKsRepeated == 0) || (repeatedNACKs[noOfNACKsRepeated-1] != p_channel_status->NACKs[i])) { repeatedNACKs[noOfNACKsRepeated++] = p_channel_status->NACKs[i]; } } } } /* Copy NACKs from tap_info into channel status for next time! */ p_channel_status->noOfNACKs = 0; for (n=0; n < MIN(tap_info->noOfNACKs, MAX_NACKs); n++) { p_channel_status->NACKs[p_channel_status->noOfNACKs++] = tap_info->NACKs[n]; } if (noOfNACKsRepeated >= 1) { /* Create space for frame state_report */ p_report_in_frame = wmem_new(wmem_file_scope(), channel_repeated_nack_report); /* Copy in found duplicates */ for (n=0; n < MIN(tap_info->noOfNACKs, MAX_NACKs); n++) { p_report_in_frame->repeatedNACKs[n] = repeatedNACKs[n]; } p_report_in_frame->noOfNACKsRepeated = noOfNACKsRepeated; p_report_in_frame->previousFrameNum = p_channel_status->frameNum; /* Associate with this frame number */ wmem_map_insert(repeated_nack_report_hash, get_report_hash_key(0, pinfo->num, p_rlc_lte_info, true), p_report_in_frame); /* Add state report for this frame into tree */ addChannelRepeatedNACKInfo(p_report_in_frame, p_rlc_lte_info, pinfo, tree, tvb); } /* Save frame number for next comparison */ p_channel_status->frameNum = pinfo->num; } /* Check that the ACK is consistent with data the expected sequence number in the other direction */ static void checkChannelACKWindow(uint16_t ack_sn, packet_info *pinfo, rlc_lte_info *p_rlc_lte_info, rlc_3gpp_tap_info *tap_info, proto_tree *tree, tvbuff_t *tvb) { channel_hash_key channel_key; channel_sequence_analysis_status *p_channel_status; sequence_analysis_report *p_report_in_frame = NULL; uint32_t snLimit; /* If find stat_report_in_frame already, use that and get out */ if (pinfo->fd->visited) { p_report_in_frame = (sequence_analysis_report*)wmem_map_lookup(sequence_analysis_report_hash, get_report_hash_key(0, pinfo->num, p_rlc_lte_info, false)); if (p_report_in_frame != NULL) { /* Add any info to tree */ addChannelSequenceInfo(p_report_in_frame, true, p_rlc_lte_info, 0, false, tap_info, pinfo, tree, tvb); return; } else { /* Give up - we must have tried already... */ return; } } /*******************************************************************/ /* Find an entry for this channel state (in the opposite direction */ channel_key.ueId = p_rlc_lte_info->ueid; channel_key.channelType = p_rlc_lte_info->channelType; channel_key.channelId = p_rlc_lte_info->channelId; channel_key.direction = (p_rlc_lte_info->direction == DIRECTION_UPLINK) ? DIRECTION_DOWNLINK : DIRECTION_UPLINK; /* Do the table lookup */ p_channel_status = (channel_sequence_analysis_status*)wmem_map_lookup(sequence_analysis_channel_hash, &channel_key); /* Create table entry if necessary */ if (p_channel_status == NULL) { return; } /* Is it in the rx window? This test will catch if it's ahead, but we don't really know what the back of the tx window is... */ snLimit = (p_rlc_lte_info->sequenceNumberLength == AM_SN_LENGTH_16_BITS) ? 65536 : 1024; if (((snLimit + (uint32_t)p_channel_status->previousSequenceNumber+1 - ack_sn) % snLimit) > (snLimit>>1)) { /* Set result */ p_report_in_frame = wmem_new0(wmem_file_scope(), sequence_analysis_report); p_report_in_frame->state = ACK_Out_of_Window; p_report_in_frame->previousFrameNum = p_channel_status->previousFrameNum; p_report_in_frame->sequenceExpected = p_channel_status->previousSequenceNumber; p_report_in_frame->firstSN = ack_sn; /* Associate with this frame number */ wmem_map_insert(sequence_analysis_report_hash, get_report_hash_key(0, pinfo->num, p_rlc_lte_info, true), p_report_in_frame); /* Add state report for this frame into tree */ addChannelSequenceInfo(p_report_in_frame, true, p_rlc_lte_info, 0, false, tap_info, pinfo, tree, tvb); } } /***************************************************/ /* Transparent mode PDU. Call RRC if configured to */ static void dissect_rlc_lte_tm(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset, rlc_lte_info *p_rlc_lte_info, proto_item *top_ti) { proto_item *raw_tm_ti; proto_item *tm_ti; /* Create hidden TM root */ tm_ti = proto_tree_add_string_format(tree, hf_rlc_lte_tm, tvb, offset, 0, "", "TM"); proto_item_set_hidden(tm_ti); /* Remaining bytes are all data */ raw_tm_ti = proto_tree_add_item(tree, hf_rlc_lte_tm_data, tvb, offset, -1, ENC_NA); if (!global_rlc_lte_call_rrc_for_ccch) { write_pdu_label_and_info(top_ti, NULL, pinfo, " [%u-bytes]", tvb_reported_length_remaining(tvb, offset)); } if (global_rlc_lte_call_rrc_for_ccch) { tvbuff_t *rrc_tvb = tvb_new_subset_remaining(tvb, offset); volatile dissector_handle_t protocol_handle; switch (p_rlc_lte_info->channelType) { case CHANNEL_TYPE_CCCH: if (p_rlc_lte_info->direction == DIRECTION_UPLINK) { protocol_handle = (p_rlc_lte_info->nbMode == rlc_nb_mode) ? lte_rrc_ul_ccch_nb : lte_rrc_ul_ccch; } else { protocol_handle = (p_rlc_lte_info->nbMode == rlc_nb_mode) ? lte_rrc_dl_ccch_nb : lte_rrc_dl_ccch; } break; case CHANNEL_TYPE_BCCH_BCH: protocol_handle = (p_rlc_lte_info->nbMode == rlc_nb_mode) ? lte_rrc_bcch_bch_nb : lte_rrc_bcch_bch; break; case CHANNEL_TYPE_BCCH_DL_SCH: protocol_handle = (p_rlc_lte_info->nbMode == rlc_nb_mode) ? lte_rrc_bcch_dl_sch_nb : lte_rrc_bcch_dl_sch; break; case CHANNEL_TYPE_PCCH: protocol_handle = (p_rlc_lte_info->nbMode == rlc_nb_mode) ? lte_rrc_pcch_nb : lte_rrc_pcch; break; case CHANNEL_TYPE_SRB: case CHANNEL_TYPE_DRB: case CHANNEL_TYPE_MCCH: case CHANNEL_TYPE_MTCH: default: /* Shouldn't happen, just return... */ return; } /* Hide raw view of bytes */ proto_item_set_hidden(raw_tm_ti); /* Call it (catch exceptions) */ TRY { call_dissector_only(protocol_handle, rrc_tvb, pinfo, tree, NULL); } CATCH_ALL { } ENDTRY } } /***************************************************/ /* Unacknowledged mode PDU */ static void dissect_rlc_lte_um(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset, rlc_lte_info *p_rlc_lte_info, proto_item *top_ti, rlc_3gpp_tap_info *tap_info) { uint64_t framing_info; bool first_includes_start; bool last_includes_end; uint64_t fixed_extension; uint64_t sn; int start_offset = offset; proto_item *um_ti; proto_tree *um_header_tree; proto_item *um_header_ti; bool is_truncated = false; proto_item *truncated_ti; rlc_channel_reassembly_info *reassembly_info = NULL; sequence_analysis_state seq_anal_state = SN_OK; /* Hidden UM root */ um_ti = proto_tree_add_string_format(tree, hf_rlc_lte_um, tvb, offset, 0, "", "UM"); proto_item_set_hidden(um_ti); /* Add UM header subtree */ um_header_ti = proto_tree_add_string_format(tree, hf_rlc_lte_um_header, tvb, offset, 0, "", "UM header"); um_header_tree = proto_item_add_subtree(um_header_ti, ett_rlc_lte_um_header); /*******************************/ /* Fixed UM header */ if (p_rlc_lte_info->sequenceNumberLength == UM_SN_LENGTH_5_BITS) { /* Framing info (2 bits) */ proto_tree_add_bits_ret_val(um_header_tree, hf_rlc_lte_um_fi, tvb, offset*8, 2, &framing_info, ENC_BIG_ENDIAN); /* Extension (1 bit) */ proto_tree_add_bits_ret_val(um_header_tree, hf_rlc_lte_um_fixed_e, tvb, (offset*8) + 2, 1, &fixed_extension, ENC_BIG_ENDIAN); /* Sequence Number (5 bit) */ proto_tree_add_bits_ret_val(um_header_tree, hf_rlc_lte_um_sn, tvb, (offset*8) + 3, 5, &sn, ENC_BIG_ENDIAN); offset++; } else if (p_rlc_lte_info->sequenceNumberLength == UM_SN_LENGTH_10_BITS) { uint32_t reserved; proto_item *ti; /* Check 3 Reserved bits */ ti = proto_tree_add_item_ret_uint(um_header_tree, hf_rlc_lte_um_fixed_reserved, tvb, offset, 1, ENC_BIG_ENDIAN, &reserved); if (reserved != 0) { expert_add_info_format(pinfo, ti, &ei_rlc_lte_reserved_bits_not_zero, "RLC UM Fixed header Reserved bits not zero (found 0x%x)", reserved); } /* Framing info (2 bits) */ proto_tree_add_bits_ret_val(um_header_tree, hf_rlc_lte_um_fi, tvb, (offset*8)+3, 2, &framing_info, ENC_BIG_ENDIAN); /* Extension (1 bit) */ proto_tree_add_bits_ret_val(um_header_tree, hf_rlc_lte_um_fixed_e, tvb, (offset*8) + 5, 1, &fixed_extension, ENC_BIG_ENDIAN); /* Sequence Number (10 bits) */ proto_tree_add_bits_ret_val(um_header_tree, hf_rlc_lte_um_sn, tvb, (offset*8) + 6, 10, &sn, ENC_BIG_ENDIAN); offset += 2; } else { /* Invalid length of sequence number */ proto_tree_add_expert_format(um_header_tree, pinfo, &ei_rlc_lte_um_sn, tvb, 0, 0, "Invalid sequence number length (%u bits)", p_rlc_lte_info->sequenceNumberLength); return; } tap_info->sequenceNumberGiven = true; tap_info->sequenceNumber = (uint16_t)sn; /* Show SN in info column */ if ((p_rlc_lte_info->channelType == CHANNEL_TYPE_MCCH) || (p_rlc_lte_info->channelType == CHANNEL_TYPE_MTCH)) { write_pdu_label_and_info(top_ti, um_header_ti, pinfo, " sn=%-4u", (uint16_t)sn); } else { write_pdu_label_and_info(top_ti, um_header_ti, pinfo, " sn=%-4u", (uint16_t)sn); } proto_item_set_len(um_header_ti, offset-start_offset); /*************************************/ /* UM header extension */ if (fixed_extension) { offset = dissect_rlc_lte_extension_header(tvb, pinfo, um_header_tree, offset, p_rlc_lte_info); } /* Extract these 2 flags from framing_info */ first_includes_start = ((uint8_t)framing_info & 0x02) == 0; last_includes_end = ((uint8_t)framing_info & 0x01) == 0; if (global_rlc_lte_headers_expected) { /* There might not be any data, if only headers (plus control data) were logged */ is_truncated = (tvb_captured_length_remaining(tvb, offset) == 0); truncated_ti = proto_tree_add_uint(tree, hf_rlc_lte_header_only, tvb, 0, 0, is_truncated); if (is_truncated) { int n; proto_item_set_generated(truncated_ti); expert_add_info(pinfo, truncated_ti, &ei_rlc_lte_header_only); /* Show in the info column how long the data would be */ for (n=0; n < s_number_of_extensions; n++) { show_PDU_in_info(pinfo, top_ti, s_lengths[n], (n==0) ? first_includes_start : true, true); offset += s_lengths[n]; } /* Last one */ show_PDU_in_info(pinfo, top_ti, p_rlc_lte_info->pduLength - offset, (s_number_of_extensions == 0) ? first_includes_start : true, last_includes_end); } else { proto_item_set_hidden(truncated_ti); } } /* Show number of extensions in header root */ if (s_number_of_extensions > 0) { proto_item_append_text(um_header_ti, " (%u extensions)", s_number_of_extensions); } /* Call sequence analysis function now */ if (((global_rlc_lte_um_sequence_analysis == SEQUENCE_ANALYSIS_MAC_ONLY) && (p_get_proto_data(wmem_file_scope(), pinfo, proto_mac_lte, 0) != NULL)) || ((global_rlc_lte_um_sequence_analysis == SEQUENCE_ANALYSIS_RLC_ONLY) && (p_get_proto_data(wmem_file_scope(), pinfo, proto_mac_lte, 0) == NULL))) { uint16_t lastSegmentOffset = offset; if (s_number_of_extensions >= 1) { int n; lastSegmentOffset = offset; for (n=0; n < s_number_of_extensions; n++) { lastSegmentOffset += s_lengths[n]; } } seq_anal_state = checkChannelSequenceInfo(pinfo, tvb, p_rlc_lte_info, false, s_number_of_extensions+1, offset, s_number_of_extensions ? s_lengths[0] : p_rlc_lte_info->pduLength - offset, lastSegmentOffset, (uint16_t)sn, first_includes_start, last_includes_end, false, /* UM doesn't re-segment */ tap_info, um_header_tree); } if (is_truncated) { return; } /*************************************/ /* Data */ reassembly_info = (rlc_channel_reassembly_info *)wmem_map_lookup(reassembly_report_hash, get_report_hash_key((uint16_t)sn, pinfo->num, p_rlc_lte_info, false)); if (s_number_of_extensions > 0) { /* Show each data segment separately */ int n; for (n=0; n < s_number_of_extensions; n++) { show_PDU_in_tree(pinfo, tree, tvb, offset, s_lengths[n], p_rlc_lte_info, (n==0) ? first_includes_start : true, (n==0) ? reassembly_info : NULL, seq_anal_state); show_PDU_in_info(pinfo, top_ti, s_lengths[n], (n==0) ? first_includes_start : true, true); /* Make sure we don't lose the summary of this SDU */ col_append_str(pinfo->cinfo, COL_INFO, " | "); col_set_fence(pinfo->cinfo, COL_INFO); tvb_ensure_bytes_exist(tvb, offset, s_lengths[n]); offset += s_lengths[n]; } } /* Final data element */ show_PDU_in_tree(pinfo, tree, tvb, offset, tvb_reported_length_remaining(tvb, offset), p_rlc_lte_info, ((s_number_of_extensions == 0) ? first_includes_start : true) && last_includes_end, (s_number_of_extensions == 0) ? reassembly_info : NULL, seq_anal_state); show_PDU_in_info(pinfo, top_ti, (uint16_t)tvb_reported_length_remaining(tvb, offset), (s_number_of_extensions == 0) ? first_includes_start : true, last_includes_end); } /* Dissect an AM STATUS PDU */ static void dissect_rlc_lte_am_status_pdu(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, proto_item *status_ti, int offset, proto_item *top_ti, rlc_lte_info *p_rlc_lte_info, rlc_3gpp_tap_info *tap_info) { uint32_t cpt; uint8_t sn_size, so_size; uint32_t sn_limit; uint64_t ack_sn, nack_sn; uint16_t nack_count = 0, so_end_of_pdu; uint64_t e1 = 0, e2 = 0; uint64_t so_start, so_end; int bit_offset = offset * 8; proto_item *ti; /****************************************************************/ /* Part of RLC control PDU header */ /* Control PDU Type (CPT) */ ti = proto_tree_add_item_ret_uint(tree, hf_rlc_lte_am_cpt, tvb, offset, 1, ENC_BIG_ENDIAN, &cpt); if (cpt != 0) { /* Protest and stop - only know about STATUS PDUs */ expert_add_info_format(pinfo, ti, &ei_rlc_lte_am_cpt, "RLC Control frame type %u not handled", cpt); return; } if (p_rlc_lte_info->sequenceNumberLength == AM_SN_LENGTH_16_BITS) { sn_size = 16; sn_limit = 65536; so_size = 16; so_end_of_pdu = 0xffff; } else { sn_size = 10; sn_limit = 1024; so_size = 15; so_end_of_pdu = 0x7fff; } /* The Status PDU itself starts 4 bits into the byte */ bit_offset += 4; /* ACK SN */ proto_tree_add_bits_ret_val(tree, hf_rlc_lte_am_ack_sn, tvb, bit_offset, sn_size, &ack_sn, ENC_BIG_ENDIAN); bit_offset += sn_size; write_pdu_label_and_info(top_ti, status_ti, pinfo, " ACK_SN=%-4u", (uint16_t)ack_sn); tap_info->ACKNo = (uint16_t)ack_sn; /* E1 */ proto_tree_add_bits_ret_val(tree, hf_rlc_lte_am_e1, tvb, bit_offset, 1, &e1, ENC_BIG_ENDIAN); /* Skip another bit to byte-align the next bit... */ bit_offset++; /* Optional, extra fields */ do { if (e1) { proto_item *nack_ti; /****************************/ /* Read NACK_SN, E1, E2 */ /* NACK_SN */ nack_ti = proto_tree_add_bits_ret_val(tree, hf_rlc_lte_am_nack_sn, tvb, bit_offset, sn_size, &nack_sn, ENC_BIG_ENDIAN); bit_offset += sn_size; write_pdu_label_and_info(top_ti, NULL, pinfo, " NACK_SN=%-4u", (uint16_t)nack_sn); /* We shouldn't NACK the ACK_SN! */ if (nack_sn == ack_sn) { expert_add_info_format(pinfo, nack_ti, &ei_rlc_lte_am_nack_sn_ack_same, "Status PDU shouldn't ACK and NACK the same sequence number (%" PRIu64 ")", ack_sn); } /* NACK should always be 'behind' the ACK */ if ((sn_limit + ack_sn - nack_sn) % sn_limit > (sn_limit>>1)) { expert_add_info(pinfo, nack_ti, &ei_rlc_lte_am_nack_sn_ahead_ack); } /* Copy into struct, but don't exceed buffer */ if (nack_count < MAX_NACKs) { tap_info->NACKs[nack_count++] = (uint16_t)nack_sn; } else { /* Let it get bigger than the array for accurate stats... */ nack_count++; } /* E1 */ proto_tree_add_bits_ret_val(tree, hf_rlc_lte_am_e1, tvb, bit_offset, 1, &e1, ENC_BIG_ENDIAN); bit_offset++; /* E2 */ proto_tree_add_bits_ret_val(tree, hf_rlc_lte_am_e2, tvb, bit_offset, 1, &e2, ENC_BIG_ENDIAN); /* Report as expert info */ if (e2) { expert_add_info_format(pinfo, nack_ti, &ei_rlc_lte_am_nack_sn_partial, "Status PDU reports NACK (partial) on %s for UE %u", val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"), p_rlc_lte_info->ueid); } else { expert_add_info_format(pinfo, nack_ti, &ei_rlc_lte_am_nack_sn, "Status PDU reports NACK on %s for UE %u", val_to_str_const(p_rlc_lte_info->direction, direction_vals, "Unknown"), p_rlc_lte_info->ueid); } bit_offset++; } if (e2) { /* Read SOstart, SOend */ proto_tree_add_bits_ret_val(tree, hf_rlc_lte_am_so_start, tvb, bit_offset, so_size, &so_start, ENC_BIG_ENDIAN); bit_offset += so_size; proto_tree_add_bits_ret_val(tree, hf_rlc_lte_am_so_end, tvb, bit_offset, so_size, &so_end, ENC_BIG_ENDIAN); bit_offset += so_size; if ((uint16_t)so_end == so_end_of_pdu) { write_pdu_label_and_info(top_ti, NULL, pinfo, " (SOstart=%u SOend=)", (uint16_t)so_start); } else { write_pdu_label_and_info(top_ti, NULL, pinfo, " (SOstart=%u SOend=%u)", (uint16_t)so_start, (uint16_t)so_end); } /* Reset this flag here */ e2 = 0; } } while (e1); if (nack_count > 0) { proto_item *count_ti = proto_tree_add_uint(tree, hf_rlc_lte_am_nacks, tvb, 0, 1, nack_count); proto_item_set_generated(count_ti); proto_item_append_text(status_ti, " (%u NACKs)", nack_count); tap_info->noOfNACKs = nack_count; } /* Check that we've reached the end of the PDU. If not, show malformed */ offset = (bit_offset+7) / 8; if (tvb_reported_length_remaining(tvb, offset) > 0) { expert_add_info_format(pinfo, status_ti, &ei_rlc_lte_bytes_after_status_pdu_complete, "%cL %u bytes remaining after Status PDU complete", (p_rlc_lte_info->direction == DIRECTION_UPLINK) ? 'U' : 'D', tvb_reported_length_remaining(tvb, offset)); } /* Set selected length of control tree */ proto_item_set_len(status_ti, offset); /* Repeated NACK analysis & check ACK-SN is in range */ if (((global_rlc_lte_am_sequence_analysis == SEQUENCE_ANALYSIS_MAC_ONLY) && (p_get_proto_data(wmem_file_scope(), pinfo, proto_mac_lte, 0) != NULL)) || ((global_rlc_lte_am_sequence_analysis == SEQUENCE_ANALYSIS_RLC_ONLY) && (p_get_proto_data(wmem_file_scope(), pinfo, proto_mac_lte, 0) == NULL))) { if (!is_mac_lte_frame_retx(pinfo, p_rlc_lte_info->direction)) { checkChannelRepeatedNACKInfo(pinfo, p_rlc_lte_info, tap_info, tree, tvb); checkChannelACKWindow((uint16_t)ack_sn, pinfo, p_rlc_lte_info, tap_info, tree, tvb); } } } /***************************************************/ /* Acknowledged mode PDU */ static void dissect_rlc_lte_am(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int offset, rlc_lte_info *p_rlc_lte_info, proto_item *top_ti, rlc_3gpp_tap_info *tap_info) { bool is_data; uint32_t is_resegmented; uint32_t polling; uint32_t fixed_extension; uint32_t framing_info; bool first_includes_start; bool last_includes_end; proto_item *am_ti; proto_tree *am_header_tree; proto_item *am_header_ti; int start_offset = offset; uint32_t sn; bool is_truncated = false; proto_item *truncated_ti; rlc_channel_reassembly_info *reassembly_info = NULL; sequence_analysis_state seq_anal_state = SN_OK; uint32_t id; wmem_tree_key_t key[3]; rlc_ue_parameters *params; /* Hidden AM root */ am_ti = proto_tree_add_string_format(tree, hf_rlc_lte_am, tvb, offset, 0, "", "AM"); proto_item_set_hidden(am_ti); /* Add AM header subtree */ am_header_ti = proto_tree_add_string_format(tree, hf_rlc_lte_am_header, tvb, offset, 0, "", "AM Header "); am_header_tree = proto_item_add_subtree(am_header_ti, ett_rlc_lte_am_header); /* First bit is Data/Control flag */ proto_tree_add_item_ret_boolean(am_header_tree, hf_rlc_lte_am_data_control, tvb, offset, 1, ENC_BIG_ENDIAN, &is_data); tap_info->isControlPDU = !is_data; if (!is_data) { /**********************/ /* Status PDU */ write_pdu_label_and_info_literal(top_ti, NULL, pinfo, " [CONTROL]"); /* Control PDUs are a completely separate format */ dissect_rlc_lte_am_status_pdu(tvb, pinfo, am_header_tree, am_header_ti, offset, top_ti, p_rlc_lte_info, tap_info); return; } /******************************/ /* Data PDU fixed header */ /* Re-segmentation Flag (RF) field */ proto_tree_add_item_ret_uint(am_header_tree, hf_rlc_lte_am_rf, tvb, offset, 1, ENC_BIG_ENDIAN, &is_resegmented); tap_info->isResegmented = is_resegmented; write_pdu_label_and_info_literal(top_ti, NULL, pinfo, (is_resegmented) ? " [DATA-SEGMENT]" : " [DATA]"); /* Polling bit */ proto_tree_add_item_ret_uint(am_header_tree, hf_rlc_lte_am_p, tvb, offset, 1, ENC_BIG_ENDIAN, &polling); write_pdu_label_and_info_literal(top_ti, NULL, pinfo, (polling) ? " (P) " : " "); if (polling) { proto_item_append_text(am_header_ti, " (P) "); } /* Framing Info */ proto_tree_add_item_ret_uint(am_header_tree, hf_rlc_lte_am_fi, tvb, offset, 1, ENC_BIG_ENDIAN, &framing_info); /* Extension bit */ proto_tree_add_item_ret_uint(am_header_tree, hf_rlc_lte_am_fixed_e, tvb, offset, 1, ENC_BIG_ENDIAN, &fixed_extension); /* Sequence Number */ if (p_rlc_lte_info->sequenceNumberLength == AM_SN_LENGTH_16_BITS) { uint32_t reserved; if (is_resegmented) { /* Last Segment Field (LSF) */ proto_tree_add_item(am_header_tree, hf_rlc_lte_am_segment_lsf16, tvb, offset, 1, ENC_BIG_ENDIAN); /* Reserved (R1) */ am_ti = proto_tree_add_item_ret_uint(am_header_tree, hf_rlc_lte_am_fixed_reserved2, tvb, offset, 1, ENC_BIG_ENDIAN, &reserved); } else { /* Reserved (R1) */ am_ti = proto_tree_add_item_ret_uint(am_header_tree, hf_rlc_lte_am_fixed_reserved, tvb, offset, 1, ENC_BIG_ENDIAN, &reserved); } if (reserved != 0) { expert_add_info_format(pinfo, am_ti, &ei_rlc_lte_reserved_bits_not_zero, "RLC AM Fixed header Reserved bits not zero (found 0x02%x)", reserved); } offset += 1; proto_tree_add_item_ret_uint(am_header_tree, hf_rlc_lte_am_fixed_sn16, tvb, offset, 2, ENC_BIG_ENDIAN, &sn); offset += 2; } else { proto_tree_add_item_ret_uint(am_header_tree, hf_rlc_lte_am_fixed_sn, tvb, offset, 2, ENC_BIG_ENDIAN, &sn); offset += 2; } tap_info->sequenceNumberGiven = true; tap_info->sequenceNumber = sn; write_pdu_label_and_info(top_ti, am_header_ti, pinfo, "sn=%-4u", sn); /***************************************/ /* Dissect extra segment header fields */ if (is_resegmented) { uint32_t segmentOffset; if (p_rlc_lte_info->sequenceNumberLength == AM_SN_LENGTH_16_BITS) { /* SO */ proto_tree_add_item_ret_uint(am_header_tree, hf_rlc_lte_am_segment_so16, tvb, offset, 2, ENC_BIG_ENDIAN, &segmentOffset); } else { /* Last Segment Field (LSF) */ proto_tree_add_item(am_header_tree, hf_rlc_lte_am_segment_lsf, tvb, offset, 1, ENC_BIG_ENDIAN); /* SO */ proto_tree_add_item_ret_uint(am_header_tree, hf_rlc_lte_am_segment_so, tvb, offset, 2, ENC_BIG_ENDIAN, &segmentOffset); } write_pdu_label_and_info(top_ti, am_header_ti, pinfo, " SO=%u ", segmentOffset); offset += 2; } /*************************************/ /* AM header extension */ if (fixed_extension) { if (!PINFO_FD_VISITED(pinfo)) { id = (p_rlc_lte_info->channelId << 16) | p_rlc_lte_info->ueid; key[0].length = 1; key[0].key = &id; key[1].length = 1; key[1].key = &pinfo->num; key[2].length = 0; key[2].key = NULL; params = (rlc_ue_parameters *)wmem_tree_lookup32_array_le(ue_parameters_tree, key); if (params && (params->id == id)) { p_rlc_lte_info->extendedLiField = (p_rlc_lte_info->direction == DIRECTION_UPLINK) ? (params->ext_li_field & UL_EXT_LI): (params->ext_li_field & DL_EXT_LI); } } offset = dissect_rlc_lte_extension_header(tvb, pinfo, am_header_tree, offset, p_rlc_lte_info); } /* Header is now complete */ proto_item_set_len(am_header_ti, offset-start_offset); /* Show number of extensions in header root */ if (s_number_of_extensions > 0) { proto_item_append_text(am_header_ti, " (%u extensions)", s_number_of_extensions); } /* Extract these 2 flags from framing_info */ first_includes_start = (framing_info & 0x02) == 0; last_includes_end = (framing_info & 0x01) == 0; /* There might not be any data, if only headers (plus control data) were logged */ if (global_rlc_lte_headers_expected) { is_truncated = (tvb_captured_length_remaining(tvb, offset) == 0); truncated_ti = proto_tree_add_uint(tree, hf_rlc_lte_header_only, tvb, 0, 0, is_truncated); if (is_truncated) { int n; proto_item_set_generated(truncated_ti); expert_add_info(pinfo, truncated_ti, &ei_rlc_lte_header_only); /* Show in the info column how long the data would be */ for (n=0; n < s_number_of_extensions; n++) { show_PDU_in_info(pinfo, top_ti, s_lengths[n], (n==0) ? first_includes_start : true, true); offset += s_lengths[n]; } /* Last one */ show_PDU_in_info(pinfo, top_ti, p_rlc_lte_info->pduLength - offset, (s_number_of_extensions == 0) ? first_includes_start : true, last_includes_end); } else { proto_item_set_hidden(truncated_ti); } } /* Call sequence analysis function now */ if (((global_rlc_lte_am_sequence_analysis == SEQUENCE_ANALYSIS_MAC_ONLY) && (p_get_proto_data(wmem_file_scope(), pinfo, proto_mac_lte, 0) != NULL)) || ((global_rlc_lte_am_sequence_analysis == SEQUENCE_ANALYSIS_RLC_ONLY) && (p_get_proto_data(wmem_file_scope(), pinfo, proto_mac_lte, 0) == NULL))) { uint16_t firstSegmentLength; uint16_t lastSegmentOffset = offset; if (s_number_of_extensions >= 1) { int n; for (n=0; n < s_number_of_extensions; n++) { lastSegmentOffset += s_lengths[n]; } firstSegmentLength = s_lengths[0]; } else { firstSegmentLength = tvb_reported_length_remaining(tvb, offset); } seq_anal_state = checkChannelSequenceInfo(pinfo, tvb, p_rlc_lte_info, false, s_number_of_extensions+1, offset, firstSegmentLength, lastSegmentOffset, (uint16_t)sn, first_includes_start, last_includes_end, is_resegmented, tap_info, tree); } if (is_truncated) { return; } /*************************************/ /* Data */ if (!first_includes_start) { reassembly_info = (rlc_channel_reassembly_info *)wmem_map_lookup(reassembly_report_hash, get_report_hash_key((uint16_t)sn, pinfo->num, p_rlc_lte_info, false)); } if (s_number_of_extensions > 0) { /* Show each data segment separately */ int n; for (n=0; n < s_number_of_extensions; n++) { show_PDU_in_tree(pinfo, tree, tvb, offset, s_lengths[n], p_rlc_lte_info, (n==0) ? first_includes_start : true, (n==0) ? reassembly_info : NULL, seq_anal_state); show_PDU_in_info(pinfo, top_ti, s_lengths[n], (n==0) ? first_includes_start : true, true); /* Make sure we don't lose the summary of this SDU */ col_append_str(pinfo->cinfo, COL_INFO, " | "); col_set_fence(pinfo->cinfo, COL_INFO); tvb_ensure_bytes_exist(tvb, offset, s_lengths[n]); offset += s_lengths[n]; } } /* Final data element */ if (tvb_reported_length_remaining(tvb, offset) > 0) { show_PDU_in_tree(pinfo, tree, tvb, offset, tvb_reported_length_remaining(tvb, offset), p_rlc_lte_info, ((s_number_of_extensions == 0) ? first_includes_start : true) && last_includes_end, (s_number_of_extensions == 0) ? reassembly_info : NULL, seq_anal_state); show_PDU_in_info(pinfo, top_ti, (uint16_t)tvb_reported_length_remaining(tvb, offset), (s_number_of_extensions == 0) ? first_includes_start : true, last_includes_end); } else { /* Report that expected data was missing (unless we know it might happen) */ if (!global_rlc_lte_headers_expected) { if (s_number_of_extensions > 0) { expert_add_info(pinfo, am_header_ti, &ei_rlc_lte_am_data_no_data_beyond_extensions); } else { expert_add_info(pinfo, am_header_ti, &ei_rlc_lte_am_data_no_data); } } } } static void report_heur_error(proto_tree *tree, packet_info *pinfo, expert_field *eiindex, tvbuff_t *tvb, int start, int length) { proto_item *ti; proto_tree *subtree; col_set_str(pinfo->cinfo, COL_PROTOCOL, "RLC-LTE"); col_clear(pinfo->cinfo, COL_INFO); ti = proto_tree_add_item(tree, proto_rlc_lte, tvb, 0, -1, ENC_NA); subtree = proto_item_add_subtree(ti, ett_rlc_lte); proto_tree_add_expert(subtree, pinfo, eiindex, tvb, start, length); } /* Heuristic dissector looks for supported framing protocol (see wiki page) */ static bool dissect_rlc_lte_heur(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { int offset = 0; struct rlc_lte_info *p_rlc_lte_info; tvbuff_t *rlc_tvb; uint8_t tag = 0; bool seqNumLengthTagPresent = false; /* Needs to be at least as long as: - the signature string - fixed header bytes - tag for data - at least one byte of RLC PDU payload */ if (tvb_captured_length_remaining(tvb, offset) < (int)(strlen(RLC_LTE_START_STRING)+1+2)) { return false; } /* OK, compare with signature string */ if (tvb_strneql(tvb, offset, RLC_LTE_START_STRING, (int)strlen(RLC_LTE_START_STRING)) != 0) { return false; } offset += (int)strlen(RLC_LTE_START_STRING); /* If redissecting, use previous info struct (if available) */ p_rlc_lte_info = (rlc_lte_info *)p_get_proto_data(wmem_file_scope(), pinfo, proto_rlc_lte, 0); if (p_rlc_lte_info == NULL) { /* Allocate new info struct for this frame */ p_rlc_lte_info = wmem_new0(wmem_file_scope(), struct rlc_lte_info); /* Read fixed fields */ p_rlc_lte_info->rlcMode = tvb_get_uint8(tvb, offset++); if (p_rlc_lte_info->rlcMode == RLC_AM_MODE) { p_rlc_lte_info->sequenceNumberLength = AM_SN_LENGTH_10_BITS; } /* Read optional fields */ while (tag != RLC_LTE_PAYLOAD_TAG) { /* Process next tag */ tag = tvb_get_uint8(tvb, offset++); switch (tag) { case RLC_LTE_SN_LENGTH_TAG: p_rlc_lte_info->sequenceNumberLength = tvb_get_uint8(tvb, offset); offset++; seqNumLengthTagPresent = true; break; case RLC_LTE_DIRECTION_TAG: p_rlc_lte_info->direction = tvb_get_uint8(tvb, offset); offset++; break; case RLC_LTE_PRIORITY_TAG: p_rlc_lte_info->priority = tvb_get_uint8(tvb, offset); offset++; break; case RLC_LTE_UEID_TAG: p_rlc_lte_info->ueid = tvb_get_ntohs(tvb, offset); offset += 2; break; case RLC_LTE_CHANNEL_TYPE_TAG: p_rlc_lte_info->channelType = tvb_get_ntohs(tvb, offset); offset += 2; break; case RLC_LTE_CHANNEL_ID_TAG: p_rlc_lte_info->channelId = tvb_get_ntohs(tvb, offset); offset += 2; break; case RLC_LTE_EXT_LI_FIELD_TAG: p_rlc_lte_info->extendedLiField = true; break; case RLC_LTE_NB_MODE_TAG: p_rlc_lte_info->nbMode = (rlc_lte_nb_mode)tvb_get_uint8(tvb, offset); offset++; break; case RLC_LTE_PAYLOAD_TAG: /* Have reached data, so set payload length and get out of loop */ p_rlc_lte_info->pduLength = tvb_reported_length_remaining(tvb, offset); continue; default: /* It must be a recognised tag */ report_heur_error(tree, pinfo, &ei_rlc_lte_unknown_udp_framing_tag, tvb, offset-1, 1); wmem_free(wmem_file_scope(), p_rlc_lte_info); return true; } } if ((p_rlc_lte_info->rlcMode == RLC_UM_MODE) && (seqNumLengthTagPresent == false)) { /* Conditional field is not present */ report_heur_error(tree, pinfo, &ei_rlc_lte_missing_udp_framing_tag, tvb, 0, offset); wmem_free(wmem_file_scope(), p_rlc_lte_info); return true; } /* Store info in packet */ p_add_proto_data(wmem_file_scope(), pinfo, proto_rlc_lte, 0, p_rlc_lte_info); } else { offset = tvb_reported_length(tvb) - p_rlc_lte_info->pduLength; } /**************************************/ /* OK, now dissect as RLC LTE */ /* Create tvb that starts at actual RLC PDU */ rlc_tvb = tvb_new_subset_remaining(tvb, offset); dissect_rlc_lte_common(rlc_tvb, pinfo, tree, true); return true; } /*****************************/ /* Main dissection function. */ /*****************************/ static int dissect_rlc_lte(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_) { dissect_rlc_lte_common(tvb, pinfo, tree, false); return tvb_captured_length(tvb); } static void dissect_rlc_lte_common(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, bool is_udp_framing) { proto_tree *rlc_lte_tree; proto_tree *context_tree; proto_item *top_ti; proto_item *context_ti; proto_item *ti; proto_item *mode_ti; int offset = 0; struct rlc_lte_info *p_rlc_lte_info; /* Allocate and Zero tap struct */ rlc_3gpp_tap_info *tap_info = wmem_new0(pinfo->pool, rlc_3gpp_tap_info); tap_info->rat = RLC_RAT_LTE; /* Set protocol name */ col_set_str(pinfo->cinfo, COL_PROTOCOL, "RLC-LTE"); /* Create protocol tree. */ top_ti = proto_tree_add_item(tree, proto_rlc_lte, tvb, offset, -1, ENC_NA); rlc_lte_tree = proto_item_add_subtree(top_ti, ett_rlc_lte); /* Look for packet info! */ p_rlc_lte_info = (rlc_lte_info *)p_get_proto_data(wmem_file_scope(), pinfo, proto_rlc_lte, 0); /* Can't dissect anything without it... */ if (p_rlc_lte_info == NULL) { proto_tree_add_expert(rlc_lte_tree, pinfo, &ei_rlc_lte_no_per_frame_info, tvb, offset, -1); return; } /* Clear info column when using UDP framing */ if (is_udp_framing) { col_clear(pinfo->cinfo, COL_INFO); } /*****************************************/ /* Show context information */ /* Create context root */ context_ti = proto_tree_add_string_format(rlc_lte_tree, hf_rlc_lte_context, tvb, offset, 0, "", "Context"); context_tree = proto_item_add_subtree(context_ti, ett_rlc_lte_context); proto_item_set_generated(context_ti); ti = proto_tree_add_uint(context_tree, hf_rlc_lte_context_direction, tvb, 0, 0, p_rlc_lte_info->direction); proto_item_set_generated(ti); mode_ti = proto_tree_add_uint(context_tree, hf_rlc_lte_context_mode, tvb, 0, 0, p_rlc_lte_info->rlcMode); proto_item_set_generated(mode_ti); if (p_rlc_lte_info->ueid != 0) { ti = proto_tree_add_uint(context_tree, hf_rlc_lte_context_ueid, tvb, 0, 0, p_rlc_lte_info->ueid); proto_item_set_generated(ti); } if ((p_rlc_lte_info->priority >= 1) && (p_rlc_lte_info->priority <=16)) { ti = proto_tree_add_uint(context_tree, hf_rlc_lte_context_priority, tvb, 0, 0, p_rlc_lte_info->priority); proto_item_set_generated(ti); } ti = proto_tree_add_uint(context_tree, hf_rlc_lte_context_channel_type, tvb, 0, 0, p_rlc_lte_info->channelType); proto_item_set_generated(ti); if ((p_rlc_lte_info->channelType == CHANNEL_TYPE_SRB) || (p_rlc_lte_info->channelType == CHANNEL_TYPE_DRB) || (p_rlc_lte_info->channelType == CHANNEL_TYPE_MTCH)) { ti = proto_tree_add_uint(context_tree, hf_rlc_lte_context_channel_id, tvb, 0, 0, p_rlc_lte_info->channelId); proto_item_set_generated(ti); } ti = proto_tree_add_uint(context_tree, hf_rlc_lte_context_pdu_length, tvb, 0, 0, p_rlc_lte_info->pduLength); proto_item_set_generated(ti); if (p_rlc_lte_info->rlcMode == RLC_UM_MODE) { ti = proto_tree_add_uint(context_tree, hf_rlc_lte_context_um_sn_length, tvb, 0, 0, p_rlc_lte_info->sequenceNumberLength); proto_item_set_generated(ti); } if (p_rlc_lte_info->rlcMode == RLC_AM_MODE) { if (!p_rlc_lte_info->sequenceNumberLength) { p_rlc_lte_info->sequenceNumberLength = 10; } ti = proto_tree_add_uint(context_tree, hf_rlc_lte_context_am_sn_length, tvb, 0, 0, p_rlc_lte_info->sequenceNumberLength); proto_item_set_generated(ti); } /* Append highlights to top-level item */ if (p_rlc_lte_info->ueid != 0) { proto_item_append_text(top_ti, " UEId=%u", p_rlc_lte_info->ueid); col_append_fstr(pinfo->cinfo, COL_INFO, "UEId=%-4u ", p_rlc_lte_info->ueid); } /* Append context highlights to info column */ write_pdu_label_and_info(top_ti, NULL, pinfo, " [%s] [%s] ", (p_rlc_lte_info->direction == 0) ? "UL" : "DL", val_to_str_const(p_rlc_lte_info->rlcMode, rlc_mode_short_vals, "Unknown")); if (p_rlc_lte_info->channelId == 0) { write_pdu_label_and_info(top_ti, NULL, pinfo, "%s ", val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown")); } else { write_pdu_label_and_info(top_ti, NULL, pinfo, "%s:%-2u", val_to_str_const(p_rlc_lte_info->channelType, rlc_channel_type_vals, "Unknown"), p_rlc_lte_info->channelId); } /* Set context-info parts of tap struct */ tap_info->rlcMode = p_rlc_lte_info->rlcMode; tap_info->direction = p_rlc_lte_info->direction; tap_info->priority = p_rlc_lte_info->priority; tap_info->ueid = p_rlc_lte_info->ueid; tap_info->channelType = p_rlc_lte_info->channelType; tap_info->channelId = p_rlc_lte_info->channelId; tap_info->pduLength = p_rlc_lte_info->pduLength; tap_info->sequenceNumberLength = p_rlc_lte_info->sequenceNumberLength; tap_info->loggedInMACFrame = (p_get_proto_data(wmem_file_scope(), pinfo, proto_mac_lte, 0) != NULL); tap_info->rlc_time = pinfo->abs_ts; /* Reset this count */ s_number_of_extensions = 0; /* Dissect the RLC PDU itself. Format depends upon mode... */ switch (p_rlc_lte_info->rlcMode) { case RLC_TM_MODE: dissect_rlc_lte_tm(tvb, pinfo, rlc_lte_tree, offset, p_rlc_lte_info, top_ti); break; case RLC_UM_MODE: dissect_rlc_lte_um(tvb, pinfo, rlc_lte_tree, offset, p_rlc_lte_info, top_ti, tap_info); break; case RLC_AM_MODE: dissect_rlc_lte_am(tvb, pinfo, rlc_lte_tree, offset, p_rlc_lte_info, top_ti, tap_info); break; case RLC_PREDEF: /* Predefined data (i.e. not containing a valid RLC header */ proto_tree_add_item(rlc_lte_tree, hf_rlc_lte_predefined_pdu, tvb, offset, -1, ENC_NA); write_pdu_label_and_info(top_ti, NULL, pinfo, " [%u-bytes]", tvb_reported_length_remaining(tvb, offset)); break; default: /* Error - unrecognised mode */ expert_add_info_format(pinfo, mode_ti, &ei_rlc_lte_context_mode, "Unrecognised RLC Mode set (%u)", p_rlc_lte_info->rlcMode); break; } /* Queue tap info */ tap_queue_packet(rlc_lte_tap, pinfo, tap_info); } /* Configure number of PDCP SN bits to use for DRB channels */ void set_rlc_lte_drb_pdcp_seqnum_length(packet_info *pinfo, uint16_t ueid, uint8_t drbid, uint8_t userplane_seqnum_length) { wmem_tree_key_t key[3]; uint32_t id; rlc_ue_parameters *params; if (PINFO_FD_VISITED(pinfo)) { return; } id = (drbid << 16) | ueid; key[0].length = 1; key[0].key = &id; key[1].length = 1; key[1].key = &pinfo->num; key[2].length = 0; key[2].key = NULL; params = (rlc_ue_parameters *)wmem_tree_lookup32_array_le(ue_parameters_tree, key); if (params && (params->id != id)) { params = NULL; } if (params == NULL) { params = (rlc_ue_parameters *)wmem_new(wmem_file_scope(), rlc_ue_parameters); params->id = id; params->ext_li_field = NO_EXT_LI; wmem_tree_insert32_array(ue_parameters_tree, key, (void *)params); } params->pdcp_sn_bits = userplane_seqnum_length; } /*Configure LI field for AM DRB channels */ void set_rlc_lte_drb_li_field(packet_info *pinfo, uint16_t ueid, uint8_t drbid, bool ul_ext_li_field, bool dl_ext_li_field) { wmem_tree_key_t key[3]; uint32_t id; rlc_ue_parameters *params; if (PINFO_FD_VISITED(pinfo)) { return; } id = (drbid << 16) | ueid; key[0].length = 1; key[0].key = &id; key[1].length = 1; key[1].key = &pinfo->num; key[2].length = 0; key[2].key = NULL; params = (rlc_ue_parameters *)wmem_tree_lookup32_array_le(ue_parameters_tree, key); if (params && (params->id != id)) { params = NULL; } if (params == NULL) { params = (rlc_ue_parameters *)wmem_new(wmem_file_scope(), rlc_ue_parameters); params->id = id; params->pdcp_sn_bits = 12; wmem_tree_insert32_array(ue_parameters_tree, key, (void *)params); } params->ext_li_field = ul_ext_li_field ? UL_EXT_LI : NO_EXT_LI; params->ext_li_field |= dl_ext_li_field ? DL_EXT_LI : NO_EXT_LI; } void rlc_lte_reset_ue_bearers(packet_info *pinfo, uint16_t ueid) { if (PINFO_FD_VISITED(pinfo)) { return; } /* Need to reset current state of any entries in sequence_analysis_channel_hash */ channel_hash_key channel_key; channel_sequence_analysis_status *p_channel_status; channel_key.ueId = ueid; /* SRBs (1-2, both directions) */ channel_key.channelType = CHANNEL_TYPE_SRB; for (uint32_t channelId=1; channelId <= 2; ++channelId) { for (uint32_t direction=0; direction <=1; ++direction) { /* Update key */ channel_key.channelId = channelId; channel_key.direction = direction; /* Lookup existing channel status */ p_channel_status = (channel_sequence_analysis_status*)wmem_map_lookup(sequence_analysis_channel_hash, &channel_key); if (p_channel_status) { /* Reset if already exists */ p_channel_status->previousSequenceNumber = -1; p_channel_status->previousFrameNum = 0; p_channel_status->previousSegmentIncomplete = 0; } } } /* DRBs (1-32, both directions) */ channel_key.channelType = CHANNEL_TYPE_DRB; for (uint32_t channelId=1; channelId <= 32; ++channelId) { for (uint32_t direction=0; direction <=1; ++direction) { /* Update key */ channel_key.channelId = channelId; channel_key.direction = direction; /* Lookup existing channel status */ p_channel_status = (channel_sequence_analysis_status*)wmem_map_lookup(sequence_analysis_channel_hash, &channel_key); if (p_channel_status) { /* Reset if already exists */ p_channel_status->previousSequenceNumber = -1; p_channel_status->previousFrameNum = 0; p_channel_status->previousSegmentIncomplete = 0; } } } } void proto_register_rlc_lte(void) { static hf_register_info hf[] = { /**********************************/ /* Items for decoding context */ { &hf_rlc_lte_context, { "Context", "rlc-lte.context", FT_STRING, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_rlc_lte_context_mode, { "RLC Mode", "rlc-lte.mode", FT_UINT8, BASE_DEC, VALS(rlc_mode_vals), 0x0, NULL, HFILL } }, { &hf_rlc_lte_context_direction, { "Direction", "rlc-lte.direction", FT_UINT8, BASE_DEC, VALS(direction_vals), 0x0, "Direction of message", HFILL } }, { &hf_rlc_lte_context_priority, { "Priority", "rlc-lte.priority", FT_UINT8, BASE_DEC, 0, 0x0, NULL, HFILL } }, { &hf_rlc_lte_context_ueid, { "UEId", "rlc-lte.ueid", FT_UINT16, BASE_DEC, 0, 0x0, "User Equipment Identifier associated with message", HFILL } }, { &hf_rlc_lte_context_channel_type, { "Channel Type", "rlc-lte.channel-type", FT_UINT16, BASE_DEC, VALS(rlc_channel_type_vals), 0x0, "Channel Type associated with message", HFILL } }, { &hf_rlc_lte_context_channel_id, { "Channel ID", "rlc-lte.channel-id", FT_UINT16, BASE_DEC, 0, 0x0, "Channel ID associated with message", HFILL } }, { &hf_rlc_lte_context_pdu_length, { "PDU Length", "rlc-lte.pdu-length", FT_UINT16, BASE_DEC, 0, 0x0, "Length of PDU (in bytes)", HFILL } }, { &hf_rlc_lte_context_um_sn_length, { "UM Sequence number length", "rlc-lte.um-seqnum-length", FT_UINT8, BASE_DEC, 0, 0x0, "Length of UM sequence number in bits", HFILL } }, { &hf_rlc_lte_context_am_sn_length, { "AM Sequence number length", "rlc-lte.am-seqnum-length", FT_UINT8, BASE_DEC, 0, 0x0, "Length of AM sequence number in bits", HFILL } }, /* Transparent mode fields */ { &hf_rlc_lte_tm, { "TM", "rlc-lte.tm", FT_STRING, BASE_NONE, NULL, 0x0, "Transparent Mode", HFILL } }, { &hf_rlc_lte_tm_data, { "TM Data", "rlc-lte.tm.data", FT_BYTES, BASE_NONE, 0, 0x0, "Transparent Mode Data", HFILL } }, /* Unacknowledged mode fields */ { &hf_rlc_lte_um, { "UM", "rlc-lte.um", FT_STRING, BASE_NONE, NULL, 0x0, "Unacknowledged Mode", HFILL } }, { &hf_rlc_lte_um_header, { "UM Header", "rlc-lte.um.header", FT_STRING, BASE_NONE, NULL, 0x0, "Unacknowledged Mode Header", HFILL } }, { &hf_rlc_lte_um_fi, { "Framing Info", "rlc-lte.um.fi", FT_UINT8, BASE_HEX, VALS(framing_info_vals), 0x0, NULL, HFILL } }, { &hf_rlc_lte_um_fixed_e, { "Extension", "rlc-lte.um.fixed.e", FT_UINT8, BASE_HEX, VALS(fixed_extension_vals), 0x0, "Extension in fixed part of UM header", HFILL } }, { &hf_rlc_lte_um_sn, { "Sequence number", "rlc-lte.um.sn", FT_UINT8, BASE_DEC, 0, 0x0, "Unacknowledged Mode Sequence Number", HFILL } }, { &hf_rlc_lte_um_fixed_reserved, { "Reserved", "rlc-lte.um.reserved", FT_UINT8, BASE_DEC, 0, 0xe0, "Unacknowledged Mode Fixed header reserved bits", HFILL } }, { &hf_rlc_lte_um_data, { "UM Data", "rlc-lte.um.data", FT_BYTES, BASE_NONE, 0, 0x0, "Unacknowledged Mode Data", HFILL } }, { &hf_rlc_lte_extension_part, { "Extension Part", "rlc-lte.extension-part", FT_STRING, BASE_NONE, 0, 0x0, NULL, HFILL } }, { &hf_rlc_lte_extension_e, { "Extension", "rlc-lte.extension.e", FT_UINT8, BASE_HEX, VALS(extension_extension_vals), 0x0, "Extension in extended part of the header", HFILL } }, { &hf_rlc_lte_extension_li, { "Length Indicator", "rlc-lte.extension.li", FT_UINT16, BASE_DEC, 0, 0x0, NULL, HFILL } }, { &hf_rlc_lte_extension_padding, { "Padding", "rlc-lte.extension.padding", FT_UINT8, BASE_HEX, 0, 0x0f, "Extension header padding", HFILL } }, /* Acknowledged mode fields */ { &hf_rlc_lte_am, { "AM", "rlc-lte.am", FT_STRING, BASE_NONE, NULL, 0x0, "Acknowledged Mode", HFILL } }, { &hf_rlc_lte_am_header, { "AM Header", "rlc-lte.am.header", FT_STRING, BASE_NONE, NULL, 0x0, "Acknowledged Mode Header", HFILL } }, { &hf_rlc_lte_am_data_control, { "Frame type", "rlc-lte.am.frame-type", FT_BOOLEAN, 8, TFS(&tfs_data_pdu_control_pdu), 0x80, "AM Frame Type (Control or Data)", HFILL } }, { &hf_rlc_lte_am_rf, { "Re-segmentation Flag", "rlc-lte.am.rf", FT_UINT8, BASE_HEX, VALS(resegmentation_flag_vals), 0x40, "AM Re-segmentation Flag", HFILL } }, { &hf_rlc_lte_am_p, { "Polling Bit", "rlc-lte.am.p", FT_UINT8, BASE_HEX, VALS(polling_bit_vals), 0x20, NULL, HFILL } }, { &hf_rlc_lte_am_fi, { "Framing Info", "rlc-lte.am.fi", FT_UINT8, BASE_HEX, VALS(framing_info_vals), 0x18, "AM Framing Info", HFILL } }, { &hf_rlc_lte_am_fixed_e, { "Extension", "rlc-lte.am.fixed.e", FT_UINT8, BASE_HEX, VALS(fixed_extension_vals), 0x04, "Fixed Extension Bit", HFILL } }, { &hf_rlc_lte_am_fixed_sn, { "Sequence Number", "rlc-lte.am.fixed.sn", FT_UINT16, BASE_DEC, 0, 0x03ff, "AM Fixed Sequence Number", HFILL } }, { &hf_rlc_lte_am_fixed_reserved, { "Reserved", "rlc-lte.am.reserved", FT_UINT8, BASE_DEC, 0, 0x03, "Acknowledged Mode Fixed header reserved bits", HFILL } }, { &hf_rlc_lte_am_segment_lsf16, { "Last Segment Flag", "rlc-lte.am.segment.lsf", FT_UINT8, BASE_HEX, VALS(lsf_vals), 0x02, NULL, HFILL } }, { &hf_rlc_lte_am_fixed_reserved2, { "Reserved", "rlc-lte.am.reserved", FT_UINT8, BASE_DEC, 0, 0x01, "Acknowledged Mode Fixed header reserved bit", HFILL } }, { &hf_rlc_lte_am_fixed_sn16, { "Sequence Number", "rlc-lte.am.fixed.sn", FT_UINT16, BASE_DEC, 0, 0x0, "AM Fixed Sequence Number", HFILL } }, { &hf_rlc_lte_am_segment_lsf, { "Last Segment Flag", "rlc-lte.am.segment.lsf", FT_UINT8, BASE_HEX, VALS(lsf_vals), 0x80, NULL, HFILL } }, { &hf_rlc_lte_am_segment_so, { "Segment Offset", "rlc-lte.am.segment.offset", FT_UINT16, BASE_DEC, 0, 0x7fff, NULL, HFILL } }, { &hf_rlc_lte_am_segment_so16, { "Segment Offset", "rlc-lte.am.segment.offset", FT_UINT16, BASE_DEC, 0, 0x0, NULL, HFILL } }, { &hf_rlc_lte_am_data, { "AM Data", "rlc-lte.am.data", FT_BYTES, BASE_NONE, 0, 0x0, "Acknowledged Mode Data", HFILL } }, { &hf_rlc_lte_am_cpt, { "Control PDU Type", "rlc-lte.am.cpt", FT_UINT8, BASE_HEX, VALS(control_pdu_type_vals), 0x70, "AM Control PDU Type", HFILL } }, { &hf_rlc_lte_am_ack_sn, { "ACK Sequence Number", "rlc-lte.am.ack-sn", FT_UINT16, BASE_DEC, 0, 0x0, "Sequence Number we expect to receive next", HFILL } }, { &hf_rlc_lte_am_e1, { "Extension bit 1", "rlc-lte.am.e1", FT_UINT8, BASE_HEX, VALS(am_e1_vals), 0x0, NULL, HFILL } }, { &hf_rlc_lte_am_e2, { "Extension bit 2", "rlc-lte.am.e2", FT_UINT8, BASE_HEX, VALS(am_e2_vals), 0x0, NULL, HFILL } }, { &hf_rlc_lte_am_nacks, { "Number of NACKs", "rlc-lte.am.nacks", FT_UINT16, BASE_DEC, 0, 0x0, "Number of NACKs in this status PDU", HFILL } }, { &hf_rlc_lte_am_nack_sn, { "NACK Sequence Number", "rlc-lte.am.nack-sn", FT_UINT16, BASE_DEC, 0, 0x0, "Negative Acknowledgement Sequence Number", HFILL } }, { &hf_rlc_lte_am_so_start, { "SO Start", "rlc-lte.am.so-start", FT_UINT16, BASE_DEC, 0, 0x0, "Segment Offset Start byte index", HFILL } }, { &hf_rlc_lte_am_so_end, { "SO End", "rlc-lte.am.so-end", FT_UINT16, BASE_DEC, 0, 0x0, "Segment Offset End byte index", HFILL } }, { &hf_rlc_lte_predefined_pdu, { "Predefined data", "rlc-lte.predefined-data", FT_BYTES, BASE_NONE, 0, 0x0, "Predefined test data", HFILL } }, /* Sequence analysis fields */ { &hf_rlc_lte_sequence_analysis, { "Sequence Analysis", "rlc-lte.sequence-analysis", FT_STRING, BASE_NONE, 0, 0x0, NULL, HFILL } }, { &hf_rlc_lte_sequence_analysis_ok, { "OK", "rlc-lte.sequence-analysis.ok", FT_BOOLEAN, BASE_NONE, 0, 0x0, NULL, HFILL } }, { &hf_rlc_lte_sequence_analysis_previous_frame, { "Previous frame for channel", "rlc-lte.sequence-analysis.previous-frame", FT_FRAMENUM, BASE_NONE, 0, 0x0, NULL, HFILL } }, { &hf_rlc_lte_sequence_analysis_next_frame, { "Next frame for channel", "rlc-lte.sequence-analysis.next-frame", FT_FRAMENUM, BASE_NONE, 0, 0x0, NULL, HFILL } }, { &hf_rlc_lte_sequence_analysis_expected_sn, { "Expected SN", "rlc-lte.sequence-analysis.expected-sn", FT_UINT16, BASE_DEC, 0, 0x0, NULL, HFILL } }, { &hf_rlc_lte_sequence_analysis_framing_info_correct, { "Frame info continued correctly", "rlc-lte.sequence-analysis.framing-info-correct", FT_BOOLEAN, BASE_NONE, 0, 0x0, NULL, HFILL } }, { &hf_rlc_lte_sequence_analysis_mac_retx, { "Frame retransmitted by MAC", "rlc-lte.sequence-analysis.mac-retx", FT_BOOLEAN, BASE_NONE, 0, 0x0, NULL, HFILL } }, { &hf_rlc_lte_sequence_analysis_retx, { "Retransmitted frame", "rlc-lte.sequence-analysis.retx", FT_BOOLEAN, BASE_NONE, 0, 0x0, NULL, HFILL } }, { &hf_rlc_lte_sequence_analysis_skipped, { "Skipped frames", "rlc-lte.sequence-analysis.skipped-frames", FT_BOOLEAN, BASE_NONE, 0, 0x0, NULL, HFILL } }, { &hf_rlc_lte_sequence_analysis_repeated, { "Repeated frame", "rlc-lte.sequence-analysis.repeated-frame", FT_BOOLEAN, BASE_NONE, 0, 0x0, NULL, HFILL } }, { &hf_rlc_lte_sequence_analysis_repeated_nack, { "Repeated NACK", "rlc-lte.sequence-analysis.repeated-nack", FT_UINT16, BASE_DEC, 0, 0x0, NULL, HFILL } }, { &hf_rlc_lte_sequence_analysis_repeated_nack_original_frame, { "Frame with previous status PDU", "rlc-lte.sequence-analysis.repeated-nack.original-frame", FT_FRAMENUM, BASE_NONE, FRAMENUM_TYPE(FT_FRAMENUM_DUP_ACK), 0x0, NULL, HFILL } }, { &hf_rlc_lte_sequence_analysis_ack_out_of_range, { "Out of range ACK", "rlc-lte.sequence-analysis.ack-out-of-range", FT_BOOLEAN, BASE_NONE, 0, 0x0, NULL, HFILL } }, { &hf_rlc_lte_sequence_analysis_ack_out_of_range_opposite_frame, { "Frame with most recent SN", "rlc-lte.sequence-analysis.ack-out-of-range.last-sn-frame", FT_FRAMENUM, BASE_NONE, NULL, 0x0, NULL, HFILL } }, /* Reassembly fields */ { &hf_rlc_lte_reassembly_source, { "Reassembly Source", "rlc-lte.reassembly-info", FT_STRING, BASE_NONE, 0, 0x0, NULL, HFILL } }, { &hf_rlc_lte_reassembly_source_number_of_segments, { "Number of segments", "rlc-lte.reassembly-info.number-of-segments", FT_UINT16, BASE_DEC, 0, 0x0, NULL, HFILL } }, { &hf_rlc_lte_reassembly_source_total_length, { "Total length", "rlc-lte.reassembly-info.total-length", FT_UINT16, BASE_DEC, 0, 0x0, NULL, HFILL } }, { &hf_rlc_lte_reassembly_source_segment, { "Segment", "rlc-lte.reassembly-info.segment", FT_NONE, BASE_NONE, 0, 0x0, NULL, HFILL } }, { &hf_rlc_lte_reassembly_source_segment_sn, { "SN", "rlc-lte.reassembly-info.segment.sn", FT_UINT16, BASE_DEC, 0, 0x0, NULL, HFILL } }, { &hf_rlc_lte_reassembly_source_segment_framenum, { "Frame", "rlc-lte.reassembly-info.segment.frame", FT_FRAMENUM, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_rlc_lte_reassembly_source_segment_length, { "Length", "rlc-lte.reassembly-info.segment.length", FT_UINT32, BASE_DEC, 0, 0x0, NULL, HFILL } }, { &hf_rlc_lte_header_only, { "RLC PDU Header only", "rlc-lte.header-only", FT_UINT8, BASE_DEC, VALS(header_only_vals), 0x0, NULL, HFILL } }, }; static int *ett[] = { &ett_rlc_lte, &ett_rlc_lte_context, &ett_rlc_lte_um_header, &ett_rlc_lte_am_header, &ett_rlc_lte_extension_part, &ett_rlc_lte_sequence_analysis, &ett_rlc_lte_reassembly_source, &ett_rlc_lte_reassembly_source_segment }; static ei_register_info ei[] = { { &ei_rlc_lte_sequence_analysis_last_segment_not_continued, { "rlc-lte.sequence-analysis.last-segment-not-continued", PI_SEQUENCE, PI_WARN, "Last segment of previous PDU was not continued for UE", EXPFILL }}, { &ei_rlc_lte_sequence_analysis_last_segment_complete, { "rlc-lte.sequence-analysis.last-segment-complete", PI_SEQUENCE, PI_WARN, "Last segment of previous PDU was complete, but new segment was not started on UE", EXPFILL }}, { &ei_rlc_lte_sequence_analysis_mac_retx, { "rlc-lte.sequence-analysis.mac-retx.expert", PI_SEQUENCE, PI_WARN, "AM Frame retransmitted due to MAC retx!", EXPFILL }}, { &ei_rlc_lte_sequence_analysis_retx, { "rlc-lte.sequence-analysis.retx.expert", PI_SEQUENCE, PI_WARN, "AM Frame retransmitted most likely in response to NACK", EXPFILL }}, { &ei_rlc_lte_sequence_analysis_repeated, { "rlc-lte.sequence-analysis.repeated-frame.expert", PI_SEQUENCE, PI_WARN, "AM SN Repeated - probably because didn't receive Status PDU?", EXPFILL }}, { &ei_rlc_lte_am_sn_missing, { "rlc-lte.sequence-analysis.am-sn.missing", PI_SEQUENCE, PI_WARN, "AM SNs missing", EXPFILL }}, { &ei_rlc_lte_sequence_analysis_ack_out_of_range_opposite_frame, { "rlc-lte.sequence-analysis.ack-out-of-range.last-sn-frame.expert", PI_SEQUENCE, PI_ERROR, "AM ACK for SN - but last received SN in other direction is X", EXPFILL }}, { &ei_rlc_lte_um_sn_missing, { "rlc-lte.sequence-analysis.um-sn.missing", PI_SEQUENCE, PI_WARN, "UM SNs missing", EXPFILL }}, { &ei_rlc_lte_um_sn_repeated, { "rlc-lte.sequence-analysis.um-sn.repeated", PI_SEQUENCE, PI_WARN, "UM SN repeated", EXPFILL }}, { &ei_rlc_lte_wrong_sequence_number, { "rlc-lte.wrong-sequence-number", PI_SEQUENCE, PI_WARN, "Wrong Sequence Number", EXPFILL }}, { &ei_rlc_lte_sequence_analysis_repeated_nack, { "rlc-lte.sequence-analysis.repeated-nack.expert", PI_SEQUENCE, PI_ERROR, "Same SN NACKd on successive Status PDUs", EXPFILL }}, { &ei_rlc_lte_reserved_bits_not_zero, { "rlc-lte.reserved-bits-not-zero", PI_MALFORMED, PI_ERROR, "Reserved bits not zero", EXPFILL }}, { &ei_rlc_lte_um_sn, { "rlc-lte.um.sn.invalid", PI_MALFORMED, PI_ERROR, "Invalid sequence number length", EXPFILL }}, { &ei_rlc_lte_header_only, { "rlc-lte.header-only.expert", PI_SEQUENCE, PI_NOTE, "RLC PDU SDUs have been omitted", EXPFILL }}, { &ei_rlc_lte_am_cpt, { "rlc-lte.am.cpt.invalid", PI_MALFORMED, PI_ERROR, "RLC Control frame type not handled", EXPFILL }}, { &ei_rlc_lte_am_nack_sn_ack_same, { "rlc-lte.am.nack-sn.ack-same", PI_MALFORMED, PI_ERROR, "Status PDU shouldn't ACK and NACK the same sequence number", EXPFILL }}, { &ei_rlc_lte_am_nack_sn_ahead_ack, { "rlc-lte.am.nack-sn.ahead-ack", PI_MALFORMED, PI_ERROR, "NACK must not be ahead of ACK in status PDU", EXPFILL }}, { &ei_rlc_lte_am_nack_sn_partial, { "rlc-lte.am.nack-sn.partial", PI_SEQUENCE, PI_WARN, "Status PDU reports NACK (partial)", EXPFILL }}, { &ei_rlc_lte_am_nack_sn, { "rlc-lte.am.nack-sn.expert", PI_SEQUENCE, PI_WARN, "Status PDU reports NACK", EXPFILL }}, { &ei_rlc_lte_bytes_after_status_pdu_complete, { "rlc-lte.bytes-after-status-pdu-complete", PI_MALFORMED, PI_ERROR, "bytes remaining after Status PDU complete", EXPFILL }}, { &ei_rlc_lte_am_data_no_data_beyond_extensions, { "rlc-lte.am-data.no-data-beyond-extensions", PI_MALFORMED, PI_ERROR, "AM data PDU doesn't contain any data beyond extensions", EXPFILL }}, { &ei_rlc_lte_am_data_no_data, { "rlc-lte.am-data.no-data", PI_MALFORMED, PI_ERROR, "AM data PDU doesn't contain any data", EXPFILL }}, { &ei_rlc_lte_context_mode, { "rlc-lte.mode.invalid", PI_MALFORMED, PI_ERROR, "Unrecognised RLC Mode set", EXPFILL }}, { &ei_rlc_lte_no_per_frame_info, { "rlc-lte.no_per_frame_info", PI_UNDECODED, PI_ERROR, "Can't dissect LTE RLC frame because no per-frame info was attached!", EXPFILL }}, { &ei_rlc_lte_unknown_udp_framing_tag, { "rlc-lte.unknown-udp-framing-tag", PI_UNDECODED, PI_WARN, "Unknown UDP framing tag, aborting dissection", EXPFILL }}, { &ei_rlc_lte_missing_udp_framing_tag, { "rlc-lte.missing-udp-framing-tag", PI_UNDECODED, PI_WARN, "Missing UDP framing conditional tag, aborting dissection", EXPFILL }} }; static const enum_val_t sequence_analysis_vals[] = { {"no-analysis", "No-Analysis", false}, {"mac-only", "Only-MAC-frames", SEQUENCE_ANALYSIS_MAC_ONLY}, {"rlc-only", "Only-RLC-frames", SEQUENCE_ANALYSIS_RLC_ONLY}, {NULL, NULL, -1} }; module_t *rlc_lte_module; expert_module_t* expert_rlc_lte; /* Register protocol. */ proto_rlc_lte = proto_register_protocol("RLC-LTE", "RLC-LTE", "rlc-lte"); proto_register_field_array(proto_rlc_lte, hf, array_length(hf)); proto_register_subtree_array(ett, array_length(ett)); expert_rlc_lte = expert_register_protocol(proto_rlc_lte); expert_register_field_array(expert_rlc_lte, ei, array_length(ei)); /* Allow other dissectors to find this one by name. */ register_dissector("rlc-lte", dissect_rlc_lte, proto_rlc_lte); /* Register the tap name */ rlc_lte_tap = register_tap("rlc-3gpp"); /* Preferences */ rlc_lte_module = prefs_register_protocol(proto_rlc_lte, NULL); prefs_register_enum_preference(rlc_lte_module, "do_sequence_analysis_am", "Do sequence analysis for AM channels", "Attempt to keep track of PDUs for AM channels, and point out problems", &global_rlc_lte_am_sequence_analysis, sequence_analysis_vals, false); prefs_register_enum_preference(rlc_lte_module, "do_sequence_analysis", "Do sequence analysis for UM channels", "Attempt to keep track of PDUs for UM channels, and point out problems", &global_rlc_lte_um_sequence_analysis, sequence_analysis_vals, false); prefs_register_bool_preference(rlc_lte_module, "call_pdcp_for_srb", "Call PDCP dissector for SRB PDUs", "Call PDCP dissector for signalling PDUs. Note that without reassembly, it can" "only be called for complete PDUs (i.e. not segmented over RLC)", &global_rlc_lte_call_pdcp_for_srb); prefs_register_enum_preference(rlc_lte_module, "call_pdcp_for_drb", "Call PDCP dissector for DRB PDUs", "Call PDCP dissector for user-plane PDUs. Note that without reassembly, it can" "only be called for complete PDUs (i.e. not segmented over RLC)", &global_rlc_lte_call_pdcp_for_drb, pdcp_drb_col_vals, false); prefs_register_bool_preference(rlc_lte_module, "call_rrc_for_ccch", "Call RRC dissector for CCCH PDUs", "Call RRC dissector for CCCH PDUs", &global_rlc_lte_call_rrc_for_ccch); prefs_register_bool_preference(rlc_lte_module, "call_rrc_for_mcch", "Call RRC dissector for MCCH PDUs", "Call RRC dissector for MCCH PDUs Note that without reassembly, it can" "only be called for complete PDUs (i.e. not segmented over RLC)", &global_rlc_lte_call_rrc_for_mcch); prefs_register_bool_preference(rlc_lte_module, "call_ip_for_mtch", "Call IP dissector for MTCH PDUs", "Call ip dissector for MTCH PDUs Note that without reassembly, it can" "only be called for complete PDUs (i.e. not segmented over RLC)", &global_rlc_lte_call_ip_for_mtch); prefs_register_obsolete_preference(rlc_lte_module, "heuristic_rlc_lte_over_udp"); prefs_register_bool_preference(rlc_lte_module, "header_only_mode", "May see RLC headers only", "When enabled, if data is not present, don't report as an error, but instead " "add expert info to indicate that headers were omitted", &global_rlc_lte_headers_expected); prefs_register_bool_preference(rlc_lte_module, "reassembly", "Attempt SDU reassembly", "When enabled, attempts to re-assemble upper-layer SDUs that are split over " "more than one RLC PDU. Note: does not currently support out-of-order or " "re-segmentation. N.B. sequence analysis must also be turned on in order " "for reassembly to work", &global_rlc_lte_reassembly); ue_parameters_tree = wmem_tree_new_autoreset(wmem_epan_scope(), wmem_file_scope()); sequence_analysis_channel_hash = wmem_map_new_autoreset(wmem_epan_scope(), wmem_file_scope(), rlc_channel_hash_func, rlc_channel_equal); sequence_analysis_report_hash = wmem_map_new_autoreset(wmem_epan_scope(), wmem_file_scope(), rlc_result_hash_func, rlc_result_hash_equal); repeated_nack_channel_hash = wmem_map_new_autoreset(wmem_epan_scope(), wmem_file_scope(), rlc_channel_hash_func, rlc_channel_equal); repeated_nack_report_hash = wmem_map_new_autoreset(wmem_epan_scope(), wmem_file_scope(), rlc_result_hash_func, rlc_result_hash_equal); reassembly_report_hash = wmem_map_new_autoreset(wmem_epan_scope(), wmem_file_scope(), rlc_result_hash_func, rlc_result_hash_equal); } void proto_reg_handoff_rlc_lte(void) { /* Add as a heuristic UDP dissector */ heur_dissector_add("udp", dissect_rlc_lte_heur, "RLC-LTE over UDP", "rlc_lte_udp", proto_rlc_lte, HEURISTIC_DISABLE); pdcp_lte_handle = find_dissector_add_dependency("pdcp-lte", proto_rlc_lte); ip_handle = find_dissector_add_dependency("ip", proto_rlc_lte); lte_rrc_mcch = find_dissector_add_dependency("lte_rrc.mcch", proto_rlc_lte); lte_rrc_ul_ccch = find_dissector_add_dependency("lte_rrc.ul_ccch", proto_rlc_lte); lte_rrc_dl_ccch = find_dissector_add_dependency("lte_rrc.dl_ccch", proto_rlc_lte); lte_rrc_bcch_bch = find_dissector_add_dependency("lte_rrc.bcch_bch", proto_rlc_lte); lte_rrc_bcch_dl_sch = find_dissector_add_dependency("lte_rrc.bcch_dl_sch", proto_rlc_lte); lte_rrc_pcch = find_dissector_add_dependency("lte_rrc.pcch", proto_rlc_lte); lte_rrc_ul_ccch_nb = find_dissector_add_dependency("lte_rrc.ul_ccch.nb", proto_rlc_lte); lte_rrc_dl_ccch_nb = find_dissector_add_dependency("lte_rrc.dl_ccch.nb", proto_rlc_lte); lte_rrc_bcch_bch_nb = find_dissector_add_dependency("lte_rrc.bcch_bch.nb", proto_rlc_lte); lte_rrc_bcch_dl_sch_nb = find_dissector_add_dependency("lte_rrc.bcch_dl_sch.nb", proto_rlc_lte); lte_rrc_pcch_nb = find_dissector_add_dependency("lte_rrc.pcch.nb", proto_rlc_lte); } /* * Editor modelines - https://www.wireshark.org/tools/modelines.html * * Local variables: * c-basic-offset: 4 * tab-width: 8 * indent-tabs-mode: nil * End: * * vi: set shiftwidth=4 tabstop=8 expandtab: * :indentSize=4:tabSize=8:noTabs=true: */