aboutsummaryrefslogtreecommitdiffstats
path: root/test/packetdrill/wire_client.c
diff options
context:
space:
mode:
Diffstat (limited to 'test/packetdrill/wire_client.c')
-rw-r--r--test/packetdrill/wire_client.c302
1 files changed, 302 insertions, 0 deletions
diff --git a/test/packetdrill/wire_client.c b/test/packetdrill/wire_client.c
new file mode 100644
index 0000000..33ebbd6
--- /dev/null
+++ b/test/packetdrill/wire_client.c
@@ -0,0 +1,302 @@
+/*
+ * 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)
+ *
+ * Client-side code for remote on-the-wire testing using a real NIC.
+ */
+
+#include "wire_client.h"
+
+#include "config.h"
+#include "link_layer.h"
+#include "script.h"
+#include "run.h"
+
+struct wire_client *wire_client_new(void)
+{
+ return calloc(1, sizeof(struct wire_client));
+}
+
+void wire_client_free(struct wire_client *wire_client)
+{
+ if (wire_client->wire_conn != NULL)
+ wire_conn_free(wire_client->wire_conn);
+
+ memset(wire_client, 0, sizeof(*wire_client)); /* help catch bugs */
+ free(wire_client);
+}
+
+static void wire_client_die(struct wire_client *wire_client,
+ const char *message)
+{
+ die("error in TCP connection to wire server: %s\n", message);
+}
+
+/* Serialize client-side argv into a single string with '\0'
+ * characters between args. We do not send the -wire_client argument,
+ * since we don't want to give the server an identity crisis.
+ */
+static void wire_client_serialize_argv(const char **argv, char **args_ptr,
+ int *args_len_ptr)
+{
+ int i;
+ char *args = NULL;
+ int args_len = 0;
+ char *end = NULL;
+
+ for (i = 0; argv[i]; ++i) {
+ if (strstr(argv[i], "-wire_client"))
+ continue;
+ args_len += strlen(argv[i]) + 1; /* + 1 for '\0' */
+ }
+
+ args = calloc(args_len, 1);
+ end = args;
+
+ for (i = 0; argv[i]; ++i) {
+ int len = 0;
+ if (strstr(argv[i], "-wire_client"))
+ continue;
+ len = strlen(argv[i]) + 1; /* + 1 for '\0' */
+ memcpy(end, argv[i], len);
+ end += len;
+ }
+
+ assert(end == args + args_len);
+
+ *args_ptr = args;
+ *args_len_ptr = args_len;
+}
+
+/* Send a WIRE_COMMAND_LINE_ARGS message with our command line
+ * arguments as a single serialized string.
+ */
+static void wire_client_send_args(struct wire_client *wire_client,
+ const struct config *config)
+{
+ char *args = NULL;
+ int args_len = 0;
+
+ wire_client_serialize_argv(config->argv, &args, &args_len);
+
+ if (wire_conn_write(wire_client->wire_conn,
+ WIRE_COMMAND_LINE_ARGS,
+ args, args_len))
+ wire_client_die(wire_client,
+ "error sending WIRE_COMMAND_LINE_ARGS");
+ free(args);
+}
+
+/* Send the path name of the script we're about to run. */
+static void wire_client_send_script_path(struct wire_client *wire_client,
+ const struct config *config)
+{
+ if (wire_conn_write(wire_client->wire_conn,
+ WIRE_SCRIPT_PATH,
+ config->script_path,
+ strlen(config->script_path)))
+ wire_client_die(wire_client,
+ "error sending WIRE_SCRIPT_PATH");
+}
+
+/* Send the ASCII contents of the script we're about to run. */
+static void wire_client_send_script(struct wire_client *wire_client,
+ const struct script *script)
+{
+ if (wire_conn_write(wire_client->wire_conn,
+ WIRE_SCRIPT,
+ script->buffer, script->length))
+ wire_client_die(wire_client,
+ "error sending WIRE_SCRIPT");
+}
+
+/* Send the ethernet address to which the server should send packets. */
+static void wire_client_send_hw_address(struct wire_client *wire_client,
+ const struct config *config)
+{
+ if (wire_conn_write(wire_client->wire_conn,
+ WIRE_HARDWARE_ADDR,
+ &wire_client->client_ether_addr,
+ sizeof(wire_client->client_ether_addr)))
+ wire_client_die(wire_client,
+ "error sending WIRE_HARDWARE_ADDR");
+}
+
+/* Receive server's message that the server is ready to execute the script. */
+static void wire_client_receive_server_ready(struct wire_client *wire_client)
+{
+ enum wire_op_t op = WIRE_INVALID;
+ void *buf = NULL;
+ int buf_len = -1;
+
+ if (wire_conn_read(wire_client->wire_conn,
+ &op, &buf, &buf_len))
+ wire_client_die(wire_client, "error reading WIRE_SERVER_READY");
+ if (op != WIRE_SERVER_READY) {
+ wire_client_die(wire_client,
+ "bad wire server: expected WIRE_SERVER_READY");
+ }
+ if (buf_len != 0) {
+ wire_client_die(wire_client,
+ "bad wire server: bad WIRE_SERVER_READY len");
+ }
+}
+
+/* Tell server that client is starting script execution. */
+void wire_client_send_client_starting(struct wire_client *wire_client)
+{
+ if (wire_conn_write(wire_client->wire_conn,
+ WIRE_CLIENT_STARTING,
+ NULL, 0))
+ wire_client_die(wire_client,
+ "error sending WIRE_CLIENT_STARTING");
+}
+
+/* Send a client request for the server to execute some packet events. */
+static void wire_client_send_packets_start(struct wire_client *wire_client)
+{
+ struct wire_packets_start start;
+ start.num_events = htonl(wire_client->num_events);
+ if (wire_conn_write(wire_client->wire_conn,
+ WIRE_PACKETS_START,
+ &start, sizeof(start)))
+ wire_client_die(wire_client,
+ "error sending WIRE_PACKETS_START");
+}
+
+/* Receive a message from the server that the server is done executing
+ * some packet events. Print any warnings we receive along the way.
+ */
+static void wire_client_receive_packets_done(struct wire_client *wire_client)
+{
+ enum wire_op_t op;
+ struct wire_packets_done done;
+ void *buf = NULL;
+ int buf_len = -1;
+
+ DEBUGP("wire_client_receive_packets_done\n");
+
+ while (1) {
+ if (wire_conn_read(wire_client->wire_conn,
+ &op, &buf, &buf_len))
+ wire_client_die(wire_client, "error reading");
+ if (op == WIRE_PACKETS_DONE)
+ break;
+ else if (op == WIRE_PACKETS_WARN) {
+ /* NULL-terminate the warning and print it. */
+ char *warning = strndup(buf, buf_len);
+ fprintf(stderr, "%s", warning);
+ free(warning);
+ } else {
+ wire_client_die(
+ wire_client,
+ "bad wire server: expected "
+ "WIRE_PACKETS_DONE or WIRE_PACKETS_WARN");
+ }
+ }
+
+ if (buf_len < sizeof(done) + 1) {
+ wire_client_die(wire_client,
+ "bad wire server: bad WIRE_PACKETS_DONE len");
+ }
+ if (((char *)buf)[buf_len - 1] != '\0') {
+ wire_client_die(wire_client,
+ "bad wire server: missing string terminator");
+ }
+
+ memcpy(&done, buf, sizeof(done));
+
+ if (ntohl(done.result) == STATUS_ERR) {
+ /* Die with the error message from the server, which
+ * is a C string following the fixed "done" message.
+ */
+ die("%s", (char *)(buf + sizeof(done)));
+ } else if (ntohl(done.num_events) != wire_client->num_events) {
+ char *msg = NULL;
+ asprintf(&msg, "bad wire server: bad message count: "
+ "got: %d vs expected: %d",
+ ntohl(done.num_events), wire_client->num_events);
+ wire_client_die(wire_client, msg);
+ }
+}
+
+/* Connect to the wire server, pass it our command line argument
+ * options, the script we're going to execute, and our MAC address.
+ */
+int wire_client_init(struct wire_client *wire_client,
+ const struct config *config,
+ const struct script *script)
+{
+ DEBUGP("wire_client_init\n");
+ assert(config->is_wire_client);
+
+ get_hw_address(config->wire_client_device,
+ &wire_client->client_ether_addr,
+ config->ip_version);
+
+ wire_client->wire_conn = wire_conn_new();
+ wire_conn_connect(wire_client->wire_conn,
+ &config->wire_server_ip,
+ config->wire_server_port,
+ config->ip_version);
+
+ wire_client_send_args(wire_client, config);
+
+ wire_client_send_script_path(wire_client, config);
+
+ wire_client_send_script(wire_client, script);
+
+ wire_client_send_hw_address(wire_client, config);
+
+ wire_client_receive_server_ready(wire_client);
+
+ return STATUS_OK;
+}
+
+
+/* Tell the wire client that the interpreter has moved on to the next
+ * event. Inform the wire server if need be. The client informs the
+ * server if (a) this event is a packet event and (b) the previous
+ * event was not a packet event. In any other cases the server either
+ * (i) does not care what time this event is happening at because it's
+ * not an on-the-wire event, or (ii) already knows what time to fire
+ * this on-the-wire event because the previous event was also an
+ * on-the-wire event.
+ */
+void wire_client_next_event(struct wire_client *wire_client,
+ struct event *event)
+{
+ /* Tell the server to start executing packet events. */
+ if (event && (event->type == PACKET_EVENT) &&
+ (wire_client->last_event_type != PACKET_EVENT)) {
+ wire_client_send_packets_start(wire_client);
+ }
+
+ /* Get the result from server execution of one or more packet events. */
+ if ((!event || (event->type != PACKET_EVENT)) &&
+ (wire_client->last_event_type == PACKET_EVENT)) {
+ wire_client_receive_packets_done(wire_client);
+ }
+
+ if (event) {
+ wire_client->last_event_type = event->type;
+ ++wire_client->num_events;
+ }
+}