]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * Copyright (c) 2008, 2009, 2010 Nicira Networks. | |
3 | * | |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | * you may not use this file except in compliance with the License. | |
6 | * You may obtain a copy of the License at: | |
7 | * | |
8 | * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | * | |
10 | * Unless required by applicable law or agreed to in writing, software | |
11 | * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | * See the License for the specific language governing permissions and | |
14 | * limitations under the License. | |
15 | */ | |
16 | ||
17 | #include <config.h> | |
18 | #include <errno.h> | |
19 | #include <getopt.h> | |
20 | #include <inttypes.h> | |
21 | #include <net/if.h> | |
22 | #include <signal.h> | |
23 | #include <stdlib.h> | |
24 | #include <string.h> | |
25 | #include <unistd.h> | |
26 | #include <sys/stat.h> | |
27 | #include <sys/time.h> | |
28 | ||
29 | #include "byte-order.h" | |
30 | #include "classifier.h" | |
31 | #include "command-line.h" | |
32 | #include "compiler.h" | |
33 | #include "dirs.h" | |
34 | #include "dpif.h" | |
35 | #include "dynamic-string.h" | |
36 | #include "netlink.h" | |
37 | #include "nx-match.h" | |
38 | #include "odp-util.h" | |
39 | #include "ofp-parse.h" | |
40 | #include "ofp-print.h" | |
41 | #include "ofp-util.h" | |
42 | #include "ofpbuf.h" | |
43 | #include "openflow/nicira-ext.h" | |
44 | #include "openflow/openflow.h" | |
45 | #include "random.h" | |
46 | #include "stream-ssl.h" | |
47 | #include "timeval.h" | |
48 | #include "util.h" | |
49 | #include "vconn.h" | |
50 | #include "vlog.h" | |
51 | ||
52 | VLOG_DEFINE_THIS_MODULE(ofctl); | |
53 | ||
54 | /* Use strict matching for flow mod commands? */ | |
55 | static bool strict; | |
56 | ||
57 | static const struct command all_commands[]; | |
58 | ||
59 | static void usage(void) NO_RETURN; | |
60 | static void parse_options(int argc, char *argv[]); | |
61 | ||
62 | int | |
63 | main(int argc, char *argv[]) | |
64 | { | |
65 | set_program_name(argv[0]); | |
66 | parse_options(argc, argv); | |
67 | signal(SIGPIPE, SIG_IGN); | |
68 | run_command(argc - optind, argv + optind, all_commands); | |
69 | return 0; | |
70 | } | |
71 | ||
72 | static void | |
73 | parse_options(int argc, char *argv[]) | |
74 | { | |
75 | enum { | |
76 | OPT_STRICT = UCHAR_MAX + 1, | |
77 | VLOG_OPTION_ENUMS | |
78 | }; | |
79 | static struct option long_options[] = { | |
80 | {"timeout", required_argument, 0, 't'}, | |
81 | {"strict", no_argument, 0, OPT_STRICT}, | |
82 | {"help", no_argument, 0, 'h'}, | |
83 | {"version", no_argument, 0, 'V'}, | |
84 | VLOG_LONG_OPTIONS, | |
85 | STREAM_SSL_LONG_OPTIONS | |
86 | {0, 0, 0, 0}, | |
87 | }; | |
88 | char *short_options = long_options_to_short_options(long_options); | |
89 | ||
90 | for (;;) { | |
91 | unsigned long int timeout; | |
92 | int c; | |
93 | ||
94 | c = getopt_long(argc, argv, short_options, long_options, NULL); | |
95 | if (c == -1) { | |
96 | break; | |
97 | } | |
98 | ||
99 | switch (c) { | |
100 | case 't': | |
101 | timeout = strtoul(optarg, NULL, 10); | |
102 | if (timeout <= 0) { | |
103 | ovs_fatal(0, "value %s on -t or --timeout is not at least 1", | |
104 | optarg); | |
105 | } else { | |
106 | time_alarm(timeout); | |
107 | } | |
108 | break; | |
109 | ||
110 | case 'h': | |
111 | usage(); | |
112 | ||
113 | case 'V': | |
114 | OVS_PRINT_VERSION(OFP_VERSION, OFP_VERSION); | |
115 | exit(EXIT_SUCCESS); | |
116 | ||
117 | case OPT_STRICT: | |
118 | strict = true; | |
119 | break; | |
120 | ||
121 | VLOG_OPTION_HANDLERS | |
122 | STREAM_SSL_OPTION_HANDLERS | |
123 | ||
124 | case '?': | |
125 | exit(EXIT_FAILURE); | |
126 | ||
127 | default: | |
128 | abort(); | |
129 | } | |
130 | } | |
131 | free(short_options); | |
132 | } | |
133 | ||
134 | static void | |
135 | usage(void) | |
136 | { | |
137 | printf("%s: OpenFlow switch management utility\n" | |
138 | "usage: %s [OPTIONS] COMMAND [ARG...]\n" | |
139 | "\nFor OpenFlow switches:\n" | |
140 | " show SWITCH show OpenFlow information\n" | |
141 | " status SWITCH [KEY] report statistics (about KEY)\n" | |
142 | " dump-desc SWITCH print switch description\n" | |
143 | " dump-tables SWITCH print table stats\n" | |
144 | " mod-port SWITCH IFACE ACT modify port behavior\n" | |
145 | " dump-ports SWITCH [PORT] print port statistics\n" | |
146 | " dump-flows SWITCH print all flow entries\n" | |
147 | " dump-flows SWITCH FLOW print matching FLOWs\n" | |
148 | " dump-aggregate SWITCH print aggregate flow statistics\n" | |
149 | " dump-aggregate SWITCH FLOW print aggregate stats for FLOWs\n" | |
150 | " queue-stats SWITCH [PORT [QUEUE]] dump queue stats\n" | |
151 | " add-flow SWITCH FLOW add flow described by FLOW\n" | |
152 | " add-flows SWITCH FILE add flows from FILE\n" | |
153 | " mod-flows SWITCH FLOW modify actions of matching FLOWs\n" | |
154 | " del-flows SWITCH [FLOW] delete matching FLOWs\n" | |
155 | " monitor SWITCH [MISSLEN] print packets received from SWITCH\n" | |
156 | "\nFor OpenFlow switches and controllers:\n" | |
157 | " probe VCONN probe whether VCONN is up\n" | |
158 | " ping VCONN [N] latency of N-byte echos\n" | |
159 | " benchmark VCONN N COUNT bandwidth of COUNT N-byte echos\n" | |
160 | "where each SWITCH is an active OpenFlow connection method.\n", | |
161 | program_name, program_name); | |
162 | vconn_usage(true, false, false); | |
163 | vlog_usage(); | |
164 | printf("\nOther options:\n" | |
165 | " --strict use strict match for flow commands\n" | |
166 | " -t, --timeout=SECS give up after SECS seconds\n" | |
167 | " -h, --help display this help message\n" | |
168 | " -V, --version display version information\n"); | |
169 | exit(EXIT_SUCCESS); | |
170 | } | |
171 | ||
172 | static void run(int retval, const char *message, ...) | |
173 | PRINTF_FORMAT(2, 3); | |
174 | ||
175 | static void run(int retval, const char *message, ...) | |
176 | { | |
177 | if (retval) { | |
178 | va_list args; | |
179 | ||
180 | fprintf(stderr, "%s: ", program_name); | |
181 | va_start(args, message); | |
182 | vfprintf(stderr, message, args); | |
183 | va_end(args); | |
184 | if (retval == EOF) { | |
185 | fputs(": unexpected end of file\n", stderr); | |
186 | } else { | |
187 | fprintf(stderr, ": %s\n", strerror(retval)); | |
188 | } | |
189 | ||
190 | exit(EXIT_FAILURE); | |
191 | } | |
192 | } | |
193 | \f | |
194 | /* Generic commands. */ | |
195 | ||
196 | static void | |
197 | open_vconn_socket(const char *name, struct vconn **vconnp) | |
198 | { | |
199 | char *vconn_name = xasprintf("unix:%s", name); | |
200 | VLOG_DBG("connecting to %s", vconn_name); | |
201 | run(vconn_open_block(vconn_name, OFP_VERSION, vconnp), | |
202 | "connecting to %s", vconn_name); | |
203 | free(vconn_name); | |
204 | } | |
205 | ||
206 | static void | |
207 | open_vconn__(const char *name, const char *default_suffix, | |
208 | struct vconn **vconnp) | |
209 | { | |
210 | struct dpif *dpif; | |
211 | struct stat s; | |
212 | char *bridge_path, *datapath_name, *datapath_type; | |
213 | ||
214 | bridge_path = xasprintf("%s/%s.%s", ovs_rundir(), name, default_suffix); | |
215 | dp_parse_name(name, &datapath_name, &datapath_type); | |
216 | ||
217 | if (strstr(name, ":")) { | |
218 | run(vconn_open_block(name, OFP_VERSION, vconnp), | |
219 | "connecting to %s", name); | |
220 | } else if (!stat(name, &s) && S_ISSOCK(s.st_mode)) { | |
221 | open_vconn_socket(name, vconnp); | |
222 | } else if (!stat(bridge_path, &s) && S_ISSOCK(s.st_mode)) { | |
223 | open_vconn_socket(bridge_path, vconnp); | |
224 | } else if (!dpif_open(datapath_name, datapath_type, &dpif)) { | |
225 | char dpif_name[IF_NAMESIZE + 1]; | |
226 | char *socket_name; | |
227 | ||
228 | run(dpif_port_get_name(dpif, ODPP_LOCAL, dpif_name, sizeof dpif_name), | |
229 | "obtaining name of %s", dpif_name); | |
230 | dpif_close(dpif); | |
231 | if (strcmp(dpif_name, name)) { | |
232 | VLOG_DBG("datapath %s is named %s", name, dpif_name); | |
233 | } | |
234 | ||
235 | socket_name = xasprintf("%s/%s.%s", | |
236 | ovs_rundir(), dpif_name, default_suffix); | |
237 | if (stat(socket_name, &s)) { | |
238 | ovs_fatal(errno, "cannot connect to %s: stat failed on %s", | |
239 | name, socket_name); | |
240 | } else if (!S_ISSOCK(s.st_mode)) { | |
241 | ovs_fatal(0, "cannot connect to %s: %s is not a socket", | |
242 | name, socket_name); | |
243 | } | |
244 | ||
245 | open_vconn_socket(socket_name, vconnp); | |
246 | free(socket_name); | |
247 | } else { | |
248 | ovs_fatal(0, "%s is not a valid connection method", name); | |
249 | } | |
250 | ||
251 | free(datapath_name); | |
252 | free(datapath_type); | |
253 | free(bridge_path); | |
254 | } | |
255 | ||
256 | static void | |
257 | open_vconn(const char *name, struct vconn **vconnp) | |
258 | { | |
259 | return open_vconn__(name, "mgmt", vconnp); | |
260 | } | |
261 | ||
262 | static void * | |
263 | alloc_stats_request(size_t body_len, uint16_t type, struct ofpbuf **bufferp) | |
264 | { | |
265 | struct ofp_stats_request *rq; | |
266 | rq = make_openflow((offsetof(struct ofp_stats_request, body) | |
267 | + body_len), OFPT_STATS_REQUEST, bufferp); | |
268 | rq->type = htons(type); | |
269 | rq->flags = htons(0); | |
270 | return rq->body; | |
271 | } | |
272 | ||
273 | static void | |
274 | send_openflow_buffer(struct vconn *vconn, struct ofpbuf *buffer) | |
275 | { | |
276 | update_openflow_length(buffer); | |
277 | run(vconn_send_block(vconn, buffer), "failed to send packet to switch"); | |
278 | } | |
279 | ||
280 | static void | |
281 | dump_transaction(const char *vconn_name, struct ofpbuf *request) | |
282 | { | |
283 | struct vconn *vconn; | |
284 | struct ofpbuf *reply; | |
285 | ||
286 | update_openflow_length(request); | |
287 | open_vconn(vconn_name, &vconn); | |
288 | run(vconn_transact(vconn, request, &reply), "talking to %s", vconn_name); | |
289 | ofp_print(stdout, reply->data, reply->size, 1); | |
290 | vconn_close(vconn); | |
291 | } | |
292 | ||
293 | static void | |
294 | dump_trivial_transaction(const char *vconn_name, uint8_t request_type) | |
295 | { | |
296 | struct ofpbuf *request; | |
297 | make_openflow(sizeof(struct ofp_header), request_type, &request); | |
298 | dump_transaction(vconn_name, request); | |
299 | } | |
300 | ||
301 | static void | |
302 | dump_stats_transaction(const char *vconn_name, struct ofpbuf *request) | |
303 | { | |
304 | ovs_be32 send_xid = ((struct ofp_header *) request->data)->xid; | |
305 | struct vconn *vconn; | |
306 | bool done = false; | |
307 | ||
308 | open_vconn(vconn_name, &vconn); | |
309 | send_openflow_buffer(vconn, request); | |
310 | while (!done) { | |
311 | uint32_t recv_xid; | |
312 | struct ofpbuf *reply; | |
313 | ||
314 | run(vconn_recv_block(vconn, &reply), "OpenFlow packet receive failed"); | |
315 | recv_xid = ((struct ofp_header *) reply->data)->xid; | |
316 | if (send_xid == recv_xid) { | |
317 | struct ofp_stats_reply *osr; | |
318 | ||
319 | ofp_print(stdout, reply->data, reply->size, 1); | |
320 | ||
321 | osr = ofpbuf_at(reply, 0, sizeof *osr); | |
322 | done = !osr || !(ntohs(osr->flags) & OFPSF_REPLY_MORE); | |
323 | } else { | |
324 | VLOG_DBG("received reply with xid %08"PRIx32" " | |
325 | "!= expected %08"PRIx32, recv_xid, send_xid); | |
326 | } | |
327 | ofpbuf_delete(reply); | |
328 | } | |
329 | vconn_close(vconn); | |
330 | } | |
331 | ||
332 | static void | |
333 | dump_trivial_stats_transaction(const char *vconn_name, uint8_t stats_type) | |
334 | { | |
335 | struct ofpbuf *request; | |
336 | alloc_stats_request(0, stats_type, &request); | |
337 | dump_stats_transaction(vconn_name, request); | |
338 | } | |
339 | ||
340 | /* Sends 'request', which should be a request that only has a reply if an error | |
341 | * occurs, and waits for it to succeed or fail. If an error does occur, prints | |
342 | * it and exits with an error. */ | |
343 | static void | |
344 | dump_noreply_transaction(struct vconn *vconn, struct ofpbuf *request) | |
345 | { | |
346 | struct ofpbuf *reply; | |
347 | ||
348 | update_openflow_length(request); | |
349 | run(vconn_transact_noreply(vconn, request, &reply), | |
350 | "talking to %s", vconn_get_name(vconn)); | |
351 | if (reply) { | |
352 | ofp_print(stderr, reply->data, reply->size, 2); | |
353 | exit(1); | |
354 | } | |
355 | ofpbuf_delete(reply); | |
356 | } | |
357 | ||
358 | static void | |
359 | do_show(int argc OVS_UNUSED, char *argv[]) | |
360 | { | |
361 | dump_trivial_transaction(argv[1], OFPT_FEATURES_REQUEST); | |
362 | dump_trivial_transaction(argv[1], OFPT_GET_CONFIG_REQUEST); | |
363 | } | |
364 | ||
365 | static void | |
366 | do_status(int argc, char *argv[]) | |
367 | { | |
368 | struct nicira_header *request, *reply; | |
369 | struct vconn *vconn; | |
370 | struct ofpbuf *b; | |
371 | ||
372 | request = make_nxmsg(sizeof *request, NXT_STATUS_REQUEST, &b); | |
373 | if (argc > 2) { | |
374 | ofpbuf_put(b, argv[2], strlen(argv[2])); | |
375 | update_openflow_length(b); | |
376 | } | |
377 | open_vconn(argv[1], &vconn); | |
378 | run(vconn_transact(vconn, b, &b), "talking to %s", argv[1]); | |
379 | vconn_close(vconn); | |
380 | ||
381 | if (b->size < sizeof *reply) { | |
382 | ovs_fatal(0, "short reply (%zu bytes)", b->size); | |
383 | } | |
384 | reply = b->data; | |
385 | if (reply->header.type != OFPT_VENDOR | |
386 | || reply->vendor != ntohl(NX_VENDOR_ID) | |
387 | || reply->subtype != ntohl(NXT_STATUS_REPLY)) { | |
388 | ofp_print(stderr, b->data, b->size, 2); | |
389 | ovs_fatal(0, "bad reply"); | |
390 | } | |
391 | ||
392 | fwrite(reply + 1, b->size - sizeof *reply, 1, stdout); | |
393 | } | |
394 | ||
395 | static void | |
396 | do_dump_desc(int argc OVS_UNUSED, char *argv[]) | |
397 | { | |
398 | dump_trivial_stats_transaction(argv[1], OFPST_DESC); | |
399 | } | |
400 | ||
401 | static void | |
402 | do_dump_tables(int argc OVS_UNUSED, char *argv[]) | |
403 | { | |
404 | dump_trivial_stats_transaction(argv[1], OFPST_TABLE); | |
405 | } | |
406 | ||
407 | /* Opens a connection to 'vconn_name', fetches the ofp_phy_port structure for | |
408 | * 'port_name' (which may be a port name or number), and copies it into | |
409 | * '*oppp'. */ | |
410 | static void | |
411 | fetch_ofp_phy_port(const char *vconn_name, const char *port_name, | |
412 | struct ofp_phy_port *oppp) | |
413 | { | |
414 | struct ofpbuf *request, *reply; | |
415 | struct ofp_switch_features *osf; | |
416 | unsigned int port_no; | |
417 | struct vconn *vconn; | |
418 | int n_ports; | |
419 | int port_idx; | |
420 | ||
421 | /* Try to interpret the argument as a port number. */ | |
422 | if (!str_to_uint(port_name, 10, &port_no)) { | |
423 | port_no = UINT_MAX; | |
424 | } | |
425 | ||
426 | /* Fetch the switch's ofp_switch_features. */ | |
427 | make_openflow(sizeof(struct ofp_header), OFPT_FEATURES_REQUEST, &request); | |
428 | open_vconn(vconn_name, &vconn); | |
429 | run(vconn_transact(vconn, request, &reply), "talking to %s", vconn_name); | |
430 | ||
431 | osf = reply->data; | |
432 | if (reply->size < sizeof *osf) { | |
433 | ovs_fatal(0, "%s: received too-short features reply (only %zu bytes)", | |
434 | vconn_name, reply->size); | |
435 | } | |
436 | n_ports = (reply->size - sizeof *osf) / sizeof *osf->ports; | |
437 | ||
438 | for (port_idx = 0; port_idx < n_ports; port_idx++) { | |
439 | const struct ofp_phy_port *opp = &osf->ports[port_idx]; | |
440 | ||
441 | if (port_no != UINT_MAX | |
442 | ? htons(port_no) == opp->port_no | |
443 | : !strncmp(opp->name, port_name, sizeof opp->name)) { | |
444 | *oppp = *opp; | |
445 | ofpbuf_delete(reply); | |
446 | vconn_close(vconn); | |
447 | return; | |
448 | } | |
449 | } | |
450 | ovs_fatal(0, "%s: couldn't find port `%s'", vconn_name, port_name); | |
451 | } | |
452 | ||
453 | /* Returns the port number corresponding to 'port_name' (which may be a port | |
454 | * name or number) within the switch 'vconn_name'. */ | |
455 | static uint16_t | |
456 | str_to_port_no(const char *vconn_name, const char *port_name) | |
457 | { | |
458 | unsigned int port_no; | |
459 | ||
460 | if (str_to_uint(port_name, 10, &port_no)) { | |
461 | return port_no; | |
462 | } else { | |
463 | struct ofp_phy_port opp; | |
464 | ||
465 | fetch_ofp_phy_port(vconn_name, port_name, &opp); | |
466 | return ntohs(opp.port_no); | |
467 | } | |
468 | } | |
469 | ||
470 | static void | |
471 | do_dump_flows(int argc, char *argv[]) | |
472 | { | |
473 | struct ofp_flow_stats_request *req; | |
474 | struct parsed_flow pf; | |
475 | struct ofpbuf *request; | |
476 | ||
477 | req = alloc_stats_request(sizeof *req, OFPST_FLOW, &request); | |
478 | parse_ofp_str(&pf, NULL, argc > 2 ? argv[2] : ""); | |
479 | ofputil_cls_rule_to_match(&pf.rule, NXFF_OPENFLOW10, &req->match); | |
480 | memset(&req->pad, 0, sizeof req->pad); | |
481 | req->table_id = pf.table_idx; | |
482 | req->out_port = htons(pf.out_port); | |
483 | ||
484 | dump_stats_transaction(argv[1], request); | |
485 | } | |
486 | ||
487 | static void | |
488 | do_dump_aggregate(int argc, char *argv[]) | |
489 | { | |
490 | struct ofp_aggregate_stats_request *req; | |
491 | struct ofpbuf *request; | |
492 | struct parsed_flow pf; | |
493 | ||
494 | req = alloc_stats_request(sizeof *req, OFPST_AGGREGATE, &request); | |
495 | parse_ofp_str(&pf, NULL, argc > 2 ? argv[2] : ""); | |
496 | ofputil_cls_rule_to_match(&pf.rule, NXFF_OPENFLOW10, &req->match); | |
497 | memset(&req->pad, 0, sizeof req->pad); | |
498 | req->table_id = pf.table_idx; | |
499 | req->out_port = htons(pf.out_port); | |
500 | ||
501 | dump_stats_transaction(argv[1], request); | |
502 | } | |
503 | ||
504 | static void | |
505 | do_queue_stats(int argc, char *argv[]) | |
506 | { | |
507 | struct ofp_queue_stats_request *req; | |
508 | struct ofpbuf *request; | |
509 | ||
510 | req = alloc_stats_request(sizeof *req, OFPST_QUEUE, &request); | |
511 | ||
512 | if (argc > 2 && argv[2][0] && strcasecmp(argv[2], "all")) { | |
513 | req->port_no = htons(str_to_port_no(argv[1], argv[2])); | |
514 | } else { | |
515 | req->port_no = htons(OFPP_ALL); | |
516 | } | |
517 | if (argc > 3 && argv[3][0] && strcasecmp(argv[3], "all")) { | |
518 | req->queue_id = htonl(atoi(argv[3])); | |
519 | } else { | |
520 | req->queue_id = htonl(OFPQ_ALL); | |
521 | } | |
522 | ||
523 | memset(req->pad, 0, sizeof req->pad); | |
524 | ||
525 | dump_stats_transaction(argv[1], request); | |
526 | } | |
527 | ||
528 | static void | |
529 | do_add_flow(int argc OVS_UNUSED, char *argv[]) | |
530 | { | |
531 | struct vconn *vconn; | |
532 | struct ofpbuf *request; | |
533 | ||
534 | request = parse_ofp_flow_mod_str(argv[2], OFPFC_ADD); | |
535 | ||
536 | open_vconn(argv[1], &vconn); | |
537 | dump_noreply_transaction(vconn, request); | |
538 | vconn_close(vconn); | |
539 | } | |
540 | ||
541 | static void | |
542 | do_add_flows(int argc OVS_UNUSED, char *argv[]) | |
543 | { | |
544 | struct vconn *vconn; | |
545 | struct ofpbuf *b; | |
546 | FILE *file; | |
547 | ||
548 | file = fopen(argv[2], "r"); | |
549 | if (file == NULL) { | |
550 | ovs_fatal(errno, "%s: open", argv[2]); | |
551 | } | |
552 | ||
553 | open_vconn(argv[1], &vconn); | |
554 | while ((b = parse_ofp_add_flow_file(file)) != NULL) { | |
555 | dump_noreply_transaction(vconn, b); | |
556 | } | |
557 | vconn_close(vconn); | |
558 | fclose(file); | |
559 | } | |
560 | ||
561 | static void | |
562 | do_mod_flows(int argc OVS_UNUSED, char *argv[]) | |
563 | { | |
564 | struct vconn *vconn; | |
565 | struct ofpbuf *buffer; | |
566 | uint16_t command; | |
567 | ||
568 | command = strict ? OFPFC_MODIFY_STRICT : OFPFC_MODIFY; | |
569 | buffer = parse_ofp_flow_mod_str(argv[2], command); | |
570 | open_vconn(argv[1], &vconn); | |
571 | dump_noreply_transaction(vconn, buffer); | |
572 | vconn_close(vconn); | |
573 | } | |
574 | ||
575 | static void do_del_flows(int argc, char *argv[]) | |
576 | { | |
577 | struct vconn *vconn; | |
578 | struct ofpbuf *buffer; | |
579 | uint16_t command; | |
580 | ||
581 | command = strict ? OFPFC_DELETE_STRICT : OFPFC_DELETE; | |
582 | buffer = parse_ofp_flow_mod_str(argc > 2 ? argv[2] : "", command); | |
583 | ||
584 | open_vconn(argv[1], &vconn); | |
585 | dump_noreply_transaction(vconn, buffer); | |
586 | vconn_close(vconn); | |
587 | } | |
588 | ||
589 | static void | |
590 | monitor_vconn(struct vconn *vconn) | |
591 | { | |
592 | for (;;) { | |
593 | struct ofpbuf *b; | |
594 | run(vconn_recv_block(vconn, &b), "vconn_recv"); | |
595 | ofp_print(stderr, b->data, b->size, 2); | |
596 | ofpbuf_delete(b); | |
597 | } | |
598 | } | |
599 | ||
600 | static void | |
601 | do_monitor(int argc, char *argv[]) | |
602 | { | |
603 | struct vconn *vconn; | |
604 | ||
605 | open_vconn(argv[1], &vconn); | |
606 | if (argc > 2) { | |
607 | int miss_send_len = atoi(argv[2]); | |
608 | struct ofp_switch_config *osc; | |
609 | struct ofpbuf *buf; | |
610 | ||
611 | osc = make_openflow(sizeof *osc, OFPT_SET_CONFIG, &buf); | |
612 | osc->miss_send_len = htons(miss_send_len); | |
613 | dump_noreply_transaction(vconn, buf); | |
614 | } | |
615 | monitor_vconn(vconn); | |
616 | } | |
617 | ||
618 | static void | |
619 | do_snoop(int argc OVS_UNUSED, char *argv[]) | |
620 | { | |
621 | struct vconn *vconn; | |
622 | ||
623 | open_vconn__(argv[1], "snoop", &vconn); | |
624 | monitor_vconn(vconn); | |
625 | } | |
626 | ||
627 | static void | |
628 | do_dump_ports(int argc, char *argv[]) | |
629 | { | |
630 | struct ofp_port_stats_request *req; | |
631 | struct ofpbuf *request; | |
632 | uint16_t port; | |
633 | ||
634 | req = alloc_stats_request(sizeof *req, OFPST_PORT, &request); | |
635 | port = argc > 2 ? str_to_port_no(argv[1], argv[2]) : OFPP_NONE; | |
636 | req->port_no = htons(port); | |
637 | dump_stats_transaction(argv[1], request); | |
638 | } | |
639 | ||
640 | static void | |
641 | do_probe(int argc OVS_UNUSED, char *argv[]) | |
642 | { | |
643 | struct ofpbuf *request; | |
644 | struct vconn *vconn; | |
645 | struct ofpbuf *reply; | |
646 | ||
647 | make_openflow(sizeof(struct ofp_header), OFPT_ECHO_REQUEST, &request); | |
648 | open_vconn(argv[1], &vconn); | |
649 | run(vconn_transact(vconn, request, &reply), "talking to %s", argv[1]); | |
650 | if (reply->size != sizeof(struct ofp_header)) { | |
651 | ovs_fatal(0, "reply does not match request"); | |
652 | } | |
653 | ofpbuf_delete(reply); | |
654 | vconn_close(vconn); | |
655 | } | |
656 | ||
657 | static void | |
658 | do_mod_port(int argc OVS_UNUSED, char *argv[]) | |
659 | { | |
660 | struct ofp_port_mod *opm; | |
661 | struct ofp_phy_port opp; | |
662 | struct ofpbuf *request; | |
663 | struct vconn *vconn; | |
664 | ||
665 | fetch_ofp_phy_port(argv[1], argv[2], &opp); | |
666 | ||
667 | opm = make_openflow(sizeof(struct ofp_port_mod), OFPT_PORT_MOD, &request); | |
668 | opm->port_no = opp.port_no; | |
669 | memcpy(opm->hw_addr, opp.hw_addr, sizeof opm->hw_addr); | |
670 | opm->config = htonl(0); | |
671 | opm->mask = htonl(0); | |
672 | opm->advertise = htonl(0); | |
673 | ||
674 | if (!strcasecmp(argv[3], "up")) { | |
675 | opm->mask |= htonl(OFPPC_PORT_DOWN); | |
676 | } else if (!strcasecmp(argv[3], "down")) { | |
677 | opm->mask |= htonl(OFPPC_PORT_DOWN); | |
678 | opm->config |= htonl(OFPPC_PORT_DOWN); | |
679 | } else if (!strcasecmp(argv[3], "flood")) { | |
680 | opm->mask |= htonl(OFPPC_NO_FLOOD); | |
681 | } else if (!strcasecmp(argv[3], "noflood")) { | |
682 | opm->mask |= htonl(OFPPC_NO_FLOOD); | |
683 | opm->config |= htonl(OFPPC_NO_FLOOD); | |
684 | } else { | |
685 | ovs_fatal(0, "unknown mod-port command '%s'", argv[3]); | |
686 | } | |
687 | ||
688 | open_vconn(argv[1], &vconn); | |
689 | dump_noreply_transaction(vconn, request); | |
690 | vconn_close(vconn); | |
691 | } | |
692 | ||
693 | static void | |
694 | do_ping(int argc, char *argv[]) | |
695 | { | |
696 | size_t max_payload = 65535 - sizeof(struct ofp_header); | |
697 | unsigned int payload; | |
698 | struct vconn *vconn; | |
699 | int i; | |
700 | ||
701 | payload = argc > 2 ? atoi(argv[2]) : 64; | |
702 | if (payload > max_payload) { | |
703 | ovs_fatal(0, "payload must be between 0 and %zu bytes", max_payload); | |
704 | } | |
705 | ||
706 | open_vconn(argv[1], &vconn); | |
707 | for (i = 0; i < 10; i++) { | |
708 | struct timeval start, end; | |
709 | struct ofpbuf *request, *reply; | |
710 | struct ofp_header *rq_hdr, *rpy_hdr; | |
711 | ||
712 | rq_hdr = make_openflow(sizeof(struct ofp_header) + payload, | |
713 | OFPT_ECHO_REQUEST, &request); | |
714 | random_bytes(rq_hdr + 1, payload); | |
715 | ||
716 | gettimeofday(&start, NULL); | |
717 | run(vconn_transact(vconn, ofpbuf_clone(request), &reply), "transact"); | |
718 | gettimeofday(&end, NULL); | |
719 | ||
720 | rpy_hdr = reply->data; | |
721 | if (reply->size != request->size | |
722 | || memcmp(rpy_hdr + 1, rq_hdr + 1, payload) | |
723 | || rpy_hdr->xid != rq_hdr->xid | |
724 | || rpy_hdr->type != OFPT_ECHO_REPLY) { | |
725 | printf("Reply does not match request. Request:\n"); | |
726 | ofp_print(stdout, request, request->size, 2); | |
727 | printf("Reply:\n"); | |
728 | ofp_print(stdout, reply, reply->size, 2); | |
729 | } | |
730 | printf("%zu bytes from %s: xid=%08"PRIx32" time=%.1f ms\n", | |
731 | reply->size - sizeof *rpy_hdr, argv[1], ntohl(rpy_hdr->xid), | |
732 | (1000*(double)(end.tv_sec - start.tv_sec)) | |
733 | + (.001*(end.tv_usec - start.tv_usec))); | |
734 | ofpbuf_delete(request); | |
735 | ofpbuf_delete(reply); | |
736 | } | |
737 | vconn_close(vconn); | |
738 | } | |
739 | ||
740 | static void | |
741 | do_benchmark(int argc OVS_UNUSED, char *argv[]) | |
742 | { | |
743 | size_t max_payload = 65535 - sizeof(struct ofp_header); | |
744 | struct timeval start, end; | |
745 | unsigned int payload_size, message_size; | |
746 | struct vconn *vconn; | |
747 | double duration; | |
748 | int count; | |
749 | int i; | |
750 | ||
751 | payload_size = atoi(argv[2]); | |
752 | if (payload_size > max_payload) { | |
753 | ovs_fatal(0, "payload must be between 0 and %zu bytes", max_payload); | |
754 | } | |
755 | message_size = sizeof(struct ofp_header) + payload_size; | |
756 | ||
757 | count = atoi(argv[3]); | |
758 | ||
759 | printf("Sending %d packets * %u bytes (with header) = %u bytes total\n", | |
760 | count, message_size, count * message_size); | |
761 | ||
762 | open_vconn(argv[1], &vconn); | |
763 | gettimeofday(&start, NULL); | |
764 | for (i = 0; i < count; i++) { | |
765 | struct ofpbuf *request, *reply; | |
766 | struct ofp_header *rq_hdr; | |
767 | ||
768 | rq_hdr = make_openflow(message_size, OFPT_ECHO_REQUEST, &request); | |
769 | memset(rq_hdr + 1, 0, payload_size); | |
770 | run(vconn_transact(vconn, request, &reply), "transact"); | |
771 | ofpbuf_delete(reply); | |
772 | } | |
773 | gettimeofday(&end, NULL); | |
774 | vconn_close(vconn); | |
775 | ||
776 | duration = ((1000*(double)(end.tv_sec - start.tv_sec)) | |
777 | + (.001*(end.tv_usec - start.tv_usec))); | |
778 | printf("Finished in %.1f ms (%.0f packets/s) (%.0f bytes/s)\n", | |
779 | duration, count / (duration / 1000.0), | |
780 | count * message_size / (duration / 1000.0)); | |
781 | } | |
782 | ||
783 | static void | |
784 | do_help(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) | |
785 | { | |
786 | usage(); | |
787 | } | |
788 | \f | |
789 | /* Undocumented commands for unit testing. */ | |
790 | ||
791 | static void | |
792 | do_parse_flows(int argc OVS_UNUSED, char *argv[]) | |
793 | { | |
794 | struct ofpbuf *b; | |
795 | FILE *file; | |
796 | ||
797 | file = fopen(argv[1], "r"); | |
798 | if (file == NULL) { | |
799 | ovs_fatal(errno, "%s: open", argv[2]); | |
800 | } | |
801 | ||
802 | while ((b = parse_ofp_add_flow_file(file)) != NULL) { | |
803 | ofp_print(stdout, b->data, b->size, 0); | |
804 | ofpbuf_delete(b); | |
805 | } | |
806 | fclose(file); | |
807 | } | |
808 | ||
809 | static void | |
810 | do_parse_nx_match(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) | |
811 | { | |
812 | struct ds in; | |
813 | ||
814 | ds_init(&in); | |
815 | while (!ds_get_line(&in, stdin)) { | |
816 | struct ofpbuf nx_match; | |
817 | struct cls_rule rule; | |
818 | int match_len; | |
819 | int error; | |
820 | char *s; | |
821 | ||
822 | /* Delete comments, skip blank lines. */ | |
823 | s = ds_cstr(&in); | |
824 | if (*s == '#') { | |
825 | puts(s); | |
826 | continue; | |
827 | } | |
828 | if (strchr(s, '#')) { | |
829 | *strchr(s, '#') = '\0'; | |
830 | } | |
831 | if (s[strspn(s, " ")] == '\0') { | |
832 | putchar('\n'); | |
833 | continue; | |
834 | } | |
835 | ||
836 | /* Convert string to nx_match. */ | |
837 | ofpbuf_init(&nx_match, 0); | |
838 | match_len = nx_match_from_string(ds_cstr(&in), &nx_match); | |
839 | ||
840 | /* Convert nx_match to cls_rule. */ | |
841 | error = nx_pull_match(&nx_match, match_len, 0, &rule); | |
842 | if (!error) { | |
843 | char *out; | |
844 | ||
845 | /* Convert cls_rule back to nx_match. */ | |
846 | ofpbuf_uninit(&nx_match); | |
847 | ofpbuf_init(&nx_match, 0); | |
848 | match_len = nx_put_match(&nx_match, &rule); | |
849 | ||
850 | /* Convert nx_match to string. */ | |
851 | out = nx_match_to_string(nx_match.data, match_len); | |
852 | puts(out); | |
853 | free(out); | |
854 | } else { | |
855 | printf("nx_pull_match() returned error %x\n", error); | |
856 | } | |
857 | ||
858 | ofpbuf_uninit(&nx_match); | |
859 | } | |
860 | ds_destroy(&in); | |
861 | } | |
862 | ||
863 | static const struct command all_commands[] = { | |
864 | { "show", 1, 1, do_show }, | |
865 | { "status", 1, 2, do_status }, | |
866 | { "monitor", 1, 2, do_monitor }, | |
867 | { "snoop", 1, 1, do_snoop }, | |
868 | { "dump-desc", 1, 1, do_dump_desc }, | |
869 | { "dump-tables", 1, 1, do_dump_tables }, | |
870 | { "dump-flows", 1, 2, do_dump_flows }, | |
871 | { "dump-aggregate", 1, 2, do_dump_aggregate }, | |
872 | { "queue-stats", 1, 3, do_queue_stats }, | |
873 | { "add-flow", 2, 2, do_add_flow }, | |
874 | { "add-flows", 2, 2, do_add_flows }, | |
875 | { "mod-flows", 2, 2, do_mod_flows }, | |
876 | { "del-flows", 1, 2, do_del_flows }, | |
877 | { "dump-ports", 1, 2, do_dump_ports }, | |
878 | { "mod-port", 3, 3, do_mod_port }, | |
879 | { "probe", 1, 1, do_probe }, | |
880 | { "ping", 1, 2, do_ping }, | |
881 | { "benchmark", 3, 3, do_benchmark }, | |
882 | { "help", 0, INT_MAX, do_help }, | |
883 | ||
884 | /* Undocumented commands for testing. */ | |
885 | { "parse-flows", 1, 1, do_parse_flows }, | |
886 | { "parse-nx-match", 0, 0, do_parse_nx_match }, | |
887 | ||
888 | { NULL, 0, 0, NULL }, | |
889 | }; |