]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Merge pull request #664 in SNORT/snort3 from dce_udp_processing to master
authorMichael Altizer (mialtize) <mialtize@cisco.com>
Tue, 11 Oct 2016 16:43:23 +0000 (12:43 -0400)
committerMichael Altizer (mialtize) <mialtize@cisco.com>
Tue, 11 Oct 2016 16:43:23 +0000 (12:43 -0400)
Squashed commit of the following:

commit 83de4f6329512279a3c41924ce6260cf0e903c5a
Author: mdagon <mdagon@cisco.com>
Date:   Tue Oct 11 08:30:04 2016 -0400

    Code review comments

commit 945cab36b195269ff5eebd7f6ea9cbbd8de0d99b
Author: mdagon <mdagon@cisco.com>
Date:   Fri Sep 30 15:42:39 2016 -0400

    dce_udp packet processing

src/service_inspectors/dce_rpc/CMakeLists.txt
src/service_inspectors/dce_rpc/Makefile.am
src/service_inspectors/dce_rpc/dce_common.cc
src/service_inspectors/dce_rpc/dce_udp.cc
src/service_inspectors/dce_rpc/dce_udp.h
src/service_inspectors/dce_rpc/dce_udp_processing.cc [new file with mode: 0644]

index 4f560a6b491bdc91d0dd7b0ada8bb9e054205328..32bcb28365fd7fe9f82e0f2649b223a0f2612fae 100644 (file)
@@ -32,6 +32,7 @@ set( FILE_LIST
    dce_udp.h 
    dce_udp_module.cc 
    dce_udp_module.h
+   dce_udp_processing.cc
    dce_utils.cc
    dce_utils.h
    ips_dce_iface.cc
index 5d563eeb7bc023825266241e4b87fcb448d8c6da..3bd481622a7744efa9de0899822e24a3d98655b9 100644 (file)
@@ -31,6 +31,7 @@ dce_udp.cc\
 dce_udp.h \
 dce_udp_module.cc \
 dce_udp_module.h \
+dce_udp_processing.cc \
 dce_utils.cc \
 dce_utils.h \
 ips_dce_iface.cc \
index a4b37290771b173922b05932fdeab307126a08f1..9b5d8c478d3a15de01f46a6528e3ccd3d8e881cb 100644 (file)
@@ -27,6 +27,7 @@
 
 #include "dce_smb_utils.h"
 #include "dce_tcp.h"
+#include "dce_udp.h"
 
 THREAD_LOCAL int dce2_detected = 0;
 THREAD_LOCAL DCE2_CStack* dce2_pkt_stack = nullptr;
@@ -177,11 +178,15 @@ static void dce2_protocol_detect(DCE2_SsnData* sd, Packet* pkt)
     {
         Profile profile(dce2_tcp_pstat_detect);
     }
-    else
+    else if  (sd->trans == DCE2_TRANS_TYPE__SMB)
     {
         Profile profile(dce2_smb_pstat_detect);
-    }
-    // FIXIT-M add HTTP, UDP cases when these are ported
+    } 
+       else 
+       {
+               Profile profile(dce2_udp_pstat_detect);
+       }
+    // FIXIT-M add HTTP case when these are ported
     // Same for all other instances of profiling
 
     SnortEventqPush();
@@ -234,7 +239,14 @@ DCE2_SsnData* get_dce2_session_data(Packet* p)
         return sd;
     }
 
-    // FIXIT-L add checks for http, udp once ported
+       DCE2_UdpSsnData* udp_data = get_dce2_udp_session_data(p->flow);
+    sd = (udp_data != nullptr) ? &(udp_data->sd) : nullptr;
+    if ((sd != nullptr) && (sd->trans == DCE2_TRANS_TYPE__UDP))
+    {
+        return sd;
+    }
+
+    // FIXIT-L add checks for http once ported
 
     return nullptr;
 }
@@ -299,10 +311,14 @@ static void dce_push_pkt_log(Packet* pkt,DCE2_SsnData* sd)
     {
         Profile profile(dce2_tcp_pstat_log);
     }
-    else
+    else if (sd->trans == DCE2_TRANS_TYPE__SMB)
     {
         Profile profile(dce2_smb_pstat_log);
     }
+       else
+       {
+        Profile profile(dce2_udp_pstat_log);
+    }
 
     SnortEventqPush();
     SnortEventqLog(pkt);
@@ -334,6 +350,10 @@ void DCE2_PopPkt(DCE2_SsnData* sd)
     if (sd->trans == DCE2_TRANS_TYPE__TCP)
     {
         Profile profile(dce2_tcp_pstat_log);
+    }
+       else if (sd->trans == DCE2_TRANS_TYPE__UDP)
+    {
+        Profile profile(dce2_udp_pstat_log);
     }
     else
     {
index 284e3c176224e7515daf00397e4f7495ab503d9a..07b54562ddf58aaa5de1c57fad546816f149b478 100644 (file)
@@ -206,10 +206,30 @@ void Dce2Udp::eval(Packet* p)
 
     if (dce2_udp_sess)
     {
-        dce2_udp_stats.udp_pkts++;
+        if (DCE2_PushPkt(p,&dce2_udp_sess->sd) != DCE2_RET__SUCCESS)
+        {
+            DebugMessage(DEBUG_DCE_UDP, "Failed to push packet onto packet stack.\n");
+            return;
+        }
+        p->packet_flags |= PKT_ALLOW_MULTIPLE_DETECT;
+        dce2_detected = 0;
+
+        p->endianness = (Endianness*)new DceEndianness();
+
+               dce2_udp_stats.udp_pkts++;
+               DCE2_ClProcess(&dce2_udp_sess->sd, &dce2_udp_sess->cl_tracker);
+
+        if (!dce2_detected)
+            DCE2_Detect(&dce2_udp_sess->sd);
+
+        DCE2_ResetRopts(&dce2_udp_sess->sd.ropts);
+        DCE2_PopPkt(&dce2_udp_sess->sd);
 
         if (!DCE2_SsnAutodetected(&dce2_udp_sess->sd))
             DisableInspection();
+
+               delete p->endianness;
+        p->endianness = nullptr;
     }
 }
 
@@ -247,12 +267,25 @@ static void dce2_udp_init()
 
 static void dce2_udp_thread_init()
 {
+       if (dce2_inspector_instances == 0)
+    {
+        dce2_pkt_stack = DCE2_CStackNew(DCE2_PKT_STACK__SIZE, nullptr);
+    }
+
     dce2_udp_inspector_instances++;
+       dce2_inspector_instances++;
 }
 
 static void dce2_udp_thread_term()
 {
+       dce2_inspector_instances--;
     dce2_udp_inspector_instances--;
+
+    if (dce2_inspector_instances == 0)
+    {
+        DCE2_CStackDestroy(dce2_pkt_stack);
+        dce2_pkt_stack = nullptr;
+    }
 }
 
 const InspectApi dce2_udp_api =
index bfefeafa1713145ba5471aef506e867b5e0dc4b7..a2f6fd528c10d6ae7ad307bbee0fdb33c35bc7f3 100644 (file)
@@ -42,7 +42,6 @@ struct dce2UdpStats
     /*DCE UDP specific*/
     PegCount udp_sessions;
     PegCount udp_pkts;
-    PegCount cl_pkts;
     PegCount cl_request;
     PegCount cl_ack;
     PegCount cl_cancel;
@@ -97,6 +96,18 @@ struct DceRpcClHdr   /* Connectionless header */
 
 };
 
+enum DceRpcClFlags1
+{
+    DCERPC_CL_FLAGS1__RESERVED_01 = 0x01,
+    DCERPC_CL_FLAGS1__LASTFRAG = 0x02,
+    DCERPC_CL_FLAGS1__FRAG = 0x04,
+    DCERPC_CL_FLAGS1__NOFACK = 0x08,
+    DCERPC_CL_FLAGS1__MAYBE = 0x10,
+    DCERPC_CL_FLAGS1__IDEMPOTENT = 0x20,
+    DCERPC_CL_FLAGS1__BROADCAST = 0x40,
+    DCERPC_CL_FLAGS1__RESERVED_80 = 0x80
+};
+
 inline uint8_t DceRpcClRpcVers(const DceRpcClHdr *cl)
 {
     return cl->rpc_vers;
@@ -117,6 +128,59 @@ inline uint16_t DceRpcClLen(const DceRpcClHdr *cl)
     return DceRpcNtohs(&cl->len, DceRpcClByteOrder(cl));
 }
 
+inline uint16_t DceRpcClOpnum(const DceRpcClHdr *cl)
+{
+    return DceRpcNtohs(&cl->opnum, DceRpcClByteOrder(cl));
+}
+
+inline uint32_t DceRpcClSeqNum(const DceRpcClHdr *cl)
+{
+    return DceRpcNtohl(&cl->seqnum, DceRpcClByteOrder(cl));
+}
+
+inline const Uuid * DceRpcClIface(const DceRpcClHdr *cl)
+{
+    return &cl->if_id;
+}
+
+inline uint32_t DceRpcClIfaceVers(const DceRpcClHdr *cl)
+{
+    return DceRpcNtohl(&cl->if_vers, DceRpcClByteOrder(cl));
+}
+
+inline uint16_t DceRpcClFragNum(const DceRpcClHdr *cl)
+{
+    return DceRpcNtohs(&cl->fragnum, DceRpcClByteOrder(cl));
+}
+
+inline int DceRpcClFragFlag(const DceRpcClHdr *cl)
+{
+    return cl->flags1 & DCERPC_CL_FLAGS1__FRAG;
+}
+
+inline bool DceRpcClFirstFrag(const DceRpcClHdr *cl)
+{
+    return (DceRpcClFragFlag(cl) && (DceRpcClFragNum(cl) == 0));
+}
+
+inline int DceRpcClLastFrag(const DceRpcClHdr *cl)
+{
+    return cl->flags1 & DCERPC_CL_FLAGS1__LASTFRAG;
+}
+
+inline bool DceRpcClFrag(const DceRpcClHdr *cl)
+{
+    if (DceRpcClFragFlag(cl))
+    {
+        if (DceRpcClLastFrag(cl) && (DceRpcClFragNum(cl) == 0))
+            return false;
+
+        return true;
+    }
+
+    return false;
+}
+
 struct DCE2_ClTracker
 {
     DCE2_List* act_trackers;  /* List of activity trackers */
@@ -139,12 +203,13 @@ public:
         flow_id = FlowData::get_flow_id();
     }
 
-public:
     static unsigned flow_id;
     DCE2_UdpSsnData dce2_udp_session;
 };
 
 DCE2_UdpSsnData* get_dce2_udp_session_data(Flow*);
 
+void DCE2_ClProcess(DCE2_SsnData *sd, DCE2_ClTracker *clt);
+
 #endif
 
diff --git a/src/service_inspectors/dce_rpc/dce_udp_processing.cc b/src/service_inspectors/dce_rpc/dce_udp_processing.cc
new file mode 100644 (file)
index 0000000..ec0211e
--- /dev/null
@@ -0,0 +1,379 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved.
+// Copyright (C) 2008-2013 Sourcefire, Inc.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// 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.
+//--------------------------------------------------------------------------
+
+// dce_udp_processing.cc author Todd Wease
+// modifications for snort3 by Maya Dagon <mdagon@cisco.com>
+
+// Module for handling connectionless DCE/RPC processing.  Provides
+// functionality for tracking sub-sessions or activities within a
+// connectionless conversation and for tracking and reassembling fragments
+// within each activity.  Also sets appropriate data for use with
+// preprocessor rule options.
+
+#include "dce_udp.h"
+
+#include "flow/session.h"
+#include "main/snort_debug.h"
+#include "utils/util.h"
+
+#include "dce_common.h"
+#include "dce_udp_module.h"
+
+/********************************************************************
+ * Macros
+ ********************************************************************/
+#define DCE2_CL__MAX_SEQ_NUM  UINT32_MAX
+
+/********************************************************************
+ * Structures
+ ********************************************************************/
+struct DCE2_ClFragTracker
+{
+    Uuid iface;          /* only set on first fragment received */
+    uint32_t iface_vers; /* only set on first fragment received */
+    int opnum;           /* set to that of first fragment, i.e fragment number == 0.
+                          * initialize to a sentinel */
+    int data_byte_order; /* set to that of first fragment, i.e fragment number == 0.
+                          * initialize to sentinel */
+
+    DCE2_List* frags;         /* sorted by fragment number */
+    int num_expected_frags;   /* set when we get last frag */
+};
+
+struct DCE2_ClActTracker
+{
+    Uuid act;
+    uint32_t seq_num;
+    uint8_t seq_num_invalid;
+
+    DCE2_ClFragTracker frag_tracker;
+};
+
+/********************************************************************
+ * Private function prototypes
+ ********************************************************************/
+static DCE2_Ret DCE2_ClHdrChecks(DCE2_SsnData*, const DceRpcClHdr*);
+static DCE2_ClActTracker* DCE2_ClGetActTracker(DCE2_ClTracker*, DceRpcClHdr*);
+static DCE2_ClActTracker* DCE2_ClInsertActTracker(DCE2_ClTracker*, DceRpcClHdr*);
+static void DCE2_ClRequest(DCE2_SsnData*, DCE2_ClActTracker*, DceRpcClHdr*,
+    const uint8_t*, uint16_t);
+
+/* Callbacks */
+static void DCE2_ClActDataFree(void*);
+static void DCE2_ClActKeyFree(void*);
+
+// Main entry point for connectionless DCE/RPC processing.  Gets
+// the activity tracker associated with this session and passes
+// along to client or server handling.
+void DCE2_ClProcess(DCE2_SsnData* sd, DCE2_ClTracker* clt)
+{
+    DceRpcClHdr* cl_hdr;
+    DCE2_ClActTracker* at;
+    const uint8_t* data_ptr = sd->wire_pkt->data;
+    uint16_t data_len = sd->wire_pkt->dsize;
+
+    DebugMessage(DEBUG_DCE_UDP, "Cl processing ...\n");
+
+    if (data_len < sizeof(DceRpcClHdr))
+    {
+        // FIXIT-M  currently we always do autodetect. Uncomment once
+        // detect/autodetect is supported.
+/*
+        if (!DCE2_SsnAutodetected(sd))
+             dce_alert(GID_DCE2,  DCE2_CL_DATA_LT_HDR, (dce2CommonStats*)&dce2_udp_stats);
+*/
+        return;
+    }
+
+    cl_hdr = (DceRpcClHdr*)data_ptr;
+
+    DCE2_MOVE(data_ptr, data_len, sizeof(DceRpcClHdr));
+
+    if (DCE2_ClHdrChecks(sd, cl_hdr) != DCE2_RET__SUCCESS)
+        return;
+
+    Profile profile(dce2_udp_pstat_cl_acts);
+    at = DCE2_ClGetActTracker(clt, cl_hdr);
+    if (at == nullptr)
+        return;
+
+    if (DCE2_SsnFromClient(sd->wire_pkt))
+    {
+        switch (DceRpcClPduType(cl_hdr))
+        {
+        case DCERPC_PDU_TYPE__REQUEST:
+            DebugMessage(DEBUG_DCE_UDP, "Request\n");
+            dce2_udp_stats.cl_request++;
+            DCE2_ClRequest(sd, at, cl_hdr, data_ptr, data_len);
+            break;
+
+        case DCERPC_PDU_TYPE__ACK:
+            DebugMessage(DEBUG_DCE_UDP, "Ack\n");
+            dce2_udp_stats.cl_ack++;
+            break;
+
+        case DCERPC_PDU_TYPE__CL_CANCEL:
+            DebugMessage(DEBUG_DCE_UDP, "Cancel\n");
+            dce2_udp_stats.cl_cancel++;
+            break;
+
+        case DCERPC_PDU_TYPE__FACK:
+            DebugMessage(DEBUG_DCE_UDP, "Fack\n");
+            dce2_udp_stats.cl_cli_fack++;
+            break;
+
+        case DCERPC_PDU_TYPE__PING:
+            DebugMessage(DEBUG_DCE_UDP, "Ping\n");
+            dce2_udp_stats.cl_ping++;
+            break;
+
+        case DCERPC_PDU_TYPE__RESPONSE:
+        {
+            DebugMessage(DEBUG_DCE_UDP, "Response from client.  Changing stream direction.");
+            Packet* p = sd->wire_pkt;
+            ip::IpApi* ip_api = &p->ptrs.ip_api;
+
+            p->flow->session->update_direction(SSN_DIR_FROM_SERVER,
+                ip_api->get_src(),
+                p->ptrs.sp);
+
+            break;
+        }
+        default:
+            DebugMessage(DEBUG_DCE_UDP, "Other pdu type\n");
+            dce2_udp_stats.cl_other_req++;
+            break;
+        }
+    }
+    else
+    {
+        switch (DceRpcClPduType(cl_hdr))
+        {
+        case DCERPC_PDU_TYPE__RESPONSE:
+            DebugMessage(DEBUG_DCE_UDP, "Response\n");
+            dce2_udp_stats.cl_response++;
+            break;
+
+        case DCERPC_PDU_TYPE__REJECT:
+            DebugMessage(DEBUG_DCE_UDP, "Reject\n");
+            dce2_udp_stats.cl_reject++;
+
+            if (DceRpcClSeqNum(cl_hdr) == at->seq_num)
+            {
+                // FIXIT-M uncomment once fragments is ported
+                //DCE2_ClResetFragTracker(&at->frag_tracker);
+                at->seq_num_invalid = 1;
+            }
+
+            break;
+
+        case DCERPC_PDU_TYPE__CANCEL_ACK:
+            DebugMessage(DEBUG_DCE_UDP, "Cancel Ack\n");
+            dce2_udp_stats.cl_cancel_ack++;
+            break;
+
+        case DCERPC_PDU_TYPE__FACK:
+            DebugMessage(DEBUG_DCE_UDP, "Fack\n");
+            dce2_udp_stats.cl_srv_fack++;
+            break;
+
+        case DCERPC_PDU_TYPE__FAULT:
+            DebugMessage(DEBUG_DCE_UDP, "Fault\n");
+            dce2_udp_stats.cl_fault++;
+            break;
+
+        case DCERPC_PDU_TYPE__NOCALL:
+            DebugMessage(DEBUG_DCE_UDP, "No call\n");
+            dce2_udp_stats.cl_nocall++;
+            break;
+
+        case DCERPC_PDU_TYPE__WORKING:
+            DebugMessage(DEBUG_DCE_UDP, "Working\n");
+            dce2_udp_stats.cl_working++;
+            break;
+
+        default:
+            DebugMessage(DEBUG_DCE_UDP, "Other pdu type\n");
+            dce2_udp_stats.cl_other_resp++;
+            break;
+        }
+    }
+}
+
+// Checks to make sure header fields are sane.  If they aren't,
+// alert on the header anomaly.  If we've autodetected the session,
+// however, don't alert, but set a header anomaly flag, so we can
+// re-autodetect on the next go around.
+static DCE2_Ret DCE2_ClHdrChecks(DCE2_SsnData*, const DceRpcClHdr* cl_hdr)
+{
+    if (DceRpcClRpcVers(cl_hdr) != DCERPC_PROTO_MAJOR_VERS__4)
+    {
+        // FIXIT-M  currently we always do autodetect. Uncomment once
+        // detect/autodetect is supported.
+        /* If we autodetected the session, we probably guessed wrong */
+        /* if (!DCE2_SsnAutodetected(sd))
+            dce_alert(GID_DCE2, DCE2_CL_BAD_MAJOR_VERSION, (dce2CommonStats*)&dce2_udp_stats);
+        */
+        return DCE2_RET__ERROR;
+    }
+
+    if (DceRpcClPduType(cl_hdr) >= DCERPC_PDU_TYPE__MAX)
+    {
+        // FIXIT-M  currently we always do autodetect. Uncomment once
+        // detect/autodetect is supported.
+/*
+        if (!DCE2_SsnAutodetected(sd))
+            dce_alert(GID_DCE2, DCE2_CL_BAD_PDU_TYPE, (dce2CommonStats*)&dce2_udp_stats);
+*/
+        return DCE2_RET__ERROR;
+    }
+
+    return DCE2_RET__SUCCESS;
+}
+
+// Searches for activity tracker in list using activity UUID in
+// packet.
+static DCE2_ClActTracker* DCE2_ClGetActTracker(DCE2_ClTracker* clt, DceRpcClHdr* cl_hdr)
+{
+    DCE2_ClActTracker* at = nullptr;
+
+    /* Try to find a currently active activity tracker */
+    if (clt->act_trackers != nullptr)
+    {
+        Uuid uuid;
+
+        DCE2_CopyUuid(&uuid, &cl_hdr->act_id, DceRpcClByteOrder(cl_hdr));
+        at = (DCE2_ClActTracker*)DCE2_ListFind(clt->act_trackers, (void*)&uuid);
+    }
+    else
+    {
+        /* Create a new activity tracker list */
+        clt->act_trackers = DCE2_ListNew(DCE2_LIST_TYPE__SPLAYED, DCE2_UuidCompare,
+            DCE2_ClActDataFree, DCE2_ClActKeyFree,
+            DCE2_LIST_FLAG__NO_DUPS);
+        if (clt->act_trackers == nullptr)
+            return nullptr;
+    }
+
+    /* Didn't find a currently active activity tracker */
+    if (at == nullptr)
+    {
+        /* Insert a new activity tracker */
+        at = DCE2_ClInsertActTracker(clt, cl_hdr);
+        if (at == nullptr)
+            return nullptr;
+    }
+
+    return at;
+}
+
+static DCE2_ClActTracker* DCE2_ClInsertActTracker(DCE2_ClTracker* clt, DceRpcClHdr* cl_hdr)
+{
+    Uuid* uuid = (Uuid*)snort_calloc(sizeof(Uuid));
+    DCE2_ClActTracker* at = (DCE2_ClActTracker*)snort_calloc(sizeof(DCE2_ClActTracker));
+
+    DCE2_CopyUuid(uuid, &cl_hdr->act_id, DceRpcClByteOrder(cl_hdr));
+    DCE2_CopyUuid(&at->act, &cl_hdr->act_id, DceRpcClByteOrder(cl_hdr));
+
+    DCE2_Ret status = DCE2_ListInsert(clt->act_trackers, (void*)uuid, (void*)at);
+    if (status != DCE2_RET__SUCCESS)
+    {
+        snort_free((void*)uuid);
+        snort_free((void*)at);
+        return nullptr;
+    }
+
+    return at;
+}
+
+static void DCE2_ClRequest(DCE2_SsnData* sd, DCE2_ClActTracker* at, DceRpcClHdr* cl_hdr,
+    const uint8_t*, uint16_t)
+{
+    const uint32_t seq_num = DceRpcClSeqNum(cl_hdr);
+
+    DebugMessage(DEBUG_DCE_UDP, "Processing Request ...\n");
+
+    if (seq_num > at->seq_num)
+    {
+        /* This is the normal case where the sequence number is incremented
+         * for each request.  Set the new sequence number and mark it valid. */
+        at->seq_num = seq_num;
+        at->seq_num_invalid = 0;
+
+        /* If there are any fragments, the new sequence number invalidates
+         * all of the frags that might be currently stored. */
+        // FIXIT-M uncomment when porting fragments support
+        // DCE2_ClResetFragTracker(&at->frag_tracker);
+    }
+    else if ((seq_num < at->seq_num) || at->seq_num_invalid)
+    {
+        return;
+    }
+
+    DCE2_ResetRopts(&sd->ropts);
+
+    if (!DceRpcClFrag(cl_hdr))
+    {
+        // FIXIT-M add fragments cleanup
+
+        if (seq_num != DCE2_CL__MAX_SEQ_NUM)
+        {
+            /* This sequence number is now invalid. 0xffffffff is the end of
+             * the sequence number space and can be reused */
+            at->seq_num_invalid = 1;
+        }
+        else
+        {
+            /* Got the last sequence number in the sequence number space */
+            dce2_udp_stats.cl_max_seqnum++;
+        }
+    } // FIXIT-M add else - fragments path
+
+    /* Cache relevant values for rule option processing */
+    sd->ropts.first_frag = DceRpcClFirstFrag(cl_hdr);
+    DCE2_CopyUuid(&sd->ropts.iface, DceRpcClIface(cl_hdr), DceRpcClByteOrder(cl_hdr));
+    sd->ropts.iface_vers = DceRpcClIfaceVers(cl_hdr);
+    sd->ropts.opnum = DceRpcClOpnum(cl_hdr);
+    sd->ropts.stub_data = (uint8_t*)cl_hdr + sizeof(DceRpcClHdr);
+    DceEndianness* endianness = (DceEndianness*)sd->wire_pkt->endianness;
+    endianness->hdr_byte_order = DceRpcClByteOrder(cl_hdr);
+    endianness->data_byte_order = DceRpcClByteOrder(cl_hdr);
+    DCE2_Detect(sd);
+}
+
+static void DCE2_ClActDataFree(void* data)
+{
+    DCE2_ClActTracker* at = (DCE2_ClActTracker*)data;
+
+    if (at == nullptr)
+        return;
+
+    DCE2_ListDestroy(at->frag_tracker.frags);
+    at->frag_tracker.frags = nullptr;
+    snort_free((void*)at);
+}
+
+static void DCE2_ClActKeyFree(void* key)
+{
+    if (key == nullptr)
+        return;
+
+    snort_free(key);
+}
+