aboutsummaryrefslogtreecommitdiffstats
path: root/test/packetdrill/packet.c
diff options
context:
space:
mode:
Diffstat (limited to 'test/packetdrill/packet.c')
-rw-r--r--test/packetdrill/packet.c327
1 files changed, 327 insertions, 0 deletions
diff --git a/test/packetdrill/packet.c b/test/packetdrill/packet.c
new file mode 100644
index 0000000..d2d792a
--- /dev/null
+++ b/test/packetdrill/packet.c
@@ -0,0 +1,327 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+/*
+ * Author: ncardwell@google.com (Neal Cardwell)
+ *
+ * Implementation for a representation of TCP/IP packets.
+ * Packets are represented in their wire format.
+ */
+
+#include "packet.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include "assert.h"
+#include "ethernet.h"
+#include "gre_packet.h"
+#include "ip_packet.h"
+#include "logging.h"
+#include "mpls_packet.h"
+
+
+/* Info for all types of header we support. */
+struct header_type_info header_types[HEADER_NUM_TYPES] = {
+ { "NONE", 0, 0, NULL },
+ { "IPV4", IPPROTO_IPIP, ETHERTYPE_IP, ipv4_header_finish },
+ { "IPV6", IPPROTO_IPV6, ETHERTYPE_IPV6, ipv6_header_finish },
+ { "GRE", IPPROTO_GRE, 0, gre_header_finish },
+ { "MPLS", 0, ETHERTYPE_MPLS_UC, mpls_header_finish },
+ { "TCP", IPPROTO_TCP, 0, NULL },
+ { "UDP", IPPROTO_UDP, 0, NULL },
+ { "ICMPV4", IPPROTO_ICMP, 0, NULL },
+ { "ICMPV6", IPPROTO_ICMPV6, 0, NULL },
+};
+
+struct packet *packet_new(u32 buffer_bytes)
+{
+ struct packet *packet = calloc(1, sizeof(struct packet));
+ packet->buffer = malloc(buffer_bytes);
+ packet->buffer_bytes = buffer_bytes;
+ return packet;
+}
+
+void packet_free(struct packet *packet)
+{
+ free(packet->buffer);
+ memset(packet, 0, sizeof(*packet)); /* paranoia to help catch bugs */
+ free(packet);
+}
+
+struct packet_list *packet_list_new(void)
+{
+ struct packet_list *list = calloc(1, sizeof(struct packet_list));
+ list->packet = NULL;
+ list->next = NULL;
+ return list;
+}
+
+void packet_list_free(struct packet_list *list)
+{
+ while (list != NULL) {
+ struct packet_list *dead_list = list;
+ if (list->packet)
+ packet_free(list->packet);
+ list = list->next;
+ free(dead_list);
+ }
+}
+
+int packet_header_count(const struct packet *packet)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(packet->headers); ++i) {
+ if (packet->headers[i].type == HEADER_NONE)
+ break;
+ }
+ return i;
+}
+
+/* Copy any header info from old_packet to new_packet. */
+static void packet_copy_headers(struct packet *new_packet,
+ struct packet *old_packet,
+ int bytes_headroom)
+{
+ int i;
+ u8 *base = new_packet->buffer + bytes_headroom;
+
+ for (i = 0; i < ARRAY_SIZE(old_packet->headers); ++i) {
+ struct header *old_header = &old_packet->headers[i];
+ struct header *new_header = &new_packet->headers[i];
+ int offset = 0;
+
+ if (old_header->type == HEADER_NONE)
+ break;
+ offset = old_header->h.ptr - old_packet->buffer;
+ new_header->h.ptr = base + offset;
+ new_header->header_bytes = old_header->header_bytes;
+ new_header->total_bytes = old_header->total_bytes;
+ new_header->type = old_header->type;
+ }
+}
+
+struct header *packet_append_header(struct packet *packet,
+ enum header_t header_type,
+ int header_bytes)
+{
+ struct header *header = NULL;
+ int num_headers = packet_header_count(packet);
+ int packet_bytes;
+
+ assert(num_headers <= PACKET_MAX_HEADERS);
+ if (num_headers == PACKET_MAX_HEADERS)
+ return NULL;
+
+ header = &packet->headers[num_headers];
+
+ if (packet->ip_bytes + header_bytes > packet->buffer_bytes)
+ return NULL;
+ packet_bytes = packet->l2_header_bytes + packet->ip_bytes;
+ header->h.ptr = packet->buffer + packet_bytes;
+ packet->ip_bytes += header_bytes;
+
+ header->type = header_type;
+ header->header_bytes = header_bytes;
+ header->total_bytes = 0;
+ return header;
+}
+
+/* Map a pointer to a packet offset from an old base to a new base. */
+static void *offset_ptr(u8 *old_base, u8* new_base, void *old_ptr)
+{
+ u8 *old = (u8*)old_ptr;
+
+ return (old == NULL) ? NULL : (new_base + (old - old_base));
+}
+
+static void packet_duplicate_info(struct packet *packet,
+ struct packet *old_packet,
+ int bytes_headroom,
+ int extra_payload)
+{
+ u8 *old_base = old_packet->buffer;
+ u8 *new_base = packet->buffer + bytes_headroom;
+
+ packet->ip_bytes = old_packet->ip_bytes + extra_payload;
+ packet->direction = old_packet->direction;
+ packet->time_usecs = old_packet->time_usecs;
+ packet->flags = old_packet->flags;
+ packet->tos_chk = old_packet->tos_chk;
+
+ packet_copy_headers(packet, old_packet, bytes_headroom);
+
+ /* Set up layer 3 header pointer. */
+ packet->ipv4 = offset_ptr(old_base, new_base, old_packet->ipv4);
+ packet->ipv6 = offset_ptr(old_base, new_base, old_packet->ipv6);
+ packet->tcp = offset_ptr(old_base, new_base, old_packet->tcp);
+ packet->udp = offset_ptr(old_base, new_base, old_packet->udp);
+ packet->icmpv4 = offset_ptr(old_base, new_base, old_packet->icmpv4);
+ packet->icmpv6 = offset_ptr(old_base, new_base, old_packet->icmpv6);
+
+ packet->tcp_ts_val = offset_ptr(old_base, new_base,
+ old_packet->tcp_ts_val);
+ packet->tcp_ts_ecr = offset_ptr(old_base, new_base,
+ old_packet->tcp_ts_ecr);
+ packet->echoed_header = old_packet->echoed_header;
+}
+
+/* Make a copy of the given old packet, but in the new copy reserve the
+ * given number of bytes of headroom at the start of the packet->buffer.
+ * This empty headroom can later be filled with outer packet headers.
+ * A slow but simple model.
+ */
+static struct packet *packet_copy_with_headroom(struct packet *old_packet,
+ int bytes_headroom)
+{
+ /* Allocate a new packet and copy link layer header and IP datagram. */
+ const int bytes_used = packet_end(old_packet) - old_packet->buffer;
+ assert(bytes_used >= 0);
+ assert(bytes_used <= 128*1024);
+ struct packet *packet = packet_new(bytes_headroom + bytes_used);
+ u8 *old_base = old_packet->buffer;
+ u8 *new_base = packet->buffer + bytes_headroom;
+
+ memcpy(new_base, old_base, bytes_used);
+
+ packet_duplicate_info(packet, old_packet, bytes_headroom, 0);
+
+ return packet;
+}
+
+struct packet *packet_copy(struct packet *old_packet)
+{
+ return packet_copy_with_headroom(old_packet, 0);
+}
+
+/* Finalize all the headers once we know what's inside inner layers. */
+static void packet_finish_encapsulation_headers(struct packet *packet)
+{
+ int i;
+ struct header *header = NULL, *next = NULL;
+
+ /* Proceed from inner to outer. */
+ for (i = ARRAY_SIZE(packet->headers) - 1; i >= 0; --i, next = header) {
+ struct header_type_info *type_info = NULL;
+
+ header = &packet->headers[i];
+ if (header->type == HEADER_NONE)
+ continue;
+
+ type_info = header_type_info(header->type);
+ if (type_info->finish != NULL)
+ type_info->finish(packet, header, next);
+ }
+}
+
+struct packet *packet_encapsulate(struct packet *outer, struct packet *inner)
+{
+ struct packet *packet = NULL;
+ const int outer_headers = packet_header_count(outer);
+ const int inner_headers = packet_header_count(inner);
+
+ assert(outer_headers + inner_headers <= PACKET_MAX_HEADERS);
+
+ /* Copy the inner packet bits and header metadata. */
+ packet = packet_copy_with_headroom(inner, outer->ip_bytes);
+
+ /* Copy over the bits in the outer headers. */
+ memcpy(packet->buffer, outer->buffer, outer->ip_bytes);
+
+ /* Move the inner header metadata to make room for the outer. */
+ memmove(packet->headers + outer_headers, packet->headers + 0,
+ inner_headers * sizeof(struct header));
+
+ /* Copy over the metadata about the outer headers. */
+ packet_copy_headers(packet, outer, 0);
+
+ assert(packet_header_count(packet) == outer_headers + inner_headers);
+
+ packet_finish_encapsulation_headers(packet);
+
+ packet->ip_bytes = outer->ip_bytes + inner->ip_bytes;
+
+ return packet;
+}
+
+struct header_type_info *header_type_info(enum header_t header_type)
+{
+ assert(header_type > HEADER_NONE);
+ assert(header_type < HEADER_NUM_TYPES);
+ assert(ARRAY_SIZE(header_types) == HEADER_NUM_TYPES);
+ return &header_types[header_type];
+}
+
+/* Aggregate a list of input packets into a single output packet. */
+struct packet *aggregate_packets(const struct packet_list *head,
+ const struct packet_list *tail,
+ int payload_size)
+{
+ int i;
+ /* Copy the headers from the last source packet. */
+ struct packet *first_packet = head->packet;
+ struct packet *last_packet = tail->packet;
+ struct packet *old_packet = last_packet;
+ /* Allocate a new packet that can accommodate the combined payload */
+ int extra_payload = payload_size - packet_payload_len(old_packet);
+ int headers_len = packet_payload(old_packet) - old_packet->buffer;
+ int old_packet_size = packet_end(old_packet) - old_packet->buffer;
+ struct packet *packet = packet_new(old_packet_size + extra_payload);
+
+ u8 *old_base = old_packet->buffer;
+ u8 *new_base = packet->buffer;
+ u8 *iter_base = new_base + headers_len;
+
+ DEBUGP("aggregate_packets with combined payload size of %d bytes\n",
+ payload_size);
+ memcpy(new_base, old_base, headers_len);
+
+ /* Copy the payload from all the source packets. */
+ do {
+ memcpy(iter_base, packet_payload(head->packet),
+ packet_payload_len(head->packet));
+ iter_base += packet_payload_len(head->packet);
+ head = head->next;
+ } while (head != NULL);
+
+ packet_duplicate_info(packet, old_packet, 0, extra_payload);
+
+ /* Adjust header bytes information to account for the larger payload. */
+ for (i = 0; i < ARRAY_SIZE(packet->headers); ++i) {
+ struct header *new_header = &packet->headers[i];
+
+ if (new_header->type == HEADER_NONE)
+ break;
+ new_header->total_bytes += extra_payload;
+ DEBUGP("%s header starts at %p\n",
+ header_type_info(new_header->type)->name,
+ new_header->h.ptr);
+ /* For TCP header, we must copy the seq number and the cwr flag
+ * from the first packet.
+ */
+ if (new_header->type == HEADER_TCP) {
+ assert(packet->tcp != NULL);
+ assert(first_packet->tcp != NULL);
+ packet->tcp->seq = first_packet->tcp->seq;
+ packet->tcp->cwr = first_packet->tcp->cwr;
+ }
+ }
+ packet_finish_encapsulation_headers(packet);
+
+ return packet;
+}