diff options
Diffstat (limited to 'test/packetdrill/icmp_packet.c')
-rw-r--r-- | test/packetdrill/icmp_packet.c | 406 |
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; +} |