diff options
author | Ashlee Young <ashlee@onosfw.com> | 2015-09-09 22:21:41 -0700 |
---|---|---|
committer | Ashlee Young <ashlee@onosfw.com> | 2015-09-09 22:21:41 -0700 |
commit | 8879b125d26e8db1a5633de5a9c692eb2d1c4f83 (patch) | |
tree | c7259d85a991b83dfa85ab2e339360669fc1f58e /framework/src/suricata/src/defrag.c | |
parent | 13d05bc8458758ee39cb829098241e89616717ee (diff) |
suricata checkin based on commit id a4bce14770beee46a537eda3c3f6e8e8565d5d0a
Change-Id: I9a214fa0ee95e58fc640e50bd604dac7f42db48f
Diffstat (limited to 'framework/src/suricata/src/defrag.c')
-rw-r--r-- | framework/src/suricata/src/defrag.c | 2381 |
1 files changed, 2381 insertions, 0 deletions
diff --git a/framework/src/suricata/src/defrag.c b/framework/src/suricata/src/defrag.c new file mode 100644 index 00000000..7418b93c --- /dev/null +++ b/framework/src/suricata/src/defrag.c @@ -0,0 +1,2381 @@ +/* Copyright (C) 2007-2012 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 normalizaton (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 "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 int default_policy = DEFRAG_POLICY_BSD; + +/** The global DefragContext so all threads operate from the same + * context. */ +static DefragContext *defrag_context; + +/** + * 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; + + /* Lock the frag pool as we'll be return items to it. */ + SCMutexLock(&defrag_context->frag_pool_lock); + + while ((frag = TAILQ_FIRST(&tracker->frags)) != NULL) { + TAILQ_REMOVE(&tracker->frags, frag, next); + + /* Don't SCFree the frag, just give it back to its pool. */ + 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) { + SCLogError(SC_ERR_MEM_ALLOC, + "Defrag: Failed to initialize fragment pool."); + exit(EXIT_FAILURE); + } + if (SCMutexInit(&dc->frag_pool_lock, NULL) != 0) { + SCLogError(SC_ERR_MUTEX, + "Defrag: Failed to initialize frag pool mutex."); + exit(EXIT_FAILURE); + } + + /* Set the default timeout. */ + intmax_t timeout; + if (!ConfGetInt("defrag.timeout", &timeout)) { + dc->timeout = TIMEOUT_DEFAULT; + } + else { + if (timeout < TIMEOUT_MIN) { + SCLogError(SC_ERR_INVALID_ARGUMENT, + "defrag: Timeout less than minimum allowed value."); + exit(EXIT_FAILURE); + } + else if (timeout > TIMEOUT_MAX) { + SCLogError(SC_ERR_INVALID_ARGUMENT, + "defrag: Tiemout greater than maximum allowed value."); + exit(EXIT_FAILURE); + } + 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 all the data. Relies on the fact that + * fragments are inserted if frag_offset order. */ + Frag *frag; + int len = 0; + TAILQ_FOREACH(frag, &tracker->frags, next) { + if (frag->skip) + continue; + + if (frag == TAILQ_FIRST(&tracker->frags)) { + 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, NULL, 0, IPV4_GET_IPPROTO(p)); + if (rp == NULL) { + SCLogError(SC_ERR_MEM_ALLOC, "Failed to allocate packet for " + "fragmentation re-assembly, dumping fragments."); + goto error_remove_tracker; + } + PKT_SET_SRC(rp, PKT_SRC_DEFRAG); + rp->recursion_level = p->recursion_level; + + int fragmentable_offset = 0; + int fragmentable_len = 0; + int hlen = 0; + int ip_hdr_offset = 0; + TAILQ_FOREACH(frag, &tracker->frags, next) { + 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->data_len - frag->ltrim <= 0) + 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) { + SCLogWarning(SC_ERR_REASSEMBLY, "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 + frag->data_len > fragmentable_len) + fragmentable_len = frag->offset + frag->data_len; + } + } + + SCLogDebug("ip_hdr_offset %u, hlen %u, fragmentable_len %u", + ip_hdr_offset, hlen, fragmentable_len); + + rp->ip4h = (IPV4Hdr *)(GET_PKT_DATA(rp) + ip_hdr_offset); + int old = rp->ip4h->ip_len + rp->ip4h->ip_off; + 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 all the data. Relies on the fact that + * fragments are inserted if frag_offset order. */ + Frag *frag; + int len = 0; + TAILQ_FOREACH(frag, &tracker->frags, next) { + if (frag->skip) + continue; + + if (frag == TAILQ_FIRST(&tracker->frags)) { + 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) { + SCLogError(SC_ERR_MEM_ALLOC, "Failed to allocate packet for " + "fragmentation re-assembly, dumping fragments."); + goto error_remove_tracker; + } + PKT_SET_SRC(rp, PKT_SRC_DEFRAG); + + int unfragmentable_len = 0; + int fragmentable_offset = 0; + int fragmentable_len = 0; + int ip_hdr_offset = 0; + uint8_t next_hdr = 0; + TAILQ_FOREACH(frag, &tracker->frags, next) { + 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. */ + unfragmentable_len = (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; + } + } + + rp->ip6h = (IPV6Hdr *)(GET_PKT_DATA(rp) + ip_hdr_offset); + 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; +} + +/** + * 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, PacketQueue *pq) +{ + Packet *r = NULL; + int ltrim = 0; + + uint8_t more_frags; + uint16_t frag_offset; + + /* IPv4 header length - IPv4 only. */ + uint16_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. */ + int 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 = IPV4_GET_IPOFFSET(p) << 3; + hlen = IPV4_GET_HLEN(p); + data_offset = (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 = (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 = (uint8_t *)p->ip6eh.ip6fh + sizeof(IPV6FragHdr) - GET_PKT_DATA(p); + data_len = IPV6_GET_PLEN(p) - ( + ((uint8_t *)p->ip6eh.ip6fh + sizeof(IPV6FragHdr)) - + ((uint8_t *)p->ip6h + sizeof(IPV6Hdr))); + frag_end = frag_offset + data_len; + ip_hdr_offset = (uint8_t *)p->ip6h - GET_PKT_DATA(p); + frag_hdr_offset = (uint8_t *)p->ip6eh.ip6fh - GET_PKT_DATA(p); + + /* handle unfragmentable exthdrs */ + if (ip_hdr_offset + IPV6_HEADER_LEN < frag_hdr_offset) { + SCLogDebug("we have exthdrs before fraghdr %u bytes (%u hdrs total)", + (uint32_t)(frag_hdr_offset - (ip_hdr_offset + IPV6_HEADER_LEN)), + p->ip6eh.ip6_exthdrs_cnt); + + /* get the offset of the 'next' field in exthdr before the FH, + * relative to the buffer start */ + int t_offset = (int)((p->ip6eh.ip6_exthdrs[p->ip6eh.ip6_exthdrs_cnt - 2].data - 2) - GET_PKT_DATA(p)); + + /* store offset and FH 'next' value for updating frag buffer below */ + ip6_nh_set_offset = (int)t_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 { + /* Abort - should not happen. */ + SCLogWarning(SC_ERR_INVALID_ARGUMENT, "Invalid address family, aborting."); + return NULL; + } + + /* Update timeout. */ + tracker->timeout.tv_sec = p->ts.tv_sec + tracker->host_timeout; + tracker->timeout.tv_usec = p->ts.tv_usec; + + Frag *prev = NULL, *next; + int overlap = 0; + if (!TAILQ_EMPTY(&tracker->frags)) { + TAILQ_FOREACH(prev, &tracker->frags, next) { + ltrim = 0; + next = TAILQ_NEXT(prev, next); + + 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; + overlap++; + } + if ((next != NULL) && (frag_end > next->offset)) { + next->ltrim = frag_end - next->offset; + overlap++; + } + if ((frag_offset < prev->offset) && + (frag_end >= prev->offset + prev->data_len)) { + prev->skip = 1; + overlap++; + } + goto insert; + } + break; + case DEFRAG_POLICY_LINUX: + if (frag_offset < prev->offset + prev->data_len) { + if (frag_offset > prev->offset) { + ltrim = prev->offset + prev->data_len - frag_offset; + overlap++; + } + if ((next != NULL) && (frag_end > next->offset)) { + next->ltrim = frag_end - next->offset; + overlap++; + } + if ((frag_offset < prev->offset) && + (frag_end >= prev->offset + prev->data_len)) { + prev->skip = 1; + overlap++; + } + goto insert; + } + break; + case DEFRAG_POLICY_WINDOWS: + if (frag_offset < prev->offset + prev->data_len) { + if (frag_offset >= prev->offset) { + ltrim = prev->offset + prev->data_len - frag_offset; + overlap++; + } + if ((frag_offset < prev->offset) && + (frag_end > prev->offset + prev->data_len)) { + prev->skip = 1; + overlap++; + } + 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; + overlap++; + } + if ((frag_offset < prev->offset) && + (frag_end >= prev->offset + prev->data_len)) { + prev->skip = 1; + overlap++; + } + goto insert; + } + break; + case DEFRAG_POLICY_FIRST: + if ((frag_offset >= prev->offset) && + (frag_end <= prev->offset + prev->data_len)) { + overlap++; + 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; + overlap++; + goto insert; + } + break; + case DEFRAG_POLICY_LAST: + if (frag_offset <= prev->offset) { + if (frag_end > prev->offset) { + prev->ltrim = frag_end - prev->offset; + overlap++; + } + goto insert; + } + break; + default: + break; + } + } + } + +insert: + if (data_len - ltrim <= 0) { + if (af == AF_INET) { + ENGINE_SET_EVENT(p, IPV4_FRAG_TOO_LARGE); + } else { + ENGINE_SET_EVENT(p, IPV6_FRAG_TOO_LARGE); + } + 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; +#ifdef DEBUG + new->pcap_cnt = pcap_cnt; +#endif + + Frag *frag; + TAILQ_FOREACH(frag, &tracker->frags, next) { + if (new->offset < frag->offset) + break; + } + if (frag == NULL) { + TAILQ_INSERT_TAIL(&tracker->frags, new, next); + } + else { + TAILQ_INSERT_BEFORE(frag, new, next); + } + + 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 (pq && DecodeIPV4(tv, dtv, r, (void *)r->ip4h, + IPV4_GET_IPLEN(r), pq) != TM_ECODE_OK) { + TmqhOutputPacketpool(tv, r); + } 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 (pq && DecodeIPV6(tv, dtv, r, (uint8_t *)r->ip6h, + IPV6_GET_PLEN(r) + IPV6_HEADER_LEN, + pq) != TM_ECODE_OK) { + TmqhOutputPacketpool(tv, r); + } 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, PacketQueue *pq) +{ + 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) + return NULL; + + Packet *rp = DefragInsertFrag(tv, dtv, tracker, p, pq); + 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) { + SCLogError(SC_ERR_MEM_ALLOC, + "Failed to allocate memory for the Defrag module."); + exit(EXIT_FAILURE); + } + + DefragSetDefaultTimeout(defrag_context->timeout); + DefragInitConfig(FALSE); +} + +void DefragDestroy(void) +{ + DefragHashShutdown(); + DefragContextDestroy(defrag_context); + defrag_context = NULL; +} + +#ifdef UNITTESTS +#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(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; + + PACKET_INITIALIZE(p); + + gettimeofday(&p->ts, NULL); + //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); + ip4h.ip_off = htons(off); + if (mf) + ip4h.ip_off = htons(IP_MF | off); + else + ip4h.ip_off = htons(off); + ip4h.ip_ttl = ttl; + ip4h.ip_proto = IPPROTO_ICMP; + + 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 = IPV4CalculateChecksum((uint16_t *)GET_PKT_DATA(p), hlen); + + /* 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) != IPPROTO_ICMP) + goto error; + + return p; +error: + if (p != NULL) + SCFree(p); + return NULL; +} + +static Packet * +IPV6BuildTestPacket(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; + + PACKET_INITIALIZE(p); + + gettimeofday(&p->ts, NULL); + + 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 = IPPROTO_ICMP; + fh->ip6fh_ident = htonl(id); + fh->ip6fh_offlg = htons((off << 3) | mf); + p->ip6eh.ip6fh = fh; + + 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: + fprintf(stderr, "Error building test packet.\n"); + 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; + int ret = 0; + + DefragInit(); + + p1 = BuildTestPacket(id, 0, 1, 'A', 8); + if (p1 == NULL) + goto end; + p2 = BuildTestPacket(id, 1, 1, 'B', 8); + if (p2 == NULL) + goto end; + p3 = BuildTestPacket(id, 2, 0, 'C', 3); + if (p3 == NULL) + goto end; + + if (Defrag(NULL, NULL, p1, NULL) != NULL) + goto end; + if (Defrag(NULL, NULL, p2, NULL) != NULL) + goto end; + + reassembled = Defrag(NULL, NULL, p3, NULL); + if (reassembled == NULL) { + goto end; + } + + if (IPV4_GET_HLEN(reassembled) != 20) { + goto end; + } + if (IPV4_GET_IPLEN(reassembled) != 39) { + goto end; + } + + /* 20 bytes in we should find 8 bytes of A. */ + for (i = 20; i < 20 + 8; i++) { + if (GET_PKT_DATA(reassembled)[i] != 'A') { + goto end; + } + } + + /* 28 bytes in we should find 8 bytes of B. */ + for (i = 28; i < 28 + 8; i++) { + if (GET_PKT_DATA(reassembled)[i] != 'B') { + goto end; + } + } + + /* And 36 bytes in we should find 3 bytes of C. */ + for (i = 36; i < 36 + 3; i++) { + if (GET_PKT_DATA(reassembled)[i] != 'C') + goto end; + } + + ret = 1; + +end: + if (p1 != NULL) + SCFree(p1); + if (p2 != NULL) + SCFree(p2); + if (p3 != NULL) + SCFree(p3); + if (reassembled != NULL) + SCFree(reassembled); + + DefragDestroy(); + return ret; +} + +/** + * 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; + int ret = 0; + + DefragInit(); + + p1 = BuildTestPacket(id, 0, 1, 'A', 8); + if (p1 == NULL) + goto end; + p2 = BuildTestPacket(id, 1, 1, 'B', 8); + if (p2 == NULL) + goto end; + p3 = BuildTestPacket(id, 2, 0, 'C', 3); + if (p3 == NULL) + goto end; + + if (Defrag(NULL, NULL, p3, NULL) != NULL) + goto end; + if (Defrag(NULL, NULL, p2, NULL) != NULL) + goto end; + + reassembled = Defrag(NULL, NULL, p1, NULL); + if (reassembled == NULL) + goto end; + + if (IPV4_GET_HLEN(reassembled) != 20) + goto end; + if (IPV4_GET_IPLEN(reassembled) != 39) + goto end; + + /* 20 bytes in we should find 8 bytes of A. */ + for (i = 20; i < 20 + 8; i++) { + if (GET_PKT_DATA(reassembled)[i] != 'A') + goto end; + } + + /* 28 bytes in we should find 8 bytes of B. */ + for (i = 28; i < 28 + 8; i++) { + if (GET_PKT_DATA(reassembled)[i] != 'B') + goto end; + } + + /* And 36 bytes in we should find 3 bytes of C. */ + for (i = 36; i < 36 + 3; i++) { + if (GET_PKT_DATA(reassembled)[i] != 'C') + goto end; + } + + ret = 1; +end: + if (p1 != NULL) + SCFree(p1); + if (p2 != NULL) + SCFree(p2); + if (p3 != NULL) + SCFree(p3); + if (reassembled != NULL) + SCFree(reassembled); + + DefragDestroy(); + return ret; +} + +/** + * 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; + int ret = 0; + + DefragInit(); + + p1 = IPV6BuildTestPacket(id, 0, 1, 'A', 8); + if (p1 == NULL) + goto end; + p2 = IPV6BuildTestPacket(id, 1, 1, 'B', 8); + if (p2 == NULL) + goto end; + p3 = IPV6BuildTestPacket(id, 2, 0, 'C', 3); + if (p3 == NULL) + goto end; + + if (Defrag(NULL, NULL, p1, NULL) != NULL) + goto end; + if (Defrag(NULL, NULL, p2, NULL) != NULL) + goto end; + reassembled = Defrag(NULL, NULL, p3, NULL); + if (reassembled == NULL) + goto end; + + if (IPV6_GET_PLEN(reassembled) != 19) + goto end; + + /* 40 bytes in we should find 8 bytes of A. */ + for (i = 40; i < 40 + 8; i++) { + if (GET_PKT_DATA(reassembled)[i] != 'A') + goto end; + } + + /* 28 bytes in we should find 8 bytes of B. */ + for (i = 48; i < 48 + 8; i++) { + if (GET_PKT_DATA(reassembled)[i] != 'B') + goto end; + } + + /* And 36 bytes in we should find 3 bytes of C. */ + for (i = 56; i < 56 + 3; i++) { + if (GET_PKT_DATA(reassembled)[i] != 'C') + goto end; + } + + ret = 1; +end: + if (p1 != NULL) + SCFree(p1); + if (p2 != NULL) + SCFree(p2); + if (p3 != NULL) + SCFree(p3); + if (reassembled != NULL) + SCFree(reassembled); + + DefragDestroy(); + return ret; +} + +static int +IPV6DefragReverseSimpleTest(void) +{ + DefragContext *dc = NULL; + Packet *p1 = NULL, *p2 = NULL, *p3 = NULL; + Packet *reassembled = NULL; + int id = 12; + int i; + int ret = 0; + + DefragInit(); + + dc = DefragContextNew(); + if (dc == NULL) + goto end; + + p1 = IPV6BuildTestPacket(id, 0, 1, 'A', 8); + if (p1 == NULL) + goto end; + p2 = IPV6BuildTestPacket(id, 1, 1, 'B', 8); + if (p2 == NULL) + goto end; + p3 = IPV6BuildTestPacket(id, 2, 0, 'C', 3); + if (p3 == NULL) + goto end; + + if (Defrag(NULL, NULL, p3, NULL) != NULL) + goto end; + if (Defrag(NULL, NULL, p2, NULL) != NULL) + goto end; + reassembled = Defrag(NULL, NULL, p1, NULL); + if (reassembled == NULL) + goto end; + + /* 40 bytes in we should find 8 bytes of A. */ + for (i = 40; i < 40 + 8; i++) { + if (GET_PKT_DATA(reassembled)[i] != 'A') + goto end; + } + + /* 28 bytes in we should find 8 bytes of B. */ + for (i = 48; i < 48 + 8; i++) { + if (GET_PKT_DATA(reassembled)[i] != 'B') + goto end; + } + + /* And 36 bytes in we should find 3 bytes of C. */ + for (i = 56; i < 56 + 3; i++) { + if (GET_PKT_DATA(reassembled)[i] != 'C') + goto end; + } + + ret = 1; +end: + if (dc != NULL) + DefragContextDestroy(dc); + if (p1 != NULL) + SCFree(p1); + if (p2 != NULL) + SCFree(p2); + if (p3 != NULL) + SCFree(p3); + if (reassembled != NULL) + SCFree(reassembled); + + DefragDestroy(); + return ret; +} + +static int +DefragDoSturgesNovakTest(int policy, u_char *expected, size_t expected_len) +{ + int i; + int ret = 0; + + DefragInit(); + + /* + * Build the packets. + */ + + int id = 1; + Packet *packets[17]; + memset(packets, 0x00, sizeof(packets)); + + /* + * Original fragments. + */ + + /* A*24 at 0. */ + packets[0] = BuildTestPacket(id, 0, 1, 'A', 24); + + /* B*15 at 32. */ + packets[1] = BuildTestPacket(id, 32 >> 3, 1, 'B', 16); + + /* C*24 at 48. */ + packets[2] = BuildTestPacket(id, 48 >> 3, 1, 'C', 24); + + /* D*8 at 80. */ + packets[3] = BuildTestPacket(id, 80 >> 3, 1, 'D', 8); + + /* E*16 at 104. */ + packets[4] = BuildTestPacket(id, 104 >> 3, 1, 'E', 16); + + /* F*24 at 120. */ + packets[5] = BuildTestPacket(id, 120 >> 3, 1, 'F', 24); + + /* G*16 at 144. */ + packets[6] = BuildTestPacket(id, 144 >> 3, 1, 'G', 16); + + /* H*16 at 160. */ + packets[7] = BuildTestPacket(id, 160 >> 3, 1, 'H', 16); + + /* I*8 at 176. */ + packets[8] = BuildTestPacket(id, 176 >> 3, 1, 'I', 8); + + /* + * Overlapping subsequent fragments. + */ + + /* J*32 at 8. */ + packets[9] = BuildTestPacket(id, 8 >> 3, 1, 'J', 32); + + /* K*24 at 48. */ + packets[10] = BuildTestPacket(id, 48 >> 3, 1, 'K', 24); + + /* L*24 at 72. */ + packets[11] = BuildTestPacket(id, 72 >> 3, 1, 'L', 24); + + /* M*24 at 96. */ + packets[12] = BuildTestPacket(id, 96 >> 3, 1, 'M', 24); + + /* N*8 at 128. */ + packets[13] = BuildTestPacket(id, 128 >> 3, 1, 'N', 8); + + /* O*8 at 152. */ + packets[14] = BuildTestPacket(id, 152 >> 3, 1, 'O', 8); + + /* P*8 at 160. */ + packets[15] = BuildTestPacket(id, 160 >> 3, 1, 'P', 8); + + /* Q*16 at 176. */ + packets[16] = BuildTestPacket(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], NULL); + if (tp != NULL) { + SCFree(tp); + goto end; + } + if (ENGINE_ISSET_EVENT(packets[i], IPV4_FRAG_OVERLAP)) { + goto end; + } + } + int overlap = 0; + for (; i < 16; i++) { + Packet *tp = Defrag(NULL, NULL, packets[i], NULL); + if (tp != NULL) { + SCFree(tp); + goto end; + } + if (ENGINE_ISSET_EVENT(packets[i], IPV4_FRAG_OVERLAP)) { + overlap++; + } + } + if (!overlap) { + goto end; + } + + /* And now the last one. */ + Packet *reassembled = Defrag(NULL, NULL, packets[16], NULL); + if (reassembled == NULL) { + goto end; + } + + if (IPV4_GET_HLEN(reassembled) != 20) { + goto end; + } + if (IPV4_GET_IPLEN(reassembled) != 20 + 192) { + goto end; + } + + if (memcmp(GET_PKT_DATA(reassembled) + 20, expected, expected_len) != 0) { + goto end; + } + SCFree(reassembled); + + /* Make sure all frags were returned back to the pool. */ + if (defrag_context->frag_pool->outstanding != 0) { + goto end; + } + + ret = 1; +end: + for (i = 0; i < 17; i++) { + SCFree(packets[i]); + } + DefragDestroy(); + return ret; +} + +static int +IPV6DefragDoSturgesNovakTest(int policy, u_char *expected, size_t expected_len) +{ + int i; + int ret = 0; + + DefragInit(); + + /* + * Build the packets. + */ + + int id = 1; + Packet *packets[17]; + memset(packets, 0x00, sizeof(packets)); + + /* + * Original fragments. + */ + + /* A*24 at 0. */ + packets[0] = IPV6BuildTestPacket(id, 0, 1, 'A', 24); + + /* B*15 at 32. */ + packets[1] = IPV6BuildTestPacket(id, 32 >> 3, 1, 'B', 16); + + /* C*24 at 48. */ + packets[2] = IPV6BuildTestPacket(id, 48 >> 3, 1, 'C', 24); + + /* D*8 at 80. */ + packets[3] = IPV6BuildTestPacket(id, 80 >> 3, 1, 'D', 8); + + /* E*16 at 104. */ + packets[4] = IPV6BuildTestPacket(id, 104 >> 3, 1, 'E', 16); + + /* F*24 at 120. */ + packets[5] = IPV6BuildTestPacket(id, 120 >> 3, 1, 'F', 24); + + /* G*16 at 144. */ + packets[6] = IPV6BuildTestPacket(id, 144 >> 3, 1, 'G', 16); + + /* H*16 at 160. */ + packets[7] = IPV6BuildTestPacket(id, 160 >> 3, 1, 'H', 16); + + /* I*8 at 176. */ + packets[8] = IPV6BuildTestPacket(id, 176 >> 3, 1, 'I', 8); + + /* + * Overlapping subsequent fragments. + */ + + /* J*32 at 8. */ + packets[9] = IPV6BuildTestPacket(id, 8 >> 3, 1, 'J', 32); + + /* K*24 at 48. */ + packets[10] = IPV6BuildTestPacket(id, 48 >> 3, 1, 'K', 24); + + /* L*24 at 72. */ + packets[11] = IPV6BuildTestPacket(id, 72 >> 3, 1, 'L', 24); + + /* M*24 at 96. */ + packets[12] = IPV6BuildTestPacket(id, 96 >> 3, 1, 'M', 24); + + /* N*8 at 128. */ + packets[13] = IPV6BuildTestPacket(id, 128 >> 3, 1, 'N', 8); + + /* O*8 at 152. */ + packets[14] = IPV6BuildTestPacket(id, 152 >> 3, 1, 'O', 8); + + /* P*8 at 160. */ + packets[15] = IPV6BuildTestPacket(id, 160 >> 3, 1, 'P', 8); + + /* Q*16 at 176. */ + packets[16] = IPV6BuildTestPacket(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], NULL); + if (tp != NULL) { + SCFree(tp); + goto end; + } + if (ENGINE_ISSET_EVENT(packets[i], IPV6_FRAG_OVERLAP)) { + goto end; + } + } + int overlap = 0; + for (; i < 16; i++) { + Packet *tp = Defrag(NULL, NULL, packets[i], NULL); + if (tp != NULL) { + SCFree(tp); + goto end; + } + if (ENGINE_ISSET_EVENT(packets[i], IPV6_FRAG_OVERLAP)) { + overlap++; + } + } + if (!overlap) + goto end; + + /* And now the last one. */ + Packet *reassembled = Defrag(NULL, NULL, packets[16], NULL); + if (reassembled == NULL) + goto end; + if (memcmp(GET_PKT_DATA(reassembled) + 40, expected, expected_len) != 0) + goto end; + + if (IPV6_GET_PLEN(reassembled) != 192) + goto end; + + SCFree(reassembled); + + /* Make sure all frags were returned to the pool. */ + if (defrag_context->frag_pool->outstanding != 0) { + printf("defrag_context->frag_pool->outstanding %u: ", defrag_context->frag_pool->outstanding); + goto end; + } + + ret = 1; + +end: + for (i = 0; i < 17; i++) { + SCFree(packets[i]); + } + DefragDestroy(); + return ret; +} + +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" + }; + + return DefragDoSturgesNovakTest(DEFRAG_POLICY_BSD, expected, sizeof(expected)); +} + +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" + }; + + return IPV6DefragDoSturgesNovakTest(DEFRAG_POLICY_BSD, expected, sizeof(expected)); +} + +static int +DefragSturgesNovakLinuxTest(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" + }; + + return DefragDoSturgesNovakTest(DEFRAG_POLICY_LINUX, expected, sizeof(expected)); +} + +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" + }; + + return IPV6DefragDoSturgesNovakTest(DEFRAG_POLICY_LINUX, expected, + sizeof(expected)); +} + +static int +DefragSturgesNovakWindowsTest(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" + }; + + return DefragDoSturgesNovakTest(DEFRAG_POLICY_WINDOWS, expected, sizeof(expected)); +} + +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" + }; + + return IPV6DefragDoSturgesNovakTest(DEFRAG_POLICY_WINDOWS, expected, + sizeof(expected)); +} + +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" + }; + + return DefragDoSturgesNovakTest(DEFRAG_POLICY_SOLARIS, expected, sizeof(expected)); +} + +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" + }; + + return IPV6DefragDoSturgesNovakTest(DEFRAG_POLICY_SOLARIS, expected, + sizeof(expected)); +} + +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" + }; + + return DefragDoSturgesNovakTest(DEFRAG_POLICY_FIRST, expected, sizeof(expected)); +} + +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" + }; + + return DefragDoSturgesNovakTest(DEFRAG_POLICY_LAST, expected, sizeof(expected)); +} + +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" + }; + + return IPV6DefragDoSturgesNovakTest(DEFRAG_POLICY_LAST, expected, + sizeof(expected)); +} + +static int +DefragTimeoutTest(void) +{ + int i; + int ret = 0; + + /* Setup a small numberr of trackers. */ + if (ConfSet("defrag.trackers", "16") != 1) { + printf("ConfSet failed: "); + goto end; + } + + DefragInit(); + + /* Load in 16 packets. */ + for (i = 0; i < 16; i++) { + Packet *p = BuildTestPacket(i, 0, 1, 'A' + i, 16); + if (p == NULL) + goto end; + + Packet *tp = Defrag(NULL, NULL, p, NULL); + + SCFree(p); + + if (tp != NULL) { + SCFree(tp); + goto end; + } + } + + /* 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(99, 0, 1, 'A' + i, 16); + if (p == NULL) + goto end; + + p->ts.tv_sec += (defrag_context->timeout + 1); + Packet *tp = Defrag(NULL, NULL, p, NULL); + + if (tp != NULL) { + SCFree(tp); + goto end; + } + + DefragTracker *tracker = DefragLookupTrackerFromHash(p); + if (tracker == NULL) + goto end; + + if (tracker->id != 99) + goto end; + + SCFree(p); + + ret = 1; +end: + DefragDestroy(); + return ret; +} + +/** + * 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; + int ret = 0; + + DefragInit(); + + dc = DefragContextNew(); + if (dc == NULL) + goto end; + + /* This packet has an offset > 0, more frags set to 0 and no data. */ + p = BuildTestPacket(id, 1, 0, 'A', 0); + if (p == NULL) + goto end; + + /* We do not expect a packet returned. */ + if (Defrag(NULL, NULL, p, NULL) != NULL) + goto end; + + /* The fragment should have been ignored so no fragments should + * have been allocated from the pool. */ + if (dc->frag_pool->outstanding != 0) + return 0; + + ret = 1; +end: + if (dc != NULL) + DefragContextDestroy(dc); + if (p != NULL) + SCFree(p); + + DefragDestroy(); + return ret; +} + +static int +DefragIPv4TooLargeTest(void) +{ + DefragContext *dc = NULL; + Packet *p = NULL; + int ret = 0; + + DefragInit(); + + dc = DefragContextNew(); + if (dc == NULL) + goto end; + + /* Create a fragment that would extend past the max allowable size + * for an IPv4 packet. */ + p = BuildTestPacket(1, 8183, 0, 'A', 71); + if (p == NULL) + goto end; + + /* We do not expect a packet returned. */ + if (Defrag(NULL, NULL, p, NULL) != NULL) + goto end; + if (!ENGINE_ISSET_EVENT(p, IPV4_FRAG_PKT_TOO_LARGE)) + goto end; + + /* The fragment should have been ignored so no fragments should have + * been allocated from the pool. */ + if (dc->frag_pool->outstanding != 0) + return 0; + + ret = 1; +end: + if (dc != NULL) + DefragContextDestroy(dc); + if (p != NULL) + SCFree(p); + + DefragDestroy(); + return ret; +} + +/** + * 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; + int ret = 0; + + DefragInit(); + + p1 = BuildTestPacket(1, 0, 1, 'A', 8); + if (p1 == NULL) + goto end; + p2 = BuildTestPacket(1, 1, 0, 'B', 8); + if (p2 == NULL) + goto end; + + /* With no VLAN IDs set, packets should re-assemble. */ + if ((r = Defrag(NULL, NULL, p1, NULL)) != NULL) + goto end; + if ((r = Defrag(NULL, NULL, p2, NULL)) == NULL) + goto end; + SCFree(r); + + /* With mismatched VLANs, packets should not re-assemble. */ + p1->vlan_id[0] = 1; + p2->vlan_id[0] = 2; + if ((r = Defrag(NULL, NULL, p1, NULL)) != NULL) + goto end; + if ((r = Defrag(NULL, NULL, p2, NULL)) != NULL) + goto end; + + /* Pass. */ + ret = 1; + +end: + if (p1 != NULL) + SCFree(p1); + if (p2 != NULL) + SCFree(p2); + DefragDestroy(); + + return ret; +} + +/** + * Like DefragVlanTest, but for QinQ, testing the second level VLAN ID. + */ +static int +DefragVlanQinQTest(void) +{ + Packet *p1 = NULL, *p2 = NULL, *r = NULL; + int ret = 0; + + DefragInit(); + + p1 = BuildTestPacket(1, 0, 1, 'A', 8); + if (p1 == NULL) + goto end; + p2 = BuildTestPacket(1, 1, 0, 'B', 8); + if (p2 == NULL) + goto end; + + /* With no VLAN IDs set, packets should re-assemble. */ + if ((r = Defrag(NULL, NULL, p1, NULL)) != NULL) + goto end; + if ((r = Defrag(NULL, NULL, p2, NULL)) == NULL) + goto end; + 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; + if ((r = Defrag(NULL, NULL, p1, NULL)) != NULL) + goto end; + if ((r = Defrag(NULL, NULL, p2, NULL)) != NULL) + goto end; + + /* Pass. */ + ret = 1; + +end: + if (p1 != NULL) + SCFree(p1); + if (p2 != NULL) + SCFree(p2); + DefragDestroy(); + + return ret; +} + +#endif /* UNITTESTS */ + +void +DefragRegisterTests(void) +{ +#ifdef UNITTESTS + UtRegisterTest("DefragInOrderSimpleTest", + DefragInOrderSimpleTest, 1); + UtRegisterTest("DefragReverseSimpleTest", + DefragReverseSimpleTest, 1); + UtRegisterTest("DefragSturgesNovakBsdTest", + DefragSturgesNovakBsdTest, 1); + UtRegisterTest("DefragSturgesNovakLinuxTest", + DefragSturgesNovakLinuxTest, 1); + UtRegisterTest("DefragSturgesNovakWindowsTest", + DefragSturgesNovakWindowsTest, 1); + UtRegisterTest("DefragSturgesNovakSolarisTest", + DefragSturgesNovakSolarisTest, 1); + UtRegisterTest("DefragSturgesNovakFirstTest", + DefragSturgesNovakFirstTest, 1); + UtRegisterTest("DefragSturgesNovakLastTest", + DefragSturgesNovakLastTest, 1); + + UtRegisterTest("DefragIPv4NoDataTest", DefragIPv4NoDataTest, 1); + UtRegisterTest("DefragIPv4TooLargeTest", DefragIPv4TooLargeTest, 1); + + UtRegisterTest("IPV6DefragInOrderSimpleTest", + IPV6DefragInOrderSimpleTest, 1); + UtRegisterTest("IPV6DefragReverseSimpleTest", + IPV6DefragReverseSimpleTest, 1); + UtRegisterTest("IPV6DefragSturgesNovakBsdTest", + IPV6DefragSturgesNovakBsdTest, 1); + UtRegisterTest("IPV6DefragSturgesNovakLinuxTest", + IPV6DefragSturgesNovakLinuxTest, 1); + UtRegisterTest("IPV6DefragSturgesNovakWindowsTest", + IPV6DefragSturgesNovakWindowsTest, 1); + UtRegisterTest("IPV6DefragSturgesNovakSolarisTest", + IPV6DefragSturgesNovakSolarisTest, 1); + UtRegisterTest("IPV6DefragSturgesNovakFirstTest", + IPV6DefragSturgesNovakFirstTest, 1); + UtRegisterTest("IPV6DefragSturgesNovakLastTest", + IPV6DefragSturgesNovakLastTest, 1); + + UtRegisterTest("DefragVlanTest", DefragVlanTest, 1); + UtRegisterTest("DefragVlanQinQTest", DefragVlanQinQTest, 1); + + UtRegisterTest("DefragTimeoutTest", + DefragTimeoutTest, 1); +#endif /* UNITTESTS */ +} + |