+ case OFPTYPE_QUEUE_STATS_REQUEST:
+ return handle_queue_stats_request(ofconn, oh);
+
+ case OFPTYPE_PORT_DESC_STATS_REQUEST:
+ return handle_port_desc_stats_request(ofconn, oh);
+
+ case OFPTYPE_FLOW_MONITOR_STATS_REQUEST:
+ return handle_flow_monitor_request(ofconn, oh);
+
+ case OFPTYPE_HELLO:
+ case OFPTYPE_ERROR:
+ case OFPTYPE_FEATURES_REPLY:
+ case OFPTYPE_GET_CONFIG_REPLY:
+ case OFPTYPE_PACKET_IN:
+ case OFPTYPE_FLOW_REMOVED:
+ case OFPTYPE_PORT_STATUS:
+ case OFPTYPE_BARRIER_REPLY:
+ case OFPTYPE_DESC_STATS_REPLY:
+ case OFPTYPE_FLOW_STATS_REPLY:
+ case OFPTYPE_QUEUE_STATS_REPLY:
+ case OFPTYPE_PORT_STATS_REPLY:
+ case OFPTYPE_TABLE_STATS_REPLY:
+ case OFPTYPE_AGGREGATE_STATS_REPLY:
+ case OFPTYPE_PORT_DESC_STATS_REPLY:
+ case OFPTYPE_ROLE_REPLY:
+ case OFPTYPE_FLOW_MONITOR_PAUSED:
+ case OFPTYPE_FLOW_MONITOR_RESUMED:
+ case OFPTYPE_FLOW_MONITOR_STATS_REPLY:
+ default:
+ return OFPERR_OFPBRC_BAD_TYPE;
+ }
+}
+
+static bool
+handle_openflow(struct ofconn *ofconn, struct ofpbuf *ofp_msg)
+{
+ int error = handle_openflow__(ofconn, ofp_msg);
+ if (error && error != OFPROTO_POSTPONE) {
+ ofconn_send_error(ofconn, ofp_msg->data, error);
+ }
+ COVERAGE_INC(ofproto_recv_openflow);
+ return error != OFPROTO_POSTPONE;
+}
+\f
+/* Asynchronous operations. */
+
+/* Creates and returns a new ofopgroup that is not associated with any
+ * OpenFlow connection.
+ *
+ * The caller should add operations to the returned group with
+ * ofoperation_create() and then submit it with ofopgroup_submit(). */
+static struct ofopgroup *
+ofopgroup_create_unattached(struct ofproto *ofproto)
+{
+ struct ofopgroup *group = xzalloc(sizeof *group);
+ group->ofproto = ofproto;
+ list_init(&group->ofproto_node);
+ list_init(&group->ops);
+ list_init(&group->ofconn_node);
+ return group;
+}
+
+/* Creates and returns a new ofopgroup for 'ofproto'.
+ *
+ * If 'ofconn' is NULL, the new ofopgroup is not associated with any OpenFlow
+ * connection. The 'request' and 'buffer_id' arguments are ignored.
+ *
+ * If 'ofconn' is nonnull, then the new ofopgroup is associated with 'ofconn'.
+ * If the ofopgroup eventually fails, then the error reply will include
+ * 'request'. If the ofopgroup eventually succeeds, then the packet with
+ * buffer id 'buffer_id' on 'ofconn' will be sent by 'ofconn''s ofproto.
+ *
+ * The caller should add operations to the returned group with
+ * ofoperation_create() and then submit it with ofopgroup_submit(). */
+static struct ofopgroup *
+ofopgroup_create(struct ofproto *ofproto, struct ofconn *ofconn,
+ const struct ofp_header *request, uint32_t buffer_id)
+{
+ struct ofopgroup *group = ofopgroup_create_unattached(ofproto);
+ if (ofconn) {
+ size_t request_len = ntohs(request->length);
+
+ assert(ofconn_get_ofproto(ofconn) == ofproto);
+
+ ofconn_add_opgroup(ofconn, &group->ofconn_node);
+ group->ofconn = ofconn;
+ group->request = xmemdup(request, MIN(request_len, 64));
+ group->buffer_id = buffer_id;
+ }
+ return group;
+}
+
+/* Submits 'group' for processing.
+ *
+ * If 'group' contains no operations (e.g. none were ever added, or all of the
+ * ones that were added completed synchronously), then it is destroyed
+ * immediately. Otherwise it is added to the ofproto's list of pending
+ * groups. */
+static void
+ofopgroup_submit(struct ofopgroup *group)
+{
+ if (!group->n_running) {
+ ofopgroup_complete(group);
+ } else {
+ list_push_back(&group->ofproto->pending, &group->ofproto_node);
+ group->ofproto->n_pending++;
+ }
+}
+
+static void
+ofopgroup_complete(struct ofopgroup *group)
+{
+ struct ofproto *ofproto = group->ofproto;
+
+ struct ofconn *abbrev_ofconn;
+ ovs_be32 abbrev_xid;
+
+ struct ofoperation *op, *next_op;
+ int error;
+
+ assert(!group->n_running);
+
+ error = 0;
+ LIST_FOR_EACH (op, group_node, &group->ops) {
+ if (op->error) {
+ error = op->error;
+ break;
+ }
+ }
+
+ if (!error && group->ofconn && group->buffer_id != UINT32_MAX) {
+ LIST_FOR_EACH (op, group_node, &group->ops) {
+ if (op->type != OFOPERATION_DELETE) {
+ struct ofpbuf *packet;
+ uint16_t in_port;
+
+ error = ofconn_pktbuf_retrieve(group->ofconn, group->buffer_id,
+ &packet, &in_port);
+ if (packet) {
+ assert(!error);
+ error = rule_execute(op->rule, in_port, packet);
+ }
+ break;
+ }
+ }
+ }
+
+ if (!error && !list_is_empty(&group->ofconn_node)) {
+ abbrev_ofconn = group->ofconn;
+ abbrev_xid = group->request->xid;
+ } else {
+ abbrev_ofconn = NULL;
+ abbrev_xid = htonl(0);
+ }
+ LIST_FOR_EACH_SAFE (op, next_op, group_node, &group->ops) {
+ struct rule *rule = op->rule;
+
+ if (!op->error && !ofproto_rule_is_hidden(rule)) {
+ /* Check that we can just cast from ofoperation_type to
+ * nx_flow_update_event. */
+ BUILD_ASSERT_DECL((enum nx_flow_update_event) OFOPERATION_ADD
+ == NXFME_ADDED);
+ BUILD_ASSERT_DECL((enum nx_flow_update_event) OFOPERATION_DELETE
+ == NXFME_DELETED);
+ BUILD_ASSERT_DECL((enum nx_flow_update_event) OFOPERATION_MODIFY
+ == NXFME_MODIFIED);
+
+ ofmonitor_report(ofproto->connmgr, rule,
+ (enum nx_flow_update_event) op->type,
+ op->reason, abbrev_ofconn, abbrev_xid);
+ }
+
+ rule->pending = NULL;
+
+ switch (op->type) {
+ case OFOPERATION_ADD:
+ if (!op->error) {
+ uint16_t vid_mask;
+
+ ofproto_rule_destroy__(op->victim);
+ vid_mask = minimask_get_vid_mask(&rule->cr.match.mask);
+ if (vid_mask == VLAN_VID_MASK) {
+ if (ofproto->vlan_bitmap) {
+ uint16_t vid = miniflow_get_vid(&rule->cr.match.flow);
+ if (!bitmap_is_set(ofproto->vlan_bitmap, vid)) {
+ bitmap_set1(ofproto->vlan_bitmap, vid);
+ ofproto->vlans_changed = true;
+ }
+ } else {
+ ofproto->vlans_changed = true;
+ }
+ }
+ } else {
+ oftable_substitute_rule(rule, op->victim);
+ ofproto_rule_destroy__(rule);
+ }
+ break;
+
+ case OFOPERATION_DELETE:
+ assert(!op->error);
+ ofproto_rule_destroy__(rule);
+ op->rule = NULL;
+ break;
+
+ case OFOPERATION_MODIFY:
+ if (!op->error) {
+ rule->modified = time_msec();
+ } else {
+ rule->flow_cookie = op->flow_cookie;
+ if (op->ofpacts) {
+ free(rule->ofpacts);
+ rule->ofpacts = op->ofpacts;
+ rule->ofpacts_len = op->ofpacts_len;
+ op->ofpacts = NULL;
+ op->ofpacts_len = 0;
+ }
+ }
+ break;
+
+ default:
+ NOT_REACHED();
+ }
+
+ ofoperation_destroy(op);
+ }
+
+ ofmonitor_flush(ofproto->connmgr);
+
+ if (!list_is_empty(&group->ofproto_node)) {
+ assert(ofproto->n_pending > 0);
+ ofproto->n_pending--;
+ list_remove(&group->ofproto_node);
+ }
+ if (!list_is_empty(&group->ofconn_node)) {
+ list_remove(&group->ofconn_node);
+ if (error) {
+ ofconn_send_error(group->ofconn, group->request, error);
+ }
+ connmgr_retry(ofproto->connmgr);
+ }
+ free(group->request);
+ free(group);
+}
+
+/* Initiates a new operation on 'rule', of the specified 'type', within
+ * 'group'. Prior to calling, 'rule' must not have any pending operation.
+ *
+ * For a 'type' of OFOPERATION_DELETE, 'reason' should specify the reason that
+ * the flow is being deleted. For other 'type's, 'reason' is ignored (use 0).
+ *
+ * Returns the newly created ofoperation (which is also available as
+ * rule->pending). */
+static struct ofoperation *
+ofoperation_create(struct ofopgroup *group, struct rule *rule,
+ enum ofoperation_type type,
+ enum ofp_flow_removed_reason reason)
+{
+ struct ofproto *ofproto = group->ofproto;
+ struct ofoperation *op;
+
+ assert(!rule->pending);
+
+ op = rule->pending = xzalloc(sizeof *op);
+ op->group = group;
+ list_push_back(&group->ops, &op->group_node);
+ op->rule = rule;
+ op->type = type;
+ op->reason = reason;
+ op->flow_cookie = rule->flow_cookie;
+
+ group->n_running++;
+
+ if (type == OFOPERATION_DELETE) {
+ hmap_insert(&ofproto->deletions, &op->hmap_node,
+ cls_rule_hash(&rule->cr, rule->table_id));
+ }
+
+ return op;
+}
+
+static void
+ofoperation_destroy(struct ofoperation *op)
+{
+ struct ofopgroup *group = op->group;
+
+ if (op->rule) {
+ op->rule->pending = NULL;
+ }
+ if (op->type == OFOPERATION_DELETE) {
+ hmap_remove(&group->ofproto->deletions, &op->hmap_node);
+ }
+ list_remove(&op->group_node);
+ free(op->ofpacts);
+ free(op);
+}
+
+/* Indicates that 'op' completed with status 'error', which is either 0 to
+ * indicate success or an OpenFlow error code on failure.
+ *
+ * If 'error' is 0, indicating success, the operation will be committed
+ * permanently to the flow table. There is one interesting subcase:
+ *
+ * - If 'op' is an "add flow" operation that is replacing an existing rule in
+ * the flow table (the "victim" rule) by a new one, then the caller must
+ * have uninitialized any derived state in the victim rule, as in step 5 in
+ * the "Life Cycle" in ofproto/ofproto-provider.h. ofoperation_complete()
+ * performs steps 6 and 7 for the victim rule, most notably by calling its
+ * ->rule_dealloc() function.
+ *
+ * If 'error' is nonzero, then generally the operation will be rolled back:
+ *
+ * - If 'op' is an "add flow" operation, ofproto removes the new rule or
+ * restores the original rule. The caller must have uninitialized any
+ * derived state in the new rule, as in step 5 of in the "Life Cycle" in
+ * ofproto/ofproto-provider.h. ofoperation_complete() performs steps 6 and
+ * and 7 for the new rule, calling its ->rule_dealloc() function.
+ *
+ * - If 'op' is a "modify flow" operation, ofproto restores the original
+ * actions.
+ *
+ * - 'op' must not be a "delete flow" operation. Removing a rule is not
+ * allowed to fail. It must always succeed.
+ *
+ * Please see the large comment in ofproto/ofproto-provider.h titled
+ * "Asynchronous Operation Support" for more information. */
+void
+ofoperation_complete(struct ofoperation *op, enum ofperr error)
+{
+ struct ofopgroup *group = op->group;
+
+ assert(op->rule->pending == op);
+ assert(group->n_running > 0);
+ assert(!error || op->type != OFOPERATION_DELETE);
+
+ op->error = error;
+ if (!--group->n_running && !list_is_empty(&group->ofproto_node)) {
+ ofopgroup_complete(group);
+ }
+}
+
+struct rule *
+ofoperation_get_victim(struct ofoperation *op)
+{
+ assert(op->type == OFOPERATION_ADD);
+ return op->victim;
+}
+\f
+static uint64_t
+pick_datapath_id(const struct ofproto *ofproto)
+{
+ const struct ofport *port;
+
+ port = ofproto_get_port(ofproto, OFPP_LOCAL);
+ if (port) {
+ uint8_t ea[ETH_ADDR_LEN];
+ int error;