struct hmap_node hmap_node; /* In ofproto's "deletions" hmap. */
struct rule *rule; /* Rule being operated upon. */
enum ofoperation_type type; /* Type of operation. */
- struct rule *victim; /* OFOPERATION_ADD: Replaced rule. */
- struct ofpact *ofpacts; /* OFOPERATION_MODIFY: Replaced actions. */
- size_t ofpacts_len; /* OFOPERATION_MODIFY: Bytes of ofpacts. */
+
+ /* OFOPERATION_ADD. */
+ struct rule *victim; /* Rule being replaced, if any.. */
+
+ /* OFOPERATION_MODIFY: The old actions, if the actions are changing. */
+ struct ofpact *ofpacts;
+ size_t ofpacts_len;
+
+ /* OFOPERATION_DELETE. */
+ enum ofp_flow_removed_reason reason; /* Reason flow was removed. */
+
ovs_be64 flow_cookie; /* Rule's old flow cookie. */
enum ofperr error; /* 0 if no error. */
};
static struct ofoperation *ofoperation_create(struct ofopgroup *,
struct rule *,
- enum ofoperation_type);
+ enum ofoperation_type,
+ enum ofp_flow_removed_reason);
static void ofoperation_destroy(struct ofoperation *);
/* oftable. */
static void ofproto_rule_destroy__(struct rule *);
static void ofproto_rule_send_removed(struct rule *, uint8_t reason);
static bool rule_is_modifiable(const struct rule *);
-static bool rule_is_hidden(const struct rule *);
/* OpenFlow. */
static enum ofperr add_flow(struct ofproto *, struct ofconn *,
cls_cursor_init(&cursor, &table->cls, NULL);
CLS_CURSOR_FOR_EACH_SAFE (rule, next_rule, cr, &cursor) {
if (!rule->pending) {
- ofoperation_create(group, rule, OFOPERATION_DELETE);
+ ofoperation_create(group, rule, OFOPERATION_DELETE,
+ OFPRR_DELETE);
oftable_remove_rule(rule);
ofproto->ofproto_class->rule_destruct(rule);
}
} else {
/* Initiate deletion -> success. */
struct ofopgroup *group = ofopgroup_create_unattached(ofproto);
- ofoperation_create(group, rule, OFOPERATION_DELETE);
+ ofoperation_create(group, rule, OFOPERATION_DELETE, OFPRR_DELETE);
oftable_remove_rule(rule);
ofproto->ofproto_class->rule_destruct(rule);
ofopgroup_submit(group);
/* Returns true if 'rule' has an OpenFlow OFPAT_OUTPUT or OFPAT_ENQUEUE action
* that outputs to 'port' (output to OFPP_FLOOD and OFPP_ALL doesn't count). */
-static bool
-rule_has_out_port(const struct rule *rule, uint16_t port)
+bool
+ofproto_rule_has_out_port(const struct rule *rule, uint16_t port)
{
return (port == OFPP_NONE
|| ofpacts_output_to_port(rule->ofpacts, rule->ofpacts_len, port));
}
+/* Returns true if a rule related to 'op' has an OpenFlow OFPAT_OUTPUT or
+ * OFPAT_ENQUEUE action that outputs to 'out_port'. */
+bool
+ofoperation_has_out_port(const struct ofoperation *op, uint16_t out_port)
+{
+ if (ofproto_rule_has_out_port(op->rule, out_port)) {
+ return true;
+ }
+
+ switch (op->type) {
+ case OFOPERATION_ADD:
+ return op->victim && ofproto_rule_has_out_port(op->victim, out_port);
+
+ case OFOPERATION_DELETE:
+ return false;
+
+ case OFOPERATION_MODIFY:
+ return ofpacts_output_to_port(op->ofpacts, op->ofpacts_len, out_port);
+ }
+
+ NOT_REACHED();
+}
+
/* Executes the actions indicated by 'rule' on 'packet' and credits 'rule''s
* statistics appropriately. 'packet' must have at least sizeof(struct
* ofp_packet_in) bytes of headroom.
* Rules with priority higher than UINT16_MAX are set up by ofproto itself
* (e.g. by in-band control) and are intentionally hidden from the
* controller. */
-static bool
-rule_is_hidden(const struct rule *rule)
+bool
+ofproto_rule_is_hidden(const struct rule *rule)
{
return rule->cr.priority > UINT16_MAX;
}
if (rule->pending) {
return OFPROTO_POSTPONE;
}
- if (!rule_is_hidden(rule) && rule_has_out_port(rule, out_port)
+ if (!ofproto_rule_is_hidden(rule)
+ && ofproto_rule_has_out_port(rule, out_port)
&& !((rule->flow_cookie ^ cookie) & cookie_mask)) {
list_push_back(rules, &rule->ofproto_node);
}
if (rule->pending) {
return OFPROTO_POSTPONE;
}
- if (!rule_is_hidden(rule) && rule_has_out_port(rule, out_port)
+ if (!ofproto_rule_is_hidden(rule)
+ && ofproto_rule_has_out_port(rule, out_port)
&& !((rule->flow_cookie ^ cookie) & cookie_mask)) {
list_push_back(rules, &rule->ofproto_node);
}
rule->ofpacts_len = fm->ofpacts_len;
rule->evictable = true;
rule->eviction_group = NULL;
+ rule->monitor_flags = 0;
+ rule->add_seqno = 0;
+ rule->modify_seqno = 0;
/* Insert new rule. */
victim = oftable_replace_rule(rule);
}
group = ofopgroup_create(ofproto, ofconn, request, fm->buffer_id);
- op = ofoperation_create(group, rule, OFOPERATION_ADD);
+ op = ofoperation_create(group, rule, OFOPERATION_ADD, 0);
op->victim = victim;
error = ofproto->ofproto_class->rule_construct(rule);
group = ofopgroup_create(ofproto, ofconn, request, fm->buffer_id);
error = OFPERR_OFPBRC_EPERM;
LIST_FOR_EACH (rule, ofproto_node, rules) {
+ struct ofoperation *op;
+ bool actions_changed;
+ ovs_be64 new_cookie;
+
if (rule_is_modifiable(rule)) {
/* At least one rule is modifiable, don't report EPERM error. */
error = 0;
continue;
}
- if (!ofpacts_equal(fm->ofpacts, fm->ofpacts_len,
- rule->ofpacts, rule->ofpacts_len)) {
- struct ofoperation *op;
+ actions_changed = !ofpacts_equal(fm->ofpacts, fm->ofpacts_len,
+ rule->ofpacts, rule->ofpacts_len);
+ new_cookie = (fm->new_cookie != htonll(UINT64_MAX)
+ ? fm->new_cookie
+ : rule->flow_cookie);
+ if (!actions_changed && new_cookie == rule->flow_cookie) {
+ /* No change at all. */
+ continue;
+ }
- op = ofoperation_create(group, rule, OFOPERATION_MODIFY);
+ op = ofoperation_create(group, rule, OFOPERATION_MODIFY, 0);
+ rule->flow_cookie = new_cookie;
+ if (actions_changed) {
op->ofpacts = rule->ofpacts;
op->ofpacts_len = rule->ofpacts_len;
rule->ofpacts = xmemdup(fm->ofpacts, fm->ofpacts_len);
rule->ofpacts_len = fm->ofpacts_len;
rule->ofproto->ofproto_class->rule_modify_actions(rule);
} else {
- rule->modified = time_msec();
- }
- if (fm->new_cookie != htonll(UINT64_MAX)) {
- rule->flow_cookie = fm->new_cookie;
+ ofoperation_complete(op, 0);
}
}
ofopgroup_submit(group);
ofproto_rule_send_removed(rule, OFPRR_DELETE);
- ofoperation_create(group, rule, OFOPERATION_DELETE);
+ ofoperation_create(group, rule, OFOPERATION_DELETE, OFPRR_DELETE);
oftable_remove_rule(rule);
ofproto->ofproto_class->rule_destruct(rule);
}
{
struct ofputil_flow_removed fr;
- if (rule_is_hidden(rule) || !rule->send_flow_removed) {
+ if (ofproto_rule_is_hidden(rule) || !rule->send_flow_removed) {
return;
}
* OFPRR_HARD_TIMEOUT or OFPRR_IDLE_TIMEOUT), and then removes 'rule' from its
* ofproto.
*
+ * 'rule' must not have a pending operation (that is, 'rule->pending' must be
+ * NULL).
+ *
* ofproto implementation ->run() functions should use this function to expire
* OpenFlow flows. */
void
ofproto_rule_send_removed(rule, reason);
group = ofopgroup_create_unattached(ofproto);
- ofoperation_create(group, rule, OFOPERATION_DELETE);
+ ofoperation_create(group, rule, OFOPERATION_DELETE, reason);
oftable_remove_rule(rule);
ofproto->ofproto_class->rule_destruct(rule);
ofopgroup_submit(group);
return 0;
}
+static void
+ofproto_compose_flow_refresh_update(const struct rule *rule,
+ enum nx_flow_monitor_flags flags,
+ struct list *msgs)
+{
+ struct ofoperation *op = rule->pending;
+ struct ofputil_flow_update fu;
+
+ if (op && op->type == OFOPERATION_ADD && !op->victim) {
+ /* We'll report the final flow when the operation completes. Reporting
+ * it now would cause a duplicate report later. */
+ return;
+ }
+
+ fu.event = (flags & (NXFMF_INITIAL | NXFMF_ADD)
+ ? NXFME_ADDED : NXFME_MODIFIED);
+ fu.reason = 0;
+ fu.idle_timeout = rule->idle_timeout;
+ fu.hard_timeout = rule->hard_timeout;
+ fu.table_id = rule->table_id;
+ fu.cookie = rule->flow_cookie;
+ fu.match = (struct cls_rule *) &rule->cr;
+ if (!(flags & NXFMF_ACTIONS)) {
+ fu.ofpacts = NULL;
+ fu.ofpacts_len = 0;
+ } else if (!op) {
+ fu.ofpacts = rule->ofpacts;
+ fu.ofpacts_len = rule->ofpacts_len;
+ } else {
+ /* An operation is in progress. Use the previous version of the flow's
+ * actions, so that when the operation commits we report the change. */
+ switch (op->type) {
+ case OFOPERATION_ADD:
+ /* We already verified that there was a victim. */
+ fu.ofpacts = op->victim->ofpacts;
+ fu.ofpacts_len = op->victim->ofpacts_len;
+ break;
+
+ case OFOPERATION_MODIFY:
+ if (op->ofpacts) {
+ fu.ofpacts = op->ofpacts;
+ fu.ofpacts_len = op->ofpacts_len;
+ } else {
+ fu.ofpacts = rule->ofpacts;
+ fu.ofpacts_len = rule->ofpacts_len;
+ }
+ break;
+
+ case OFOPERATION_DELETE:
+ fu.ofpacts = rule->ofpacts;
+ fu.ofpacts_len = rule->ofpacts_len;
+ break;
+
+ default:
+ NOT_REACHED();
+ }
+ }
+
+ if (list_is_empty(msgs)) {
+ ofputil_start_flow_update(msgs);
+ }
+ ofputil_append_flow_update(&fu, msgs);
+}
+
+void
+ofmonitor_compose_refresh_updates(struct list *rules, struct list *msgs)
+{
+ struct rule *rule;
+
+ LIST_FOR_EACH (rule, ofproto_node, rules) {
+ enum nx_flow_monitor_flags flags = rule->monitor_flags;
+ rule->monitor_flags = 0;
+
+ ofproto_compose_flow_refresh_update(rule, flags, msgs);
+ }
+}
+
+static void
+ofproto_collect_ofmonitor_refresh_rule(const struct ofmonitor *m,
+ struct rule *rule, uint64_t seqno,
+ struct list *rules)
+{
+ enum nx_flow_monitor_flags update;
+
+ if (ofproto_rule_is_hidden(rule)) {
+ return;
+ }
+
+ if (!(rule->pending
+ ? ofoperation_has_out_port(rule->pending, m->out_port)
+ : ofproto_rule_has_out_port(rule, m->out_port))) {
+ return;
+ }
+
+ if (seqno) {
+ if (rule->add_seqno > seqno) {
+ update = NXFMF_ADD | NXFMF_MODIFY;
+ } else if (rule->modify_seqno > seqno) {
+ update = NXFMF_MODIFY;
+ } else {
+ return;
+ }
+
+ if (!(m->flags & update)) {
+ return;
+ }
+ } else {
+ update = NXFMF_INITIAL;
+ }
+
+ if (!rule->monitor_flags) {
+ list_push_back(rules, &rule->ofproto_node);
+ }
+ rule->monitor_flags |= update | (m->flags & NXFMF_ACTIONS);
+}
+
+static void
+ofproto_collect_ofmonitor_refresh_rules(const struct ofmonitor *m,
+ uint64_t seqno,
+ struct list *rules)
+{
+ const struct ofproto *ofproto = ofconn_get_ofproto(m->ofconn);
+ const struct ofoperation *op;
+ const struct oftable *table;
+
+ FOR_EACH_MATCHING_TABLE (table, m->table_id, ofproto) {
+ struct cls_cursor cursor;
+ struct rule *rule;
+
+ cls_cursor_init(&cursor, &table->cls, &m->match);
+ CLS_CURSOR_FOR_EACH (rule, cr, &cursor) {
+ assert(!rule->pending); /* XXX */
+ ofproto_collect_ofmonitor_refresh_rule(m, rule, seqno, rules);
+ }
+ }
+
+ HMAP_FOR_EACH (op, hmap_node, &ofproto->deletions) {
+ struct rule *rule = op->rule;
+
+ if (((m->table_id == 0xff
+ ? !(ofproto->tables[rule->table_id].flags & OFTABLE_HIDDEN)
+ : m->table_id == rule->table_id))
+ && cls_rule_is_loose_match(&rule->cr, &m->match)) {
+ ofproto_collect_ofmonitor_refresh_rule(m, rule, seqno, rules);
+ }
+ }
+}
+
+static void
+ofproto_collect_ofmonitor_initial_rules(struct ofmonitor *m,
+ struct list *rules)
+{
+ if (m->flags & NXFMF_INITIAL) {
+ ofproto_collect_ofmonitor_refresh_rules(m, 0, rules);
+ }
+}
+
+void
+ofmonitor_collect_resume_rules(struct ofmonitor *m,
+ uint64_t seqno, struct list *rules)
+{
+ ofproto_collect_ofmonitor_refresh_rules(m, seqno, rules);
+}
+
+static enum ofperr
+handle_flow_monitor_request(struct ofconn *ofconn,
+ const struct ofp_stats_msg *osm)
+{
+ struct ofproto *ofproto = ofconn_get_ofproto(ofconn);
+ struct ofmonitor **monitors;
+ size_t n_monitors, allocated_monitors;
+ struct list replies;
+ enum ofperr error;
+ struct list rules;
+ struct ofpbuf b;
+ size_t i;
+
+ error = 0;
+ ofpbuf_use_const(&b, osm, ntohs(osm->header.length));
+ monitors = NULL;
+ n_monitors = allocated_monitors = 0;
+ for (;;) {
+ struct ofputil_flow_monitor_request request;
+ struct ofmonitor *m;
+ int retval;
+
+ retval = ofputil_decode_flow_monitor_request(&request, &b);
+ if (retval == EOF) {
+ break;
+ } else if (retval) {
+ error = retval;
+ goto error;
+ }
+
+ if (request.table_id != 0xff
+ && request.table_id >= ofproto->n_tables) {
+ error = OFPERR_OFPBRC_BAD_TABLE_ID;
+ goto error;
+ }
+
+ error = ofmonitor_create(&request, ofconn, &m);
+ if (error) {
+ goto error;
+ }
+
+ if (n_monitors >= allocated_monitors) {
+ monitors = x2nrealloc(monitors, &allocated_monitors,
+ sizeof *monitors);
+ }
+ monitors[n_monitors++] = m;
+ }
+
+ list_init(&rules);
+ for (i = 0; i < n_monitors; i++) {
+ ofproto_collect_ofmonitor_initial_rules(monitors[i], &rules);
+ }
+
+ ofputil_start_stats_reply(osm, &replies);
+ ofmonitor_compose_refresh_updates(&rules, &replies);
+ ofconn_send_replies(ofconn, &replies);
+
+ free(monitors);
+
+ return 0;
+
+error:
+ for (i = 0; i < n_monitors; i++) {
+ ofmonitor_destroy(monitors[i]);
+ }
+ free(monitors);
+ return error;
+}
+
+static enum ofperr
+handle_flow_monitor_cancel(struct ofconn *ofconn, const struct ofp_header *oh)
+{
+ struct ofmonitor *m;
+ uint32_t id;
+
+ id = ofputil_decode_flow_monitor_cancel(oh);
+ m = ofmonitor_lookup(ofconn, id);
+ if (!m) {
+ return OFPERR_NXBRC_FM_BAD_ID;
+ }
+
+ ofmonitor_destroy(m);
+ return 0;
+}
+
static enum ofperr
handle_openflow__(struct ofconn *ofconn, const struct ofpbuf *msg)
{
/* Nothing to do. */
return 0;
+ case OFPUTIL_NXT_FLOW_MONITOR_CANCEL:
+ return handle_flow_monitor_cancel(ofconn, oh);
+
case OFPUTIL_NXT_SET_ASYNC_CONFIG:
return handle_nxt_set_async_config(ofconn, oh);
case OFPUTIL_OFPST_PORT_DESC_REQUEST:
return handle_port_desc_stats_request(ofconn, msg->data);
+ case OFPUTIL_NXST_FLOW_MONITOR_REQUEST:
+ return handle_flow_monitor_request(ofconn, msg->data);
+
case OFPUTIL_MSG_INVALID:
case OFPUTIL_OFPT_HELLO:
case OFPUTIL_OFPT_ERROR:
case OFPUTIL_NXT_ROLE_REPLY:
case OFPUTIL_NXT_FLOW_REMOVED:
case OFPUTIL_NXT_PACKET_IN:
+ case OFPUTIL_NXT_FLOW_MONITOR_PAUSED:
+ case OFPUTIL_NXT_FLOW_MONITOR_RESUMED:
case OFPUTIL_NXST_FLOW_REPLY:
case OFPUTIL_NXST_AGGREGATE_REPLY:
+ case OFPUTIL_NXST_FLOW_MONITOR_REPLY:
default:
return (oh->type == OFPT10_STATS_REQUEST ||
oh->type == OFPT10_STATS_REPLY
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;
}
}
+ 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) {
rule->modified = time_msec();
} else {
rule->flow_cookie = op->flow_cookie;
- free(rule->ofpacts);
- rule->ofpacts = op->ofpacts;
- rule->ofpacts_len = op->ofpacts_len;
- op->ofpacts = NULL;
- op->ofpacts_len = 0;
+ if (op->ofpacts) {
+ free(rule->ofpacts);
+ rule->ofpacts = op->ofpacts;
+ rule->ofpacts_len = op->ofpacts_len;
+ op->ofpacts = NULL;
+ op->ofpacts_len = 0;
+ }
}
break;
ofoperation_destroy(op);
}
+ ofmonitor_flush(ofproto->connmgr);
+
if (!list_is_empty(&group->ofproto_node)) {
assert(ofproto->n_pending > 0);
ofproto->n_pending--;
/* 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 ofoperation_type type,
+ enum ofp_flow_removed_reason reason)
{
struct ofproto *ofproto = group->ofproto;
struct ofoperation *op;
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++;
break;
}
- ofoperation_create(group, rule, OFOPERATION_DELETE);
+ ofoperation_create(group, rule,
+ OFOPERATION_DELETE, OFPRR_EVICTION);
oftable_remove_rule(rule);
ofproto->ofproto_class->rule_destruct(rule);
}