/* Copyright (C) 2000 - 2006 Christian Kreibich . 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 #endif #include #include #include #include #include #include #ifdef LINUX #define __FAVOR_BSD #endif #include #include #include #include #include #include #include #include #include #include #include #include #include static GList *observers; /* GList */ 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); }