#include <epan/packet.h>
#include <epan/expert.h>
#include <epan/prefs.h>
+#include <epan/tap.h>
#include "packet-rlc-lte.h"
+#include "packet-pdcp-lte.h"
/* Described in:
/* By default try to analyse the sequence of messages for UM channels */
static gboolean global_rlc_lte_sequence_analysis = TRUE;
+/* By default don't call PDCP/RRC dissectors for SDU data */
+static gboolean global_rlc_lte_call_pdcp = FALSE;
+static gboolean global_rlc_lte_call_rrc = FALSE;
/* Initialize the protocol and registered fields. */
int proto_rlc_lte = -1;
+static int rlc_lte_tap = -1;
+
/* Decoding context */
static int hf_rlc_lte_context_mode = -1;
static int hf_rlc_lte_context_direction = -1;
};
+extern int proto_pdcp_lte;
+
/**********************************************************************************/
/* These are for keeping track of UM/AM extension headers, and the lengths found */
}
+static void show_AM_PDU_in_tree(packet_info *pinfo, proto_tree *tree, tvbuff_t *tvb, gint offset, gint length,
+ rlc_lte_info *rlc_info, gboolean whole_pdu)
+{
+ proto_item *data_ti = proto_tree_add_item(tree, hf_rlc_lte_am_data, tvb, offset, length, FALSE);
+
+ /* Decode signalling PDUs as PDCP */
+ if (global_rlc_lte_call_pdcp && whole_pdu) {
+ if (rlc_info->channelType == CHANNEL_TYPE_SRB) {
+ /* Attempt to decode payload using LTE PDCP dissector */
+ tvbuff_t *pdcp_tvb = tvb_new_subset(tvb, offset, length, length);
+ volatile dissector_handle_t protocol_handle;
+
+ struct pdcp_lte_info *p_pdcp_lte_info;
+
+ /* Reuse or allocate struct */
+ p_pdcp_lte_info = p_get_proto_data(pinfo->fd, proto_pdcp_lte);
+ if (p_pdcp_lte_info == NULL) {
+ p_pdcp_lte_info = se_alloc0(sizeof(struct pdcp_lte_info));
+ }
+ if (p_pdcp_lte_info == NULL) {
+ return;
+ }
+
+ p_pdcp_lte_info->channelType = Channel_DCCH;
+ p_pdcp_lte_info->direction = rlc_info->direction;
+ p_pdcp_lte_info->no_header_pdu = FALSE;
+ p_pdcp_lte_info->plane = SIGNALING_PLANE;
+
+ p_pdcp_lte_info->rohc_compression = FALSE;
+
+ /* Store info in packet */
+ p_add_proto_data(pinfo->fd, proto_pdcp_lte, p_pdcp_lte_info);
+
+ /* Get dissector handle */
+ protocol_handle = find_dissector("pdcp-lte");
+
+ TRY {
+ call_dissector_only(protocol_handle, pdcp_tvb, pinfo, tree);
+ }
+ CATCH_ALL {
+ }
+ ENDTRY
+
+ PROTO_ITEM_SET_HIDDEN(data_ti);
+ }
+ }
+}
+
/*********************************************************************/
/* UM/AM sequence analysis */
+/***************************************************/
+/* Unacknowledged mode PDU */
+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 _U_)
+{
+ proto_item *raw_tm_ti;
+
+ /* Remaining bytes are all data */
+ raw_tm_ti = proto_tree_add_item(tree, hf_rlc_lte_tm_data, tvb, offset, -1, FALSE);
+ if (!global_rlc_lte_call_rrc) {
+ col_append_fstr(pinfo->cinfo, COL_INFO, " [%u-bytes]",
+ tvb_length_remaining(tvb, offset));
+ }
+
+ if (global_rlc_lte_call_rrc) {
+ tvbuff_t *rrc_tvb = tvb_new_subset(tvb, offset, -1, tvb_length_remaining(tvb, offset));
+ volatile dissector_handle_t protocol_handle = 0;
+
+ switch (p_rlc_lte_info->channelType) {
+ case CHANNEL_TYPE_CCCH:
+ if (p_rlc_lte_info->direction == DIRECTION_UPLINK) {
+ protocol_handle = find_dissector("lte-rrc.ul.ccch");
+ }
+ else {
+ protocol_handle = find_dissector("lte-rrc.dl.ccch");
+ }
+ break;
+
+ case CHANNEL_TYPE_BCCH:
+ /* TODO: Problem is don't know which transport channel... */
+ return;
+
+ case CHANNEL_TYPE_PCCH:
+ protocol_handle = find_dissector("lte-rrc.pcch");
+ break;
+ }
+
+ /* 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);
+ }
+ CATCH_ALL {
+ }
+ ENDTRY
+ }
+}
+
/***************************************************/
proto_tree *tree,
int offset,
rlc_lte_info *p_rlc_lte_info,
- proto_item *top_ti)
+ proto_item *top_ti,
+ rlc_lte_tap_info *tap_info)
{
guint64 framing_info;
gboolean first_includes_start;
return;
}
+ tap_info->sequenceNumber = (guint16)sn;
+
/* Show SN in info column */
col_append_fstr(pinfo->cinfo, COL_INFO, " SN=%04u", (guint16)sn);
proto_tree *tree,
proto_item *status_ti,
int offset,
- proto_item *top_ti)
+ proto_item *top_ti,
+ rlc_lte_tap_info *tap_info)
{
guint8 cpt;
guint64 ack_sn, nack_sn;
col_append_fstr(pinfo->cinfo, COL_INFO, " ACK_SN=%u", (guint16)ack_sn);
proto_item_append_text(top_ti, " ACK_SN=%u", (guint16)ack_sn);
proto_item_append_text(status_ti, " ACK_SN=%u", (guint16)ack_sn);
+ tap_info->ACKNo = (guint16)ack_sn;
/* E1 */
proto_tree_add_bits_ret_val(tree, hf_rlc_lte_am_e1, tvb,
bit_offset += 10;
col_append_fstr(pinfo->cinfo, COL_INFO, " NACK_SN=%u", (guint16)nack_sn);
proto_item_append_text(top_ti, " NACK_SN=%u", (guint16)nack_sn);
- expert_add_info_format(pinfo, nack_ti, PI_SEQUENCE, PI_WARN,
- "Status PDU reports NACK for SN=%u", (guint16)nack_sn);
-
+ tap_info->NACKs[nack_count] = (guint16)nack_sn;
/* E1 */
proto_tree_add_bits_ret_val(tree, hf_rlc_lte_am_e1, tvb,
/* E2 */
proto_tree_add_bits_ret_val(tree, hf_rlc_lte_am_e2, tvb,
bit_offset, 1, &e2, FALSE);
+
+ /* Report as expert info */
+ if (e2) {
+ expert_add_info_format(pinfo, nack_ti, PI_SEQUENCE, PI_WARN,
+ "Status PDU reports NACK for SN=%u (partial)", (guint16)nack_sn);
+ }
+ else {
+ expert_add_info_format(pinfo, nack_ti, PI_SEQUENCE, PI_WARN,
+ "Status PDU reports NACK for SN=%u", (guint16)nack_sn);
+ }
+
bit_offset++;
}
if (nack_count > 0) {
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 */
static void dissect_rlc_lte_am(tvbuff_t *tvb, packet_info *pinfo,
proto_tree *tree,
int offset,
- rlc_lte_info *p_rlc_lte_info _U_,
- proto_item *top_ti)
+ rlc_lte_info *p_rlc_lte_info,
+ proto_item *top_ti,
+ rlc_lte_tap_info *tap_info)
{
guint8 is_data;
guint8 is_segment;
gint start_offset = offset;
guint16 sn;
- /* Add UM header subtree */
+ /* Add AM header subtree */
am_header_ti = proto_tree_add_string_format(tree,
hf_rlc_lte_am_header,
tvb, offset, 0,
/* First bit is Data/Control flag */
is_data = (tvb_get_guint8(tvb, offset) & 0x80) >> 7;
proto_tree_add_item(am_header_tree, hf_rlc_lte_am_data_control, tvb, offset, 1, FALSE);
+ tap_info->isControlPDU = !is_data;
/**************************************************/
if (!is_data) {
proto_item_append_text(top_ti, " [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);
+ dissect_rlc_lte_am_status_pdu(tvb, pinfo, am_header_tree, am_header_ti,
+ offset, top_ti, tap_info);
return;
}
/* Re-segmentation Flag (RF) field */
is_segment = (tvb_get_guint8(tvb, offset) & 0x40) >> 6;
proto_tree_add_item(am_header_tree, hf_rlc_lte_am_rf, tvb, offset, 1, FALSE);
+ tap_info->isResegmented = is_segment;
col_append_str(pinfo->cinfo, COL_INFO, (is_segment) ? " [DATA-SEGMENT]" : " [DATA]");
proto_item_append_text(top_ti, (is_segment) ? " [DATA-SEGMENT]" : " [DATA]");
sn = tvb_get_ntohs(tvb, offset) & 0x03ff;
proto_tree_add_item(am_header_tree, hf_rlc_lte_am_fixed_sn, tvb, offset, 2, FALSE);
offset += 2;
+ tap_info->sequenceNumber = sn;
col_append_fstr(pinfo->cinfo, COL_INFO, "sn=%u", sn);
proto_item_append_text(top_ti, " (SN=%u)", sn);
/* Show SN in AM header root */
proto_item_append_text(am_header_ti, " (SN=%u)", sn);
- proto_item_set_len(am_header_ti, offset-start_offset);
/***************************************/
/* Dissect extra segment header fields */
offset = dissect_rlc_lte_extension_header(tvb, pinfo, tree, offset);
}
+ /* Head is now complete */
+ proto_item_set_len(am_header_ti, offset-start_offset);
/* Extract these 2 flags from framing_info */
first_includes_start = (framing_info & 0x02) == 0;
/* Show each data segment separately */
int n;
for (n=0; n < s_number_of_extensions; n++) {
- proto_tree_add_item(tree, hf_rlc_lte_am_data, tvb, offset, s_lengths[n], FALSE);
+ show_AM_PDU_in_tree(pinfo, tree, tvb, offset, s_lengths[n], p_rlc_lte_info,
+ first_includes_start && last_includes_end);
show_PDU_in_info(pinfo, top_ti, s_lengths[n],
(n==0) ? first_includes_start : TRUE,
TRUE);
/* Final data element */
if (tvb_length_remaining(tvb, offset) > 0) {
- proto_tree_add_item(tree, hf_rlc_lte_am_data, tvb, offset, -1, FALSE);
+ show_AM_PDU_in_tree(pinfo, tree, tvb, offset, -1, p_rlc_lte_info,
+ first_includes_start && last_includes_end);
show_PDU_in_info(pinfo, top_ti, (guint16)tvb_length_remaining(tvb, offset),
(s_number_of_extensions == 0) ? first_includes_start : TRUE,
last_includes_end);
}
else {
- expert_add_info_format(pinfo, am_header_ti, PI_MALFORMED, PI_WARN,
- "AM data PDU doesn't contain any data");
+ if (s_number_of_extensions > 0) {
+ expert_add_info_format(pinfo, am_header_ti, PI_MALFORMED, PI_ERROR,
+ "AM data PDU doesn't contain any data beyond extensions");
+ }
+ else {
+ expert_add_info_format(pinfo, am_header_ti, PI_MALFORMED, PI_ERROR,
+ "AM data PDU doesn't contain any data");
+ }
}
}
+/* Forwad declarations */
+void proto_reg_handoff_rlc_lte(void);
+void dissect_rlc_lte(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree);
+
+/* Heuristic dissection */
+static gboolean global_rlc_lte_heur = FALSE;
+
+/* Heuristic dissector looks for supported framing protocol (see wiki page) */
+static gboolean dissect_rlc_lte_heur(tvbuff_t *tvb, packet_info *pinfo,
+ proto_tree *tree)
+{
+ gint offset = 0;
+ struct rlc_lte_info *p_rlc_lte_info;
+ tvbuff_t *rlc_tvb;
+ guint8 tag = 0;
+ gboolean infoAlreadySet = FALSE;
+ gboolean umSeqNumLengthTagPresent = 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_rlc_lte_heur) {
+ return FALSE;
+ }
+
+ /* If redissecting, use previous info struct (if available) */
+ p_rlc_lte_info = p_get_proto_data(pinfo->fd, proto_rlc_lte);
+ if (p_rlc_lte_info == NULL) {
+ /* Allocate new info struct for this frame */
+ p_rlc_lte_info = se_alloc0(sizeof(struct rlc_lte_info));
+ if (p_rlc_lte_info == NULL) {
+ return FALSE;
+ }
+ infoAlreadySet = FALSE;
+ }
+ else {
+ infoAlreadySet = TRUE;
+ }
+
+ /* 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 RLC PDU payload */
+ if ((size_t)tvb_length_remaining(tvb, offset) < (strlen(RLC_LTE_START_STRING)+1+2)) {
+ return FALSE;
+ }
+
+ /* OK, compare with signature string */
+ if (tvb_strneql(tvb, offset, RLC_LTE_START_STRING, (gint)strlen(RLC_LTE_START_STRING)) != 0) {
+ return FALSE;
+ }
+ offset += (gint)strlen(RLC_LTE_START_STRING);
+
+ /* Read fixed fields */
+ p_rlc_lte_info->rlcMode = tvb_get_guint8(tvb, offset++);
+
+ /* Read optional fields */
+ while (tag != RLC_LTE_PAYLOAD_TAG) {
+ /* Process next tag */
+ tag = tvb_get_guint8(tvb, offset++);
+ switch (tag) {
+ case RLC_LTE_UM_SN_LENGTH_TAG:
+ p_rlc_lte_info->UMSequenceNumberLength = tvb_get_guint8(tvb, offset);
+ offset++;
+ umSeqNumLengthTagPresent = TRUE;
+ break;
+ case RLC_LTE_DIRECTION_TAG:
+ p_rlc_lte_info->direction = tvb_get_guint8(tvb, offset);
+ offset++;
+ break;
+ case RLC_LTE_PRIORITY_TAG:
+ p_rlc_lte_info->priority = tvb_get_guint8(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_PAYLOAD_TAG:
+ /* Have reached data, so set payload length and get out of loop */
+ p_rlc_lte_info->pduLength= tvb_length_remaining(tvb, offset);
+ continue;
+
+ default:
+ /* It must be a recognised tag */
+ return FALSE;
+ }
+ }
+
+ if ((p_rlc_lte_info->rlcMode == RLC_UM_MODE) && (umSeqNumLengthTagPresent == FALSE)) {
+ /* Conditional field is not present */
+ return FALSE;
+ }
+
+ if (!infoAlreadySet) {
+ /* Store info in packet */
+ p_add_proto_data(pinfo->fd, proto_rlc_lte, p_rlc_lte_info);
+ }
+
+ /**************************************/
+ /* OK, now dissect as RLC LTE */
+
+ /* Create tvb that starts at actual RLC PDU */
+ rlc_tvb = tvb_new_subset(tvb, offset, -1, tvb_reported_length(tvb)-offset);
+ dissect_rlc_lte(rlc_tvb, pinfo, tree);
+ return TRUE;
+}
+
/*****************************/
/* Main dissection function. */
gint offset = 0;
struct rlc_lte_info *p_rlc_lte_info = NULL;
+ /* Zero out tap */
+ static rlc_lte_tap_info tap_info;
+ memset(&tap_info, 0, sizeof(rlc_lte_tap_info));
+
/* Set protocol name */
col_set_str(pinfo->cinfo, COL_PROTOCOL, "RLC-LTE");
/*****************************************/
/* Show context information */
- /* TODO: hide inside own tree? */
ti = proto_tree_add_uint(rlc_lte_tree, hf_rlc_lte_context_direction,
tvb, 0, 0, p_rlc_lte_info->direction);
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.UMSequenceNumberLength = p_rlc_lte_info->UMSequenceNumberLength;
+
/* Reset this count */
s_number_of_extensions = 0;
switch (p_rlc_lte_info->rlcMode) {
case RLC_TM_MODE:
- /* Remaining bytes are all data */
- proto_tree_add_item(rlc_lte_tree, hf_rlc_lte_tm_data, tvb, offset, -1, FALSE);
- col_append_fstr(pinfo->cinfo, COL_INFO, " [%u-bytes]",
- tvb_length_remaining(tvb, offset));
+ 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);
+ 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);
+ dissect_rlc_lte_am(tvb, pinfo, rlc_lte_tree, offset, p_rlc_lte_info, top_ti,
+ &tap_info);
break;
case RLC_PREDEF:
"Unrecognised RLC Mode set (%u)", p_rlc_lte_info->rlcMode);
break;
}
+
+ /* Queue tap info */
+ if (!pinfo->in_error_pkt) {
+ tap_queue_packet(rlc_lte_tap, pinfo, &tap_info);
+ }
}
/* 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-lte");
+
/* Preferences */
rlc_lte_module = prefs_register_protocol(proto_rlc_lte, NULL);
"Attempt to keep track of PDUs for UM channels, and point out problems",
&global_rlc_lte_sequence_analysis);
+ 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);
+
+ 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);
+
+ prefs_register_bool_preference(rlc_lte_module, "heuristic_rlc_lte_over_udp",
+ "Try Heuristic LTE-RLC over UDP framing",
+ "When enabled, use heuristic dissector to find RLC-LTE frames sent with "
+ "UDP framing",
+ &global_rlc_lte_heur);
+
register_init_routine(&rlc_lte_init_protocol);
}
+void
+proto_reg_handoff_rlc_lte(void)
+{
+ static dissector_handle_t rlc_lte_handle;
+ if (!rlc_lte_handle) {
+ rlc_lte_handle = find_dissector("rlc-lte");
+ /* Add as a heuristic UDP dissector */
+ heur_dissector_add("udp", dissect_rlc_lte_heur, proto_rlc_lte);
+ }
+}