diff options
Diffstat (limited to 'src/defrag.c')
-rw-r--r-- | src/defrag.c | 2559 |
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 */ +} |