/* * 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) * * Server-side code for remote on-the-wire testing using a real NIC. */ #include "wire_server.h" #include #include #include #include "link_layer.h" #include "logging.h" #include "run.h" #include "wire_conn.h" #include "wire_server.h" #include "wire_server_netdev.h" /* Internal private state for the wire server to run one script. */ struct wire_server { struct wire_conn *wire_conn; /* connection to wire client */ u16 port; /* port we listen on */ int argc; /* args in client cmd line */ char **argv; /* client command line */ struct config config; /* run-time configuration */ struct script script; /* raw and parsed script */ struct state *state; /* interpreter engine state */ char *script_path; /* path of script (on cli!) */ char *script_buffer; /* contents of script */ char *wire_server_device; /* name of our eth interface */ struct ether_addr client_ether_addr; /* wire client hardware addr */ struct ether_addr server_ether_addr; /* wire server hardware addr */ enum event_t last_event_type; /* type of previous event */ int num_events; /* events executed so far */ }; static struct wire_server *wire_server_new(struct wire_conn *accepted_conn, const char *wire_server_device, u16 wire_server_port) { struct wire_server *wire_server = calloc(1, sizeof(struct wire_server)); wire_server->wire_conn = accepted_conn; wire_server->wire_server_device = strdup(wire_server_device); get_hw_address(wire_server_device, &wire_server->server_ether_addr); wire_server->port = wire_server_port; return wire_server; } static void wire_server_free(struct wire_server *wire_server) { wire_conn_free(wire_server->wire_conn); free(wire_server->script_path); free(wire_server->script_buffer); free(wire_server->wire_server_device); memset(wire_server, 0, sizeof(*wire_server)); /* catch bugs */ free(wire_server); } /* Unserialize argv from a single string with '\0' characters between * args. Add a --wire_server so that we don't have an identity crisis. */ static void wire_server_unserialize_argv(struct wire_server *wire_server, const char *args, int args_len) { int argc, i; char **argv = NULL; const char *end = NULL; argc = 0; for (i = 0; i < args_len; ++i) { if (args[i] == '\0') ++argc; } ++argc; /* for --wire_server argument */ DEBUGP("argc = %d\n", argc); /* We use argc+1 here because, following main() calling * conventions, we make the array element at argv[argc] a NULL * pointer. */ argv = calloc(argc + 1, sizeof(char *)); end = args; for (i = 0; i < argc; ++i) { argv[i] = strdup(end); end += strlen(end) + 1; /* + 1 for '\0' */ } asprintf(&argv[argc-1], "--wire_server"); for (i = 0; i < argc; ++i) DEBUGP("argv[%d] = '%s'\n", i, argv[i]); wire_server->argc = argc; wire_server->argv = argv; } /* Receive a WIRE_COMMAND_LINE_ARGS message */ static int wire_server_receive_args(struct wire_server *wire_server) { enum wire_op_t op = WIRE_INVALID; void *buf = NULL; int buf_len = -1; if (wire_conn_read(wire_server->wire_conn, &op, &buf, &buf_len)) return STATUS_ERR; if (op != WIRE_COMMAND_LINE_ARGS) { fprintf(stderr, "bad wire client: expected WIRE_COMMAND_LINE_ARGS\n"); return STATUS_ERR; } wire_server_unserialize_argv(wire_server, buf, buf_len); return STATUS_OK; } /* Receive the path name of the script we're about to run. */ static int wire_server_receive_script_path(struct wire_server *wire_server) { enum wire_op_t op = WIRE_INVALID; void *buf = NULL; int buf_len = -1; if (wire_conn_read(wire_server->wire_conn, &op, &buf, &buf_len)) return STATUS_ERR; if (op != WIRE_SCRIPT_PATH) { fprintf(stderr, "bad wire client: expected WIRE_SCRIPT_PATH\n"); return STATUS_ERR; } wire_server->script_path = strndup(buf, buf_len); return STATUS_OK; } /* Receive the script we're about to run. */ static int wire_server_receive_script(struct wire_server *wire_server) { enum wire_op_t op = WIRE_INVALID; void *buf = NULL; int buf_len = -1; if (wire_conn_read(wire_server->wire_conn, &op, &buf, &buf_len)) return STATUS_ERR; if (op != WIRE_SCRIPT) { fprintf(stderr, "bad wire client: expected WIRE_SCRIPT\n"); return STATUS_ERR; } wire_server->script_buffer = strndup(buf, buf_len); return STATUS_OK; } /* Receive the ethernet address to which the server should send packets. */ static int wire_server_receive_hw_address(struct wire_server *wire_server) { enum wire_op_t op = WIRE_INVALID; void *buf = NULL; int buf_len = -1; if (wire_conn_read(wire_server->wire_conn, &op, &buf, &buf_len)) return STATUS_ERR; if (op != WIRE_HARDWARE_ADDR) { fprintf(stderr, "bad wire client: expected WIRE_HARDWARE_ADDR\n"); return STATUS_ERR; } if (buf_len != sizeof(wire_server->client_ether_addr)) { fprintf(stderr, "bad wire client: bad hw address length\n"); return STATUS_ERR; } ether_copy(&wire_server->client_ether_addr, buf); return STATUS_OK; } /* Send a message to tell the client we're ready to excecute the script. */ static int wire_server_send_server_ready(struct wire_server *wire_server) { if (wire_conn_write(wire_server->wire_conn, WIRE_SERVER_READY, NULL, 0)) { fprintf(stderr, "error sending WIRE_SERVER_READY\n"); return STATUS_ERR; } return STATUS_OK; } /* Wait for the client to say it's starting script execution. */ static int wire_server_receive_client_starting(struct wire_server *wire_server) { enum wire_op_t op = WIRE_INVALID; void *buf = NULL; int buf_len = -1; if (wire_conn_read(wire_server->wire_conn, &op, &buf, &buf_len)) return STATUS_ERR; if (op != WIRE_CLIENT_STARTING) { fprintf(stderr, "bad wire client: expected WIRE_CLIENT_STARTING\n"); return STATUS_ERR; } if (buf_len != 0) { fprintf(stderr, "bad wire client: bad WIRE_CLIENT_STARTING length\n"); return STATUS_ERR; } return STATUS_OK; } /* Wait for the client request for the server to execute some packet events. */ static int wire_server_receive_packets_start(struct wire_server *wire_server) { enum wire_op_t op = WIRE_INVALID; void *buf = NULL; int buf_len = -1; struct wire_packets_start start; if (wire_conn_read(wire_server->wire_conn, &op, &buf, &buf_len)) return STATUS_ERR; if (op != WIRE_PACKETS_START) { fprintf(stderr, "bad wire client: expected WIRE_PACKETS_START\n"); return STATUS_ERR; } if (buf_len != sizeof(start)) { fprintf(stderr, "bad wire client: bad WIRE_PACKETS_START length\n"); return STATUS_ERR; } memcpy(&start, buf, sizeof(start)); if (ntohl(start.num_events) != wire_server->num_events) { fprintf(stderr, "bad client event count; expected %d but got %d", wire_server->num_events, ntohl(start.num_events)); return STATUS_ERR; } return STATUS_OK; } /* Send back to the client a human-readable warning about a fishy packet. */ static int wire_server_send_packet_warning(struct wire_server *wire_server, const char *warning) { if (wire_conn_write(wire_server->wire_conn, WIRE_PACKETS_WARN, warning, strlen(warning))) { fprintf(stderr, "error sending WIRE_PACKETS_WARN\n"); return STATUS_ERR; } return STATUS_OK; } /* Tell the client that the server is done executing some packet events. */ static int wire_server_send_packets_done(struct wire_server *wire_server, int result, const char *error) { struct wire_packets_done done; int error_len = strlen(error) + 1; /* +1 for '\0' */ int buf_len = sizeof(done) + error_len; char *buf = malloc(buf_len); done.result = htonl(result); done.num_events = htonl(wire_server->num_events); memcpy(buf, &done, sizeof(done)); memcpy(buf + sizeof(done), error, error_len); if (wire_conn_write(wire_server->wire_conn, WIRE_PACKETS_DONE, buf, buf_len)) { fprintf(stderr, "error sending WIRE_PACKETS_DONE\n"); return STATUS_ERR; } return STATUS_OK; } /* Coordinate with the wire client. See wire_client_next_event(). */ static int wire_server_next_event(struct wire_server *wire_server, struct event *event) { /* Wait for the client's request to start executing packet events. */ if (event && (event->type == PACKET_EVENT) && (wire_server->last_event_type != PACKET_EVENT)) { if (wire_server_receive_packets_start(wire_server)) return STATUS_ERR; } /* Send the result from server execution of packet events. */ if ((!event || (event->type != PACKET_EVENT)) && (wire_server->last_event_type == PACKET_EVENT)) { if (wire_server_send_packets_done(wire_server, STATUS_OK, "")) return STATUS_ERR; } if (event) { wire_server->last_event_type = event->type; ++wire_server->num_events; } return STATUS_OK; } /* Run the given packet event; send any error or warning back to the client. */ static int wire_server_run_packet_event( struct wire_server *wire_server, struct event *event, struct packet *packet, char **error) { int result = STATUS_OK; result = run_packet_event(wire_server->state, event, packet, error); if (result == STATUS_ERR) { /* When we sniff an incorrect packet, don't exit the * process (we're a daemon), just return the error * message via the TCP socket and finish the thread. */ DEBUGP("wire_server_run_packet_event: error!\n"); if (wire_server_send_packets_done(wire_server, STATUS_ERR, *error)) return STATUS_ERR; } else if (result == STATUS_WARN) { /* A non-fatal problem with the packet. Return the * warning message via the TCP socket and keep going. */ DEBUGP("wire_server_run_packet_event: warning!\n"); if (wire_server_send_packet_warning(wire_server, *error)) return STATUS_ERR; } return result; } /* Execute the server-side duties for remote on-the-wire testing using * a real NIC. Basically the server side just needs to send packets * over the wire (to the kernel under test) and sniff and verify * packets on the wire (from the kernel under test). This is analogous * to run_script(), which executes scripts for stand-alone mode, * and also executes the client side for remote on-the-wire testing * using a real NIC. */ static int wire_server_run_script(struct wire_server *wire_server, char **error) { struct state *state = wire_server->state; struct event *event = NULL; DEBUGP("wire_server_run_script\n"); state->live_start_time_usecs = now_usecs(); DEBUGP("live_start_time_usecs is %lld\n", state->live_start_time_usecs); while (1) { if (get_next_event(state, error)) return STATUS_ERR; event = state->event; if (event == NULL) break; if (wire_server_next_event(wire_server, event)) return STATUS_ERR; /* We adjust relative times after getting notification * that previous client-side events have completed. */ adjust_relative_event_times(state, event); switch (event->type) { case PACKET_EVENT: if (wire_server_run_packet_event(wire_server, event, event->event.packet, error) == STATUS_ERR) return STATUS_ERR; break; case SYSCALL_EVENT: DEBUGP("SYSCALL_EVENT happens on client side...\n"); break; case COMMAND_EVENT: DEBUGP("COMMAND_EVENT happens on client side...\n"); break; case CODE_EVENT: DEBUGP("CODE_EVENT happens on client side...\n"); break; case INVALID_EVENT: case NUM_EVENT_TYPES: assert(!"bogus type"); break; /* We omit default case so compiler catches missing values. */ } } /* Tell the client about any outstanding packet events it requested. */ wire_server_next_event(wire_server, NULL); DEBUGP("wire_server_run_script: done running\n"); return STATUS_OK; } /* Handle a wire connection from a client. */ static void *wire_server_thread(void *arg) { struct wire_server *wire_server = (struct wire_server *)arg; struct netdev *netdev = NULL; char *error = NULL; DEBUGP("wire_server_thread\n"); set_default_config(&wire_server->config); if (wire_server_receive_args(wire_server)) goto error_done; if (wire_server_receive_script_path(wire_server)) goto error_done; if (wire_server_receive_script(wire_server)) goto error_done; if (wire_server_receive_hw_address(wire_server)) goto error_done; if (parse_script_and_set_config(wire_server->argc, wire_server->argv, &wire_server->config, &wire_server->script, wire_server->script_path, wire_server->script_buffer)) goto error_done; set_scheduling_priority(); lock_memory(); netdev = wire_server_netdev_new(&wire_server->config, wire_server->wire_server_device, &wire_server->client_ether_addr, &wire_server->server_ether_addr); wire_server->state = state_new(&wire_server->config, &wire_server->script, netdev); if (wire_server_send_server_ready(wire_server)) goto error_done; if (wire_server_receive_client_starting(wire_server)) goto error_done; if (wire_server_run_script(wire_server, &error)) goto error_done; DEBUGP("wire_server_thread: finished test successfully\n"); error_done: if (error != NULL) fprintf(stderr, "%s\n", error); if (wire_server->state != NULL) state_free(wire_server->state); DEBUGP("wire_server_thread: connection is done\n"); wire_server_free(wire_server); return NULL; } static void start_wire_server_thread(struct wire_server *wire_server) { DEBUGP("start_wire_server_thread\n"); pthread_t thread; /* pthread thread handle */ if (pthread_create(&thread, NULL, wire_server_thread, wire_server) != 0) { die_perror("pthread_create"); } } void run_wire_server(const struct config *config) { struct wire_conn *listen_conn = NULL; wire_server_netdev_init(config->wire_server_device); listen_conn = wire_conn_new(); wire_conn_bind_listen(listen_conn, config->wire_server_port); while (1) { struct wire_conn *accepted_conn = NULL; wire_conn_accept(listen_conn, &accepted_conn); struct wire_server *wire_server = wire_server_new(accepted_conn, config->wire_server_device, config->wire_server_port); start_wire_server_thread(wire_server); } }