]>
Commit | Line | Data |
---|---|---|
064af421 BP |
1 | /* |
2 | * Copyright (c) 2008, 2009 Nicira Networks. | |
3 | * | |
4 | * Permission to use, copy, modify, and/or distribute this software for any | |
5 | * purpose with or without fee is hereby granted, provided that the above | |
6 | * copyright notice and this permission notice appear in all copies. | |
7 | * | |
8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |
11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |
13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |
14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
15 | */ | |
16 | ||
17 | #include <config.h> | |
18 | #include <arpa/inet.h> | |
19 | #include <errno.h> | |
20 | #include <getopt.h> | |
21 | #include <inttypes.h> | |
22 | #include <net/if.h> | |
23 | #include <netinet/in.h> | |
24 | #include <signal.h> | |
25 | #include <stdarg.h> | |
26 | #include <stdlib.h> | |
27 | #include <string.h> | |
28 | #include <unistd.h> | |
29 | #include <sys/stat.h> | |
30 | #include <sys/time.h> | |
31 | ||
32 | #include "command-line.h" | |
33 | #include "compiler.h" | |
34 | #include "dirs.h" | |
35 | #include "dpif.h" | |
36 | #include "dynamic-string.h" | |
37 | #include "netdev.h" | |
38 | #include "netlink.h" | |
39 | #include "odp-util.h" | |
40 | #include "ofp-print.h" | |
41 | #include "ofpbuf.h" | |
42 | #include "openflow/nicira-ext.h" | |
43 | #include "openflow/openflow.h" | |
44 | #include "packets.h" | |
45 | #include "random.h" | |
46 | #include "socket-util.h" | |
47 | #include "timeval.h" | |
48 | #include "util.h" | |
49 | #include "vconn-ssl.h" | |
50 | #include "vconn.h" | |
51 | ||
52 | #include "vlog.h" | |
53 | #define THIS_MODULE VLM_ofctl | |
54 | ||
55 | #define DEFAULT_IDLE_TIMEOUT 60 | |
56 | ||
57 | #define MOD_PORT_CMD_UP "up" | |
58 | #define MOD_PORT_CMD_DOWN "down" | |
59 | #define MOD_PORT_CMD_FLOOD "flood" | |
60 | #define MOD_PORT_CMD_NOFLOOD "noflood" | |
61 | ||
62 | ||
63 | /* Settings that may be configured by the user. */ | |
64 | struct settings { | |
65 | bool strict; /* Use strict matching for flow mod commands */ | |
66 | }; | |
67 | ||
68 | struct command { | |
69 | const char *name; | |
70 | int min_args; | |
71 | int max_args; | |
72 | void (*handler)(const struct settings *, int argc, char *argv[]); | |
73 | }; | |
74 | ||
75 | static struct command all_commands[]; | |
76 | ||
77 | static void usage(void) NO_RETURN; | |
78 | static void parse_options(int argc, char *argv[], struct settings *); | |
79 | ||
80 | int main(int argc, char *argv[]) | |
81 | { | |
82 | struct settings s; | |
83 | struct command *p; | |
84 | ||
85 | set_program_name(argv[0]); | |
86 | time_init(); | |
87 | vlog_init(); | |
88 | parse_options(argc, argv, &s); | |
89 | signal(SIGPIPE, SIG_IGN); | |
90 | ||
91 | argc -= optind; | |
92 | argv += optind; | |
93 | if (argc < 1) | |
94 | ovs_fatal(0, "missing command name; use --help for help"); | |
95 | ||
96 | for (p = all_commands; p->name != NULL; p++) { | |
97 | if (!strcmp(p->name, argv[0])) { | |
98 | int n_arg = argc - 1; | |
99 | if (n_arg < p->min_args) | |
100 | ovs_fatal(0, "'%s' command requires at least %d arguments", | |
101 | p->name, p->min_args); | |
102 | else if (n_arg > p->max_args) | |
103 | ovs_fatal(0, "'%s' command takes at most %d arguments", | |
104 | p->name, p->max_args); | |
105 | else { | |
106 | p->handler(&s, argc, argv); | |
107 | if (ferror(stdout)) { | |
108 | ovs_fatal(0, "write to stdout failed"); | |
109 | } | |
110 | if (ferror(stderr)) { | |
111 | ovs_fatal(0, "write to stderr failed"); | |
112 | } | |
113 | exit(0); | |
114 | } | |
115 | } | |
116 | } | |
117 | ovs_fatal(0, "unknown command '%s'; use --help for help", argv[0]); | |
118 | ||
119 | return 0; | |
120 | } | |
121 | ||
122 | static void | |
123 | parse_options(int argc, char *argv[], struct settings *s) | |
124 | { | |
125 | enum { | |
126 | OPT_STRICT = UCHAR_MAX + 1 | |
127 | }; | |
128 | static struct option long_options[] = { | |
129 | {"timeout", required_argument, 0, 't'}, | |
130 | {"verbose", optional_argument, 0, 'v'}, | |
131 | {"strict", no_argument, 0, OPT_STRICT}, | |
132 | {"help", no_argument, 0, 'h'}, | |
133 | {"version", no_argument, 0, 'V'}, | |
134 | VCONN_SSL_LONG_OPTIONS | |
135 | {0, 0, 0, 0}, | |
136 | }; | |
137 | char *short_options = long_options_to_short_options(long_options); | |
138 | ||
139 | /* Set defaults that we can figure out before parsing options. */ | |
140 | s->strict = false; | |
141 | ||
142 | for (;;) { | |
143 | unsigned long int timeout; | |
144 | int c; | |
145 | ||
146 | c = getopt_long(argc, argv, short_options, long_options, NULL); | |
147 | if (c == -1) { | |
148 | break; | |
149 | } | |
150 | ||
151 | switch (c) { | |
152 | case 't': | |
153 | timeout = strtoul(optarg, NULL, 10); | |
154 | if (timeout <= 0) { | |
155 | ovs_fatal(0, "value %s on -t or --timeout is not at least 1", | |
156 | optarg); | |
157 | } else { | |
158 | time_alarm(timeout); | |
159 | } | |
160 | break; | |
161 | ||
162 | case 'h': | |
163 | usage(); | |
164 | ||
165 | case 'V': | |
166 | OVS_PRINT_VERSION(OFP_VERSION, OFP_VERSION); | |
167 | exit(EXIT_SUCCESS); | |
168 | ||
169 | case 'v': | |
170 | vlog_set_verbosity(optarg); | |
171 | break; | |
172 | ||
173 | case OPT_STRICT: | |
174 | s->strict = true; | |
175 | break; | |
176 | ||
177 | VCONN_SSL_OPTION_HANDLERS | |
178 | ||
179 | case '?': | |
180 | exit(EXIT_FAILURE); | |
181 | ||
182 | default: | |
183 | abort(); | |
184 | } | |
185 | } | |
186 | free(short_options); | |
187 | } | |
188 | ||
189 | static void | |
190 | usage(void) | |
191 | { | |
192 | printf("%s: OpenFlow switch management utility\n" | |
193 | "usage: %s [OPTIONS] COMMAND [ARG...]\n" | |
194 | "\nFor OpenFlow switches:\n" | |
195 | " show SWITCH show OpenFlow information\n" | |
196 | " status SWITCH [KEY] report statistics (about KEY)\n" | |
197 | " dump-desc SWITCH print switch description\n" | |
198 | " dump-tables SWITCH print table stats\n" | |
199 | " mod-port SWITCH IFACE ACT modify port behavior\n" | |
200 | " dump-ports SWITCH print port statistics\n" | |
201 | " dump-flows SWITCH print all flow entries\n" | |
202 | " dump-flows SWITCH FLOW print matching FLOWs\n" | |
203 | " dump-aggregate SWITCH print aggregate flow statistics\n" | |
204 | " dump-aggregate SWITCH FLOW print aggregate stats for FLOWs\n" | |
205 | " add-flow SWITCH FLOW add flow described by FLOW\n" | |
206 | " add-flows SWITCH FILE add flows from FILE\n" | |
207 | " mod-flows SWITCH FLOW modify actions of matching FLOWs\n" | |
208 | " del-flows SWITCH [FLOW] delete matching FLOWs\n" | |
209 | " monitor SWITCH MISSLEN EXP print packets received from SWITCH\n" | |
210 | " execute SWITCH CMD [ARG...] execute CMD with ARGS on SWITCH\n" | |
211 | "\nFor OpenFlow switches and controllers:\n" | |
212 | " probe VCONN probe whether VCONN is up\n" | |
213 | " ping VCONN [N] latency of N-byte echos\n" | |
214 | " benchmark VCONN N COUNT bandwidth of COUNT N-byte echos\n" | |
215 | "where each SWITCH is an active OpenFlow connection method.\n", | |
216 | program_name, program_name); | |
217 | vconn_usage(true, false, false); | |
218 | vlog_usage(); | |
219 | printf("\nOther options:\n" | |
220 | " --strict use strict match for flow commands\n" | |
221 | " -t, --timeout=SECS give up after SECS seconds\n" | |
222 | " -h, --help display this help message\n" | |
223 | " -V, --version display version information\n"); | |
224 | exit(EXIT_SUCCESS); | |
225 | } | |
226 | ||
227 | static void run(int retval, const char *message, ...) | |
228 | PRINTF_FORMAT(2, 3); | |
229 | ||
230 | static void run(int retval, const char *message, ...) | |
231 | { | |
232 | if (retval) { | |
233 | va_list args; | |
234 | ||
235 | fprintf(stderr, "%s: ", program_name); | |
236 | va_start(args, message); | |
237 | vfprintf(stderr, message, args); | |
238 | va_end(args); | |
239 | if (retval == EOF) { | |
240 | fputs(": unexpected end of file\n", stderr); | |
241 | } else { | |
242 | fprintf(stderr, ": %s\n", strerror(retval)); | |
243 | } | |
244 | ||
245 | exit(EXIT_FAILURE); | |
246 | } | |
247 | } | |
248 | \f | |
249 | /* Generic commands. */ | |
250 | ||
251 | static void | |
252 | open_vconn(const char *name, struct vconn **vconnp) | |
253 | { | |
254 | struct dpif dpif; | |
255 | struct stat s; | |
256 | ||
257 | if (strstr(name, ":")) { | |
258 | run(vconn_open_block(name, OFP_VERSION, vconnp), | |
259 | "connecting to %s", name); | |
260 | } else if (!stat(name, &s) && S_ISSOCK(s.st_mode)) { | |
261 | char *vconn_name = xasprintf("unix:%s", name); | |
262 | VLOG_INFO("connecting to %s", vconn_name); | |
263 | run(vconn_open_block(vconn_name, OFP_VERSION, vconnp), | |
264 | "connecting to %s", vconn_name); | |
265 | free(vconn_name); | |
266 | } else if (!dpif_open(name, &dpif)) { | |
267 | char dpif_name[IF_NAMESIZE + 1]; | |
268 | char *socket_name; | |
269 | char *vconn_name; | |
270 | ||
271 | run(dpif_get_name(&dpif, dpif_name, sizeof dpif_name), | |
272 | "obtaining name of %s", dpif_name); | |
273 | dpif_close(&dpif); | |
274 | if (strcmp(dpif_name, name)) { | |
275 | VLOG_INFO("datapath %s is named %s", name, dpif_name); | |
276 | } | |
277 | ||
278 | socket_name = xasprintf("%s/%s.mgmt", ovs_rundir, dpif_name); | |
279 | if (stat(socket_name, &s)) { | |
280 | ovs_fatal(errno, "cannot connect to %s: stat failed on %s", | |
281 | name, socket_name); | |
282 | } else if (!S_ISSOCK(s.st_mode)) { | |
283 | ovs_fatal(0, "cannot connect to %s: %s is not a socket", | |
284 | name, socket_name); | |
285 | } | |
286 | ||
287 | vconn_name = xasprintf("unix:%s", socket_name); | |
288 | VLOG_INFO("connecting to %s", vconn_name); | |
289 | run(vconn_open_block(vconn_name, OFP_VERSION, vconnp), | |
290 | "connecting to %s", vconn_name); | |
291 | free(socket_name); | |
292 | free(vconn_name); | |
293 | } else { | |
294 | ovs_fatal(0, "%s is not a valid connection method", name); | |
295 | } | |
296 | } | |
297 | ||
298 | static void * | |
299 | alloc_stats_request(size_t body_len, uint16_t type, struct ofpbuf **bufferp) | |
300 | { | |
301 | struct ofp_stats_request *rq; | |
302 | rq = make_openflow((offsetof(struct ofp_stats_request, body) | |
303 | + body_len), OFPT_STATS_REQUEST, bufferp); | |
304 | rq->type = htons(type); | |
305 | rq->flags = htons(0); | |
306 | return rq->body; | |
307 | } | |
308 | ||
309 | static void | |
310 | send_openflow_buffer(struct vconn *vconn, struct ofpbuf *buffer) | |
311 | { | |
312 | update_openflow_length(buffer); | |
313 | run(vconn_send_block(vconn, buffer), "failed to send packet to switch"); | |
314 | } | |
315 | ||
316 | static void | |
317 | dump_transaction(const char *vconn_name, struct ofpbuf *request) | |
318 | { | |
319 | struct vconn *vconn; | |
320 | struct ofpbuf *reply; | |
321 | ||
322 | update_openflow_length(request); | |
323 | open_vconn(vconn_name, &vconn); | |
324 | run(vconn_transact(vconn, request, &reply), "talking to %s", vconn_name); | |
325 | ofp_print(stdout, reply->data, reply->size, 1); | |
326 | vconn_close(vconn); | |
327 | } | |
328 | ||
329 | static void | |
330 | dump_trivial_transaction(const char *vconn_name, uint8_t request_type) | |
331 | { | |
332 | struct ofpbuf *request; | |
333 | make_openflow(sizeof(struct ofp_header), request_type, &request); | |
334 | dump_transaction(vconn_name, request); | |
335 | } | |
336 | ||
337 | static void | |
338 | dump_stats_transaction(const char *vconn_name, struct ofpbuf *request) | |
339 | { | |
340 | uint32_t send_xid = ((struct ofp_header *) request->data)->xid; | |
341 | struct vconn *vconn; | |
342 | bool done = false; | |
343 | ||
344 | open_vconn(vconn_name, &vconn); | |
345 | send_openflow_buffer(vconn, request); | |
346 | while (!done) { | |
347 | uint32_t recv_xid; | |
348 | struct ofpbuf *reply; | |
349 | ||
350 | run(vconn_recv_block(vconn, &reply), "OpenFlow packet receive failed"); | |
351 | recv_xid = ((struct ofp_header *) reply->data)->xid; | |
352 | if (send_xid == recv_xid) { | |
353 | struct ofp_stats_reply *osr; | |
354 | ||
355 | ofp_print(stdout, reply->data, reply->size, 1); | |
356 | ||
357 | osr = ofpbuf_at(reply, 0, sizeof *osr); | |
358 | done = !osr || !(ntohs(osr->flags) & OFPSF_REPLY_MORE); | |
359 | } else { | |
360 | VLOG_DBG("received reply with xid %08"PRIx32" " | |
361 | "!= expected %08"PRIx32, recv_xid, send_xid); | |
362 | } | |
363 | ofpbuf_delete(reply); | |
364 | } | |
365 | vconn_close(vconn); | |
366 | } | |
367 | ||
368 | static void | |
369 | dump_trivial_stats_transaction(const char *vconn_name, uint8_t stats_type) | |
370 | { | |
371 | struct ofpbuf *request; | |
372 | alloc_stats_request(0, stats_type, &request); | |
373 | dump_stats_transaction(vconn_name, request); | |
374 | } | |
375 | ||
376 | static void | |
377 | do_show(const struct settings *s UNUSED, int argc UNUSED, char *argv[]) | |
378 | { | |
379 | dump_trivial_transaction(argv[1], OFPT_FEATURES_REQUEST); | |
380 | dump_trivial_transaction(argv[1], OFPT_GET_CONFIG_REQUEST); | |
381 | } | |
382 | ||
383 | static void | |
384 | do_status(const struct settings *s UNUSED, int argc, char *argv[]) | |
385 | { | |
386 | struct nicira_header *request, *reply; | |
387 | struct vconn *vconn; | |
388 | struct ofpbuf *b; | |
389 | ||
390 | request = make_openflow(sizeof *request, OFPT_VENDOR, &b); | |
391 | request->vendor = htonl(NX_VENDOR_ID); | |
392 | request->subtype = htonl(NXT_STATUS_REQUEST); | |
393 | if (argc > 2) { | |
394 | ofpbuf_put(b, argv[2], strlen(argv[2])); | |
395 | update_openflow_length(b); | |
396 | } | |
397 | open_vconn(argv[1], &vconn); | |
398 | run(vconn_transact(vconn, b, &b), "talking to %s", argv[1]); | |
399 | vconn_close(vconn); | |
400 | ||
401 | if (b->size < sizeof *reply) { | |
402 | ovs_fatal(0, "short reply (%zu bytes)", b->size); | |
403 | } | |
404 | reply = b->data; | |
405 | if (reply->header.type != OFPT_VENDOR | |
406 | || reply->vendor != ntohl(NX_VENDOR_ID) | |
407 | || reply->subtype != ntohl(NXT_STATUS_REPLY)) { | |
408 | ofp_print(stderr, b->data, b->size, 2); | |
409 | ovs_fatal(0, "bad reply"); | |
410 | } | |
411 | ||
412 | fwrite(reply + 1, b->size - sizeof *reply, 1, stdout); | |
413 | } | |
414 | ||
415 | static void | |
416 | do_dump_desc(const struct settings *s UNUSED, int argc UNUSED, char *argv[]) | |
417 | { | |
418 | dump_trivial_stats_transaction(argv[1], OFPST_DESC); | |
419 | } | |
420 | ||
421 | static void | |
422 | do_dump_tables(const struct settings *s UNUSED, int argc UNUSED, char *argv[]) | |
423 | { | |
424 | dump_trivial_stats_transaction(argv[1], OFPST_TABLE); | |
425 | } | |
426 | ||
427 | ||
428 | static uint32_t | |
429 | str_to_u32(const char *str) | |
430 | { | |
431 | char *tail; | |
432 | uint32_t value; | |
433 | ||
434 | errno = 0; | |
435 | value = strtoul(str, &tail, 0); | |
436 | if (errno == EINVAL || errno == ERANGE || *tail) { | |
437 | ovs_fatal(0, "invalid numeric format %s", str); | |
438 | } | |
439 | return value; | |
440 | } | |
441 | ||
442 | static void | |
443 | str_to_mac(const char *str, uint8_t mac[6]) | |
444 | { | |
445 | if (sscanf(str, "%"SCNx8":%"SCNx8":%"SCNx8":%"SCNx8":%"SCNx8":%"SCNx8, | |
446 | &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]) != 6) { | |
447 | ovs_fatal(0, "invalid mac address %s", str); | |
448 | } | |
449 | } | |
450 | ||
451 | static uint32_t | |
452 | str_to_ip(const char *str_, uint32_t *ip) | |
453 | { | |
454 | char *str = xstrdup(str_); | |
455 | char *save_ptr = NULL; | |
456 | const char *name, *netmask; | |
457 | struct in_addr in_addr; | |
458 | int n_wild, retval; | |
459 | ||
460 | name = strtok_r(str, "//", &save_ptr); | |
461 | retval = name ? lookup_ip(name, &in_addr) : EINVAL; | |
462 | if (retval) { | |
463 | ovs_fatal(0, "%s: could not convert to IP address", str); | |
464 | } | |
465 | *ip = in_addr.s_addr; | |
466 | ||
467 | netmask = strtok_r(NULL, "//", &save_ptr); | |
468 | if (netmask) { | |
469 | uint8_t o[4]; | |
470 | if (sscanf(netmask, "%"SCNu8".%"SCNu8".%"SCNu8".%"SCNu8, | |
471 | &o[0], &o[1], &o[2], &o[3]) == 4) { | |
472 | uint32_t nm = (o[0] << 24) | (o[1] << 16) | (o[2] << 8) | o[3]; | |
473 | int i; | |
474 | ||
475 | /* Find first 1-bit. */ | |
476 | for (i = 0; i < 32; i++) { | |
477 | if (nm & (1u << i)) { | |
478 | break; | |
479 | } | |
480 | } | |
481 | n_wild = i; | |
482 | ||
483 | /* Verify that the rest of the bits are 1-bits. */ | |
484 | for (; i < 32; i++) { | |
485 | if (!(nm & (1u << i))) { | |
486 | ovs_fatal(0, "%s: %s is not a valid netmask", | |
487 | str, netmask); | |
488 | } | |
489 | } | |
490 | } else { | |
491 | int prefix = atoi(netmask); | |
492 | if (prefix <= 0 || prefix > 32) { | |
493 | ovs_fatal(0, "%s: network prefix bits not between 1 and 32", | |
494 | str); | |
495 | } | |
496 | n_wild = 32 - prefix; | |
497 | } | |
498 | } else { | |
499 | n_wild = 0; | |
500 | } | |
501 | ||
502 | free(str); | |
503 | return n_wild; | |
504 | } | |
505 | ||
506 | static void * | |
507 | put_action(struct ofpbuf *b, size_t size, uint16_t type) | |
508 | { | |
509 | struct ofp_action_header *ah = ofpbuf_put_zeros(b, size); | |
510 | ah->type = htons(type); | |
511 | ah->len = htons(size); | |
512 | return ah; | |
513 | } | |
514 | ||
515 | static struct ofp_action_output * | |
516 | put_output_action(struct ofpbuf *b, uint16_t port) | |
517 | { | |
518 | struct ofp_action_output *oao = put_action(b, sizeof *oao, OFPAT_OUTPUT); | |
519 | oao->port = htons(port); | |
520 | return oao; | |
521 | } | |
522 | ||
523 | static void | |
524 | put_dl_addr_action(struct ofpbuf *b, uint16_t type, const char *addr) | |
525 | { | |
526 | struct ofp_action_dl_addr *oada = put_action(b, sizeof *oada, type); | |
527 | str_to_mac(addr, oada->dl_addr); | |
528 | } | |
529 | ||
530 | ||
531 | static bool | |
532 | parse_port_name(const char *name, uint16_t *port) | |
533 | { | |
534 | struct pair { | |
535 | const char *name; | |
536 | uint16_t value; | |
537 | }; | |
538 | static const struct pair pairs[] = { | |
539 | #define DEF_PAIR(NAME) {#NAME, OFPP_##NAME} | |
540 | DEF_PAIR(IN_PORT), | |
541 | DEF_PAIR(TABLE), | |
542 | DEF_PAIR(NORMAL), | |
543 | DEF_PAIR(FLOOD), | |
544 | DEF_PAIR(ALL), | |
545 | DEF_PAIR(CONTROLLER), | |
546 | DEF_PAIR(LOCAL), | |
547 | DEF_PAIR(NONE), | |
548 | #undef DEF_PAIR | |
549 | }; | |
550 | static const int n_pairs = ARRAY_SIZE(pairs); | |
551 | size_t i; | |
552 | ||
553 | for (i = 0; i < n_pairs; i++) { | |
554 | if (!strcasecmp(name, pairs[i].name)) { | |
555 | *port = pairs[i].value; | |
556 | return true; | |
557 | } | |
558 | } | |
559 | return false; | |
560 | } | |
561 | ||
562 | static void | |
563 | str_to_action(char *str, struct ofpbuf *b) | |
564 | { | |
565 | char *act, *arg; | |
566 | char *saveptr = NULL; | |
567 | bool drop = false; | |
568 | int n_actions; | |
569 | ||
570 | for (act = strtok_r(str, ", \t\r\n", &saveptr), n_actions = 0; act; | |
571 | act = strtok_r(NULL, ", \t\r\n", &saveptr), n_actions++) | |
572 | { | |
573 | uint16_t port; | |
574 | ||
575 | if (drop) { | |
576 | ovs_fatal(0, "Drop actions must not be followed by other actions"); | |
577 | } | |
578 | ||
579 | /* Arguments are separated by colons */ | |
580 | arg = strchr(act, ':'); | |
581 | if (arg) { | |
582 | *arg = '\0'; | |
583 | arg++; | |
584 | } | |
585 | ||
586 | if (!strcasecmp(act, "mod_vlan_vid")) { | |
587 | struct ofp_action_vlan_vid *va; | |
588 | va = put_action(b, sizeof *va, OFPAT_SET_VLAN_VID); | |
589 | va->vlan_vid = htons(str_to_u32(arg)); | |
590 | } else if (!strcasecmp(act, "mod_vlan_pcp")) { | |
591 | struct ofp_action_vlan_pcp *va; | |
592 | va = put_action(b, sizeof *va, OFPAT_SET_VLAN_PCP); | |
593 | va->vlan_pcp = str_to_u32(arg); | |
594 | } else if (!strcasecmp(act, "strip_vlan")) { | |
595 | struct ofp_action_header *ah; | |
596 | ah = put_action(b, sizeof *ah, OFPAT_STRIP_VLAN); | |
597 | ah->type = htons(OFPAT_STRIP_VLAN); | |
598 | } else if (!strcasecmp(act, "mod_dl_src")) { | |
599 | put_dl_addr_action(b, OFPAT_SET_DL_SRC, arg); | |
600 | } else if (!strcasecmp(act, "mod_dl_dst")) { | |
601 | put_dl_addr_action(b, OFPAT_SET_DL_DST, arg); | |
602 | } else if (!strcasecmp(act, "output")) { | |
603 | put_output_action(b, str_to_u32(arg)); | |
604 | } else if (!strcasecmp(act, "drop")) { | |
605 | /* A drop action in OpenFlow occurs by just not setting | |
606 | * an action. */ | |
607 | drop = true; | |
608 | if (n_actions) { | |
609 | ovs_fatal(0, "Drop actions must not be preceded by other " | |
610 | "actions"); | |
611 | } | |
612 | } else if (!strcasecmp(act, "CONTROLLER")) { | |
613 | struct ofp_action_output *oao; | |
614 | oao = put_output_action(b, OFPP_CONTROLLER); | |
615 | ||
616 | /* Unless a numeric argument is specified, we send the whole | |
617 | * packet to the controller. */ | |
618 | if (arg && (strspn(act, "0123456789") == strlen(act))) { | |
619 | oao->max_len = htons(str_to_u32(arg)); | |
620 | } | |
621 | } else if (parse_port_name(act, &port)) { | |
622 | put_output_action(b, port); | |
623 | } else if (strspn(act, "0123456789") == strlen(act)) { | |
624 | put_output_action(b, str_to_u32(act)); | |
625 | } else { | |
626 | ovs_fatal(0, "Unknown action: %s", act); | |
627 | } | |
628 | } | |
629 | } | |
630 | ||
631 | struct protocol { | |
632 | const char *name; | |
633 | uint16_t dl_type; | |
634 | uint8_t nw_proto; | |
635 | }; | |
636 | ||
637 | static bool | |
638 | parse_protocol(const char *name, const struct protocol **p_out) | |
639 | { | |
640 | static const struct protocol protocols[] = { | |
641 | { "ip", ETH_TYPE_IP, 0 }, | |
642 | { "arp", ETH_TYPE_ARP, 0 }, | |
643 | { "icmp", ETH_TYPE_IP, IP_TYPE_ICMP }, | |
644 | { "tcp", ETH_TYPE_IP, IP_TYPE_TCP }, | |
645 | { "udp", ETH_TYPE_IP, IP_TYPE_UDP }, | |
646 | }; | |
647 | const struct protocol *p; | |
648 | ||
649 | for (p = protocols; p < &protocols[ARRAY_SIZE(protocols)]; p++) { | |
650 | if (!strcmp(p->name, name)) { | |
651 | *p_out = p; | |
652 | return true; | |
653 | } | |
654 | } | |
655 | *p_out = NULL; | |
656 | return false; | |
657 | } | |
658 | ||
659 | struct field { | |
660 | const char *name; | |
661 | uint32_t wildcard; | |
662 | enum { F_U8, F_U16, F_MAC, F_IP } type; | |
663 | size_t offset, shift; | |
664 | }; | |
665 | ||
666 | static bool | |
667 | parse_field(const char *name, const struct field **f_out) | |
668 | { | |
669 | #define F_OFS(MEMBER) offsetof(struct ofp_match, MEMBER) | |
670 | static const struct field fields[] = { | |
671 | { "in_port", OFPFW_IN_PORT, F_U16, F_OFS(in_port), 0 }, | |
672 | { "dl_vlan", OFPFW_DL_VLAN, F_U16, F_OFS(dl_vlan), 0 }, | |
673 | { "dl_src", OFPFW_DL_SRC, F_MAC, F_OFS(dl_src), 0 }, | |
674 | { "dl_dst", OFPFW_DL_DST, F_MAC, F_OFS(dl_dst), 0 }, | |
675 | { "dl_type", OFPFW_DL_TYPE, F_U16, F_OFS(dl_type), 0 }, | |
676 | { "nw_src", OFPFW_NW_SRC_MASK, F_IP, | |
677 | F_OFS(nw_src), OFPFW_NW_SRC_SHIFT }, | |
678 | { "nw_dst", OFPFW_NW_DST_MASK, F_IP, | |
679 | F_OFS(nw_dst), OFPFW_NW_DST_SHIFT }, | |
680 | { "nw_proto", OFPFW_NW_PROTO, F_U8, F_OFS(nw_proto), 0 }, | |
681 | { "tp_src", OFPFW_TP_SRC, F_U16, F_OFS(tp_src), 0 }, | |
682 | { "tp_dst", OFPFW_TP_DST, F_U16, F_OFS(tp_dst), 0 }, | |
683 | { "icmp_type", OFPFW_ICMP_TYPE, F_U16, F_OFS(icmp_type), 0 }, | |
684 | { "icmp_code", OFPFW_ICMP_CODE, F_U16, F_OFS(icmp_code), 0 } | |
685 | }; | |
686 | const struct field *f; | |
687 | ||
688 | for (f = fields; f < &fields[ARRAY_SIZE(fields)]; f++) { | |
689 | if (!strcmp(f->name, name)) { | |
690 | *f_out = f; | |
691 | return true; | |
692 | } | |
693 | } | |
694 | *f_out = NULL; | |
695 | return false; | |
696 | } | |
697 | ||
698 | static void | |
699 | str_to_flow(char *string, struct ofp_match *match, struct ofpbuf *actions, | |
700 | uint8_t *table_idx, uint16_t *out_port, uint16_t *priority, | |
701 | uint16_t *idle_timeout, uint16_t *hard_timeout) | |
702 | { | |
703 | char *save_ptr = NULL; | |
704 | char *name; | |
705 | uint32_t wildcards; | |
706 | ||
707 | if (table_idx) { | |
708 | *table_idx = 0xff; | |
709 | } | |
710 | if (out_port) { | |
711 | *out_port = OFPP_NONE; | |
712 | } | |
713 | if (priority) { | |
714 | *priority = OFP_DEFAULT_PRIORITY; | |
715 | } | |
716 | if (idle_timeout) { | |
717 | *idle_timeout = DEFAULT_IDLE_TIMEOUT; | |
718 | } | |
719 | if (hard_timeout) { | |
720 | *hard_timeout = OFP_FLOW_PERMANENT; | |
721 | } | |
722 | if (actions) { | |
723 | char *act_str = strstr(string, "action"); | |
724 | if (!act_str) { | |
725 | ovs_fatal(0, "must specify an action"); | |
726 | } | |
727 | *(act_str-1) = '\0'; | |
728 | ||
729 | act_str = strchr(act_str, '='); | |
730 | if (!act_str) { | |
731 | ovs_fatal(0, "must specify an action"); | |
732 | } | |
733 | ||
734 | act_str++; | |
735 | ||
736 | str_to_action(act_str, actions); | |
737 | } | |
738 | memset(match, 0, sizeof *match); | |
739 | wildcards = OFPFW_ALL; | |
740 | for (name = strtok_r(string, "=, \t\r\n", &save_ptr); name; | |
741 | name = strtok_r(NULL, "=, \t\r\n", &save_ptr)) { | |
742 | const struct protocol *p; | |
743 | ||
744 | if (parse_protocol(name, &p)) { | |
745 | wildcards &= ~OFPFW_DL_TYPE; | |
746 | match->dl_type = htons(p->dl_type); | |
747 | if (p->nw_proto) { | |
748 | wildcards &= ~OFPFW_NW_PROTO; | |
749 | match->nw_proto = p->nw_proto; | |
750 | } | |
751 | } else { | |
752 | const struct field *f; | |
753 | char *value; | |
754 | ||
755 | value = strtok_r(NULL, ", \t\r\n", &save_ptr); | |
756 | if (!value) { | |
757 | ovs_fatal(0, "field %s missing value", name); | |
758 | } | |
759 | ||
760 | if (table_idx && !strcmp(name, "table")) { | |
761 | *table_idx = atoi(value); | |
762 | } else if (out_port && !strcmp(name, "out_port")) { | |
763 | *out_port = atoi(value); | |
764 | } else if (priority && !strcmp(name, "priority")) { | |
765 | *priority = atoi(value); | |
766 | } else if (idle_timeout && !strcmp(name, "idle_timeout")) { | |
767 | *idle_timeout = atoi(value); | |
768 | } else if (hard_timeout && !strcmp(name, "hard_timeout")) { | |
769 | *hard_timeout = atoi(value); | |
770 | } else if (parse_field(name, &f)) { | |
771 | void *data = (char *) match + f->offset; | |
772 | if (!strcmp(value, "*") || !strcmp(value, "ANY")) { | |
773 | wildcards |= f->wildcard; | |
774 | } else { | |
775 | wildcards &= ~f->wildcard; | |
776 | if (f->wildcard == OFPFW_IN_PORT | |
777 | && parse_port_name(value, (uint16_t *) data)) { | |
778 | /* Nothing to do. */ | |
779 | } else if (f->type == F_U8) { | |
780 | *(uint8_t *) data = str_to_u32(value); | |
781 | } else if (f->type == F_U16) { | |
782 | *(uint16_t *) data = htons(str_to_u32(value)); | |
783 | } else if (f->type == F_MAC) { | |
784 | str_to_mac(value, data); | |
785 | } else if (f->type == F_IP) { | |
786 | wildcards |= str_to_ip(value, data) << f->shift; | |
787 | } else { | |
788 | NOT_REACHED(); | |
789 | } | |
790 | } | |
791 | } else { | |
792 | ovs_fatal(0, "unknown keyword %s", name); | |
793 | } | |
794 | } | |
795 | } | |
796 | match->wildcards = htonl(wildcards); | |
797 | } | |
798 | ||
799 | static void | |
800 | do_dump_flows(const struct settings *s UNUSED, int argc, char *argv[]) | |
801 | { | |
802 | struct ofp_flow_stats_request *req; | |
803 | uint16_t out_port; | |
804 | struct ofpbuf *request; | |
805 | ||
806 | req = alloc_stats_request(sizeof *req, OFPST_FLOW, &request); | |
807 | str_to_flow(argc > 2 ? argv[2] : "", &req->match, NULL, | |
808 | &req->table_id, &out_port, NULL, NULL, NULL); | |
809 | memset(&req->pad, 0, sizeof req->pad); | |
810 | req->out_port = htons(out_port); | |
811 | ||
812 | dump_stats_transaction(argv[1], request); | |
813 | } | |
814 | ||
815 | static void | |
816 | do_dump_aggregate(const struct settings *s UNUSED, int argc, char *argv[]) | |
817 | { | |
818 | struct ofp_aggregate_stats_request *req; | |
819 | struct ofpbuf *request; | |
820 | uint16_t out_port; | |
821 | ||
822 | req = alloc_stats_request(sizeof *req, OFPST_AGGREGATE, &request); | |
823 | str_to_flow(argc > 2 ? argv[2] : "", &req->match, NULL, | |
824 | &req->table_id, &out_port, NULL, NULL, NULL); | |
825 | memset(&req->pad, 0, sizeof req->pad); | |
826 | req->out_port = htons(out_port); | |
827 | ||
828 | dump_stats_transaction(argv[1], request); | |
829 | } | |
830 | ||
831 | static void | |
832 | do_add_flow(const struct settings *s UNUSED, int argc UNUSED, char *argv[]) | |
833 | { | |
834 | struct vconn *vconn; | |
835 | struct ofpbuf *buffer; | |
836 | struct ofp_flow_mod *ofm; | |
837 | uint16_t priority, idle_timeout, hard_timeout; | |
838 | struct ofp_match match; | |
839 | ||
840 | /* Parse and send. str_to_flow() will expand and reallocate the data in | |
841 | * 'buffer', so we can't keep pointers to across the str_to_flow() call. */ | |
842 | make_openflow(sizeof *ofm, OFPT_FLOW_MOD, &buffer); | |
843 | str_to_flow(argv[2], &match, buffer, | |
844 | NULL, NULL, &priority, &idle_timeout, &hard_timeout); | |
845 | ofm = buffer->data; | |
846 | ofm->match = match; | |
847 | ofm->command = htons(OFPFC_ADD); | |
848 | ofm->idle_timeout = htons(idle_timeout); | |
849 | ofm->hard_timeout = htons(hard_timeout); | |
850 | ofm->buffer_id = htonl(UINT32_MAX); | |
851 | ofm->priority = htons(priority); | |
852 | ofm->reserved = htonl(0); | |
853 | ||
854 | open_vconn(argv[1], &vconn); | |
855 | send_openflow_buffer(vconn, buffer); | |
856 | vconn_close(vconn); | |
857 | } | |
858 | ||
859 | static void | |
860 | do_add_flows(const struct settings *s UNUSED, int argc UNUSED, char *argv[]) | |
861 | { | |
862 | struct vconn *vconn; | |
863 | FILE *file; | |
864 | char line[1024]; | |
865 | ||
866 | file = fopen(argv[2], "r"); | |
867 | if (file == NULL) { | |
868 | ovs_fatal(errno, "%s: open", argv[2]); | |
869 | } | |
870 | ||
871 | open_vconn(argv[1], &vconn); | |
872 | while (fgets(line, sizeof line, file)) { | |
873 | struct ofpbuf *buffer; | |
874 | struct ofp_flow_mod *ofm; | |
875 | uint16_t priority, idle_timeout, hard_timeout; | |
876 | struct ofp_match match; | |
877 | ||
878 | char *comment; | |
879 | ||
880 | /* Delete comments. */ | |
881 | comment = strchr(line, '#'); | |
882 | if (comment) { | |
883 | *comment = '\0'; | |
884 | } | |
885 | ||
886 | /* Drop empty lines. */ | |
887 | if (line[strspn(line, " \t\n")] == '\0') { | |
888 | continue; | |
889 | } | |
890 | ||
891 | /* Parse and send. str_to_flow() will expand and reallocate the data | |
892 | * in 'buffer', so we can't keep pointers to across the str_to_flow() | |
893 | * call. */ | |
894 | ofm = make_openflow(sizeof *ofm, OFPT_FLOW_MOD, &buffer); | |
895 | str_to_flow(line, &match, buffer, | |
896 | NULL, NULL, &priority, &idle_timeout, &hard_timeout); | |
897 | ofm = buffer->data; | |
898 | ofm->match = match; | |
899 | ofm->command = htons(OFPFC_ADD); | |
900 | ofm->idle_timeout = htons(idle_timeout); | |
901 | ofm->hard_timeout = htons(hard_timeout); | |
902 | ofm->buffer_id = htonl(UINT32_MAX); | |
903 | ofm->priority = htons(priority); | |
904 | ofm->reserved = htonl(0); | |
905 | ||
906 | send_openflow_buffer(vconn, buffer); | |
907 | } | |
908 | vconn_close(vconn); | |
909 | fclose(file); | |
910 | } | |
911 | ||
912 | static void | |
913 | do_mod_flows(const struct settings *s, int argc UNUSED, char *argv[]) | |
914 | { | |
915 | uint16_t priority, idle_timeout, hard_timeout; | |
916 | struct vconn *vconn; | |
917 | struct ofpbuf *buffer; | |
918 | struct ofp_flow_mod *ofm; | |
919 | ||
920 | /* Parse and send. */ | |
921 | ofm = make_openflow(sizeof *ofm, OFPT_FLOW_MOD, &buffer); | |
922 | str_to_flow(argv[2], &ofm->match, buffer, | |
923 | NULL, NULL, &priority, &idle_timeout, &hard_timeout); | |
924 | if (s->strict) { | |
925 | ofm->command = htons(OFPFC_MODIFY_STRICT); | |
926 | } else { | |
927 | ofm->command = htons(OFPFC_MODIFY); | |
928 | } | |
929 | ofm->idle_timeout = htons(idle_timeout); | |
930 | ofm->hard_timeout = htons(hard_timeout); | |
931 | ofm->buffer_id = htonl(UINT32_MAX); | |
932 | ofm->priority = htons(priority); | |
933 | ofm->reserved = htonl(0); | |
934 | ||
935 | open_vconn(argv[1], &vconn); | |
936 | send_openflow_buffer(vconn, buffer); | |
937 | vconn_close(vconn); | |
938 | } | |
939 | ||
940 | static void do_del_flows(const struct settings *s, int argc, char *argv[]) | |
941 | { | |
942 | struct vconn *vconn; | |
943 | uint16_t priority; | |
944 | uint16_t out_port; | |
945 | struct ofpbuf *buffer; | |
946 | struct ofp_flow_mod *ofm; | |
947 | ||
948 | /* Parse and send. */ | |
949 | ofm = make_openflow(sizeof *ofm, OFPT_FLOW_MOD, &buffer); | |
950 | str_to_flow(argc > 2 ? argv[2] : "", &ofm->match, NULL, NULL, | |
951 | &out_port, &priority, NULL, NULL); | |
952 | if (s->strict) { | |
953 | ofm->command = htons(OFPFC_DELETE_STRICT); | |
954 | } else { | |
955 | ofm->command = htons(OFPFC_DELETE); | |
956 | } | |
957 | ofm->idle_timeout = htons(0); | |
958 | ofm->hard_timeout = htons(0); | |
959 | ofm->buffer_id = htonl(UINT32_MAX); | |
960 | ofm->out_port = htons(out_port); | |
961 | ofm->priority = htons(priority); | |
962 | ofm->reserved = htonl(0); | |
963 | ||
964 | open_vconn(argv[1], &vconn); | |
965 | send_openflow_buffer(vconn, buffer); | |
966 | vconn_close(vconn); | |
967 | } | |
968 | ||
969 | static void | |
970 | do_monitor(const struct settings *s UNUSED, int argc UNUSED, char *argv[]) | |
971 | { | |
972 | struct vconn *vconn; | |
973 | ||
974 | open_vconn(argv[1], &vconn); | |
975 | if (argc > 2) { | |
976 | int miss_send_len = atoi(argv[2]); | |
977 | int send_flow_exp = argc > 3 ? atoi(argv[3]) : 0; | |
978 | struct ofp_switch_config *osc; | |
979 | struct ofpbuf *buf; | |
980 | ||
981 | osc = make_openflow(sizeof *osc, OFPT_SET_CONFIG, &buf); | |
982 | osc->flags = htons(send_flow_exp ? OFPC_SEND_FLOW_EXP : 0); | |
983 | osc->miss_send_len = htons(miss_send_len); | |
984 | send_openflow_buffer(vconn, buf); | |
985 | } | |
986 | for (;;) { | |
987 | struct ofpbuf *b; | |
988 | run(vconn_recv_block(vconn, &b), "vconn_recv"); | |
989 | ofp_print(stderr, b->data, b->size, 2); | |
990 | ofpbuf_delete(b); | |
991 | } | |
992 | } | |
993 | ||
994 | static void | |
995 | do_dump_ports(const struct settings *s UNUSED, int argc UNUSED, char *argv[]) | |
996 | { | |
997 | dump_trivial_stats_transaction(argv[1], OFPST_PORT); | |
998 | } | |
999 | ||
1000 | static void | |
1001 | do_probe(const struct settings *s UNUSED, int argc UNUSED, char *argv[]) | |
1002 | { | |
1003 | struct ofpbuf *request; | |
1004 | struct vconn *vconn; | |
1005 | struct ofpbuf *reply; | |
1006 | ||
1007 | make_openflow(sizeof(struct ofp_header), OFPT_ECHO_REQUEST, &request); | |
1008 | open_vconn(argv[1], &vconn); | |
1009 | run(vconn_transact(vconn, request, &reply), "talking to %s", argv[1]); | |
1010 | if (reply->size != sizeof(struct ofp_header)) { | |
1011 | ovs_fatal(0, "reply does not match request"); | |
1012 | } | |
1013 | ofpbuf_delete(reply); | |
1014 | vconn_close(vconn); | |
1015 | } | |
1016 | ||
1017 | static void | |
1018 | do_mod_port(const struct settings *s UNUSED, int argc UNUSED, char *argv[]) | |
1019 | { | |
1020 | struct ofpbuf *request, *reply; | |
1021 | struct ofp_switch_features *osf; | |
1022 | struct ofp_port_mod *opm; | |
1023 | struct vconn *vconn; | |
1024 | char *endptr; | |
1025 | int n_ports; | |
1026 | int port_idx; | |
1027 | int port_no; | |
1028 | ||
1029 | ||
1030 | /* Check if the argument is a port index. Otherwise, treat it as | |
1031 | * the port name. */ | |
1032 | port_no = strtol(argv[2], &endptr, 10); | |
1033 | if (port_no == 0 && endptr == argv[2]) { | |
1034 | port_no = -1; | |
1035 | } | |
1036 | ||
1037 | /* Send a "Features Request" to get the information we need in order | |
1038 | * to modify the port. */ | |
1039 | make_openflow(sizeof(struct ofp_header), OFPT_FEATURES_REQUEST, &request); | |
1040 | open_vconn(argv[1], &vconn); | |
1041 | run(vconn_transact(vconn, request, &reply), "talking to %s", argv[1]); | |
1042 | ||
1043 | osf = reply->data; | |
1044 | n_ports = (reply->size - sizeof *osf) / sizeof *osf->ports; | |
1045 | ||
1046 | for (port_idx = 0; port_idx < n_ports; port_idx++) { | |
1047 | if (port_no != -1) { | |
1048 | /* Check argument as a port index */ | |
1049 | if (osf->ports[port_idx].port_no == htons(port_no)) { | |
1050 | break; | |
1051 | } | |
1052 | } else { | |
1053 | /* Check argument as an interface name */ | |
1054 | if (!strncmp((char *)osf->ports[port_idx].name, argv[2], | |
1055 | sizeof osf->ports[0].name)) { | |
1056 | break; | |
1057 | } | |
1058 | ||
1059 | } | |
1060 | } | |
1061 | if (port_idx == n_ports) { | |
1062 | ovs_fatal(0, "couldn't find monitored port: %s", argv[2]); | |
1063 | } | |
1064 | ||
1065 | opm = make_openflow(sizeof(struct ofp_port_mod), OFPT_PORT_MOD, &request); | |
1066 | opm->port_no = osf->ports[port_idx].port_no; | |
1067 | memcpy(opm->hw_addr, osf->ports[port_idx].hw_addr, sizeof opm->hw_addr); | |
1068 | opm->config = htonl(0); | |
1069 | opm->mask = htonl(0); | |
1070 | opm->advertise = htonl(0); | |
1071 | ||
1072 | printf("modifying port: %s\n", osf->ports[port_idx].name); | |
1073 | ||
1074 | if (!strncasecmp(argv[3], MOD_PORT_CMD_UP, sizeof MOD_PORT_CMD_UP)) { | |
1075 | opm->mask |= htonl(OFPPC_PORT_DOWN); | |
1076 | } else if (!strncasecmp(argv[3], MOD_PORT_CMD_DOWN, | |
1077 | sizeof MOD_PORT_CMD_DOWN)) { | |
1078 | opm->mask |= htonl(OFPPC_PORT_DOWN); | |
1079 | opm->config |= htonl(OFPPC_PORT_DOWN); | |
1080 | } else if (!strncasecmp(argv[3], MOD_PORT_CMD_FLOOD, | |
1081 | sizeof MOD_PORT_CMD_FLOOD)) { | |
1082 | opm->mask |= htonl(OFPPC_NO_FLOOD); | |
1083 | } else if (!strncasecmp(argv[3], MOD_PORT_CMD_NOFLOOD, | |
1084 | sizeof MOD_PORT_CMD_NOFLOOD)) { | |
1085 | opm->mask |= htonl(OFPPC_NO_FLOOD); | |
1086 | opm->config |= htonl(OFPPC_NO_FLOOD); | |
1087 | } else { | |
1088 | ovs_fatal(0, "unknown mod-port command '%s'", argv[3]); | |
1089 | } | |
1090 | ||
1091 | send_openflow_buffer(vconn, request); | |
1092 | ||
1093 | ofpbuf_delete(reply); | |
1094 | vconn_close(vconn); | |
1095 | } | |
1096 | ||
1097 | static void | |
1098 | do_ping(const struct settings *s UNUSED, int argc, char *argv[]) | |
1099 | { | |
1100 | size_t max_payload = 65535 - sizeof(struct ofp_header); | |
1101 | unsigned int payload; | |
1102 | struct vconn *vconn; | |
1103 | int i; | |
1104 | ||
1105 | payload = argc > 2 ? atoi(argv[2]) : 64; | |
1106 | if (payload > max_payload) { | |
1107 | ovs_fatal(0, "payload must be between 0 and %zu bytes", max_payload); | |
1108 | } | |
1109 | ||
1110 | open_vconn(argv[1], &vconn); | |
1111 | for (i = 0; i < 10; i++) { | |
1112 | struct timeval start, end; | |
1113 | struct ofpbuf *request, *reply; | |
1114 | struct ofp_header *rq_hdr, *rpy_hdr; | |
1115 | ||
1116 | rq_hdr = make_openflow(sizeof(struct ofp_header) + payload, | |
1117 | OFPT_ECHO_REQUEST, &request); | |
1118 | random_bytes(rq_hdr + 1, payload); | |
1119 | ||
1120 | gettimeofday(&start, NULL); | |
1121 | run(vconn_transact(vconn, ofpbuf_clone(request), &reply), "transact"); | |
1122 | gettimeofday(&end, NULL); | |
1123 | ||
1124 | rpy_hdr = reply->data; | |
1125 | if (reply->size != request->size | |
1126 | || memcmp(rpy_hdr + 1, rq_hdr + 1, payload) | |
1127 | || rpy_hdr->xid != rq_hdr->xid | |
1128 | || rpy_hdr->type != OFPT_ECHO_REPLY) { | |
1129 | printf("Reply does not match request. Request:\n"); | |
1130 | ofp_print(stdout, request, request->size, 2); | |
1131 | printf("Reply:\n"); | |
1132 | ofp_print(stdout, reply, reply->size, 2); | |
1133 | } | |
1134 | printf("%d bytes from %s: xid=%08"PRIx32" time=%.1f ms\n", | |
1135 | reply->size - sizeof *rpy_hdr, argv[1], rpy_hdr->xid, | |
1136 | (1000*(double)(end.tv_sec - start.tv_sec)) | |
1137 | + (.001*(end.tv_usec - start.tv_usec))); | |
1138 | ofpbuf_delete(request); | |
1139 | ofpbuf_delete(reply); | |
1140 | } | |
1141 | vconn_close(vconn); | |
1142 | } | |
1143 | ||
1144 | static void | |
1145 | do_benchmark(const struct settings *s UNUSED, int argc UNUSED, char *argv[]) | |
1146 | { | |
1147 | size_t max_payload = 65535 - sizeof(struct ofp_header); | |
1148 | struct timeval start, end; | |
1149 | unsigned int payload_size, message_size; | |
1150 | struct vconn *vconn; | |
1151 | double duration; | |
1152 | int count; | |
1153 | int i; | |
1154 | ||
1155 | payload_size = atoi(argv[2]); | |
1156 | if (payload_size > max_payload) { | |
1157 | ovs_fatal(0, "payload must be between 0 and %zu bytes", max_payload); | |
1158 | } | |
1159 | message_size = sizeof(struct ofp_header) + payload_size; | |
1160 | ||
1161 | count = atoi(argv[3]); | |
1162 | ||
1163 | printf("Sending %d packets * %u bytes (with header) = %u bytes total\n", | |
1164 | count, message_size, count * message_size); | |
1165 | ||
1166 | open_vconn(argv[1], &vconn); | |
1167 | gettimeofday(&start, NULL); | |
1168 | for (i = 0; i < count; i++) { | |
1169 | struct ofpbuf *request, *reply; | |
1170 | struct ofp_header *rq_hdr; | |
1171 | ||
1172 | rq_hdr = make_openflow(message_size, OFPT_ECHO_REQUEST, &request); | |
1173 | memset(rq_hdr + 1, 0, payload_size); | |
1174 | run(vconn_transact(vconn, request, &reply), "transact"); | |
1175 | ofpbuf_delete(reply); | |
1176 | } | |
1177 | gettimeofday(&end, NULL); | |
1178 | vconn_close(vconn); | |
1179 | ||
1180 | duration = ((1000*(double)(end.tv_sec - start.tv_sec)) | |
1181 | + (.001*(end.tv_usec - start.tv_usec))); | |
1182 | printf("Finished in %.1f ms (%.0f packets/s) (%.0f bytes/s)\n", | |
1183 | duration, count / (duration / 1000.0), | |
1184 | count * message_size / (duration / 1000.0)); | |
1185 | } | |
1186 | ||
1187 | static void | |
1188 | do_execute(const struct settings *s UNUSED, int argc, char *argv[]) | |
1189 | { | |
1190 | struct vconn *vconn; | |
1191 | struct ofpbuf *request; | |
1192 | struct nicira_header *nicira; | |
1193 | struct nx_command_reply *ncr; | |
1194 | uint32_t xid; | |
1195 | int i; | |
1196 | ||
1197 | nicira = make_openflow(sizeof *nicira, OFPT_VENDOR, &request); | |
1198 | xid = nicira->header.xid; | |
1199 | nicira->vendor = htonl(NX_VENDOR_ID); | |
1200 | nicira->subtype = htonl(NXT_COMMAND_REQUEST); | |
1201 | ofpbuf_put(request, argv[2], strlen(argv[2])); | |
1202 | for (i = 3; i < argc; i++) { | |
1203 | ofpbuf_put_zeros(request, 1); | |
1204 | ofpbuf_put(request, argv[i], strlen(argv[i])); | |
1205 | } | |
1206 | update_openflow_length(request); | |
1207 | ||
1208 | open_vconn(argv[1], &vconn); | |
1209 | run(vconn_send_block(vconn, request), "send"); | |
1210 | ||
1211 | for (;;) { | |
1212 | struct ofpbuf *reply; | |
1213 | uint32_t status; | |
1214 | ||
1215 | run(vconn_recv_xid(vconn, xid, &reply), "recv_xid"); | |
1216 | if (reply->size < sizeof *ncr) { | |
1217 | ovs_fatal(0, "reply is too short (%zu bytes < %zu bytes)", | |
1218 | reply->size, sizeof *ncr); | |
1219 | } | |
1220 | ncr = reply->data; | |
1221 | if (ncr->nxh.header.type != OFPT_VENDOR | |
1222 | || ncr->nxh.vendor != htonl(NX_VENDOR_ID) | |
1223 | || ncr->nxh.subtype != htonl(NXT_COMMAND_REPLY)) { | |
1224 | ovs_fatal(0, "reply is invalid"); | |
1225 | } | |
1226 | ||
1227 | status = ntohl(ncr->status); | |
1228 | if (status & NXT_STATUS_STARTED) { | |
1229 | /* Wait for a second reply. */ | |
1230 | continue; | |
1231 | } else if (status & NXT_STATUS_EXITED) { | |
1232 | fprintf(stderr, "process terminated normally with exit code %d", | |
1233 | status & NXT_STATUS_EXITSTATUS); | |
1234 | } else if (status & NXT_STATUS_SIGNALED) { | |
1235 | fprintf(stderr, "process terminated by signal %d", | |
1236 | status & NXT_STATUS_TERMSIG); | |
1237 | } else if (status & NXT_STATUS_ERROR) { | |
1238 | fprintf(stderr, "error executing command"); | |
1239 | } else { | |
1240 | fprintf(stderr, "process terminated for unknown reason"); | |
1241 | } | |
1242 | if (status & NXT_STATUS_COREDUMP) { | |
1243 | fprintf(stderr, " (core dumped)"); | |
1244 | } | |
1245 | putc('\n', stderr); | |
1246 | ||
1247 | fwrite(ncr + 1, reply->size - sizeof *ncr, 1, stdout); | |
1248 | break; | |
1249 | } | |
1250 | } | |
1251 | ||
1252 | static void | |
1253 | do_help(const struct settings *s UNUSED, int argc UNUSED, char *argv[] UNUSED) | |
1254 | { | |
1255 | usage(); | |
1256 | } | |
1257 | ||
1258 | static struct command all_commands[] = { | |
1259 | { "show", 1, 1, do_show }, | |
1260 | { "status", 1, 2, do_status }, | |
1261 | { "monitor", 1, 3, do_monitor }, | |
1262 | { "dump-desc", 1, 1, do_dump_desc }, | |
1263 | { "dump-tables", 1, 1, do_dump_tables }, | |
1264 | { "dump-flows", 1, 2, do_dump_flows }, | |
1265 | { "dump-aggregate", 1, 2, do_dump_aggregate }, | |
1266 | { "add-flow", 2, 2, do_add_flow }, | |
1267 | { "add-flows", 2, 2, do_add_flows }, | |
1268 | { "mod-flows", 2, 2, do_mod_flows }, | |
1269 | { "del-flows", 1, 2, do_del_flows }, | |
1270 | { "dump-ports", 1, 1, do_dump_ports }, | |
1271 | { "mod-port", 3, 3, do_mod_port }, | |
1272 | { "probe", 1, 1, do_probe }, | |
1273 | { "ping", 1, 2, do_ping }, | |
1274 | { "benchmark", 3, 3, do_benchmark }, | |
1275 | { "execute", 2, INT_MAX, do_execute }, | |
1276 | { "help", 0, INT_MAX, do_help }, | |
1277 | { NULL, 0, 0, NULL }, | |
1278 | }; |