/* Copyright (C) 2000 - 2003 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 #ifdef LINUX #define __FAVOR_BSD #endif #include #include #include #include #include #include #include #include #include #include #include #include #include void nd_ip_frag_show_dialog(LND_Packet *packet) { ND_FragData *data; struct ip *iphdr; char s[128]; GtkWidget *sb1, *sb2; GtkLabel *label; GtkObject *adj; if (!packet) return; iphdr = (struct ip *) libnd_packet_get_data(packet, nd_ip_get(), 0); if (!iphdr) return; data = g_new0(ND_FragData, 1); D_ASSERT_PTR(data); /* The initial setting is to split the packet approximately in half: */ data->size_orig = ND_IP_PAYLOAD_LENGTH(iphdr); data->size1 = data->size_orig/2 - ((data->size_orig/2) % 8); data->size2 = data->size_orig - data->size1; data->packet = packet; data->dialog = create_ip_frag_dialog(); D_ASSERT_PTR(data->dialog); ND_GTK_GET(sb1, data->dialog, "ip_frag1_spinbutton"); ND_GTK_GET(sb2, data->dialog, "ip_frag2_spinbutton"); gtk_object_set_data_full(GTK_OBJECT(data->dialog), "data", data, (GtkDestroyNotify) g_free); /* Disable callbacks for a moment in order to not trigger the callbacks while we're still only initializing settings */ gtk_signal_handler_block_by_func(GTK_OBJECT(sb1), on_ip_frag1_spinbutton_changed, NULL); gtk_signal_handler_block_by_func(GTK_OBJECT(sb2), on_ip_frag2_spinbutton_changed, NULL); adj = gtk_adjustment_new(0, 0, data->size_orig, 8, 8, 1); gtk_spin_button_set_adjustment(GTK_SPIN_BUTTON(sb1), GTK_ADJUSTMENT(adj)); gtk_spin_button_set_value(GTK_SPIN_BUTTON(sb1), (gfloat) data->size1); adj = gtk_adjustment_new(0, 0, data->size_orig, 8, 8, 1); gtk_spin_button_set_adjustment(GTK_SPIN_BUTTON(sb2), GTK_ADJUSTMENT(adj)); gtk_spin_button_set_value(GTK_SPIN_BUTTON(sb2), (gfloat) data->size2); /* Re-enable callbacks ... */ gtk_signal_handler_unblock_by_func(GTK_OBJECT(sb1), on_ip_frag1_spinbutton_changed, NULL); gtk_signal_handler_unblock_by_func(GTK_OBJECT(sb2), on_ip_frag2_spinbutton_changed, NULL); /* Get the offsets of our fragments from the fragment offset of the packet (maybe it's a fragment already) */ data->offset1 = (ntohs(iphdr->ip_off) & IP_OFFMASK) * 8; data->offset2 = data->offset1 + data->size1; D_ASSERT_PTR(data->dialog); ND_GTK_GET(label, data->dialog, "ip_frag1_offset_label"); g_snprintf(s, 128, "%u", data->offset1); gtk_label_set_text(label, s); ND_GTK_GET(label, data->dialog, "ip_frag2_offset_label"); g_snprintf(s, 128, "%u", data->offset2); gtk_label_set_text(label, s); gtk_widget_show(data->dialog); } void nd_ip_frag_adjust(ND_FragData *data, int fragment_size, int fragment_number) { char s[128]; GtkWidget *sb1, *sb2; GtkLabel *label; if (!data) return; D_ASSERT_PTR(data->dialog); switch (fragment_number) { case 0: data->size1 = fragment_size; data->size2 = data->size_orig - data->size1; break; case 1: data->size2 = fragment_size; data->size1 = data->size_orig - data->size2; break; default: D(("Huh? We only split into two fragments!")); return; } data->offset2 = data->offset1 + data->size1; ND_GTK_GET(sb1, data->dialog, "ip_frag1_spinbutton"); ND_GTK_GET(sb2, data->dialog, "ip_frag2_spinbutton"); /* Disable callbacks for a moment to not trigger callbacks */ gtk_signal_handler_block_by_func(GTK_OBJECT(sb1), on_ip_frag1_spinbutton_changed, NULL); gtk_signal_handler_block_by_func(GTK_OBJECT(sb2), on_ip_frag2_spinbutton_changed, NULL); if (fragment_number == 0) gtk_spin_button_set_value(GTK_SPIN_BUTTON(sb2), (gfloat) data->size2); else gtk_spin_button_set_value(GTK_SPIN_BUTTON(sb1), (gfloat) data->size1); /* Re-enable callbacks ... */ gtk_signal_handler_unblock_by_func(GTK_OBJECT(sb1), on_ip_frag1_spinbutton_changed, NULL); gtk_signal_handler_unblock_by_func(GTK_OBJECT(sb2), on_ip_frag2_spinbutton_changed, NULL); ND_GTK_GET(label, data->dialog, "ip_frag2_offset_label"); g_snprintf(s, 128, "%u", data->offset2); gtk_label_set_text(label, s); } void nd_ip_frag_cancel(ND_FragData *data) { if (!data || !data->dialog) return; gtk_widget_destroy(data->dialog); } void nd_ip_frag_apply(ND_FragData *data) { if (!data || !data->packet) return; nd_ip_frag_fragment(data->packet, data->size1, data->size2); gtk_widget_destroy(data->dialog); } void nd_ip_frag_fragment(LND_Packet *packet, guint size1, guint size2) { LND_Protocol *ip; struct ip *iphdr; LND_Packet *p_copy; guchar *payload_start, *payload_end, *fragment_start, *fragment_end; int remainder_len, offset; if (!packet) return; ip = nd_ip_get(); /* Create the second fragment: */ p_copy = libnd_packet_duplicate(packet); iphdr = (struct ip *) libnd_packet_get_data(p_copy, ip, 0); D_ASSERT_PTR(iphdr); if (!iphdr) { D(("Packet doesn't contain IP\n")); return; } /* Sanity-check input: sum of fragment sizes must be equal to packet payload size: */ if ((size1 + size2) != (guint) (ntohs(iphdr->ip_len) - (iphdr->ip_hl * 4))) { D(("Fragment sizes don't sum up to payload size\n")); return; } /* First fragment size must be multiple of 8: */ if (size1 % 8) { D(("Fragment size is not multiple of 8\n")); return; } offset = (ntohs(iphdr->ip_off) & IP_OFFMASK) * 8 + size1; payload_start = ((guchar *) iphdr) + (iphdr->ip_hl * 4); payload_end = libnd_packet_get_data_end(p_copy, ip, 0); fragment_start = payload_start + size1; remainder_len = libnd_packet_get_end(p_copy) - fragment_start; p_copy->ph.caplen -= size1; p_copy->ph.len -= size1; memmove(payload_start, fragment_start, remainder_len); /* We don't need to adjust IP_MF -- if the original didn't have it set, the copy is now the last fragment and doesn't need it. If it did have it set, there's going to be another fragment after this one anyway. But we need to set the fragment offset: */ iphdr->ip_off = htons((ntohs(iphdr->ip_off) & ~IP_OFFMASK) | ((offset / 8) & IP_OFFMASK)); /* Adjust IP packet size: */ iphdr->ip_len = htons(ntohs(iphdr->ip_len) - size1); /* .. and fix the checksum. */ libnd_ip_fix_packet(p_copy); /* Realloc the packet's memory. All protocol offsets may become invalid at this point, but we re-initialize immediately below: */ p_copy->data = realloc(p_copy->data, p_copy->ph.caplen); /* We've just cut out a part of the packet -- reinitialize the packet: */ libnd_packet_init(p_copy); /* Now adjust the original packet: */ iphdr = (struct ip *) libnd_packet_get_data(packet, ip, 0); fragment_start = ((guchar *) iphdr) + (iphdr->ip_hl * 4); payload_end = libnd_packet_get_data_end(packet, ip, 0); fragment_end = fragment_start + size1; remainder_len = libnd_packet_get_end(packet) - payload_end; packet->ph.caplen -= size2; packet->ph.len -= size2; /* Move anything coming after the IP payload to the end of the fragment: */ if (remainder_len > 0) memmove(fragment_end, payload_end, remainder_len); packet->data = realloc(packet->data, packet->ph.caplen); /* Correct IP fields -- fragmentation flags, IP length, checksum: */ iphdr->ip_off = htons((ntohs(iphdr->ip_off) | IP_MF) & ~IP_DF); iphdr->ip_len = htons(ntohs(iphdr->ip_len) - size2); libnd_ip_fix_packet(packet); /* And reinitialize the whole thing */ libnd_packet_init(packet); libnd_packet_modified(packet); /* Insert new fragment after the original into trace: */ libnd_tp_insert_packets(packet->part, p_copy, libnd_packet_get_index(packet) + 1); } /* Compare function for IP fragment offsets. Returns -1/0/1 when the first packet's fragment offset is smaller/equal/larger than the second's */ static gint ip_off_cmp(gconstpointer d1, gconstpointer d2) { LND_Packet *p1, *p2; struct ip *iphdr1, *iphdr2; p1 = (LND_Packet *) d1; p2 = (LND_Packet *) d2; iphdr1 = (struct ip *) libnd_packet_get_data(p1, nd_ip_get(), 0); D_ASSERT_PTR(iphdr1); iphdr2 = (struct ip *) libnd_packet_get_data(p2, nd_ip_get(), 0); D_ASSERT_PTR(iphdr2); if (ND_IP_FRAG_OFFSET(iphdr1) < ND_IP_FRAG_OFFSET(iphdr2)) return (-1); if (ND_IP_FRAG_OFFSET(iphdr1) > ND_IP_FRAG_OFFSET(iphdr2)) return (1); return (0); } static void ip_frag_reassemble_packet(gpointer key, GList *fraglist, gpointer user_data) { struct ip *base_iphdr, *frag_iphdr; GList *f, *f2, *f3; int payload_increase, trailer_len, covered_offset; LND_Packet *base_packet, *frag_packet, *input_packet; guchar *trailer, *base_payload, *dest; if (!fraglist || (g_list_length(fraglist) == 1)) return; input_packet = (LND_Packet *) user_data; D(("Beginning reassembly.\n")); /* Sort fragments in increasing order by offsets. */ fraglist = g_list_sort(fraglist, ip_off_cmp); for (f = fraglist; f; /* Increment depends on fragments, see below */) { base_packet = (LND_Packet *) f->data; base_iphdr = (struct ip *) libnd_packet_get_data(base_packet, nd_ip_get(), 0); D_ASSERT_PTR(base_iphdr); covered_offset = ND_IP_FRAG_OFFSET(base_iphdr) + ND_IP_PAYLOAD_LENGTH(base_iphdr); payload_increase = 0; f2 = g_list_next(f); D(("Handling fragment of %u at %u\n", ntohs(base_iphdr->ip_id), ND_IP_FRAG_OFFSET(base_iphdr))); while (f2) { frag_iphdr = (struct ip *) libnd_packet_get_data((LND_Packet *) f2->data, nd_ip_get(), 0); if (ND_IP_FRAG_OFFSET(frag_iphdr) > covered_offset) break; D(("One consecutive fragment.\n")); payload_increase += ND_IP_PAYLOAD_LENGTH(frag_iphdr); covered_offset = ND_IP_FRAG_OFFSET(frag_iphdr) + ND_IP_PAYLOAD_LENGTH(frag_iphdr); f2 = g_list_next(f2); } D(("Payload increase in base packet: %i bytes\n", payload_increase)); /* f2 is now NULL or the first list item containing a fragment packet that does not consecutively add payload to the previous ones. */ /* The packet size increases by the amount of all pasted payload sizes: */ base_packet->ph.caplen += payload_increase; base_packet->ph.len += payload_increase; base_iphdr->ip_len = htons(ntohs(base_iphdr->ip_len) + payload_increase); /* Realloc packet size to new size: */ base_packet->data = realloc(base_packet->data, base_packet->ph.caplen); /* This may have made offsets invalid, so re-init: */ libnd_packet_init(base_packet); base_iphdr = (struct ip *) libnd_packet_get_data(base_packet, nd_ip_get(), 0); base_payload = ND_IP_PAYLOAD(base_iphdr); trailer = libnd_packet_get_data_end(base_packet, nd_ip_get(), 0); trailer_len = libnd_packet_get_end(base_packet) - trailer; D(("Reallocd content, adjusted length values.\n")); /* Move anything that follows IP to the new trailer location: */ if (trailer_len) { memmove(base_payload + ND_IP_PAYLOAD_LENGTH(base_iphdr) + payload_increase, trailer, trailer_len); D(("Trailer of %i bytes moved to end.\n", trailer_len)); } /* Go through list and reassemble (we can skip the first packet since its payload is already there) */ for (f3 = g_list_next(f); f3 && (f3 != f2) ; f3 = g_list_next(f3)) { frag_packet = (LND_Packet *) f3->data; frag_iphdr = (struct ip *) libnd_packet_get_data(frag_packet, nd_ip_get(), 0); D(("Merging one packet.\n")); /* The offset of the new fragment payload must start at the currently covered payload, otherwise we don't have consecutive fragments */ dest = base_payload + /* Start address of data */ ND_IP_FRAG_OFFSET(frag_iphdr) - /* plus fragment offset of the pasted fragment */ ND_IP_FRAG_OFFSET(base_iphdr); /* minus fragment offset ofthe base fragment */ /* Copy the payload of the fragment packet to the correct offset in the base fragment */ memcpy(dest, ND_IP_PAYLOAD(frag_iphdr), ND_IP_PAYLOAD_LENGTH(frag_iphdr)); /* If we're pasting a last fragment, the base fragment will now be a last fragment as well (since we are only handling consecutive areas). Thus clear MF if necessary: */ if ((ntohs(frag_iphdr->ip_off) & IP_MF) == 0) base_iphdr->ip_off = htons(ntohs(base_iphdr->ip_off) & ~IP_MF); /* This fragment is reassembled and can thus be nuked: */ if (input_packet == frag_packet) nd_trace_set_current_packet(libnd_packet_get_trace(base_packet), NULL); libnd_packet_remove(frag_packet); libnd_packet_free(frag_packet); f3->data = NULL; } D(("Fixing checksum in merged packet.\n")); libnd_ip_fix_packet(base_packet); libnd_packet_modified(base_packet); /* If this is a now-complete IP packet, reinitialize it so that payload protocols become visible: */ if (((ntohs(base_iphdr->ip_off) & IP_OFFMASK) == 0) && ((ntohs(base_iphdr->ip_off) & IP_MF) == 0)) { D(("New packet is complete IP datagram, initializing ...\n")); libnd_packet_init(base_packet); } /* Continue where new consecutive fragments start: */ f = f2; } return; TOUCH(key); TOUCH(user_data); } static void ip_frag_free(gpointer key, GList *fraglist, gpointer user_data) { g_list_free(fraglist); return; TOUCH(key); TOUCH(user_data); } void nd_ip_frag_reassemble(LND_Packet *input_packet) { struct ip *iphdr; LND_Packet *packet; LND_Trace *trace; GHashTable *fraggles; GList *fragslist = NULL; LND_PacketIterator pit; LND_PacketIteratorMode mode; int fragments = 0, packets = 0; char message[MAXPATHLEN]; if (!input_packet || !libnd_packet_get_trace(input_packet)) return_if_no_current_trace(trace); else trace = libnd_packet_get_trace(input_packet); fraggles = g_hash_table_new(g_direct_hash, g_direct_equal); mode = trace->iterator_mode; if (mode == LND_PACKET_IT_AREA_R || mode == LND_PACKET_IT_AREA_RW) { /* We're currently only reassembling packets in memory, * so reduce this to apply-to-trace-part if the current * mode is apply-to-all: */ mode = LND_PACKET_IT_PART_RW; } for (libnd_pit_init_mode(&pit, trace, mode); libnd_pit_get(&pit); libnd_pit_next(&pit)) { packet = libnd_pit_get(&pit); iphdr = (struct ip *) libnd_packet_get_data(packet, nd_ip_get(), 0); if (!iphdr) continue; /* If this is a fragment, put it into the hashtable. */ if ((ntohs(iphdr->ip_off) & IP_MF) || (ntohs(iphdr->ip_off) & IP_OFFMASK)) { fragslist = g_hash_table_lookup(fraggles, GINT_TO_POINTER((int) (iphdr->ip_id))); if (!fragslist) { packets++; fragslist = g_list_append(fragslist, packet); g_hash_table_insert(fraggles, GINT_TO_POINTER((int) (iphdr->ip_id)), fragslist); D(("Creating fragment list for id %u\n", ntohs(iphdr->ip_id))); } else { fragslist = g_list_append(fragslist, packet); D(("Adding fragment to list for id %u\n", ntohs(iphdr->ip_id))); } fragments++; } } if (fragments > 1) { g_hash_table_foreach(fraggles, (GHFunc) ip_frag_reassemble_packet, input_packet); g_snprintf(message, MAXPATHLEN, _("Reassembled %i fragments belonging to %i IP packet(s)."), fragments, packets); nd_dialog_message(_("Reassembly results"), message, FALSE); } g_hash_table_foreach(fraggles, (GHFunc) ip_frag_free, NULL); g_hash_table_destroy(fraggles); }