/* Routines for LTE PDCP * * Martin Mathieson * * $Id$ * * Wireshark - Network traffic analyzer * By Gerald Combs * Copyright 1998 Gerald Combs * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "config.h" #include #include #include #include #include #include #include #include "packet-rlc-lte.h" #include "packet-pdcp-lte.h" /* Described in: * 3GPP TS 36.323 Evolved Universal Terrestrial Radio Access (E-UTRA) * Packet Data Convergence Protocol (PDCP) specification v11.0.0 */ /* TODO: - Support for deciphering - Next step would be to maintain HFN for COUNT input - Verify MAC authentication bytes - Add Relay Node user plane data PDU dissection */ /* Initialize the protocol and registered fields. */ int proto_pdcp_lte = -1; extern int proto_rlc_lte; /* Configuration (info known outside of PDU) */ static int hf_pdcp_lte_configuration = -1; static int hf_pdcp_lte_direction = -1; static int hf_pdcp_lte_ueid = -1; static int hf_pdcp_lte_channel_type = -1; static int hf_pdcp_lte_channel_id = -1; static int hf_pdcp_lte_rohc_compression = -1; static int hf_pdcp_lte_rohc_mode = -1; static int hf_pdcp_lte_rohc_rnd = -1; static int hf_pdcp_lte_rohc_udp_checksum_present = -1; static int hf_pdcp_lte_rohc_profile = -1; static int hf_pdcp_lte_no_header_pdu = -1; static int hf_pdcp_lte_plane = -1; static int hf_pdcp_lte_seqnum_length = -1; static int hf_pdcp_lte_cid_inclusion_info = -1; static int hf_pdcp_lte_large_cid_present = -1; /* PDCP header fields */ static int hf_pdcp_lte_control_plane_reserved = -1; static int hf_pdcp_lte_seq_num_5 = -1; static int hf_pdcp_lte_seq_num_7 = -1; static int hf_pdcp_lte_reserved3 = -1; static int hf_pdcp_lte_seq_num_12 = -1; static int hf_pdcp_lte_seq_num_15 = -1; static int hf_pdcp_lte_signalling_data = -1; static int hf_pdcp_lte_mac = -1; static int hf_pdcp_lte_data_control = -1; static int hf_pdcp_lte_user_plane_data = -1; static int hf_pdcp_lte_control_pdu_type = -1; static int hf_pdcp_lte_fms = -1; static int hf_pdcp_lte_reserved4 = -1; static int hf_pdcp_lte_fms2 = -1; static int hf_pdcp_lte_bitmap = -1; /* Sequence Analysis */ static int hf_pdcp_lte_sequence_analysis = -1; static int hf_pdcp_lte_sequence_analysis_ok = -1; static int hf_pdcp_lte_sequence_analysis_previous_frame = -1; static int hf_pdcp_lte_sequence_analysis_next_frame = -1; static int hf_pdcp_lte_sequence_analysis_expected_sn = -1; static int hf_pdcp_lte_sequence_analysis_repeated = -1; static int hf_pdcp_lte_sequence_analysis_skipped = -1; /* Security Settings */ static int hf_pdcp_lte_security = -1; static int hf_pdcp_lte_security_setup_frame = -1; static int hf_pdcp_lte_security_integrity_algorithm = -1; static int hf_pdcp_lte_security_ciphering_algorithm = -1; /* Protocol subtree. */ static int ett_pdcp = -1; static int ett_pdcp_configuration = -1; static int ett_pdcp_packet = -1; static int ett_pdcp_lte_sequence_analysis = -1; static int ett_pdcp_report_bitmap = -1; static int ett_pdcp_security = -1; static expert_field ei_pdcp_lte_sequence_analysis_wrong_sequence_number = EI_INIT; static expert_field ei_pdcp_lte_reserved_bits_not_zero = EI_INIT; static expert_field ei_pdcp_lte_sequence_analysis_sn_repeated = EI_INIT; static expert_field ei_pdcp_lte_sequence_analysis_sn_missing = EI_INIT; static const value_string direction_vals[] = { { DIRECTION_UPLINK, "Uplink"}, { DIRECTION_DOWNLINK, "Downlink"}, { 0, NULL } }; static const value_string pdcp_plane_vals[] = { { SIGNALING_PLANE, "Signalling" }, { USER_PLANE, "User" }, { 0, NULL } }; static const value_string logical_channel_vals[] = { { Channel_DCCH, "DCCH"}, { Channel_BCCH, "BCCH"}, { Channel_CCCH, "CCCH"}, { Channel_PCCH, "PCCH"}, { 0, NULL} }; static const value_string rohc_mode_vals[] = { { UNIDIRECTIONAL, "Unidirectional" }, { OPTIMISTIC_BIDIRECTIONAL, "Optimistic Bidirectional" }, { RELIABLE_BIDIRECTIONAL, "Reliable Bidirectional" }, { 0, NULL } }; /* Values taken from: http://www.iana.org/assignments/rohc-pro-ids/rohc-pro-ids.txt */ static const value_string rohc_profile_vals[] = { { 0x0000, "ROHC uncompressed" }, /* [RFC5795] */ { 0x0001, "ROHC RTP" }, /* [RFC3095] */ { 0x0101, "ROHCv2 RTP" }, /* [RFC5225] */ { 0x0002, "ROHC UDP" }, /* [RFC3095] */ { 0x0102, "ROHCv2 UDP" }, /* [RFC5225] */ { 0x0003, "ROHC ESP" }, /* [RFC3095] */ { 0x0103, "ROHCv2 ESP" }, /* [RFC5225] */ { 0x0004, "ROHC IP" }, /* [RFC3843] */ { 0x0104, "ROHCv2 IP" }, /* [RFC5225] */ { 0x0005, "ROHC LLA" }, /* [RFC4362] */ { 0x0105, "ROHC LLA with R-mode" }, /* [RFC3408] */ { 0x0006, "ROHC TCP" }, /* [RFC4996] */ { 0x0007, "ROHC RTP/UDP-Lite" }, /* [RFC4019] */ { 0x0107, "ROHCv2 RTP/UDP-Lite" }, /* [RFC5225] */ { 0x0008, "ROHC UDP-Lite" }, /* [RFC4019] */ { 0x0108, "ROHCv2 UDP-Lite" }, /* [RFC5225] */ { 0, NULL } }; static const value_string pdu_type_vals[] = { { 0, "Control PDU" }, { 1, "Data PDU" }, { 0, NULL } }; static const value_string control_pdu_type_vals[] = { { 0, "PDCP Status report" }, { 1, "Header Compression Feedback Information" }, { 0, NULL } }; static const value_string integrity_algorithm_vals[] = { { 0, "EIA0" }, { 1, "EIA1" }, { 2, "EIA2" }, { 0, NULL } }; static const value_string ciphering_algorithm_vals[] = { { 0, "EEA0" }, { 1, "EEA1" }, { 2, "EEA2" }, { 0, NULL } }; static dissector_handle_t ip_handle; static dissector_handle_t ipv6_handle; static dissector_handle_t rohc_handle; static dissector_handle_t data_handle; #define SEQUENCE_ANALYSIS_RLC_ONLY 1 #define SEQUENCE_ANALYSIS_PDCP_ONLY 2 /* Preference variables */ static gboolean global_pdcp_dissect_user_plane_as_ip = TRUE; static gboolean global_pdcp_dissect_signalling_plane_as_rrc = TRUE; static gint global_pdcp_check_sequence_numbers = TRUE; static gboolean global_pdcp_dissect_rohc = FALSE; /* Which layer info to show in the info column */ enum layer_to_show { ShowRLCLayer, ShowPDCPLayer, ShowTrafficLayer }; static gint global_pdcp_lte_layer_to_show = (gint)ShowRLCLayer; /**************************************************/ /* Sequence number analysis */ /* Channel key */ typedef struct { /* Using bit fields to fit into 32 bits, so avoiding the need to allocate heap memory for these structs */ guint ueId : 16; guint plane : 2; guint channelId : 6; guint direction : 1; guint notUsed : 7; } pdcp_channel_hash_key; /* Channel state */ typedef struct { guint16 previousSequenceNumber; guint32 previousFrameNum; } pdcp_channel_status; /* The sequence analysis channel hash table. Maps key -> status */ static GHashTable *pdcp_sequence_analysis_channel_hash = NULL; /* Equal keys */ static gint pdcp_channel_equal(gconstpointer v, gconstpointer v2) { /* Key fits in 4 bytes, so just compare pointers! */ return (v == v2); } /* Compute a hash value for a given key. */ static guint pdcp_channel_hash_func(gconstpointer v) { /* Just use pointer, as the fields are all in this value */ return GPOINTER_TO_UINT(v); } /* Hash table types & functions for frame reports */ typedef struct { guint32 frameNumber; guint32 SN : 15; guint32 plane : 2; guint32 channelId: 5; guint32 direction: 1; guint32 notUsed : 9; } pdcp_result_hash_key; static gint pdcp_result_hash_equal(gconstpointer v, gconstpointer v2) { const pdcp_result_hash_key* val1 = (const pdcp_result_hash_key *)v; const pdcp_result_hash_key* val2 = (const pdcp_result_hash_key *)v2; /* All fields must match */ return (memcmp(val1, val2, sizeof(pdcp_result_hash_key)) == 0); } /* Compute a hash value for a given key. */ static guint pdcp_result_hash_func(gconstpointer v) { const pdcp_result_hash_key* val1 = (const pdcp_result_hash_key *)v; /* TODO: This is a bit random. */ return val1->frameNumber + (val1->channelId<<13) + (val1->plane<<5) + (val1->SN<<18) + (val1->direction<<9); } /* pdcp_channel_hash_key fits into the pointer, so just copy the value into a guint, cast to apointer and return that as the key */ static gpointer get_channel_hash_key(pdcp_channel_hash_key *key) { guint asInt = 0; /* TODO: assert that sizeof(pdcp_channel_hash_key) <= sizeof(guint) ? */ memcpy(&asInt, key, sizeof(pdcp_channel_hash_key)); return GUINT_TO_POINTER(asInt); } /* Convenience function to get a pointer for the hash_func to work with */ static gpointer get_report_hash_key(guint16 SN, guint32 frameNumber, pdcp_lte_info *p_pdcp_lte_info, gboolean do_persist) { static pdcp_result_hash_key key; pdcp_result_hash_key *p_key; /* Only allocate a struct when will be adding entry */ if (do_persist) { p_key = wmem_new(wmem_file_scope(), pdcp_result_hash_key); } else { memset(&key, 0, sizeof(pdcp_result_hash_key)); p_key = &key; } /* Fill in details, and return pointer */ p_key->frameNumber = frameNumber; p_key->SN = SN; p_key->plane = (guint8)p_pdcp_lte_info->plane; p_key->channelId = p_pdcp_lte_info->channelId; p_key->direction = p_pdcp_lte_info->direction; p_key->notUsed = 0; return p_key; } /* 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 } sequence_state; typedef struct { gboolean sequenceExpectedCorrect; guint16 sequenceExpected; guint32 previousFrameNum; guint32 nextFrameNum; guint16 firstSN; guint16 lastSN; sequence_state state; } pdcp_sequence_report_in_frame; /* The sequence analysis frame report hash table instance itself */ static GHashTable *pdcp_lte_sequence_analysis_report_hash = NULL; /* Add to the tree values associated with sequence analysis for this frame */ static void addChannelSequenceInfo(pdcp_sequence_report_in_frame *p, pdcp_lte_info *p_pdcp_lte_info, guint16 sequenceNumber, packet_info *pinfo, proto_tree *tree, tvbuff_t *tvb) { proto_tree *seqnum_tree; proto_item *seqnum_ti; proto_item *ti_expected_sn; proto_item *ti; /* Create subtree */ seqnum_ti = proto_tree_add_string_format(tree, hf_pdcp_lte_sequence_analysis, tvb, 0, 0, "", "Sequence Analysis"); seqnum_tree = proto_item_add_subtree(seqnum_ti, ett_pdcp_lte_sequence_analysis); PROTO_ITEM_SET_GENERATED(seqnum_ti); /* Previous channel frame */ if (p->previousFrameNum != 0) { proto_tree_add_uint(seqnum_tree, hf_pdcp_lte_sequence_analysis_previous_frame, tvb, 0, 0, p->previousFrameNum); } /* Expected sequence number */ ti_expected_sn = proto_tree_add_uint(seqnum_tree, hf_pdcp_lte_sequence_analysis_expected_sn, tvb, 0, 0, p->sequenceExpected); PROTO_ITEM_SET_GENERATED(ti_expected_sn); /* Make sure we have recognised SN length */ switch (p_pdcp_lte_info->seqnum_length) { case PDCP_SN_LENGTH_5_BITS: case PDCP_SN_LENGTH_7_BITS: case PDCP_SN_LENGTH_12_BITS: case PDCP_SN_LENGTH_15_BITS: break; default: DISSECTOR_ASSERT_NOT_REACHED(); break; } switch (p->state) { case SN_OK: PROTO_ITEM_SET_HIDDEN(ti_expected_sn); ti = proto_tree_add_boolean(seqnum_tree, hf_pdcp_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_pdcp_lte_sequence_analysis_next_frame, tvb, 0, 0, p->nextFrameNum); } break; case SN_Missing: ti = proto_tree_add_boolean(seqnum_tree, hf_pdcp_lte_sequence_analysis_ok, tvb, 0, 0, FALSE); PROTO_ITEM_SET_GENERATED(ti); ti = proto_tree_add_boolean(seqnum_tree, hf_pdcp_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_pdcp_lte_sequence_analysis_sn_missing, "PDCP SNs (%u to %u) missing for %s on UE %u (%s-%u)", p->firstSN, p->lastSN, val_to_str_const(p_pdcp_lte_info->direction, direction_vals, "Unknown"), p_pdcp_lte_info->ueid, val_to_str_const(p_pdcp_lte_info->channelType, logical_channel_vals, "Unknown"), p_pdcp_lte_info->channelId); proto_item_append_text(seqnum_ti, " - SNs missing (%u to %u)", p->firstSN, p->lastSN); } else { expert_add_info_format(pinfo, ti, &ei_pdcp_lte_sequence_analysis_sn_missing, "PDCP SN (%u) missing for %s on UE %u (%s-%u)", p->firstSN, val_to_str_const(p_pdcp_lte_info->direction, direction_vals, "Unknown"), p_pdcp_lte_info->ueid, val_to_str_const(p_pdcp_lte_info->channelType, logical_channel_vals, "Unknown"), p_pdcp_lte_info->channelId); proto_item_append_text(seqnum_ti, " - SN missing (%u)", p->firstSN); } break; case SN_Repeated: ti = proto_tree_add_boolean(seqnum_tree, hf_pdcp_lte_sequence_analysis_ok, tvb, 0, 0, FALSE); PROTO_ITEM_SET_GENERATED(ti); ti = proto_tree_add_boolean(seqnum_tree, hf_pdcp_lte_sequence_analysis_repeated, tvb, 0, 0, TRUE); PROTO_ITEM_SET_GENERATED(ti); expert_add_info_format(pinfo, ti, &ei_pdcp_lte_sequence_analysis_sn_repeated, "PDCP SN (%u) repeated for %s for UE %u (%s-%u)", p->firstSN, val_to_str_const(p_pdcp_lte_info->direction, direction_vals, "Unknown"), p_pdcp_lte_info->ueid, val_to_str_const(p_pdcp_lte_info->channelType, logical_channel_vals, "Unknown"), p_pdcp_lte_info->channelId); proto_item_append_text(seqnum_ti, "- SN %u Repeated", p->firstSN); break; default: /* Incorrect sequence number */ expert_add_info_format(pinfo, ti_expected_sn, &ei_pdcp_lte_sequence_analysis_wrong_sequence_number, "Wrong Sequence Number for %s on UE %u (%s-%u) - got %u, expected %u", val_to_str_const(p_pdcp_lte_info->direction, direction_vals, "Unknown"), p_pdcp_lte_info->ueid, val_to_str_const(p_pdcp_lte_info->channelType, logical_channel_vals, "Unknown"), p_pdcp_lte_info->channelId, sequenceNumber, p->sequenceExpected); break; } } /* Update the channel status and set report for this frame */ static void checkChannelSequenceInfo(packet_info *pinfo, tvbuff_t *tvb, pdcp_lte_info *p_pdcp_lte_info, guint16 sequenceNumber, proto_tree *tree) { pdcp_channel_hash_key channel_key; pdcp_channel_status *p_channel_status; pdcp_sequence_report_in_frame *p_report_in_frame = NULL; gboolean createdChannel = FALSE; guint16 expectedSequenceNumber = 0; guint16 snLimit = 0; /* If find stat_report_in_frame already, use that and get out */ if (pinfo->fd->flags.visited) { p_report_in_frame = (pdcp_sequence_report_in_frame*)g_hash_table_lookup(pdcp_lte_sequence_analysis_report_hash, get_report_hash_key(sequenceNumber, pinfo->fd->num, p_pdcp_lte_info, FALSE)); if (p_report_in_frame != NULL) { addChannelSequenceInfo(p_report_in_frame, p_pdcp_lte_info, sequenceNumber, 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_pdcp_lte_info->ueid; channel_key.plane = p_pdcp_lte_info->plane; channel_key.channelId = p_pdcp_lte_info->channelId; channel_key.direction = p_pdcp_lte_info->direction; channel_key.notUsed = 0; /* Do the table lookup */ p_channel_status = (pdcp_channel_status*)g_hash_table_lookup(pdcp_sequence_analysis_channel_hash, get_channel_hash_key(&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(), pdcp_channel_status); /* Add entry */ g_hash_table_insert(pdcp_sequence_analysis_channel_hash, get_channel_hash_key(&channel_key), p_channel_status); } /* Create space for frame state_report */ p_report_in_frame = wmem_new(wmem_file_scope(), pdcp_sequence_report_in_frame); p_report_in_frame->nextFrameNum = 0; switch (p_pdcp_lte_info->seqnum_length) { case PDCP_SN_LENGTH_5_BITS: snLimit = 32; break; case PDCP_SN_LENGTH_7_BITS: snLimit = 128; break; case PDCP_SN_LENGTH_12_BITS: snLimit = 4096; break; case PDCP_SN_LENGTH_15_BITS: snLimit = 32768; break; default: DISSECTOR_ASSERT_NOT_REACHED(); break; } /* Work out expected sequence number */ if (!createdChannel) { expectedSequenceNumber = (p_channel_status->previousSequenceNumber + 1) % snLimit; } else { expectedSequenceNumber = sequenceNumber; } /* Set report for this frame */ /* For PDCP, sequence number is always expectedSequence number */ p_report_in_frame->sequenceExpectedCorrect = (sequenceNumber == expectedSequenceNumber); /* For wrong sequence number... */ if (!p_report_in_frame->sequenceExpectedCorrect) { /* Frames are not missing if we get an earlier sequence number again */ if (((snLimit + expectedSequenceNumber - sequenceNumber) % snLimit) > 15) { p_report_in_frame->state = SN_Missing; 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; /* Update channel status to remember *this* frame */ p_channel_status->previousFrameNum = pinfo->fd->num; p_channel_status->previousSequenceNumber = sequenceNumber; } 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->state = SN_OK; p_report_in_frame->sequenceExpected = expectedSequenceNumber; p_report_in_frame->previousFrameNum = p_channel_status->previousFrameNum; /* Update channel status to remember *this* frame */ p_channel_status->previousFrameNum = pinfo->fd->num; p_channel_status->previousSequenceNumber = sequenceNumber; if (p_report_in_frame->previousFrameNum != 0) { /* Get report for previous frame */ pdcp_sequence_report_in_frame *p_previous_report; p_previous_report = (pdcp_sequence_report_in_frame*)g_hash_table_lookup(pdcp_lte_sequence_analysis_report_hash, get_report_hash_key((sequenceNumber+32767) % 32768, p_report_in_frame->previousFrameNum, p_pdcp_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->fd->num; } } } /* Associate with this frame number */ g_hash_table_insert(pdcp_lte_sequence_analysis_report_hash, get_report_hash_key(sequenceNumber, pinfo->fd->num, p_pdcp_lte_info, TRUE), p_report_in_frame); /* Add state report for this frame into tree */ addChannelSequenceInfo(p_report_in_frame, p_pdcp_lte_info, sequenceNumber, pinfo, tree, tvb); } /* Hash table for security state for a UE Maps UEId -> pdcp_security_info_t* */ static gint pdcp_lte_ueid_hash_equal(gconstpointer v, gconstpointer v2) { return (v == v2); } static guint pdcp_lte_ueid_hash_func(gconstpointer v) { return GPOINTER_TO_UINT(v); } static GHashTable *pdcp_security_hash = NULL; /* Result is (ueid, framenum) -> pdcp_security_info_t* */ typedef struct ueid_frame_t { guint32 framenum; guint16 ueid; } ueid_frame_t; /* Convenience function to get a pointer for the hash_func to work with */ static gpointer get_ueid_frame_hash_key(guint16 ueid, guint32 frameNumber, gboolean do_persist) { static ueid_frame_t key; ueid_frame_t *p_key; /* Only allocate a struct when will be adding entry */ if (do_persist) { p_key = wmem_new(wmem_file_scope(), ueid_frame_t); } else { memset(&key, 0, sizeof(ueid_frame_t)); p_key = &key; } /* Fill in details, and return pointer */ p_key->framenum = frameNumber; p_key->ueid = ueid; return p_key; } static gint pdcp_lte_ueid_frame_hash_equal(gconstpointer v, gconstpointer v2) { ueid_frame_t *ueid_frame_1 = (ueid_frame_t *)v; ueid_frame_t *ueid_frame_2 = (ueid_frame_t *)v2; return ((ueid_frame_1->framenum == ueid_frame_2->framenum) && (ueid_frame_1->ueid == ueid_frame_2->ueid)); } static guint pdcp_lte_ueid_frame_hash_func(gconstpointer v) { ueid_frame_t *ueid_frame = (ueid_frame_t *)v; return ueid_frame->framenum + 100*ueid_frame->ueid; } static GHashTable *pdcp_security_result_hash = NULL; /* Write the given formatted text to: - the info column - the top-level RLC PDU item */ static void write_pdu_label_and_info(proto_item *pdu_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); g_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); } /***************************************************************/ /* Show in the tree the config info attached to this frame, as generated fields */ static void show_pdcp_config(packet_info *pinfo, tvbuff_t *tvb, proto_tree *tree, pdcp_lte_info *p_pdcp_info) { proto_item *ti; proto_tree *configuration_tree; proto_item *configuration_ti = proto_tree_add_item(tree, hf_pdcp_lte_configuration, tvb, 0, 0, ENC_ASCII|ENC_NA); configuration_tree = proto_item_add_subtree(configuration_ti, ett_pdcp_configuration); /* Direction */ ti = proto_tree_add_uint(configuration_tree, hf_pdcp_lte_direction, tvb, 0, 0, p_pdcp_info->direction); PROTO_ITEM_SET_GENERATED(ti); /* Plane */ ti = proto_tree_add_uint(configuration_tree, hf_pdcp_lte_plane, tvb, 0, 0, p_pdcp_info->plane); PROTO_ITEM_SET_GENERATED(ti); /* UEId */ if (p_pdcp_info->ueid != 0) { ti = proto_tree_add_uint(configuration_tree, hf_pdcp_lte_ueid, tvb, 0, 0, p_pdcp_info->ueid); PROTO_ITEM_SET_GENERATED(ti); } /* Channel type */ ti = proto_tree_add_uint(configuration_tree, hf_pdcp_lte_channel_type, tvb, 0, 0, p_pdcp_info->channelType); PROTO_ITEM_SET_GENERATED(ti); if (p_pdcp_info->channelId != 0) { /* Channel type */ ti = proto_tree_add_uint(configuration_tree, hf_pdcp_lte_channel_id, tvb, 0, 0, p_pdcp_info->channelId); PROTO_ITEM_SET_GENERATED(ti); } /* User-plane-specific fields */ if (p_pdcp_info->plane == USER_PLANE) { /* No Header PDU */ ti = proto_tree_add_uint(configuration_tree, hf_pdcp_lte_no_header_pdu, tvb, 0, 0, p_pdcp_info->no_header_pdu); PROTO_ITEM_SET_GENERATED(ti); if (!p_pdcp_info->no_header_pdu) { /* Seqnum length */ ti = proto_tree_add_uint(configuration_tree, hf_pdcp_lte_seqnum_length, tvb, 0, 0, p_pdcp_info->seqnum_length); PROTO_ITEM_SET_GENERATED(ti); } } /* ROHC compression */ ti = proto_tree_add_boolean(configuration_tree, hf_pdcp_lte_rohc_compression, tvb, 0, 0, p_pdcp_info->rohc.rohc_compression); PROTO_ITEM_SET_GENERATED(ti); /* ROHC-specific settings */ if (p_pdcp_info->rohc.rohc_compression) { /* Show ROHC mode */ ti = proto_tree_add_uint(configuration_tree, hf_pdcp_lte_rohc_mode, tvb, 0, 0, p_pdcp_info->rohc.mode); PROTO_ITEM_SET_GENERATED(ti); /* Show RND */ ti = proto_tree_add_uint(configuration_tree, hf_pdcp_lte_rohc_rnd, tvb, 0, 0, p_pdcp_info->rohc.rnd); PROTO_ITEM_SET_GENERATED(ti); /* UDP Checksum */ ti = proto_tree_add_uint(configuration_tree, hf_pdcp_lte_rohc_udp_checksum_present, tvb, 0, 0, p_pdcp_info->rohc.udp_checksum_present); PROTO_ITEM_SET_GENERATED(ti); /* ROHC profile */ ti = proto_tree_add_uint(configuration_tree, hf_pdcp_lte_rohc_profile, tvb, 0, 0, p_pdcp_info->rohc.profile); PROTO_ITEM_SET_GENERATED(ti); /* CID Inclusion Info */ ti = proto_tree_add_uint(configuration_tree, hf_pdcp_lte_cid_inclusion_info, tvb, 0, 0, p_pdcp_info->rohc.cid_inclusion_info); PROTO_ITEM_SET_GENERATED(ti); /* Large CID */ ti = proto_tree_add_uint(configuration_tree, hf_pdcp_lte_large_cid_present, tvb, 0, 0, p_pdcp_info->rohc.large_cid_present); PROTO_ITEM_SET_GENERATED(ti); } /* Append summary to configuration root */ proto_item_append_text(configuration_ti, "(direction=%s, plane=%s", val_to_str_const(p_pdcp_info->direction, direction_vals, "Unknown"), val_to_str_const(p_pdcp_info->plane, pdcp_plane_vals, "Unknown")); if (p_pdcp_info->rohc.rohc_compression) { const char *mode = val_to_str_const(p_pdcp_info->rohc.mode, rohc_mode_vals, "Error"); proto_item_append_text(configuration_ti, ", mode=%c, profile=%s", mode[0], val_to_str_const(p_pdcp_info->rohc.profile, rohc_profile_vals, "Unknown")); } proto_item_append_text(configuration_ti, ")"); PROTO_ITEM_SET_GENERATED(configuration_ti); /* Show plane in info column */ col_append_fstr(pinfo->cinfo, COL_INFO, " %s: ", val_to_str_const(p_pdcp_info->plane, pdcp_plane_vals, "Unknown")); } /* Look for an RRC dissector for signalling data (using channel type and direction) */ static dissector_handle_t lookup_rrc_dissector_handle(struct pdcp_lte_info *p_pdcp_info) { dissector_handle_t rrc_handle = 0; switch (p_pdcp_info->channelType) { case Channel_CCCH: if (p_pdcp_info->direction == DIRECTION_UPLINK) { rrc_handle = find_dissector("lte_rrc.ul_ccch"); } else { rrc_handle = find_dissector("lte_rrc.dl_ccch"); } break; case Channel_PCCH: rrc_handle = find_dissector("lte_rrc.pcch"); break; case Channel_BCCH: switch (p_pdcp_info->BCCHTransport) { case BCH_TRANSPORT: rrc_handle = find_dissector("lte_rrc.bcch_bch"); break; case DLSCH_TRANSPORT: rrc_handle = find_dissector("lte_rrc.bcch_dl_sch"); break; } break; case Channel_DCCH: if (p_pdcp_info->direction == DIRECTION_UPLINK) { rrc_handle = find_dissector("lte_rrc.ul_dcch"); } else { rrc_handle = find_dissector("lte_rrc.dl_dcch"); } break; default: break; } return rrc_handle; } /* Forwad declarations */ static void dissect_pdcp_lte(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree); /* Heuristic dissection */ static gboolean global_pdcp_lte_heur = FALSE; /* Heuristic dissector looks for supported framing protocol (see wiki page) */ static gboolean dissect_pdcp_lte_heur(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { gint offset = 0; struct pdcp_lte_info *p_pdcp_lte_info; tvbuff_t *pdcp_tvb; guint8 tag = 0; gboolean infoAlreadySet = FALSE; gboolean seqnumLengthTagPresent = FALSE; /* This is a heuristic dissector, which means we get all the UDP * traffic not sent to a known dissector and not claimed by * a heuristic dissector called before us! */ if (!global_pdcp_lte_heur) { return FALSE; } /* Do this again on re-dissection to re-discover offset of actual PDU */ /* Needs to be at least as long as: - the signature string - fixed header bytes - tag for data - at least one byte of PDCP PDU payload */ if (tvb_length_remaining(tvb, offset) < (gint)(strlen(PDCP_LTE_START_STRING)+3+2)) { return FALSE; } /* OK, compare with signature string */ if (tvb_strneql(tvb, offset, PDCP_LTE_START_STRING, strlen(PDCP_LTE_START_STRING)) != 0) { return FALSE; } offset += (gint)strlen(PDCP_LTE_START_STRING); /* If redissecting, use previous info struct (if available) */ 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) { /* Allocate new info struct for this frame */ p_pdcp_lte_info = wmem_new0(wmem_file_scope(), pdcp_lte_info); infoAlreadySet = FALSE; } else { infoAlreadySet = TRUE; } /* Read fixed fields */ p_pdcp_lte_info->no_header_pdu = (gboolean)tvb_get_guint8(tvb, offset++); p_pdcp_lte_info->plane = (enum pdcp_plane)tvb_get_guint8(tvb, offset++); p_pdcp_lte_info->rohc.rohc_compression = (gboolean)tvb_get_guint8(tvb, offset++); /* Read optional fields */ while (tag != PDCP_LTE_PAYLOAD_TAG) { /* Process next tag */ tag = tvb_get_guint8(tvb, offset++); switch (tag) { case PDCP_LTE_SEQNUM_LENGTH_TAG: p_pdcp_lte_info->seqnum_length = tvb_get_guint8(tvb, offset); offset++; seqnumLengthTagPresent = TRUE; break; case PDCP_LTE_DIRECTION_TAG: p_pdcp_lte_info->direction = tvb_get_guint8(tvb, offset); offset++; break; case PDCP_LTE_LOG_CHAN_TYPE_TAG: p_pdcp_lte_info->channelType = (LogicalChannelType)tvb_get_guint8(tvb, offset); offset++; break; case PDCP_LTE_BCCH_TRANSPORT_TYPE_TAG: p_pdcp_lte_info->BCCHTransport = (BCCHTransportType)tvb_get_guint8(tvb, offset); offset++; break; case PDCP_LTE_ROHC_IP_VERSION_TAG: p_pdcp_lte_info->rohc.rohc_ip_version = tvb_get_ntohs(tvb, offset); offset += 2; break; case PDCP_LTE_ROHC_CID_INC_INFO_TAG: p_pdcp_lte_info->rohc.cid_inclusion_info = tvb_get_guint8(tvb, offset); offset++; break; case PDCP_LTE_ROHC_LARGE_CID_PRES_TAG: p_pdcp_lte_info->rohc.large_cid_present = tvb_get_guint8(tvb, offset); offset++; break; case PDCP_LTE_ROHC_MODE_TAG: p_pdcp_lte_info->rohc.mode = (enum rohc_mode)tvb_get_guint8(tvb, offset); offset++; break; case PDCP_LTE_ROHC_RND_TAG: p_pdcp_lte_info->rohc.rnd = tvb_get_guint8(tvb, offset); offset++; break; case PDCP_LTE_ROHC_UDP_CHECKSUM_PRES_TAG: p_pdcp_lte_info->rohc.udp_checksum_present = tvb_get_guint8(tvb, offset); offset++; break; case PDCP_LTE_ROHC_PROFILE_TAG: p_pdcp_lte_info->rohc.profile = tvb_get_ntohs(tvb, offset); offset += 2; break; case PDCP_LTE_CHANNEL_ID_TAG: p_pdcp_lte_info->channelId = tvb_get_ntohs(tvb, offset); offset += 2; break; case PDCP_LTE_UEID_TAG: p_pdcp_lte_info->ueid = tvb_get_ntohs(tvb, offset); offset += 2; break; case PDCP_LTE_PAYLOAD_TAG: /* Have reached data, so get out of loop */ continue; default: /* It must be a recognised tag */ return FALSE; } } if ((p_pdcp_lte_info->plane == USER_PLANE) && (seqnumLengthTagPresent == FALSE)) { /* Conditional field is not present */ return FALSE; } if (!infoAlreadySet) { /* Store info in packet */ p_add_proto_data(wmem_file_scope(), pinfo, proto_pdcp_lte, 0, p_pdcp_lte_info); } /**************************************/ /* OK, now dissect as PDCP LTE */ /* Create tvb that starts at actual PDCP PDU */ pdcp_tvb = tvb_new_subset_remaining(tvb, offset); dissect_pdcp_lte(pdcp_tvb, pinfo, tree); return TRUE; } /* Called from control protocol to configure security algorithms for the given UE */ void set_pdcp_lte_security_algorithms(guint16 ueid, pdcp_security_info_t *security_info) { /* Copy security struct */ pdcp_security_info_t *p_security = wmem_new(wmem_file_scope(), pdcp_security_info_t); *p_security = *security_info; /* And add into security table */ g_hash_table_insert(pdcp_security_hash, GUINT_TO_POINTER((guint)ueid), p_security); } /******************************/ /* Main dissection function. */ static void dissect_pdcp_lte(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) { const char *mode; proto_tree *pdcp_tree = NULL; proto_item *root_ti = NULL; gint offset = 0; gint rohc_offset; struct pdcp_lte_info *p_pdcp_info; tvbuff_t *rohc_tvb = NULL; pdcp_security_info_t *current_security = NULL; /* current security for this UE */ pdcp_security_info_t *pdu_security; /* security in place for this PDU */ /* Set protocol name. */ col_set_str(pinfo->cinfo, COL_PROTOCOL, "PDCP-LTE"); /* Look for attached packet info! */ p_pdcp_info = (struct pdcp_lte_info *)p_get_proto_data(wmem_file_scope(), pinfo, proto_pdcp_lte, 0); /* Can't dissect anything without it... */ if (p_pdcp_info == NULL) { return; } /* Don't want to overwrite the RLC Info column if configured not to */ if ((global_pdcp_lte_layer_to_show == ShowRLCLayer) && (p_get_proto_data(wmem_file_scope(), pinfo, proto_rlc_lte, 0) != NULL)) { col_set_writable(pinfo->cinfo, FALSE); } else { /* TODO: won't help with multiple PDCP-or-traffic PDUs / frame... */ col_clear(pinfo->cinfo, COL_INFO); col_set_writable(pinfo->cinfo, TRUE); } /* Create pdcp tree. */ if (tree) { root_ti = proto_tree_add_item(tree, proto_pdcp_lte, tvb, offset, -1, ENC_NA); pdcp_tree = proto_item_add_subtree(root_ti, ett_pdcp); } /* Set mode string */ mode = val_to_str_const(p_pdcp_info->rohc.mode, rohc_mode_vals, "Error"); /*****************************************************/ /* Show configuration (attached packet) info in tree */ if (pdcp_tree) { show_pdcp_config(pinfo, tvb, pdcp_tree, p_pdcp_info); } /* Show ROHC mode */ if (p_pdcp_info->rohc.rohc_compression) { col_append_fstr(pinfo->cinfo, COL_INFO, " (mode=%c)", mode[0]); } /***************************************/ /* UE security algorithms */ if (!pinfo->fd->flags.visited) { /* Look up current state by UEID */ current_security = (pdcp_security_info_t*)g_hash_table_lookup(pdcp_security_hash, GUINT_TO_POINTER((guint)p_pdcp_info->ueid)); if (current_security != NULL) { /* Store any result for this frame in the result table */ pdcp_security_info_t *security_to_store = wmem_new(wmem_file_scope(), pdcp_security_info_t); *security_to_store = *current_security; g_hash_table_insert(pdcp_security_result_hash, get_ueid_frame_hash_key(p_pdcp_info->ueid, pinfo->fd->num, TRUE), security_to_store); } } /* Show security settings for this PDU */ pdu_security = (pdcp_security_info_t*)g_hash_table_lookup(pdcp_security_result_hash, get_ueid_frame_hash_key(p_pdcp_info->ueid, pinfo->fd->num, FALSE)); if (pdu_security != NULL) { proto_tree *security_tree; proto_item *security_ti, *ti; /* Create subtree */ security_ti = proto_tree_add_string_format(pdcp_tree, hf_pdcp_lte_security, tvb, 0, 0, "", "UE Security"); security_tree = proto_item_add_subtree(security_ti, ett_pdcp_security); PROTO_ITEM_SET_GENERATED(security_ti); /* Setup frame */ ti = proto_tree_add_uint(security_tree, hf_pdcp_lte_security_setup_frame, tvb, 0, 0, pdu_security->configuration_frame); PROTO_ITEM_SET_GENERATED(ti); /* Ciphering */ ti = proto_tree_add_uint(security_tree, hf_pdcp_lte_security_ciphering_algorithm, tvb, 0, 0, pdu_security->ciphering); PROTO_ITEM_SET_GENERATED(ti); /* Integrity */ ti = proto_tree_add_uint(security_tree, hf_pdcp_lte_security_integrity_algorithm, tvb, 0, 0, pdu_security->integrity); PROTO_ITEM_SET_GENERATED(ti); proto_item_append_text(security_ti, " (ciphering=%s, integrity=%s)", val_to_str_const(pdu_security->ciphering, ciphering_algorithm_vals, "Unknown"), val_to_str_const(pdu_security->integrity, integrity_algorithm_vals, "Unknown")); } /***********************************/ /* Handle PDCP header (if present) */ if (!p_pdcp_info->no_header_pdu) { /* TODO: shouldn't need to initialise this one!! */ guint16 seqnum = 0; gboolean seqnum_set = FALSE; guint8 first_byte = tvb_get_guint8(tvb, offset); /*****************************/ /* Signalling plane messages */ if (p_pdcp_info->plane == SIGNALING_PLANE) { guint32 mac; guint32 data_length; /* Verify 3 reserved bits are 0 */ guint8 reserved = (first_byte & 0xe0) >> 5; proto_item *ti = proto_tree_add_item(pdcp_tree, hf_pdcp_lte_control_plane_reserved, tvb, offset, 1, ENC_BIG_ENDIAN); if (reserved != 0) { expert_add_info_format(pinfo, ti, &ei_pdcp_lte_reserved_bits_not_zero, "PDCP signalling header reserved bits not zero"); } /* 5-bit sequence number */ seqnum = first_byte & 0x1f; seqnum_set = TRUE; proto_tree_add_item(pdcp_tree, hf_pdcp_lte_seq_num_5, tvb, offset, 1, ENC_BIG_ENDIAN); write_pdu_label_and_info(root_ti, pinfo, " sn=%-2u ", seqnum); offset++; if (tvb_length_remaining(tvb, offset) == 0) { /* Only PDCP header was captured, stop dissection here */ return; } /* RRC data is all but last 4 bytes. Call lte-rrc dissector (according to direction and channel type) */ if ((global_pdcp_dissect_signalling_plane_as_rrc) && ((pdu_security == NULL) || (pdu_security->ciphering == 0) || !pdu_security->seen_next_ul_pdu)){ /* Get appropriate dissector handle */ dissector_handle_t rrc_handle = lookup_rrc_dissector_handle(p_pdcp_info); if (rrc_handle != 0) { /* Call RRC dissector if have one */ tvbuff_t *payload_tvb = tvb_new_subset(tvb, offset, tvb_length_remaining(tvb, offset) - 4, tvb_length_remaining(tvb, offset) - 4); gboolean was_writable = col_get_writable(pinfo->cinfo); /* We always want to see this in the info column */ col_set_writable(pinfo->cinfo, TRUE); call_dissector_only(rrc_handle, payload_tvb, pinfo, pdcp_tree, NULL); /* Restore to whatever it was */ col_set_writable(pinfo->cinfo, was_writable); } else { /* Just show data */ proto_tree_add_item(pdcp_tree, hf_pdcp_lte_signalling_data, tvb, offset, tvb_length_remaining(tvb, offset) - 4, ENC_NA); } if (!pinfo->fd->flags.visited && (current_security != NULL) && !current_security->seen_next_ul_pdu && p_pdcp_info->direction == DIRECTION_UPLINK) { /* i.e. we have already seen SecurityModeResponse! */ current_security->seen_next_ul_pdu = TRUE; } } else { /* Just show as unparsed data */ proto_tree_add_item(pdcp_tree, hf_pdcp_lte_signalling_data, tvb, offset, tvb_length_remaining(tvb, offset) - 4, ENC_NA); } data_length = tvb_length_remaining(tvb, offset) - 4; offset += data_length; /* Last 4 bytes are MAC */ mac = tvb_get_ntohl(tvb, offset); proto_tree_add_item(pdcp_tree, hf_pdcp_lte_mac, tvb, offset, 4, ENC_BIG_ENDIAN); offset += 4; col_append_fstr(pinfo->cinfo, COL_INFO, " MAC=0x%08x (%u bytes data)", mac, data_length); } else if (p_pdcp_info->plane == USER_PLANE) { /**********************************/ /* User-plane messages */ gboolean pdu_type = (first_byte & 0x80) >> 7; /* Data/Control flag */ proto_tree_add_item(pdcp_tree, hf_pdcp_lte_data_control, tvb, offset, 1, ENC_BIG_ENDIAN); if (pdu_type == 1) { /*****************************/ /* Use-plane Data */ /* Number of sequence number bits depends upon config */ switch (p_pdcp_info->seqnum_length) { case PDCP_SN_LENGTH_7_BITS: seqnum = first_byte & 0x7f; seqnum_set = TRUE; proto_tree_add_item(pdcp_tree, hf_pdcp_lte_seq_num_7, tvb, offset, 1, ENC_BIG_ENDIAN); offset++; break; case PDCP_SN_LENGTH_12_BITS: { proto_item *ti; guint8 reserved_value; /* 3 reserved bits */ ti = proto_tree_add_item(pdcp_tree, hf_pdcp_lte_reserved3, tvb, offset, 1, ENC_BIG_ENDIAN); reserved_value = (first_byte & 0x70) >> 4; /* Complain if not 0 */ if (reserved_value != 0) { expert_add_info_format(pinfo, ti, &ei_pdcp_lte_reserved_bits_not_zero, "Reserved bits have value 0x%x - should be 0x0", reserved_value); } /* 12-bit sequence number */ seqnum = tvb_get_ntohs(tvb, offset) & 0x0fff; seqnum_set = TRUE; proto_tree_add_item(pdcp_tree, hf_pdcp_lte_seq_num_12, tvb, offset, 2, ENC_BIG_ENDIAN); offset += 2; } break; case PDCP_SN_LENGTH_15_BITS: seqnum = tvb_get_ntohs(tvb, offset) & 0x7fff; seqnum_set = TRUE; proto_tree_add_item(pdcp_tree, hf_pdcp_lte_seq_num_15, tvb, offset, 2, ENC_BIG_ENDIAN); offset += 2; break; default: /* Not a recognised data format!!!!! */ return; } write_pdu_label_and_info(root_ti, pinfo, " (SN=%u)", seqnum); } else { /*******************************/ /* User-plane Control messages */ guint8 control_pdu_type = (first_byte & 0x70) >> 4; proto_tree_add_item(pdcp_tree, hf_pdcp_lte_control_pdu_type, tvb, offset, 1, ENC_BIG_ENDIAN); switch (control_pdu_type) { case 0: /* PDCP status report */ { guint8 bits; guint16 fms; guint16 modulo; guint not_received = 0; guint sn, i, j, l; guint32 len, bit_offset; proto_tree *bitmap_tree; proto_item *bitmap_ti = NULL; gchar *buff = NULL; #define BUFF_SIZE 49 if (p_pdcp_info->seqnum_length == PDCP_SN_LENGTH_12_BITS) { /* First-Missing-Sequence SN */ fms = tvb_get_ntohs(tvb, offset) & 0x0fff; sn = (fms + 1) % 4096; proto_tree_add_item(pdcp_tree, hf_pdcp_lte_fms, tvb, offset, 2, ENC_BIG_ENDIAN); offset += 2; modulo = 4096; } else { proto_item *ti; guint8 reserved_value; /* 5 reserved bits */ ti = proto_tree_add_item(pdcp_tree, hf_pdcp_lte_reserved4, tvb, offset, 2, ENC_BIG_ENDIAN); reserved_value = (tvb_get_ntohs(tvb, offset) & 0x0f80)>>7; offset++; /* Complain if not 0 */ if (reserved_value != 0) { expert_add_info_format(pinfo, ti, &ei_pdcp_lte_reserved_bits_not_zero, "Reserved bits have value 0x%x - should be 0x0", reserved_value); } /* First-Missing-Sequence SN */ fms = tvb_get_ntohs(tvb, offset) & 0x7fff; sn = (fms + 1) % 32768; proto_tree_add_item(pdcp_tree, hf_pdcp_lte_fms2, tvb, offset, 2, ENC_BIG_ENDIAN); offset += 2; modulo = 32768; } /* Bitmap tree */ if (tvb_length_remaining(tvb, offset) > 0) { bitmap_ti = proto_tree_add_item(pdcp_tree, hf_pdcp_lte_bitmap, tvb, offset, -1, ENC_NA); bitmap_tree = proto_item_add_subtree(bitmap_ti, ett_pdcp_report_bitmap); buff = (gchar *)wmem_alloc(wmem_packet_scope(), BUFF_SIZE); len = tvb_length_remaining(tvb, offset); bit_offset = offset<<3; /* For each byte... */ for (i=0; iplane); return; } /* Do sequence analysis if configured to. */ if (seqnum_set) { gboolean do_analysis = FALSE; switch (global_pdcp_check_sequence_numbers) { case FALSE: break; case SEQUENCE_ANALYSIS_RLC_ONLY: if ((p_get_proto_data(wmem_file_scope(), pinfo, proto_rlc_lte, 0) != NULL) && !p_pdcp_info->is_retx) { do_analysis = TRUE; } break; case SEQUENCE_ANALYSIS_PDCP_ONLY: if (p_get_proto_data(wmem_file_scope(), pinfo, proto_rlc_lte, 0) == NULL) { do_analysis = TRUE; } break; } if (do_analysis) { checkChannelSequenceInfo(pinfo, tvb, p_pdcp_info, (guint16)seqnum, pdcp_tree); } } } else { /* Show that it's a no-header PDU */ write_pdu_label_and_info(root_ti, pinfo, " No-Header "); } /* If not compressed with ROHC, show as user-plane data */ if (!p_pdcp_info->rohc.rohc_compression) { gint payload_length = tvb_length_remaining(tvb, offset); if (payload_length > 0) { if (p_pdcp_info->plane == USER_PLANE) { /* Not attempting to decode payload if ciphering is enabled (and NULL ciphering is not being used) */ if (global_pdcp_dissect_user_plane_as_ip && ((pdu_security == NULL) || (pdu_security->ciphering == 0))) { tvbuff_t *payload_tvb = tvb_new_subset_remaining(tvb, offset); /* Don't update info column for ROHC unless configured to */ if (global_pdcp_lte_layer_to_show != ShowTrafficLayer) { col_set_writable(pinfo->cinfo, FALSE); } switch (tvb_get_guint8(tvb, offset) & 0xf0) { case 0x40: call_dissector_only(ip_handle, payload_tvb, pinfo, pdcp_tree, NULL); break; case 0x60: call_dissector_only(ipv6_handle, payload_tvb, pinfo, pdcp_tree, NULL); break; default: call_dissector_only(data_handle, payload_tvb, pinfo, pdcp_tree, NULL); break; } /* Freeze the columns again because we don't want other layers writing to info */ if (global_pdcp_lte_layer_to_show == ShowTrafficLayer) { col_set_writable(pinfo->cinfo, FALSE); } } else { proto_tree_add_item(pdcp_tree, hf_pdcp_lte_user_plane_data, tvb, offset, -1, ENC_NA); } } write_pdu_label_and_info(root_ti, pinfo, "(%u bytes data)", payload_length); } /* (there will be no signalling data left at this point) */ /* Let RLC write to columns again */ col_set_writable(pinfo->cinfo, global_pdcp_lte_layer_to_show == ShowRLCLayer); /* DROPPING OUT HERE IF NOT DOING ROHC! */ return; } /***************************/ /* ROHC packets */ /***************************/ /* Only attempt ROHC if configured to */ if (!global_pdcp_dissect_rohc) { col_append_fstr(pinfo->cinfo, COL_PROTOCOL, "|ROHC(%s)", val_to_str_const(p_pdcp_info->rohc.profile, rohc_profile_vals, "Unknown")); return; } rohc_offset = offset; rohc_tvb = tvb_new_subset_remaining(tvb, rohc_offset); /* Only enable writing to column if configured to show ROHC */ if (global_pdcp_lte_layer_to_show != ShowTrafficLayer) { col_set_writable(pinfo->cinfo, FALSE); } else { col_clear(pinfo->cinfo, COL_INFO); } /* Call the ROHC dissector */ call_dissector_with_data(rohc_handle, rohc_tvb, pinfo, tree, &p_pdcp_info->rohc); /* Let RLC write to columns again */ col_set_writable(pinfo->cinfo, global_pdcp_lte_layer_to_show == ShowRLCLayer); } /* Initializes the hash tables each time a new * file is loaded or re-loaded in wireshark */ static void pdcp_lte_init_protocol(void) { /* Destroy any existing hashes. */ if (pdcp_sequence_analysis_channel_hash) { g_hash_table_destroy(pdcp_sequence_analysis_channel_hash); } if (pdcp_lte_sequence_analysis_report_hash) { g_hash_table_destroy(pdcp_lte_sequence_analysis_report_hash); } if (pdcp_security_hash) { g_hash_table_destroy(pdcp_security_hash); } if (pdcp_security_result_hash) { g_hash_table_destroy(pdcp_security_result_hash); } /* Now create them over */ pdcp_sequence_analysis_channel_hash = g_hash_table_new(pdcp_channel_hash_func, pdcp_channel_equal); pdcp_lte_sequence_analysis_report_hash = g_hash_table_new(pdcp_result_hash_func, pdcp_result_hash_equal); pdcp_security_hash = g_hash_table_new(pdcp_lte_ueid_hash_func, pdcp_lte_ueid_hash_equal); pdcp_security_result_hash = g_hash_table_new(pdcp_lte_ueid_frame_hash_func, pdcp_lte_ueid_frame_hash_equal); } void proto_register_pdcp(void) { static hf_register_info hf[] = { { &hf_pdcp_lte_configuration, { "Configuration", "pdcp-lte.configuration", FT_STRING, BASE_NONE, NULL, 0x0, "Configuration info passed into dissector", HFILL } }, { &hf_pdcp_lte_rohc_compression, { "ROHC Compression", "pdcp-lte.rohc.compression", FT_BOOLEAN, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_pdcp_lte_rohc_mode, { "ROHC Mode", "pdcp-lte.rohc.mode", FT_UINT8, BASE_DEC, VALS(rohc_mode_vals), 0x0, NULL, HFILL } }, { &hf_pdcp_lte_rohc_rnd, { "RND", "pdcp-lte.rohc.rnd", FT_UINT8, BASE_DEC, NULL, 0x0, "RND of outer ip header", HFILL } }, { &hf_pdcp_lte_rohc_udp_checksum_present, { "UDP Checksum", "pdcp-lte.rohc.checksum-present", FT_UINT8, BASE_DEC, NULL, 0x0, "UDP Checksum present", HFILL } }, { &hf_pdcp_lte_direction, { "Direction", "pdcp-lte.direction", FT_UINT8, BASE_DEC, VALS(direction_vals), 0x0, "Direction of message", HFILL } }, { &hf_pdcp_lte_ueid, { "UE", "pdcp-lte.ueid", FT_UINT16, BASE_DEC, 0, 0x0, "UE Identifier", HFILL } }, { &hf_pdcp_lte_channel_type, { "Channel type", "pdcp-lte.channel-type", FT_UINT8, BASE_DEC, VALS(logical_channel_vals), 0x0, NULL, HFILL } }, { &hf_pdcp_lte_channel_id, { "Channel Id", "pdcp-lte.channel-id", FT_UINT8, BASE_DEC, 0, 0x0, NULL, HFILL } }, { &hf_pdcp_lte_rohc_profile, { "ROHC profile", "pdcp-lte.rohc.profile", FT_UINT8, BASE_DEC, VALS(rohc_profile_vals), 0x0, "ROHC Mode", HFILL } }, { &hf_pdcp_lte_no_header_pdu, { "No Header PDU", "pdcp-lte.no-header_pdu", FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL } }, { &hf_pdcp_lte_plane, { "Plane", "pdcp-lte.plane", FT_UINT8, BASE_DEC, VALS(pdcp_plane_vals), 0x0, NULL, HFILL } }, { &hf_pdcp_lte_seqnum_length, { "Seqnum length", "pdcp-lte.seqnum_length", FT_UINT8, BASE_DEC, NULL, 0x0, "Sequence Number Length", HFILL } }, { &hf_pdcp_lte_cid_inclusion_info, { "CID Inclusion Info", "pdcp-lte.cid-inclusion-info", FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL } }, { &hf_pdcp_lte_large_cid_present, { "Large CID Present", "pdcp-lte.large-cid-present", FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL } }, { &hf_pdcp_lte_control_plane_reserved, { "Reserved", "pdcp-lte.reserved", FT_UINT8, BASE_DEC, NULL, 0xe0, NULL, HFILL } }, { &hf_pdcp_lte_seq_num_5, { "Seq Num", "pdcp-lte.seq-num", FT_UINT8, BASE_DEC, NULL, 0x1f, "PDCP Seq num", HFILL } }, { &hf_pdcp_lte_seq_num_7, { "Seq Num", "pdcp-lte.seq-num", FT_UINT8, BASE_DEC, NULL, 0x7f, "PDCP Seq num", HFILL } }, { &hf_pdcp_lte_reserved3, { "Reserved", "pdcp-lte.reserved3", FT_UINT8, BASE_HEX, NULL, 0x70, "3 reserved bits", HFILL } }, { &hf_pdcp_lte_seq_num_12, { "Seq Num", "pdcp-lte.seq-num", FT_UINT16, BASE_DEC, NULL, 0x0fff, "PDCP Seq num", HFILL } }, { &hf_pdcp_lte_seq_num_15, { "Seq Num", "pdcp-lte.seq-num", FT_UINT16, BASE_DEC, NULL, 0x7fff, "PDCP Seq num", HFILL } }, { &hf_pdcp_lte_signalling_data, { "Signalling Data", "pdcp-lte.signalling-data", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_pdcp_lte_mac, { "MAC", "pdcp-lte.mac", FT_UINT32, BASE_HEX_DEC, NULL, 0x0, NULL, HFILL } }, { &hf_pdcp_lte_data_control, { "PDU Type", "pdcp-lte.pdu-type", FT_UINT8, BASE_HEX, VALS(pdu_type_vals), 0x80, NULL, HFILL } }, { &hf_pdcp_lte_user_plane_data, { "User-Plane Data", "pdcp-lte.user-data", FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL } }, { &hf_pdcp_lte_control_pdu_type, { "Control PDU Type", "pdcp-lte.control-pdu-type", FT_UINT8, BASE_HEX, VALS(control_pdu_type_vals), 0x70, NULL, HFILL } }, { &hf_pdcp_lte_fms, { "First Missing Sequence Number", "pdcp-lte.fms", FT_UINT16, BASE_DEC, NULL, 0x0fff, "First Missing PDCP Sequence Number", HFILL } }, { &hf_pdcp_lte_reserved4, { "Reserved", "pdcp-lte.reserved4", FT_UINT16, BASE_HEX, NULL, 0x0f80, "5 reserved bits", HFILL } }, { &hf_pdcp_lte_fms2, { "First Missing Sequence Number", "pdcp-lte.fms", FT_UINT16, BASE_DEC, NULL, 0x07fff, "First Missing PDCP Sequence Number", HFILL } }, { &hf_pdcp_lte_bitmap, { "Bitmap", "pdcp-lte.bitmap", FT_NONE, BASE_NONE, NULL, 0x0, "Status report bitmap (0=error, 1=OK)", HFILL } }, { &hf_pdcp_lte_sequence_analysis, { "Sequence Analysis", "pdcp-lte.sequence-analysis", FT_STRING, BASE_NONE, 0, 0x0, NULL, HFILL } }, { &hf_pdcp_lte_sequence_analysis_ok, { "OK", "pdcp-lte.sequence-analysis.ok", FT_BOOLEAN, BASE_NONE, 0, 0x0, NULL, HFILL } }, { &hf_pdcp_lte_sequence_analysis_previous_frame, { "Previous frame for channel", "pdcp-lte.sequence-analysis.previous-frame", FT_FRAMENUM, BASE_NONE, 0, 0x0, NULL, HFILL } }, { &hf_pdcp_lte_sequence_analysis_next_frame, { "Next frame for channel", "pdcp-lte.sequence-analysis.next-frame", FT_FRAMENUM, BASE_NONE, 0, 0x0, NULL, HFILL } }, { &hf_pdcp_lte_sequence_analysis_expected_sn, { "Expected SN", "pdcp-lte.sequence-analysis.expected-sn", FT_UINT16, BASE_DEC, 0, 0x0, NULL, HFILL } }, { &hf_pdcp_lte_sequence_analysis_skipped, { "Skipped frames", "pdcp-lte.sequence-analysis.skipped-frames", FT_BOOLEAN, BASE_NONE, 0, 0x0, NULL, HFILL } }, { &hf_pdcp_lte_sequence_analysis_repeated, { "Repeated frame", "pdcp-lte.sequence-analysis.repeated-frame", FT_BOOLEAN, BASE_NONE, 0, 0x0, NULL, HFILL } }, { &hf_pdcp_lte_security, { "Security Config", "pdcp-lte.security-cofig", FT_STRING, BASE_NONE, 0, 0x0, NULL, HFILL } }, { &hf_pdcp_lte_security_setup_frame, { "Configuration frame", "pdcp-lte.security-config.setup-frame", FT_FRAMENUM, BASE_NONE, 0, 0x0, NULL, HFILL } }, { &hf_pdcp_lte_security_integrity_algorithm, { "Integrity Algorithm", "pdcp-lte.security-config.integrity", FT_UINT16, BASE_DEC, VALS(integrity_algorithm_vals), 0x0, NULL, HFILL } }, { &hf_pdcp_lte_security_ciphering_algorithm, { "Ciphering Algorithm", "pdcp-lte.security-config.ciphering", FT_UINT16, BASE_DEC, VALS(ciphering_algorithm_vals), 0x0, NULL, HFILL } }, }; static gint *ett[] = { &ett_pdcp, &ett_pdcp_configuration, &ett_pdcp_packet, &ett_pdcp_lte_sequence_analysis, &ett_pdcp_report_bitmap, &ett_pdcp_security }; static ei_register_info ei[] = { { &ei_pdcp_lte_sequence_analysis_sn_missing, { "pdcp-lte.sequence-analysis.sn-missing", PI_SEQUENCE, PI_WARN, "PDCP SN missing", EXPFILL }}, { &ei_pdcp_lte_sequence_analysis_sn_repeated, { "pdcp-lte.sequence-analysis.sn-repeated", PI_SEQUENCE, PI_WARN, "PDCP SN repeated", EXPFILL }}, { &ei_pdcp_lte_sequence_analysis_wrong_sequence_number, { "pdcp-lte.sequence-analysis.wrong-sequence-number", PI_SEQUENCE, PI_WARN, "Wrong Sequence Number", EXPFILL }}, { &ei_pdcp_lte_reserved_bits_not_zero, { "pdcp-lte.reserved-bits-not-zero", PI_MALFORMED, PI_ERROR, "Reserved bits not zero", EXPFILL }}, }; static const enum_val_t sequence_analysis_vals[] = { {"no-analysis", "No-Analysis", FALSE}, {"rlc-only", "Only-RLC-frames", SEQUENCE_ANALYSIS_RLC_ONLY}, {"pdcp-only", "Only-PDCP-frames", SEQUENCE_ANALYSIS_PDCP_ONLY}, {NULL, NULL, -1} }; static const enum_val_t show_info_col_vals[] = { {"show-rlc", "RLC Info", ShowRLCLayer}, {"show-pdcp", "PDCP Info", ShowPDCPLayer}, {"show-traffic", "Traffic Info", ShowTrafficLayer}, {NULL, NULL, -1} }; module_t *pdcp_lte_module; expert_module_t* expert_pdcp_lte; /* Register protocol. */ proto_pdcp_lte = proto_register_protocol("PDCP-LTE", "PDCP-LTE", "pdcp-lte"); proto_register_field_array(proto_pdcp_lte, hf, array_length(hf)); proto_register_subtree_array(ett, array_length(ett)); expert_pdcp_lte = expert_register_protocol(proto_pdcp_lte); expert_register_field_array(expert_pdcp_lte, ei, array_length(ei)); /* Allow other dissectors to find this one by name. */ register_dissector("pdcp-lte", dissect_pdcp_lte, proto_pdcp_lte); pdcp_lte_module = prefs_register_protocol(proto_pdcp_lte, NULL); /* Obsolete preferences */ prefs_register_obsolete_preference(pdcp_lte_module, "show_feedback_option_tag_length"); /* Dissect uncompressed user-plane data as IP */ prefs_register_bool_preference(pdcp_lte_module, "show_user_plane_as_ip", "Show uncompressed User-Plane data as IP", "Show uncompressed User-Plane data as IP", &global_pdcp_dissect_user_plane_as_ip); /* Dissect unciphered signalling data as RRC */ prefs_register_bool_preference(pdcp_lte_module, "show_signalling_plane_as_rrc", "Show unciphered Signalling-Plane data as RRC", "Show unciphered Signalling-Plane data as RRC", &global_pdcp_dissect_signalling_plane_as_rrc); /* Check for missing sequence numbers */ prefs_register_enum_preference(pdcp_lte_module, "check_sequence_numbers", "Do sequence number analysis", "Do sequence number analysis", &global_pdcp_check_sequence_numbers, sequence_analysis_vals, FALSE); /* Attempt to dissect ROHC messages */ prefs_register_bool_preference(pdcp_lte_module, "dissect_rohc", "Attempt to decode ROHC data", "Attempt to decode ROHC data", &global_pdcp_dissect_rohc); prefs_register_bool_preference(pdcp_lte_module, "heuristic_pdcp_lte_over_udp", "Try Heuristic LTE-PDCP over UDP framing", "When enabled, use heuristic dissector to find PDCP-LTE frames sent with " "UDP framing", &global_pdcp_lte_heur); prefs_register_enum_preference(pdcp_lte_module, "layer_to_show", "Which layer info to show in Info column", "Can show RLC, PDCP or Traffic layer info in Info column", &global_pdcp_lte_layer_to_show, show_info_col_vals, FALSE); register_init_routine(&pdcp_lte_init_protocol); } void proto_reg_handoff_pdcp_lte(void) { /* Add as a heuristic UDP dissector */ heur_dissector_add("udp", dissect_pdcp_lte_heur, proto_pdcp_lte); ip_handle = find_dissector("ip"); ipv6_handle = find_dissector("ipv6"); rohc_handle = find_dissector("rohc"); data_handle = find_dissector("data"); }