+static void
+learned_cookies_dec(struct ofproto *ofproto,
+ const struct rule_actions *actions,
+ struct list *dead_cookies)
+ OVS_REQUIRES(ofproto_mutex)
+{
+ learned_cookies_update__(ofproto, actions, -1, dead_cookies);
+}
+
+static void
+learned_cookies_flush(struct ofproto *ofproto, struct list *dead_cookies)
+ OVS_REQUIRES(ofproto_mutex)
+{
+ struct learned_cookie *c, *next;
+
+ LIST_FOR_EACH_SAFE (c, next, u.list_node, dead_cookies) {
+ struct rule_criteria criteria;
+ struct rule_collection rules;
+ struct match match;
+
+ match_init_catchall(&match);
+ rule_criteria_init(&criteria, c->table_id, &match, 0,
+ c->cookie, OVS_BE64_MAX, OFPP_ANY, OFPG_ANY);
+ rule_criteria_require_rw(&criteria, false);
+ collect_rules_loose(ofproto, &criteria, &rules);
+ delete_flows__(&rules, OFPRR_DELETE, NULL);
+ rule_criteria_destroy(&criteria);
+ rule_collection_destroy(&rules);
+
+ list_remove(&c->u.list_node);
+ free(c);
+ }
+}
+\f
+static enum ofperr
+handle_echo_request(struct ofconn *ofconn, const struct ofp_header *oh)
+{
+ ofconn_send_reply(ofconn, make_echo_reply(oh));
+ return 0;
+}
+
+static void
+query_tables(struct ofproto *ofproto,
+ struct ofputil_table_features **featuresp,
+ struct ofputil_table_stats **statsp)
+{
+ struct mf_bitmap rw_fields = oxm_writable_fields();
+ struct mf_bitmap match = oxm_matchable_fields();
+ struct mf_bitmap mask = oxm_maskable_fields();
+
+ struct ofputil_table_features *features;
+ struct ofputil_table_stats *stats;
+ int i;
+
+ features = *featuresp = xcalloc(ofproto->n_tables, sizeof *features);
+ for (i = 0; i < ofproto->n_tables; i++) {
+ struct ofputil_table_features *f = &features[i];
+
+ f->table_id = i;
+ sprintf(f->name, "table%d", i);
+ f->metadata_match = OVS_BE64_MAX;
+ f->metadata_write = OVS_BE64_MAX;
+ atomic_read_relaxed(&ofproto->tables[i].miss_config, &f->miss_config);
+ f->max_entries = 1000000;
+
+ bitmap_set_multiple(f->nonmiss.next, i + 1,
+ ofproto->n_tables - (i + 1), true);
+ f->nonmiss.instructions = (1u << N_OVS_INSTRUCTIONS) - 1;
+ if (i == ofproto->n_tables - 1) {
+ f->nonmiss.instructions &= ~(1u << OVSINST_OFPIT11_GOTO_TABLE);
+ }
+ f->nonmiss.write.ofpacts = (UINT64_C(1) << N_OFPACTS) - 1;
+ f->nonmiss.write.set_fields = rw_fields;
+ f->nonmiss.apply = f->nonmiss.write;
+ f->miss = f->nonmiss;
+
+ f->match = match;
+ f->mask = mask;
+ f->wildcard = match;
+ }
+
+ if (statsp) {
+ stats = *statsp = xcalloc(ofproto->n_tables, sizeof *stats);
+ for (i = 0; i < ofproto->n_tables; i++) {
+ struct ofputil_table_stats *s = &stats[i];
+ struct classifier *cls = &ofproto->tables[i].cls;
+
+ s->table_id = i;
+ s->active_count = classifier_count(cls);
+ }
+ } else {
+ stats = NULL;
+ }
+
+ ofproto->ofproto_class->query_tables(ofproto, features, stats);
+
+ for (i = 0; i < ofproto->n_tables; i++) {
+ const struct oftable *table = &ofproto->tables[i];
+ struct ofputil_table_features *f = &features[i];
+
+ if (table->name) {
+ ovs_strzcpy(f->name, table->name, sizeof f->name);
+ }
+
+ if (table->max_flows < f->max_entries) {
+ f->max_entries = table->max_flows;
+ }
+ }
+}
+
+static void
+query_switch_features(struct ofproto *ofproto,
+ bool *arp_match_ip, uint64_t *ofpacts)
+{
+ struct ofputil_table_features *features, *f;
+
+ *arp_match_ip = false;
+ *ofpacts = 0;
+
+ query_tables(ofproto, &features, NULL);
+ for (f = features; f < &features[ofproto->n_tables]; f++) {
+ *ofpacts |= f->nonmiss.apply.ofpacts | f->miss.apply.ofpacts;
+ if (bitmap_is_set(f->match.bm, MFF_ARP_SPA) ||
+ bitmap_is_set(f->match.bm, MFF_ARP_TPA)) {
+ *arp_match_ip = true;
+ }
+ }
+ free(features);
+
+ /* Sanity check. */
+ ovs_assert(*ofpacts & (UINT64_C(1) << OFPACT_OUTPUT));
+}
+
+static enum ofperr
+handle_features_request(struct ofconn *ofconn, const struct ofp_header *oh)
+{
+ struct ofproto *ofproto = ofconn_get_ofproto(ofconn);
+ struct ofputil_switch_features features;
+ struct ofport *port;
+ bool arp_match_ip;
+ struct ofpbuf *b;
+
+ query_switch_features(ofproto, &arp_match_ip, &features.ofpacts);