]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Merge pull request #679 in SNORT/snort3 from dce_udp_fragments to master
authorMichael Altizer (mialtize) <mialtize@cisco.com>
Wed, 19 Oct 2016 13:47:05 +0000 (09:47 -0400)
committerMichael Altizer (mialtize) <mialtize@cisco.com>
Wed, 19 Oct 2016 13:47:05 +0000 (09:47 -0400)
Squashed commit of the following:

commit a4a4bcc9fbc0048aa05c5c7b304b3a7155f452da
Author: mdagon <mdagon@cisco.com>
Date:   Wed Oct 12 09:57:54 2016 -0400

    dce_udp fragments

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

index 9b5d8c478d3a15de01f46a6528e3ccd3d8e881cb..dd3f7b1720c001d1501d03ea3f9fe0cd31eae83f 100644 (file)
@@ -178,14 +178,14 @@ static void dce2_protocol_detect(DCE2_SsnData* sd, Packet* pkt)
     {
         Profile profile(dce2_tcp_pstat_detect);
     }
-    else if  (sd->trans == DCE2_TRANS_TYPE__SMB)
+    else if (sd->trans == DCE2_TRANS_TYPE__SMB)
     {
         Profile profile(dce2_smb_pstat_detect);
-    } 
-       else 
-       {
-               Profile profile(dce2_udp_pstat_detect);
-       }
+    }
+    else
+    {
+        Profile profile(dce2_udp_pstat_detect);
+    }
     // FIXIT-M add HTTP case when these are ported
     // Same for all other instances of profiling
 
@@ -239,7 +239,7 @@ DCE2_SsnData* get_dce2_session_data(Packet* p)
         return sd;
     }
 
-       DCE2_UdpSsnData* udp_data = get_dce2_udp_session_data(p->flow);
+    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))
     {
@@ -315,8 +315,8 @@ static void dce_push_pkt_log(Packet* pkt,DCE2_SsnData* sd)
     {
         Profile profile(dce2_smb_pstat_log);
     }
-       else
-       {
+    else
+    {
         Profile profile(dce2_udp_pstat_log);
     }
 
@@ -351,7 +351,7 @@ void DCE2_PopPkt(DCE2_SsnData* sd)
     {
         Profile profile(dce2_tcp_pstat_log);
     }
-       else if (sd->trans == DCE2_TRANS_TYPE__UDP)
+    else if (sd->trans == DCE2_TRANS_TYPE__UDP)
     {
         Profile profile(dce2_udp_pstat_log);
     }
@@ -500,8 +500,13 @@ Packet* DCE2_GetRpkt(Packet* p,DCE2_RpktType rpkt_type,
         break;
 
     case DCE2_RPKT_TYPE__UDP_CL_FRAG:
-        // FIXIT-M add support when UDP is ported
-        return nullptr;
+        rpkt = dce2_udp_rpkt;
+        dce2_fill_rpkt_info(rpkt, p);
+        rpkt->pseudo_type = PSEUDO_PKT_DCE_FRAG;
+        data_overhead = DCE2_MOCK_HDR_LEN__CL;
+        memset((void*)rpkt->data, 0, data_overhead);
+        DCE2_ClInitRdata((uint8_t*)rpkt->data);
+        break;
 
     case DCE2_RPKT_TYPE__TCP_CO_SEG:
     case DCE2_RPKT_TYPE__TCP_CO_FRAG:
index 07b54562ddf58aaa5de1c57fad546816f149b478..2032832d6a3ae42f919594e72f9f362b9212624b 100644 (file)
@@ -22,6 +22,7 @@
 #include "dce_udp.h"
 
 #include "detection/detect.h"
+#include "utils/util.h"
 
 #include "dce_udp_module.h"
 
@@ -38,6 +39,8 @@ THREAD_LOCAL ProfileStats dce2_udp_pstat_cl_acts;
 THREAD_LOCAL ProfileStats dce2_udp_pstat_cl_frag;
 THREAD_LOCAL ProfileStats dce2_udp_pstat_cl_reass;
 
+THREAD_LOCAL Packet* dce2_udp_rpkt = nullptr;
+
 static void DCE2_ClCleanTracker(DCE2_ClTracker* clt)
 {
     if (clt == nullptr)
@@ -216,8 +219,8 @@ void Dce2Udp::eval(Packet* p)
 
         p->endianness = (Endianness*)new DceEndianness();
 
-               dce2_udp_stats.udp_pkts++;
-               DCE2_ClProcess(&dce2_udp_sess->sd, &dce2_udp_sess->cl_tracker);
+        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);
@@ -228,7 +231,7 @@ void Dce2Udp::eval(Packet* p)
         if (!DCE2_SsnAutodetected(&dce2_udp_sess->sd))
             DisableInspection();
 
-               delete p->endianness;
+        delete p->endianness;
         p->endianness = nullptr;
     }
 }
@@ -267,20 +270,42 @@ static void dce2_udp_init()
 
 static void dce2_udp_thread_init()
 {
-       if (dce2_inspector_instances == 0)
+    if (dce2_inspector_instances == 0)
     {
         dce2_pkt_stack = DCE2_CStackNew(DCE2_PKT_STACK__SIZE, nullptr);
     }
 
+    if (dce2_udp_inspector_instances == 0)
+    {
+        dce2_udp_rpkt = (Packet*)snort_calloc(sizeof(Packet));
+        dce2_udp_rpkt->data = (uint8_t*)snort_calloc(DCE2_REASSEMBLY_BUF_SIZE);
+        dce2_udp_rpkt->endianness = (Endianness*)new DceEndianness();
+        dce2_udp_rpkt->dsize = DCE2_REASSEMBLY_BUF_SIZE;
+    }
+
     dce2_udp_inspector_instances++;
-       dce2_inspector_instances++;
+    dce2_inspector_instances++;
 }
 
 static void dce2_udp_thread_term()
 {
-       dce2_inspector_instances--;
+    dce2_inspector_instances--;
     dce2_udp_inspector_instances--;
 
+    if (dce2_udp_inspector_instances == 0)
+    {
+        if ( dce2_udp_rpkt != nullptr )
+        {
+            if (dce2_udp_rpkt->data)
+            {
+                snort_free((void*)dce2_udp_rpkt->data);
+            }
+            delete dce2_udp_rpkt->endianness;
+            snort_free(dce2_udp_rpkt);
+            dce2_udp_rpkt = nullptr;
+        }
+    }
+
     if (dce2_inspector_instances == 0)
     {
         DCE2_CStackDestroy(dce2_pkt_stack);
index a2f6fd528c10d6ae7ad307bbee0fdb33c35bc7f3..13f2011742bc01720962752d6013cd63937e58e4 100644 (file)
@@ -29,6 +29,8 @@
 #define DCE2_UDP_NAME "dce_udp"
 #define DCE2_UDP_HELP "dce over udp inspection"
 
+#define DCE2_MOCK_HDR_LEN__CL  (sizeof(DceRpcClHdr))
+
 struct dce2UdpStats
 {
     /* The common stats block has to be at the beginning followed
@@ -71,6 +73,7 @@ extern THREAD_LOCAL ProfileStats dce2_udp_pstat_log;
 extern THREAD_LOCAL ProfileStats dce2_udp_pstat_cl_acts;
 extern THREAD_LOCAL ProfileStats dce2_udp_pstat_cl_frag;
 extern THREAD_LOCAL ProfileStats dce2_udp_pstat_cl_reass;
+extern THREAD_LOCAL Packet* dce2_udp_rpkt;
 
 struct DceRpcClHdr   /* Connectionless header */
 {
@@ -93,7 +96,6 @@ struct DceRpcClHdr   /* Connectionless header */
     uint16_t fragnum;
     uint8_t auth_proto;
     uint8_t serial_lo;
-
 };
 
 enum DceRpcClFlags1
@@ -108,67 +110,67 @@ enum DceRpcClFlags1
     DCERPC_CL_FLAGS1__RESERVED_80 = 0x80
 };
 
-inline uint8_t DceRpcClRpcVers(const DceRpcClHdr *cl)
+inline uint8_t DceRpcClRpcVers(const DceRpcClHdrcl)
 {
     return cl->rpc_vers;
 }
 
-inline uint8_t DceRpcClPduType(const DceRpcClHdr *cl)
+inline uint8_t DceRpcClPduType(const DceRpcClHdrcl)
 {
     return cl->ptype;
 }
 
-inline DceRpcBoFlag DceRpcClByteOrder(const DceRpcClHdr *cl)
+inline DceRpcBoFlag DceRpcClByteOrder(const DceRpcClHdrcl)
 {
     return DceRpcByteOrder(cl->drep[0]);
 }
 
-inline uint16_t DceRpcClLen(const DceRpcClHdr *cl)
+inline uint16_t DceRpcClLen(const DceRpcClHdrcl)
 {
     return DceRpcNtohs(&cl->len, DceRpcClByteOrder(cl));
 }
 
-inline uint16_t DceRpcClOpnum(const DceRpcClHdr *cl)
+inline uint16_t DceRpcClOpnum(const DceRpcClHdrcl)
 {
     return DceRpcNtohs(&cl->opnum, DceRpcClByteOrder(cl));
 }
 
-inline uint32_t DceRpcClSeqNum(const DceRpcClHdr *cl)
+inline uint32_t DceRpcClSeqNum(const DceRpcClHdrcl)
 {
     return DceRpcNtohl(&cl->seqnum, DceRpcClByteOrder(cl));
 }
 
-inline const Uuid * DceRpcClIface(const DceRpcClHdr *cl)
+inline const Uuid* DceRpcClIface(const DceRpcClHdr* cl)
 {
     return &cl->if_id;
 }
 
-inline uint32_t DceRpcClIfaceVers(const DceRpcClHdr *cl)
+inline uint32_t DceRpcClIfaceVers(const DceRpcClHdrcl)
 {
     return DceRpcNtohl(&cl->if_vers, DceRpcClByteOrder(cl));
 }
 
-inline uint16_t DceRpcClFragNum(const DceRpcClHdr *cl)
+inline uint16_t DceRpcClFragNum(const DceRpcClHdrcl)
 {
     return DceRpcNtohs(&cl->fragnum, DceRpcClByteOrder(cl));
 }
 
-inline int DceRpcClFragFlag(const DceRpcClHdr *cl)
+inline int DceRpcClFragFlag(const DceRpcClHdrcl)
 {
     return cl->flags1 & DCERPC_CL_FLAGS1__FRAG;
 }
 
-inline bool DceRpcClFirstFrag(const DceRpcClHdr *cl)
+inline bool DceRpcClFirstFrag(const DceRpcClHdrcl)
 {
     return (DceRpcClFragFlag(cl) && (DceRpcClFragNum(cl) == 0));
 }
 
-inline int DceRpcClLastFrag(const DceRpcClHdr *cl)
+inline int DceRpcClLastFrag(const DceRpcClHdrcl)
 {
     return cl->flags1 & DCERPC_CL_FLAGS1__LASTFRAG;
 }
 
-inline bool DceRpcClFrag(const DceRpcClHdr *cl)
+inline bool DceRpcClFrag(const DceRpcClHdrcl)
 {
     if (DceRpcClFragFlag(cl))
     {
@@ -209,7 +211,8 @@ public:
 
 DCE2_UdpSsnData* get_dce2_udp_session_data(Flow*);
 
-void DCE2_ClProcess(DCE2_SsnData *sd, DCE2_ClTracker *clt);
+void DCE2_ClProcess(DCE2_SsnData* sd, DCE2_ClTracker* clt);
+void DCE2_ClInitRdata(uint8_t*);
 
 #endif
 
index ec0211ed4042f955486d06c35c33c1b0d460cc0d..e7a4fbd5bbbd38fecbf9369315d750451ba53398 100644 (file)
@@ -30,6 +30,7 @@
 
 #include "flow/session.h"
 #include "main/snort_debug.h"
+#include "utils/safec.h"
 #include "utils/util.h"
 
 #include "dce_common.h"
 /********************************************************************
  * Structures
  ********************************************************************/
+struct DCE2_ClFragNode
+{
+    uint32_t frag_number;
+    uint16_t frag_len;
+    uint8_t* frag_data;
+};
+
 struct DCE2_ClFragTracker
 {
     Uuid iface;          /* only set on first fragment received */
@@ -73,10 +81,17 @@ 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);
+static void DCE2_ClHandleFrag(DCE2_SsnData*, DCE2_ClActTracker*,
+    DceRpcClHdr*, const uint8_t*, uint16_t);
+static void DCE2_ClFragReassemble(DCE2_SsnData*, DCE2_ClActTracker*, const DceRpcClHdr*);
+static void DCE2_ClResetFragTracker(DCE2_ClFragTracker*);
+static void DCE2_ClSetRdata(DCE2_ClActTracker*, const DceRpcClHdr*, uint8_t*, uint16_t);
 
 /* Callbacks */
 static void DCE2_ClActDataFree(void*);
 static void DCE2_ClActKeyFree(void*);
+static int DCE2_ClFragCompare(const void*, const void*);
+static void DCE2_ClFragDataFree(void*);
 
 // Main entry point for connectionless DCE/RPC processing.  Gets
 // the activity tracker associated with this session and passes
@@ -176,8 +191,7 @@ void DCE2_ClProcess(DCE2_SsnData* sd, DCE2_ClTracker* clt)
 
             if (DceRpcClSeqNum(cl_hdr) == at->seq_num)
             {
-                // FIXIT-M uncomment once fragments is ported
-                //DCE2_ClResetFragTracker(&at->frag_tracker);
+                DCE2_ClResetFragTracker(&at->frag_tracker);
                 at->seq_num_invalid = 1;
             }
 
@@ -276,8 +290,6 @@ static DCE2_ClActTracker* DCE2_ClGetActTracker(DCE2_ClTracker* clt, DceRpcClHdr*
     {
         /* Insert a new activity tracker */
         at = DCE2_ClInsertActTracker(clt, cl_hdr);
-        if (at == nullptr)
-            return nullptr;
     }
 
     return at;
@@ -303,7 +315,7 @@ static DCE2_ClActTracker* DCE2_ClInsertActTracker(DCE2_ClTracker* clt, DceRpcClH
 }
 
 static void DCE2_ClRequest(DCE2_SsnData* sd, DCE2_ClActTracker* at, DceRpcClHdr* cl_hdr,
-    const uint8_t*, uint16_t)
+    const uint8_t* data_ptr, uint16_t data_len)
 {
     const uint32_t seq_num = DceRpcClSeqNum(cl_hdr);
 
@@ -318,8 +330,7 @@ static void DCE2_ClRequest(DCE2_SsnData* sd, DCE2_ClActTracker* at, DceRpcClHdr*
 
         /* 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);
+        DCE2_ClResetFragTracker(&at->frag_tracker);
     }
     else if ((seq_num < at->seq_num) || at->seq_num_invalid)
     {
@@ -328,11 +339,16 @@ static void DCE2_ClRequest(DCE2_SsnData* sd, DCE2_ClActTracker* at, DceRpcClHdr*
 
     DCE2_ResetRopts(&sd->ropts);
 
-    if (!DceRpcClFrag(cl_hdr))
+    if (!DceRpcClFrag(cl_hdr))  /* It's a full request */
     {
-        // FIXIT-M add fragments cleanup
-
-        if (seq_num != DCE2_CL__MAX_SEQ_NUM)
+        if ((at->frag_tracker.frags != nullptr) &&
+            !DCE2_ListIsEmpty(at->frag_tracker.frags))
+        {
+            /* If we get a full request, i.e. not a frag, any frags
+             * we have collected are invalidated */
+            DCE2_ClResetFragTracker(&at->frag_tracker);
+        }
+        else 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 */
@@ -343,7 +359,16 @@ static void DCE2_ClRequest(DCE2_SsnData* sd, DCE2_ClActTracker* at, DceRpcClHdr*
             /* Got the last sequence number in the sequence number space */
             dce2_udp_stats.cl_max_seqnum++;
         }
-    } // FIXIT-M add else - fragments path
+    }
+    else  /* It's a frag */
+    {
+        dce2_udp_stats.cl_fragments++;
+        if (DCE2_GcDceDefrag((dce2CommonProtoConf*)sd->config))
+        {
+            DCE2_ClHandleFrag(sd, at, cl_hdr, data_ptr, data_len);
+            return;
+        }
+    }
 
     /* Cache relevant values for rule option processing */
     sd->ropts.first_frag = DceRpcClFirstFrag(cl_hdr);
@@ -377,3 +402,302 @@ static void DCE2_ClActKeyFree(void* key)
     snort_free(key);
 }
 
+// Handles connectionless fragments.  Creates a new fragment list
+// if necessary and inserts fragment into list.  Sets rule option
+// values based on the fragment.
+static void DCE2_ClHandleFrag(DCE2_SsnData* sd, DCE2_ClActTracker* at, DceRpcClHdr* cl_hdr,
+    const uint8_t* data_ptr, uint16_t data_len)
+{
+    DCE2_ClFragTracker* ft = &at->frag_tracker;
+    DCE2_ClFragNode* fn;
+    uint16_t frag_len;
+    int status;
+
+    Profile profile(dce2_udp_pstat_cl_frag);
+
+    /* If the frag length is less than data length there might be authentication
+     * data that we don't want to include, otherwise just set to data len */
+    if (DceRpcClLen(cl_hdr) < data_len)
+        frag_len = DceRpcClLen(cl_hdr);
+    else
+        frag_len = data_len;
+
+    if (frag_len == 0)
+    {
+        return;
+    }
+
+    if (frag_len > dce2_udp_stats.cl_max_frag_size)
+        dce2_udp_stats.cl_max_frag_size = frag_len;
+
+    if (DCE2_GcMaxFrag((dce2CommonProtoConf*)sd->config)
+        && (frag_len > DCE2_GcMaxFragLen((dce2CommonProtoConf*)sd->config)))
+        frag_len = DCE2_GcMaxFragLen((dce2CommonProtoConf*)sd->config);
+
+    if (ft->frags == nullptr)
+    {
+        /* Create new list if we don't have one already */
+        ft->frags = DCE2_ListNew(DCE2_LIST_TYPE__SORTED, DCE2_ClFragCompare, DCE2_ClFragDataFree,
+            nullptr, DCE2_LIST_FLAG__NO_DUPS | DCE2_LIST_FLAG__INS_TAIL);
+
+        if (ft->frags == nullptr)
+        {
+            return;
+        }
+    }
+    else
+    {
+        /* If we already have a fragment in the list with the same fragment number,
+         * that fragment will take precedence over this fragment and this fragment
+         * will not be used by the server */
+        fn = (DCE2_ClFragNode*)DCE2_ListFind(ft->frags, (void*)(uintptr_t)DceRpcClFragNum(cl_hdr));
+        if (fn != nullptr)
+        {
+            return;
+        }
+    }
+
+    /* Create a new frag node to insert into the list */
+    fn = (DCE2_ClFragNode*)snort_calloc(sizeof(DCE2_ClFragNode));
+    fn->frag_number = DceRpcClFragNum(cl_hdr);
+    fn->frag_len = frag_len;
+
+    /* Allocate space for the fragment data */
+    fn->frag_data = (uint8_t*)snort_calloc(frag_len);
+
+    /* Copy the fragment data in the packet to the space just allocated */
+    memcpy(fn->frag_data, data_ptr, frag_len);
+
+    if (DCE2_ListIsEmpty(ft->frags))
+    {
+        /* If this is the first fragment we've received, set interface uuid */
+        DCE2_CopyUuid(&ft->iface, DceRpcClIface(cl_hdr), DceRpcClByteOrder(cl_hdr));
+        ft->iface_vers = DceRpcClIfaceVers(cl_hdr);
+    }
+
+    if (DceRpcClLastFrag(cl_hdr))
+    {
+        /* Set number of expected frags on last frag */
+        ft->num_expected_frags = DceRpcClFragNum(cl_hdr) + 1;
+    }
+    else if (DceRpcClFirstFrag(cl_hdr))
+    {
+        /* Set opum and byte order on first frag */
+        ft->opnum = DceRpcClOpnum(cl_hdr);
+        ft->data_byte_order = DceRpcClByteOrder(cl_hdr);
+    }
+
+    /* Insert frag node into the list */
+    status = DCE2_ListInsert(ft->frags, (void*)(uintptr_t)fn->frag_number, (void*)fn);
+    if (status != DCE2_RET__SUCCESS)
+    {
+        snort_free((void*)fn->frag_data);
+        snort_free((void*)fn);
+
+        DCE2_ClFragReassemble(sd, at, cl_hdr);
+        return;
+    }
+
+    /* Fragment number field in header is uint16_t */
+    if ((ft->num_expected_frags != DCE2_SENTINEL) &&
+        (uint16_t)ft->frags->num_nodes == (uint16_t)ft->num_expected_frags)
+    {
+        /* We got all of the frags - reassemble */
+        DCE2_ClFragReassemble(sd, at, cl_hdr);
+        at->seq_num_invalid = 1;
+
+        return;
+    }
+
+    /* Cache relevant values for rule option processing */
+    sd->ropts.first_frag = DceRpcClFirstFrag(cl_hdr);
+    DCE2_CopyUuid(&sd->ropts.iface, &ft->iface, DCERPC_BO_FLAG__NONE);
+    sd->ropts.iface_vers = ft->iface_vers;
+    DceEndianness* endianness = (DceEndianness*)sd->wire_pkt->endianness;
+    endianness->hdr_byte_order = DceRpcClByteOrder(cl_hdr);
+
+    if (ft->data_byte_order != DCE2_SENTINEL)
+        endianness->data_byte_order = ft->data_byte_order;
+    else
+        endianness->data_byte_order = DceRpcClByteOrder(cl_hdr);
+
+    if (ft->opnum != DCE2_SENTINEL)
+        sd->ropts.opnum = ft->opnum;
+    else
+        sd->ropts.opnum = DceRpcClOpnum(cl_hdr);
+
+    sd->ropts.stub_data = (uint8_t*)cl_hdr + sizeof(DceRpcClHdr);
+
+    DCE2_Detect(sd);
+}
+
+/********************************************************************
+ * Function: DCE2_ClFragCompare()
+ *
+ * Callback to fragment list for sorting the nodes in the list
+ * by fragment number.  Values passed in are the fragment numbers.
+ *
+ * Arguments:
+ *  const void *
+ *      First fragment number to compare.
+ *  const void *
+ *      Second fragment number to compare.
+ *
+ * Returns:
+ *  int
+ *       1 if first value is greater than second value
+ *      -1 if first value is less than second value
+ *       0 if first value equals second value
+ *
+ ********************************************************************/
+static int DCE2_ClFragCompare(const void* a, const void* b)
+{
+    const int x = (int)(uintptr_t)a;
+    const int y = (int)(uintptr_t)b;
+
+    if (x > y)
+        return 1;
+    if (x < y)
+        return -1;
+
+    return 0;
+}
+
+// Reassembles fragments into reassembly buffer and copies to
+// reassembly packet.
+static void DCE2_ClFragReassemble(DCE2_SsnData* sd, DCE2_ClActTracker* at, const
+    DceRpcClHdr* cl_hdr)
+{
+       uint8_t dce2_cl_rbuf[IP_MAXPACKET];
+       DCE2_ClFragTracker* ft = &at->frag_tracker;
+       uint8_t* rdata = dce2_cl_rbuf;
+    uint16_t rlen = sizeof(dce2_cl_rbuf);
+       DCE2_ClFragNode* fnode;
+       uint32_t stub_len = 0;
+
+    Profile profile(dce2_udp_pstat_cl_reass);
+
+    for (fnode = (DCE2_ClFragNode*)DCE2_ListFirst(ft->frags);
+        fnode != nullptr;
+        fnode = (DCE2_ClFragNode*)DCE2_ListNext(ft->frags))
+    {
+        if (fnode->frag_len > rlen)
+        {
+            DebugFormat(DEBUG_DCE_UDP,
+                "%s(%d) Size of fragments exceeds reassembly buffer size. "
+                "Using as many fragments as will fit.", __FILE__, __LINE__);
+            break;
+        }
+
+        memcpy(rdata, fnode->frag_data, fnode->frag_len);
+        DCE2_MOVE(rdata, rlen, fnode->frag_len);
+        stub_len += fnode->frag_len;
+    }
+
+     Packet* rpkt = DCE2_GetRpkt(sd->wire_pkt, DCE2_RPKT_TYPE__UDP_CL_FRAG, dce2_cl_rbuf, stub_len);
+    if (rpkt == nullptr)
+    {
+        DebugFormat(DEBUG_DCE_UDP,
+            "%s(%d) Failed to create reassembly packet.",
+            __FILE__, __LINE__);
+        return;
+    }
+
+    DCE2_ClSetRdata(at, cl_hdr, (uint8_t*)rpkt->data,
+        (uint16_t)(rpkt->dsize - DCE2_MOCK_HDR_LEN__CL));
+
+    const uint8_t* stub_data = rpkt->data + DCE2_MOCK_HDR_LEN__CL;
+
+    if (DCE2_PushPkt(rpkt, sd) != DCE2_RET__SUCCESS)
+    {
+        DebugFormat(DEBUG_DCE_UDP,
+            "%s(%d) Failed to push packet onto packet stack.",
+            __FILE__, __LINE__);
+        return;
+    }
+
+    /* Cache relevant values for rule option processing */
+    sd->ropts.first_frag = 1;
+    DCE2_CopyUuid(&sd->ropts.iface, &ft->iface, DCERPC_BO_FLAG__NONE);
+    sd->ropts.iface_vers = ft->iface_vers;
+    DceEndianness* endianness = (DceEndianness*)sd->wire_pkt->endianness;
+    endianness->hdr_byte_order = DceRpcClByteOrder(cl_hdr);
+
+    if (ft->data_byte_order != DCE2_SENTINEL)
+        endianness->data_byte_order = ft->data_byte_order;
+    else
+        endianness->data_byte_order = DceRpcClByteOrder(cl_hdr);
+
+    if (ft->opnum != DCE2_SENTINEL)
+        sd->ropts.opnum = ft->opnum;
+    else
+        sd->ropts.opnum = DceRpcClOpnum(cl_hdr);
+
+    sd->ropts.stub_data = stub_data;
+
+    DCE2_Detect(sd);
+    DCE2_PopPkt(sd);
+
+    dce2_udp_stats.cl_frag_reassembled++;
+}
+
+// Callback to fragment list for freeing data kept in list.  Need
+// to free the frag node and the data attached to it.
+static void DCE2_ClFragDataFree(void* data)
+{
+    DCE2_ClFragNode* fn = (DCE2_ClFragNode*)data;
+
+    if (fn == nullptr)
+        return;
+
+    if (fn->frag_data != nullptr)
+        snort_free((void*)fn->frag_data);
+
+    snort_free((void*)fn);
+}
+
+// Destroys the fragment tracker's fragment list and resets opnum,
+// byte order and number of expected frags to a sentinel.
+static void DCE2_ClResetFragTracker(DCE2_ClFragTracker* ft)
+{
+    if (ft == nullptr)
+        return;
+
+    if (ft->frags != nullptr)
+    {
+        DCE2_ListDestroy(ft->frags);
+        ft->frags = nullptr;
+    }
+
+    ft->opnum = DCE2_SENTINEL;
+    ft->data_byte_order = DCE2_SENTINEL;
+    ft->num_expected_frags = DCE2_SENTINEL;
+}
+
+void DCE2_ClInitRdata(uint8_t* buf)
+{
+    DceRpcClHdr* cl_hdr = (DceRpcClHdr*)buf;
+
+    /* Set some relevant fields.  These should never get reset */
+    cl_hdr->rpc_vers = DCERPC_PROTO_MAJOR_VERS__4;
+    cl_hdr->ptype = DCERPC_PDU_TYPE__REQUEST;
+    cl_hdr->drep[0] = 0x10;   /* Little endian */
+}
+
+// Sets relevant data fields in the reassembly packet.
+static void DCE2_ClSetRdata(DCE2_ClActTracker* at, const DceRpcClHdr* pkt_cl_hdr,
+    uint8_t* cl_ptr, uint16_t stub_len)
+{
+    DCE2_ClFragTracker* ft = &at->frag_tracker;
+    DceRpcClHdr* cl_hdr = (DceRpcClHdr*)cl_ptr;
+    const uint16_t opnum = (ft->opnum != DCE2_SENTINEL) ? (uint16_t)ft->opnum : DceRpcClOpnum(
+        pkt_cl_hdr);
+
+    cl_hdr->len = DceRpcHtons(&stub_len, DCERPC_BO_FLAG__LITTLE_ENDIAN);
+    DCE2_CopyUuid(&cl_hdr->object, &pkt_cl_hdr->object, DceRpcClByteOrder(cl_hdr));
+    DCE2_CopyUuid(&cl_hdr->if_id, &ft->iface, DCERPC_BO_FLAG__LITTLE_ENDIAN);
+    DCE2_CopyUuid(&cl_hdr->act_id, &at->act, DCERPC_BO_FLAG__LITTLE_ENDIAN);
+    cl_hdr->if_vers = DceRpcHtonl(&ft->iface_vers, DCERPC_BO_FLAG__LITTLE_ENDIAN);
+    cl_hdr->opnum = DceRpcHtons(&opnum, DCERPC_BO_FLAG__LITTLE_ENDIAN);
+}
+