/* * 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) * * A module to execute a packet command from a test script. */ #include "run_packet.h" #include #include #include #include #include #include #include "checksum.h" #include "gre.h" #include "logging.h" #include "netdev.h" #include "packet.h" #include "packet_checksum.h" #include "packet_to_string.h" #include "run.h" #include "script.h" #include "tcp.h" #include "tcp_options_iterator.h" #include "tcp_options_to_string.h" #include "tcp_packet.h" #include "wrap.h" /* To avoid issues with TIME_WAIT, FIN_WAIT1, and FIN_WAIT2 we use * dynamically-chosen, unique 4-tuples for each test. We implement the * picking of unique ports by binding a socket to port 0 and seeing * what port we are assigned. Note that we keep the socket fd open for * the lifetime of our process to ensure that the port is not * reused by a later test. */ static u16 ephemeral_port(enum ip_version_t ip_version) { int fd = wrap_socket(ip_version, SOCK_STREAM); return wrap_bind_listen(fd, ip_version, 0); } /* Return the next ephemeral port to use. We want quick results for * the very common case where there is only one remote port to use * over the course of a test. So we avoid paying the overhead of the * several system calls in ephemeral_port() right before injecting an * incoming SYN by pre-allocating and caching a single port to use * before starting each test. */ static u16 next_ephemeral_port(struct state *state) { if (state->packets->next_ephemeral_port >= 0) { int port = state->packets->next_ephemeral_port; assert(port <= 0xffff); state->packets->next_ephemeral_port = -1; return port; } else { return ephemeral_port(state->config->ip_version); } } /* Add a dump of the given packet to the given error message. * Frees *error and replaces it with a version that has the original * *error followed by the given type and a hex dump of the given * packet. */ static void add_packet_dump(char **error, const char *type, struct packet *packet, s64 time_usecs, enum dump_format_t format) { if (packet->ip_bytes != 0) { char *old_error = *error; char *dump = NULL, *dump_error = NULL; packet_to_string(packet, format, &dump, &dump_error); asprintf(error, "%s\n%s packet: %9.6f %s%s%s", old_error, type, usecs_to_secs(time_usecs), dump, dump_error ? "\n" : "", dump_error ? dump_error : ""); free(dump); free(dump_error); free(old_error); } } /* For verbose runs, print a short packet dump of all live packets. */ static void verbose_packet_dump(struct state *state, const char *type, struct packet *live_packet, s64 time_usecs) { if (state->config->verbose) { char *dump = NULL, *dump_error = NULL; packet_to_string(live_packet, DUMP_SHORT, &dump, &dump_error); printf("%s packet: %9.6f %s%s%s\n", type, usecs_to_secs(time_usecs), dump, dump_error ? "\n" : "", dump_error ? dump_error : ""); free(dump); free(dump_error); } } /* See if the live packet matches the live 4-tuple of the socket (UDP/TCP) * or matches the src/dst IP addr for the ICMP socket */ static struct socket *find_socket_for_live_packet( struct state *state, const struct packet *packet, enum direction_t *direction) { struct socket *socket = state->socket_under_test; /* shortcut */ if (socket == NULL) return NULL; struct tuple packet_tuple, live_outbound, live_inbound; bool is_icmp = (socket->protocol == IPPROTO_ICMP && packet->icmpv4) || (socket->protocol == IPPROTO_ICMPV6 && packet->icmpv6); get_packet_tuple(packet, &packet_tuple); /* Is packet inbound to the socket under test? */ socket_get_inbound(&socket->live, &live_inbound); if (is_equal_tuple(&packet_tuple, &live_inbound) || (is_icmp && is_equal_ip(&packet_tuple.dst.ip, &live_inbound.dst.ip) && is_equal_ip(&packet_tuple.src.ip, &live_inbound.src.ip))) { *direction = DIRECTION_INBOUND; DEBUGP("inbound live packet, socket in state %d\n", socket->state); return socket; } /* Is packet outbound from the socket under test? */ socket_get_outbound(&socket->live, &live_outbound); if (is_equal_tuple(&packet_tuple, &live_outbound) || (is_icmp && is_equal_ip(&packet_tuple.dst.ip, &live_outbound.dst.ip) && is_equal_ip(&packet_tuple.src.ip, &live_outbound.src.ip))) { *direction = DIRECTION_OUTBOUND; DEBUGP("outbound live packet, socket in state %d\n", socket->state); return socket; } return NULL; } /* See if the socket under test is listening and is willing to receive * this incoming SYN packet. If so, create a new child socket, anoint * it as the new socket under test, and return a pointer to * it. Otherwise, return NULL. */ static struct socket *handle_listen_for_script_packet( struct state *state, const struct packet *packet, enum direction_t direction) { /* Does this packet match this socket? For now we only support * testing one socket at a time, so we merely check whether * the socket is listening. (If we were to support testing * more than one socket at a time then we'd want to check to * see if the address tuples in the packet and socket match.) */ struct config *config = state->config; struct socket *socket = state->socket_under_test; /* shortcut */ bool match = (direction == DIRECTION_INBOUND); if (!match) return NULL; if (config->is_wire_server) { /* On wire servers we don't see the system calls, so * we won't have any socket_under_test yet. */ match = (socket == NULL); } else { /* In local mode we typically know about the socket, but * we also allow null socket_under_test to facilitate tests * where we intentionally want no matching socket. */ match = (socket == NULL) || (socket->state == SOCKET_PASSIVE_LISTENING); } if (!match) return NULL; /* Create a child passive socket for this incoming SYN packet. * Any further packets in the test script will be directed to * this child socket. */ socket = socket_new(state); state->socket_under_test = socket; assert(socket->state == SOCKET_INIT); socket->state = SOCKET_PASSIVE_PACKET_RECEIVED; socket->address_family = packet_address_family(packet); socket->protocol = packet_ip_protocol(packet); /* Set script info for this socket using script packet. */ struct tuple tuple; get_packet_tuple(packet, &tuple); socket->script.remote = tuple.src; socket->script.local = tuple.dst; socket->script.remote_isn = ntohl(packet->tcp->seq); socket->fd.script_fd = -1; /* Set up the live info for this socket based * on the script packet and our overall config. */ socket->live.remote.ip = config->live_remote_ip; socket->live.remote.port = htons(next_ephemeral_port(state)); socket->live.local.ip = config->live_local_ip; socket->live.local.port = htons(config->live_bind_port); socket->live.remote_isn = ntohl(packet->tcp->seq); socket->fd.live_fd = -1; if (DEBUG_LOGGING) { char local_string[ADDR_STR_LEN]; char remote_string[ADDR_STR_LEN]; DEBUGP("live: local: %s.%d\n", ip_to_string(&socket->live.local.ip, local_string), ntohs(socket->live.local.port)); DEBUGP("live: remote: %s.%d\n", ip_to_string(&socket->live.remote.ip, remote_string), ntohs(socket->live.remote.port)); DEBUGP("live: ISN: %u\n", socket->live.remote_isn); } return socket; } /* See if the socket under test is a connecting socket that would emit * this outgoing script SYN. If so, return a pointer to the socket; * otherwise, return NULL. */ static struct socket *handle_connect_for_script_packet( struct state *state, const struct packet *packet, enum direction_t direction) { /* Does this packet match this socket? For now we only support * testing one socket at a time, so we merely check whether * the socket is connecting. (If we were to support testing * more than one socket at a time then we'd want to check to * see if the address tuples in the packet and socket match.) */ struct config *config = state->config; struct socket *socket = state->socket_under_test; /* shortcut */ bool match = ((direction == DIRECTION_OUTBOUND) && packet->tcp->syn && !packet->tcp->ack); if (!match) return NULL; if (config->is_wire_server) { /* On wire servers we don't see the system calls, so * we won't have any socket_under_test yet. */ match = (socket == NULL); } else { /* In local mode we will certainly know about this socket. */ match = ((socket != NULL) && (socket->state == SOCKET_ACTIVE_CONNECTING)); } if (!match) return NULL; if (socket == NULL) { /* Wire server. Create a socket for this outbound SYN * packet. Any further packets in the test script are * mapped here. */ socket = socket_new(state); state->socket_under_test = socket; assert(socket->state == SOCKET_INIT); socket->address_family = packet_address_family(packet); socket->protocol = packet_ip_protocol(packet); socket->fd.script_fd = -1; socket->live.remote.ip = config->live_remote_ip; socket->live.remote.port = htons(config->live_connect_port); socket->fd.live_fd = -1; } /* Fill in the new info about this connection. */ struct tuple tuple; get_packet_tuple(packet, &tuple); socket->state = SOCKET_ACTIVE_SYN_SENT; socket->script.remote = tuple.dst; socket->script.local = tuple.src; socket->script.local_isn = ntohl(packet->tcp->seq); return socket; } /* Look for a connecting socket that would emit this outgoing live packet. */ static struct socket *find_connect_for_live_packet( struct state *state, struct packet *packet, enum direction_t *direction) { struct tuple tuple; get_packet_tuple(packet, &tuple); *direction = DIRECTION_INVALID; struct socket *socket = state->socket_under_test; /* shortcut */ if (!socket) return NULL; bool is_udp_match = (packet->udp && (socket->protocol == IPPROTO_UDP) && (socket->state == SOCKET_ACTIVE_CONNECTING)); bool is_icmp_match = (((packet->icmpv4 && socket->protocol == IPPROTO_ICMP) || (packet->icmpv6 && socket->protocol == IPPROTO_ICMPV6)) && (socket->state == SOCKET_ACTIVE_CONNECTING)); bool is_tcp_match = (packet->tcp && packet->tcp->syn && !packet->tcp->ack && (socket->protocol == IPPROTO_TCP) && (socket->state == SOCKET_ACTIVE_SYN_SENT)); if (!is_udp_match && !is_tcp_match && !is_icmp_match) return NULL; if (is_icmp_match) { if (!is_equal_ip(&tuple.dst.ip, &socket->live.remote.ip)) return NULL; } else { if (!is_equal_ip(&tuple.dst.ip, &socket->live.remote.ip) || !is_equal_port(tuple.dst.port, socket->live.remote.port)) return NULL; } *direction = DIRECTION_OUTBOUND; /* Using the details in this outgoing packet, fill in the * new details we've learned about this actively initiated * connection (for which we've seen a connect() call). */ socket->live.local.ip = tuple.src.ip; socket->live.local.port = tuple.src.port; if (packet->tcp) socket->live.local_isn = ntohl(packet->tcp->seq); return socket; } /* Convert outbound TCP timestamp value from scripted value to live value. */ static int get_outbound_ts_val_mapping( struct socket *socket, u32 script_timestamp, u32 *live_timestamp) { DEBUGP("get_outbound_ts_val_mapping\n"); DEBUGP("ts_val_mapping %u -> ?\n", ntohl(script_timestamp)); if (hash_map_get(socket->ts_val_map, script_timestamp, live_timestamp)) return STATUS_OK; return STATUS_ERR; } /* Store script->live mapping for outbound TCP timestamp value. */ static void set_outbound_ts_val_mapping( struct socket *socket, u32 script_timestamp, u32 live_timestamp) { DEBUGP("set_outbound_ts_val_mapping\n"); DEBUGP("ts_val_mapping %u -> %u\n", ntohl(script_timestamp), ntohl(live_timestamp)); hash_map_set(socket->ts_val_map, script_timestamp, live_timestamp); } /* A helper to find the TCP timestamp option in a packet. Parse the * TCP options and fill in packet->tcp_ts_val with the location of the * TCP timestamp value field (or NULL if there isn't one), and * likewise fill in packet->tcp_ts_ecr with the location of the TCP * timestamp echo reply field (or NULL if there isn't one). Returns * STATUS_OK on success; on failure returns STATUS_ERR and sets * error message. */ static int find_tcp_timestamp(struct packet *packet, char **error) { struct tcp_options_iterator iter; struct tcp_option *option = NULL; packet->tcp_ts_val = NULL; packet->tcp_ts_ecr = NULL; for (option = tcp_options_begin(packet, &iter); option != NULL; option = tcp_options_next(&iter, error)) if (option->kind == TCPOPT_TIMESTAMP) { packet->tcp_ts_val = (void *)&(option->data.time_stamp.val); packet->tcp_ts_ecr = (void *)&(option->data.time_stamp.ecr); } return *error ? STATUS_ERR : STATUS_OK; } /* A helper to help translate SACK sequence numbers between live and * script space. Specifically, it offsets SACK block sequence numbers * by the given 'ack_offset'. Returns STATUS_OK on success; on * failure returns STATUS_ERR and sets error message. */ static int offset_sack_blocks(struct packet *packet, u32 ack_offset, char **error) { struct tcp_options_iterator iter; struct tcp_option *option = NULL; for (option = tcp_options_begin(packet, &iter); option != NULL; option = tcp_options_next(&iter, error)) { if (option->kind == TCPOPT_SACK) { int num_blocks = 0; if (num_sack_blocks(option->length, &num_blocks, error)) return STATUS_ERR; int i = 0; for (i = 0; i < num_blocks; ++i) { u32 val; val = ntohl(option->data.sack.block[i].left); val += ack_offset; option->data.sack.block[i].left = htonl(val); val = ntohl(option->data.sack.block[i].right); val += ack_offset; option->data.sack.block[i].right = htonl(val); } } } return *error ? STATUS_ERR : STATUS_OK; } /* Rewrite the TCP sequence number echoed by the ICMP packet. * The Linux TCP layer ignores ICMP messages with bogus sequence numbers. */ static int map_inbound_icmp_tcp_packet( struct socket *socket, struct packet *live_packet, char **error) { u32 *seq = packet_echoed_tcp_seq(live_packet); bool is_syn = false; u32 seq_offset = local_seq_script_to_live_offset(socket, is_syn); *seq = htonl(ntohl(*seq) + seq_offset); return STATUS_OK; } /* UDP headers echoed by ICMP messages need no special rewriting. */ static int map_inbound_icmp_udp_packet( struct socket *socket, struct packet *live_packet, char **error) { return STATUS_OK; } static int map_inbound_icmp_packet( struct socket *socket, struct packet *live_packet, char **error) { if (live_packet->echoed_header) { if (packet_echoed_ip_protocol(live_packet) == IPPROTO_TCP) return map_inbound_icmp_tcp_packet(socket, live_packet, error); else if (packet_echoed_ip_protocol(live_packet) == IPPROTO_UDP) return map_inbound_icmp_udp_packet(socket, live_packet, error); else assert(!"unsupported layer 4 protocol echoed in ICMP packet"); return STATUS_ERR; } else { return STATUS_OK; } } /* Rewrite the IP and TCP, UDP, or ICMP fields in 'live_packet', mapping * inbound packet values (address 4-tuple and sequence numbers in seq, * ACK, SACK blocks) from script values to live values, so that we can * inject this packet into the kernel and have the kernel accept it * for the given socket and process it. Returns STATUS_OK on success; * on failure returns STATUS_ERR and sets error message. */ static int map_inbound_packet( struct state *state, struct socket *socket, struct packet *live_packet, char **error) { DEBUGP("map_inbound_packet\n"); /* Remap packet to live values. */ struct tuple live_inbound; u16 src_port = 0; u16 dst_port = 0; if (live_packet->tcp) { src_port = live_packet->tcp->src_port; dst_port = live_packet->tcp->dst_port; } else if (live_packet->udp) { src_port = live_packet->udp->src_port; dst_port = live_packet->udp->dst_port; } socket_get_inbound(&socket->live, &live_inbound); set_packet_tuple(live_packet, &live_inbound); /* restore preset src and dst port */ if (live_packet->tcp) { if (src_port) live_packet->tcp->src_port = src_port; if (dst_port) live_packet->tcp->dst_port = dst_port; } else if (live_packet->udp) { if (src_port) live_packet->udp->src_port = src_port; if (dst_port) live_packet->udp->dst_port = dst_port; } live_packet->mss = state->config->mss; if ((live_packet->icmpv4 != NULL) || (live_packet->icmpv6 != NULL)) return map_inbound_icmp_packet(socket, live_packet, error); /* If no TCP headers to rewrite, then we're done. */ if (live_packet->tcp == NULL) return STATUS_OK; /* Remap the sequence number from script sequence number to live. */ const bool is_syn = live_packet->tcp->syn; const u32 seq_offset = remote_seq_script_to_live_offset(socket, is_syn); live_packet->tcp->seq = htonl(ntohl(live_packet->tcp->seq) + seq_offset); /* Remap the ACK and SACKs from script sequence number to live. */ const u32 ack_offset = local_seq_script_to_live_offset(socket, is_syn); if (live_packet->tcp->ack) live_packet->tcp->ack_seq = htonl(ntohl(live_packet->tcp->ack_seq) + ack_offset); if (offset_sack_blocks(live_packet, ack_offset, error)) return STATUS_ERR; /* Find the timestamp echo reply is, so we can remap that below. */ if (find_tcp_timestamp(live_packet, error)) return STATUS_ERR; /* Remap TCP timestamp echo reply from script value to a live * value. We say "a" rather than "the" live value because * there could be multiple live values corresponding to the * same script value if a live test replay flips to a new * jiffie in a spot where the script did not. */ if (live_packet->tcp->ack && (live_packet->tcp_ts_ecr != NULL)) { u32 live_ts_ecr = 0; if (get_outbound_ts_val_mapping(socket, packet_tcp_ts_ecr(live_packet), &live_ts_ecr) == STATUS_OK) { /* TS ecr refers to an exact outbound TS val. */ packet_set_tcp_ts_ecr(live_packet, live_ts_ecr); } else if (state->config->tcp_ts_ecr_scaled && socket->found_first_tcp_ts) { /* Interpolate to approximate TS ecr. By * default we verify that inbound TCP * timestamp ECR values reflect earlier * outbound TCP timestamp VAL values, since * this is what well-behaved stacks will do. */ live_ts_ecr = (packet_tcp_ts_ecr(live_packet) - socket->first_script_ts_val + socket->first_actual_ts_val); packet_set_tcp_ts_ecr(live_packet, live_ts_ecr); } else { asprintf(error, "unable to infer live TS ecr for " "script TS ecr %u; " "no matching or preceding outound TS val", packet_tcp_ts_ecr(live_packet)); return STATUS_ERR; } } return STATUS_OK; } static int tcp_convert_seq_number(struct socket *socket, struct packet *packet, char **error) { /* Rewrite TCP sequence number from live to script space. */ const bool is_syn = packet->tcp->syn; const u32 seq_offset = local_seq_live_to_script_offset(socket, is_syn); packet->tcp->seq = htonl(ntohl(packet->tcp->seq) + seq_offset); /* Rewrite ACKs and SACKs from live to script space. */ const u32 ack_offset = remote_seq_live_to_script_offset(socket, is_syn); if (packet->tcp->ack) packet->tcp->ack_seq = htonl(ntohl(packet->tcp->ack_seq) + ack_offset); if (offset_sack_blocks(packet, ack_offset, error)) return STATUS_ERR; return STATUS_OK; } /* Transforms values in the 'actual_packet' by mapping outbound packet * values in the sniffed 'live_packet' (address 4-tuple, sequence * number in seq, timestamp value) from live values to script values * in the space of 'script_packet'. This will allow us to compare a * packet sent by the kernel to the packet expected by the script. */ static int map_outbound_live_packet( struct socket *socket, struct packet *live_packet, struct packet *actual_packet, struct packet *script_packet, char **error) { DEBUGP("map_outbound_live_packet\n"); struct tuple live_packet_tuple, live_outbound, script_outbound; /* Verify packet addresses are outbound and live for this socket. */ get_packet_tuple(live_packet, &live_packet_tuple); socket_get_outbound(&socket->live, &live_outbound); if (socket->protocol == IPPROTO_ICMP || socket->protocol == IPPROTO_ICMPV6) assert(is_equal_ip(&live_packet_tuple.src.ip, &live_outbound.src.ip) && is_equal_ip(&live_packet_tuple.dst.ip, &live_outbound.dst.ip)); else assert(is_equal_tuple(&live_packet_tuple, &live_outbound)); /* Rewrite 4-tuple to be outbound script values. */ socket_get_outbound(&socket->script, &script_outbound); set_packet_tuple(actual_packet, &script_outbound); /* If no TCP headers to rewrite, then we're done. */ if (live_packet->tcp == NULL) return STATUS_OK; /* Extract location of script and actual TCP timestamp values. */ if (find_tcp_timestamp(script_packet, error)) return STATUS_ERR; if (find_tcp_timestamp(actual_packet, error)) return STATUS_ERR; if ((script_packet->tcp_ts_val != NULL) && (actual_packet->tcp_ts_val != NULL)) { u32 script_ts_val = packet_tcp_ts_val(script_packet); u32 actual_ts_val = packet_tcp_ts_val(actual_packet); u32 script_ts_ecr = packet_tcp_ts_ecr(script_packet); u32 actual_ts_ecr = packet_tcp_ts_ecr(actual_packet); /* Remember script->actual TS val mapping for later. */ set_outbound_ts_val_mapping(socket, script_ts_val, actual_ts_val); /* Find baseline for socket's live->script TS val mapping. */ if (!socket->found_first_tcp_ts) { socket->found_first_tcp_ts = true; socket->first_script_ts_val = script_ts_val; socket->first_actual_ts_val = actual_ts_val; socket->first_script_ts_ecr = script_ts_ecr; socket->first_actual_ts_ecr = actual_ts_ecr; } /* Rewrite TCP timestamp value to script space, so we * can compare the script and actual outbound TCP * timestamp val. */ packet_set_tcp_ts_val(actual_packet, socket->first_script_ts_val + (actual_ts_val - socket->first_actual_ts_val)); } return STATUS_OK; } /* Verify IP and TCP checksums on an outbound live packet. */ static int verify_outbound_live_checksums(struct packet *live_packet, char **error) { /* Verify IP header checksum. */ if ((live_packet->ipv4 != NULL) && ipv4_checksum(live_packet->ipv4, ipv4_header_len(live_packet->ipv4))) { asprintf(error, "bad outbound IP checksum"); return STATUS_ERR; } /* TODO(ncardwell): Verify TCP and UDP checksum. This is a little * subtle, due to TCP checksum offloading. */ return STATUS_OK; } /* Check whether the given field of a packet matches the expected * value, and emit a human-readable error message if not. */ static int check_field( const char *name, /* human-readable name of the header field */ u32 expected, /* value script hopes to see */ u32 actual, /* actual value seen during test */ char **error) /* human-readable error string on failure */ { if (actual != expected) { asprintf(error, "live packet field %s: " "expected: %u (0x%x) vs actual: %u (0x%x)", name, expected, expected, actual, actual); return STATUS_ERR; } return STATUS_OK; } /* Verify that the actual TOS byte is as the script expected. */ static int verify_outbound_live_tos(enum tos_chk_t tos_chk, u8 actual_tos_byte, u8 script_tos_byte, char **error) { if (tos_chk == TOS_CHECK_ECN) { u8 actual_ecn_bits = actual_tos_byte & IP_ECN_MASK; if (script_tos_byte == ECN_ECT01) { if ((actual_ecn_bits != IP_ECN_ECT0) && (actual_ecn_bits != IP_ECN_ECT1)) { asprintf(error, "live packet field ip_ecn: " "expected: 0x1 or 0x2 vs actual: 0x%x", actual_ecn_bits); return STATUS_ERR; } } else if (check_field("ip_ecn", script_tos_byte, actual_ecn_bits, error)) { return STATUS_ERR; } } else if (tos_chk == TOS_CHECK_TOS) { if (check_field("tos", script_tos_byte, actual_tos_byte, error)) { return STATUS_ERR; } } return STATUS_OK; } static int verify_outbound_live_ttl_or_hl(u8 actual_ttl_byte, u8 script_ttl_byte, char **error) { if (script_ttl_byte != TTL_CHECK_NONE) { if (check_field("ttl", script_ttl_byte, actual_ttl_byte, error)) { return STATUS_ERR; } } return STATUS_OK; } /* How many bytes should we tack onto the script packet to account for * the actual TCP options we did see? */ static int tcp_options_allowance(const struct packet *actual_packet, const struct packet *script_packet) { if (script_packet->flags & FLAG_OPTIONS_NOCHECK) return packet_tcp_options_len(actual_packet); else return 0; } /* Verify that required actual IPv4 header fields are as the script expected. */ static int verify_ipv4( const struct packet *actual_packet, const struct packet *script_packet, int layer, bool strict, char **error) { const struct ipv4 *actual_ipv4 = actual_packet->headers[layer].h.ipv4; const struct ipv4 *script_ipv4 = script_packet->headers[layer].h.ipv4; if (check_field("ipv4_version", script_ipv4->version, actual_ipv4->version, error) || check_field("ipv4_protocol", script_ipv4->protocol, actual_ipv4->protocol, error) || check_field("ipv4_header_length", script_ipv4->ihl, actual_ipv4->ihl, error) || (strict && check_field("ipv4_total_length", (ntohs(script_ipv4->tot_len) + tcp_options_allowance(actual_packet, script_packet)), ntohs(actual_ipv4->tot_len), error))) return STATUS_ERR; if (verify_outbound_live_tos(script_packet->tos_chk, ipv4_tos_byte(actual_ipv4), ipv4_tos_byte(script_ipv4), error)) return STATUS_ERR; if (verify_outbound_live_ttl_or_hl(ipv4_ttl_byte(actual_ipv4), ipv4_ttl_byte(script_ipv4), error)) return STATUS_ERR; return STATUS_OK; } /* Verify that required actual IPv6 header fields are as the script expected. */ static int verify_ipv6( const struct packet *actual_packet, const struct packet *script_packet, int layer, bool strict, char **error) { const struct ipv6 *actual_ipv6 = actual_packet->headers[layer].h.ipv6; const struct ipv6 *script_ipv6 = script_packet->headers[layer].h.ipv6; if (check_field("ipv6_version", script_ipv6->version, actual_ipv6->version, error) || (strict && check_field("ipv6_payload_len", (ntohs(script_ipv6->payload_len) + tcp_options_allowance(actual_packet, script_packet)), ntohs(actual_ipv6->payload_len), error)) || check_field("ipv6_next_header", script_ipv6->next_header, actual_ipv6->next_header, error)) return STATUS_ERR; if (verify_outbound_live_tos(script_packet->tos_chk, ipv6_tos_byte(actual_ipv6), ipv6_tos_byte(script_ipv6), error)) return STATUS_ERR; if (verify_outbound_live_ttl_or_hl(ipv6_hoplimit_byte(actual_ipv6), ipv6_hoplimit_byte(script_ipv6), error)) return STATUS_ERR; return STATUS_OK; } /* Verify that required actual TCP header fields are as the script expected. */ static int verify_tcp( const struct packet *actual_packet, const struct packet *script_packet, int layer, bool strict, char **error) { const struct tcp *actual_tcp = actual_packet->headers[layer].h.tcp; const struct tcp *script_tcp = script_packet->headers[layer].h.tcp; int script_payload_len = packet_payload_len(script_packet); if (check_field("tcp_data_offset", (script_tcp->doff + tcp_options_allowance(actual_packet, script_packet)/sizeof(u32)), actual_tcp->doff, error) || (strict && check_field("tcp_fin", script_tcp->fin, actual_tcp->fin, error)) || check_field("tcp_syn", script_tcp->syn, actual_tcp->syn, error) || check_field("tcp_rst", script_tcp->rst, actual_tcp->rst, error) || (strict && check_field("tcp_psh", script_tcp->psh, actual_tcp->psh, error)) || check_field("tcp_ack", script_tcp->ack, actual_tcp->ack, error) || check_field("tcp_urg", script_tcp->urg, actual_tcp->urg, error) || check_field("tcp_ece", script_tcp->ece, actual_tcp->ece, error) || (strict && check_field("tcp_cwr", script_tcp->cwr, actual_tcp->cwr, error)) || check_field("tcp_reserved_bits", script_tcp->res1, actual_tcp->res1, error) || (strict && check_field("tcp_seq", ntohl(script_tcp->seq), ntohl(actual_tcp->seq), error)) || (!strict && check_field("tcp_seq", ntohl(script_tcp->seq) + script_payload_len, ntohl(actual_tcp->seq), error)) || check_field("tcp_ack_seq", ntohl(script_tcp->ack_seq), ntohl(actual_tcp->ack_seq), error) || (script_packet->flags & FLAG_WIN_NOCHECK ? STATUS_OK : check_field("tcp_window", ntohs(script_tcp->window), ntohs(actual_tcp->window), error)) || check_field("tcp_urg_ptr", ntohs(script_tcp->urg_ptr), ntohs(actual_tcp->urg_ptr), error)) return STATUS_ERR; return STATUS_OK; } /* Verify that required actual UDP header fields are as the script expected. */ static int verify_udp( const struct packet *actual_packet, const struct packet *script_packet, int layer, bool strict, char **error) { const struct udp *actual_udp = actual_packet->headers[layer].h.udp; const struct udp *script_udp = script_packet->headers[layer].h.udp; /* udp_len is either filled in by packetdrill or specified by user. * If strict is set, we should check udp_len */ if (strict && check_field("udp_len", ntohs(script_udp->len), ntohs(actual_udp->len), error)) return STATUS_ERR; return STATUS_OK; } /* Verify that required actual GRE header fields are as the script expected. */ static int verify_gre( const struct packet *actual_packet, const struct packet *script_packet, int layer, bool strict, char **error) { const struct gre *actual_gre = actual_packet->headers[layer].h.gre; const struct gre *script_gre = script_packet->headers[layer].h.gre; int i = 0; if (check_field("gre_len", gre_len(script_gre), gre_len(actual_gre), error)) return STATUS_ERR; if (script_gre->flags != actual_gre->flags) { asprintf(error, "mismatch in GRE flags"); return STATUS_ERR; } if (script_gre->proto != actual_gre->proto) { asprintf(error, "mismatch in GRE proto"); return STATUS_ERR; } if (script_gre->has_checksum || script_gre->has_routing) { if (script_gre->be16[0] != actual_gre->be16[0]) { asprintf(error, "mismatch in GRE sum"); return STATUS_ERR; } if (script_gre->be16[1] != actual_gre->be16[1]) { asprintf(error, "mismatch in GRE off"); return STATUS_ERR; } i++; } if (script_gre->has_key) { if (script_gre->be32[i] != actual_gre->be32[i]) { asprintf(error, "mismatch in GRE key"); return STATUS_ERR; } i++; } if (script_gre->has_seq) { if (script_gre->be32[i] != actual_gre->be32[i]) { asprintf(error, "mismatch in GRE seq"); return STATUS_ERR; } i++; } return STATUS_OK; } /* Verify that required actual MPLS header fields are as the script expected. */ static int verify_mpls( const struct packet *actual_packet, const struct packet *script_packet, int layer, bool strict, char **error) { const struct header *actual_header = &actual_packet->headers[layer]; const struct header *script_header = &script_packet->headers[layer]; const struct mpls *actual_mpls = actual_packet->headers[layer].h.mpls; const struct mpls *script_mpls = script_packet->headers[layer].h.mpls; int num_entries = script_header->header_bytes / sizeof(struct mpls); int i = 0; if (script_header->header_bytes != actual_header->header_bytes) { asprintf(error, "mismatch in MPLS label stack depth"); return STATUS_ERR; } for (i = 0; i < num_entries; ++i) { const struct mpls *actual_entry = actual_mpls + i; const struct mpls *script_entry = script_mpls + i; if (memcmp(actual_entry, script_entry, sizeof(*script_entry))) { asprintf(error, "mismatch in MPLS label %d", i); return STATUS_ERR; } } return STATUS_OK; } /* Verify type and code field in ICMPv4 header */ static int verify_icmpv4( const struct packet *actual_packet, const struct packet *script_packet, int layer, bool strict, char **error) { const struct icmpv4 *actual_icmpv4 = actual_packet->headers[layer].h.icmpv4; const struct icmpv4 *script_icmpv4 = script_packet->headers[layer].h.icmpv4; if (check_field("icmp_type", script_icmpv4->type, actual_icmpv4->type, error)) return STATUS_ERR; if (check_field("icmp_code", script_icmpv4->code, actual_icmpv4->code, error)) return STATUS_ERR; return STATUS_OK; } /* Verify type and code field in ICMPv6 header */ static int verify_icmpv6( const struct packet *actual_packet, const struct packet *script_packet, int layer, bool strict, char **error) { const struct icmpv6 *actual_icmpv6 = actual_packet->headers[layer].h.icmpv6; const struct icmpv6 *script_icmpv6 = script_packet->headers[layer].h.icmpv6; if (check_field("icmp_type", script_icmpv6->type, actual_icmpv6->type, error)) return STATUS_ERR; if (check_field("icmp_code", script_icmpv6->code, actual_icmpv6->code, error)) return STATUS_ERR; return STATUS_OK; } typedef int (*verifier_func)( const struct packet *actual_packet, const struct packet *script_packet, int layer, bool strict, char **error); /* Verify that required actual header fields are as the script expected. */ static int verify_header( const struct packet *actual_packet, const struct packet *script_packet, int layer, bool strict, char **error) { verifier_func verifiers[HEADER_NUM_TYPES] = { [HEADER_IPV4] = verify_ipv4, [HEADER_IPV6] = verify_ipv6, [HEADER_GRE] = verify_gre, [HEADER_MPLS] = verify_mpls, [HEADER_TCP] = verify_tcp, [HEADER_UDP] = verify_udp, [HEADER_ICMPV4] = verify_icmpv4, [HEADER_ICMPV6] = verify_icmpv6, }; verifier_func verifier = NULL; const struct header *actual_header = &actual_packet->headers[layer]; const struct header *script_header = &script_packet->headers[layer]; enum header_t type = script_header->type; if (script_header->type != actual_header->type) { asprintf(error, "live packet header layer %d: " "expected: %s header vs actual: %s header", layer, header_type_info(script_header->type)->name, header_type_info(actual_header->type)->name); return STATUS_ERR; } assert(type > HEADER_NONE); assert(type < HEADER_NUM_TYPES); verifier = verifiers[type]; assert(verifier != NULL); return verifier(actual_packet, script_packet, layer, strict, error); } /* Verify that required actual header fields are as the script expected. * This function has a secondary use. It is also used to verify compatibility * between two consecutive packet fragments that are candidates for aggregation. * The later mode is invoked with parameter 'strict' set to false. */ static int verify_outbound_live_headers( const struct packet *actual_packet, const struct packet *script_packet, bool strict, char **error) { const int actual_headers = packet_header_count(actual_packet); const int script_headers = packet_header_count(script_packet); int i; assert((actual_packet->ipv4 != NULL) || (actual_packet->ipv6 != NULL)); assert((actual_packet->tcp != NULL) || (actual_packet->udp != NULL) || (actual_packet->icmpv4 != NULL) || (actual_packet->icmpv6 != NULL)); if (actual_headers != script_headers) { asprintf(error, "%s packet header layers: " "expected: %d headers vs actual: %d headers", (strict ? "live" : "aggregate candidate"), script_headers, actual_headers); return STATUS_ERR; } /* Compare actual vs script headers, layer by layer. */ for (i = 0; i < ARRAY_SIZE(script_packet->headers); ++i) { if (script_packet->headers[i].type == HEADER_NONE) break; if (verify_header(actual_packet, script_packet, i, strict, error)) return STATUS_ERR; } return STATUS_OK; } /* Verify the flow label in IPv6 header is as expected * Note: the initial value for a flow comes from the first packet */ static int verify_outbound_live_ipv6_flowlabel( struct socket *socket, struct packet *actual_packet, struct packet *script_packet, char **error) { u32 script_flowlabel = ipv6_flow_label(script_packet->ipv6); u32 live_flowlabel = ipv6_flow_label(actual_packet->ipv6); /* Return directly if script_flowlabel is not set */ if (!script_flowlabel) return STATUS_OK; /* Check flowlabel is marked in live packet */ if (!live_flowlabel) { asprintf(error, "flowlabel unmarked in the live packet"); return STATUS_ERR; } /* Initialize flowlabel_map in socket */ if (!socket->flowlabel_map.flowlabel_script) { socket->flowlabel_map.flowlabel_script = script_flowlabel; socket->flowlabel_map.flowlabel_live = live_flowlabel; return STATUS_OK; } /* Expecting different flowlabel */ if (script_flowlabel != socket->flowlabel_map.flowlabel_script) { if (live_flowlabel != socket->flowlabel_map.flowlabel_live) { socket->flowlabel_map.flowlabel_script = script_flowlabel; socket->flowlabel_map.flowlabel_live = live_flowlabel; return STATUS_OK; } else { asprintf(error, "expected a different flowlabel but got " "the same"); return STATUS_ERR; } /* Expecting consistent flowlabel */ } else { if (live_flowlabel == socket->flowlabel_map.flowlabel_live) { return STATUS_OK; } else { asprintf(error, "inconsistent flowlabels for this packet: " "expected: 0x%x vs actual: 0x%x", socket->flowlabel_map.flowlabel_live, live_flowlabel); return STATUS_ERR; } } /* Should not reach here, only for compiling */ return STATUS_OK; } static int verify_outbound_tcp_option( struct config *config, struct packet *actual_packet, struct packet *script_packet, struct tcp_option *actual_option, struct tcp_option *script_option, char **error) { u32 script_ts_val, actual_ts_val; int ts_val_tick_usecs; long tolerance_usecs; tolerance_usecs = config->tolerance_usecs; switch (actual_option->kind) { case TCPOPT_EOL: case TCPOPT_NOP: break; case TCPOPT_TIMESTAMP: script_ts_val = packet_tcp_ts_val(script_packet); actual_ts_val = packet_tcp_ts_val(actual_packet); ts_val_tick_usecs = config->tcp_ts_tick_usecs; /* See if the deviation from the script TS val is * within our configured tolerance. */ if (ts_val_tick_usecs && ((abs((s32)(actual_ts_val - script_ts_val)) * ts_val_tick_usecs) > tolerance_usecs)) { asprintf(error, "bad outbound TCP timestamp value, tolerance %ld", tolerance_usecs); return STATUS_ERR; } break; default: if (script_option->length != actual_option->length) { asprintf(error, "bad lengths for outbound TCP option %d", script_option->kind); return STATUS_ERR; } if (script_option->length > 2 && memcmp(&actual_option->data, &script_option->data, actual_option->length - 2) != 0) { asprintf(error, "bad value outbound TCP option %d", script_option->kind); return STATUS_ERR; } } return STATUS_OK; } /* Verify that the TCP option values matched expected values. */ static int verify_outbound_live_tcp_options( struct config *config, struct packet *actual_packet, struct packet *script_packet, char **error) { struct tcp_options_iterator a_iter, s_iter; struct tcp_option *a_opt, *s_opt; /* See if we should validate TCP options at all. */ if (script_packet->flags & FLAG_OPTIONS_NOCHECK) return STATUS_OK; a_opt = tcp_options_begin(actual_packet, &a_iter), s_opt = tcp_options_begin(script_packet, &s_iter); /* TCP options are expected to be a deterministic order. */ while (a_opt != NULL || s_opt != NULL) { if (a_opt == NULL || s_opt == NULL || a_opt->kind != s_opt->kind) { asprintf(error, "bad outbound TCP options"); return STATUS_ERR; } if (verify_outbound_tcp_option(config, actual_packet, script_packet, a_opt, s_opt, error) != STATUS_OK) { return STATUS_ERR; } a_opt = tcp_options_next(&a_iter, error); s_opt = tcp_options_next(&s_iter, error); } return STATUS_OK; } /* Verify TCP/UDP payload matches expected value. */ static int verify_outbound_live_payload( struct packet *actual_packet, struct packet *script_packet, char **error) { /* Diff the TCP/UDP data payloads. We've already implicitly * checked their length by checking the IP and TCP/UDP headers. */ assert(packet_payload_len(actual_packet) == packet_payload_len(script_packet)); if (memcmp(packet_payload(script_packet), packet_payload(actual_packet), packet_payload_len(script_packet)) != 0) { asprintf(error, "incorrect outbound data payload"); return STATUS_ERR; } return STATUS_OK; } /* Verify that the outbound packet correctly matches the expected * outbound packet from the script. * Return STATUS_OK upon success. If non_fatal_packet is unset in the * config, return STATUS_ERR upon all failures. With non_fatal_packet, * return STATUS_WARN upon non-fatal failures. */ static int verify_outbound_live_packet( struct state *state, struct socket *socket, struct packet *script_packet, struct packet *live_packet, char **error) { DEBUGP("verify_outbound_live_packet\n"); int result = STATUS_ERR; /* return value */ bool non_fatal = false; /* ok to continue on error? */ enum event_time_t time_type = state->event->time_type; s64 script_usecs = state->event->time_usecs; s64 script_usecs_end = state->event->time_usecs_end; /* The "actual" packet will be the live packet with values * mapped into script space. */ struct packet *actual_packet = packet_copy(live_packet); s64 actual_usecs = live_time_to_script_time_usecs( state, live_packet->time_usecs); /* Before mapping, see if the live outgoing checksums are correct. */ if (verify_outbound_live_checksums(live_packet, error)) goto out; /* Map live packet values into script space for easy comparison. */ if (map_outbound_live_packet( socket, live_packet, actual_packet, script_packet, error)) goto out; /* Verify actual IP, TCP/UDP header values matched expected ones. */ if (verify_outbound_live_headers(actual_packet, script_packet, true, error)) { non_fatal = true; goto out; } if (script_packet->ipv6) { if (verify_outbound_live_ipv6_flowlabel(socket, actual_packet, script_packet, error)) { non_fatal = true; goto out; } } if (script_packet->tcp) { /* Verify TCP options matched expected values. */ if (verify_outbound_live_tcp_options( state->config, actual_packet, script_packet, error)) { non_fatal = true; goto out; } } /* Verify TCP/UDP payload matches expected value. * We skip the payload check for ICMP packets as the payload generated * by packetdrill is incomplete. */ if (!actual_packet->icmpv4 && !actual_packet->icmpv6) { if (verify_outbound_live_payload(actual_packet, script_packet, error)) { non_fatal = true; goto out; } } /* Verify that kernel sent packet at the time the script expected. */ DEBUGP("packet time_usecs: %lld\n", live_packet->time_usecs); if (verify_time(state, time_type, script_usecs, script_usecs_end, live_packet->time_usecs, "outbound packet", error)) { non_fatal = true; goto out; } result = STATUS_OK; out: add_packet_dump(error, "script", script_packet, script_usecs, DUMP_SHORT); if (actual_packet != NULL) { add_packet_dump(error, "actual", actual_packet, actual_usecs, DUMP_SHORT); packet_free(actual_packet); } if (result == STATUS_ERR && non_fatal && state->config->non_fatal_packet) { result = STATUS_WARN; } return result; } /* Check compatibility between two sequential packet fragments that are * candidates for aggregation. Parameter current_payload represents the payload * of the current fragment, while expected_payload repesents how much we need to * match the script packet payload, and it is used to determine if this fragment * is the last one. */ static int verify_packet_fragments( const struct packet *current_fragment, const struct packet *previous_fragment, int current_payload, int sniffed_payload, int expected_total_payload, char **error) { /* Ensure that current fragment headers match the previous fragment * headers. */ if (verify_outbound_live_headers(current_fragment, previous_fragment, false, error)) return STATUS_ERR; /* If this is not the last fragment, also check that its payload matches * the payload of the previous fragment. */ if (current_payload < expected_total_payload - sniffed_payload && current_payload != packet_payload_len(previous_fragment)) { asprintf(error, "fragment payload: expected %d bytes vs " "actual %d bytes; " "total payload: expected %d bytes vs " "actual %d bytes", packet_payload_len(previous_fragment), current_payload, expected_total_payload, current_payload + sniffed_payload); return STATUS_ERR; } return STATUS_OK; } /* Sniff the next outbound live packet and return it. */ static int sniff_outbound_live_packet( struct state *state, struct socket *expected_socket, struct packet **packet, char **error) { DEBUGP("sniff_outbound_live_packet\n"); struct socket *socket = NULL; enum direction_t direction = DIRECTION_INVALID; assert(*packet == NULL); while (1) { if (netdev_receive(state->netdev, packet, error)) return STATUS_ERR; /* See if the packet matches an existing, known socket. */ socket = find_socket_for_live_packet(state, *packet, &direction); if ((socket != NULL) && (direction == DIRECTION_OUTBOUND)) break; /* See if the packet matches a recent connect() call. */ socket = find_connect_for_live_packet(state, *packet, &direction); if ((socket != NULL) && (direction == DIRECTION_OUTBOUND)) break; packet_free(*packet); *packet = NULL; } assert(*packet != NULL); assert(socket != NULL); assert(direction == DIRECTION_OUTBOUND); if (socket != expected_socket) { asprintf(error, "packet is not for expected socket"); return STATUS_ERR; } return STATUS_OK; } /* Return true iff the given packet could be sent/received by the socket. */ static bool is_script_packet_match_for_socket( struct state *state, struct packet *packet, struct socket *socket) { const bool is_packet_icmp = (packet->icmpv4 || packet->icmpv6); if (socket->protocol == IPPROTO_TCP) return packet->tcp || is_packet_icmp; else if (socket->protocol == IPPROTO_UDP) return packet->udp || is_packet_icmp; else if (socket->protocol == IPPROTO_ICMP) return (packet->icmpv4 != NULL); else if (socket->protocol == IPPROTO_ICMPV6) return (packet->icmpv6 != NULL); else assert(!"unsupported layer 4 protocol in socket"); return false; } /* Find or create a socket object matching the given packet. */ static int find_or_create_socket_for_script_packet( struct state *state, struct packet *packet, enum direction_t direction, struct socket **socket, char **error) { *socket = NULL; DEBUGP("find_or_create_socket_for_script_packet\n"); if (packet->tcp != NULL) { /* Is this an inbound packet matching a listening * socket? If so, this call will create a new child * socket object. */ *socket = handle_listen_for_script_packet(state, packet, direction); if (*socket != NULL) return STATUS_OK; /* Is this an outbound packet matching a connecting socket? */ *socket = handle_connect_for_script_packet(state, packet, direction); if (*socket != NULL) return STATUS_OK; } /* See if there is an existing connection to handle this packet. */ if (state->socket_under_test != NULL && is_script_packet_match_for_socket(state, packet, state->socket_under_test)) { *socket = state->socket_under_test; return STATUS_OK; } /* For tcp packet with foreign port, use state->socket_under_test */ if (packet->tcp && (packet->tcp->src_port || packet->tcp->dst_port)) { *socket = state->socket_under_test; return STATUS_OK; } /* For udp packet with foreign port, use state->socket_under_test */ if (packet->udp && (packet->udp->src_port || packet->udp->dst_port)) { *socket = state->socket_under_test; return STATUS_OK; } asprintf(error, "no matching socket for script packet"); return STATUS_ERR; } /* Perform the action implied by an outbound packet in a script * Return STATUS_OK upon success. Without --use_expect, return STATUS_ERR * upon all failures. With --use_expect, return STATUS_WARN upon non-fatal * failures. */ static int do_outbound_script_packet( struct state *state, struct packet *packet, struct socket *socket, char **error) { DEBUGP("do_outbound_script_packet\n"); int result = STATUS_ERR; /* return value */ struct packet *live_packet = NULL; struct packet_list *sniffed_packets_start = NULL; /* list head */ struct packet_list *sniffed_packets_end = NULL; /* list tail */ int packet_count = 0; /* number of sniffed packets */ int expected_payload_len = packet_payload_len(packet); int sniffed_payload_len = 0; if ((socket->state == SOCKET_PASSIVE_PACKET_RECEIVED) && packet->tcp && packet->tcp->syn && packet->tcp->ack) { /* Script says we should see an outbound server SYNACK. */ socket->script.local_isn = ntohl(packet->tcp->seq); DEBUGP("SYNACK script.local_isn: %u\n", socket->script.local_isn); } DEBUGP("Expecting packet with payload %d bytes\n", expected_payload_len); /* To allow remote mode execution of scripts that expect TSO or GSO * segmentation, this loop aggregates sequential outbound packets until * they reach the length expected by the script. * We explicitly disable aggregation in local mode, to facilitate * testing of certain features, such as automatic packet sizing, without * interferences from the packet aggregation algorithm. */ do { struct packet_list *sniffed = NULL; int live_payload = 0; /* Sniff outbound live packet and verify it's for the right * socket. */ if (sniff_outbound_live_packet(state, socket, &live_packet, error)) goto out; live_payload = packet_payload_len(live_packet); DEBUGP("Sniffed packet with payload %d bytes\n", live_payload); if ((socket->state == SOCKET_PASSIVE_PACKET_RECEIVED) && packet->tcp && packet->tcp->syn && packet->tcp->ack) { socket->state = SOCKET_PASSIVE_SYNACK_SENT; socket->live.local_isn = ntohl(live_packet->tcp->seq); DEBUGP("SYNACK live.local_isn: %u\n", socket->live.local_isn); } verbose_packet_dump(state, "outbound sniffed", live_packet, live_time_to_script_time_usecs( state, live_packet->time_usecs)); /* Save the TCP header so we can reset the connection at the * end. */ if (live_packet->tcp) { socket->last_outbound_tcp_header = *(live_packet->tcp); /* Rewrite TCP sequence number */ if (tcp_convert_seq_number(socket, live_packet, error)) goto out; } sniffed = packet_list_new(); sniffed->packet = live_packet; /* Reset live_packet so we can sniff the next packet if * needed. */ live_packet = NULL; if (sniffed_packets_start == NULL) { sniffed_packets_start = sniffed; } else { /* In remote mode, we do not see socket openings on the * server side. Sometimes, there are residual packets * sent over an old socket that packetdrill tests do not * explicitly account for. In local mode, such packets * are ignored because they don't match the test socket. * In remote mode, we sniff them because we do not know * any better. However, while we are trying to sniff * enough packets for the expected payload, we can check * if the source IPs/ports of the packets match. Discard * packets on a missmatch and start anew. * TODO(gmx): send syscall state from the wire_client to * the wire_server and get rid of this test. */ struct tuple old_packet_tuple, new_packet_tuple; get_packet_tuple(sniffed_packets_end->packet, &old_packet_tuple); get_packet_tuple(sniffed->packet, &new_packet_tuple); if (!is_equal_tuple(&old_packet_tuple, &new_packet_tuple)) { /* Discard old packets. */ DEBUGP("Discarding previously sniffed %d " "packets with total payload %d\n", packet_count, sniffed_payload_len); packet_list_free(sniffed_packets_start); packet_count = 0; sniffed_payload_len = 0; sniffed_packets_start = sniffed; } else { if (verify_packet_fragments( sniffed->packet, sniffed_packets_end->packet, live_payload, sniffed_payload_len, expected_payload_len, error)) { packet_list_free(sniffed); goto out; } sniffed_packets_end->next = sniffed; } } sniffed_packets_end = sniffed; packet_count++; sniffed_payload_len += live_payload; } while (sniffed_payload_len < expected_payload_len && (!state->config->strict_segments || state->config->is_wire_server)); /* Check that we matched the payload size. */ if (sniffed_payload_len != expected_payload_len) { asprintf(error, "live packet payload: expected %d bytes vs " "actual %d bytes", expected_payload_len, sniffed_payload_len); goto out; } /* If we have just one packet, use it directly, no need to incur the * aggregation overhead. */ if (packet_count == 1) { live_packet = sniffed_packets_start->packet; sniffed_packets_start->packet = NULL; } else { if (DEBUG_LOGGING) { char *debug = NULL; add_packet_dump(&debug, "first", sniffed_packets_start->packet, live_time_to_script_time_usecs(state, sniffed_packets_start->packet->time_usecs), DUMP_FULL); DEBUGP("%s\n", debug); free(debug); debug = NULL; add_packet_dump(&debug, "last", sniffed_packets_end->packet, live_time_to_script_time_usecs(state, sniffed_packets_end->packet->time_usecs), DUMP_FULL); DEBUGP("%s\n", debug); free(debug); } live_packet = aggregate_packets(sniffed_packets_start, sniffed_packets_end, sniffed_payload_len); if (DEBUG_LOGGING) { char *debug = NULL; add_packet_dump(&debug, "live", live_packet, live_time_to_script_time_usecs(state, live_packet->time_usecs), DUMP_FULL); DEBUGP("%s\n", debug); free(debug); } } /* Verify the bits the kernel sent were what the script expected. */ result = verify_outbound_live_packet( state, socket, packet, live_packet, error); out: if (live_packet != NULL) packet_free(live_packet); packet_list_free(sniffed_packets_start); return result; } /* Checksum the packet and inject it into the kernel under test. */ static int send_live_ip_packet(struct netdev *netdev, struct packet *packet) { assert(packet->ip_bytes > 0); /* We do IPv4 and IPv6 */ assert(packet->ipv4 || packet->ipv6); /* We only do TCP, UDP, and ICMP */ assert(packet->tcp || packet->udp || packet->icmpv4 || packet->icmpv6); /* Fill in layer 3 and layer 4 checksums */ checksum_packet(packet); return netdev_send(netdev, packet); } /* Perform the action implied by an inbound packet in a script */ static int do_inbound_script_packet( struct state *state, struct packet *packet, struct socket *socket, char **error) { DEBUGP("do_inbound_script_packet\n"); int result = STATUS_ERR; /* return value */ if ((socket->state == SOCKET_PASSIVE_SYNACK_SENT) && packet->tcp && packet->tcp->ack) { /* Received the ACK that completes the 3-way handshake. */ socket->state = SOCKET_PASSIVE_SYNACK_ACKED; } else if ((socket->state == SOCKET_ACTIVE_SYN_SENT) && packet->tcp && packet->tcp->syn && packet->tcp->ack) { /* Received the server's SYNACK, which ACKs our SYN. */ socket->state = SOCKET_ACTIVE_SYN_ACKED; socket->script.remote_isn = ntohl(packet->tcp->seq); socket->live.remote_isn = ntohl(packet->tcp->seq); } /* For anyip UDP/ICMP rx test, no tx packet will be sent before rx. * So the 4-tuple addr in socket->live are all 0. * We need to fill in 4-tuple first before sending pkt out. */ if (state->config->is_anyip && (packet->udp || packet->icmpv4 || packet->icmpv6)) { socket->live.remote.ip = state->config->live_remote_ip; socket->live.local.ip = state->config->live_local_ip; if (!(socket->live.remote.port)) socket->live.remote.port = htons(state->config->live_connect_port); if (!(socket->live.local.port)) socket->live.local.port = htons(state->config->live_bind_port); } /* Start with a bit-for-bit copy of the packet from the script. */ struct packet *live_packet = packet_copy(packet); /* Map packet fields from script values to live values. */ if (map_inbound_packet(state, socket, live_packet, error)) goto out; verbose_packet_dump(state, "inbound injected", live_packet, live_time_to_script_time_usecs( state, now_usecs(state))); if (live_packet->tcp) { /* Save the TCP header so we can reset the connection later. */ socket->last_injected_tcp_header = *(live_packet->tcp); socket->last_injected_tcp_payload_len = packet_payload_len(live_packet); } /* Inject live packet into kernel. */ result = send_live_ip_packet(state->netdev, live_packet); out: packet_free(live_packet); return result; } int run_packet_event( struct state *state, struct event *event, struct packet *packet, char **error) { DEBUGP("%d: packet\n", event->line_number); char *err = NULL; struct socket *socket = NULL; int result = STATUS_ERR; enum direction_t direction = packet_direction(packet); assert(direction != DIRECTION_INVALID); if (find_or_create_socket_for_script_packet( state, packet, direction, &socket, &err)) goto out; assert(socket != NULL); if (direction == DIRECTION_OUTBOUND) { /* We don't wait for outbound event packets because we * want to start sniffing ASAP in order to see if * packets go out earlier than the script specifies. */ result = do_outbound_script_packet(state, packet, socket, &err); if (result == STATUS_WARN) goto out; else if (result == STATUS_ERR) goto out; } else if (direction == DIRECTION_INBOUND) { wait_for_event(state); if (do_inbound_script_packet(state, packet, socket, &err)) goto out; } else { assert(!"bad direction"); /* internal bug */ } return STATUS_OK; /* everything went fine */ out: /* Format a more complete error message and return that. */ asprintf(error, "%s:%d: %s handling packet: %s\n", state->config->script_path, event->line_number, result == STATUS_ERR ? "error" : "warning", err); free(err); return result; } /* Inject a TCP RST packet to clear the connection state out of the * kernel, so the connection does not continue to retransmit packets * that may be sniffed during later test executions and cause false * negatives. */ int reset_connection(struct state *state, struct socket *socket) { char *error = NULL; u32 seq = 0, ack_seq = 0; u16 window = 0; struct packet *packet = NULL; struct tuple live_inbound; const struct ip_info ip_info = {{TOS_CHECK_NONE, 0}, 0}; int result = 0; /* Pick TCP header fields to be something the kernel will accept. */ if (socket->last_injected_tcp_header.ack) { /* If we've already injected something, then use a sequence * number right after the last one we injected, and ACK * the last thing we ACKed, and offer the same receive * window we last offered. */ seq = (ntohl(socket->last_injected_tcp_header.seq) + (socket->last_injected_tcp_header.syn ? 1 : 0) + (socket->last_injected_tcp_header.fin ? 1 : 0) + socket->last_injected_tcp_payload_len); ack_seq = ntohl(socket->last_injected_tcp_header.ack_seq); window = ntohs(socket->last_injected_tcp_header.window); } else if (socket->last_outbound_tcp_header.ack) { /* If the kernel ACKed something, then just make sure * we use the sequence number it ACKed, which will be * something it expects. */ seq = ntohl(socket->last_outbound_tcp_header.ack_seq); ack_seq = ntohl(socket->last_outbound_tcp_header.seq); } else { /* If the kernel didn't ACK anything, then it probably * sent only an initial SYN. So we get to send any * sequence number we want, but should send an ACK * suggesting we've seen the kernel's SYN. */ seq = 0; ack_seq = ntohl(socket->last_outbound_tcp_header.seq) + 1; } packet = new_tcp_packet(socket->address_family, DIRECTION_INBOUND, ip_info, 0, 0, "R.", seq, 0, ack_seq, window, 0, NULL, &error); if (packet == NULL) die("%s", error); /* Rewrite addresses and port to match inbound live traffic. */ socket_get_inbound(&socket->live, &live_inbound); set_packet_tuple(packet, &live_inbound); /* Inject live packet into kernel. */ result = send_live_ip_packet(state->netdev, packet); packet_free(packet); return result; } struct packets *packets_new(const struct state *state) { struct packets *packets = calloc(1, sizeof(struct packets)); /* cache a port */ packets->next_ephemeral_port = ephemeral_port(state->config->ip_version); return packets; } void packets_free(struct packets *packets) { memset(packets, 0, sizeof(*packets)); /* to help catch bugs */ free(packets); }