+ struct rule *rule = find_flow_strict(p, ofm);
+ if (rule && !rule_is_hidden(rule)) {
+ modify_flow(p, ofm, n_actions, rule);
+ return send_buffered_packet(p, ofconn, rule, ofm);
+ } else {
+ return add_flow(p, ofconn, ofm, n_actions);
+ }
+}
+
+/* Callback for modify_flows_loose(). */
+static void
+modify_flows_cb(struct cls_rule *rule_, void *cbdata_)
+{
+ struct rule *rule = rule_from_cls_rule(rule_);
+ struct modify_flows_cbdata *cbdata = cbdata_;
+
+ if (!rule_is_hidden(rule)) {
+ cbdata->match = rule;
+ modify_flow(cbdata->ofproto, cbdata->ofm, cbdata->n_actions, rule);
+ }
+}
+
+/* Implements core of OFPFC_MODIFY and OFPFC_MODIFY_STRICT where 'rule' has
+ * been identified as a flow in 'p''s flow table to be modified, by changing
+ * the rule's actions to match those in 'ofm' (which is followed by 'n_actions'
+ * ofp_action[] structures). */
+static int
+modify_flow(struct ofproto *p, const struct ofp_flow_mod *ofm,
+ size_t n_actions, struct rule *rule)
+{
+ size_t actions_len = n_actions * sizeof *rule->actions;
+
+ rule->flow_cookie = ofm->cookie;
+
+ /* If the actions are the same, do nothing. */
+ if (n_actions == rule->n_actions
+ && (!n_actions || !memcmp(ofm->actions, rule->actions, actions_len)))
+ {
+ return 0;
+ }
+
+ /* Replace actions. */
+ free(rule->actions);
+ rule->actions = n_actions ? xmemdup(ofm->actions, actions_len) : NULL;
+ rule->n_actions = n_actions;
+
+ /* Make sure that the datapath gets updated properly. */
+ if (rule->cr.wc.wildcards) {
+ COVERAGE_INC(ofproto_mod_wc_flow);
+ p->need_revalidate = true;
+ } else {
+ rule_update_actions(p, rule);
+ }
+
+ return 0;
+}
+\f
+/* OFPFC_DELETE implementation. */
+
+struct delete_flows_cbdata {
+ struct ofproto *ofproto;
+ ovs_be16 out_port;
+};
+
+static void delete_flows_cb(struct cls_rule *, void *cbdata_);
+static void delete_flow(struct ofproto *, struct rule *, ovs_be16 out_port);
+
+/* Implements OFPFC_DELETE. */
+static void
+delete_flows_loose(struct ofproto *p, const struct ofp_flow_mod *ofm)
+{
+ struct delete_flows_cbdata cbdata;
+ struct cls_rule target;
+
+ cbdata.ofproto = p;
+ cbdata.out_port = ofm->out_port;
+
+ cls_rule_from_match(&ofm->match, 0, p->flow_format, ofm->cookie, &target);
+
+ classifier_for_each_match(&p->cls, &target, CLS_INC_ALL,
+ delete_flows_cb, &cbdata);
+}
+
+/* Implements OFPFC_DELETE_STRICT. */
+static void
+delete_flow_strict(struct ofproto *p, struct ofp_flow_mod *ofm)
+{
+ struct rule *rule = find_flow_strict(p, ofm);
+ if (rule) {
+ delete_flow(p, rule, ofm->out_port);
+ }
+}
+
+/* Callback for delete_flows_loose(). */
+static void
+delete_flows_cb(struct cls_rule *rule_, void *cbdata_)
+{
+ struct rule *rule = rule_from_cls_rule(rule_);
+ struct delete_flows_cbdata *cbdata = cbdata_;
+
+ delete_flow(cbdata->ofproto, rule, cbdata->out_port);
+}
+
+/* Implements core of OFPFC_DELETE and OFPFC_DELETE_STRICT where 'rule' has
+ * been identified as a flow to delete from 'p''s flow table, by deleting the
+ * flow and sending out a OFPT_FLOW_REMOVED message to any interested
+ * controller.
+ *
+ * Will not delete 'rule' if it is hidden. Will delete 'rule' only if
+ * 'out_port' is htons(OFPP_NONE) or if 'rule' actually outputs to the
+ * specified 'out_port'. */
+static void
+delete_flow(struct ofproto *p, struct rule *rule, ovs_be16 out_port)
+{
+ if (rule_is_hidden(rule)) {
+ return;
+ }
+
+ if (out_port != htons(OFPP_NONE) && !rule_has_out_port(rule, out_port)) {
+ return;
+ }
+
+ send_flow_removed(p, rule, time_msec(), OFPRR_DELETE);
+ rule_remove(p, rule);
+}
+\f
+static int
+handle_flow_mod(struct ofproto *p, struct ofconn *ofconn,
+ struct ofp_flow_mod *ofm)
+{
+ struct ofp_match orig_match;
+ size_t n_actions;
+ int error;
+
+ error = reject_slave_controller(ofconn, &ofm->header);
+ if (error) {
+ return error;
+ }
+ error = check_ofp_message_array(&ofm->header, OFPT_FLOW_MOD, sizeof *ofm,
+ sizeof *ofm->actions, &n_actions);
+ if (error) {
+ return error;
+ }
+
+ /* We do not support the emergency flow cache. It will hopefully
+ * get dropped from OpenFlow in the near future. */
+ if (ofm->flags & htons(OFPFF_EMERG)) {
+ /* There isn't a good fit for an error code, so just state that the
+ * flow table is full. */
+ return ofp_mkerr(OFPET_FLOW_MOD_FAILED, OFPFMFC_ALL_TABLES_FULL);
+ }
+
+ /* Normalize ofp->match. If normalization actually changes anything, then
+ * log the differences. */
+ ofm->match.pad1[0] = ofm->match.pad2[0] = 0;
+ orig_match = ofm->match;
+ normalize_match(&ofm->match);
+ if (memcmp(&ofm->match, &orig_match, sizeof orig_match)) {
+ static struct vlog_rate_limit normal_rl = VLOG_RATE_LIMIT_INIT(1, 1);
+ if (!VLOG_DROP_INFO(&normal_rl)) {
+ char *old = ofp_match_to_literal_string(&orig_match);
+ char *new = ofp_match_to_literal_string(&ofm->match);
+ VLOG_INFO("%s: normalization changed ofp_match, details:",
+ rconn_get_name(ofconn->rconn));
+ VLOG_INFO(" pre: %s", old);
+ VLOG_INFO("post: %s", new);
+ free(old);
+ free(new);
+ }
+ }
+
+ if (!ofm->match.wildcards) {
+ ofm->priority = htons(UINT16_MAX);