aboutsummaryrefslogtreecommitdiffstats
path: root/test/packetdrill/icmp_packet.c
diff options
context:
space:
mode:
Diffstat (limited to 'test/packetdrill/icmp_packet.c')
-rw-r--r--test/packetdrill/icmp_packet.c406
1 files changed, 406 insertions, 0 deletions
diff --git a/test/packetdrill/icmp_packet.c b/test/packetdrill/icmp_packet.c
new file mode 100644
index 0000000..6dc5f9b
--- /dev/null
+++ b/test/packetdrill/icmp_packet.c
@@ -0,0 +1,406 @@
+/*
+ * 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 module for formatting ICMP packets.
+ */
+
+#include "icmp_packet.h"
+
+#include "icmp.h"
+#include "icmpv6.h"
+#include "ip_packet.h"
+
+/* A table entry mapping an ICMP code string to byte. */
+struct icmp_code_info {
+ u8 code_byte; /* type byte on the wire */
+ const char *code_string; /* human-readable code */
+};
+
+/* A table entry mapping an ICMP type string to byte and code table. */
+struct icmp_type_info {
+ u8 type_byte; /* type byte on the wire */
+ const char *type_string; /* human-readable type */
+ const struct icmp_code_info *code_table; /* codes for this type */
+};
+
+/* Values for the 'code' byte of an IPv4 ICMP_DEST_UNREACH header (RFC 1700). */
+struct icmp_code_info icmpv4_unreachable_codes[] = {
+ { ICMP_NET_UNREACH, "net_unreachable" },
+ { ICMP_HOST_UNREACH, "host_unreachable" },
+ { ICMP_PROT_UNREACH, "protocol_unreachable" },
+ { ICMP_PORT_UNREACH, "port_unreachable" },
+ { ICMP_FRAG_NEEDED, "frag_needed" },
+ { ICMP_SR_FAILED, "source_route_failed" },
+ { ICMP_NET_UNKNOWN, "net_unknown" },
+ { ICMP_HOST_UNKNOWN, "host_unknown" },
+ { ICMP_HOST_ISOLATED, "source_host_isolated" },
+ { ICMP_NET_ANO, "net_prohibited" },
+ { ICMP_HOST_ANO, "host_prohibited" },
+ { ICMP_NET_UNR_TOS, "net_unreachable_for_tos" },
+ { ICMP_HOST_UNR_TOS, "host_unreachable_for_tos" },
+ { ICMP_PKT_FILTERED, "packet_filtered" },
+ { ICMP_PREC_VIOLATION, "precedence_violation" },
+ { ICMP_PREC_CUTOFF, "precedence_cutoff" },
+ { 0, NULL },
+};
+
+/* Information about the supported types of ICMPv4 header (RFC 1700). */
+struct icmp_type_info icmpv4_types[] = {
+ { ICMP_ECHOREPLY, "echo_reply" },
+ { ICMP_DEST_UNREACH, "unreachable", icmpv4_unreachable_codes },
+ { ICMP_SOURCE_QUENCH, "source_quench" },
+ { ICMP_REDIRECT, "redirect" },
+ { ICMP_ECHO, "echo_request" },
+ { ICMP_TIME_EXCEEDED, "time_exceeded" },
+ { ICMP_PARAMETERPROB, "parameter_problem" },
+ { ICMP_TIMESTAMP, "timestamp_request" },
+ { ICMP_TIMESTAMPREPLY, "timestamp_reply" },
+ { ICMP_INFO_REQUEST, "information_request" },
+ { ICMP_INFO_REPLY, "information_reply" },
+ { ICMP_ADDRESS, "address_mask_request" },
+ { ICMP_ADDRESSREPLY, "address_mask_reply" },
+ { 0, NULL, NULL },
+};
+
+/* Values for the 'code' byte of an ICMPV6_DEST_UNREACH header (RFC 2463). */
+struct icmp_code_info icmpv6_unreachable_codes[] = {
+ { ICMP_NET_UNREACH, "net_unreachable" },
+ { ICMPV6_NOROUTE, "no_route" },
+ { ICMPV6_ADM_PROHIBITED, "admin_prohibited" },
+ { ICMPV6_NOT_NEIGHBOUR, "not_neighbour" },
+ { ICMPV6_ADDR_UNREACH, "address_unreachable" },
+ { ICMPV6_PORT_UNREACH, "port_unreachable" },
+ { 0, NULL },
+};
+
+/* Values for the 'code' byte of an ICMPV6_TIME_EXCEED header (RFC 2463). */
+struct icmp_code_info icmpv6_time_exceed_codes[] = {
+ { ICMPV6_EXC_HOPLIMIT, "exceeded_hop_limit" },
+ { ICMPV6_EXC_FRAGTIME, "exceeded_frag_time" },
+ { 0, NULL },
+};
+
+/* Values for the 'code' byte of an ICMPV6_PARAMPROB header (RFC 2463). */
+struct icmp_code_info icmpv6_paramprob_codes[] = {
+ { ICMPV6_HDR_FIELD, "header_field" },
+ { ICMPV6_UNK_NEXTHDR, "unknown_next_header" },
+ { ICMPV6_UNK_OPTION, "unknown_option" },
+ { 0, NULL },
+};
+
+/* Information about the supported types of ICMPv6 header (RFC 2463). */
+struct icmp_type_info icmpv6_types[] = {
+ { ICMPV6_DEST_UNREACH, "unreachable", icmpv6_unreachable_codes },
+ { ICMPV6_PKT_TOOBIG, "packet_too_big" },
+ { ICMPV6_TIME_EXCEED, "time_exceeded", icmpv6_time_exceed_codes },
+ { ICMPV6_PARAMPROB, "parameter_problem", icmpv6_paramprob_codes },
+ { ICMPV6_ECHO_REQUEST, "echo_request" },
+ { ICMPV6_ECHO_REPLY, "echo_reply" },
+ { 0, NULL, NULL },
+};
+
+/* Return the ICMP protocol number for the given address family. */
+static int icmp_protocol(int address_family)
+{
+ if (address_family == AF_INET)
+ return IPPROTO_ICMP;
+ else if (address_family == AF_INET6)
+ return IPPROTO_ICMPV6;
+ else
+ assert(!"bad ip version");
+ return 0;
+}
+
+/* Return the length in bytes of the ICMP header. */
+static int icmp_header_len(int address_family)
+{
+ if (address_family == AF_INET)
+ return sizeof(struct icmpv4);
+ else if (address_family == AF_INET6)
+ return sizeof(struct icmpv6);
+ else
+ assert(!"bad ip version");
+ return 0;
+}
+
+/* Fill in ICMPv4 header fields. */
+static int set_icmpv4_header(struct icmpv4 *icmpv4, u8 type, u8 code,
+ s64 mtu, u16 echo_id, char **error)
+{
+ icmpv4->type = type;
+ icmpv4->code = code;
+ icmpv4->checksum = htons(0);
+
+ if (mtu >= 0) {
+ if ((type != ICMP_DEST_UNREACH) || (code != ICMP_FRAG_NEEDED)) {
+ asprintf(error,
+ "ICMPv4 MTU is only valid for "
+ "unreachable-frag_needed");
+ return STATUS_ERR;
+ }
+ if (!is_valid_u16(mtu)) {
+ asprintf(error, "ICMPv4 MTU out of 16-bit range");
+ return STATUS_ERR;
+ }
+ icmpv4->message.frag.mtu = htons(mtu);
+ }
+ if (echo_id > 0)
+ icmpv4->message.echo.id = htons(echo_id);
+
+ return STATUS_OK;
+}
+
+/* Fill in ICMPv4 header fields. */
+static int set_icmpv6_header(struct icmpv6 *icmpv6, u8 type, u8 code,
+ s64 mtu, u16 echo_id, char **error)
+{
+ icmpv6->type = type;
+ icmpv6->code = code;
+ icmpv6->checksum = htons(0);
+
+ if (mtu >= 0) {
+ if ((type != ICMPV6_PKT_TOOBIG) || (code != 0)) {
+ asprintf(error,
+ "ICMPv6 MTU is only valid for "
+ "packet_too_big-0");
+ return STATUS_ERR;
+ }
+ if (!is_valid_u32(mtu)) {
+ asprintf(error, "ICMPv6 MTU out of 32-bit range");
+ return STATUS_ERR;
+ }
+ icmpv6->message.packet_too_big.mtu = htonl(mtu);
+ }
+ if (echo_id > 0) {
+ icmpv6->message.u_echo.identifier = htons(echo_id);
+ }
+ return STATUS_OK;
+}
+
+/* Populate ICMP header fields. */
+static int set_packet_icmp_header(struct packet *packet, void *icmp,
+ int address_family, int icmp_bytes,
+ u8 type, u8 code, s64 mtu, u16 echo_id,
+ char **error)
+{
+ struct header *icmp_header = NULL;
+
+ if (address_family == AF_INET) {
+ struct icmpv4 *icmpv4 = (struct icmpv4 *) icmp;
+ packet->icmpv4 = icmpv4;
+ assert(packet->icmpv6 == NULL);
+ icmp_header = packet_append_header(packet, HEADER_ICMPV4,
+ sizeof(*icmpv4));
+ icmp_header->total_bytes = icmp_bytes;
+ return set_icmpv4_header(icmpv4, type, code, mtu, echo_id, error);
+ } else if (address_family == AF_INET6) {
+ struct icmpv6 *icmpv6 = (struct icmpv6 *) icmp;
+ packet->icmpv6 = icmpv6;
+ assert(packet->icmpv4 == NULL);
+ icmp_header = packet_append_header(packet, HEADER_ICMPV6,
+ sizeof(*icmpv6));
+ icmp_header->total_bytes = icmp_bytes;
+ return set_icmpv6_header(icmpv6, type, code, mtu, echo_id, error);
+ } else {
+ assert(!"bad ip_version in config");
+ }
+ return STATUS_ERR;
+}
+
+/* Parse the given ICMP type and code strings, and fill in the
+ * *type and *code with the results. If there is an error during
+ * parsing, fill in *error and return STATUS_ERR; otherwise return
+ * STATUS_OK.
+ */
+static int parse_icmp_type_and_code(int address_family,
+ const char *type_string,
+ const char *code_string,
+ s32 *type, s32 *code, char **error)
+{
+ int i = 0;
+ const struct icmp_type_info *icmp_types = NULL;
+ const struct icmp_code_info *code_table = NULL; /* for this type */
+
+ if (address_family == AF_INET)
+ icmp_types = icmpv4_types;
+ else if (address_family == AF_INET6)
+ icmp_types = icmpv6_types;
+ else
+ assert(!"bad ip_version in config");
+
+ /* Parse the type string. */
+ if (sscanf(type_string, "type_%d", type) == 1) {
+ /* Legal but non-standard type in tcpdump-inspired notation. */
+ } else {
+ /* Look in our table of known types. */
+ for (i = 0; icmp_types[i].type_string != NULL; ++i) {
+ if (!strcmp(type_string, icmp_types[i].type_string)) {
+ *type = icmp_types[i].type_byte;
+ code_table = icmp_types[i].code_table;
+ }
+ }
+ }
+ if (!is_valid_u8(*type)) {
+ asprintf(error, "bad ICMP type %s", type_string);
+ return STATUS_ERR;
+ }
+
+ /* Parse the code string. */
+ if (code_string == NULL) {
+ *code = 0; /* missing code means code = 0 */
+ } else if (sscanf(code_string, "code_%d", code) == 1) {
+ /* Legal but non-standard code in tcpdump-inspired notation. */
+ } else if (code_table != NULL) {
+ /* Look in our table of known codes. */
+ for (i = 0; code_table[i].code_string != NULL; ++i) {
+ if (!strcmp(code_string, code_table[i].code_string))
+ *code = code_table[i].code_byte;
+ }
+ }
+ if (!is_valid_u8(*code)) {
+ asprintf(error, "bad ICMP code %s", code_string);
+ return STATUS_ERR;
+ }
+
+ return STATUS_OK;
+}
+
+struct packet *new_icmp_packet(int address_family,
+ enum direction_t direction,
+ const char *type_string,
+ const char *code_string,
+ int protocol,
+ u32 tcp_start_sequence,
+ u32 payload_bytes,
+ struct ip_info ip_info,
+ s64 mtu,
+ s64 echo_id,
+ char **error)
+{
+ s32 type = -1; /* bad type; means "unknown so far" */
+ s32 code = -1; /* bad code; means "unknown so far" */
+
+ struct packet *packet = NULL; /* the newly-allocated result packet */
+ /* Calculate lengths in bytes of all sections of the packet.
+ * For TCP/UDP, for now we only support the most common ICMP message
+ * format, which includes at the end the original outgoing IP
+ * header and the first 8 bytes after that (which will
+ * typically have the port info needed to demux the message).
+ * For RAW, we pad the icmp packet with 0 and the total length is
+ * payload_bytes.
+ */
+ const int ip_fixed_bytes = ip_header_min_len(address_family);
+ const int ip_option_bytes = 0;
+ const int ip_header_bytes = ip_fixed_bytes + ip_option_bytes;
+ int echoed_bytes = 0;
+ int icmp_bytes = 0;
+ int ip_bytes = 0;
+
+ if (protocol == IPPROTO_TCP || protocol == IPPROTO_UDP) {
+ echoed_bytes = ip_fixed_bytes + ICMP_ECHO_BYTES;
+ icmp_bytes = icmp_header_len(address_family) + echoed_bytes;
+ ip_bytes = ip_header_bytes + icmp_bytes;
+ } else if (protocol == IPPROTO_RAW) {
+ echoed_bytes = 0;
+ icmp_bytes = payload_bytes;
+ ip_bytes = ip_header_bytes + payload_bytes;
+ }
+
+ /* Sanity-check on echo_id to make sure it fits in u16 */
+ if (echo_id < 0 || echo_id > 65535) {
+ asprintf(error,
+ "invalid echo_id, must be between 0 and 65535");
+ goto error_out;
+ }
+
+ /* Sanity-check all the various lengths */
+ if (ip_option_bytes & 0x3) {
+ asprintf(error, "IP options are not padded correctly "
+ "to ensure IP header is a multiple of 4 bytes: "
+ "%d excess bytes", ip_option_bytes & 0x3);
+ goto error_out;
+ }
+ assert((ip_header_bytes & 0x3) == 0);
+ if (icmp_bytes < icmp_header_len(address_family)) {
+ asprintf(error, "icmp_bytes %d smaller than icmp header "
+ "length %d",
+ icmp_bytes, icmp_header_len(address_family));
+ goto error_out;
+ }
+
+
+ /* Parse the ICMP type and code */
+ if (parse_icmp_type_and_code(address_family, type_string, code_string,
+ &type, &code, error))
+ goto error_out;
+ assert(is_valid_u8(type));
+ assert(is_valid_u8(code));
+
+ /* Allocate and zero out a packet object of the desired size */
+ packet = packet_new(ip_bytes);
+ memset(packet->buffer, 0, ip_bytes);
+
+ packet->direction = direction;
+ packet->flags = 0;
+ packet->tos_chk = ip_info.tos.check;
+
+ /* Set IP header fields */
+ set_packet_ip_header(packet, address_family, ip_bytes, ip_info.tos.value,
+ ip_info.flow_label, ip_info.ttl,
+ icmp_protocol(address_family));
+
+ /* Find the start of the ICMP header and then populate common fields. */
+ void *icmp_header = ip_start(packet) + ip_header_bytes;
+ if (set_packet_icmp_header(packet, icmp_header, address_family,
+ icmp_bytes, type, code, mtu, echo_id, error))
+ goto error_out;
+
+ /* All ICMP message types currently supported by this tool
+ * include a copy of the outbound IP header and the first few
+ * bytes inside. To ensure that the inbound ICMP message gets
+ * demuxed to the correct socket in the kernel, here we
+ * construct enough of a basic IP header and during test
+ * execution we fill in the port numbers and (if specified)
+ * TCP sequence number in the TCP header.
+ */
+ if (echoed_bytes) {
+ u8 *echoed_ip = packet_echoed_ip_header(packet);
+ const int echoed_ip_bytes = (ip_fixed_bytes +
+ layer4_header_len(protocol) +
+ payload_bytes);
+ set_ip_header(echoed_ip, address_family, echoed_ip_bytes,
+ 0, 0, 0, protocol);
+ if (protocol == IPPROTO_TCP) {
+ u32 *seq = packet_echoed_tcp_seq(packet);
+ *seq = htonl(tcp_start_sequence);
+ }
+ packet->echoed_header = true;
+ } else
+ packet->echoed_header = false;
+
+ packet->ip_bytes = ip_bytes;
+ return packet;
+
+error_out:
+ if (packet != NULL)
+ packet_free(packet);
+ return NULL;
+}