diff options
Diffstat (limited to '')
-rw-r--r-- | src/tmqh-packetpool.c | 475 |
1 files changed, 475 insertions, 0 deletions
diff --git a/src/tmqh-packetpool.c b/src/tmqh-packetpool.c new file mode 100644 index 0000000..8594651 --- /dev/null +++ b/src/tmqh-packetpool.c @@ -0,0 +1,475 @@ +/* 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. + */ + +/** + * \file + * + * \author Victor Julien <victor@inliniac.net> + * + * Packetpool queue handlers. Packet pool is implemented as a stack. + */ + +#include "suricata-common.h" +#include "tmqh-packetpool.h" +#include "tm-queuehandlers.h" +#include "tm-threads.h" +#include "threads.h" +#include "decode.h" +#include "tm-modules.h" +#include "packet.h" +#include "util-profiling.h" +#include "util-validate.h" +#include "action-globals.h" + +/* Number of freed packet to save for one pool before freeing them. */ +#define MAX_PENDING_RETURN_PACKETS 32 +static uint32_t max_pending_return_packets = MAX_PENDING_RETURN_PACKETS; + +thread_local PktPool thread_pkt_pool; + +static inline PktPool *GetThreadPacketPool(void) +{ + return &thread_pkt_pool; +} + +/** + * \brief TmqhPacketpoolRegister + * \initonly + */ +void TmqhPacketpoolRegister (void) +{ + tmqh_table[TMQH_PACKETPOOL].name = "packetpool"; + tmqh_table[TMQH_PACKETPOOL].InHandler = TmqhInputPacketpool; + tmqh_table[TMQH_PACKETPOOL].OutHandler = TmqhOutputPacketpool; +} + +static int PacketPoolIsEmpty(PktPool *pool) +{ + /* Check local stack first. */ + if (pool->head || pool->return_stack.head) + return 0; + + return 1; +} + +void PacketPoolWait(void) +{ + PktPool *my_pool = GetThreadPacketPool(); + + if (PacketPoolIsEmpty(my_pool)) { + SCMutexLock(&my_pool->return_stack.mutex); + SC_ATOMIC_ADD(my_pool->return_stack.sync_now, 1); + SCCondWait(&my_pool->return_stack.cond, &my_pool->return_stack.mutex); + SCMutexUnlock(&my_pool->return_stack.mutex); + } + + while(PacketPoolIsEmpty(my_pool)) + cc_barrier(); +} + +/** \brief a initialized packet + * + * \warning Use *only* at init, not at packet runtime + */ +static void PacketPoolStorePacket(Packet *p) +{ + p->pool = GetThreadPacketPool(); + p->ReleasePacket = PacketPoolReturnPacket; + PacketPoolReturnPacket(p); +} + +static void PacketPoolGetReturnedPackets(PktPool *pool) +{ + SCMutexLock(&pool->return_stack.mutex); + /* Move all the packets from the locked return stack to the local stack. */ + pool->head = pool->return_stack.head; + pool->return_stack.head = NULL; + SCMutexUnlock(&pool->return_stack.mutex); +} + +/** \brief Get a new packet from the packet pool + * + * Only allocates from the thread's local stack, or mallocs new packets. + * If the local stack is empty, first move all the return stack packets to + * the local stack. + * \retval Packet pointer, or NULL on failure. + */ +Packet *PacketPoolGetPacket(void) +{ + PktPool *pool = GetThreadPacketPool(); +#ifdef DEBUG_VALIDATION + BUG_ON(pool->initialized == 0); + BUG_ON(pool->destroyed == 1); +#endif /* DEBUG_VALIDATION */ + if (pool->head) { + /* Stack is not empty. */ + Packet *p = pool->head; + pool->head = p->next; + p->pool = pool; + PacketReinit(p); + return p; + } + + /* Local Stack is empty, so check the return stack, which requires + * locking. */ + PacketPoolGetReturnedPackets(pool); + + /* Try to allocate again. Need to check for not empty again, since the + * return stack might have been empty too. + */ + if (pool->head) { + /* Stack is not empty. */ + Packet *p = pool->head; + pool->head = p->next; + p->pool = pool; + PacketReinit(p); + return p; + } + + /* Failed to allocate a packet, so return NULL. */ + /* Optionally, could allocate a new packet here. */ + return NULL; +} + +/** \brief Return packet to Packet pool + * + */ +void PacketPoolReturnPacket(Packet *p) +{ + PktPool *my_pool = GetThreadPacketPool(); + PktPool *pool = p->pool; + if (pool == NULL) { + PacketFree(p); + return; + } + + PacketReleaseRefs(p); + +#ifdef DEBUG_VALIDATION + BUG_ON(pool->initialized == 0); + BUG_ON(pool->destroyed == 1); + BUG_ON(my_pool->initialized == 0); + BUG_ON(my_pool->destroyed == 1); +#endif /* DEBUG_VALIDATION */ + + if (pool == my_pool) { + /* Push back onto this thread's own stack, so no locking. */ + p->next = my_pool->head; + my_pool->head = p; + } else { + PktPool *pending_pool = my_pool->pending_pool; + if (pending_pool == NULL || pending_pool == pool) { + if (pending_pool == NULL) { + /* No pending packet, so store the current packet. */ + p->next = NULL; + my_pool->pending_pool = pool; + my_pool->pending_head = p; + my_pool->pending_tail = p; + my_pool->pending_count = 1; + } else if (pending_pool == pool) { + /* Another packet for the pending pool list. */ + p->next = my_pool->pending_head; + my_pool->pending_head = p; + my_pool->pending_count++; + } + + if (SC_ATOMIC_GET(pool->return_stack.sync_now) || my_pool->pending_count > max_pending_return_packets) { + /* Return the entire list of pending packets. */ + SCMutexLock(&pool->return_stack.mutex); + my_pool->pending_tail->next = pool->return_stack.head; + pool->return_stack.head = my_pool->pending_head; + SC_ATOMIC_RESET(pool->return_stack.sync_now); + SCCondSignal(&pool->return_stack.cond); + SCMutexUnlock(&pool->return_stack.mutex); + /* Clear the list of pending packets to return. */ + my_pool->pending_pool = NULL; + my_pool->pending_head = NULL; + my_pool->pending_tail = NULL; + my_pool->pending_count = 0; + } + } else { + /* Push onto return stack for this pool */ + SCMutexLock(&pool->return_stack.mutex); + p->next = pool->return_stack.head; + pool->return_stack.head = p; + SC_ATOMIC_RESET(pool->return_stack.sync_now); + SCMutexUnlock(&pool->return_stack.mutex); + SCCondSignal(&pool->return_stack.cond); + } + } +} + +void PacketPoolInitEmpty(void) +{ + PktPool *my_pool = GetThreadPacketPool(); + +#ifdef DEBUG_VALIDATION + BUG_ON(my_pool->initialized); + my_pool->initialized = 1; + my_pool->destroyed = 0; +#endif /* DEBUG_VALIDATION */ + + SCMutexInit(&my_pool->return_stack.mutex, NULL); + SCCondInit(&my_pool->return_stack.cond, NULL); + SC_ATOMIC_INIT(my_pool->return_stack.sync_now); +} + +void PacketPoolInit(void) +{ + extern uint16_t max_pending_packets; + + PktPool *my_pool = GetThreadPacketPool(); + +#ifdef DEBUG_VALIDATION + BUG_ON(my_pool->initialized); + my_pool->initialized = 1; + my_pool->destroyed = 0; +#endif /* DEBUG_VALIDATION */ + + SCMutexInit(&my_pool->return_stack.mutex, NULL); + SCCondInit(&my_pool->return_stack.cond, NULL); + SC_ATOMIC_INIT(my_pool->return_stack.sync_now); + + /* pre allocate packets */ + SCLogDebug("preallocating packets... packet size %" PRIuMAX "", + (uintmax_t)SIZE_OF_PACKET); + int i = 0; + for (i = 0; i < max_pending_packets; i++) { + Packet *p = PacketGetFromAlloc(); + if (unlikely(p == NULL)) { + FatalError("Fatal error encountered while allocating a packet. Exiting..."); + } + PacketPoolStorePacket(p); + } + + //SCLogInfo("preallocated %"PRIiMAX" packets. Total memory %"PRIuMAX"", + // max_pending_packets, (uintmax_t)(max_pending_packets*SIZE_OF_PACKET)); +} + +void PacketPoolDestroy(void) +{ + Packet *p = NULL; + PktPool *my_pool = GetThreadPacketPool(); + +#ifdef DEBUG_VALIDATION + BUG_ON(my_pool && my_pool->destroyed); +#endif /* DEBUG_VALIDATION */ + + if (my_pool && my_pool->pending_pool != NULL) { + p = my_pool->pending_head; + while (p) { + Packet *next_p = p->next; + PacketFree(p); + p = next_p; + my_pool->pending_count--; + } +#ifdef DEBUG_VALIDATION + BUG_ON(my_pool->pending_count); +#endif /* DEBUG_VALIDATION */ + my_pool->pending_pool = NULL; + my_pool->pending_head = NULL; + my_pool->pending_tail = NULL; + } + + while ((p = PacketPoolGetPacket()) != NULL) { + PacketFree(p); + } + +#ifdef DEBUG_VALIDATION + my_pool->initialized = 0; + my_pool->destroyed = 1; +#endif /* DEBUG_VALIDATION */ +} + +Packet *TmqhInputPacketpool(ThreadVars *tv) +{ + return PacketPoolGetPacket(); +} + +void TmqhOutputPacketpool(ThreadVars *t, Packet *p) +{ + bool proot = false; + + SCEnter(); + SCLogDebug("Packet %p, p->root %p, alloced %s", p, p->root, BOOL2STR(p->pool == NULL)); + + if (IS_TUNNEL_PKT(p)) { + SCLogDebug("Packet %p is a tunnel packet: %s", + p,p->root ? "upper layer" : "tunnel root"); + + /* get a lock to access root packet fields */ + SCSpinlock *lock = p->root ? &p->root->persistent.tunnel_lock : &p->persistent.tunnel_lock; + SCSpinLock(lock); + + if (IS_TUNNEL_ROOT_PKT(p)) { + SCLogDebug("IS_TUNNEL_ROOT_PKT == TRUE"); + CaptureStatsUpdate(t, p); + + const uint16_t outstanding = TUNNEL_PKT_TPR(p) - TUNNEL_PKT_RTV(p); + SCLogDebug("root pkt: outstanding %u", outstanding); + if (outstanding == 0) { + SCLogDebug("no tunnel packets outstanding, no more tunnel " + "packet(s) depending on this root"); + /* if this packet is the root and there are no + * more tunnel packets to consider + * + * return it to the pool */ + } else { + SCLogDebug("tunnel root Packet %p: outstanding > 0, so " + "packets are still depending on this root, setting " + "SET_TUNNEL_PKT_VERDICTED", p); + /* if this is the root and there are more tunnel + * packets, return this to the pool. It's still referenced + * by the tunnel packets, and we will return it + * when we handle them */ + SET_TUNNEL_PKT_VERDICTED(p); + + PACKET_PROFILING_END(p); + SCSpinUnlock(lock); + SCReturn; + } + } else { + SCLogDebug("NOT IS_TUNNEL_ROOT_PKT, so tunnel pkt"); + + TUNNEL_INCR_PKT_RTV_NOLOCK(p); + const uint16_t outstanding = TUNNEL_PKT_TPR(p) - TUNNEL_PKT_RTV(p); + SCLogDebug("tunnel pkt: outstanding %u", outstanding); + /* all tunnel packets are processed except us. Root already + * processed. So return tunnel pkt and root packet to the + * pool. */ + if (outstanding == 0 && + p->root && IS_TUNNEL_PKT_VERDICTED(p->root)) + { + SCLogDebug("root verdicted == true && no outstanding"); + + /* handle freeing the root as well*/ + SCLogDebug("setting proot = 1 for root pkt, p->root %p " + "(tunnel packet %p)", p->root, p); + proot = true; + + /* fall through */ + + } else { + /* root not ready yet, or not the last tunnel packet, + * so get rid of the tunnel pkt only */ + + SCLogDebug("NOT IS_TUNNEL_PKT_VERDICTED (%s) || " + "outstanding > 0 (%u)", + (p->root && IS_TUNNEL_PKT_VERDICTED(p->root)) ? "true" : "false", + outstanding); + + /* fall through */ + } + } + SCSpinUnlock(lock); + + SCLogDebug("tunnel stuff done, move on (proot %d)", proot); + + } else { + CaptureStatsUpdate(t, p); + } + + SCLogDebug("[packet %p][%s] %s", p, + IS_TUNNEL_PKT(p) ? IS_TUNNEL_ROOT_PKT(p) ? "tunnel::root" : "tunnel::leaf" + : "no tunnel", + (p->action & ACTION_DROP) ? "DROP" : "no drop"); + + /* we're done with the tunnel root now as well */ + if (proot == true) { + SCLogDebug("getting rid of root pkt... alloc'd %s", BOOL2STR(p->root->pool == NULL)); + + PacketReleaseRefs(p->root); + p->root->ReleasePacket(p->root); + p->root = NULL; + } + + PACKET_PROFILING_END(p); + + PacketReleaseRefs(p); + p->ReleasePacket(p); + + SCReturn; +} + +/** + * \brief Release all the packets in the queue back to the packetpool. Mainly + * used by threads that have failed, and wants to return the packets back + * to the packetpool. + * + * \param pq Pointer to the packetqueue from which the packets have to be + * returned back to the packetpool + * + * \warning this function assumes that the pq does not use locking + */ +void TmqhReleasePacketsToPacketPool(PacketQueue *pq) +{ + Packet *p = NULL; + + if (pq == NULL) + return; + + while ((p = PacketDequeue(pq)) != NULL) { + DEBUG_VALIDATE_BUG_ON(p->flow != NULL); + TmqhOutputPacketpool(NULL, p); + } + + return; +} + +/** number of packets to keep reserved when calculating the pending + * return packets count. This assumes we need at max 10 packets in one + * PacketPoolWaitForN call. The actual number is 9 now, so this has a + * bit of margin. */ +#define RESERVED_PACKETS 10 + +/** + * \brief Set the max_pending_return_packets value + * + * Set it to the max pending packets value, divided by the number + * of lister threads. Normally, in autofp these are the stream/detect/log + * worker threads. + * + * The max_pending_return_packets value needs to stay below the packet + * pool size of the 'producers' (normally pkt capture threads but also + * flow timeout injection ) to avoid a deadlock where all the 'workers' + * keep packets in their return pools, while the capture thread can't + * continue because its pool is empty. + */ +void PacketPoolPostRunmodes(void) +{ + extern uint16_t max_pending_packets; + uint16_t pending_packets = max_pending_packets; + if (pending_packets < RESERVED_PACKETS) { + FatalError("'max-pending-packets' setting " + "must be at least %d", + RESERVED_PACKETS); + } + uint32_t threads = TmThreadCountThreadsByTmmFlags(TM_FLAG_DETECT_TM); + if (threads == 0) + return; + + uint32_t packets = (pending_packets / threads) - 1; + if (packets < max_pending_return_packets) + max_pending_return_packets = packets; + + /* make sure to have a margin in the return logic */ + if (max_pending_return_packets >= RESERVED_PACKETS) + max_pending_return_packets -= RESERVED_PACKETS; + + SCLogDebug("detect threads %u, max packets %u, max_pending_return_packets %u", + threads, packets, max_pending_return_packets); +} |