aboutsummaryrefslogtreecommitdiffstats
path: root/test/packetdrill/wire_client.c
blob: 33ebbd6a1073c566a21625f29342fa8a9bfec0b7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
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;
	}
}