+ 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 *) reply->data)->xid != send_xid);
+
+ error = ofptype_decode(&type, reply->data);
+ if (error || type != OFPTYPE_FLOW_STATS_REPLY) {
+ ovs_fatal(0, "received bad reply: %s",
+ ofp_to_string(reply->data, reply->size,
+ 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->l2);
+ 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;
+ cls_rule_init_catchall(&fsr.match, 0);
+ fsr.out_port = OFPP_NONE;
+ fsr.table_id = 0xff;
+ fsr.cookie = fsr.cookie_mask = htonll(0);
+ request = ofputil_encode_flow_stats_request(&fsr, protocol);
+ send_xid = ((struct ofp_header *) request->data)->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.rule, 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;
+
+ fm.cr = fte->rule;
+ fm.cookie = htonll(0);
+ fm.cookie_mask = htonll(0);
+ fm.new_cookie = version->cookie;
+ 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_NONE;
+ fm.flags = version->flags;
+ if (command == OFPFC_ADD || command == OFPFC_MODIFY ||
+ command == OFPFC_MODIFY_STRICT) {
+ fm.ofpacts = version->ofpacts;
+ fm.ofpacts_len = version->ofpacts_len;
+ } else {
+ fm.ofpacts = NULL;
+ fm.ofpacts_len = 0;
+ }
+
+ ofm = ofputil_encode_flow_mod(&fm, protocol);
+ list_push_back(packets, &ofm->list_node);
+}
+
+static void
+ofctl_replace_flows(int argc OVS_UNUSED, char *argv[])
+{
+ enum { FILE_IDX = 0, SWITCH_IDX = 1 };
+ enum ofputil_protocol usable_protocols, protocol;
+ struct cls_cursor cursor;
+ struct classifier cls;
+ struct list requests;
+ struct vconn *vconn;
+ struct fte *fte;
+
+ classifier_init(&cls);
+ usable_protocols = read_flows_from_file(argv[2], &cls, FILE_IDX);
+
+ protocol = open_vconn(argv[1], &vconn);
+ protocol = set_protocol_for_flow_dump(vconn, protocol, usable_protocols);
+
+ read_flows_from_switch(vconn, protocol, &cls, SWITCH_IDX);
+
+ list_init(&requests);
+
+ /* Delete flows that exist on the switch but not in the file. */
+ cls_cursor_init(&cursor, &cls, NULL);
+ CLS_CURSOR_FOR_EACH (fte, rule, &cursor) {
+ struct fte_version *file_ver = fte->versions[FILE_IDX];
+ struct fte_version *sw_ver = fte->versions[SWITCH_IDX];
+
+ if (sw_ver && !file_ver) {
+ fte_make_flow_mod(fte, SWITCH_IDX, OFPFC_DELETE_STRICT,
+ protocol, &requests);
+ }
+ }
+
+ /* Add flows that exist in the file but not on the switch.
+ * Update flows that exist in both places but differ. */
+ cls_cursor_init(&cursor, &cls, NULL);
+ CLS_CURSOR_FOR_EACH (fte, rule, &cursor) {
+ struct fte_version *file_ver = fte->versions[FILE_IDX];
+ struct fte_version *sw_ver = fte->versions[SWITCH_IDX];
+
+ if (file_ver
+ && (readd || !sw_ver || !fte_version_equals(sw_ver, file_ver))) {
+ fte_make_flow_mod(fte, FILE_IDX, OFPFC_ADD, protocol, &requests);
+ }
+ }
+ transact_multiple_noreply(vconn, &requests);
+ vconn_close(vconn);
+
+ fte_free_all(&cls);
+}
+
+static void
+read_flows_from_source(const char *source, struct classifier *cls, int index)
+{
+ struct stat s;
+
+ if (source[0] == '/' || source[0] == '.'
+ || (!strchr(source, ':') && !stat(source, &s))) {
+ read_flows_from_file(source, cls, index);
+ } else {
+ enum ofputil_protocol protocol;
+ struct vconn *vconn;
+
+ protocol = open_vconn(source, &vconn);
+ protocol = set_protocol_for_flow_dump(vconn, protocol, OFPUTIL_P_ANY);
+ read_flows_from_switch(vconn, protocol, cls, index);
+ vconn_close(vconn);
+ }
+}
+
+static void
+ofctl_diff_flows(int argc OVS_UNUSED, char *argv[])
+{
+ bool differences = false;
+ struct cls_cursor cursor;
+ struct classifier cls;
+ struct ds a_s, b_s;
+ struct fte *fte;
+
+ classifier_init(&cls);
+ read_flows_from_source(argv[1], &cls, 0);
+ read_flows_from_source(argv[2], &cls, 1);
+
+ ds_init(&a_s);
+ ds_init(&b_s);
+
+ cls_cursor_init(&cursor, &cls, NULL);
+ CLS_CURSOR_FOR_EACH (fte, rule, &cursor) {
+ struct fte_version *a = fte->versions[0];
+ struct fte_version *b = fte->versions[1];
+
+ if (!a || !b || !fte_version_equals(a, b)) {
+ fte_version_format(fte, 0, &a_s);
+ fte_version_format(fte, 1, &b_s);
+ if (strcmp(ds_cstr(&a_s), ds_cstr(&b_s))) {
+ if (a_s.length) {
+ printf("-%s", ds_cstr(&a_s));
+ }
+ if (b_s.length) {
+ printf("+%s", ds_cstr(&b_s));
+ }
+ differences = true;
+ }
+ }
+ }
+
+ ds_destroy(&a_s);
+ ds_destroy(&b_s);
+
+ fte_free_all(&cls);
+
+ if (differences) {
+ exit(2);
+ }
+}
+\f
+/* Undocumented commands for unit testing. */
+
+static void
+ofctl_parse_flows__(struct ofputil_flow_mod *fms, size_t n_fms)
+{
+ enum ofputil_protocol usable_protocols;
+ enum ofputil_protocol protocol = 0;
+ char *usable_s;
+ size_t i;
+
+ usable_protocols = ofputil_flow_mod_usable_protocols(fms, n_fms);
+ usable_s = ofputil_protocols_to_string(usable_protocols);
+ printf("usable protocols: %s\n", usable_s);
+ free(usable_s);
+
+ if (!(usable_protocols & allowed_protocols)) {
+ ovs_fatal(0, "no usable protocol");
+ }
+ for (i = 0; i < sizeof(enum ofputil_protocol) * CHAR_BIT; i++) {
+ protocol = 1 << i;
+ if (protocol & usable_protocols & allowed_protocols) {
+ break;
+ }
+ }
+ assert(IS_POW2(protocol));
+
+ printf("chosen protocol: %s\n", ofputil_protocol_to_string(protocol));
+
+ for (i = 0; i < n_fms; i++) {
+ struct ofputil_flow_mod *fm = &fms[i];
+ struct ofpbuf *msg;
+
+ msg = ofputil_encode_flow_mod(fm, protocol);
+ ofp_print(stdout, msg->data, msg->size, verbosity);
+ ofpbuf_delete(msg);
+
+ free(fm->ofpacts);
+ }
+}
+
+/* "parse-flow FLOW": parses the argument as a flow (like add-flow) and prints
+ * it back to stdout. */
+static void
+ofctl_parse_flow(int argc OVS_UNUSED, char *argv[])
+{
+ struct ofputil_flow_mod fm;
+
+ parse_ofp_flow_mod_str(&fm, argv[1], OFPFC_ADD, false);
+ ofctl_parse_flows__(&fm, 1);
+}
+
+/* "parse-flows FILENAME": reads the named file as a sequence of flows (like
+ * add-flows) and prints each of the flows back to stdout. */
+static void
+ofctl_parse_flows(int argc OVS_UNUSED, char *argv[])
+{
+ struct ofputil_flow_mod *fms = NULL;
+ size_t n_fms = 0;
+
+ parse_ofp_flow_mod_file(argv[1], OFPFC_ADD, &fms, &n_fms);
+ ofctl_parse_flows__(fms, n_fms);
+ free(fms);
+}
+
+static void
+ofctl_parse_nxm__(bool oxm)
+{
+ struct ds in;
+
+ ds_init(&in);
+ while (!ds_get_test_line(&in, stdin)) {
+ struct ofpbuf nx_match;
+ struct cls_rule rule;
+ ovs_be64 cookie, cookie_mask;
+ enum ofperr error;
+ int match_len;
+
+ /* Convert string to nx_match. */
+ ofpbuf_init(&nx_match, 0);
+ if (oxm) {
+ match_len = oxm_match_from_string(ds_cstr(&in), &nx_match);
+ } else {
+ match_len = nx_match_from_string(ds_cstr(&in), &nx_match);
+ }
+
+ /* Convert nx_match to cls_rule. */
+ if (strict) {
+ if (oxm) {
+ error = oxm_pull_match(&nx_match, 0, &rule);
+ } else {
+ error = nx_pull_match(&nx_match, match_len, 0, &rule,
+ &cookie, &cookie_mask);
+ }
+ } else {
+ if (oxm) {
+ error = oxm_pull_match_loose(&nx_match, 0, &rule);
+ } else {
+ error = nx_pull_match_loose(&nx_match, match_len, 0, &rule,
+ &cookie, &cookie_mask);
+ }
+ }
+
+
+ if (!error) {
+ char *out;
+
+ /* Convert cls_rule back to nx_match. */
+ ofpbuf_uninit(&nx_match);
+ ofpbuf_init(&nx_match, 0);
+ if (oxm) {
+ match_len = oxm_put_match(&nx_match, &rule);
+ out = oxm_match_to_string(nx_match.data, match_len);
+ } else {
+ match_len = nx_put_match(&nx_match, &rule,
+ cookie, cookie_mask);
+ out = nx_match_to_string(nx_match.data, match_len);
+ }
+
+ puts(out);
+ free(out);
+ } else {
+ printf("nx_pull_match() returned error %s\n",
+ ofperr_get_name(error));
+ }
+
+ ofpbuf_uninit(&nx_match);
+ }
+ ds_destroy(&in);
+}
+
+/* "parse-nxm": reads a series of NXM nx_match specifications as strings from
+ * stdin, does some internal fussing with them, and then prints them back as
+ * strings on stdout. */
+static void
+ofctl_parse_nxm(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+ return ofctl_parse_nxm__(false);
+}
+
+/* "parse-oxm": reads a series of OXM nx_match specifications as strings from
+ * stdin, does some internal fussing with them, and then prints them back as
+ * strings on stdout. */
+static void
+ofctl_parse_oxm(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+ return ofctl_parse_nxm__(true);
+}
+
+static void
+print_differences(const char *prefix,
+ const void *a_, size_t a_len,
+ const void *b_, size_t b_len)
+{
+ const uint8_t *a = a_;
+ const uint8_t *b = b_;
+ size_t i;