You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
suricata/src/defrag-hash.c

767 lines
23 KiB
C

/* Copyright (C) 2007-2022 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* 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
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#include "suricata-common.h"
#include "conf.h"
#include "defrag-hash.h"
#include "defrag-queue.h"
#include "defrag-config.h"
#include "util-random.h"
#include "util-byte.h"
#include "util-misc.h"
#include "util-hash-lookup3.h"
/** defrag tracker hash table */
DefragTrackerHashRow *defragtracker_hash;
DefragConfig defrag_config;
SC_ATOMIC_DECLARE(uint64_t,defrag_memuse);
SC_ATOMIC_DECLARE(unsigned int,defragtracker_counter);
SC_ATOMIC_DECLARE(unsigned int,defragtracker_prune_idx);
static DefragTracker *DefragTrackerGetUsedDefragTracker(void);
/** queue with spare tracker */
static DefragTrackerQueue defragtracker_spare_q;
/**
* \brief Update memcap value
*
* \param size new memcap value
*/
int DefragTrackerSetMemcap(uint64_t size)
{
if ((uint64_t)SC_ATOMIC_GET(defrag_memuse) < size) {
SC_ATOMIC_SET(defrag_config.memcap, size);
return 1;
}
return 0;
}
/**
* \brief Return memcap value
*
* \retval memcap value
*/
uint64_t DefragTrackerGetMemcap(void)
{
uint64_t memcapcopy = SC_ATOMIC_GET(defrag_config.memcap);
return memcapcopy;
}
/**
* \brief Return memuse value
*
* \retval memuse value
*/
uint64_t DefragTrackerGetMemuse(void)
{
uint64_t memusecopy = (uint64_t)SC_ATOMIC_GET(defrag_memuse);
return memusecopy;
}
uint32_t DefragTrackerSpareQueueGetSize(void)
{
return DefragTrackerQueueLen(&defragtracker_spare_q);
}
void DefragTrackerMoveToSpare(DefragTracker *h)
{
DefragTrackerEnqueue(&defragtracker_spare_q, h);
(void) SC_ATOMIC_SUB(defragtracker_counter, 1);
}
static DefragTracker *DefragTrackerAlloc(void)
{
if (!(DEFRAG_CHECK_MEMCAP(sizeof(DefragTracker)))) {
return NULL;
}
(void) SC_ATOMIC_ADD(defrag_memuse, sizeof(DefragTracker));
DefragTracker *dt = SCMalloc(sizeof(DefragTracker));
if (unlikely(dt == NULL))
goto error;
memset(dt, 0x00, sizeof(DefragTracker));
SCMutexInit(&dt->lock, NULL);
SC_ATOMIC_INIT(dt->use_cnt);
return dt;
error:
return NULL;
}
static void DefragTrackerFree(DefragTracker *dt)
{
if (dt != NULL) {
DefragTrackerClearMemory(dt);
SCMutexDestroy(&dt->lock);
SCFree(dt);
(void) SC_ATOMIC_SUB(defrag_memuse, sizeof(DefragTracker));
}
}
#define DefragTrackerIncrUsecnt(dt) \
SC_ATOMIC_ADD((dt)->use_cnt, 1)
#define DefragTrackerDecrUsecnt(dt) \
SC_ATOMIC_SUB((dt)->use_cnt, 1)
static void DefragTrackerInit(DefragTracker *dt, Packet *p)
{
/* copy address */
COPY_ADDRESS(&p->src, &dt->src_addr);
COPY_ADDRESS(&p->dst, &dt->dst_addr);
if (PKT_IS_IPV4(p)) {
dt->id = (int32_t)IPV4_GET_IPID(p);
dt->af = AF_INET;
} else {
dt->id = (int32_t)IPV6_EXTHDR_GET_FH_ID(p);
dt->af = AF_INET6;
}
dt->proto = IP_GET_IPPROTO(p);
memcpy(&dt->vlan_id[0], &p->vlan_id[0], sizeof(dt->vlan_id));
dt->policy = DefragGetOsPolicy(p);
dt->host_timeout = DefragPolicyGetHostTimeout(p);
dt->remove = 0;
dt->seen_last = 0;
(void) DefragTrackerIncrUsecnt(dt);
}
void DefragTrackerRelease(DefragTracker *t)
{
(void) DefragTrackerDecrUsecnt(t);
SCMutexUnlock(&t->lock);
}
void DefragTrackerClearMemory(DefragTracker *dt)
{
DefragTrackerFreeFrags(dt);
}
#define DEFRAG_DEFAULT_HASHSIZE 4096
#define DEFRAG_DEFAULT_MEMCAP 16777216
#define DEFRAG_DEFAULT_PREALLOC 1000
/** \brief initialize the configuration
* \warning Not thread safe */
void DefragInitConfig(bool quiet)
{
SCLogDebug("initializing defrag engine...");
memset(&defrag_config, 0, sizeof(defrag_config));
//SC_ATOMIC_INIT(flow_flags);
SC_ATOMIC_INIT(defragtracker_counter);
SC_ATOMIC_INIT(defrag_memuse);
SC_ATOMIC_INIT(defragtracker_prune_idx);
SC_ATOMIC_INIT(defrag_config.memcap);
DefragTrackerQueueInit(&defragtracker_spare_q);
/* set defaults */
defrag_config.hash_rand = (uint32_t)RandomGet();
defrag_config.hash_size = DEFRAG_DEFAULT_HASHSIZE;
defrag_config.prealloc = DEFRAG_DEFAULT_PREALLOC;
SC_ATOMIC_SET(defrag_config.memcap, DEFRAG_DEFAULT_MEMCAP);
defrag_config.memcap_policy = ExceptionPolicyParse("defrag.memcap-policy", false);
/* Check if we have memcap and hash_size defined at config */
const char *conf_val;
uint32_t configval = 0;
uint64_t defrag_memcap;
/** set config values for memcap, prealloc and hash_size */
if ((ConfGet("defrag.memcap", &conf_val)) == 1)
{
if (ParseSizeStringU64(conf_val, &defrag_memcap) < 0) {
SCLogError("Error parsing defrag.memcap "
"from conf file - %s. Killing engine",
conf_val);
exit(EXIT_FAILURE);
} else {
SC_ATOMIC_SET(defrag_config.memcap, defrag_memcap);
}
}
if ((ConfGet("defrag.hash-size", &conf_val)) == 1)
{
if (StringParseUint32(&configval, 10, strlen(conf_val),
conf_val) > 0) {
defrag_config.hash_size = configval;
} else {
WarnInvalidConfEntry("defrag.hash-size", "%"PRIu32, defrag_config.hash_size);
}
}
if ((ConfGet("defrag.trackers", &conf_val)) == 1)
{
if (StringParseUint32(&configval, 10, strlen(conf_val),
conf_val) > 0) {
defrag_config.prealloc = configval;
} else {
WarnInvalidConfEntry("defrag.trackers", "%"PRIu32, defrag_config.prealloc);
}
}
SCLogDebug("DefragTracker config from suricata.yaml: memcap: %"PRIu64", hash-size: "
"%"PRIu32", prealloc: %"PRIu32, SC_ATOMIC_GET(defrag_config.memcap),
defrag_config.hash_size, defrag_config.prealloc);
/* alloc hash memory */
uint64_t hash_size = defrag_config.hash_size * sizeof(DefragTrackerHashRow);
if (!(DEFRAG_CHECK_MEMCAP(hash_size))) {
SCLogError("allocating defrag hash failed: "
"max defrag memcap is smaller than projected hash size. "
"Memcap: %" PRIu64 ", Hash table size %" PRIu64 ". Calculate "
"total hash size by multiplying \"defrag.hash-size\" with %" PRIuMAX ", "
"which is the hash bucket size.",
SC_ATOMIC_GET(defrag_config.memcap), hash_size,
(uintmax_t)sizeof(DefragTrackerHashRow));
exit(EXIT_FAILURE);
}
defragtracker_hash = SCCalloc(defrag_config.hash_size, sizeof(DefragTrackerHashRow));
if (unlikely(defragtracker_hash == NULL)) {
FatalError("Fatal error encountered in DefragTrackerInitConfig. Exiting...");
}
memset(defragtracker_hash, 0, defrag_config.hash_size * sizeof(DefragTrackerHashRow));
uint32_t i = 0;
for (i = 0; i < defrag_config.hash_size; i++) {
DRLOCK_INIT(&defragtracker_hash[i]);
}
(void) SC_ATOMIC_ADD(defrag_memuse, (defrag_config.hash_size * sizeof(DefragTrackerHashRow)));
if (!quiet) {
SCLogConfig("allocated %"PRIu64" bytes of memory for the defrag hash... "
"%" PRIu32 " buckets of size %" PRIuMAX "",
SC_ATOMIC_GET(defrag_memuse), defrag_config.hash_size,
(uintmax_t)sizeof(DefragTrackerHashRow));
}
if ((ConfGet("defrag.prealloc", &conf_val)) == 1)
{
if (ConfValIsTrue(conf_val)) {
/* pre allocate defrag trackers */
for (i = 0; i < defrag_config.prealloc; i++) {
if (!(DEFRAG_CHECK_MEMCAP(sizeof(DefragTracker)))) {
SCLogError("preallocating defrag trackers failed: "
"max defrag memcap reached. Memcap %" PRIu64 ", "
"Memuse %" PRIu64 ".",
SC_ATOMIC_GET(defrag_config.memcap),
((uint64_t)SC_ATOMIC_GET(defrag_memuse) +
(uint64_t)sizeof(DefragTracker)));
exit(EXIT_FAILURE);
}
DefragTracker *h = DefragTrackerAlloc();
if (h == NULL) {
SCLogError("preallocating defrag failed: %s", strerror(errno));
exit(EXIT_FAILURE);
}
DefragTrackerEnqueue(&defragtracker_spare_q,h);
}
if (!quiet) {
SCLogConfig("preallocated %" PRIu32 " defrag trackers of size %" PRIuMAX "",
defragtracker_spare_q.len, (uintmax_t)sizeof(DefragTracker));
}
}
}
if (!quiet) {
SCLogConfig("defrag memory usage: %"PRIu64" bytes, maximum: %"PRIu64,
SC_ATOMIC_GET(defrag_memuse), SC_ATOMIC_GET(defrag_config.memcap));
}
return;
}
/** \brief print some defrag stats
* \warning Not thread safe */
static void DefragTrackerPrintStats (void)
{
}
/** \brief shutdown the flow engine
* \warning Not thread safe */
void DefragHashShutdown(void)
{
DefragTracker *dt;
uint32_t u;
DefragTrackerPrintStats();
/* free spare queue */
while((dt = DefragTrackerDequeue(&defragtracker_spare_q))) {
BUG_ON(SC_ATOMIC_GET(dt->use_cnt) > 0);
DefragTrackerFree(dt);
}
/* clear and free the hash */
if (defragtracker_hash != NULL) {
for (u = 0; u < defrag_config.hash_size; u++) {
dt = defragtracker_hash[u].head;
while (dt) {
DefragTracker *n = dt->hnext;
DefragTrackerClearMemory(dt);
DefragTrackerFree(dt);
dt = n;
}
DRLOCK_DESTROY(&defragtracker_hash[u]);
}
SCFree(defragtracker_hash);
defragtracker_hash = NULL;
}
(void) SC_ATOMIC_SUB(defrag_memuse, defrag_config.hash_size * sizeof(DefragTrackerHashRow));
DefragTrackerQueueDestroy(&defragtracker_spare_q);
return;
}
/** \brief compare two raw ipv6 addrs
*
* \note we don't care about the real ipv6 ip's, this is just
* to consistently fill the DefragHashKey6 struct, without all
* the SCNtohl calls.
*
* \warning do not use elsewhere unless you know what you're doing.
* detect-engine-address-ipv6.c's AddressIPv6GtU32 is likely
* what you are looking for.
*/
static inline int DefragHashRawAddressIPv6GtU32(const uint32_t *a, const uint32_t *b)
{
for (int i = 0; i < 4; i++) {
if (a[i] > b[i])
return 1;
if (a[i] < b[i])
break;
}
return 0;
}
typedef struct DefragHashKey4_ {
union {
struct {
uint32_t src, dst;
uint32_t id;
uint16_t vlan_id[VLAN_MAX_LAYERS];
uint16_t pad[1];
};
uint32_t u32[5];
};
} DefragHashKey4;
typedef struct DefragHashKey6_ {
union {
struct {
uint32_t src[4], dst[4];
uint32_t id;
uint16_t vlan_id[VLAN_MAX_LAYERS];
uint16_t pad[1];
};
uint32_t u32[11];
};
} DefragHashKey6;
/* calculate the hash key for this packet
*
* we're using:
* hash_rand -- set at init time
* source address
* destination address
* id
* vlan_id
*/
static inline uint32_t DefragHashGetKey(Packet *p)
{
uint32_t key;
if (p->ip4h != NULL) {
DefragHashKey4 dhk = { .pad[0] = 0 };
if (p->src.addr_data32[0] > p->dst.addr_data32[0]) {
dhk.src = p->src.addr_data32[0];
dhk.dst = p->dst.addr_data32[0];
} else {
dhk.src = p->dst.addr_data32[0];
dhk.dst = p->src.addr_data32[0];
}
dhk.id = (uint32_t)IPV4_GET_IPID(p);
memcpy(&dhk.vlan_id[0], &p->vlan_id[0], sizeof(dhk.vlan_id));
uint32_t hash =
hashword(dhk.u32, sizeof(dhk.u32) / sizeof(uint32_t), defrag_config.hash_rand);
key = hash % defrag_config.hash_size;
} else if (p->ip6h != NULL) {
DefragHashKey6 dhk = { .pad[0] = 0 };
if (DefragHashRawAddressIPv6GtU32(p->src.addr_data32, p->dst.addr_data32)) {
dhk.src[0] = p->src.addr_data32[0];
dhk.src[1] = p->src.addr_data32[1];
dhk.src[2] = p->src.addr_data32[2];
dhk.src[3] = p->src.addr_data32[3];
dhk.dst[0] = p->dst.addr_data32[0];
dhk.dst[1] = p->dst.addr_data32[1];
dhk.dst[2] = p->dst.addr_data32[2];
dhk.dst[3] = p->dst.addr_data32[3];
} else {
dhk.src[0] = p->dst.addr_data32[0];
dhk.src[1] = p->dst.addr_data32[1];
dhk.src[2] = p->dst.addr_data32[2];
dhk.src[3] = p->dst.addr_data32[3];
dhk.dst[0] = p->src.addr_data32[0];
dhk.dst[1] = p->src.addr_data32[1];
dhk.dst[2] = p->src.addr_data32[2];
dhk.dst[3] = p->src.addr_data32[3];
}
dhk.id = IPV6_EXTHDR_GET_FH_ID(p);
memcpy(&dhk.vlan_id[0], &p->vlan_id[0], sizeof(dhk.vlan_id));
uint32_t hash =
hashword(dhk.u32, sizeof(dhk.u32) / sizeof(uint32_t), defrag_config.hash_rand);
key = hash % defrag_config.hash_size;
} else
key = 0;
return key;
}
/* Since two or more trackers can have the same hash key, we need to compare
* the tracker with the current tracker key. */
#define CMP_DEFRAGTRACKER(d1, d2, id) \
(((CMP_ADDR(&(d1)->src_addr, &(d2)->src) && CMP_ADDR(&(d1)->dst_addr, &(d2)->dst)) || \
(CMP_ADDR(&(d1)->src_addr, &(d2)->dst) && CMP_ADDR(&(d1)->dst_addr, &(d2)->src))) && \
(d1)->proto == IP_GET_IPPROTO(d2) && (d1)->id == (id) && \
(d1)->vlan_id[0] == (d2)->vlan_id[0] && (d1)->vlan_id[1] == (d2)->vlan_id[1] && \
(d1)->vlan_id[2] == (d2)->vlan_id[2])
static inline int DefragTrackerCompare(DefragTracker *t, Packet *p)
{
uint32_t id;
if (PKT_IS_IPV4(p)) {
id = (uint32_t)IPV4_GET_IPID(p);
} else {
id = IPV6_EXTHDR_GET_FH_ID(p);
}
return CMP_DEFRAGTRACKER(t, p, id);
}
/**
* \brief Get a new defrag tracker
*
* Get a new defrag tracker. We're checking memcap first and will try to make room
* if the memcap is reached.
*
* \retval dt *LOCKED* tracker on success, NULL on error.
*/
static DefragTracker *DefragTrackerGetNew(Packet *p)
{
#ifdef DEBUG
if (g_eps_defrag_memcap != UINT64_MAX && g_eps_defrag_memcap == p->pcap_cnt) {
SCLogNotice("simulating memcap hit for packet %" PRIu64, p->pcap_cnt);
ExceptionPolicyApply(p, defrag_config.memcap_policy, PKT_DROP_REASON_DEFRAG_MEMCAP);
return NULL;
}
#endif
DefragTracker *dt = NULL;
/* get a tracker from the spare queue */
dt = DefragTrackerDequeue(&defragtracker_spare_q);
if (dt == NULL) {
/* If we reached the max memcap, we get a used tracker */
if (!(DEFRAG_CHECK_MEMCAP(sizeof(DefragTracker)))) {
/* declare state of emergency */
//if (!(SC_ATOMIC_GET(defragtracker_flags) & DEFRAG_EMERGENCY)) {
// SC_ATOMIC_OR(defragtracker_flags, DEFRAG_EMERGENCY);
/* under high load, waking up the flow mgr each time leads
* to high cpu usage. Flows are not timed out much faster if
* we check a 1000 times a second. */
// FlowWakeupFlowManagerThread();
//}
dt = DefragTrackerGetUsedDefragTracker();
if (dt == NULL) {
ExceptionPolicyApply(p, defrag_config.memcap_policy, PKT_DROP_REASON_DEFRAG_MEMCAP);
return NULL;
}
/* freed a tracker, but it's unlocked */
} else {
/* now see if we can alloc a new tracker */
dt = DefragTrackerAlloc();
if (dt == NULL) {
ExceptionPolicyApply(p, defrag_config.memcap_policy, PKT_DROP_REASON_DEFRAG_MEMCAP);
return NULL;
}
/* tracker is initialized but *unlocked* */
}
} else {
/* tracker has been recycled before it went into the spare queue */
/* tracker is initialized (recycled) but *unlocked* */
}
(void) SC_ATOMIC_ADD(defragtracker_counter, 1);
SCMutexLock(&dt->lock);
return dt;
}
/* DefragGetTrackerFromHash
*
* Hash retrieval function for trackers. Looks up the hash bucket containing the
* tracker pointer. Then compares the packet with the found tracker to see if it is
* the tracker we need. If it isn't, walk the list until the right tracker is found.
*
* returns a *LOCKED* tracker or NULL
*/
DefragTracker *DefragGetTrackerFromHash (Packet *p)
{
DefragTracker *dt = NULL;
/* get the key to our bucket */
uint32_t key = DefragHashGetKey(p);
/* get our hash bucket and lock it */
DefragTrackerHashRow *hb = &defragtracker_hash[key];
DRLOCK_LOCK(hb);
/* see if the bucket already has a tracker */
if (hb->head == NULL) {
dt = DefragTrackerGetNew(p);
if (dt == NULL) {
DRLOCK_UNLOCK(hb);
return NULL;
}
/* tracker is locked */
hb->head = dt;
hb->tail = dt;
/* got one, now lock, initialize and return */
DefragTrackerInit(dt,p);
DRLOCK_UNLOCK(hb);
return dt;
}
/* ok, we have a tracker in the bucket. Let's find out if it is our tracker */
dt = hb->head;
/* see if this is the tracker we are looking for */
if (dt->remove || DefragTrackerCompare(dt, p) == 0) {
DefragTracker *pdt = NULL; /* previous tracker */
while (dt) {
pdt = dt;
dt = dt->hnext;
if (dt == NULL) {
dt = pdt->hnext = DefragTrackerGetNew(p);
if (dt == NULL) {
DRLOCK_UNLOCK(hb);
return NULL;
}
hb->tail = dt;
/* tracker is locked */
dt->hprev = pdt;
/* initialize and return */
DefragTrackerInit(dt,p);
DRLOCK_UNLOCK(hb);
return dt;
}
if (DefragTrackerCompare(dt, p) != 0) {
/* we found our tracker, lets put it on top of the
* hash list -- this rewards active trackers */
if (dt->hnext) {
dt->hnext->hprev = dt->hprev;
}
if (dt->hprev) {
dt->hprev->hnext = dt->hnext;
}
if (dt == hb->tail) {
hb->tail = dt->hprev;
}
dt->hnext = hb->head;
dt->hprev = NULL;
hb->head->hprev = dt;
hb->head = dt;
/* found our tracker, lock & return */
SCMutexLock(&dt->lock);
(void) DefragTrackerIncrUsecnt(dt);
DRLOCK_UNLOCK(hb);
return dt;
}
}
}
/* lock & return */
SCMutexLock(&dt->lock);
(void) DefragTrackerIncrUsecnt(dt);
DRLOCK_UNLOCK(hb);
return dt;
}
/** \brief look up a tracker in the hash
*
* \param a address to look up
*
* \retval h *LOCKED* tracker or NULL
*/
DefragTracker *DefragLookupTrackerFromHash (Packet *p)
{
DefragTracker *dt = NULL;
/* get the key to our bucket */
uint32_t key = DefragHashGetKey(p);
/* get our hash bucket and lock it */
DefragTrackerHashRow *hb = &defragtracker_hash[key];
DRLOCK_LOCK(hb);
/* see if the bucket already has a tracker */
if (hb->head == NULL) {
DRLOCK_UNLOCK(hb);
return dt;
}
/* ok, we have a tracker in the bucket. Let's find out if it is our tracker */
dt = hb->head;
/* see if this is the tracker we are looking for */
if (DefragTrackerCompare(dt, p) == 0) {
while (dt) {
dt = dt->hnext;
if (dt == NULL) {
DRLOCK_UNLOCK(hb);
return dt;
}
if (DefragTrackerCompare(dt, p) != 0) {
/* we found our tracker, lets put it on top of the
* hash list -- this rewards active tracker */
if (dt->hnext) {
dt->hnext->hprev = dt->hprev;
}
if (dt->hprev) {
dt->hprev->hnext = dt->hnext;
}
if (dt == hb->tail) {
hb->tail = dt->hprev;
}
dt->hnext = hb->head;
dt->hprev = NULL;
hb->head->hprev = dt;
hb->head = dt;
/* found our tracker, lock & return */
SCMutexLock(&dt->lock);
(void) DefragTrackerIncrUsecnt(dt);
DRLOCK_UNLOCK(hb);
return dt;
}
}
}
/* lock & return */
SCMutexLock(&dt->lock);
(void) DefragTrackerIncrUsecnt(dt);
DRLOCK_UNLOCK(hb);
return dt;
}
/** \internal
* \brief Get a tracker from the hash directly.
*
* Called in conditions where the spare queue is empty and memcap is reached.
*
* Walks the hash until a tracker can be freed. "defragtracker_prune_idx" atomic int makes
* sure we don't start at the top each time since that would clear the top of
* the hash leading to longer and longer search times under high pressure (observed).
*
* \retval dt tracker or NULL
*/
static DefragTracker *DefragTrackerGetUsedDefragTracker(void)
{
uint32_t idx = SC_ATOMIC_GET(defragtracker_prune_idx) % defrag_config.hash_size;
uint32_t cnt = defrag_config.hash_size;
while (cnt--) {
if (++idx >= defrag_config.hash_size)
idx = 0;
DefragTrackerHashRow *hb = &defragtracker_hash[idx];
if (DRLOCK_TRYLOCK(hb) != 0)
continue;
DefragTracker *dt = hb->tail;
if (dt == NULL) {
DRLOCK_UNLOCK(hb);
continue;
}
if (SCMutexTrylock(&dt->lock) != 0) {
DRLOCK_UNLOCK(hb);
continue;
}
/** never prune a tracker that is used by a packets
* we are currently processing in one of the threads */
if (SC_ATOMIC_GET(dt->use_cnt) > 0) {
DRLOCK_UNLOCK(hb);
SCMutexUnlock(&dt->lock);
continue;
}
/* remove from the hash */
if (dt->hprev != NULL)
dt->hprev->hnext = dt->hnext;
if (dt->hnext != NULL)
dt->hnext->hprev = dt->hprev;
if (hb->head == dt)
hb->head = dt->hnext;
if (hb->tail == dt)
hb->tail = dt->hprev;
dt->hnext = NULL;
dt->hprev = NULL;
DRLOCK_UNLOCK(hb);
DefragTrackerClearMemory(dt);
SCMutexUnlock(&dt->lock);
(void) SC_ATOMIC_ADD(defragtracker_prune_idx, (defrag_config.hash_size - cnt));
return dt;
}
return NULL;
}