940 lines
24 KiB
Diff
940 lines
24 KiB
Diff
diff -urN ngrep-1.45/ipreasm.c ngrep-1.45-reasm/ipreasm.c
|
|
--- ngrep-1.45/ipreasm.c 1970-01-01 01:00:00.000000000 +0100
|
|
+++ ngrep-1.45-reasm/ipreasm.c 2007-06-16 19:17:20.124795623 +0200
|
|
@@ -0,0 +1,717 @@
|
|
+/*
|
|
+ * ipreasm -- Routines for reassembly of fragmented IPv4 and IPv6 packets.
|
|
+ *
|
|
+ * Copyright (c) 2007 Jan Andres <jandres@gmx.net>
|
|
+ *
|
|
+ */
|
|
+
|
|
+#include <stdio.h>
|
|
+#include <stdlib.h>
|
|
+#include <string.h>
|
|
+#include <stddef.h>
|
|
+
|
|
+#ifdef HAVE_CONFIG_H
|
|
+#include "config.h"
|
|
+#endif /* HAVE_CONFIG_H */
|
|
+
|
|
+#include <netinet/ip.h>
|
|
+#include <netinet/udp.h>
|
|
+#if USE_IPv6
|
|
+#include <netinet/ip6.h>
|
|
+#endif /* USE_IPv6 */
|
|
+
|
|
+#include "ipreasm.h"
|
|
+
|
|
+
|
|
+#define REASM_IP_HASH_SIZE 1021U
|
|
+
|
|
+
|
|
+enum entry_state {
|
|
+ STATE_ACTIVE,
|
|
+ STATE_INVALID,
|
|
+};
|
|
+
|
|
+
|
|
+enum reasm_proto {
|
|
+ PROTO_IPV4,
|
|
+#if USE_IPv6
|
|
+ PROTO_IPV6,
|
|
+#endif /* USE_IPv6 */
|
|
+};
|
|
+
|
|
+
|
|
+/*
|
|
+ * This tuple uniquely identifies all fragments belonging to
|
|
+ * the same IPv4 packet.
|
|
+ */
|
|
+struct reasm_id_ipv4 {
|
|
+ uint8_t ip_src[4], ip_dst[4];
|
|
+ uint16_t ip_id;
|
|
+ uint8_t ip_proto;
|
|
+};
|
|
+
|
|
+
|
|
+/*
|
|
+ * Same for IPv6.
|
|
+ */
|
|
+struct reasm_id_ipv6 {
|
|
+ uint8_t ip_src[16], ip_dst[16];
|
|
+ uint32_t ip_id;
|
|
+};
|
|
+
|
|
+
|
|
+union reasm_id {
|
|
+ struct reasm_id_ipv4 ipv4;
|
|
+ struct reasm_id_ipv6 ipv6;
|
|
+};
|
|
+
|
|
+
|
|
+struct reasm_frag_entry {
|
|
+ unsigned len; /* payload length of this fragment */
|
|
+ unsigned offset; /* offset of this fragment into the payload of the reassembled packet */
|
|
+ unsigned data_offset; /* offset to the data pointer where payload starts */
|
|
+ unsigned char *data; /* payload starts at data + data_offset */
|
|
+ struct reasm_frag_entry *next;
|
|
+};
|
|
+
|
|
+
|
|
+/*
|
|
+ * Reception of a complete packet is detected by counting the number
|
|
+ * of "holes" that remain between the cached fragments. A hole is
|
|
+ * assumed to exist at the upper end of the packet until the final
|
|
+ * fragment has been received. When the number of holes drops to 0,
|
|
+ * all fragments have been received and the packet can be reassembled.
|
|
+ */
|
|
+struct reasm_ip_entry {
|
|
+ union reasm_id id;
|
|
+ unsigned len, holes, frag_count, hash;
|
|
+ reasm_time_t timeout;
|
|
+ enum entry_state state;
|
|
+ enum reasm_proto protocol;
|
|
+ struct reasm_frag_entry *frags;
|
|
+ struct reasm_ip_entry *prev, *next;
|
|
+ struct reasm_ip_entry *time_prev, *time_next;
|
|
+};
|
|
+
|
|
+
|
|
+/*
|
|
+ * This struct contains some metadata, the main hash table, and a pointer
|
|
+ * to the first entry that will time out. A linked list is kept in the
|
|
+ * order in which packets will time out. Using a linked list for this
|
|
+ * purpose requires that packets are input in chronological order, and
|
|
+ * that a constant timeout value is used, which doesn't change even when
|
|
+ * the entry's state transitions from active to invalid.
|
|
+ */
|
|
+struct reasm_ip {
|
|
+ struct reasm_ip_entry *table[REASM_IP_HASH_SIZE];
|
|
+ struct reasm_ip_entry *time_first, *time_last;
|
|
+ unsigned waiting, max_waiting, timed_out, dropped_frags;
|
|
+ reasm_time_t timeout;
|
|
+};
|
|
+
|
|
+
|
|
+/*
|
|
+ * Hash functions.
|
|
+ */
|
|
+static unsigned reasm_ipv4_hash (const struct reasm_id_ipv4 *id);
|
|
+#if USE_IPv6
|
|
+static unsigned reasm_ipv6_hash (const struct reasm_id_ipv6 *id);
|
|
+#endif /* USE_IPv6 */
|
|
+
|
|
+/*
|
|
+ * Insert a new fragment to the correct position in the list of fragments.
|
|
+ * Check for fragment overlap and other error conditions. Update the
|
|
+ * "hole count".
|
|
+ */
|
|
+static bool add_fragment (struct reasm_ip_entry *entry, struct reasm_frag_entry *frag, bool last_frag);
|
|
+
|
|
+/*
|
|
+ * Is the entry complete, ready for reassembly?
|
|
+ */
|
|
+static bool is_complete (struct reasm_ip_entry *entry);
|
|
+
|
|
+/*
|
|
+ * Create the reassembled packet.
|
|
+ */
|
|
+static unsigned char *assemble (struct reasm_ip_entry *entry, unsigned *output_len);
|
|
+
|
|
+/*
|
|
+ * Drop and free entries.
|
|
+ */
|
|
+static void drop_entry (struct reasm_ip *reasm, struct reasm_ip_entry *entry);
|
|
+static void free_entry (struct reasm_ip_entry *entry);
|
|
+
|
|
+/*
|
|
+ * Dispose of any entries which have expired before "now".
|
|
+ */
|
|
+static void process_timeouts (struct reasm_ip *reasm, reasm_time_t now);
|
|
+
|
|
+/*
|
|
+ * Create fragment structure from IPv6 packet. Returns NULL if the input
|
|
+ * is not a fragment.
|
|
+ * This function is called by parse_packet(), don't call it directly.
|
|
+ */
|
|
+#if USE_IPv6
|
|
+static struct reasm_frag_entry *frag_from_ipv6 (unsigned char *packet, uint32_t *ip_id, bool *last_frag);
|
|
+#endif /* USE_IPv6 */
|
|
+
|
|
+/*
|
|
+ * Compare packet identification tuples for specified protocol.
|
|
+ */
|
|
+static bool reasm_id_equal (enum reasm_proto proto, const union reasm_id *left, const union reasm_id *right);
|
|
+
|
|
+/*
|
|
+ * Create fragment structure from an IPv4 or IPv6 packet. Returns NULL
|
|
+ * if the input is not a fragment.
|
|
+ */
|
|
+static struct reasm_frag_entry *parse_packet (unsigned char *packet, unsigned len, enum reasm_proto *protocol, union reasm_id *id, unsigned *hash, bool *last_frag);
|
|
+
|
|
+
|
|
+static unsigned
|
|
+reasm_ipv4_hash (const struct reasm_id_ipv4 *id)
|
|
+{
|
|
+ unsigned hash = 0;
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < 4; i++) {
|
|
+ hash = 37U * hash + id->ip_src[i];
|
|
+ hash = 37U * hash + id->ip_dst[i];
|
|
+ }
|
|
+
|
|
+ hash = 59U * hash + id->ip_id;
|
|
+
|
|
+ hash = 47U * hash + id->ip_proto;
|
|
+
|
|
+ return hash;
|
|
+}
|
|
+
|
|
+
|
|
+#if USE_IPv6
|
|
+static unsigned
|
|
+reasm_ipv6_hash (const struct reasm_id_ipv6 *id)
|
|
+{
|
|
+ unsigned hash = 0;
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < 16; i++) {
|
|
+ hash = 37U * hash + id->ip_src[i];
|
|
+ hash = 37U * hash + id->ip_dst[i];
|
|
+ }
|
|
+
|
|
+ hash = 59U * hash + id->ip_id;
|
|
+
|
|
+ return hash;
|
|
+}
|
|
+#endif /* USE_IPv6 */
|
|
+
|
|
+
|
|
+unsigned char *
|
|
+reasm_ip_next (struct reasm_ip *reasm, unsigned char *packet, unsigned len, reasm_time_t timestamp, unsigned *output_len)
|
|
+{
|
|
+ enum reasm_proto proto;
|
|
+ union reasm_id id;
|
|
+ unsigned hash;
|
|
+ bool last_frag;
|
|
+
|
|
+ process_timeouts (reasm, timestamp);
|
|
+
|
|
+ struct reasm_frag_entry *frag = parse_packet (packet, len, &proto, &id, &hash, &last_frag);
|
|
+ if (frag == NULL) {
|
|
+ *output_len = len;
|
|
+ return packet; /* some packet that we don't recognize as a fragment */
|
|
+ }
|
|
+
|
|
+ hash %= REASM_IP_HASH_SIZE;
|
|
+ struct reasm_ip_entry *entry = reasm->table[hash];
|
|
+ while (entry != NULL && (proto != entry->protocol || !reasm_id_equal (proto, &id, &entry->id)))
|
|
+ entry = entry->next;
|
|
+
|
|
+ if (entry == NULL) {
|
|
+ entry = malloc (sizeof (*entry));
|
|
+ if (entry == NULL) {
|
|
+ free (frag);
|
|
+ abort ();
|
|
+ }
|
|
+
|
|
+ struct reasm_frag_entry *list_head = malloc (sizeof (*list_head));
|
|
+ if (list_head == NULL) {
|
|
+ free (frag);
|
|
+ free (entry);
|
|
+ abort ();
|
|
+ }
|
|
+
|
|
+ *entry = (struct reasm_ip_entry) {
|
|
+ .id = id,
|
|
+ .len = 0,
|
|
+ .holes = 1,
|
|
+ .frags = list_head,
|
|
+ .hash = hash,
|
|
+ .protocol = proto,
|
|
+ .timeout = timestamp + reasm->timeout,
|
|
+ .state = STATE_ACTIVE,
|
|
+ .prev = NULL,
|
|
+ .next = reasm->table[hash],
|
|
+ .time_prev = reasm->time_last,
|
|
+ .time_next = NULL,
|
|
+ };
|
|
+
|
|
+ *list_head = (struct reasm_frag_entry) {
|
|
+ .len = 0,
|
|
+ .offset = 0,
|
|
+ .data_offset = 0,
|
|
+ .data = NULL,
|
|
+ };
|
|
+
|
|
+ if (entry->next != NULL)
|
|
+ entry->next->prev = entry;
|
|
+ reasm->table[hash] = entry;
|
|
+
|
|
+ if (reasm->time_last != NULL)
|
|
+ reasm->time_last->time_next = entry;
|
|
+ else
|
|
+ reasm->time_first = entry;
|
|
+ reasm->time_last = entry;
|
|
+
|
|
+ reasm->waiting++;
|
|
+ if (reasm->waiting > reasm->max_waiting)
|
|
+ reasm->max_waiting = reasm->waiting;
|
|
+ }
|
|
+
|
|
+ if (entry->state != STATE_ACTIVE) {
|
|
+ reasm->dropped_frags++;
|
|
+ return NULL;
|
|
+ }
|
|
+
|
|
+ if (!add_fragment (entry, frag, last_frag)) {
|
|
+ entry->state = STATE_INVALID;
|
|
+ reasm->dropped_frags += entry->frag_count + 1;
|
|
+ return NULL;
|
|
+ }
|
|
+
|
|
+ if (!is_complete (entry))
|
|
+ return NULL;
|
|
+
|
|
+ unsigned char *r = assemble (entry, output_len);
|
|
+ drop_entry (reasm, entry);
|
|
+ return r;
|
|
+}
|
|
+
|
|
+
|
|
+static bool
|
|
+add_fragment (struct reasm_ip_entry *entry, struct reasm_frag_entry *frag, bool last_frag)
|
|
+{
|
|
+ /*
|
|
+ * When a fragment is inserted into the list, different cases can occur
|
|
+ * concerning the number of holes.
|
|
+ * - The new fragment can be inserted in the middle of a hole, such that
|
|
+ * it will split the hole in two. The number of holes increases by 1.
|
|
+ * - The new fragment can be attached to one end of a hole, such that
|
|
+ * the rest of the hole remains at the opposite side of the fragment.
|
|
+ * The number of holes remains constant.
|
|
+ * - The new fragment can fill a hole completely. The number of holes
|
|
+ * decreases by 1.
|
|
+ */
|
|
+
|
|
+ /*
|
|
+ * If more fragments follow and the payload size is not an integer
|
|
+ * multiple of 8, the packet will never be reassembled completely.
|
|
+ */
|
|
+ if (!last_frag && (frag->len & 7) != 0)
|
|
+ return false;
|
|
+
|
|
+ if (entry->len != 0 && frag->len + frag->offset > entry->len)
|
|
+ return false; /* fragment extends past end of packet */
|
|
+
|
|
+ bool fit_left = false, fit_right = false;
|
|
+
|
|
+ if (last_frag) {
|
|
+ if (entry->len != 0) {
|
|
+ fprintf (stderr, "* ERROR: Multiple final fragments.\n");
|
|
+ return false;
|
|
+ }
|
|
+ entry->len = frag->offset + frag->len;
|
|
+ fit_right = true;
|
|
+ }
|
|
+
|
|
+ struct reasm_frag_entry *cur = entry->frags, *next = cur->next;
|
|
+
|
|
+ while (cur->next != NULL && cur->next->offset <= frag->offset)
|
|
+ cur = cur->next;
|
|
+ next = cur->next;
|
|
+
|
|
+ /* Fragment is to be inserted between cur and next; next may be NULL. */
|
|
+
|
|
+ /* Overlap checks. */
|
|
+ if (cur->offset + cur->len > frag->offset)
|
|
+ return false; /* overlaps with cur */
|
|
+ else if (cur->offset + cur->len == frag->offset)
|
|
+ fit_left = true;
|
|
+
|
|
+ if (next != NULL) {
|
|
+ if (last_frag)
|
|
+ return false; /* next extends past end of packet */
|
|
+ if (frag->offset + frag->len > next->offset)
|
|
+ return false; /* overlaps with next */
|
|
+ else if (frag->offset + frag->len == next->offset)
|
|
+ fit_right = true;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Everything's fine, insert it.
|
|
+ */
|
|
+ if (frag->len != 0) {
|
|
+ frag->next = cur->next;
|
|
+ cur->next = frag;
|
|
+
|
|
+ if (fit_left && fit_right)
|
|
+ entry->holes--;
|
|
+ else if (!fit_left && !fit_right)
|
|
+ entry->holes++;
|
|
+
|
|
+ entry->frag_count++;
|
|
+ } else {
|
|
+ /*
|
|
+ * If the fragment has zero size, we don't insert it into the list,
|
|
+ * but one case remains to be handled: If the zero-size fragment
|
|
+ * is the last fragment, and fits exactly with the fragment to its
|
|
+ * left, the number of holes decreases.
|
|
+ */
|
|
+ if (last_frag && fit_left)
|
|
+ entry->holes--;
|
|
+ }
|
|
+
|
|
+
|
|
+ return true;
|
|
+}
|
|
+
|
|
+
|
|
+struct reasm_ip *
|
|
+reasm_ip_new (void)
|
|
+{
|
|
+ struct reasm_ip *reasm = malloc (sizeof (*reasm));
|
|
+ if (reasm == NULL)
|
|
+ return NULL;
|
|
+
|
|
+ memset (reasm, 0, sizeof (*reasm));
|
|
+ return reasm;
|
|
+}
|
|
+
|
|
+
|
|
+void
|
|
+reasm_ip_free (struct reasm_ip *reasm)
|
|
+{
|
|
+ while (reasm->time_first != NULL)
|
|
+ drop_entry (reasm, reasm->time_first);
|
|
+ free (reasm);
|
|
+}
|
|
+
|
|
+
|
|
+static bool
|
|
+is_complete (struct reasm_ip_entry *entry)
|
|
+{
|
|
+ return entry->holes == 0;
|
|
+}
|
|
+
|
|
+
|
|
+static unsigned char *
|
|
+assemble (struct reasm_ip_entry *entry, unsigned *output_len)
|
|
+{
|
|
+ struct reasm_frag_entry *frag = entry->frags->next; /* skip list head */
|
|
+ unsigned offset0 = frag->data_offset;
|
|
+ unsigned char *p = malloc (entry->len + offset0);
|
|
+ if (p == NULL)
|
|
+ abort ();
|
|
+
|
|
+ switch (entry->protocol) {
|
|
+ case PROTO_IPV4:
|
|
+ break;
|
|
+
|
|
+#if USE_IPv6
|
|
+ case PROTO_IPV6:
|
|
+ offset0 -= 8; /* size of frag header */
|
|
+ break;
|
|
+#endif /* USE_IPv6 */
|
|
+
|
|
+ default:
|
|
+ abort ();
|
|
+ }
|
|
+
|
|
+ *output_len = entry->len + offset0;
|
|
+
|
|
+ /* copy the (unfragmentable) header from the first fragment received */
|
|
+ memcpy (p, frag->data, offset0);
|
|
+
|
|
+ /* join all the payload fragments together */
|
|
+ while (frag != NULL) {
|
|
+ memcpy (p + offset0 + frag->offset, frag->data + frag->data_offset, frag->len);
|
|
+ frag = frag->next;
|
|
+ }
|
|
+
|
|
+ /* some cleanups, e.g. update the length field of reassembled packet */
|
|
+ switch (entry->protocol) {
|
|
+ case PROTO_IPV4: {
|
|
+ struct ip *ip_header = (struct ip *) p;
|
|
+ ip_header->ip_len = htons (offset0 + entry->len);
|
|
+ ip_header->ip_off = 0;
|
|
+ // XXX recompute the checksum
|
|
+ break;
|
|
+ }
|
|
+
|
|
+#if USE_IPv6
|
|
+ case PROTO_IPV6: {
|
|
+ struct ip6_hdr *ip6_header = (struct ip6_hdr *) p;
|
|
+ ip6_header->ip6_plen = htons (offset0 + entry->len - 40);
|
|
+ break;
|
|
+ }
|
|
+#endif /* USE_IPv6 */
|
|
+
|
|
+ default:
|
|
+ abort ();
|
|
+ }
|
|
+
|
|
+ return p;
|
|
+}
|
|
+
|
|
+
|
|
+static void
|
|
+drop_entry (struct reasm_ip *reasm, struct reasm_ip_entry *entry)
|
|
+{
|
|
+ if (entry->prev != NULL)
|
|
+ entry->prev->next = entry->next;
|
|
+ else
|
|
+ reasm->table[entry->hash] = entry->next;
|
|
+
|
|
+ if (entry->next != NULL)
|
|
+ entry->next->prev = entry->prev;
|
|
+
|
|
+ if (entry->time_prev != NULL)
|
|
+ entry->time_prev->time_next = entry->time_next;
|
|
+ else
|
|
+ reasm->time_first = entry->time_next;
|
|
+
|
|
+ if (entry->time_next != NULL)
|
|
+ entry->time_next->time_prev = entry->time_prev;
|
|
+ else
|
|
+ reasm->time_last = entry->time_prev;
|
|
+
|
|
+ reasm->waiting--;
|
|
+
|
|
+ free_entry (entry);
|
|
+}
|
|
+
|
|
+
|
|
+static void
|
|
+free_entry (struct reasm_ip_entry *entry)
|
|
+{
|
|
+ struct reasm_frag_entry *frag = entry->frags, *next;
|
|
+ while (frag != NULL) {
|
|
+ next = frag->next;
|
|
+ if (frag->data != NULL)
|
|
+ free (frag->data);
|
|
+ free (frag);
|
|
+ frag = next;
|
|
+ }
|
|
+
|
|
+ free (entry);
|
|
+}
|
|
+
|
|
+
|
|
+unsigned
|
|
+reasm_ip_waiting (const struct reasm_ip *reasm)
|
|
+{
|
|
+ return reasm->waiting;
|
|
+}
|
|
+
|
|
+
|
|
+unsigned
|
|
+reasm_ip_max_waiting (const struct reasm_ip *reasm)
|
|
+{
|
|
+ return reasm->max_waiting;
|
|
+}
|
|
+
|
|
+
|
|
+unsigned
|
|
+reasm_ip_timed_out (const struct reasm_ip *reasm)
|
|
+{
|
|
+ return reasm->timed_out;
|
|
+}
|
|
+
|
|
+
|
|
+unsigned
|
|
+reasm_ip_dropped_frags (const struct reasm_ip *reasm)
|
|
+{
|
|
+ return reasm->dropped_frags;
|
|
+}
|
|
+
|
|
+
|
|
+bool
|
|
+reasm_ip_set_timeout (struct reasm_ip *reasm, reasm_time_t timeout)
|
|
+{
|
|
+ if (reasm->time_first != NULL)
|
|
+ return false;
|
|
+
|
|
+ reasm->timeout = timeout;
|
|
+ return true;
|
|
+}
|
|
+
|
|
+
|
|
+static void
|
|
+process_timeouts (struct reasm_ip *reasm, reasm_time_t now)
|
|
+{
|
|
+ while (reasm->time_first != NULL && reasm->time_first->timeout < now) {
|
|
+ reasm->timed_out++;
|
|
+ drop_entry (reasm, reasm->time_first);
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+#if USE_IPv6
|
|
+static struct reasm_frag_entry *
|
|
+frag_from_ipv6 (unsigned char *packet, uint32_t *ip_id, bool *last_frag)
|
|
+{
|
|
+ struct ip6_hdr *ip6_header = (struct ip6_hdr *) packet;
|
|
+ unsigned offset = 40; /* IPv6 header size */
|
|
+ uint8_t nxt = ip6_header->ip6_nxt;
|
|
+ unsigned total_len = 40 + ntohs (ip6_header->ip6_plen);
|
|
+ unsigned last_nxt = offsetof (struct ip6_hdr, ip6_nxt);
|
|
+
|
|
+ /*
|
|
+ * IPv6 extension headers from RFC 2460:
|
|
+ * 0 Hop-by-Hop Options
|
|
+ * 43 Routing
|
|
+ * 44 Fragment
|
|
+ * 60 Destination Options
|
|
+ *
|
|
+ * We look out for the Fragment header; the other 3 header
|
|
+ * types listed above are recognized and considered safe to
|
|
+ * skip over if they occur before the Fragment header.
|
|
+ * Any unrecognized header will cause processing to stop and
|
|
+ * a subsequent Fragment header to stay unrecognized.
|
|
+ */
|
|
+ while (nxt == IPPROTO_HOPOPTS || nxt == IPPROTO_ROUTING || nxt == IPPROTO_DSTOPTS) {
|
|
+ if (offset + 2 > total_len)
|
|
+ return NULL; /* header extends past end of packet */
|
|
+
|
|
+ unsigned exthdr_len = 8 + 8 * packet[offset + 1];
|
|
+ if (offset + exthdr_len > total_len)
|
|
+ return NULL; /* header extends past end of packet */
|
|
+
|
|
+ nxt = packet[offset];
|
|
+ last_nxt = offset;
|
|
+ offset += exthdr_len;
|
|
+ }
|
|
+
|
|
+ if (nxt != IPPROTO_FRAGMENT)
|
|
+ return NULL;
|
|
+
|
|
+ if (offset + 8 > total_len)
|
|
+ return NULL; /* Fragment header extends past end of packet */
|
|
+
|
|
+ struct reasm_frag_entry *frag = malloc (sizeof (*frag));
|
|
+ if (frag == NULL)
|
|
+ abort ();
|
|
+
|
|
+ struct ip6_frag *frag_header = (struct ip6_frag *) (packet + offset);
|
|
+ offset += 8;
|
|
+
|
|
+ /*
|
|
+ * The Fragment header will be removed on reassembly, so we have to
|
|
+ * replace the Next Header field of the previous header (which is
|
|
+ * currently IPPROTO_FRAGMENT), with the Next Header field of the
|
|
+ * Fragment header.
|
|
+ *
|
|
+ * XXX We really shouldn't manipulate the input packet in-place.
|
|
+ */
|
|
+ packet[last_nxt] = frag_header->ip6f_nxt;
|
|
+
|
|
+ *frag = (struct reasm_frag_entry) {
|
|
+ .len = total_len - offset,
|
|
+ .data_offset = offset,
|
|
+ .offset = ntohs (frag_header->ip6f_offlg & IP6F_OFF_MASK),
|
|
+ .data = packet,
|
|
+ };
|
|
+
|
|
+ *ip_id = ntohl (frag_header->ip6f_ident);
|
|
+ *last_frag = (frag_header->ip6f_offlg & IP6F_MORE_FRAG) == 0;
|
|
+
|
|
+ return frag;
|
|
+}
|
|
+#endif /* USE_IPv6 */
|
|
+
|
|
+
|
|
+static bool
|
|
+reasm_id_equal (enum reasm_proto proto, const union reasm_id *left, const union reasm_id *right)
|
|
+{
|
|
+ switch (proto) {
|
|
+ case PROTO_IPV4:
|
|
+ return memcmp (left->ipv4.ip_src, right->ipv4.ip_src, 4) == 0
|
|
+ && memcmp (left->ipv4.ip_dst, right->ipv4.ip_dst, 4) == 0
|
|
+ && left->ipv4.ip_id == right->ipv4.ip_id
|
|
+ && left->ipv4.ip_proto == right->ipv4.ip_proto;
|
|
+#if USE_IPv6
|
|
+ case PROTO_IPV6:
|
|
+ return memcmp (left->ipv6.ip_src, right->ipv6.ip_src, 16) == 0
|
|
+ && memcmp (left->ipv6.ip_dst, right->ipv6.ip_dst, 16) == 0
|
|
+ && left->ipv6.ip_id == right->ipv6.ip_id;
|
|
+#endif /* USE_IPv6 */
|
|
+ default:
|
|
+ abort ();
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+static struct reasm_frag_entry *
|
|
+parse_packet (unsigned char *packet, unsigned len, enum reasm_proto *protocol, union reasm_id *id, unsigned *hash, bool *last_frag)
|
|
+{
|
|
+ struct ip *ip_header = (struct ip *) packet;
|
|
+ struct reasm_frag_entry *frag = NULL;
|
|
+
|
|
+ switch (ip_header->ip_v) {
|
|
+ case 4: {
|
|
+ *protocol = PROTO_IPV4;
|
|
+ uint16_t offset = ntohs (ip_header->ip_off);
|
|
+ if (len >= ntohs (ip_header->ip_len) && (offset & (IP_MF | IP_OFFMASK)) != 0) {
|
|
+ frag = malloc (sizeof (*frag));
|
|
+ if (frag == NULL)
|
|
+ abort ();
|
|
+
|
|
+ *frag = (struct reasm_frag_entry) {
|
|
+ .len = ntohs (ip_header->ip_len) - ip_header->ip_hl * 4,
|
|
+ .offset = (offset & IP_OFFMASK) * 8,
|
|
+ .data_offset = ip_header->ip_hl * 4,
|
|
+ .data = packet,
|
|
+ };
|
|
+
|
|
+ *last_frag = (offset & IP_MF) == 0;
|
|
+
|
|
+ memcpy (id->ipv4.ip_src, &ip_header->ip_src, 4);
|
|
+ memcpy (id->ipv4.ip_dst, &ip_header->ip_dst, 4);
|
|
+ id->ipv4.ip_id = ntohs (ip_header->ip_id);
|
|
+ id->ipv4.ip_proto = ip_header->ip_p;
|
|
+
|
|
+ *hash = reasm_ipv4_hash (&id->ipv4);
|
|
+ }
|
|
+ break;
|
|
+ }
|
|
+
|
|
+#if USE_IPv6
|
|
+ case 6: {
|
|
+ struct ip6_hdr *ip6_header = (struct ip6_hdr *) packet;
|
|
+ *protocol = PROTO_IPV6;
|
|
+ if (len >= ntohs (ip6_header->ip6_plen) + 40)
|
|
+ frag = frag_from_ipv6 (packet, &id->ipv6.ip_id, last_frag);
|
|
+ if (frag != NULL) {
|
|
+ memcpy (id->ipv6.ip_src, &ip6_header->ip6_src, 16);
|
|
+ memcpy (id->ipv6.ip_dst, &ip6_header->ip6_dst, 16);
|
|
+ *hash = reasm_ipv6_hash (&id->ipv6);
|
|
+ }
|
|
+ break;
|
|
+ }
|
|
+#endif /* USE_IPv6 */
|
|
+
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ return frag;
|
|
+}
|
|
diff -urN ngrep-1.45/ipreasm.h ngrep-1.45-reasm/ipreasm.h
|
|
--- ngrep-1.45/ipreasm.h 1970-01-01 01:00:00.000000000 +0100
|
|
+++ ngrep-1.45-reasm/ipreasm.h 2007-06-16 19:17:20.124795623 +0200
|
|
@@ -0,0 +1,57 @@
|
|
+#ifndef _IPREASM_H
|
|
+#define _IPREASM_H
|
|
+
|
|
+#include <stdbool.h>
|
|
+
|
|
+#include <pcap.h>
|
|
+
|
|
+
|
|
+/*
|
|
+ * This is an abstract time stamp. ipreasm doesn't care whether it is
|
|
+ * in seconds, milliseconds, or nanodecades. All it does it add the
|
|
+ * configured timeout value to it, and then compare it to the timstamps
|
|
+ * of subsequent packets to decide whether a fragment has expired.
|
|
+ */
|
|
+typedef uint64_t reasm_time_t;
|
|
+
|
|
+struct reasm_ip;
|
|
+
|
|
+/*
|
|
+ * Functions to create and destroy the reassembly environment.
|
|
+ */
|
|
+struct reasm_ip *reasm_ip_new (void);
|
|
+void reasm_ip_free (struct reasm_ip *reasm);
|
|
+
|
|
+/*
|
|
+ * This is the main packet processing function. It inputs one packet,
|
|
+ * and MAY output one packet in turn. If the input was not a fragment,
|
|
+ * it is passed unmodified. If the input was a fragment that completed
|
|
+ * reassembly of a packet, the reassembled packet is output.
|
|
+ * If more fragments are required for reassembly, or the input packet
|
|
+ * is invalid for some reason, a NULL pointer is returned.
|
|
+ *
|
|
+ * The input must be a pointer allocated by malloc(). The output will
|
|
+ * be a pointer allocated by malloc().
|
|
+ *
|
|
+ * Note that in the case of an IPv6 fragment, the input buffer will be
|
|
+ * modified in-place. This is considered a bug and should be fixed in
|
|
+ * the future.
|
|
+ */
|
|
+unsigned char *reasm_ip_next (struct reasm_ip *reasm, unsigned char *packet, unsigned len, reasm_time_t timestamp, unsigned *output_len);
|
|
+
|
|
+/*
|
|
+ * Set the timeout after which a noncompleted reassembly expires, in
|
|
+ * abstract time units (see above for the definition of reasm_time_t).
|
|
+ */
|
|
+bool reasm_ip_set_timeout (struct reasm_ip *reasm, reasm_time_t timeout);
|
|
+
|
|
+/*
|
|
+ * Query certain information about the current state.
|
|
+ */
|
|
+unsigned reasm_ip_waiting (const struct reasm_ip *reasm);
|
|
+unsigned reasm_ip_max_waiting (const struct reasm_ip *reasm);
|
|
+unsigned reasm_ip_timed_out (const struct reasm_ip *reasm);
|
|
+unsigned reasm_ip_dropped_frags (const struct reasm_ip *reasm);
|
|
+
|
|
+
|
|
+#endif /* _IPREASM_H */
|
|
diff -urN ngrep-1.45/Makefile.in ngrep-1.45-reasm/Makefile.in
|
|
--- ngrep-1.45/Makefile.in 2006-11-28 14:35:37.000000000 +0100
|
|
+++ ngrep-1.45-reasm/Makefile.in 2007-06-16 15:41:22.859876074 +0200
|
|
@@ -15,8 +15,8 @@
|
|
|
|
STRIPFLAG=@STRIPFLAG@
|
|
|
|
-SRC=ngrep.c
|
|
-OBJS=ngrep.o
|
|
+SRC=ngrep.c ipreasm.c
|
|
+OBJS=ngrep.o ipreasm.o
|
|
TARGET=ngrep
|
|
MANPAGE=ngrep.8
|
|
|
|
@@ -65,7 +65,7 @@
|
|
$(REGEX_OBJS): $(REGEX_OBJS:.o=.c) $(REGEX_DIR)/*.h
|
|
$(MAKE) $(MAKEFLAGS) -C $(REGEX_DIR) $(notdir $(REGEX_OBJS))
|
|
|
|
-$(OBJS): Makefile ngrep.c ngrep.h
|
|
+$(OBJS): Makefile ngrep.c ngrep.h ipreasm.c ipreasm.h
|
|
|
|
tardist:
|
|
@( VERSION=`perl -ne '/VERSION\s+"(.*)"/ && print "$$1\n"' ngrep.h` ; \
|
|
diff -urN ngrep-1.45/ngrep.c ngrep-1.45-reasm/ngrep.c
|
|
--- ngrep-1.45/ngrep.c 2006-11-28 14:38:43.000000000 +0100
|
|
+++ ngrep-1.45-reasm/ngrep.c 2007-06-16 21:41:43.142117150 +0200
|
|
@@ -98,6 +98,7 @@
|
|
#endif
|
|
|
|
#include "ngrep.h"
|
|
+#include "ipreasm.h"
|
|
|
|
|
|
static char rcsver[] = "$Revision: 1.93 $";
|
|
@@ -156,7 +157,7 @@
|
|
uint8_t link_offset;
|
|
uint8_t radiotap_present = 0;
|
|
|
|
-pcap_t *pd = NULL;
|
|
+pcap_t *pd = NULL, *pd_dumppcap = NULL;
|
|
pcap_dumper_t *pd_dump = NULL;
|
|
struct bpf_program pcapfilter;
|
|
struct in_addr net, mask;
|
|
@@ -183,6 +184,12 @@
|
|
uint32_t ws_row, ws_col = 80, ws_col_forced = 0;
|
|
|
|
|
|
+/*
|
|
+ * Reassembly
|
|
+ */
|
|
+struct reasm_ip *reasm = NULL;
|
|
+
|
|
+
|
|
int main(int argc, char **argv) {
|
|
int32_t c;
|
|
|
|
@@ -195,7 +202,7 @@
|
|
signal(SIGWINCH, update_windowsize);
|
|
#endif
|
|
|
|
- while ((c = getopt(argc, argv, "LNhXViwqpevxlDtTRMs:n:c:d:A:I:O:S:P:F:W:")) != EOF) {
|
|
+ while ((c = getopt(argc, argv, "LNhXViwqpevxlDtTrRMs:n:c:d:A:I:O:S:P:F:W:")) != EOF) {
|
|
switch (c) {
|
|
case 'W': {
|
|
if (!strcasecmp(optarg, "normal"))
|
|
@@ -260,6 +267,10 @@
|
|
case 'M':
|
|
re_multiline_match = 0;
|
|
break;
|
|
+ case 'r':
|
|
+ reasm = reasm_ip_new ();
|
|
+ reasm_ip_set_timeout (reasm, 30000000);
|
|
+ break;
|
|
case 'R':
|
|
dont_dropprivs = 1;
|
|
break;
|
|
@@ -585,7 +596,15 @@
|
|
}
|
|
|
|
if (dump_file) {
|
|
- if (!(pd_dump = pcap_dump_open(pd, dump_file))) {
|
|
+ if (reasm != NULL) {
|
|
+ if (!(pd_dumppcap = pcap_open_dead(DLT_RAW, 65535))) {
|
|
+ fprintf(stderr, "fatal: pcap_open_dead failed\n");
|
|
+ clean_exit(-1);
|
|
+ }
|
|
+ pd_dump = pcap_dump_open(pd_dumppcap, dump_file);
|
|
+ } else
|
|
+ pd_dump = pcap_dump_open(pd, dump_file);
|
|
+ if (!pd_dump) {
|
|
fprintf(stderr, "fatal: %s\n", pcap_geterr(pd));
|
|
clean_exit(-1);
|
|
} else printf("output: %s\n", dump_file);
|
|
@@ -641,6 +660,23 @@
|
|
}
|
|
#endif
|
|
|
|
+ if (reasm != NULL) {
|
|
+ unsigned new_len;
|
|
+ u_char *new_p = malloc(len - link_offset);
|
|
+ memcpy(new_p, ip4_pkt, len - link_offset);
|
|
+ p = reasm_ip_next(reasm, new_p, len - link_offset, (reasm_time_t) 1000000UL * h->ts.tv_sec + h->ts.tv_usec, &new_len);
|
|
+ if (p == NULL)
|
|
+ return;
|
|
+ len = new_len + link_offset;
|
|
+ h->len = new_len;
|
|
+ h->caplen = new_len;
|
|
+
|
|
+ ip4_pkt = (struct ip *) p;
|
|
+#if USE_IPv6
|
|
+ ip6_pkt = (struct ip6_hdr*)p;
|
|
+#endif
|
|
+ }
|
|
+
|
|
ip_ver = ip4_pkt->ip_v;
|
|
|
|
switch (ip_ver) {
|
|
@@ -802,6 +838,9 @@
|
|
|
|
if (match_after && keep_matching)
|
|
keep_matching--;
|
|
+
|
|
+ if (reasm != NULL)
|
|
+ free(p);
|
|
}
|
|
|
|
void dump_packet(struct pcap_pkthdr *h, u_char *p, uint8_t proto, unsigned char *data, uint32_t len,
|
|
@@ -1242,6 +1281,7 @@
|
|
" -s is set the bpf caplen\n"
|
|
" -S is set the limitlen on matched packets\n"
|
|
" -W is set the dump format (normal, byline, single, none)\n"
|
|
+ " -r is reassemble any fragmented IPV4 or IPV6 packets\n"
|
|
" -c is force the column width to the specified size\n"
|
|
" -P is set the non-printable display char to what is specified\n"
|
|
" -F is read the bpf filter from the specified file\n"
|
|
@@ -1292,8 +1331,9 @@
|
|
&& pd && !pcap_stats(pd, &s))
|
|
printf("%u received, %u dropped\n", s.ps_recv, s.ps_drop);
|
|
|
|
- if (pd) pcap_close(pd);
|
|
- if (pd_dump) pcap_dump_close(pd_dump);
|
|
+ if (pd) pcap_close(pd);
|
|
+ if (pd_dumppcap) pcap_close(pd_dumppcap);
|
|
+ if (pd_dump) pcap_dump_close(pd_dump);
|
|
|
|
#if defined(_WIN32)
|
|
if (delay_socket) closesocket(delay_socket);
|
|
@@ -1301,6 +1341,9 @@
|
|
if (usedev) free(usedev);
|
|
#endif
|
|
|
|
+ if (reasm != NULL)
|
|
+ reasm_ip_free(reasm);
|
|
+
|
|
exit(sig);
|
|
}
|
|
|