Added casts wanted by win64 compiler.
[obnox/wireshark/wip.git] / epan / dissectors / packet-rlc-lte.c
index bc7730fca6de86daf91ac0711d0aedb45f73d138..ca7c292d686e230ffe8f93d264a975d4c37f58d7 100644 (file)
 #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;
@@ -230,6 +237,8 @@ static const value_string am_e2_vals[] =
 };
 
 
+extern int proto_pdcp_lte;
+
 
 /**********************************************************************************/
 /* These are for keeping track of UM/AM extension headers, and the lengths found  */
@@ -333,6 +342,54 @@ static void show_PDU_in_info(packet_info *pinfo,
 }
 
 
+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                                           */
@@ -603,6 +660,59 @@ static void checkChannelSequenceInfo(packet_info *pinfo, tvbuff_t *tvb,
 
 
 
+/***************************************************/
+/* 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
+    }
+}
+
 
 
 /***************************************************/
@@ -611,7 +721,8 @@ 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)
+                               proto_item *top_ti,
+                               rlc_lte_tap_info *tap_info)
 {
     guint64 framing_info;
     gboolean first_includes_start;
@@ -690,6 +801,8 @@ static void dissect_rlc_lte_um(tvbuff_t *tvb, packet_info *pinfo,
         return;
     }
 
+    tap_info->sequenceNumber = (guint16)sn;
+
     /* Show SN in info column */
     col_append_fstr(pinfo->cinfo, COL_INFO, "  SN=%04u", (guint16)sn);
 
@@ -749,7 +862,8 @@ static void dissect_rlc_lte_am_status_pdu(tvbuff_t *tvb,
                                           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;
@@ -786,6 +900,7 @@ static void dissect_rlc_lte_am_status_pdu(tvbuff_t *tvb,
     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,
@@ -809,9 +924,7 @@ static void dissect_rlc_lte_am_status_pdu(tvbuff_t *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,
@@ -821,6 +934,17 @@ static void dissect_rlc_lte_am_status_pdu(tvbuff_t *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++;
         }
 
@@ -851,6 +975,7 @@ static void dissect_rlc_lte_am_status_pdu(tvbuff_t *tvb,
 
     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 */
@@ -871,8 +996,9 @@ static void dissect_rlc_lte_am_status_pdu(tvbuff_t *tvb,
 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;
@@ -886,7 +1012,7 @@ static void dissect_rlc_lte_am(tvbuff_t *tvb, packet_info *pinfo,
     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,
@@ -900,6 +1026,7 @@ static void dissect_rlc_lte_am(tvbuff_t *tvb, packet_info *pinfo,
     /* 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) {
@@ -907,7 +1034,8 @@ static void dissect_rlc_lte_am(tvbuff_t *tvb, packet_info *pinfo,
         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;
     }
 
@@ -917,6 +1045,7 @@ static void dissect_rlc_lte_am(tvbuff_t *tvb, packet_info *pinfo,
     /* 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]");
@@ -943,13 +1072,13 @@ static void dissect_rlc_lte_am(tvbuff_t *tvb, packet_info *pinfo,
     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 */
@@ -974,6 +1103,8 @@ static void dissect_rlc_lte_am(tvbuff_t *tvb, packet_info *pinfo,
         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;
@@ -996,7 +1127,8 @@ static void dissect_rlc_lte_am(tvbuff_t *tvb, packet_info *pinfo,
         /* 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);
@@ -1007,19 +1139,148 @@ static void dissect_rlc_lte_am(tvbuff_t *tvb, packet_info *pinfo,
 
     /* 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. */
@@ -1034,6 +1295,10 @@ void dissect_rlc_lte(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
     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");
 
@@ -1056,7 +1321,6 @@ void dissect_rlc_lte(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
 
     /*****************************************/
     /* 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);
@@ -1133,6 +1397,16 @@ void dissect_rlc_lte(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
                         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;
 
@@ -1140,18 +1414,17 @@ void dissect_rlc_lte(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
     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:
@@ -1167,6 +1440,11 @@ void dissect_rlc_lte(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
                                    "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);
+    }
 }
 
 
@@ -1479,6 +1757,9 @@ void proto_register_rlc_lte(void)
     /* 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);
 
@@ -1487,7 +1768,34 @@ void proto_register_rlc_lte(void)
         "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);
+    }
+}