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