summaryrefslogtreecommitdiffstats
path: root/src/defrag.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/defrag.c')
-rw-r--r--src/defrag.c2559
1 files changed, 2559 insertions, 0 deletions
diff --git a/src/defrag.c b/src/defrag.c
new file mode 100644
index 0000000..71cf420
--- /dev/null
+++ b/src/defrag.c
@@ -0,0 +1,2559 @@
+/* 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 Endace Technology Limited, Jason Ish <jason.ish@endace.com>
+ *
+ * Defragmentation module.
+ * References:
+ * - RFC 815
+ * - OpenBSD PF's IP normalization (pf_norm.c)
+ *
+ * \todo pool for frag packet storage
+ * \todo policy bsd-right
+ * \todo profile hash function
+ * \todo log anomalies
+ */
+
+#include "suricata-common.h"
+
+#include "queue.h"
+
+#include "suricata.h"
+#include "threads.h"
+#include "conf.h"
+#include "decode-ipv6.h"
+#include "util-hashlist.h"
+#include "util-pool.h"
+#include "util-time.h"
+#include "util-print.h"
+#include "util-debug.h"
+#include "util-fix_checksum.h"
+#include "util-random.h"
+#include "stream-tcp-private.h"
+#include "stream-tcp-reassemble.h"
+#include "util-host-os-info.h"
+#include "util-validate.h"
+
+#include "defrag.h"
+#include "defrag-hash.h"
+#include "defrag-queue.h"
+#include "defrag-config.h"
+
+#include "tmqh-packetpool.h"
+#include "decode.h"
+
+#ifdef UNITTESTS
+#include "util-unittest.h"
+#endif
+
+#define DEFAULT_DEFRAG_HASH_SIZE 0xffff
+#define DEFAULT_DEFRAG_POOL_SIZE 0xffff
+
+/**
+ * Default timeout (in seconds) before a defragmentation tracker will
+ * be released.
+ */
+#define TIMEOUT_DEFAULT 60
+
+/**
+ * Maximum allowed timeout, 24 hours.
+ */
+#define TIMEOUT_MAX (60 * 60 * 24)
+
+/**
+ * Minimum allowed timeout, 1 second.
+ */
+#define TIMEOUT_MIN 1
+
+/** Fragment reassembly policies. */
+enum defrag_policies {
+ DEFRAG_POLICY_FIRST = 1,
+ DEFRAG_POLICY_LAST,
+ DEFRAG_POLICY_BSD,
+ DEFRAG_POLICY_BSD_RIGHT,
+ DEFRAG_POLICY_LINUX,
+ DEFRAG_POLICY_WINDOWS,
+ DEFRAG_POLICY_SOLARIS,
+
+ DEFRAG_POLICY_DEFAULT = DEFRAG_POLICY_BSD,
+};
+
+static uint8_t default_policy = DEFRAG_POLICY_BSD;
+
+/** The global DefragContext so all threads operate from the same
+ * context. */
+static DefragContext *defrag_context;
+
+RB_GENERATE(IP_FRAGMENTS, Frag_, rb, DefragRbFragCompare);
+
+/**
+ * Utility/debugging function to dump the frags associated with a
+ * tracker. Only enable when unit tests are enabled.
+ */
+#if 0
+#ifdef UNITTESTS
+static void
+DumpFrags(DefragTracker *tracker)
+{
+ Frag *frag;
+
+ printf("Dumping frags for packet: ID=%d\n", tracker->id);
+ TAILQ_FOREACH(frag, &tracker->frags, next) {
+ printf("-> Frag: frag_offset=%d, frag_len=%d, data_len=%d, ltrim=%d, skip=%d\n", frag->offset, frag->len, frag->data_len, frag->ltrim, frag->skip);
+ PrintRawDataFp(stdout, frag->pkt, frag->len);
+ }
+}
+#endif /* UNITTESTS */
+#endif
+
+/**
+ * \brief Reset a frag for reuse in a pool.
+ */
+static void
+DefragFragReset(Frag *frag)
+{
+ if (frag->pkt != NULL)
+ SCFree(frag->pkt);
+ memset(frag, 0, sizeof(*frag));
+}
+
+/**
+ * \brief Allocate a new frag for use in a pool.
+ */
+static int
+DefragFragInit(void *data, void *initdata)
+{
+ Frag *frag = data;
+
+ memset(frag, 0, sizeof(*frag));
+ return 1;
+}
+
+/**
+ * \brief Free all frags associated with a tracker.
+ */
+void
+DefragTrackerFreeFrags(DefragTracker *tracker)
+{
+ Frag *frag, *tmp;
+
+ /* Lock the frag pool as we'll be return items to it. */
+ SCMutexLock(&defrag_context->frag_pool_lock);
+
+ RB_FOREACH_SAFE(frag, IP_FRAGMENTS, &tracker->fragment_tree, tmp) {
+ RB_REMOVE(IP_FRAGMENTS, &tracker->fragment_tree, frag);
+ DefragFragReset(frag);
+ PoolReturn(defrag_context->frag_pool, frag);
+ }
+
+ SCMutexUnlock(&defrag_context->frag_pool_lock);
+}
+
+/**
+ * \brief Create a new DefragContext.
+ *
+ * \retval On success a return an initialized DefragContext, otherwise
+ * NULL will be returned.
+ */
+static DefragContext *
+DefragContextNew(void)
+{
+ DefragContext *dc;
+
+ dc = SCCalloc(1, sizeof(*dc));
+ if (unlikely(dc == NULL))
+ return NULL;
+
+ /* Initialize the pool of trackers. */
+ intmax_t tracker_pool_size;
+ if (!ConfGetInt("defrag.trackers", &tracker_pool_size) || tracker_pool_size == 0) {
+ tracker_pool_size = DEFAULT_DEFRAG_HASH_SIZE;
+ }
+
+ /* Initialize the pool of frags. */
+ intmax_t frag_pool_size;
+ if (!ConfGetInt("defrag.max-frags", &frag_pool_size) || frag_pool_size == 0) {
+ frag_pool_size = DEFAULT_DEFRAG_POOL_SIZE;
+ }
+ intmax_t frag_pool_prealloc = frag_pool_size / 2;
+ dc->frag_pool = PoolInit(frag_pool_size, frag_pool_prealloc,
+ sizeof(Frag),
+ NULL, DefragFragInit, dc, NULL, NULL);
+ if (dc->frag_pool == NULL) {
+ FatalError("Defrag: Failed to initialize fragment pool.");
+ }
+ if (SCMutexInit(&dc->frag_pool_lock, NULL) != 0) {
+ FatalError("Defrag: Failed to initialize frag pool mutex.");
+ }
+
+ /* Set the default timeout. */
+ intmax_t timeout;
+ if (!ConfGetInt("defrag.timeout", &timeout)) {
+ dc->timeout = TIMEOUT_DEFAULT;
+ }
+ else {
+ if (timeout < TIMEOUT_MIN) {
+ FatalError("defrag: Timeout less than minimum allowed value.");
+ }
+ else if (timeout > TIMEOUT_MAX) {
+ FatalError("defrag: Timeout greater than maximum allowed value.");
+ }
+ dc->timeout = timeout;
+ }
+
+ SCLogDebug("Defrag Initialized:");
+ SCLogDebug("\tTimeout: %"PRIuMAX, (uintmax_t)dc->timeout);
+ SCLogDebug("\tMaximum defrag trackers: %"PRIuMAX, tracker_pool_size);
+ SCLogDebug("\tPreallocated defrag trackers: %"PRIuMAX, tracker_pool_size);
+ SCLogDebug("\tMaximum fragments: %"PRIuMAX, (uintmax_t)frag_pool_size);
+ SCLogDebug("\tPreallocated fragments: %"PRIuMAX, (uintmax_t)frag_pool_prealloc);
+
+ return dc;
+}
+
+static void
+DefragContextDestroy(DefragContext *dc)
+{
+ if (dc == NULL)
+ return;
+
+ PoolFree(dc->frag_pool);
+ SCFree(dc);
+}
+
+/**
+ * Attempt to re-assemble a packet.
+ *
+ * \param tracker The defragmentation tracker to reassemble from.
+ */
+static Packet *
+Defrag4Reassemble(ThreadVars *tv, DefragTracker *tracker, Packet *p)
+{
+ Packet *rp = NULL;
+
+ /* Should not be here unless we have seen the last fragment. */
+ if (!tracker->seen_last) {
+ return NULL;
+ }
+
+ /* Check that we have the first fragment and its of a valid size. */
+ Frag *first = RB_MIN(IP_FRAGMENTS, &tracker->fragment_tree);
+ if (first == NULL) {
+ goto done;
+ } else if (first->offset != 0) {
+ /* Still waiting for the first fragment. */
+ goto done;
+ } else if (first->len < sizeof(IPV4Hdr)) {
+ /* First fragment isn't enough for an IPv6 header. */
+ goto error_remove_tracker;
+ }
+
+ /* Check that we have all the data. Relies on the fact that
+ * fragments are inserted if frag_offset order. */
+ Frag *frag = NULL;
+ size_t len = 0;
+ RB_FOREACH_FROM(frag, IP_FRAGMENTS, first) {
+ if (frag->offset > len) {
+ /* This fragment starts after the end of the previous
+ * fragment. We have a hole. */
+ goto done;
+ }
+ else {
+ len += frag->data_len;
+ }
+ }
+
+ /* Allocate a Packet for the reassembled packet. On failure we
+ * SCFree all the resources held by this tracker. */
+ rp = PacketDefragPktSetup(p, NULL, 0, IPV4_GET_IPPROTO(p));
+ if (rp == NULL) {
+ goto error_remove_tracker;
+ }
+ PKT_SET_SRC(rp, PKT_SRC_DEFRAG);
+ rp->flags |= PKT_REBUILT_FRAGMENT;
+ rp->recursion_level = p->recursion_level;
+
+ int fragmentable_offset = 0;
+ uint16_t fragmentable_len = 0;
+ uint16_t hlen = 0;
+ int ip_hdr_offset = 0;
+
+ RB_FOREACH(frag, IP_FRAGMENTS, &tracker->fragment_tree) {
+ SCLogDebug("frag %p, data_len %u, offset %u, pcap_cnt %"PRIu64,
+ frag, frag->data_len, frag->offset, frag->pcap_cnt);
+
+ if (frag->skip)
+ continue;
+ if (frag->ltrim >= frag->data_len)
+ continue;
+ if (frag->offset == 0) {
+
+ if (PacketCopyData(rp, frag->pkt, frag->len) == -1)
+ goto error_remove_tracker;
+
+ hlen = frag->hlen;
+ ip_hdr_offset = frag->ip_hdr_offset;
+
+ /* This is the start of the fragmentable portion of the
+ * first packet. All fragment offsets are relative to
+ * this. */
+ fragmentable_offset = frag->ip_hdr_offset + frag->hlen;
+ fragmentable_len = frag->data_len;
+ }
+ else {
+ int pkt_end = fragmentable_offset + frag->offset + frag->data_len;
+ if (pkt_end > (int)MAX_PAYLOAD_SIZE) {
+ SCLogDebug("Failed re-assemble "
+ "fragmented packet, exceeds size of packet buffer.");
+ goto error_remove_tracker;
+ }
+ if (PacketCopyDataOffset(rp,
+ fragmentable_offset + frag->offset + frag->ltrim,
+ frag->pkt + frag->data_offset + frag->ltrim,
+ frag->data_len - frag->ltrim) == -1) {
+ goto error_remove_tracker;
+ }
+ if (frag->offset > UINT16_MAX - frag->data_len) {
+ SCLogDebug("Failed re-assemble "
+ "fragmentable_len exceeds UINT16_MAX");
+ goto error_remove_tracker;
+ }
+ if (frag->offset + frag->data_len > fragmentable_len)
+ fragmentable_len = frag->offset + frag->data_len;
+ }
+
+ if (!frag->more_frags) {
+ break;
+ }
+ }
+
+ SCLogDebug("ip_hdr_offset %u, hlen %" PRIu16 ", fragmentable_len %" PRIu16, ip_hdr_offset, hlen,
+ fragmentable_len);
+
+ rp->ip4h = (IPV4Hdr *)(GET_PKT_DATA(rp) + ip_hdr_offset);
+ uint16_t old = rp->ip4h->ip_len + rp->ip4h->ip_off;
+ DEBUG_VALIDATE_BUG_ON(hlen > UINT16_MAX - fragmentable_len);
+ rp->ip4h->ip_len = htons(fragmentable_len + hlen);
+ rp->ip4h->ip_off = 0;
+ rp->ip4h->ip_csum = FixChecksum(rp->ip4h->ip_csum,
+ old, rp->ip4h->ip_len + rp->ip4h->ip_off);
+ SET_PKT_LEN(rp, ip_hdr_offset + hlen + fragmentable_len);
+
+ tracker->remove = 1;
+ DefragTrackerFreeFrags(tracker);
+done:
+ return rp;
+
+error_remove_tracker:
+ tracker->remove = 1;
+ DefragTrackerFreeFrags(tracker);
+ if (rp != NULL)
+ PacketFreeOrRelease(rp);
+ return NULL;
+}
+
+/**
+ * Attempt to re-assemble a packet.
+ *
+ * \param tracker The defragmentation tracker to reassemble from.
+ */
+static Packet *
+Defrag6Reassemble(ThreadVars *tv, DefragTracker *tracker, Packet *p)
+{
+ Packet *rp = NULL;
+
+ /* Should not be here unless we have seen the last fragment. */
+ if (!tracker->seen_last)
+ return NULL;
+
+ /* Check that we have the first fragment and its of a valid size. */
+ Frag *first = RB_MIN(IP_FRAGMENTS, &tracker->fragment_tree);
+ if (first == NULL) {
+ goto done;
+ } else if (first->offset != 0) {
+ /* Still waiting for the first fragment. */
+ goto done;
+ } else if (first->len < sizeof(IPV6Hdr)) {
+ /* First fragment isn't enough for an IPv6 header. */
+ goto error_remove_tracker;
+ }
+
+ /* Check that we have all the data. Relies on the fact that
+ * fragments are inserted if frag_offset order. */
+ size_t len = 0;
+ Frag *frag = NULL;
+ RB_FOREACH_FROM(frag, IP_FRAGMENTS, first) {
+ if (frag->skip) {
+ continue;
+ }
+
+ if (frag == first) {
+ if (frag->offset != 0) {
+ goto done;
+ }
+ len = frag->data_len;
+ }
+ else {
+ if (frag->offset > len) {
+ /* This fragment starts after the end of the previous
+ * fragment. We have a hole. */
+ goto done;
+ }
+ else {
+ len += frag->data_len;
+ }
+ }
+ }
+
+ /* Allocate a Packet for the reassembled packet. On failure we
+ * SCFree all the resources held by this tracker. */
+ rp = PacketDefragPktSetup(p, (uint8_t *)p->ip6h,
+ IPV6_GET_PLEN(p) + sizeof(IPV6Hdr), 0);
+ if (rp == NULL) {
+ goto error_remove_tracker;
+ }
+ PKT_SET_SRC(rp, PKT_SRC_DEFRAG);
+
+ uint16_t unfragmentable_len = 0;
+ int fragmentable_offset = 0;
+ uint16_t fragmentable_len = 0;
+ int ip_hdr_offset = 0;
+ uint8_t next_hdr = 0;
+ RB_FOREACH(frag, IP_FRAGMENTS, &tracker->fragment_tree) {
+ if (frag->skip)
+ continue;
+ if (frag->data_len - frag->ltrim <= 0)
+ continue;
+ if (frag->offset == 0) {
+ IPV6FragHdr *frag_hdr = (IPV6FragHdr *)(frag->pkt +
+ frag->frag_hdr_offset);
+ next_hdr = frag_hdr->ip6fh_nxt;
+
+ /* This is the first packet, we use this packets link and
+ * IPv6 headers. We also copy in its data, but remove the
+ * fragmentation header. */
+ if (PacketCopyData(rp, frag->pkt, frag->frag_hdr_offset) == -1)
+ goto error_remove_tracker;
+ if (PacketCopyDataOffset(rp, frag->frag_hdr_offset,
+ frag->pkt + frag->frag_hdr_offset + sizeof(IPV6FragHdr),
+ frag->data_len) == -1)
+ goto error_remove_tracker;
+ ip_hdr_offset = frag->ip_hdr_offset;
+
+ /* This is the start of the fragmentable portion of the
+ * first packet. All fragment offsets are relative to
+ * this. */
+ fragmentable_offset = frag->frag_hdr_offset;
+ fragmentable_len = frag->data_len;
+
+ /* unfragmentable part is the part between the ipv6 header
+ * and the frag header. */
+ DEBUG_VALIDATE_BUG_ON(fragmentable_offset < ip_hdr_offset + IPV6_HEADER_LEN);
+ DEBUG_VALIDATE_BUG_ON(
+ fragmentable_offset - ip_hdr_offset - IPV6_HEADER_LEN > UINT16_MAX);
+ unfragmentable_len = (uint16_t)(fragmentable_offset - ip_hdr_offset - IPV6_HEADER_LEN);
+ if (unfragmentable_len >= fragmentable_offset)
+ goto error_remove_tracker;
+ }
+ else {
+ if (PacketCopyDataOffset(rp, fragmentable_offset + frag->offset + frag->ltrim,
+ frag->pkt + frag->data_offset + frag->ltrim,
+ frag->data_len - frag->ltrim) == -1)
+ goto error_remove_tracker;
+ if (frag->offset + frag->data_len > fragmentable_len)
+ fragmentable_len = frag->offset + frag->data_len;
+ }
+
+ if (!frag->more_frags) {
+ break;
+ }
+ }
+
+ rp->ip6h = (IPV6Hdr *)(GET_PKT_DATA(rp) + ip_hdr_offset);
+ DEBUG_VALIDATE_BUG_ON(unfragmentable_len > UINT16_MAX - fragmentable_len);
+ rp->ip6h->s_ip6_plen = htons(fragmentable_len + unfragmentable_len);
+ /* if we have no unfragmentable part, so no ext hdrs before the frag
+ * header, we need to update the ipv6 headers next header field. This
+ * points to the frag header, and we will make it point to the layer
+ * directly after the frag header. */
+ if (unfragmentable_len == 0)
+ rp->ip6h->s_ip6_nxt = next_hdr;
+ SET_PKT_LEN(rp, ip_hdr_offset + sizeof(IPV6Hdr) +
+ unfragmentable_len + fragmentable_len);
+
+ tracker->remove = 1;
+ DefragTrackerFreeFrags(tracker);
+done:
+ return rp;
+
+error_remove_tracker:
+ tracker->remove = 1;
+ DefragTrackerFreeFrags(tracker);
+ if (rp != NULL)
+ PacketFreeOrRelease(rp);
+ return NULL;
+}
+
+/**
+ * The RB_TREE compare function for fragments.
+ *
+ * When it comes to adding fragments, we want subsequent ones with the
+ * same offset to be treated as greater than, so we don't have an
+ * equal return value here.
+ */
+int DefragRbFragCompare(struct Frag_ *a, struct Frag_ *b) {
+ if (a->offset < b->offset) {
+ return -1;
+ }
+ return 1;
+}
+
+/**
+ * Insert a new IPv4/IPv6 fragment into a tracker.
+ *
+ * \todo Allocate packet buffers from a pool.
+ */
+static Packet *
+DefragInsertFrag(ThreadVars *tv, DecodeThreadVars *dtv, DefragTracker *tracker, Packet *p)
+{
+ Packet *r = NULL;
+ uint16_t ltrim = 0;
+
+ uint8_t more_frags;
+ uint16_t frag_offset;
+
+ /* IPv4 header length - IPv4 only. */
+ uint8_t hlen = 0;
+
+ /* This is the offset of the start of the data in the packet that
+ * falls after the IP header. */
+ uint16_t data_offset;
+
+ /* The length of the (fragmented) data. This is the length of the
+ * data that falls after the IP header. */
+ uint16_t data_len;
+
+ /* Where the fragment ends. */
+ uint16_t frag_end;
+
+ /* Offset in the packet to the IPv6 header. */
+ uint16_t ip_hdr_offset;
+
+ /* Offset in the packet to the IPv6 frag header. IPv6 only. */
+ uint16_t frag_hdr_offset = 0;
+
+ /* Address family */
+ int af = tracker->af;
+
+ /* settings for updating a payload when an ip6 fragment with
+ * unfragmentable exthdrs are encountered. */
+ uint32_t ip6_nh_set_offset = 0;
+ uint8_t ip6_nh_set_value = 0;
+
+#ifdef DEBUG
+ uint64_t pcap_cnt = p->pcap_cnt;
+#endif
+
+ if (tracker->af == AF_INET) {
+ more_frags = IPV4_GET_MF(p);
+ frag_offset = (uint16_t)(IPV4_GET_IPOFFSET(p) << 3);
+ hlen = IPV4_GET_HLEN(p);
+ data_offset = (uint16_t)((uint8_t *)p->ip4h + hlen - GET_PKT_DATA(p));
+ data_len = IPV4_GET_IPLEN(p) - hlen;
+ frag_end = frag_offset + data_len;
+ ip_hdr_offset = (uint16_t)((uint8_t *)p->ip4h - GET_PKT_DATA(p));
+
+ /* Ignore fragment if the end of packet extends past the
+ * maximum size of a packet. */
+ if (IPV4_HEADER_LEN + frag_offset + data_len > IPV4_MAXPACKET_LEN) {
+ ENGINE_SET_EVENT(p, IPV4_FRAG_PKT_TOO_LARGE);
+ return NULL;
+ }
+ }
+ else if (tracker->af == AF_INET6) {
+ more_frags = IPV6_EXTHDR_GET_FH_FLAG(p);
+ frag_offset = IPV6_EXTHDR_GET_FH_OFFSET(p);
+ data_offset = p->ip6eh.fh_data_offset;
+ data_len = p->ip6eh.fh_data_len;
+ frag_end = frag_offset + data_len;
+ ip_hdr_offset = (uint16_t)((uint8_t *)p->ip6h - GET_PKT_DATA(p));
+ frag_hdr_offset = p->ip6eh.fh_header_offset;
+
+ SCLogDebug("mf %s frag_offset %u data_offset %u, data_len %u, "
+ "frag_end %u, ip_hdr_offset %u, frag_hdr_offset %u",
+ more_frags ? "true" : "false", frag_offset, data_offset,
+ data_len, frag_end, ip_hdr_offset, frag_hdr_offset);
+
+ /* handle unfragmentable exthdrs */
+ if (ip_hdr_offset + IPV6_HEADER_LEN < frag_hdr_offset) {
+ SCLogDebug("we have exthdrs before fraghdr %u bytes",
+ (uint32_t)(frag_hdr_offset - (ip_hdr_offset + IPV6_HEADER_LEN)));
+
+ /* get the offset of the 'next' field in exthdr before the FH,
+ * relative to the buffer start */
+
+ /* store offset and FH 'next' value for updating frag buffer below */
+ ip6_nh_set_offset = p->ip6eh.fh_prev_hdr_offset;
+ ip6_nh_set_value = IPV6_EXTHDR_GET_FH_NH(p);
+ SCLogDebug("offset %d, value %u", ip6_nh_set_offset, ip6_nh_set_value);
+ }
+
+ /* Ignore fragment if the end of packet extends past the
+ * maximum size of a packet. */
+ if (frag_offset + data_len > IPV6_MAXPACKET) {
+ ENGINE_SET_EVENT(p, IPV6_FRAG_PKT_TOO_LARGE);
+ return NULL;
+ }
+ }
+ else {
+ DEBUG_VALIDATE_BUG_ON(1);
+ return NULL;
+ }
+
+ /* Update timeout. */
+ tracker->timeout = SCTIME_FROM_SECS(SCTIME_SECS(p->ts) + tracker->host_timeout);
+
+ Frag *prev = NULL, *next = NULL;
+ bool overlap = false;
+ ltrim = 0;
+
+ if (!RB_EMPTY(&tracker->fragment_tree)) {
+ Frag key = {
+ .offset = frag_offset - 1,
+ };
+ next = RB_NFIND(IP_FRAGMENTS, &tracker->fragment_tree, &key);
+ if (next == NULL) {
+ prev = RB_MIN(IP_FRAGMENTS, &tracker->fragment_tree);
+ next = IP_FRAGMENTS_RB_NEXT(prev);
+ } else {
+ prev = IP_FRAGMENTS_RB_PREV(next);
+ if (prev == NULL) {
+ prev = next;
+ next = IP_FRAGMENTS_RB_NEXT(prev);
+ }
+ }
+ while (prev != NULL) {
+ if (prev->skip) {
+ goto next;
+ }
+ if (frag_offset < prev->offset + prev->data_len && prev->offset < frag_end) {
+ overlap = true;
+ }
+
+ switch (tracker->policy) {
+ case DEFRAG_POLICY_BSD:
+ if (frag_offset < prev->offset + prev->data_len) {
+ if (frag_offset >= prev->offset) {
+ ltrim = prev->offset + prev->data_len - frag_offset;
+ }
+ if ((next != NULL) && (frag_end > next->offset)) {
+ next->ltrim = frag_end - next->offset;
+ }
+ if ((frag_offset < prev->offset) &&
+ (frag_end >= prev->offset + prev->data_len)) {
+ prev->skip = 1;
+ }
+ goto insert;
+ }
+ break;
+ case DEFRAG_POLICY_LINUX:
+ /* Check if new fragment overlaps the end of previous
+ * fragment, if it does, trim the new fragment.
+ *
+ * Old: AAAAAAAA AAAAAAAA AAAAAAAA
+ * New: BBBBBBBB BBBBBBBB BBBBBBBB
+ * Res: AAAAAAAA AAAAAAAA AAAAAAAA BBBBBBBB
+ */
+ if (prev->offset + prev->ltrim < frag_offset + ltrim &&
+ prev->offset + prev->data_len > frag_offset + ltrim) {
+ ltrim += prev->offset + prev->data_len - frag_offset;
+ }
+
+ /* Check if new fragment overlaps the beginning of
+ * previous fragment, if it does, tim the previous
+ * fragment.
+ *
+ * Old: AAAAAAAA AAAAAAAA
+ * New: BBBBBBBB BBBBBBBB BBBBBBBB
+ * Res: BBBBBBBB BBBBBBBB BBBBBBBB
+ */
+ if (frag_offset + ltrim < prev->offset + prev->ltrim &&
+ frag_end > prev->offset + prev->ltrim) {
+ prev->ltrim += frag_end - (prev->offset + prev->ltrim);
+ goto insert;
+ }
+
+ /* If the new fragment completely overlaps the
+ * previous fragment, mark the previous to be
+ * skipped. Re-assembly would succeed without doing
+ * this, but this will prevent the bytes from being
+ * copied just to be overwritten. */
+ if (frag_offset + ltrim <= prev->offset + prev->ltrim &&
+ frag_end >= prev->offset + prev->data_len) {
+ prev->skip = 1;
+ goto insert;
+ }
+
+ break;
+ case DEFRAG_POLICY_WINDOWS:
+ /* If new fragment fits inside a previous fragment, drop it. */
+ if (frag_offset + ltrim >= prev->offset + ltrim &&
+ frag_end <= prev->offset + prev->data_len) {
+ goto done;
+ }
+
+ /* If new fragment starts before and ends after
+ * previous fragment, drop the previous fragment. */
+ if (frag_offset + ltrim < prev->offset + ltrim &&
+ frag_end > prev->offset + prev->data_len) {
+ prev->skip = 1;
+ goto insert;
+ }
+
+ /* Check if new fragment overlaps the end of previous
+ * fragment, if it does, trim the new fragment.
+ *
+ * Old: AAAAAAAA AAAAAAAA AAAAAAAA
+ * New: BBBBBBBB BBBBBBBB BBBBBBBB
+ * Res: AAAAAAAA AAAAAAAA AAAAAAAA BBBBBBBB
+ */
+ if (frag_offset + ltrim > prev->offset + prev->ltrim &&
+ frag_offset + ltrim < prev->offset + prev->data_len) {
+ ltrim += prev->offset + prev->data_len - frag_offset;
+ goto insert;
+ }
+
+ /* If new fragment starts at same offset as an
+ * existing fragment, but ends after it, trim the new
+ * fragment. */
+ if (frag_offset + ltrim == prev->offset + ltrim &&
+ frag_end > prev->offset + prev->data_len) {
+ ltrim += prev->offset + prev->data_len - frag_offset;
+ goto insert;
+ }
+ break;
+ case DEFRAG_POLICY_SOLARIS:
+ if (frag_offset < prev->offset + prev->data_len) {
+ if (frag_offset >= prev->offset) {
+ ltrim = prev->offset + prev->data_len - frag_offset;
+ }
+ if ((frag_offset < prev->offset) &&
+ (frag_end >= prev->offset + prev->data_len)) {
+ prev->skip = 1;
+ }
+ goto insert;
+ }
+ break;
+ case DEFRAG_POLICY_FIRST:
+ if ((frag_offset >= prev->offset) &&
+ (frag_end <= prev->offset + prev->data_len)) {
+ goto done;
+ }
+ if (frag_offset < prev->offset) {
+ goto insert;
+ }
+ if (frag_offset < prev->offset + prev->data_len) {
+ ltrim = prev->offset + prev->data_len - frag_offset;
+ goto insert;
+ }
+ break;
+ case DEFRAG_POLICY_LAST:
+ if (frag_offset <= prev->offset) {
+ if (frag_end > prev->offset) {
+ prev->ltrim = frag_end - prev->offset;
+ }
+ goto insert;
+ }
+ break;
+ default:
+ break;
+ }
+
+ next:
+ prev = next;
+ if (next != NULL) {
+ next = IP_FRAGMENTS_RB_NEXT(next);
+ }
+ continue;
+
+ insert:
+ /* If existing fragment has been trimmed up completely
+ * (complete overlap), remove it now instead of holding
+ * onto it. */
+ if (prev->skip || prev->ltrim >= prev->data_len) {
+ RB_REMOVE(IP_FRAGMENTS, &tracker->fragment_tree, prev);
+ DefragFragReset(prev);
+ SCMutexLock(&defrag_context->frag_pool_lock);
+ PoolReturn(defrag_context->frag_pool, prev);
+ SCMutexUnlock(&defrag_context->frag_pool_lock);
+ }
+ break;
+ }
+ }
+
+ if (ltrim > data_len) {
+ /* Full packet has been trimmed due to the overlap policy. Overlap
+ * already set. */
+ goto done;
+ }
+
+ /* Allocate fragment and insert. */
+ SCMutexLock(&defrag_context->frag_pool_lock);
+ Frag *new = PoolGet(defrag_context->frag_pool);
+ SCMutexUnlock(&defrag_context->frag_pool_lock);
+ if (new == NULL) {
+ if (af == AF_INET) {
+ ENGINE_SET_EVENT(p, IPV4_FRAG_IGNORED);
+ } else {
+ ENGINE_SET_EVENT(p, IPV6_FRAG_IGNORED);
+ }
+ goto done;
+ }
+ new->pkt = SCMalloc(GET_PKT_LEN(p));
+ if (new->pkt == NULL) {
+ SCMutexLock(&defrag_context->frag_pool_lock);
+ PoolReturn(defrag_context->frag_pool, new);
+ SCMutexUnlock(&defrag_context->frag_pool_lock);
+ if (af == AF_INET) {
+ ENGINE_SET_EVENT(p, IPV4_FRAG_IGNORED);
+ } else {
+ ENGINE_SET_EVENT(p, IPV6_FRAG_IGNORED);
+ }
+ goto done;
+ }
+ memcpy(new->pkt, GET_PKT_DATA(p) + ltrim, GET_PKT_LEN(p) - ltrim);
+ new->len = (GET_PKT_LEN(p) - ltrim);
+ /* in case of unfragmentable exthdrs, update the 'next hdr' field
+ * in the raw buffer so the reassembled packet will point to the
+ * correct next header after stripping the frag header */
+ if (ip6_nh_set_offset > 0 && frag_offset == 0 && ltrim == 0) {
+ if (new->len > ip6_nh_set_offset) {
+ SCLogDebug("updating frag to have 'correct' nh value: %u -> %u",
+ new->pkt[ip6_nh_set_offset], ip6_nh_set_value);
+ new->pkt[ip6_nh_set_offset] = ip6_nh_set_value;
+ }
+ }
+
+ new->hlen = hlen;
+ new->offset = frag_offset + ltrim;
+ new->data_offset = data_offset;
+ new->data_len = data_len - ltrim;
+ new->ip_hdr_offset = ip_hdr_offset;
+ new->frag_hdr_offset = frag_hdr_offset;
+ new->more_frags = more_frags;
+#ifdef DEBUG
+ new->pcap_cnt = pcap_cnt;
+#endif
+
+ IP_FRAGMENTS_RB_INSERT(&tracker->fragment_tree, new);
+
+ if (!more_frags) {
+ tracker->seen_last = 1;
+ }
+
+ if (tracker->seen_last) {
+ if (tracker->af == AF_INET) {
+ r = Defrag4Reassemble(tv, tracker, p);
+ if (r != NULL && tv != NULL && dtv != NULL) {
+ StatsIncr(tv, dtv->counter_defrag_ipv4_reassembled);
+ if (DecodeIPV4(tv, dtv, r, (void *)r->ip4h,
+ IPV4_GET_IPLEN(r)) != TM_ECODE_OK) {
+
+ UNSET_TUNNEL_PKT(r);
+ r->root = NULL;
+ TmqhOutputPacketpool(tv, r);
+ r = NULL;
+ } else {
+ PacketDefragPktSetupParent(p);
+ }
+ }
+ }
+ else if (tracker->af == AF_INET6) {
+ r = Defrag6Reassemble(tv, tracker, p);
+ if (r != NULL && tv != NULL && dtv != NULL) {
+ StatsIncr(tv, dtv->counter_defrag_ipv6_reassembled);
+ if (DecodeIPV6(tv, dtv, r, (uint8_t *)r->ip6h,
+ IPV6_GET_PLEN(r) + IPV6_HEADER_LEN)
+ != TM_ECODE_OK) {
+
+ UNSET_TUNNEL_PKT(r);
+ r->root = NULL;
+ TmqhOutputPacketpool(tv, r);
+ r = NULL;
+ } else {
+ PacketDefragPktSetupParent(p);
+ }
+ }
+ }
+ }
+
+
+done:
+ if (overlap) {
+ if (af == AF_INET) {
+ ENGINE_SET_EVENT(p, IPV4_FRAG_OVERLAP);
+ }
+ else {
+ ENGINE_SET_EVENT(p, IPV6_FRAG_OVERLAP);
+ }
+ }
+ return r;
+}
+
+/**
+ * \brief Get the defrag policy based on the destination address of
+ * the packet.
+ *
+ * \param p The packet used to get the destination address.
+ *
+ * \retval The defrag policy to use.
+ */
+uint8_t
+DefragGetOsPolicy(Packet *p)
+{
+ int policy = -1;
+
+ if (PKT_IS_IPV4(p)) {
+ policy = SCHInfoGetIPv4HostOSFlavour((uint8_t *)GET_IPV4_DST_ADDR_PTR(p));
+ }
+ else if (PKT_IS_IPV6(p)) {
+ policy = SCHInfoGetIPv6HostOSFlavour((uint8_t *)GET_IPV6_DST_ADDR(p));
+ }
+
+ if (policy == -1) {
+ return default_policy;
+ }
+
+ /* Map the OS policies returned from the configured host info to
+ * defrag specific policies. */
+ switch (policy) {
+ /* BSD. */
+ case OS_POLICY_BSD:
+ case OS_POLICY_HPUX10:
+ case OS_POLICY_IRIX:
+ return DEFRAG_POLICY_BSD;
+
+ /* BSD-Right. */
+ case OS_POLICY_BSD_RIGHT:
+ return DEFRAG_POLICY_BSD_RIGHT;
+
+ /* Linux. */
+ case OS_POLICY_OLD_LINUX:
+ case OS_POLICY_LINUX:
+ return DEFRAG_POLICY_LINUX;
+
+ /* First. */
+ case OS_POLICY_OLD_SOLARIS:
+ case OS_POLICY_HPUX11:
+ case OS_POLICY_MACOS:
+ case OS_POLICY_FIRST:
+ return DEFRAG_POLICY_FIRST;
+
+ /* Solaris. */
+ case OS_POLICY_SOLARIS:
+ return DEFRAG_POLICY_SOLARIS;
+
+ /* Windows. */
+ case OS_POLICY_WINDOWS:
+ case OS_POLICY_VISTA:
+ case OS_POLICY_WINDOWS2K3:
+ return DEFRAG_POLICY_WINDOWS;
+
+ /* Last. */
+ case OS_POLICY_LAST:
+ return DEFRAG_POLICY_LAST;
+
+ default:
+ return default_policy;
+ }
+}
+
+/** \internal
+ *
+ * \retval NULL or a *LOCKED* tracker */
+static DefragTracker *
+DefragGetTracker(ThreadVars *tv, DecodeThreadVars *dtv, Packet *p)
+{
+ return DefragGetTrackerFromHash(p);
+}
+
+/**
+ * \brief Entry point for IPv4 and IPv6 fragments.
+ *
+ * \param tv ThreadVars for the calling decoder.
+ * \param p The packet fragment.
+ *
+ * \retval A new Packet resembling the re-assembled packet if the most
+ * recent fragment allowed the packet to be re-assembled, otherwise
+ * NULL is returned.
+ */
+Packet *
+Defrag(ThreadVars *tv, DecodeThreadVars *dtv, Packet *p)
+{
+ uint16_t frag_offset;
+ uint8_t more_frags;
+ DefragTracker *tracker;
+ int af;
+
+ if (PKT_IS_IPV4(p)) {
+ af = AF_INET;
+ more_frags = IPV4_GET_MF(p);
+ frag_offset = IPV4_GET_IPOFFSET(p);
+ }
+ else if (PKT_IS_IPV6(p)) {
+ af = AF_INET6;
+ frag_offset = IPV6_EXTHDR_GET_FH_OFFSET(p);
+ more_frags = IPV6_EXTHDR_GET_FH_FLAG(p);
+ }
+ else {
+ return NULL;
+ }
+
+ if (frag_offset == 0 && more_frags == 0) {
+ return NULL;
+ }
+
+ if (tv != NULL && dtv != NULL) {
+ if (af == AF_INET) {
+ StatsIncr(tv, dtv->counter_defrag_ipv4_fragments);
+ }
+ else if (af == AF_INET6) {
+ StatsIncr(tv, dtv->counter_defrag_ipv6_fragments);
+ }
+ }
+
+ /* return a locked tracker or NULL */
+ tracker = DefragGetTracker(tv, dtv, p);
+ if (tracker == NULL) {
+ if (tv != NULL && dtv != NULL) {
+ StatsIncr(tv, dtv->counter_defrag_max_hit);
+ }
+ return NULL;
+ }
+
+ Packet *rp = DefragInsertFrag(tv, dtv, tracker, p);
+ DefragTrackerRelease(tracker);
+
+ return rp;
+}
+
+void
+DefragInit(void)
+{
+ intmax_t tracker_pool_size;
+ if (!ConfGetInt("defrag.trackers", &tracker_pool_size)) {
+ tracker_pool_size = DEFAULT_DEFRAG_HASH_SIZE;
+ }
+
+ /* Load the defrag-per-host lookup. */
+ DefragPolicyLoadFromConfig();
+
+ /* Allocate the DefragContext. */
+ defrag_context = DefragContextNew();
+ if (defrag_context == NULL) {
+ FatalError("Failed to allocate memory for the Defrag module.");
+ }
+
+ DefragSetDefaultTimeout(defrag_context->timeout);
+ DefragInitConfig(false);
+}
+
+void DefragDestroy(void)
+{
+ DefragHashShutdown();
+ DefragContextDestroy(defrag_context);
+ defrag_context = NULL;
+ DefragTreeDestroy();
+}
+
+#ifdef UNITTESTS
+#include "util-unittest-helper.h"
+#include "packet.h"
+
+#define IP_MF 0x2000
+
+/**
+ * Allocate a test packet. Nothing to fancy, just a simple IP packet
+ * with some payload of no particular protocol.
+ */
+static Packet *BuildTestPacket(uint8_t proto, uint16_t id, uint16_t off, int mf,
+ const char content, int content_len)
+{
+ Packet *p = NULL;
+ int hlen = 20;
+ int ttl = 64;
+ uint8_t *pcontent;
+ IPV4Hdr ip4h;
+
+ p = SCCalloc(1, sizeof(*p) + default_packet_size);
+ if (unlikely(p == NULL))
+ return NULL;
+
+ PacketInit(p);
+
+ struct timeval tval;
+ gettimeofday(&tval, NULL);
+ p->ts = SCTIME_FROM_TIMEVAL(&tval);
+ //p->ip4h = (IPV4Hdr *)GET_PKT_DATA(p);
+ ip4h.ip_verhl = 4 << 4;
+ ip4h.ip_verhl |= hlen >> 2;
+ ip4h.ip_len = htons(hlen + content_len);
+ ip4h.ip_id = htons(id);
+ if (mf)
+ ip4h.ip_off = htons(IP_MF | off);
+ else
+ ip4h.ip_off = htons(off);
+ ip4h.ip_ttl = ttl;
+ ip4h.ip_proto = proto;
+
+ ip4h.s_ip_src.s_addr = 0x01010101; /* 1.1.1.1 */
+ ip4h.s_ip_dst.s_addr = 0x02020202; /* 2.2.2.2 */
+
+ /* copy content_len crap, we need full length */
+ PacketCopyData(p, (uint8_t *)&ip4h, sizeof(ip4h));
+ p->ip4h = (IPV4Hdr *)GET_PKT_DATA(p);
+ SET_IPV4_SRC_ADDR(p, &p->src);
+ SET_IPV4_DST_ADDR(p, &p->dst);
+
+ pcontent = SCCalloc(1, content_len);
+ if (unlikely(pcontent == NULL))
+ return NULL;
+ memset(pcontent, content, content_len);
+ PacketCopyDataOffset(p, hlen, pcontent, content_len);
+ SET_PKT_LEN(p, hlen + content_len);
+ SCFree(pcontent);
+
+ p->ip4h->ip_csum = IPV4Checksum((uint16_t *)GET_PKT_DATA(p), hlen, 0);
+
+ /* Self test. */
+ if (IPV4_GET_VER(p) != 4)
+ goto error;
+ if (IPV4_GET_HLEN(p) != hlen)
+ goto error;
+ if (IPV4_GET_IPLEN(p) != hlen + content_len)
+ goto error;
+ if (IPV4_GET_IPID(p) != id)
+ goto error;
+ if (IPV4_GET_IPOFFSET(p) != off)
+ goto error;
+ if (IPV4_GET_MF(p) != mf)
+ goto error;
+ if (IPV4_GET_IPTTL(p) != ttl)
+ goto error;
+ if (IPV4_GET_IPPROTO(p) != proto)
+ goto error;
+
+ return p;
+error:
+ if (p != NULL)
+ SCFree(p);
+ return NULL;
+}
+
+static Packet *IPV6BuildTestPacket(uint8_t proto, uint32_t id, uint16_t off,
+ int mf, const char content, int content_len)
+{
+ Packet *p = NULL;
+ uint8_t *pcontent;
+ IPV6Hdr ip6h;
+
+ p = SCCalloc(1, sizeof(*p) + default_packet_size);
+ if (unlikely(p == NULL))
+ return NULL;
+
+ PacketInit(p);
+
+ struct timeval tval;
+ gettimeofday(&tval, NULL);
+ p->ts = SCTIME_FROM_TIMEVAL(&tval);
+
+ ip6h.s_ip6_nxt = 44;
+ ip6h.s_ip6_hlim = 2;
+
+ /* Source and dest address - very bogus addresses. */
+ ip6h.s_ip6_src[0] = 0x01010101;
+ ip6h.s_ip6_src[1] = 0x01010101;
+ ip6h.s_ip6_src[2] = 0x01010101;
+ ip6h.s_ip6_src[3] = 0x01010101;
+ ip6h.s_ip6_dst[0] = 0x02020202;
+ ip6h.s_ip6_dst[1] = 0x02020202;
+ ip6h.s_ip6_dst[2] = 0x02020202;
+ ip6h.s_ip6_dst[3] = 0x02020202;
+
+ /* copy content_len crap, we need full length */
+ PacketCopyData(p, (uint8_t *)&ip6h, sizeof(IPV6Hdr));
+
+ p->ip6h = (IPV6Hdr *)GET_PKT_DATA(p);
+ IPV6_SET_RAW_VER(p->ip6h, 6);
+ /* Fragmentation header. */
+ IPV6FragHdr *fh = (IPV6FragHdr *)(GET_PKT_DATA(p) + sizeof(IPV6Hdr));
+ fh->ip6fh_nxt = proto;
+ fh->ip6fh_ident = htonl(id);
+ fh->ip6fh_offlg = htons((off << 3) | mf);
+
+ DecodeIPV6FragHeader(p, (uint8_t *)fh, 8, 8 + content_len, 0);
+
+ pcontent = SCCalloc(1, content_len);
+ if (unlikely(pcontent == NULL))
+ return NULL;
+ memset(pcontent, content, content_len);
+ PacketCopyDataOffset(p, sizeof(IPV6Hdr) + sizeof(IPV6FragHdr), pcontent, content_len);
+ SET_PKT_LEN(p, sizeof(IPV6Hdr) + sizeof(IPV6FragHdr) + content_len);
+ SCFree(pcontent);
+
+ p->ip6h->s_ip6_plen = htons(sizeof(IPV6FragHdr) + content_len);
+
+ SET_IPV6_SRC_ADDR(p, &p->src);
+ SET_IPV6_DST_ADDR(p, &p->dst);
+
+ /* Self test. */
+ if (IPV6_GET_VER(p) != 6)
+ goto error;
+ if (IPV6_GET_NH(p) != 44)
+ goto error;
+ if (IPV6_GET_PLEN(p) != sizeof(IPV6FragHdr) + content_len)
+ goto error;
+
+ return p;
+error:
+ if (p != NULL)
+ SCFree(p);
+ return NULL;
+}
+
+/**
+ * Test the simplest possible re-assembly scenario. All packet in
+ * order and no overlaps.
+ */
+static int DefragInOrderSimpleTest(void)
+{
+ Packet *p1 = NULL, *p2 = NULL, *p3 = NULL;
+ Packet *reassembled = NULL;
+ int id = 12;
+ int i;
+
+ DefragInit();
+
+ p1 = BuildTestPacket(IPPROTO_ICMP, id, 0, 1, 'A', 8);
+ FAIL_IF_NULL(p1);
+ p2 = BuildTestPacket(IPPROTO_ICMP, id, 1, 1, 'B', 8);
+ FAIL_IF_NULL(p2);
+ p3 = BuildTestPacket(IPPROTO_ICMP, id, 2, 0, 'C', 3);
+ FAIL_IF_NULL(p3);
+
+ FAIL_IF(Defrag(NULL, NULL, p1) != NULL);
+ FAIL_IF(Defrag(NULL, NULL, p2) != NULL);
+
+ reassembled = Defrag(NULL, NULL, p3);
+ FAIL_IF_NULL(reassembled);
+
+ FAIL_IF(IPV4_GET_HLEN(reassembled) != 20);
+ FAIL_IF(IPV4_GET_IPLEN(reassembled) != 39);
+
+ /* 20 bytes in we should find 8 bytes of A. */
+ for (i = 20; i < 20 + 8; i++) {
+ FAIL_IF(GET_PKT_DATA(reassembled)[i] != 'A');
+ }
+
+ /* 28 bytes in we should find 8 bytes of B. */
+ for (i = 28; i < 28 + 8; i++) {
+ FAIL_IF(GET_PKT_DATA(reassembled)[i] != 'B');
+ }
+
+ /* And 36 bytes in we should find 3 bytes of C. */
+ for (i = 36; i < 36 + 3; i++) {
+ FAIL_IF(GET_PKT_DATA(reassembled)[i] != 'C');
+ }
+
+ SCFree(p1);
+ SCFree(p2);
+ SCFree(p3);
+ SCFree(reassembled);
+
+ DefragDestroy();
+ PASS;
+}
+
+/**
+ * Simple fragmented packet in reverse order.
+ */
+static int DefragReverseSimpleTest(void)
+{
+ Packet *p1 = NULL, *p2 = NULL, *p3 = NULL;
+ Packet *reassembled = NULL;
+ int id = 12;
+ int i;
+
+ DefragInit();
+
+ p1 = BuildTestPacket(IPPROTO_ICMP, id, 0, 1, 'A', 8);
+ FAIL_IF_NULL(p1);
+ p2 = BuildTestPacket(IPPROTO_ICMP, id, 1, 1, 'B', 8);
+ FAIL_IF_NULL(p2);
+ p3 = BuildTestPacket(IPPROTO_ICMP, id, 2, 0, 'C', 3);
+ FAIL_IF_NULL(p3);
+
+ FAIL_IF(Defrag(NULL, NULL, p3) != NULL);
+ FAIL_IF(Defrag(NULL, NULL, p2) != NULL);
+
+ reassembled = Defrag(NULL, NULL, p1);
+ FAIL_IF_NULL(reassembled);
+
+ FAIL_IF(IPV4_GET_HLEN(reassembled) != 20);
+ FAIL_IF(IPV4_GET_IPLEN(reassembled) != 39);
+
+ /* 20 bytes in we should find 8 bytes of A. */
+ for (i = 20; i < 20 + 8; i++) {
+ FAIL_IF(GET_PKT_DATA(reassembled)[i] != 'A');
+ }
+
+ /* 28 bytes in we should find 8 bytes of B. */
+ for (i = 28; i < 28 + 8; i++) {
+ FAIL_IF(GET_PKT_DATA(reassembled)[i] != 'B');
+ }
+
+ /* And 36 bytes in we should find 3 bytes of C. */
+ for (i = 36; i < 36 + 3; i++) {
+ FAIL_IF(GET_PKT_DATA(reassembled)[i] != 'C');
+ }
+
+ SCFree(p1);
+ SCFree(p2);
+ SCFree(p3);
+ SCFree(reassembled);
+
+ DefragDestroy();
+ PASS;
+}
+
+/**
+ * Test the simplest possible re-assembly scenario. All packet in
+ * order and no overlaps.
+ */
+static int IPV6DefragInOrderSimpleTest(void)
+{
+ Packet *p1 = NULL, *p2 = NULL, *p3 = NULL;
+ Packet *reassembled = NULL;
+ int id = 12;
+ int i;
+
+ DefragInit();
+
+ p1 = IPV6BuildTestPacket(IPPROTO_ICMPV6, id, 0, 1, 'A', 8);
+ FAIL_IF_NULL(p1);
+ p2 = IPV6BuildTestPacket(IPPROTO_ICMPV6, id, 1, 1, 'B', 8);
+ FAIL_IF_NULL(p2);
+ p3 = IPV6BuildTestPacket(IPPROTO_ICMPV6, id, 2, 0, 'C', 3);
+ FAIL_IF_NULL(p3);
+
+ FAIL_IF(Defrag(NULL, NULL, p1) != NULL);
+ FAIL_IF(Defrag(NULL, NULL, p2) != NULL);
+ reassembled = Defrag(NULL, NULL, p3);
+ FAIL_IF_NULL(reassembled);
+
+ FAIL_IF(IPV6_GET_PLEN(reassembled) != 19);
+
+ /* 40 bytes in we should find 8 bytes of A. */
+ for (i = 40; i < 40 + 8; i++) {
+ FAIL_IF(GET_PKT_DATA(reassembled)[i] != 'A');
+ }
+
+ /* 28 bytes in we should find 8 bytes of B. */
+ for (i = 48; i < 48 + 8; i++) {
+ FAIL_IF(GET_PKT_DATA(reassembled)[i] != 'B');
+ }
+
+ /* And 36 bytes in we should find 3 bytes of C. */
+ for (i = 56; i < 56 + 3; i++) {
+ FAIL_IF(GET_PKT_DATA(reassembled)[i] != 'C');
+ }
+
+ SCFree(p1);
+ SCFree(p2);
+ SCFree(p3);
+ SCFree(reassembled);
+
+ DefragDestroy();
+ PASS;
+}
+
+static int IPV6DefragReverseSimpleTest(void)
+{
+ DefragContext *dc = NULL;
+ Packet *p1 = NULL, *p2 = NULL, *p3 = NULL;
+ Packet *reassembled = NULL;
+ int id = 12;
+ int i;
+
+ DefragInit();
+
+ dc = DefragContextNew();
+ FAIL_IF_NULL(dc);
+
+ p1 = IPV6BuildTestPacket(IPPROTO_ICMPV6, id, 0, 1, 'A', 8);
+ FAIL_IF_NULL(p1);
+ p2 = IPV6BuildTestPacket(IPPROTO_ICMPV6, id, 1, 1, 'B', 8);
+ FAIL_IF_NULL(p2);
+ p3 = IPV6BuildTestPacket(IPPROTO_ICMPV6, id, 2, 0, 'C', 3);
+ FAIL_IF_NULL(p3);
+
+ FAIL_IF(Defrag(NULL, NULL, p3) != NULL);
+ FAIL_IF(Defrag(NULL, NULL, p2) != NULL);
+ reassembled = Defrag(NULL, NULL, p1);
+ FAIL_IF_NULL(reassembled);
+
+ /* 40 bytes in we should find 8 bytes of A. */
+ for (i = 40; i < 40 + 8; i++) {
+ FAIL_IF(GET_PKT_DATA(reassembled)[i] != 'A');
+ }
+
+ /* 28 bytes in we should find 8 bytes of B. */
+ for (i = 48; i < 48 + 8; i++) {
+ FAIL_IF(GET_PKT_DATA(reassembled)[i] != 'B');
+ }
+
+ /* And 36 bytes in we should find 3 bytes of C. */
+ for (i = 56; i < 56 + 3; i++) {
+ FAIL_IF(GET_PKT_DATA(reassembled)[i] != 'C');
+ }
+
+ DefragContextDestroy(dc);
+ SCFree(p1);
+ SCFree(p2);
+ SCFree(p3);
+ SCFree(reassembled);
+
+ DefragDestroy();
+ PASS;
+}
+
+static int DefragDoSturgesNovakTest(int policy, u_char *expected,
+ size_t expected_len)
+{
+ int i;
+
+ DefragInit();
+
+ /*
+ * Build the packets.
+ */
+
+ int id = 1;
+ Packet *packets[17];
+ memset(packets, 0x00, sizeof(packets));
+
+ /*
+ * Original fragments.
+ */
+
+ /* A*24 at 0. */
+ packets[0] = BuildTestPacket(IPPROTO_ICMP, id, 0, 1, 'A', 24);
+
+ /* B*15 at 32. */
+ packets[1] = BuildTestPacket(IPPROTO_ICMP, id, 32 >> 3, 1, 'B', 16);
+
+ /* C*24 at 48. */
+ packets[2] = BuildTestPacket(IPPROTO_ICMP, id, 48 >> 3, 1, 'C', 24);
+
+ /* D*8 at 80. */
+ packets[3] = BuildTestPacket(IPPROTO_ICMP, id, 80 >> 3, 1, 'D', 8);
+
+ /* E*16 at 104. */
+ packets[4] = BuildTestPacket(IPPROTO_ICMP, id, 104 >> 3, 1, 'E', 16);
+
+ /* F*24 at 120. */
+ packets[5] = BuildTestPacket(IPPROTO_ICMP, id, 120 >> 3, 1, 'F', 24);
+
+ /* G*16 at 144. */
+ packets[6] = BuildTestPacket(IPPROTO_ICMP, id, 144 >> 3, 1, 'G', 16);
+
+ /* H*16 at 160. */
+ packets[7] = BuildTestPacket(IPPROTO_ICMP, id, 160 >> 3, 1, 'H', 16);
+
+ /* I*8 at 176. */
+ packets[8] = BuildTestPacket(IPPROTO_ICMP, id, 176 >> 3, 1, 'I', 8);
+
+ /*
+ * Overlapping subsequent fragments.
+ */
+
+ /* J*32 at 8. */
+ packets[9] = BuildTestPacket(IPPROTO_ICMP, id, 8 >> 3, 1, 'J', 32);
+
+ /* K*24 at 48. */
+ packets[10] = BuildTestPacket(IPPROTO_ICMP, id, 48 >> 3, 1, 'K', 24);
+
+ /* L*24 at 72. */
+ packets[11] = BuildTestPacket(IPPROTO_ICMP, id, 72 >> 3, 1, 'L', 24);
+
+ /* M*24 at 96. */
+ packets[12] = BuildTestPacket(IPPROTO_ICMP, id, 96 >> 3, 1, 'M', 24);
+
+ /* N*8 at 128. */
+ packets[13] = BuildTestPacket(IPPROTO_ICMP, id, 128 >> 3, 1, 'N', 8);
+
+ /* O*8 at 152. */
+ packets[14] = BuildTestPacket(IPPROTO_ICMP, id, 152 >> 3, 1, 'O', 8);
+
+ /* P*8 at 160. */
+ packets[15] = BuildTestPacket(IPPROTO_ICMP, id, 160 >> 3, 1, 'P', 8);
+
+ /* Q*16 at 176. */
+ packets[16] = BuildTestPacket(IPPROTO_ICMP, id, 176 >> 3, 0, 'Q', 16);
+
+ default_policy = policy;
+
+ /* Send all but the last. */
+ for (i = 0; i < 9; i++) {
+ Packet *tp = Defrag(NULL, NULL, packets[i]);
+ FAIL_IF_NOT_NULL(tp);
+ FAIL_IF(ENGINE_ISSET_EVENT(packets[i], IPV4_FRAG_OVERLAP));
+ }
+ int overlap = 0;
+ for (; i < 16; i++) {
+ Packet *tp = Defrag(NULL, NULL, packets[i]);
+ FAIL_IF_NOT_NULL(tp);
+ if (ENGINE_ISSET_EVENT(packets[i], IPV4_FRAG_OVERLAP)) {
+ overlap++;
+ }
+ }
+ FAIL_IF_NOT(overlap);
+
+ /* And now the last one. */
+ Packet *reassembled = Defrag(NULL, NULL, packets[16]);
+ FAIL_IF_NULL(reassembled);
+
+ FAIL_IF(IPV4_GET_HLEN(reassembled) != 20);
+ FAIL_IF(IPV4_GET_IPLEN(reassembled) != 20 + 192);
+
+ FAIL_IF(memcmp(GET_PKT_DATA(reassembled) + 20, expected, expected_len) != 0);
+ SCFree(reassembled);
+
+ /* Make sure all frags were returned back to the pool. */
+ FAIL_IF(defrag_context->frag_pool->outstanding != 0);
+
+ for (i = 0; i < 17; i++) {
+ SCFree(packets[i]);
+ }
+ DefragDestroy();
+ PASS;
+}
+
+static int IPV6DefragDoSturgesNovakTest(int policy, u_char *expected,
+ size_t expected_len)
+{
+ int i;
+
+ DefragInit();
+
+ /*
+ * Build the packets.
+ */
+
+ int id = 1;
+ Packet *packets[17];
+ memset(packets, 0x00, sizeof(packets));
+
+ /*
+ * Original fragments.
+ */
+
+ /* A*24 at 0. */
+ packets[0] = IPV6BuildTestPacket(IPPROTO_ICMPV6, id, 0, 1, 'A', 24);
+
+ /* B*15 at 32. */
+ packets[1] = IPV6BuildTestPacket(IPPROTO_ICMPV6, id, 32 >> 3, 1, 'B', 16);
+
+ /* C*24 at 48. */
+ packets[2] = IPV6BuildTestPacket(IPPROTO_ICMPV6, id, 48 >> 3, 1, 'C', 24);
+
+ /* D*8 at 80. */
+ packets[3] = IPV6BuildTestPacket(IPPROTO_ICMPV6, id, 80 >> 3, 1, 'D', 8);
+
+ /* E*16 at 104. */
+ packets[4] = IPV6BuildTestPacket(IPPROTO_ICMPV6, id, 104 >> 3, 1, 'E', 16);
+
+ /* F*24 at 120. */
+ packets[5] = IPV6BuildTestPacket(IPPROTO_ICMPV6, id, 120 >> 3, 1, 'F', 24);
+
+ /* G*16 at 144. */
+ packets[6] = IPV6BuildTestPacket(IPPROTO_ICMPV6, id, 144 >> 3, 1, 'G', 16);
+
+ /* H*16 at 160. */
+ packets[7] = IPV6BuildTestPacket(IPPROTO_ICMPV6, id, 160 >> 3, 1, 'H', 16);
+
+ /* I*8 at 176. */
+ packets[8] = IPV6BuildTestPacket(IPPROTO_ICMPV6, id, 176 >> 3, 1, 'I', 8);
+
+ /*
+ * Overlapping subsequent fragments.
+ */
+
+ /* J*32 at 8. */
+ packets[9] = IPV6BuildTestPacket(IPPROTO_ICMPV6, id, 8 >> 3, 1, 'J', 32);
+
+ /* K*24 at 48. */
+ packets[10] = IPV6BuildTestPacket(IPPROTO_ICMPV6, id, 48 >> 3, 1, 'K', 24);
+
+ /* L*24 at 72. */
+ packets[11] = IPV6BuildTestPacket(IPPROTO_ICMPV6, id, 72 >> 3, 1, 'L', 24);
+
+ /* M*24 at 96. */
+ packets[12] = IPV6BuildTestPacket(IPPROTO_ICMPV6, id, 96 >> 3, 1, 'M', 24);
+
+ /* N*8 at 128. */
+ packets[13] = IPV6BuildTestPacket(IPPROTO_ICMPV6, id, 128 >> 3, 1, 'N', 8);
+
+ /* O*8 at 152. */
+ packets[14] = IPV6BuildTestPacket(IPPROTO_ICMPV6, id, 152 >> 3, 1, 'O', 8);
+
+ /* P*8 at 160. */
+ packets[15] = IPV6BuildTestPacket(IPPROTO_ICMPV6, id, 160 >> 3, 1, 'P', 8);
+
+ /* Q*16 at 176. */
+ packets[16] = IPV6BuildTestPacket(IPPROTO_ICMPV6, id, 176 >> 3, 0, 'Q', 16);
+
+ default_policy = policy;
+
+ /* Send all but the last. */
+ for (i = 0; i < 9; i++) {
+ Packet *tp = Defrag(NULL, NULL, packets[i]);
+ FAIL_IF_NOT_NULL(tp);
+ FAIL_IF(ENGINE_ISSET_EVENT(packets[i], IPV6_FRAG_OVERLAP));
+ }
+ int overlap = 0;
+ for (; i < 16; i++) {
+ Packet *tp = Defrag(NULL, NULL, packets[i]);
+ FAIL_IF_NOT_NULL(tp);
+ if (ENGINE_ISSET_EVENT(packets[i], IPV6_FRAG_OVERLAP)) {
+ overlap++;
+ }
+ }
+ FAIL_IF_NOT(overlap);
+
+ /* And now the last one. */
+ Packet *reassembled = Defrag(NULL, NULL, packets[16]);
+ FAIL_IF_NULL(reassembled);
+ FAIL_IF(memcmp(GET_PKT_DATA(reassembled) + 40, expected, expected_len) != 0);
+
+ FAIL_IF(IPV6_GET_PLEN(reassembled) != 192);
+
+ SCFree(reassembled);
+
+ /* Make sure all frags were returned to the pool. */
+ FAIL_IF(defrag_context->frag_pool->outstanding != 0);
+
+ for (i = 0; i < 17; i++) {
+ SCFree(packets[i]);
+ }
+ DefragDestroy();
+ PASS;
+}
+
+static int
+DefragSturgesNovakBsdTest(void)
+{
+ /* Expected data. */
+ u_char expected[] = {
+ "AAAAAAAA"
+ "AAAAAAAA"
+ "AAAAAAAA"
+ "JJJJJJJJ"
+ "JJJJJJJJ"
+ "BBBBBBBB"
+ "CCCCCCCC"
+ "CCCCCCCC"
+ "CCCCCCCC"
+ "LLLLLLLL"
+ "LLLLLLLL"
+ "LLLLLLLL"
+ "MMMMMMMM"
+ "MMMMMMMM"
+ "MMMMMMMM"
+ "FFFFFFFF"
+ "FFFFFFFF"
+ "FFFFFFFF"
+ "GGGGGGGG"
+ "GGGGGGGG"
+ "HHHHHHHH"
+ "HHHHHHHH"
+ "IIIIIIII"
+ "QQQQQQQQ"
+ };
+
+ FAIL_IF_NOT(DefragDoSturgesNovakTest(DEFRAG_POLICY_BSD, expected,
+ sizeof(expected)));
+ PASS;
+}
+
+static int IPV6DefragSturgesNovakBsdTest(void)
+{
+ /* Expected data. */
+ u_char expected[] = {
+ "AAAAAAAA"
+ "AAAAAAAA"
+ "AAAAAAAA"
+ "JJJJJJJJ"
+ "JJJJJJJJ"
+ "BBBBBBBB"
+ "CCCCCCCC"
+ "CCCCCCCC"
+ "CCCCCCCC"
+ "LLLLLLLL"
+ "LLLLLLLL"
+ "LLLLLLLL"
+ "MMMMMMMM"
+ "MMMMMMMM"
+ "MMMMMMMM"
+ "FFFFFFFF"
+ "FFFFFFFF"
+ "FFFFFFFF"
+ "GGGGGGGG"
+ "GGGGGGGG"
+ "HHHHHHHH"
+ "HHHHHHHH"
+ "IIIIIIII"
+ "QQQQQQQQ"
+ };
+
+ FAIL_IF_NOT(IPV6DefragDoSturgesNovakTest(DEFRAG_POLICY_BSD, expected,
+ sizeof(expected)));
+ PASS;
+}
+
+static int DefragSturgesNovakLinuxIpv4Test(void)
+{
+ /* Expected data. */
+ u_char expected[] = {
+ "AAAAAAAA"
+ "AAAAAAAA"
+ "AAAAAAAA"
+ "JJJJJJJJ"
+ "JJJJJJJJ"
+ "BBBBBBBB"
+ "KKKKKKKK"
+ "KKKKKKKK"
+ "KKKKKKKK"
+ "LLLLLLLL"
+ "LLLLLLLL"
+ "LLLLLLLL"
+ "MMMMMMMM"
+ "MMMMMMMM"
+ "MMMMMMMM"
+ "FFFFFFFF"
+ "FFFFFFFF"
+ "FFFFFFFF"
+ "GGGGGGGG"
+ "GGGGGGGG"
+ "PPPPPPPP"
+ "HHHHHHHH"
+ "QQQQQQQQ"
+ "QQQQQQQQ"
+ };
+
+ FAIL_IF_NOT(DefragDoSturgesNovakTest(DEFRAG_POLICY_LINUX, expected,
+ sizeof(expected)));
+ PASS;
+}
+
+static int IPV6DefragSturgesNovakLinuxTest(void)
+{
+ /* Expected data. */
+ u_char expected[] = {
+ "AAAAAAAA"
+ "AAAAAAAA"
+ "AAAAAAAA"
+ "JJJJJJJJ"
+ "JJJJJJJJ"
+ "BBBBBBBB"
+ "KKKKKKKK"
+ "KKKKKKKK"
+ "KKKKKKKK"
+ "LLLLLLLL"
+ "LLLLLLLL"
+ "LLLLLLLL"
+ "MMMMMMMM"
+ "MMMMMMMM"
+ "MMMMMMMM"
+ "FFFFFFFF"
+ "FFFFFFFF"
+ "FFFFFFFF"
+ "GGGGGGGG"
+ "GGGGGGGG"
+ "PPPPPPPP"
+ "HHHHHHHH"
+ "QQQQQQQQ"
+ "QQQQQQQQ"
+ };
+
+ FAIL_IF_NOT(IPV6DefragDoSturgesNovakTest(DEFRAG_POLICY_LINUX, expected,
+ sizeof(expected)));
+ PASS;
+}
+
+static int DefragSturgesNovakWindowsIpv4Test(void)
+{
+ /* Expected data. */
+ u_char expected[] = {
+ "AAAAAAAA"
+ "AAAAAAAA"
+ "AAAAAAAA"
+ "JJJJJJJJ"
+ "BBBBBBBB"
+ "BBBBBBBB"
+ "CCCCCCCC"
+ "CCCCCCCC"
+ "CCCCCCCC"
+ "LLLLLLLL"
+ "LLLLLLLL"
+ "LLLLLLLL"
+ "MMMMMMMM"
+ "EEEEEEEE"
+ "EEEEEEEE"
+ "FFFFFFFF"
+ "FFFFFFFF"
+ "FFFFFFFF"
+ "GGGGGGGG"
+ "GGGGGGGG"
+ "HHHHHHHH"
+ "HHHHHHHH"
+ "IIIIIIII"
+ "QQQQQQQQ"
+ };
+
+ FAIL_IF_NOT(DefragDoSturgesNovakTest(DEFRAG_POLICY_WINDOWS, expected,
+ sizeof(expected)));
+ PASS;
+}
+
+static int IPV6DefragSturgesNovakWindowsTest(void)
+{
+ /* Expected data. */
+ u_char expected[] = {
+ "AAAAAAAA"
+ "AAAAAAAA"
+ "AAAAAAAA"
+ "JJJJJJJJ"
+ "BBBBBBBB"
+ "BBBBBBBB"
+ "CCCCCCCC"
+ "CCCCCCCC"
+ "CCCCCCCC"
+ "LLLLLLLL"
+ "LLLLLLLL"
+ "LLLLLLLL"
+ "MMMMMMMM"
+ "EEEEEEEE"
+ "EEEEEEEE"
+ "FFFFFFFF"
+ "FFFFFFFF"
+ "FFFFFFFF"
+ "GGGGGGGG"
+ "GGGGGGGG"
+ "HHHHHHHH"
+ "HHHHHHHH"
+ "IIIIIIII"
+ "QQQQQQQQ"
+ };
+
+ FAIL_IF_NOT(IPV6DefragDoSturgesNovakTest(DEFRAG_POLICY_WINDOWS, expected,
+ sizeof(expected)));
+ PASS;
+}
+
+static int DefragSturgesNovakSolarisTest(void)
+{
+ /* Expected data. */
+ u_char expected[] = {
+ "AAAAAAAA"
+ "AAAAAAAA"
+ "AAAAAAAA"
+ "JJJJJJJJ"
+ "BBBBBBBB"
+ "BBBBBBBB"
+ "CCCCCCCC"
+ "CCCCCCCC"
+ "CCCCCCCC"
+ "LLLLLLLL"
+ "LLLLLLLL"
+ "LLLLLLLL"
+ "MMMMMMMM"
+ "MMMMMMMM"
+ "MMMMMMMM"
+ "FFFFFFFF"
+ "FFFFFFFF"
+ "FFFFFFFF"
+ "GGGGGGGG"
+ "GGGGGGGG"
+ "HHHHHHHH"
+ "HHHHHHHH"
+ "IIIIIIII"
+ "QQQQQQQQ"
+ };
+
+ FAIL_IF_NOT(DefragDoSturgesNovakTest(DEFRAG_POLICY_SOLARIS, expected,
+ sizeof(expected)));
+ PASS;
+}
+
+static int IPV6DefragSturgesNovakSolarisTest(void)
+{
+ /* Expected data. */
+ u_char expected[] = {
+ "AAAAAAAA"
+ "AAAAAAAA"
+ "AAAAAAAA"
+ "JJJJJJJJ"
+ "BBBBBBBB"
+ "BBBBBBBB"
+ "CCCCCCCC"
+ "CCCCCCCC"
+ "CCCCCCCC"
+ "LLLLLLLL"
+ "LLLLLLLL"
+ "LLLLLLLL"
+ "MMMMMMMM"
+ "MMMMMMMM"
+ "MMMMMMMM"
+ "FFFFFFFF"
+ "FFFFFFFF"
+ "FFFFFFFF"
+ "GGGGGGGG"
+ "GGGGGGGG"
+ "HHHHHHHH"
+ "HHHHHHHH"
+ "IIIIIIII"
+ "QQQQQQQQ"
+ };
+
+ FAIL_IF_NOT(IPV6DefragDoSturgesNovakTest(DEFRAG_POLICY_SOLARIS, expected,
+ sizeof(expected)));
+ PASS;
+}
+
+static int DefragSturgesNovakFirstTest(void)
+{
+ /* Expected data. */
+ u_char expected[] = {
+ "AAAAAAAA"
+ "AAAAAAAA"
+ "AAAAAAAA"
+ "JJJJJJJJ"
+ "BBBBBBBB"
+ "BBBBBBBB"
+ "CCCCCCCC"
+ "CCCCCCCC"
+ "CCCCCCCC"
+ "LLLLLLLL"
+ "DDDDDDDD"
+ "LLLLLLLL"
+ "MMMMMMMM"
+ "EEEEEEEE"
+ "EEEEEEEE"
+ "FFFFFFFF"
+ "FFFFFFFF"
+ "FFFFFFFF"
+ "GGGGGGGG"
+ "GGGGGGGG"
+ "HHHHHHHH"
+ "HHHHHHHH"
+ "IIIIIIII"
+ "QQQQQQQQ"
+ };
+
+ FAIL_IF_NOT(DefragDoSturgesNovakTest(DEFRAG_POLICY_FIRST, expected,
+ sizeof(expected)));
+ PASS;
+}
+
+static int IPV6DefragSturgesNovakFirstTest(void)
+{
+ /* Expected data. */
+ u_char expected[] = {
+ "AAAAAAAA"
+ "AAAAAAAA"
+ "AAAAAAAA"
+ "JJJJJJJJ"
+ "BBBBBBBB"
+ "BBBBBBBB"
+ "CCCCCCCC"
+ "CCCCCCCC"
+ "CCCCCCCC"
+ "LLLLLLLL"
+ "DDDDDDDD"
+ "LLLLLLLL"
+ "MMMMMMMM"
+ "EEEEEEEE"
+ "EEEEEEEE"
+ "FFFFFFFF"
+ "FFFFFFFF"
+ "FFFFFFFF"
+ "GGGGGGGG"
+ "GGGGGGGG"
+ "HHHHHHHH"
+ "HHHHHHHH"
+ "IIIIIIII"
+ "QQQQQQQQ"
+ };
+
+ return IPV6DefragDoSturgesNovakTest(DEFRAG_POLICY_FIRST, expected,
+ sizeof(expected));
+}
+
+static int
+DefragSturgesNovakLastTest(void)
+{
+ /* Expected data. */
+ u_char expected[] = {
+ "AAAAAAAA"
+ "JJJJJJJJ"
+ "JJJJJJJJ"
+ "JJJJJJJJ"
+ "JJJJJJJJ"
+ "BBBBBBBB"
+ "KKKKKKKK"
+ "KKKKKKKK"
+ "KKKKKKKK"
+ "LLLLLLLL"
+ "LLLLLLLL"
+ "LLLLLLLL"
+ "MMMMMMMM"
+ "MMMMMMMM"
+ "MMMMMMMM"
+ "FFFFFFFF"
+ "NNNNNNNN"
+ "FFFFFFFF"
+ "GGGGGGGG"
+ "OOOOOOOO"
+ "PPPPPPPP"
+ "HHHHHHHH"
+ "QQQQQQQQ"
+ "QQQQQQQQ"
+ };
+
+ FAIL_IF_NOT(DefragDoSturgesNovakTest(DEFRAG_POLICY_LAST, expected,
+ sizeof(expected)));
+ PASS;
+}
+
+static int IPV6DefragSturgesNovakLastTest(void)
+{
+ /* Expected data. */
+ u_char expected[] = {
+ "AAAAAAAA"
+ "JJJJJJJJ"
+ "JJJJJJJJ"
+ "JJJJJJJJ"
+ "JJJJJJJJ"
+ "BBBBBBBB"
+ "KKKKKKKK"
+ "KKKKKKKK"
+ "KKKKKKKK"
+ "LLLLLLLL"
+ "LLLLLLLL"
+ "LLLLLLLL"
+ "MMMMMMMM"
+ "MMMMMMMM"
+ "MMMMMMMM"
+ "FFFFFFFF"
+ "NNNNNNNN"
+ "FFFFFFFF"
+ "GGGGGGGG"
+ "OOOOOOOO"
+ "PPPPPPPP"
+ "HHHHHHHH"
+ "QQQQQQQQ"
+ "QQQQQQQQ"
+ };
+
+ FAIL_IF_NOT(IPV6DefragDoSturgesNovakTest(DEFRAG_POLICY_LAST, expected,
+ sizeof(expected)));
+ PASS;
+}
+
+static int DefragTimeoutTest(void)
+{
+ int i;
+
+ /* Setup a small number of trackers. */
+ FAIL_IF_NOT(ConfSet("defrag.trackers", "16"));
+
+ DefragInit();
+
+ /* Load in 16 packets. */
+ for (i = 0; i < 16; i++) {
+ Packet *p = BuildTestPacket(IPPROTO_ICMP,i, 0, 1, 'A' + i, 16);
+ FAIL_IF_NULL(p);
+
+ Packet *tp = Defrag(NULL, NULL, p);
+ SCFree(p);
+ FAIL_IF_NOT_NULL(tp);
+ }
+
+ /* Build a new packet but push the timestamp out by our timeout.
+ * This should force our previous fragments to be timed out. */
+ Packet *p = BuildTestPacket(IPPROTO_ICMP, 99, 0, 1, 'A' + i, 16);
+ FAIL_IF_NULL(p);
+
+ p->ts = SCTIME_ADD_SECS(p->ts, defrag_context->timeout + 1);
+ Packet *tp = Defrag(NULL, NULL, p);
+ FAIL_IF_NOT_NULL(tp);
+
+ DefragTracker *tracker = DefragLookupTrackerFromHash(p);
+ FAIL_IF_NULL(tracker);
+
+ FAIL_IF(tracker->id != 99);
+
+ SCMutexUnlock(&tracker->lock);
+ SCFree(p);
+
+ DefragDestroy();
+ PASS;
+}
+
+/**
+ * QA found that if you send a packet where more frags is 0, offset is
+ * > 0 and there is no data in the packet that the re-assembler will
+ * fail. The fix was simple, but this unit test is just to make sure
+ * its not introduced.
+ */
+static int DefragIPv4NoDataTest(void)
+{
+ DefragContext *dc = NULL;
+ Packet *p = NULL;
+ int id = 12;
+
+ DefragInit();
+
+ dc = DefragContextNew();
+ FAIL_IF_NULL(dc);
+
+ /* This packet has an offset > 0, more frags set to 0 and no data. */
+ p = BuildTestPacket(IPPROTO_ICMP, id, 1, 0, 'A', 0);
+ FAIL_IF_NULL(p);
+
+ /* We do not expect a packet returned. */
+ FAIL_IF(Defrag(NULL, NULL, p) != NULL);
+
+ /* The fragment should have been ignored so no fragments should
+ * have been allocated from the pool. */
+ FAIL_IF(dc->frag_pool->outstanding != 0);
+
+ DefragContextDestroy(dc);
+ SCFree(p);
+
+ DefragDestroy();
+ PASS;
+}
+
+static int DefragIPv4TooLargeTest(void)
+{
+ DefragContext *dc = NULL;
+ Packet *p = NULL;
+
+ DefragInit();
+
+ dc = DefragContextNew();
+ FAIL_IF_NULL(dc);
+
+ /* Create a fragment that would extend past the max allowable size
+ * for an IPv4 packet. */
+ p = BuildTestPacket(IPPROTO_ICMP, 1, 8183, 0, 'A', 71);
+ FAIL_IF_NULL(p);
+
+ /* We do not expect a packet returned. */
+ FAIL_IF(Defrag(NULL, NULL, p) != NULL);
+
+ /* We do expect an event. */
+ FAIL_IF_NOT(ENGINE_ISSET_EVENT(p, IPV4_FRAG_PKT_TOO_LARGE));
+
+ /* The fragment should have been ignored so no fragments should have
+ * been allocated from the pool. */
+ FAIL_IF(dc->frag_pool->outstanding != 0);
+
+ DefragContextDestroy(dc);
+ SCFree(p);
+
+ DefragDestroy();
+ PASS;
+}
+
+/**
+ * Test that fragments in different VLANs that would otherwise be
+ * re-assembled, are not re-assembled. Just use simple in-order
+ * fragments.
+ */
+static int DefragVlanTest(void)
+{
+ Packet *p1 = NULL, *p2 = NULL, *r = NULL;
+
+ DefragInit();
+
+ p1 = BuildTestPacket(IPPROTO_ICMP, 1, 0, 1, 'A', 8);
+ FAIL_IF_NULL(p1);
+ p2 = BuildTestPacket(IPPROTO_ICMP, 1, 1, 0, 'B', 8);
+ FAIL_IF_NULL(p2);
+
+ /* With no VLAN IDs set, packets should re-assemble. */
+ FAIL_IF((r = Defrag(NULL, NULL, p1)) != NULL);
+ FAIL_IF((r = Defrag(NULL, NULL, p2)) == NULL);
+ SCFree(r);
+
+ /* With mismatched VLANs, packets should not re-assemble. */
+ p1->vlan_id[0] = 1;
+ p2->vlan_id[0] = 2;
+ FAIL_IF((r = Defrag(NULL, NULL, p1)) != NULL);
+ FAIL_IF((r = Defrag(NULL, NULL, p2)) != NULL);
+
+ SCFree(p1);
+ SCFree(p2);
+ DefragDestroy();
+
+ PASS;
+}
+
+/**
+ * Like DefragVlanTest, but for QinQ, testing the second level VLAN ID.
+ */
+static int DefragVlanQinQTest(void)
+{
+ Packet *p1 = NULL, *p2 = NULL, *r = NULL;
+
+ DefragInit();
+
+ p1 = BuildTestPacket(IPPROTO_ICMP, 1, 0, 1, 'A', 8);
+ FAIL_IF_NULL(p1);
+ p2 = BuildTestPacket(IPPROTO_ICMP, 1, 1, 0, 'B', 8);
+ FAIL_IF_NULL(p2);
+
+ /* With no VLAN IDs set, packets should re-assemble. */
+ FAIL_IF((r = Defrag(NULL, NULL, p1)) != NULL);
+ FAIL_IF((r = Defrag(NULL, NULL, p2)) == NULL);
+ SCFree(r);
+
+ /* With mismatched VLANs, packets should not re-assemble. */
+ p1->vlan_id[0] = 1;
+ p2->vlan_id[0] = 1;
+ p1->vlan_id[1] = 1;
+ p2->vlan_id[1] = 2;
+ FAIL_IF((r = Defrag(NULL, NULL, p1)) != NULL);
+ FAIL_IF((r = Defrag(NULL, NULL, p2)) != NULL);
+
+ SCFree(p1);
+ SCFree(p2);
+ DefragDestroy();
+
+ PASS;
+}
+
+/**
+ * Like DefragVlanTest, but for QinQinQ, testing the third level VLAN ID.
+ */
+static int DefragVlanQinQinQTest(void)
+{
+ Packet *r = NULL;
+
+ DefragInit();
+
+ Packet *p1 = BuildTestPacket(IPPROTO_ICMP, 1, 0, 1, 'A', 8);
+ FAIL_IF_NULL(p1);
+ Packet *p2 = BuildTestPacket(IPPROTO_ICMP, 1, 1, 0, 'B', 8);
+ FAIL_IF_NULL(p2);
+
+ /* With no VLAN IDs set, packets should re-assemble. */
+ FAIL_IF((r = Defrag(NULL, NULL, p1)) != NULL);
+ FAIL_IF((r = Defrag(NULL, NULL, p2)) == NULL);
+ SCFree(r);
+
+ /* With mismatched VLANs, packets should not re-assemble. */
+ p1->vlan_id[0] = 1;
+ p2->vlan_id[0] = 1;
+ p1->vlan_id[1] = 2;
+ p2->vlan_id[1] = 2;
+ p1->vlan_id[2] = 3;
+ p2->vlan_id[2] = 4;
+ FAIL_IF((r = Defrag(NULL, NULL, p1)) != NULL);
+ FAIL_IF((r = Defrag(NULL, NULL, p2)) != NULL);
+
+ PacketFree(p1);
+ PacketFree(p2);
+ DefragDestroy();
+
+ PASS;
+}
+static int DefragTrackerReuseTest(void)
+{
+ int id = 1;
+ Packet *p1 = NULL;
+ DefragTracker *tracker1 = NULL, *tracker2 = NULL;
+
+ DefragInit();
+
+ /* Build a packet, its not a fragment but shouldn't matter for
+ * this test. */
+ p1 = BuildTestPacket(IPPROTO_ICMP, id, 0, 0, 'A', 8);
+ FAIL_IF_NULL(p1);
+
+ /* Get a tracker. It shouldn't look like its already in use. */
+ tracker1 = DefragGetTracker(NULL, NULL, p1);
+ FAIL_IF_NULL(tracker1);
+ FAIL_IF(tracker1->seen_last);
+ FAIL_IF(tracker1->remove);
+ DefragTrackerRelease(tracker1);
+
+ /* Get a tracker again, it should be the same one. */
+ tracker2 = DefragGetTracker(NULL, NULL, p1);
+ FAIL_IF_NULL(tracker2);
+ FAIL_IF(tracker2 != tracker1);
+ DefragTrackerRelease(tracker1);
+
+ /* Now mark the tracker for removal. It should not be returned
+ * when we get a tracker for a packet that may have the same
+ * attributes. */
+ tracker1->remove = 1;
+
+ tracker2 = DefragGetTracker(NULL, NULL, p1);
+ FAIL_IF_NULL(tracker2);
+ FAIL_IF(tracker2 == tracker1);
+ FAIL_IF(tracker2->remove);
+
+ SCFree(p1);
+ DefragDestroy();
+ PASS;
+}
+
+/**
+ * IPV4: Test the case where you have a packet fragmented in 3 parts
+ * and send like:
+ * - Offset: 2; MF: 1
+ * - Offset: 0; MF: 1
+ * - Offset: 1; MF: 0
+ *
+ * Only the fragments with offset 0 and 1 should be reassembled.
+ */
+static int DefragMfIpv4Test(void)
+{
+ int ip_id = 9;
+ Packet *p = NULL;
+
+ DefragInit();
+
+ Packet *p1 = BuildTestPacket(IPPROTO_ICMP, ip_id, 2, 1, 'C', 8);
+ Packet *p2 = BuildTestPacket(IPPROTO_ICMP, ip_id, 0, 1, 'A', 8);
+ Packet *p3 = BuildTestPacket(IPPROTO_ICMP, ip_id, 1, 0, 'B', 8);
+ FAIL_IF(p1 == NULL || p2 == NULL || p3 == NULL);
+
+ p = Defrag(NULL, NULL, p1);
+ FAIL_IF_NOT_NULL(p);
+
+ p = Defrag(NULL, NULL, p2);
+ FAIL_IF_NOT_NULL(p);
+
+ /* This should return a packet as MF=0. */
+ p = Defrag(NULL, NULL, p3);
+ FAIL_IF_NULL(p);
+
+ /* Expected IP length is 20 + 8 + 8 = 36 as only 2 of the
+ * fragments should be in the re-assembled packet. */
+ FAIL_IF(IPV4_GET_IPLEN(p) != 36);
+
+ SCFree(p1);
+ SCFree(p2);
+ SCFree(p3);
+ SCFree(p);
+ DefragDestroy();
+ PASS;
+}
+
+/**
+ * IPV6: Test the case where you have a packet fragmented in 3 parts
+ * and send like:
+ * - Offset: 2; MF: 1
+ * - Offset: 0; MF: 1
+ * - Offset: 1; MF: 0
+ *
+ * Only the fragments with offset 0 and 1 should be reassembled.
+ */
+static int DefragMfIpv6Test(void)
+{
+ int ip_id = 9;
+ Packet *p = NULL;
+
+ DefragInit();
+
+ Packet *p1 = IPV6BuildTestPacket(IPPROTO_ICMPV6, ip_id, 2, 1, 'C', 8);
+ Packet *p2 = IPV6BuildTestPacket(IPPROTO_ICMPV6, ip_id, 0, 1, 'A', 8);
+ Packet *p3 = IPV6BuildTestPacket(IPPROTO_ICMPV6, ip_id, 1, 0, 'B', 8);
+ FAIL_IF(p1 == NULL || p2 == NULL || p3 == NULL);
+
+ p = Defrag(NULL, NULL, p1);
+ FAIL_IF_NOT_NULL(p);
+
+ p = Defrag(NULL, NULL, p2);
+ FAIL_IF_NOT_NULL(p);
+
+ /* This should return a packet as MF=0. */
+ p = Defrag(NULL, NULL, p3);
+ FAIL_IF_NULL(p);
+
+ /* For IPv6 the expected length is just the length of the payload
+ * of 2 fragments, so 16. */
+ FAIL_IF(IPV6_GET_PLEN(p) != 16);
+
+ SCFree(p1);
+ SCFree(p2);
+ SCFree(p3);
+ SCFree(p);
+ DefragDestroy();
+ PASS;
+}
+
+/**
+ * \brief Test that fragments that match other than the proto don't
+ * actually get matched.
+ */
+static int DefragTestBadProto(void)
+{
+ Packet *p1 = NULL, *p2 = NULL, *p3 = NULL;
+ int id = 12;
+
+ DefragInit();
+
+ p1 = BuildTestPacket(IPPROTO_ICMP, id, 0, 1, 'A', 8);
+ FAIL_IF_NULL(p1);
+ p2 = BuildTestPacket(IPPROTO_UDP, id, 1, 1, 'B', 8);
+ FAIL_IF_NULL(p2);
+ p3 = BuildTestPacket(IPPROTO_ICMP, id, 2, 0, 'C', 3);
+ FAIL_IF_NULL(p3);
+
+ FAIL_IF_NOT_NULL(Defrag(NULL, NULL, p1));
+ FAIL_IF_NOT_NULL(Defrag(NULL, NULL, p2));
+ FAIL_IF_NOT_NULL(Defrag(NULL, NULL, p3));
+
+ SCFree(p1);
+ SCFree(p2);
+ SCFree(p3);
+
+ DefragDestroy();
+ PASS;
+}
+
+/**
+ * \test Test a report Linux overlap issue that doesn't appear to be
+ * covered by the Sturges/Novak tests above.
+ */
+static int DefragTestJeremyLinux(void)
+{
+ char expected[] = "AAAAAAAA"
+ "AAAAAAAA"
+ "AAAAAAAA"
+ "CCCCCCCC"
+ "CCCCCCCC"
+ "CCCCCCCC"
+ "CCCCCCCC"
+ "CCCCCCCC"
+ "CCCCCCCC"
+ "BBBBBBBB"
+ "BBBBBBBB"
+ "DDDDDDDD"
+ "DDDDDD";
+
+ DefragInit();
+ default_policy = DEFRAG_POLICY_LINUX;
+
+ int id = 1;
+ Packet *packets[4];
+ int i = 0;
+
+ packets[0] = BuildTestPacket(IPPROTO_ICMP, id, 0, 1, 'A', 24);
+ packets[1] = BuildTestPacket(IPPROTO_ICMP, id, 40 >> 3, 1, 'B', 48);
+ packets[2] = BuildTestPacket(IPPROTO_ICMP, id, 24 >> 3, 1, 'C', 48);
+ packets[3] = BuildTestPacket(IPPROTO_ICMP, id, 88 >> 3, 0, 'D', 14);
+
+ Packet *r = Defrag(NULL, NULL, packets[0]);
+ FAIL_IF_NOT_NULL(r);
+
+ r = Defrag(NULL, NULL, packets[1]);
+ FAIL_IF_NOT_NULL(r);
+
+ r = Defrag(NULL, NULL, packets[2]);
+ FAIL_IF_NOT_NULL(r);
+
+ r = Defrag(NULL, NULL, packets[3]);
+ FAIL_IF_NULL(r);
+
+ FAIL_IF(memcmp(expected, GET_PKT_DATA(r) + 20, sizeof(expected)) != 0);
+
+ for (i = 0; i < 4; i++) {
+ SCFree(packets[i]);
+ }
+ SCFree(r);
+
+ DefragDestroy();
+ PASS;
+}
+
+#endif /* UNITTESTS */
+
+void DefragRegisterTests(void)
+{
+#ifdef UNITTESTS
+ UtRegisterTest("DefragInOrderSimpleTest", DefragInOrderSimpleTest);
+ UtRegisterTest("DefragReverseSimpleTest", DefragReverseSimpleTest);
+ UtRegisterTest("DefragSturgesNovakBsdTest", DefragSturgesNovakBsdTest);
+ UtRegisterTest("DefragSturgesNovakLinuxIpv4Test",
+ DefragSturgesNovakLinuxIpv4Test);
+ UtRegisterTest("DefragSturgesNovakWindowsIpv4Test",
+ DefragSturgesNovakWindowsIpv4Test);
+ UtRegisterTest("DefragSturgesNovakSolarisTest",
+ DefragSturgesNovakSolarisTest);
+ UtRegisterTest("DefragSturgesNovakFirstTest", DefragSturgesNovakFirstTest);
+ UtRegisterTest("DefragSturgesNovakLastTest", DefragSturgesNovakLastTest);
+
+ UtRegisterTest("DefragIPv4NoDataTest", DefragIPv4NoDataTest);
+ UtRegisterTest("DefragIPv4TooLargeTest", DefragIPv4TooLargeTest);
+
+ UtRegisterTest("IPV6DefragInOrderSimpleTest", IPV6DefragInOrderSimpleTest);
+ UtRegisterTest("IPV6DefragReverseSimpleTest", IPV6DefragReverseSimpleTest);
+ UtRegisterTest("IPV6DefragSturgesNovakBsdTest",
+ IPV6DefragSturgesNovakBsdTest);
+ UtRegisterTest("IPV6DefragSturgesNovakLinuxTest",
+ IPV6DefragSturgesNovakLinuxTest);
+ UtRegisterTest("IPV6DefragSturgesNovakWindowsTest",
+ IPV6DefragSturgesNovakWindowsTest);
+ UtRegisterTest("IPV6DefragSturgesNovakSolarisTest",
+ IPV6DefragSturgesNovakSolarisTest);
+ UtRegisterTest("IPV6DefragSturgesNovakFirstTest",
+ IPV6DefragSturgesNovakFirstTest);
+ UtRegisterTest("IPV6DefragSturgesNovakLastTest",
+ IPV6DefragSturgesNovakLastTest);
+
+ UtRegisterTest("DefragVlanTest", DefragVlanTest);
+ UtRegisterTest("DefragVlanQinQTest", DefragVlanQinQTest);
+ UtRegisterTest("DefragVlanQinQinQTest", DefragVlanQinQinQTest);
+ UtRegisterTest("DefragTrackerReuseTest", DefragTrackerReuseTest);
+ UtRegisterTest("DefragTimeoutTest", DefragTimeoutTest);
+ UtRegisterTest("DefragMfIpv4Test", DefragMfIpv4Test);
+ UtRegisterTest("DefragMfIpv6Test", DefragMfIpv6Test);
+ UtRegisterTest("DefragTestBadProto", DefragTestBadProto);
+
+ UtRegisterTest("DefragTestJeremyLinux", DefragTestJeremyLinux);
+#endif /* UNITTESTS */
+}