+ fd = open(argv[1], O_CREAT | O_TRUNC | O_WRONLY, 0666);
+ if (fd < 0) {
+ unixctl_command_reply_error(conn, ovs_strerror(errno));
+ return;
+ }
+
+ fflush(stderr);
+ dup2(fd, STDERR_FILENO);
+ close(fd);
+ unixctl_command_reply(conn, NULL);
+}
+
+static void
+ofctl_block(struct unixctl_conn *conn, int argc OVS_UNUSED,
+ const char *argv[] OVS_UNUSED, void *blocked_)
+{
+ bool *blocked = blocked_;
+
+ if (!*blocked) {
+ *blocked = true;
+ unixctl_command_reply(conn, NULL);
+ } else {
+ unixctl_command_reply(conn, "already blocking");
+ }
+}
+
+static void
+ofctl_unblock(struct unixctl_conn *conn, int argc OVS_UNUSED,
+ const char *argv[] OVS_UNUSED, void *blocked_)
+{
+ bool *blocked = blocked_;
+
+ if (*blocked) {
+ *blocked = false;
+ unixctl_command_reply(conn, NULL);
+ } else {
+ unixctl_command_reply(conn, "already unblocked");
+ }
+}
+
+/* Prints to stdout all of the messages received on 'vconn'.
+ *
+ * Iff 'reply_to_echo_requests' is true, sends a reply to any echo request
+ * received on 'vconn'. */
+static void
+monitor_vconn(struct vconn *vconn, bool reply_to_echo_requests)
+{
+ struct barrier_aux barrier_aux = { vconn, NULL };
+ struct unixctl_server *server;
+ bool exiting = false;
+ bool blocked = false;
+ int error;
+
+ daemon_save_fd(STDERR_FILENO);
+ daemonize_start();
+ error = unixctl_server_create(NULL, &server);
+ if (error) {
+ ovs_fatal(error, "failed to create unixctl server");
+ }
+ unixctl_command_register("exit", "", 0, 0, ofctl_exit, &exiting);
+ unixctl_command_register("ofctl/send", "OFMSG...", 1, INT_MAX,
+ ofctl_send, vconn);
+ unixctl_command_register("ofctl/barrier", "", 0, 0,
+ ofctl_barrier, &barrier_aux);
+ unixctl_command_register("ofctl/set-output-file", "FILE", 1, 1,
+ ofctl_set_output_file, NULL);
+
+ unixctl_command_register("ofctl/block", "", 0, 0, ofctl_block, &blocked);
+ unixctl_command_register("ofctl/unblock", "", 0, 0, ofctl_unblock,
+ &blocked);
+
+ daemonize_complete();
+
+ for (;;) {
+ struct ofpbuf *b;
+ int retval;
+
+ unixctl_server_run(server);
+
+ while (!blocked) {
+ enum ofptype type;
+
+ retval = vconn_recv(vconn, &b);
+ if (retval == EAGAIN) {
+ break;
+ }
+ run(retval, "vconn_recv");
+
+ if (timestamp) {
+ char *s = xastrftime_msec("%Y-%m-%d %H:%M:%S.###: ",
+ time_wall_msec(), true);
+ fputs(s, stderr);
+ free(s);
+ }
+
+ ofptype_decode(&type, ofpbuf_data(b));
+ ofp_print(stderr, ofpbuf_data(b), ofpbuf_size(b), verbosity + 2);
+
+ switch ((int) type) {
+ case OFPTYPE_BARRIER_REPLY:
+ if (barrier_aux.conn) {
+ unixctl_command_reply(barrier_aux.conn, NULL);
+ barrier_aux.conn = NULL;
+ }
+ break;
+
+ case OFPTYPE_ECHO_REQUEST:
+ if (reply_to_echo_requests) {
+ struct ofpbuf *reply;
+
+ reply = make_echo_reply(ofpbuf_data(b));
+ retval = vconn_send_block(vconn, reply);
+ if (retval) {
+ ovs_fatal(retval, "failed to send echo reply");
+ }
+ }
+ break;
+ }
+ ofpbuf_delete(b);
+ }
+
+ if (exiting) {
+ break;
+ }
+
+ vconn_run(vconn);
+ vconn_run_wait(vconn);
+ if (!blocked) {
+ vconn_recv_wait(vconn);
+ }
+ unixctl_server_wait(server);
+ poll_block();
+ }
+ vconn_close(vconn);
+ unixctl_server_destroy(server);
+}
+
+static void
+ofctl_monitor(int argc, char *argv[])
+{
+ struct vconn *vconn;
+ int i;
+ enum ofputil_protocol usable_protocols;
+
+ open_vconn(argv[1], &vconn);
+ for (i = 2; i < argc; i++) {
+ const char *arg = argv[i];
+
+ if (isdigit((unsigned char) *arg)) {
+ struct ofp_switch_config config;
+
+ fetch_switch_config(vconn, &config);
+ config.miss_send_len = htons(atoi(arg));
+ set_switch_config(vconn, &config);
+ } else if (!strcmp(arg, "invalid_ttl")) {
+ monitor_set_invalid_ttl_to_controller(vconn);
+ } else if (!strncmp(arg, "watch:", 6)) {
+ struct ofputil_flow_monitor_request fmr;
+ struct ofpbuf *msg;
+ char *error;
+
+ error = parse_flow_monitor_request(&fmr, arg + 6,
+ &usable_protocols);
+ if (error) {
+ ovs_fatal(0, "%s", error);
+ }
+
+ msg = ofpbuf_new(0);
+ ofputil_append_flow_monitor_request(&fmr, msg);
+ dump_stats_transaction(vconn, msg);
+ } else {
+ ovs_fatal(0, "%s: unsupported \"monitor\" argument", arg);
+ }
+ }
+
+ if (preferred_packet_in_format >= 0) {
+ set_packet_in_format(vconn, preferred_packet_in_format);
+ } else {
+ enum ofp_version version = vconn_get_version(vconn);
+
+ switch (version) {
+ case OFP10_VERSION: {
+ struct ofpbuf *spif, *reply;
+
+ spif = ofputil_make_set_packet_in_format(vconn_get_version(vconn),
+ NXPIF_NXM);
+ run(vconn_transact_noreply(vconn, spif, &reply),
+ "talking to %s", vconn_get_name(vconn));
+ if (reply) {
+ char *s = ofp_to_string(ofpbuf_data(reply), ofpbuf_size(reply), 2);
+ VLOG_DBG("%s: failed to set packet in format to nxm, controller"
+ " replied: %s. Falling back to the switch default.",
+ vconn_get_name(vconn), s);
+ free(s);
+ ofpbuf_delete(reply);
+ }
+ break;
+ }
+ case OFP11_VERSION:
+ case OFP12_VERSION:
+ case OFP13_VERSION:
+ case OFP14_VERSION:
+ break;
+ default:
+ OVS_NOT_REACHED();
+ }
+ }
+
+ monitor_vconn(vconn, true);
+}
+
+static void
+ofctl_snoop(int argc OVS_UNUSED, char *argv[])
+{
+ struct vconn *vconn;
+
+ open_vconn__(argv[1], SNOOP, &vconn);
+ monitor_vconn(vconn, false);
+}
+
+static void
+ofctl_dump_ports(int argc, char *argv[])
+{
+ struct ofpbuf *request;
+ struct vconn *vconn;
+ ofp_port_t port;
+
+ open_vconn(argv[1], &vconn);
+ port = argc > 2 ? str_to_port_no(argv[1], argv[2]) : OFPP_ANY;
+ request = ofputil_encode_dump_ports_request(vconn_get_version(vconn), port);
+ dump_stats_transaction(vconn, request);
+ vconn_close(vconn);
+}
+
+static void
+ofctl_dump_ports_desc(int argc OVS_UNUSED, char *argv[])
+{
+ dump_trivial_stats_transaction(argv[1], OFPRAW_OFPST_PORT_DESC_REQUEST);
+}
+
+static void
+ofctl_probe(int argc OVS_UNUSED, char *argv[])
+{
+ struct ofpbuf *request;
+ struct vconn *vconn;
+ struct ofpbuf *reply;
+
+ open_vconn(argv[1], &vconn);
+ request = make_echo_request(vconn_get_version(vconn));
+ run(vconn_transact(vconn, request, &reply), "talking to %s", argv[1]);
+ if (ofpbuf_size(reply) != sizeof(struct ofp_header)) {
+ ovs_fatal(0, "reply does not match request");
+ }
+ ofpbuf_delete(reply);
+ vconn_close(vconn);
+}
+
+static void
+ofctl_packet_out(int argc, char *argv[])
+{
+ enum ofputil_protocol protocol;
+ struct ofputil_packet_out po;
+ struct ofpbuf ofpacts;
+ struct vconn *vconn;
+ char *error;
+ int i;
+ enum ofputil_protocol usable_protocols; /* XXX: Use in proto selection */
+
+ ofpbuf_init(&ofpacts, 64);
+ error = parse_ofpacts(argv[3], &ofpacts, &usable_protocols);
+ if (error) {
+ ovs_fatal(0, "%s", error);
+ }
+
+ po.buffer_id = UINT32_MAX;
+ po.in_port = str_to_port_no(argv[1], argv[2]);
+ po.ofpacts = ofpbuf_data(&ofpacts);
+ po.ofpacts_len = ofpbuf_size(&ofpacts);
+
+ protocol = open_vconn(argv[1], &vconn);
+ for (i = 4; i < argc; i++) {
+ struct ofpbuf *packet, *opo;
+ const char *error_msg;
+
+ error_msg = eth_from_hex(argv[i], &packet);
+ if (error_msg) {
+ ovs_fatal(0, "%s", error_msg);
+ }
+
+ po.packet = ofpbuf_data(packet);
+ po.packet_len = ofpbuf_size(packet);
+ opo = ofputil_encode_packet_out(&po, protocol);
+ transact_noreply(vconn, opo);
+ ofpbuf_delete(packet);
+ }
+ vconn_close(vconn);
+ ofpbuf_uninit(&ofpacts);
+}
+
+static void
+ofctl_mod_port(int argc OVS_UNUSED, char *argv[])
+{
+ struct ofp_config_flag {
+ const char *name; /* The flag's name. */
+ enum ofputil_port_config bit; /* Bit to turn on or off. */
+ bool on; /* Value to set the bit to. */
+ };
+ static const struct ofp_config_flag flags[] = {
+ { "up", OFPUTIL_PC_PORT_DOWN, false },
+ { "down", OFPUTIL_PC_PORT_DOWN, true },
+ { "stp", OFPUTIL_PC_NO_STP, false },
+ { "receive", OFPUTIL_PC_NO_RECV, false },
+ { "receive-stp", OFPUTIL_PC_NO_RECV_STP, false },
+ { "flood", OFPUTIL_PC_NO_FLOOD, false },
+ { "forward", OFPUTIL_PC_NO_FWD, false },
+ { "packet-in", OFPUTIL_PC_NO_PACKET_IN, false },
+ };
+
+ const struct ofp_config_flag *flag;
+ enum ofputil_protocol protocol;
+ struct ofputil_port_mod pm;
+ struct ofputil_phy_port pp;
+ struct vconn *vconn;
+ const char *command;
+ bool not;
+
+ fetch_ofputil_phy_port(argv[1], argv[2], &pp);
+
+ pm.port_no = pp.port_no;
+ memcpy(pm.hw_addr, pp.hw_addr, ETH_ADDR_LEN);
+ pm.config = 0;
+ pm.mask = 0;
+ pm.advertise = 0;
+
+ if (!strncasecmp(argv[3], "no-", 3)) {
+ command = argv[3] + 3;
+ not = true;
+ } else if (!strncasecmp(argv[3], "no", 2)) {
+ command = argv[3] + 2;
+ not = true;
+ } else {
+ command = argv[3];
+ not = false;
+ }
+ for (flag = flags; flag < &flags[ARRAY_SIZE(flags)]; flag++) {
+ if (!strcasecmp(command, flag->name)) {
+ pm.mask = flag->bit;
+ pm.config = flag->on ^ not ? flag->bit : 0;
+ goto found;
+ }
+ }
+ ovs_fatal(0, "unknown mod-port command '%s'", argv[3]);
+
+found:
+ protocol = open_vconn(argv[1], &vconn);
+ transact_noreply(vconn, ofputil_encode_port_mod(&pm, protocol));
+ vconn_close(vconn);
+}
+
+static void
+ofctl_mod_table(int argc OVS_UNUSED, char *argv[])
+{
+ enum ofputil_protocol protocol, usable_protocols;
+ struct ofputil_table_mod tm;
+ struct vconn *vconn;
+ char *error;
+ int i;
+
+ error = parse_ofp_table_mod(&tm, argv[2], argv[3], &usable_protocols);
+ if (error) {
+ ovs_fatal(0, "%s", error);
+ }
+
+ protocol = open_vconn(argv[1], &vconn);
+ if (!(protocol & usable_protocols)) {
+ for (i = 0; i < sizeof(enum ofputil_protocol) * CHAR_BIT; i++) {
+ enum ofputil_protocol f = 1 << i;
+ if (f != protocol
+ && f & usable_protocols
+ && try_set_protocol(vconn, f, &protocol)) {
+ protocol = f;
+ break;
+ }
+ }
+ }
+
+ if (!(protocol & usable_protocols)) {
+ char *usable_s = ofputil_protocols_to_string(usable_protocols);
+ ovs_fatal(0, "Switch does not support table mod message(%s)", usable_s);
+ }
+
+ transact_noreply(vconn, ofputil_encode_table_mod(&tm, protocol));
+ vconn_close(vconn);
+}
+
+static void
+ofctl_get_frags(int argc OVS_UNUSED, char *argv[])
+{
+ struct ofp_switch_config config;
+ struct vconn *vconn;
+
+ open_vconn(argv[1], &vconn);
+ fetch_switch_config(vconn, &config);
+ puts(ofputil_frag_handling_to_string(ntohs(config.flags)));
+ vconn_close(vconn);
+}
+
+static void
+ofctl_set_frags(int argc OVS_UNUSED, char *argv[])
+{
+ struct ofp_switch_config config;
+ enum ofp_config_flags mode;
+ struct vconn *vconn;
+ ovs_be16 flags;
+
+ if (!ofputil_frag_handling_from_string(argv[2], &mode)) {
+ ovs_fatal(0, "%s: unknown fragment handling mode", argv[2]);
+ }
+
+ open_vconn(argv[1], &vconn);
+ fetch_switch_config(vconn, &config);
+ flags = htons(mode) | (config.flags & htons(~OFPC_FRAG_MASK));
+ if (flags != config.flags) {
+ /* Set the configuration. */
+ config.flags = flags;
+ set_switch_config(vconn, &config);
+
+ /* Then retrieve the configuration to see if it really took. OpenFlow
+ * doesn't define error reporting for bad modes, so this is all we can
+ * do. */
+ fetch_switch_config(vconn, &config);
+ if (flags != config.flags) {
+ ovs_fatal(0, "%s: setting fragment handling mode failed (this "
+ "switch probably doesn't support mode \"%s\")",
+ argv[1], ofputil_frag_handling_to_string(mode));
+ }
+ }
+ vconn_close(vconn);
+}
+
+static void
+ofctl_ofp_parse(int argc OVS_UNUSED, char *argv[])
+{
+ const char *filename = argv[1];
+ struct ofpbuf b;
+ FILE *file;
+
+ file = !strcmp(filename, "-") ? stdin : fopen(filename, "r");
+ if (file == NULL) {
+ ovs_fatal(errno, "%s: open", filename);
+ }
+
+ ofpbuf_init(&b, 65536);
+ for (;;) {
+ struct ofp_header *oh;
+ size_t length, tail_len;
+ void *tail;
+ size_t n;
+
+ ofpbuf_clear(&b);
+ oh = ofpbuf_put_uninit(&b, sizeof *oh);
+ n = fread(oh, 1, sizeof *oh, file);
+ if (n == 0) {
+ break;
+ } else if (n < sizeof *oh) {
+ ovs_fatal(0, "%s: unexpected end of file mid-message", filename);
+ }
+
+ length = ntohs(oh->length);
+ if (length < sizeof *oh) {
+ ovs_fatal(0, "%s: %"PRIuSIZE"-byte message is too short for OpenFlow",
+ filename, length);
+ }
+
+ tail_len = length - sizeof *oh;
+ tail = ofpbuf_put_uninit(&b, tail_len);
+ n = fread(tail, 1, tail_len, file);
+ if (n < tail_len) {
+ ovs_fatal(0, "%s: unexpected end of file mid-message", filename);
+ }
+
+ ofp_print(stdout, ofpbuf_data(&b), ofpbuf_size(&b), verbosity + 2);
+ }
+ ofpbuf_uninit(&b);
+
+ if (file != stdin) {
+ fclose(file);
+ }
+}
+
+static bool
+is_openflow_port(ovs_be16 port_, char *ports[])
+{
+ uint16_t port = ntohs(port_);
+ if (ports[0]) {
+ int i;
+
+ for (i = 0; ports[i]; i++) {
+ if (port == atoi(ports[i])) {
+ return true;
+ }
+ }
+ return false;
+ } else {
+ return port == OFP_PORT || port == OFP_OLD_PORT;
+ }
+}
+
+static void
+ofctl_ofp_parse_pcap(int argc OVS_UNUSED, char *argv[])
+{
+ struct tcp_reader *reader;
+ FILE *file;
+ int error;
+ bool first;
+
+ file = ovs_pcap_open(argv[1], "rb");
+ if (!file) {
+ ovs_fatal(errno, "%s: open failed", argv[1]);
+ }
+
+ reader = tcp_reader_open();
+ first = true;
+ for (;;) {
+ struct ofpbuf *packet;
+ long long int when;
+ struct flow flow;
+ const struct pkt_metadata md = PKT_METADATA_INITIALIZER(ODPP_NONE);
+
+ error = ovs_pcap_read(file, &packet, &when);
+ if (error) {
+ break;
+ }
+ flow_extract(packet, &md, &flow);
+ if (flow.dl_type == htons(ETH_TYPE_IP)
+ && flow.nw_proto == IPPROTO_TCP
+ && (is_openflow_port(flow.tp_src, argv + 2) ||
+ is_openflow_port(flow.tp_dst, argv + 2))) {
+ struct ofpbuf *payload = tcp_reader_run(reader, &flow, packet);
+ if (payload) {
+ while (ofpbuf_size(payload) >= sizeof(struct ofp_header)) {
+ const struct ofp_header *oh;
+ void *data = ofpbuf_data(payload);
+ int length;
+
+ /* Align OpenFlow on 8-byte boundary for safe access. */
+ ofpbuf_shift(payload, -((intptr_t) data & 7));
+
+ oh = ofpbuf_data(payload);
+ length = ntohs(oh->length);
+ if (ofpbuf_size(payload) < length) {
+ break;
+ }
+
+ if (!first) {
+ putchar('\n');
+ }
+ first = false;
+
+ if (timestamp) {
+ char *s = xastrftime_msec("%H:%M:%S.### ", when, true);
+ fputs(s, stdout);
+ free(s);
+ }
+
+ printf(IP_FMT".%"PRIu16" > "IP_FMT".%"PRIu16":\n",
+ IP_ARGS(flow.nw_src), ntohs(flow.tp_src),
+ IP_ARGS(flow.nw_dst), ntohs(flow.tp_dst));
+ ofp_print(stdout, ofpbuf_data(payload), length, verbosity + 1);
+ ofpbuf_pull(payload, length);
+ }
+ }
+ }
+ ofpbuf_delete(packet);
+ }
+ tcp_reader_close(reader);
+}
+
+static void
+ofctl_ping(int argc, char *argv[])
+{
+ size_t max_payload = 65535 - sizeof(struct ofp_header);
+ unsigned int payload;
+ struct vconn *vconn;
+ int i;
+
+ payload = argc > 2 ? atoi(argv[2]) : 64;
+ if (payload > max_payload) {
+ ovs_fatal(0, "payload must be between 0 and %"PRIuSIZE" bytes", max_payload);
+ }
+
+ open_vconn(argv[1], &vconn);
+ for (i = 0; i < 10; i++) {
+ struct timeval start, end;
+ struct ofpbuf *request, *reply;
+ const struct ofp_header *rpy_hdr;
+ enum ofptype type;
+
+ request = ofpraw_alloc(OFPRAW_OFPT_ECHO_REQUEST,
+ vconn_get_version(vconn), payload);
+ random_bytes(ofpbuf_put_uninit(request, payload), payload);
+
+ xgettimeofday(&start);
+ run(vconn_transact(vconn, ofpbuf_clone(request), &reply), "transact");
+ xgettimeofday(&end);
+
+ rpy_hdr = ofpbuf_data(reply);
+ if (ofptype_pull(&type, reply)
+ || type != OFPTYPE_ECHO_REPLY
+ || ofpbuf_size(reply) != payload
+ || memcmp(ofpbuf_l3(request), ofpbuf_l3(reply), payload)) {
+ printf("Reply does not match request. Request:\n");
+ ofp_print(stdout, request, ofpbuf_size(request), verbosity + 2);
+ printf("Reply:\n");
+ ofp_print(stdout, reply, ofpbuf_size(reply), verbosity + 2);
+ }
+ printf("%"PRIu32" bytes from %s: xid=%08"PRIx32" time=%.1f ms\n",
+ ofpbuf_size(reply), argv[1], ntohl(rpy_hdr->xid),
+ (1000*(double)(end.tv_sec - start.tv_sec))
+ + (.001*(end.tv_usec - start.tv_usec)));
+ ofpbuf_delete(request);
+ ofpbuf_delete(reply);
+ }
+ vconn_close(vconn);
+}
+
+static void
+ofctl_benchmark(int argc OVS_UNUSED, char *argv[])
+{
+ size_t max_payload = 65535 - sizeof(struct ofp_header);
+ struct timeval start, end;
+ unsigned int payload_size, message_size;
+ struct vconn *vconn;
+ double duration;
+ int count;
+ int i;
+
+ payload_size = atoi(argv[2]);
+ if (payload_size > max_payload) {
+ ovs_fatal(0, "payload must be between 0 and %"PRIuSIZE" bytes", max_payload);
+ }
+ message_size = sizeof(struct ofp_header) + payload_size;
+
+ count = atoi(argv[3]);
+
+ printf("Sending %d packets * %u bytes (with header) = %u bytes total\n",
+ count, message_size, count * message_size);
+
+ open_vconn(argv[1], &vconn);
+ xgettimeofday(&start);
+ for (i = 0; i < count; i++) {
+ struct ofpbuf *request, *reply;
+
+ request = ofpraw_alloc(OFPRAW_OFPT_ECHO_REQUEST,
+ vconn_get_version(vconn), payload_size);
+ ofpbuf_put_zeros(request, payload_size);
+ run(vconn_transact(vconn, request, &reply), "transact");
+ ofpbuf_delete(reply);
+ }
+ xgettimeofday(&end);
+ vconn_close(vconn);
+
+ duration = ((1000*(double)(end.tv_sec - start.tv_sec))
+ + (.001*(end.tv_usec - start.tv_usec)));
+ printf("Finished in %.1f ms (%.0f packets/s) (%.0f bytes/s)\n",
+ duration, count / (duration / 1000.0),
+ count * message_size / (duration / 1000.0));
+}
+
+static void
+ofctl_group_mod__(const char *remote, struct ofputil_group_mod *gms,
+ size_t n_gms)
+{
+ struct ofputil_group_mod *gm;
+ struct ofpbuf *request;
+
+ struct vconn *vconn;
+ size_t i;
+
+ open_vconn(remote, &vconn);
+
+ for (i = 0; i < n_gms; i++) {
+ gm = &gms[i];
+ request = ofputil_encode_group_mod(vconn_get_version(vconn), gm);
+ if (request) {
+ transact_noreply(vconn, request);
+ }
+ }
+
+ vconn_close(vconn);
+
+}
+
+
+static void
+ofctl_group_mod_file(int argc OVS_UNUSED, char *argv[], uint16_t command)
+{
+ struct ofputil_group_mod *gms = NULL;
+ enum ofputil_protocol usable_protocols;
+ size_t n_gms = 0;
+ char *error;
+
+ error = parse_ofp_group_mod_file(argv[2], command, &gms, &n_gms,
+ &usable_protocols);
+ if (error) {
+ ovs_fatal(0, "%s", error);
+ }
+ ofctl_group_mod__(argv[1], gms, n_gms);
+ free(gms);
+}
+
+static void
+ofctl_group_mod(int argc, char *argv[], uint16_t command)
+{
+ if (argc > 2 && !strcmp(argv[2], "-")) {
+ ofctl_group_mod_file(argc, argv, command);
+ } else {
+ enum ofputil_protocol usable_protocols;
+ struct ofputil_group_mod gm;
+ char *error;
+
+ error = parse_ofp_group_mod_str(&gm, command, argc > 2 ? argv[2] : "",
+ &usable_protocols);
+ if (error) {
+ ovs_fatal(0, "%s", error);
+ }
+ ofctl_group_mod__(argv[1], &gm, 1);
+ }
+}
+
+static void
+ofctl_add_group(int argc, char *argv[])
+{
+ ofctl_group_mod(argc, argv, OFPGC11_ADD);
+}
+
+static void
+ofctl_add_groups(int argc, char *argv[])
+{
+ ofctl_group_mod_file(argc, argv, OFPGC11_ADD);
+}
+
+static void
+ofctl_mod_group(int argc, char *argv[])
+{
+ ofctl_group_mod(argc, argv, OFPGC11_MODIFY);
+}
+
+static void
+ofctl_del_groups(int argc, char *argv[])
+{
+ ofctl_group_mod(argc, argv, OFPGC11_DELETE);
+}
+
+static void
+ofctl_dump_group_stats(int argc, char *argv[])
+{
+ enum ofputil_protocol usable_protocols;
+ struct ofputil_group_mod gm;
+ struct ofpbuf *request;
+ struct vconn *vconn;
+ uint32_t group_id;
+ char *error;
+
+ memset(&gm, 0, sizeof gm);
+
+ error = parse_ofp_group_mod_str(&gm, OFPGC11_DELETE,
+ argc > 2 ? argv[2] : "",
+ &usable_protocols);
+ if (error) {
+ ovs_fatal(0, "%s", error);
+ }
+
+ group_id = gm.group_id;
+
+ open_vconn(argv[1], &vconn);
+ request = ofputil_encode_group_stats_request(vconn_get_version(vconn),
+ group_id);
+ if (request) {
+ dump_stats_transaction(vconn, request);
+ }
+
+ vconn_close(vconn);
+}
+
+static void
+ofctl_dump_group_desc(int argc OVS_UNUSED, char *argv[])
+{
+ struct ofpbuf *request;
+ struct vconn *vconn;
+
+ open_vconn(argv[1], &vconn);
+
+ request = ofputil_encode_group_desc_request(vconn_get_version(vconn));
+ if (request) {
+ dump_stats_transaction(vconn, request);
+ }
+
+ vconn_close(vconn);
+}
+
+static void
+ofctl_dump_group_features(int argc OVS_UNUSED, char *argv[])
+{
+ struct ofpbuf *request;
+ struct vconn *vconn;
+
+ open_vconn(argv[1], &vconn);
+ request = ofputil_encode_group_features_request(vconn_get_version(vconn));
+ if (request) {
+ dump_stats_transaction(vconn, request);
+ }
+
+ vconn_close(vconn);
+}
+
+static void
+ofctl_help(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+ usage();
+}
+\f
+/* replace-flows and diff-flows commands. */
+
+/* A flow table entry, possibly with two different versions. */
+struct fte {
+ struct cls_rule rule; /* Within a "struct classifier". */
+ struct fte_version *versions[2];
+};
+
+/* One version of a Flow Table Entry. */
+struct fte_version {
+ ovs_be64 cookie;
+ uint16_t idle_timeout;
+ uint16_t hard_timeout;
+ uint16_t flags;
+ struct ofpact *ofpacts;
+ size_t ofpacts_len;
+};
+
+/* Frees 'version' and the data that it owns. */
+static void
+fte_version_free(struct fte_version *version)
+{
+ if (version) {
+ free(CONST_CAST(struct ofpact *, version->ofpacts));
+ free(version);
+ }
+}
+
+/* Returns true if 'a' and 'b' are the same, false if they differ.
+ *
+ * Ignores differences in 'flags' because there's no way to retrieve flags from
+ * an OpenFlow switch. We have to assume that they are the same. */
+static bool
+fte_version_equals(const struct fte_version *a, const struct fte_version *b)
+{
+ return (a->cookie == b->cookie
+ && a->idle_timeout == b->idle_timeout
+ && a->hard_timeout == b->hard_timeout
+ && ofpacts_equal(a->ofpacts, a->ofpacts_len,
+ b->ofpacts, b->ofpacts_len));
+}
+
+/* Clears 's', then if 's' has a version 'index', formats 'fte' and version
+ * 'index' into 's', followed by a new-line. */
+static void
+fte_version_format(const struct fte *fte, int index, struct ds *s)
+{
+ const struct fte_version *version = fte->versions[index];
+
+ ds_clear(s);
+ if (!version) {
+ return;
+ }
+
+ cls_rule_format(&fte->rule, s);
+ if (version->cookie != htonll(0)) {
+ ds_put_format(s, " cookie=0x%"PRIx64, ntohll(version->cookie));
+ }
+ if (version->idle_timeout != OFP_FLOW_PERMANENT) {
+ ds_put_format(s, " idle_timeout=%"PRIu16, version->idle_timeout);
+ }
+ if (version->hard_timeout != OFP_FLOW_PERMANENT) {
+ ds_put_format(s, " hard_timeout=%"PRIu16, version->hard_timeout);
+ }
+
+ ds_put_cstr(s, " actions=");
+ ofpacts_format(version->ofpacts, version->ofpacts_len, s);
+
+ ds_put_char(s, '\n');
+}
+
+static struct fte *
+fte_from_cls_rule(const struct cls_rule *cls_rule)
+{
+ return cls_rule ? CONTAINER_OF(cls_rule, struct fte, rule) : NULL;
+}
+
+/* Frees 'fte' and its versions. */
+static void
+fte_free(struct fte *fte)
+{
+ if (fte) {
+ fte_version_free(fte->versions[0]);
+ fte_version_free(fte->versions[1]);
+ cls_rule_destroy(&fte->rule);
+ free(fte);
+ }
+}
+
+/* Frees all of the FTEs within 'cls'. */
+static void
+fte_free_all(struct classifier *cls)
+{
+ struct cls_cursor cursor;
+ struct fte *fte, *next;
+
+ fat_rwlock_wrlock(&cls->rwlock);
+ cls_cursor_init(&cursor, cls, NULL);
+ CLS_CURSOR_FOR_EACH_SAFE (fte, next, rule, &cursor) {
+ classifier_remove(cls, &fte->rule);
+ fte_free(fte);
+ }
+ fat_rwlock_unlock(&cls->rwlock);
+ classifier_destroy(cls);
+}
+
+/* Searches 'cls' for an FTE matching 'rule', inserting a new one if
+ * necessary. Sets 'version' as the version of that rule with the given
+ * 'index', replacing any existing version, if any.
+ *
+ * Takes ownership of 'version'. */
+static void
+fte_insert(struct classifier *cls, const struct match *match,
+ unsigned int priority, struct fte_version *version, int index)
+{
+ struct fte *old, *fte;
+
+ fte = xzalloc(sizeof *fte);
+ cls_rule_init(&fte->rule, match, priority);
+ fte->versions[index] = version;
+
+ fat_rwlock_wrlock(&cls->rwlock);
+ old = fte_from_cls_rule(classifier_replace(cls, &fte->rule));
+ fat_rwlock_unlock(&cls->rwlock);
+ if (old) {
+ fte_version_free(old->versions[index]);
+ fte->versions[!index] = old->versions[!index];
+ cls_rule_destroy(&old->rule);
+ free(old);
+ }
+}
+
+/* Reads the flows in 'filename' as flow table entries in 'cls' for the version
+ * with the specified 'index'. Returns the flow formats able to represent the
+ * flows that were read. */
+static enum ofputil_protocol
+read_flows_from_file(const char *filename, struct classifier *cls, int index)
+{
+ enum ofputil_protocol usable_protocols;
+ int line_number;
+ struct ds s;
+ FILE *file;
+
+ file = !strcmp(filename, "-") ? stdin : fopen(filename, "r");
+ if (file == NULL) {
+ ovs_fatal(errno, "%s: open", filename);
+ }
+
+ ds_init(&s);
+ usable_protocols = OFPUTIL_P_ANY;
+ line_number = 0;
+ while (!ds_get_preprocessed_line(&s, file, &line_number)) {
+ struct fte_version *version;
+ struct ofputil_flow_mod fm;
+ char *error;
+ enum ofputil_protocol usable;
+
+ error = parse_ofp_str(&fm, OFPFC_ADD, ds_cstr(&s), &usable);
+ if (error) {
+ ovs_fatal(0, "%s:%d: %s", filename, line_number, error);
+ }
+ usable_protocols &= usable;
+
+ version = xmalloc(sizeof *version);
+ version->cookie = fm.new_cookie;
+ version->idle_timeout = fm.idle_timeout;
+ version->hard_timeout = fm.hard_timeout;
+ version->flags = fm.flags & (OFPUTIL_FF_SEND_FLOW_REM
+ | OFPUTIL_FF_EMERG);
+ version->ofpacts = fm.ofpacts;
+ version->ofpacts_len = fm.ofpacts_len;
+
+ fte_insert(cls, &fm.match, fm.priority, version, index);
+ }
+ ds_destroy(&s);
+
+ if (file != stdin) {
+ fclose(file);
+ }
+
+ return usable_protocols;
+}
+
+static bool
+recv_flow_stats_reply(struct vconn *vconn, ovs_be32 send_xid,
+ struct ofpbuf **replyp,
+ struct ofputil_flow_stats *fs, struct ofpbuf *ofpacts)
+{
+ struct ofpbuf *reply = *replyp;
+
+ for (;;) {
+ int retval;
+ bool more;
+
+ /* Get a flow stats reply message, if we don't already have one. */
+ if (!reply) {
+ enum ofptype type;
+ enum ofperr error;
+
+ do {
+ run(vconn_recv_block(vconn, &reply),
+ "OpenFlow packet receive failed");
+ } while (((struct ofp_header *) ofpbuf_data(reply))->xid != send_xid);
+
+ error = ofptype_decode(&type, ofpbuf_data(reply));
+ if (error || type != OFPTYPE_FLOW_STATS_REPLY) {
+ ovs_fatal(0, "received bad reply: %s",
+ ofp_to_string(ofpbuf_data(reply), ofpbuf_size(reply),
+ verbosity + 1));
+ }
+ }
+
+ /* Pull an individual flow stats reply out of the message. */
+ retval = ofputil_decode_flow_stats_reply(fs, reply, false, ofpacts);
+ switch (retval) {
+ case 0:
+ *replyp = reply;
+ return true;
+
+ case EOF:
+ more = ofpmp_more(reply->frame);
+ ofpbuf_delete(reply);
+ reply = NULL;
+ if (!more) {
+ *replyp = NULL;
+ return false;
+ }
+ break;
+
+ default:
+ ovs_fatal(0, "parse error in reply (%s)",
+ ofperr_to_string(retval));
+ }
+ }
+}
+
+/* Reads the OpenFlow flow table from 'vconn', which has currently active flow
+ * format 'protocol', and adds them as flow table entries in 'cls' for the
+ * version with the specified 'index'. */
+static void
+read_flows_from_switch(struct vconn *vconn,
+ enum ofputil_protocol protocol,
+ struct classifier *cls, int index)
+{
+ struct ofputil_flow_stats_request fsr;
+ struct ofputil_flow_stats fs;
+ struct ofpbuf *request;
+ struct ofpbuf ofpacts;
+ struct ofpbuf *reply;
+ ovs_be32 send_xid;
+
+ fsr.aggregate = false;
+ match_init_catchall(&fsr.match);
+ fsr.out_port = OFPP_ANY;
+ fsr.table_id = 0xff;
+ fsr.cookie = fsr.cookie_mask = htonll(0);
+ request = ofputil_encode_flow_stats_request(&fsr, protocol);
+ send_xid = ((struct ofp_header *) ofpbuf_data(request))->xid;
+ send_openflow_buffer(vconn, request);
+
+ reply = NULL;
+ ofpbuf_init(&ofpacts, 0);
+ while (recv_flow_stats_reply(vconn, send_xid, &reply, &fs, &ofpacts)) {
+ struct fte_version *version;
+
+ version = xmalloc(sizeof *version);
+ version->cookie = fs.cookie;
+ version->idle_timeout = fs.idle_timeout;
+ version->hard_timeout = fs.hard_timeout;
+ version->flags = 0;
+ version->ofpacts_len = fs.ofpacts_len;
+ version->ofpacts = xmemdup(fs.ofpacts, fs.ofpacts_len);
+
+ fte_insert(cls, &fs.match, fs.priority, version, index);
+ }
+ ofpbuf_uninit(&ofpacts);
+}
+
+static void
+fte_make_flow_mod(const struct fte *fte, int index, uint16_t command,
+ enum ofputil_protocol protocol, struct list *packets)
+{
+ const struct fte_version *version = fte->versions[index];
+ struct ofputil_flow_mod fm;
+ struct ofpbuf *ofm;
+
+ minimatch_expand(&fte->rule.match, &fm.match);
+ fm.priority = fte->rule.priority;
+ fm.cookie = htonll(0);
+ fm.cookie_mask = htonll(0);
+ fm.new_cookie = version->cookie;
+ fm.modify_cookie = true;
+ fm.table_id = 0xff;
+ fm.command = command;
+ fm.idle_timeout = version->idle_timeout;
+ fm.hard_timeout = version->hard_timeout;
+ fm.buffer_id = UINT32_MAX;
+ fm.out_port = OFPP_ANY;
+ fm.flags = version->flags;