/*
Copyright (C) 2000 - 2006 Christian Kreibich <christian@whoop.org>.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies of the Software and its documentation and acknowledgment shall be
given in the documentation and software packages that this Software was
used.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <errno.h>
#ifdef LINUX
#define __FAVOR_BSD
#endif
#include <pcapnav.h>
#include <libnd_debug.h>
#include <libnd_macros.h>
#include <libnd_globals.h>
#include <libnd_trace.h>
#include <libnd_tpm.h>
#include <libnd_tp.h>
#include <libnd_packet_recycler.h>
#include <libnd_protocol.h>
#include <libnd_protocol_registry.h>
#include <libnd_protocol_inst.h>
#include <libnd_raw_protocol.h>
#include <libnd_packet.h>
static GList *observers; /* GList<LND_PacketObserver> */
static void
packet_free_proto_data(gpointer data, gpointer user_data)
{
LND_ProtoData *pd = (LND_ProtoData *) data;
if (!pd)
return;
libnd_proto_data_free(pd);
return;
TOUCH(user_data);
}
static void
packet_clear_proto_flag(gpointer data, gpointer user_data)
{
LND_ProtoData *pd = (LND_ProtoData *) data;
LND_Packet *packet = (LND_Packet *) user_data;
if (!pd || !packet)
return;
packet->protocols &= ~(pd->inst.proto->id);
}
static void
packet_free(LND_Packet *packet)
{
if (!packet)
return;
libnd_packet_cleanup(packet);
if (! libnd_prec_put(packet))
{
g_free(packet->data);
g_free(packet);
}
}
void
libnd_packet_remove(LND_Packet *packet)
{
D_ENTER;
if (!packet)
D_RETURN;
libnd_packet_tell_observers(packet, LND_PACKET_DELETE_PRE, NULL);
if (packet->next)
{
if (packet->prev)
{
/* It's a normal packet (not first or last) --> just cut it out */
packet->prev->next = packet->next;
packet->next->prev = packet->prev;
}
else
{
/* It's got a next packet, but no previous one --> first packet */
if (packet->part)
packet->part->pl = packet->next;
packet->next->prev = NULL;
}
}
else if (packet->prev)
{
/* It's got no next one, but a previous one --> last packet */
packet->prev->next = NULL;
if (packet->part)
packet->part->pl_end = packet->prev;
}
else if (packet->part)
{
/* It's the only packet in the trace */
packet->part->pl = NULL;
packet->part->pl_end = NULL;
}
/* This packet may be part of the selection, update accordingly: */
if (packet->sel_next || packet->sel_prev)
{
if (packet->part)
packet->part->sel.size--;
packet->part->sel.last_valid = FALSE;
/* Same pattern as above: */
if (packet->sel_next)
{
if (packet->sel_prev)
{
/* selected before and after -- just cut it out */
packet->sel_prev->sel_next = packet->sel_next;
packet->sel_next->sel_prev = packet->sel_prev;
}
else
{
/* No selections before this one -- new first selected */
if (packet->part)
packet->part->sel.pl = packet->sel_next;
packet->sel_next->sel_prev = NULL;
}
}
else if (packet->sel_prev)
packet->sel_prev->sel_next = NULL;
else if (packet->part)
packet->part->sel.pl = NULL;
}
packet->part->dirty = TRUE;
packet->part->num_packets--;
packet->part->tpm->size -= pcapnav_get_pkthdr_size(packet->part->pcn) + packet->ph.caplen;
packet->part->size -= pcapnav_get_pkthdr_size(packet->part->pcn) + packet->ph.caplen;
libnd_packet_tell_observers(packet, LND_PACKET_DELETE_POST, NULL);
D_RETURN;
}
LND_Packet *
libnd_packet_new(LND_TracePart *tp, guint data_size)
{
LND_Packet *packet;
packet = libnd_prec_get(data_size);
D_ASSERT_PTR(packet);
if (!packet)
return NULL;
packet->ph.caplen = data_size;
packet->part = tp;
return packet;
}
LND_Packet *
libnd_packet_from_pcap(pcap_t *pcap)
{
struct pcap_pkthdr hdr;
const guchar *data;
LND_Packet *packet;
if (! pcap)
return NULL;
if (! (data = pcap_next(pcap, &hdr)))
return NULL;
if (! (packet = libnd_prec_get(hdr.caplen)))
return NULL;
packet->ph.caplen = hdr.caplen;
libnd_packet_set_data(packet, &hdr, data);
return packet;
}
void
libnd_packet_free(LND_Packet *packet)
{
packet_free(packet);
}
int
libnd_packet_cmp(const LND_Packet *packet1,
const LND_Packet *packet2)
{
if (! packet1 || ! packet2)
return 0;
return pcapnav_timeval_cmp(&(packet1->ph.ts),
&(packet1->ph.ts));
}
LND_Trace *
libnd_packet_get_trace(const LND_Packet *packet)
{
if (!packet || !packet->part || !packet->part->tpm)
return NULL;
return packet->part->tpm->trace;
}
void
libnd_packet_dump(const LND_Packet *packet, pcap_dumper_t *dumper)
{
FILE *f = (FILE*) dumper;
if (!packet || !dumper)
return;
/*
* if (fwrite(&packet->ph, sizeof(packet->ph), 1, f) != 1)
* fprintf(stderr, "WARNING: packet dump failed, %s\n", strerror(errno));
*
* if (fwrite(packet->data, packet->ph.caplen, 1, f) != 1)
* fprintf(stderr, "WARNING: packet dump failed, %s\n", strerror(errno));
*/
/* Dammit, pcap doesn't return the outcome of the
* operation! We could run out of diskspace here and
* never know ... big fat FIXME for the future.
*/
pcap_dump((u_char*) dumper, &packet->ph, packet->data);
}
void
libnd_packet_set_data(LND_Packet *packet,
const struct pcap_pkthdr *hdr,
const guchar *data)
{
guint len;
if (!packet || !data || !hdr)
return;
len = MIN(hdr->caplen, packet->ph.caplen);
memcpy(packet->data, data, len);
packet->ph = *hdr;
}
LND_Packet*
libnd_packet_duplicate(LND_Packet *p)
{
LND_Packet *copy;
LND_ProtoData *pd_old, *pd_new;
GList *l;
if (!p)
return (NULL);
/* copy = libnd_packet_new(p->part, p->ph.caplen);*/
copy = libnd_packet_new(NULL, p->ph.caplen);
copy->ph = p->ph;
copy->protocols = p->protocols;
/* Avoid pointer havoc -- this packet does not at first
belong to any packet lists! */
copy->prev = copy->next = copy->sel_prev = copy->sel_next = NULL;
/* Copy the packet data and hook it into the right place --
* memory for the payload is already allocated.
*/
memcpy(copy->data, p->data, p->ph.caplen);
/* Now make sure the offsets of the upper layers are correct. */
for (l = p->pd; l; l = g_list_next(l))
{
pd_old = (LND_ProtoData*) l->data;
pd_new =
libnd_proto_data_new(pd_old->inst.proto, pd_old->inst.nesting,
copy->data + (pd_old->data - p->data),
copy->data + (pd_old->data_end - p->data));
copy->pd = g_list_append(copy->pd, pd_new);
}
return copy;
}
static void
packet_init(LND_Packet *packet, pcap_t * pcap)
{
int type;
LND_Protocol *proto;
LND_Trace *trace;
D_ENTER;
if (!packet || !pcap)
{
D(("Input error, packet: %p, pcap: %p\n", packet, pcap));
D_RETURN;
}
libnd_packet_cleanup(packet);
/* Check what we have at the link layer and handle things off
* to the according protocol. The rest is up to them.
*/
type = pcap_datalink(pcap);
switch (type)
{
case DLT_NULL:
/* We'll assume it's IP for now */
case DLT_RAW:
proto = libnd_proto_registry_find(LND_PROTO_LAYER_NET, 0x0800);
break;
default:
proto = libnd_proto_registry_find(LND_PROTO_LAYER_LINK, type);
}
if (!proto)
proto = libnd_raw_proto_get();
/* Start initializing the packet using the protocol we've just
* determined. Each protocol decides what protocol to use next,
* if applicable.
*/
proto->init_packet(packet, packet->data, libnd_packet_get_end(packet));
/* Depending on our current filter configuration, mark this packet
* as filtered or not. Only applicable if the packet belongs to trace,
* as the trace defines the filtering configuration.
*/
if ( (trace = libnd_packet_get_trace(packet)))
libnd_filter_list_apply(trace->filters, packet, trace->filter_mode);
/* Finally inform anyone interested that we've initialized this packet. */
libnd_packet_tell_observers(packet, LND_PACKET_INITIALIZED, NULL);
D_RETURN;
}
void
libnd_packet_init(LND_Packet *packet)
{
D_ENTER;
if (!packet || !packet->part)
{
D(("Not initializing packet %p\n", packet));
D_RETURN;
}
D_ASSERT_PTR(packet->part);
packet_init(packet, pcapnav_pcap(packet->part->pcn));
D_RETURN;
}
void
libnd_packet_cleanup(LND_Packet *packet)
{
D_ENTER;
if (! packet)
D_RETURN;
/* Clear the index of contained protocols: */
packet->protocols = 0;
/* Remove any existing offsets */
if (packet->pd)
{
g_list_foreach(packet->pd, packet_free_proto_data, NULL);
g_list_free(packet->pd);
packet->pd = NULL;
}
/*
packet->sel_next = packet->sel_prev = NULL;
packet->next = packet->prev = NULL;
*/
packet->filtered = FALSE;
D_RETURN;
}
void
libnd_packet_set_filtered(LND_Packet *packet, gboolean filtered)
{
if (!packet)
return;
D(("Filter state of packet %p: %i\n", packet, filtered));
if (filtered != packet->filtered)
{
packet->filtered = filtered;
libnd_packet_tell_observers(packet, LND_PACKET_VISIBILITY, NULL);
}
}
gboolean
libnd_packet_is_filtered(LND_Packet *packet)
{
if (!packet)
return FALSE;
D(("Packet %p's filter status: %i\n", packet, packet->filtered));
return packet->filtered;
}
void
libnd_packet_init_from_pcap(LND_Packet *p, pcap_t *pcap)
{
D_ENTER;
if (!p || !pcap)
{
D(("Not initializing packet %p", p));
D_RETURN;
}
packet_init(p, pcap);
D_RETURN;
}
void
libnd_packet_update(LND_Packet *packet, LND_Protocol *proto, guint nesting)
{
GList *l, *l2;
LND_ProtoData *pd;
D_ENTER;
if (!packet)
D_RETURN;
if (!proto)
{
libnd_packet_init(packet);
D_RETURN;
}
for (l = packet->pd; l; l = g_list_next(l))
{
pd = (LND_ProtoData *) l->data;
if (pd->inst.proto == proto && pd->inst.nesting == nesting)
break;
}
if (!l)
{
D(("WARNING -- header to update from not found in packet.\n"));
D_RETURN;
}
/* Split off the rest of the previously recognized protocols: */
if (l == packet->pd)
{
/* It is the first header. We can just as well reinitialize
the whole thing. */
libnd_packet_init(packet);
D_RETURN;
}
pd = (LND_ProtoData *) l->data;
/* If there's a previous item, make sure the list ends there */
if ( (l2 = g_list_previous(l)))
l2->next = NULL;
/* Clear protocol flags of all protocols beyond that point */
g_list_foreach(l, packet_clear_proto_flag, packet);
/* Make sure the current data doesn't get
deleted, and clean up the rest. */
l->data = NULL;
g_list_foreach(l, packet_free_proto_data, NULL);
g_list_free(l);
D(("Updating packet from %s/%i onward\n",
pd->inst.proto->name, pd->inst.nesting));
/* Re-initialize packet from this protocol on: */
pd->inst.proto->init_packet(packet, pd->data, pd->data_end);
/* Finally, clean up the single chunk we have excluded above */
libnd_proto_data_free(pd);
libnd_packet_tell_observers(packet, LND_PACKET_UPDATED, NULL);
D_RETURN;
}
guchar *
libnd_packet_get_data(const LND_Packet *packet,
const LND_Protocol *proto,
guint nesting)
{
LND_ProtoData *pd;
GList *l;
if (!packet)
return NULL;
if (!proto)
return packet->data;
if (!libnd_packet_has_proto(packet, proto))
return NULL;
for (l = packet->pd; l; l = g_list_next(l))
{
pd = (LND_ProtoData *) l->data;
if (pd->inst.proto->id == proto->id &&
pd->inst.nesting == nesting)
return pd->data;
}
return NULL;
}
guchar *
libnd_packet_get_data_end(const LND_Packet *packet,
const LND_Protocol *proto,
guint nesting)
{
LND_ProtoData *pd;
GList *l;
if (!packet || !proto || !libnd_packet_has_proto(packet, proto))
return NULL;
for (l = packet->pd; l; l = g_list_next(l))
{
pd = (LND_ProtoData*) l->data;
if (pd->inst.proto->id == proto->id &&
pd->inst.nesting == nesting)
return pd->data_end;
}
return NULL;
}
void
libnd_packet_add_proto_data(LND_Packet *packet, LND_Protocol *proto,
guchar *data, guchar *data_end)
{
guchar *real_end;
LND_ProtoData *pd;
guint nesting = 0;
GList *l;
if (!packet || !proto)
return;
for (l = packet->pd; l; l = g_list_next(l))
{
pd = (LND_ProtoData *) l->data;
if (pd->inst.proto == proto)
nesting++;
if (g_list_next(l) == NULL)
break;
}
/* l is now last list item */
real_end = libnd_packet_get_end(packet);
if (real_end >= data_end)
pd = libnd_proto_data_new(proto, nesting, data, data_end);
else
pd = libnd_proto_data_new(proto, nesting, data, real_end);
D_ASSERT_PTR(pd);
if (!pd)
return;
if (nesting > 0)
{
/* We are nesting this protocol, so we must make sure
that the trace's protocol notebook has enough copies
of this protocol's tab.
*/
D(("Nesting protocol %s, level %i\n", proto->name, nesting));
/* FIXME -- reinvestigate
if (! libnd_trace_get_proto_info(packet->trace, proto, nesting))
libnd_trace_add_proto_tab(packet->trace, proto, nesting);
*/
}
packet->pd = g_list_append(packet->pd, pd);
packet->protocols |= proto->id;
/*
D(("Added proto %s at offset %u, nesting is %i\n",
proto->name, data - packet->data, pd->inst.nesting));
*/
}
LND_ProtoData *
libnd_packet_get_proto_data(const LND_Packet *packet,
const LND_Protocol *proto,
guint nesting)
{
GList *l;
LND_ProtoData *pd;
for (l = packet->pd; l; l = g_list_next(l))
{
pd = (LND_ProtoData *) l->data;
if (pd->inst.proto == proto &&
pd->inst.nesting == nesting)
return pd;
}
return NULL;
}
guchar *
libnd_packet_get_end(const LND_Packet *packet)
{
if (!packet)
return (NULL);
return (packet->data + packet->ph.caplen);
}
gboolean
libnd_packet_has_proto(const LND_Packet *packet,
const LND_Protocol *proto)
{
if (!packet || !proto)
return FALSE;
return ((packet->protocols & proto->id) > 0);
}
gboolean
libnd_packet_has_proto_nested(const LND_Packet *packet,
const LND_Protocol *proto,
guint nesting)
{
LND_ProtoData *pd;
GList *l;
if (!packet || !proto)
return FALSE;
for (l = packet->pd; l; l = g_list_next(l))
{
pd = (LND_ProtoData *) l->data;
if (pd->inst.proto == proto &&
pd->inst.nesting == nesting)
return TRUE;
}
return FALSE;
}
LND_ProtoData *
libnd_packet_get_last_nonraw(const LND_Packet *packet)
{
GList *l;
LND_ProtoData *pd, *pd_last = NULL;
LND_Protocol *raw_proto;
if (!packet)
return NULL;
raw_proto = libnd_raw_proto_get();
if (! libnd_packet_has_proto(packet, raw_proto))
return NULL;
for (l = packet->pd; l; l = g_list_next(l))
{
pd = (LND_ProtoData *) l->data;
if (pd->inst.proto == raw_proto)
return pd_last;
pd_last = pd;
}
return NULL;
}
gboolean
libnd_packet_has_complete_header(const LND_Packet *p,
const LND_Protocol *proto,
guint nesting)
{
if (!p || !proto)
return (FALSE);
if (!libnd_packet_has_proto(p, proto))
return (FALSE);
return (proto->header_complete(p, nesting));
}
gboolean
libnd_packet_is_complete(const LND_Packet *packet)
{
if (!packet)
return FALSE;
/* We return the result of comparing the capture length to
* the real length, but ignoring the uppermost bit, as we use
* that for storing the filtered status.
*/
return (packet->ph.caplen == packet->ph.len);
}
void
libnd_packet_update_proto_state(LND_Packet *packet, int index)
{
GList *l;
LND_ProtoData *pd;
for (l = packet->pd; l; l = g_list_next(l))
{
pd = (LND_ProtoData *) l->data;
if (pd->inst.proto->is_stateful)
pd->inst.proto->update_state(packet, index);
}
}
void
libnd_packet_foreach_proto(LND_Packet *packet,
LND_PacketFunc callback,
void *user_data)
{
LND_ProtoData *pd;
GList *l;
if (!packet || !callback)
return;
for (l = packet->pd; l; l = g_list_next(l))
{
pd = (LND_ProtoData *) l->data;
callback(packet, pd, user_data);
}
}
void
libnd_packet_foreach_proto_backward(LND_Packet *packet,
LND_PacketFunc callback,
void *user_data)
{
LND_ProtoData *pd;
GList *l;
if (!packet || !callback)
return;
for (l = g_list_last(packet->pd); l; l = g_list_previous(l))
{
pd = (LND_ProtoData *) l->data;
callback(packet, pd, user_data);
}
}
void
libnd_packet_modified(LND_Packet *packet)
{
LND_Trace *trace = libnd_packet_get_trace(packet);
if (!packet || !trace)
return;
libnd_trace_set_dirty(trace, TRUE);
libnd_packet_tell_observers(packet, LND_PACKET_MODIFIED, NULL);
}
int
libnd_packet_get_index(const LND_Packet *needle)
{
LND_Packet *p;
int i = 0;
if (!needle)
return -1;
if (!needle->part)
return -1;
p = needle->part->pl;
while (p)
{
if (p == needle)
return i;
i++;
p = p->next;
}
D(("Packet lookup failed!\n"));
return -1;
}
int
libnd_packet_get_proto_nesting(const LND_Packet *packet,
const LND_Protocol *proto,
guchar *data)
{
GList *l;
LND_ProtoData *pd = NULL;
if (!packet || !proto || !data ||
(data < packet->data) ||
(data > packet->data + packet->ph.caplen))
{
D(("Warning -- error in input (%p %p [%p, %p, %p], returning -1\n",
packet, proto, data,
(packet ? packet->data : NULL),
(packet ? packet->data + packet->ph.caplen : NULL)));
return -1;
}
for (l = packet->pd; l; l = g_list_next(l))
{
pd = (LND_ProtoData *) l->data;
if (pd->data > data && g_list_previous(l))
{
pd = (LND_ProtoData *) g_list_previous(l)->data;
/* The nesting we are looking for is in the
previous proto data */
D(("Proto nesting for %s: %i\n",
proto->name, pd->inst.nesting));
return pd->inst.nesting;
}
}
/* If we get here, the data pointer may be pointing into the innermost
protocol's data, but not beyond the end of the packet data.
Check that case.
*/
if (pd && data < packet->data + packet->ph.caplen)
return pd->inst.nesting;
D(("Proto nesting for %s: NOT FOUND\n", proto->name));
return -1;
}
typedef struct nd_fix_data
{
gboolean changed;
} LND_FixData;
static void
packet_fix_cb(LND_Packet *p, LND_ProtoData *pd, void *user_data)
{
LND_FixData *data = (LND_FixData *) user_data;
if (!p || !pd)
return;
/* Fix up the protocol data, depending on how the
* protocol plugin implements that operation.
* If the packet was modified, set a flag to let our
* called know about it.
*/
if (pd->inst.proto->fix_packet(p))
data->changed = TRUE;
}
gboolean
libnd_packet_fix(LND_Packet *packet)
{
LND_FixData data;
if (!packet)
return FALSE;
memset(&data, 0, sizeof(LND_FixData));
/* Iterate over the protocols contained in the packet,
* from inner- to outermost, and call our callback for
* each protocol which will then correct any checksums.
*/
libnd_packet_foreach_proto_backward(packet,
packet_fix_cb,
&data);
libnd_packet_tell_observers(packet, LND_PACKET_FIXED, NULL);
return data.changed;
}
LND_PacketObserver *
libnd_packet_observer_new(void)
{
return g_new0(LND_PacketObserver, 1);
}
void
libnd_packet_observer_free(LND_PacketObserver *ob)
{
g_free(ob);
}
void
libnd_packet_add_observer(LND_PacketObserver *observer)
{
if (!observer)
return;
observers = g_list_prepend(observers, observer);
}
void
libnd_packet_del_observer(LND_PacketObserver *observer)
{
if (!observer)
return;
observers = g_list_remove(observers, observer);
}
void
libnd_packet_tell_observers(LND_Packet *packet, LND_PacketObserverOp op, void *data)
{
LND_Trace *trace;
GList *l;
LND_PacketObserver *ob;
if (!packet)
return;
trace = libnd_packet_get_trace(packet);
if (trace && (trace->packet_observer_blocks & op))
{
D(("Blocked packet op %i, not reporting.\n", op));
return;
}
for (l = observers; l; l = g_list_next(l))
{
ob = (LND_PacketObserver *) l->data;
switch (op)
{
case LND_PACKET_INITIALIZED:
if (ob->packet_initialized)
ob->packet_initialized(packet);
break;
case LND_PACKET_MODIFIED:
if (ob->packet_modified)
ob->packet_modified(packet);
break;
case LND_PACKET_DELETE_PRE:
if (ob->packet_delete_pre)
ob->packet_delete_pre(packet);
break;
case LND_PACKET_DELETE_POST:
if (ob->packet_delete_post)
ob->packet_delete_post(packet);
break;
case LND_PACKET_INSERT_PRE:
if (ob->packet_insert_pre)
ob->packet_insert_pre(packet);
break;
case LND_PACKET_INSERT_POST:
if (ob->packet_insert_post)
ob->packet_insert_post(packet);
break;
case LND_PACKET_DUPLICATED:
if (ob->packet_duplicated)
ob->packet_duplicated(packet);
break;
case LND_PACKET_VISIBILITY:
if (ob->packet_visibility)
ob->packet_visibility(packet);
break;
case LND_PACKET_UPDATED:
if (ob->packet_updated)
ob->packet_updated(packet);
break;
case LND_PACKET_FIXED:
if (ob->packet_fixed)
ob->packet_fixed(packet);
break;
default:
D(("Unknown packet operation\n"));
}
}
return;
TOUCH(data);
}
syntax highlighted by Code2HTML, v. 0.9.1