[ovs-dev] [PATCH v2 05/16] ofp-util: Abstract table miss configuration and fix related bugs.

Ben Pfaff blp at nicira.com
Thu Aug 7 23:13:51 UTC 2014


The ofproto implementation has had an abstraction layer on top of
OFPTC11_TABLE_MISS for a while.  This commit pushes that abstraction layer
farther down, into ofp-util.  This will be more useful in an upcoming
commit.

During the conversion I realized that the previous implementation was
not entirely correct.  In particular, the OpenFlow 1.3+ "table mod" was
still being treated as if it had table miss configuration bits, even
though it doesn't.  This commit fixes that issue and updates the tests.

OpenFlow 1.4 adds some more OFPTC_* flags that this new abstraction doesn't
yet support, but OVS didn't support those flags any better before this
commit, so abstracting those is left as future work.

Signed-off-by: Ben Pfaff <blp at nicira.com>
---
 lib/ofp-parse.c            |    9 +-
 lib/ofp-print.c            |  408 ++++++++++--------------------
 lib/ofp-util.c             |  589 +++++++++++++++++++++++++++++++++-----------
 lib/ofp-util.h             |   67 +++--
 ofproto/connmgr.c          |   13 +-
 ofproto/ofproto-dpif.c     |   43 ++--
 ofproto/ofproto-provider.h |   72 ++----
 ofproto/ofproto.c          |  216 ++++++++++------
 ofproto/ofproto.h          |   20 +-
 tests/ofp-print.at         |  107 +++++---
 tests/ofproto-dpif.at      |   59 +++--
 tests/ofproto.at           |  109 +++++---
 12 files changed, 1011 insertions(+), 701 deletions(-)

diff --git a/lib/ofp-parse.c b/lib/ofp-parse.c
index f0a30cf..2925157 100644
--- a/lib/ofp-parse.c
+++ b/lib/ofp-parse.c
@@ -1865,16 +1865,17 @@ parse_ofp_table_mod(struct ofputil_table_mod *tm, const char *table_id,
     }
 
     if (strcmp(flow_miss_handling, "controller") == 0) {
-        tm->config = OFPTC11_TABLE_MISS_CONTROLLER;
+        tm->miss_config = OFPUTIL_TABLE_MISS_CONTROLLER;
     } else if (strcmp(flow_miss_handling, "continue") == 0) {
-        tm->config = OFPTC11_TABLE_MISS_CONTINUE;
+        tm->miss_config = OFPUTIL_TABLE_MISS_CONTINUE;
     } else if (strcmp(flow_miss_handling, "drop") == 0) {
-        tm->config = OFPTC11_TABLE_MISS_DROP;
+        tm->miss_config = OFPUTIL_TABLE_MISS_DROP;
     } else {
         return xasprintf("invalid flow_miss_handling %s", flow_miss_handling);
     }
 
-    if (tm->table_id == 0xfe && tm->config == OFPTC11_TABLE_MISS_CONTINUE) {
+    if (tm->table_id == 0xfe
+        && tm->miss_config == OFPUTIL_TABLE_MISS_CONTINUE) {
         return xstrdup("last table's flow miss handling can not be continue");
     }
 
diff --git a/lib/ofp-print.c b/lib/ofp-print.c
index 2b0b20f..dbff150 100644
--- a/lib/ofp-print.c
+++ b/lib/ofp-print.c
@@ -51,7 +51,9 @@
 
 static void ofp_print_queue_name(struct ds *string, uint32_t port);
 static void ofp_print_error(struct ds *, enum ofperr);
-
+static void ofp_print_table_features(struct ds *,
+                                     const struct ofputil_table_features *,
+                                     const struct ofputil_table_stats *);
 
 /* Returns a string that represents the contents of the Ethernet frame in the
  * 'len' bytes starting at 'data'.  The caller must free the returned string.*/
@@ -957,22 +959,21 @@ ofp_print_port_mod(struct ds *string, const struct ofp_header *oh)
 }
 
 static void
-ofp_print_table_miss_config(struct ds *string, const uint32_t config)
+ofp_print_table_miss_config(struct ds *string, enum ofputil_table_miss miss)
 {
-    uint32_t table_miss_config = config & OFPTC11_TABLE_MISS_MASK;
-
-    switch (table_miss_config) {
-    case OFPTC11_TABLE_MISS_CONTROLLER:
+    switch (miss) {
+    case OFPUTIL_TABLE_MISS_CONTROLLER:
         ds_put_cstr(string, "controller\n");
         break;
-    case OFPTC11_TABLE_MISS_CONTINUE:
+    case OFPUTIL_TABLE_MISS_CONTINUE:
         ds_put_cstr(string, "continue\n");
         break;
-    case OFPTC11_TABLE_MISS_DROP:
+    case OFPUTIL_TABLE_MISS_DROP:
         ds_put_cstr(string, "drop\n");
         break;
+    case OFPUTIL_TABLE_MISS_DEFAULT:
     default:
-        ds_put_cstr(string, "Unknown\n");
+        ds_put_format(string, "Unknown (%d)\n", miss);
         break;
     }
 }
@@ -995,8 +996,10 @@ ofp_print_table_mod(struct ds *string, const struct ofp_header *oh)
         ds_put_format(string, " table_id=%"PRIu8, pm.table_id);
     }
 
-    ds_put_cstr(string, ", flow_miss_config=");
-    ofp_print_table_miss_config(string, pm.config);
+    if (pm.miss_config != OFPUTIL_TABLE_MISS_DEFAULT) {
+        ds_put_cstr(string, ", flow_miss_config=");
+        ofp_print_table_miss_config(string, pm.miss_config);
+    }
 }
 
 static void
@@ -1573,216 +1576,27 @@ ofp_print_ofpst_port_reply(struct ds *string, const struct ofp_header *oh,
 }
 
 static void
-ofp_print_one_ofpst_table_reply(struct ds *string, enum ofp_version ofp_version,
-                                const char *name, struct ofp12_table_stats *ts)
-{
-    char name_[OFP_MAX_TABLE_NAME_LEN + 1];
-
-    /* ofp13_table_stats is different */
-    if (ofp_version > OFP12_VERSION) {
-        return;
-    }
-
-    ovs_strlcpy(name_, name, sizeof name_);
-
-    ds_put_format(string, "  %d: %-8s: ", ts->table_id, name_);
-    ds_put_format(string, "wild=0x%05"PRIx64", ", ntohll(ts->wildcards));
-    ds_put_format(string, "max=%6"PRIu32", ", ntohl(ts->max_entries));
-    ds_put_format(string, "active=%"PRIu32"\n", ntohl(ts->active_count));
-    ds_put_cstr(string, "               ");
-    ds_put_format(string, "lookup=%"PRIu64", ", ntohll(ts->lookup_count));
-    ds_put_format(string, "matched=%"PRIu64"\n", ntohll(ts->matched_count));
-
-    if (ofp_version < OFP11_VERSION) {
-        return;
-    }
-
-    ds_put_cstr(string, "               ");
-    ds_put_format(string, "match=0x%08"PRIx64", ", ntohll(ts->match));
-    ds_put_format(string, "instructions=0x%08"PRIx32", ",
-                  ntohl(ts->instructions));
-    ds_put_format(string, "config=0x%08"PRIx32"\n", ntohl(ts->config));
-    ds_put_cstr(string, "               ");
-    ds_put_format(string, "write_actions=0x%08"PRIx32", ",
-                  ntohl(ts->write_actions));
-    ds_put_format(string, "apply_actions=0x%08"PRIx32"\n",
-                  ntohl(ts->apply_actions));
-
-    if (ofp_version < OFP12_VERSION) {
-        return;
-    }
-
-    ds_put_cstr(string, "               ");
-    ds_put_format(string, "write_setfields=0x%016"PRIx64"\n",
-                  ntohll(ts->write_setfields));
-    ds_put_cstr(string, "               ");
-    ds_put_format(string, "apply_setfields=0x%016"PRIx64"\n",
-                  ntohll(ts->apply_setfields));
-    ds_put_cstr(string, "               ");
-    ds_put_format(string, "metadata_match=0x%016"PRIx64"\n",
-                  ntohll(ts->metadata_match));
-    ds_put_cstr(string, "               ");
-    ds_put_format(string, "metadata_write=0x%016"PRIx64"\n",
-                  ntohll(ts->metadata_write));
-}
-
-static void
-ofp_print_ofpst_table_reply13(struct ds *string, const struct ofp_header *oh,
-                              int verbosity)
-{
-    struct ofp13_table_stats *ts;
-    struct ofpbuf b;
-    size_t n;
-
-    ofpbuf_use_const(&b, oh, ntohs(oh->length));
-    ofpraw_pull_assert(&b);
-
-    n = ofpbuf_size(&b) / sizeof *ts;
-    ds_put_format(string, " %"PRIuSIZE" tables\n", n);
-    if (verbosity < 1) {
-        return;
-    }
-
-    for (;;) {
-        ts = ofpbuf_try_pull(&b, sizeof *ts);
-        if (!ts) {
-            return;
-        }
-        ds_put_format(string,
-                      "  %d: active=%"PRIu32", lookup=%"PRIu64  \
-                      ", matched=%"PRIu64"\n",
-                      ts->table_id, ntohl(ts->active_count),
-                      ntohll(ts->lookup_count), ntohll(ts->matched_count));
-    }
-}
-
-static void
-ofp_print_ofpst_table_reply12(struct ds *string, const struct ofp_header *oh,
-                              int verbosity)
+ofp_print_table_stats_reply(struct ds *string, const struct ofp_header *oh)
 {
-    struct ofp12_table_stats *ts;
     struct ofpbuf b;
-    size_t n;
 
     ofpbuf_use_const(&b, oh, ntohs(oh->length));
     ofpraw_pull_assert(&b);
 
-    n = ofpbuf_size(&b) / sizeof *ts;
-    ds_put_format(string, " %"PRIuSIZE" tables\n", n);
-    if (verbosity < 1) {
-        return;
-    }
-
     for (;;) {
-        ts = ofpbuf_try_pull(&b, sizeof *ts);
-        if (!ts) {
-            return;
-        }
-
-        ofp_print_one_ofpst_table_reply(string, OFP12_VERSION, ts->name, ts);
-     }
-}
-
-static void
-ofp_print_ofpst_table_reply11(struct ds *string, const struct ofp_header *oh,
-                              int verbosity)
-{
-    struct ofp11_table_stats *ts;
-    struct ofpbuf b;
-    size_t n;
-
-    ofpbuf_use_const(&b, oh, ntohs(oh->length));
-    ofpraw_pull_assert(&b);
-
-    n = ofpbuf_size(&b) / sizeof *ts;
-    ds_put_format(string, " %"PRIuSIZE" tables\n", n);
-    if (verbosity < 1) {
-        return;
-    }
-
-    for (;;) {
-        struct ofp12_table_stats ts12;
-
-        ts = ofpbuf_try_pull(&b, sizeof *ts);
-        if (!ts) {
-            return;
-        }
-
-        ts12.table_id = ts->table_id;
-        ts12.wildcards = htonll(ntohl(ts->wildcards));
-        ts12.max_entries = ts->max_entries;
-        ts12.active_count = ts->active_count;
-        ts12.lookup_count = ts->lookup_count;
-        ts12.matched_count = ts->matched_count;
-        ts12.match = htonll(ntohl(ts->match));
-        ts12.instructions = ts->instructions;
-        ts12.config = ts->config;
-        ts12.write_actions = ts->write_actions;
-        ts12.apply_actions = ts->apply_actions;
-        ofp_print_one_ofpst_table_reply(string, OFP11_VERSION, ts->name, &ts12);
-     }
-}
-
-static void
-ofp_print_ofpst_table_reply10(struct ds *string, const struct ofp_header *oh,
-                              int verbosity)
-{
-    struct ofp10_table_stats *ts;
-    struct ofpbuf b;
-    size_t n;
-
-    ofpbuf_use_const(&b, oh, ntohs(oh->length));
-    ofpraw_pull_assert(&b);
-
-    n = ofpbuf_size(&b) / sizeof *ts;
-    ds_put_format(string, " %"PRIuSIZE" tables\n", n);
-    if (verbosity < 1) {
-        return;
-    }
-
-    for (;;) {
-        struct ofp12_table_stats ts12;
+        struct ofputil_table_features features;
+        struct ofputil_table_stats stats;
+        int retval;
 
-        ts = ofpbuf_try_pull(&b, sizeof *ts);
-        if (!ts) {
+        retval = ofputil_decode_table_stats_reply(&b, &stats, &features);
+        if (retval) {
+            if (retval != EOF) {
+                ofp_print_error(string, retval);
+            }
             return;
         }
 
-        ts12.table_id = ts->table_id;
-        ts12.wildcards = htonll(ntohl(ts->wildcards));
-        ts12.max_entries = ts->max_entries;
-        ts12.active_count = ts->active_count;
-        ts12.lookup_count = get_32aligned_be64(&ts->lookup_count);
-        ts12.matched_count = get_32aligned_be64(&ts->matched_count);
-        ofp_print_one_ofpst_table_reply(string, OFP10_VERSION, ts->name, &ts12);
-     }
-}
-
-static void
-ofp_print_ofpst_table_reply(struct ds *string, const struct ofp_header *oh,
-                            int verbosity)
-{
-    switch ((enum ofp_version)oh->version) {
-    case OFP15_VERSION:
-    case OFP14_VERSION:
-    case OFP13_VERSION:
-        ofp_print_ofpst_table_reply13(string, oh, verbosity);
-        break;
-
-    case OFP12_VERSION:
-        ofp_print_ofpst_table_reply12(string, oh, verbosity);
-        break;
-
-    case OFP11_VERSION:
-        ofp_print_ofpst_table_reply11(string, oh, verbosity);
-        break;
-
-    case OFP10_VERSION:
-        ofp_print_ofpst_table_reply10(string, oh, verbosity);
-        break;
-
-    default:
-        OVS_NOT_REACHED();
+        ofp_print_table_features(string, &features, &stats);
     }
 }
 
@@ -2495,22 +2309,21 @@ static void
 print_table_action_features(struct ds *s,
                             const struct ofputil_table_action_features *taf)
 {
-    ds_put_cstr(s, "        actions: ");
-    ofpact_bitmap_format(taf->ofpacts, s);
-    ds_put_char(s, '\n');
+    if (taf->ofpacts) {
+        ds_put_cstr(s, "        actions: ");
+        ofpact_bitmap_format(taf->ofpacts, s);
+        ds_put_char(s, '\n');
+    }
 
-    ds_put_cstr(s, "        supported on Set-Field: ");
     if (!bitmap_is_all_zeros(taf->set_fields.bm, MFF_N_IDS)) {
         int i;
 
+        ds_put_cstr(s, "        supported on Set-Field:");
         BITMAP_FOR_EACH_1 (i, MFF_N_IDS, taf->set_fields.bm) {
-            ds_put_format(s, "%s,", mf_from_id(i)->name);
+            ds_put_format(s, " %s", mf_from_id(i)->name);
         }
-        ds_chomp(s, ',');
-    } else {
-        ds_put_cstr(s, "none");
+        ds_put_char(s, '\n');
     }
-    ds_put_char(s, '\n');
 }
 
 static bool
@@ -2521,30 +2334,38 @@ table_action_features_equal(const struct ofputil_table_action_features *a,
             && bitmap_equal(a->set_fields.bm, b->set_fields.bm, MFF_N_IDS));
 }
 
+static bool
+table_action_features_empty(const struct ofputil_table_action_features *taf)
+{
+    return !taf->ofpacts && bitmap_is_all_zeros(taf->set_fields.bm, MFF_N_IDS);
+}
+
 static void
 print_table_instruction_features(
     struct ds *s, const struct ofputil_table_instruction_features *tif)
 {
     int start, end;
 
-    ds_put_cstr(s, "      next tables: ");
-    for (start = bitmap_scan(tif->next, 1, 0, 255); start < 255;
-         start = bitmap_scan(tif->next, 1, end, 255)) {
-        end = bitmap_scan(tif->next, 0, start + 1, 255);
-        if (end == start + 1) {
-            ds_put_format(s, "%d,", start);
-        } else {
-            ds_put_format(s, "%d-%d,", start, end - 1);
+    if (!bitmap_is_all_zeros(tif->next, 255)) {
+        ds_put_cstr(s, "      next tables: ");
+        for (start = bitmap_scan(tif->next, 1, 0, 255); start < 255;
+             start = bitmap_scan(tif->next, 1, end, 255)) {
+            end = bitmap_scan(tif->next, 0, start + 1, 255);
+            if (end == start + 1) {
+                ds_put_format(s, "%d,", start);
+            } else {
+                ds_put_format(s, "%d-%d,", start, end - 1);
+            }
         }
+        ds_chomp(s, ',');
+        if (ds_last(s) == ' ') {
+            ds_put_cstr(s, "none");
+        }
+        ds_put_char(s, '\n');
     }
-    ds_chomp(s, ',');
-    if (ds_last(s) == ' ') {
-        ds_put_cstr(s, "none");
-    }
-    ds_put_char(s, '\n');
 
-    ds_put_cstr(s, "      instructions: ");
     if (tif->instructions) {
+        ds_put_cstr(s, "      instructions: ");
         int i;
 
         for (i = 0; i < 32; i++) {
@@ -2553,19 +2374,18 @@ print_table_instruction_features(
             }
         }
         ds_chomp(s, ',');
-    } else {
-        ds_put_cstr(s, "none");
+        ds_put_char(s, '\n');
     }
-    ds_put_char(s, '\n');
 
-    if (table_action_features_equal(&tif->write, &tif->apply)) {
-        ds_put_cstr(s, "      Write-Actions and Apply-Actions features:\n");
-        print_table_action_features(s, &tif->write);
-    } else {
+    if (!table_action_features_equal(&tif->write, &tif->apply)) {
         ds_put_cstr(s, "      Write-Actions features:\n");
         print_table_action_features(s, &tif->write);
         ds_put_cstr(s, "      Apply-Actions features:\n");
         print_table_action_features(s, &tif->apply);
+    } else if (tif->write.ofpacts
+               || !bitmap_is_all_zeros(tif->write.set_fields.bm, MFF_N_IDS)) {
+        ds_put_cstr(s, "      Write-Actions and Apply-Actions features:\n");
+        print_table_action_features(s, &tif->write);
     }
 }
 
@@ -2580,51 +2400,65 @@ table_instruction_features_equal(
             && table_action_features_equal(&a->apply, &b->apply));
 }
 
-static void
-ofp_print_table_features(struct ds *s, const struct ofp_header *oh)
+static bool
+table_instruction_features_empty(
+    const struct ofputil_table_instruction_features *tif)
 {
-    struct ofpbuf b;
-
-    ofpbuf_use_const(&b, oh, ntohs(oh->length));
-
-    for (;;) {
-        struct ofputil_table_features tf;
-        int retval;
-        int i;
+    return (bitmap_is_all_zeros(tif->next, 255)
+            && !tif->instructions
+            && table_action_features_empty(&tif->write)
+            && table_action_features_empty(&tif->apply));
+}
 
-        retval = ofputil_decode_table_features(&b, &tf, true);
-        if (retval) {
-            if (retval != EOF) {
-                ofp_print_error(s, retval);
-            }
-            return;
-        }
+static void
+ofp_print_table_features(struct ds *s,
+                         const struct ofputil_table_features *features,
+                         const struct ofputil_table_stats *stats)
+{
+    int i;
 
-        ds_put_format(s, "\n  table %"PRIu8":\n", tf.table_id);
-        ds_put_format(s, "    name=\"%s\"\n", tf.name);
+    ds_put_format(s, "\n  table %"PRIu8, features->table_id);
+    if (features->name[0]) {
+        ds_put_format(s, " (\"%s\")", features->name);
+    }
+    ds_put_cstr(s, ":\n");
+    if (stats) {
+        ds_put_format(s, "    active=%"PRIu32", ", stats->active_count);
+        ds_put_format(s, "lookup=%"PRIu64", ", stats->lookup_count);
+        ds_put_format(s, "matched=%"PRIu64"\n", stats->matched_count);
+    }
+    if (features->metadata_match || features->metadata_match) {
         ds_put_format(s, "    metadata: match=%#"PRIx64" write=%#"PRIx64"\n",
-                      ntohll(tf.metadata_match), ntohll(tf.metadata_write));
+                      ntohll(features->metadata_match),
+                      ntohll(features->metadata_write));
+    }
 
+    if (features->miss_config != OFPUTIL_TABLE_MISS_DEFAULT) {
         ds_put_cstr(s, "    config=");
-        ofp_print_table_miss_config(s, tf.config);
+        ofp_print_table_miss_config(s, features->miss_config);
+    }
 
-        ds_put_format(s, "    max_entries=%"PRIu32"\n", tf.max_entries);
+    if (features->max_entries) {
+        ds_put_format(s, "    max_entries=%"PRIu32"\n", features->max_entries);
+    }
 
-        if (table_instruction_features_equal(&tf.nonmiss, &tf.miss)) {
-            ds_put_cstr(s, "    instructions (table miss and others):\n");
-            print_table_instruction_features(s, &tf.nonmiss);
-        } else {
-            ds_put_cstr(s, "    instructions (other than table miss):\n");
-            print_table_instruction_features(s, &tf.nonmiss);
-            ds_put_cstr(s, "    instructions (table miss):\n");
-            print_table_instruction_features(s, &tf.miss);
-        }
+    if (!table_instruction_features_equal(&features->nonmiss,
+                                          &features->miss)) {
+        ds_put_cstr(s, "    instructions (other than table miss):\n");
+        print_table_instruction_features(s, &features->nonmiss);
+        ds_put_cstr(s, "    instructions (table miss):\n");
+        print_table_instruction_features(s, &features->miss);
+    } else if (!table_instruction_features_empty(&features->nonmiss)) {
+        ds_put_cstr(s, "    instructions (table miss and others):\n");
+        print_table_instruction_features(s, &features->nonmiss);
+    }
 
+    if (!bitmap_is_all_zeros(features->match.bm, MFF_N_IDS)){
         ds_put_cstr(s, "    matching:\n");
-        BITMAP_FOR_EACH_1 (i, MFF_N_IDS, tf.match.bm) {
+        BITMAP_FOR_EACH_1 (i, MFF_N_IDS, features->match.bm) {
             const struct mf_field *f = mf_from_id(i);
-            bool mask = bitmap_is_set(tf.mask.bm, i);
-            bool wildcard = bitmap_is_set(tf.wildcard.bm, i);
+            bool mask = bitmap_is_set(features->mask.bm, i);
+            bool wildcard = bitmap_is_set(features->wildcard.bm, i);
 
             ds_put_format(s, "      %s: %s\n",
                           f->name,
@@ -2635,6 +2469,28 @@ ofp_print_table_features(struct ds *s, const struct ofp_header *oh)
     }
 }
 
+static void
+ofp_print_table_features_reply(struct ds *s, const struct ofp_header *oh)
+{
+    struct ofpbuf b;
+
+    ofpbuf_use_const(&b, oh, ntohs(oh->length));
+
+    for (;;) {
+        struct ofputil_table_features tf;
+        int retval;
+
+        retval = ofputil_decode_table_features(&b, &tf, true);
+        if (retval) {
+            if (retval != EOF) {
+                ofp_print_error(s, retval);
+            }
+            return;
+        }
+        ofp_print_table_features(s, &tf, NULL);
+    }
+}
+
 static const char *
 bundle_flags_to_name(uint32_t bit)
 {
@@ -2760,7 +2616,7 @@ ofp_to_string__(const struct ofp_header *oh, enum ofpraw raw,
 
     case OFPTYPE_TABLE_FEATURES_STATS_REQUEST:
     case OFPTYPE_TABLE_FEATURES_STATS_REPLY:
-        ofp_print_table_features(string, oh);
+        ofp_print_table_features_reply(string, oh);
         break;
 
     case OFPTYPE_HELLO:
@@ -2911,7 +2767,7 @@ ofp_to_string__(const struct ofp_header *oh, enum ofpraw raw,
 
     case OFPTYPE_TABLE_STATS_REPLY:
         ofp_print_stats(string, oh);
-        ofp_print_ofpst_table_reply(string, oh, verbosity);
+        ofp_print_table_stats_reply(string, oh);
         break;
 
     case OFPTYPE_AGGREGATE_STATS_REPLY:
diff --git a/lib/ofp-util.c b/lib/ofp-util.c
index e643354..7217d46 100644
--- a/lib/ofp-util.c
+++ b/lib/ofp-util.c
@@ -50,6 +50,9 @@ VLOG_DEFINE_THIS_MODULE(ofp_util);
  * in the peer and so there's not much point in showing a lot of them. */
 static struct vlog_rate_limit bad_ofmsg_rl = VLOG_RATE_LIMIT_INIT(1, 5);
 
+static enum ofputil_table_miss ofputil_table_miss_from_config(
+    ovs_be32 config_, enum ofp_version);
+
 struct ofp_prop_header {
     ovs_be16 type;
     ovs_be16 len;
@@ -4606,6 +4609,8 @@ ofputil_decode_table_features(struct ofpbuf *msg,
     struct ofp13_table_features *otf;
     unsigned int len;
 
+    memset(tf, 0, sizeof *tf);
+
     if (!msg->frame) {
         ofpraw_pull_assert(msg);
     }
@@ -4634,7 +4639,7 @@ ofputil_decode_table_features(struct ofpbuf *msg,
     ovs_strlcpy(tf->name, otf->name, OFP_MAX_TABLE_NAME_LEN);
     tf->metadata_match = otf->metadata_match;
     tf->metadata_write = otf->metadata_write;
-    tf->config = ntohl(otf->config);
+    tf->miss_config = ofputil_table_miss_from_config(otf->config, oh->version);
     tf->max_entries = ntohl(otf->max_entries);
 
     while (ofpbuf_size(msg) > 0) {
@@ -4763,6 +4768,60 @@ ofputil_encode_table_features_request(enum ofp_version ofp_version)
 
 /* ofputil_table_mod */
 
+/* Given 'config', taken from an OpenFlow 'version' message that specifies
+ * table configuration (a table mod, table stats, or table features message),
+ * returns the table miss configuration that it specifies.  */
+static enum ofputil_table_miss
+ofputil_table_miss_from_config(ovs_be32 config_, enum ofp_version version)
+{
+    uint32_t config = ntohl(config_);
+
+    if (version < OFP13_VERSION) {
+        switch (config & OFPTC11_TABLE_MISS_MASK) {
+        case OFPTC11_TABLE_MISS_CONTROLLER:
+            return OFPUTIL_TABLE_MISS_CONTROLLER;
+
+        case OFPTC11_TABLE_MISS_CONTINUE:
+            return OFPUTIL_TABLE_MISS_CONTINUE;
+
+        case OFPTC11_TABLE_MISS_DROP:
+            return OFPUTIL_TABLE_MISS_DROP;
+
+        default:
+            VLOG_WARN_RL(&bad_ofmsg_rl, "bad table miss config %d", config);
+            return OFPUTIL_TABLE_MISS_CONTROLLER;
+        }
+    } else {
+        return OFPUTIL_TABLE_MISS_DEFAULT;
+    }
+}
+
+/* Given a table miss configuration, returns the corresponding OpenFlow table
+ * configuration for use in an OpenFlow message of the given 'version'. */
+ovs_be32
+ofputil_table_miss_to_config(enum ofputil_table_miss miss,
+                             enum ofp_version version)
+{
+    if (version < OFP13_VERSION) {
+        switch (miss) {
+        case OFPUTIL_TABLE_MISS_CONTROLLER:
+        case OFPUTIL_TABLE_MISS_DEFAULT:
+            return htonl(OFPTC11_TABLE_MISS_CONTROLLER);
+
+        case OFPUTIL_TABLE_MISS_CONTINUE:
+            return htonl(OFPTC11_TABLE_MISS_CONTINUE);
+
+        case OFPUTIL_TABLE_MISS_DROP:
+            return htonl(OFPTC11_TABLE_MISS_DROP);
+
+        default:
+            OVS_NOT_REACHED();
+        }
+    } else {
+        return htonl(0);
+    }
+}
+
 /* Decodes the OpenFlow "table mod" message in '*oh' into an abstract form in
  * '*pm'.  Returns 0 if successful, otherwise an OFPERR_* value. */
 enum ofperr
@@ -4779,12 +4838,14 @@ ofputil_decode_table_mod(const struct ofp_header *oh,
         const struct ofp11_table_mod *otm = ofpbuf_data(&b);
 
         pm->table_id = otm->table_id;
-        pm->config = ntohl(otm->config);
+        pm->miss_config = ofputil_table_miss_from_config(otm->config,
+                                                         oh->version);
     } else if (raw == OFPRAW_OFPT14_TABLE_MOD) {
         const struct ofp14_table_mod *otm = ofpbuf_pull(&b, sizeof *otm);
 
         pm->table_id = otm->table_id;
-        pm->config = ntohl(otm->config);
+        pm->miss_config = ofputil_table_miss_from_config(otm->config,
+                                                         oh->version);
         /* We do not understand any properties yet, so we do not bother
          * parsing them. */
     } else {
@@ -4818,7 +4879,8 @@ ofputil_encode_table_mod(const struct ofputil_table_mod *pm,
         b = ofpraw_alloc(OFPRAW_OFPT11_TABLE_MOD, ofp_version, 0);
         otm = ofpbuf_put_zeros(b, sizeof *otm);
         otm->table_id = pm->table_id;
-        otm->config = htonl(pm->config);
+        otm->config = ofputil_table_miss_to_config(pm->miss_config,
+                                                   ofp_version);
         break;
     }
     case OFP14_VERSION:
@@ -4828,7 +4890,8 @@ ofputil_encode_table_mod(const struct ofputil_table_mod *pm,
         b = ofpraw_alloc(OFPRAW_OFPT14_TABLE_MOD, ofp_version, 0);
         otm = ofpbuf_put_zeros(b, sizeof *otm);
         otm->table_id = pm->table_id;
-        otm->config = htonl(pm->config);
+        otm->config = ofputil_table_miss_to_config(pm->miss_config,
+                                                   ofp_version);
         break;
     }
     default:
@@ -4991,109 +5054,160 @@ ofputil_decode_role_status(const struct ofp_header *oh,
 
 /* Table stats. */
 
+/* OpenFlow 1.0 and 1.1 don't distinguish between a field that cannot be
+ * matched and a field that must be wildcarded.  This function returns a bitmap
+ * that contains both kinds of fields. */
+static struct mf_bitmap
+wild_or_nonmatchable_fields(const struct ofputil_table_features *features)
+{
+    struct mf_bitmap wc = features->match;
+    bitmap_not(wc.bm, MFF_N_IDS);
+    bitmap_or(wc.bm, features->wildcard.bm, MFF_N_IDS);
+    return wc;
+}
+
+struct ofp10_wc_map {
+    enum ofp10_flow_wildcards wc10;
+    enum mf_field_id mf;
+};
+
+static const struct ofp10_wc_map ofp10_wc_map[] = {
+    { OFPFW10_IN_PORT,     MFF_IN_PORT },
+    { OFPFW10_DL_VLAN,     MFF_VLAN_VID },
+    { OFPFW10_DL_SRC,      MFF_ETH_SRC },
+    { OFPFW10_DL_DST,      MFF_ETH_DST},
+    { OFPFW10_DL_TYPE,     MFF_ETH_TYPE },
+    { OFPFW10_NW_PROTO,    MFF_IP_PROTO },
+    { OFPFW10_TP_SRC,      MFF_TCP_SRC },
+    { OFPFW10_TP_DST,      MFF_TCP_DST },
+    { OFPFW10_NW_SRC_MASK, MFF_IPV4_SRC },
+    { OFPFW10_NW_DST_MASK, MFF_IPV4_DST },
+    { OFPFW10_DL_VLAN_PCP, MFF_VLAN_PCP },
+    { OFPFW10_NW_TOS,      MFF_IP_DSCP },
+};
+
+static ovs_be32
+mf_bitmap_to_of10(const struct mf_bitmap *fields)
+{
+    const struct ofp10_wc_map *p;
+    uint32_t wc10 = 0;
+
+    for (p = ofp10_wc_map; p < &ofp10_wc_map[ARRAY_SIZE(ofp10_wc_map)]; p++) {
+        if (bitmap_is_set(fields->bm, p->mf)) {
+            wc10 |= p->wc10;
+        }
+    }
+    return htonl(wc10);
+}
+
+static struct mf_bitmap
+mf_bitmap_from_of10(ovs_be32 wc10)
+{
+    struct mf_bitmap fields = MF_BITMAP_INITIALIZER;
+    const struct ofp10_wc_map *p;
+
+    for (p = ofp10_wc_map; p < &ofp10_wc_map[ARRAY_SIZE(ofp10_wc_map)]; p++) {
+        if (wc10 & htonl(p->wc10)) {
+            bitmap_set1(fields.bm, p->mf);
+        }
+    }
+    return fields;
+}
+
 static void
-ofputil_put_ofp10_table_stats(const struct ofputil_table_stats *in,
+ofputil_put_ofp10_table_stats(const struct ofputil_table_stats *stats,
+                              const struct ofputil_table_features *features,
                               struct ofpbuf *buf)
 {
-    struct wc_map {
-        enum ofp10_flow_wildcards wc10;
-        enum mf_field_id mf;
-    };
-
-    static const struct wc_map wc_map[] = {
-        { OFPFW10_IN_PORT,     MFF_IN_PORT },
-        { OFPFW10_DL_VLAN,     MFF_VLAN_VID },
-        { OFPFW10_DL_SRC,      MFF_ETH_SRC },
-        { OFPFW10_DL_DST,      MFF_ETH_DST},
-        { OFPFW10_DL_TYPE,     MFF_ETH_TYPE },
-        { OFPFW10_NW_PROTO,    MFF_IP_PROTO },
-        { OFPFW10_TP_SRC,      MFF_TCP_SRC },
-        { OFPFW10_TP_DST,      MFF_TCP_DST },
-        { OFPFW10_NW_SRC_MASK, MFF_IPV4_SRC },
-        { OFPFW10_NW_DST_MASK, MFF_IPV4_DST },
-        { OFPFW10_DL_VLAN_PCP, MFF_VLAN_PCP },
-        { OFPFW10_NW_TOS,      MFF_IP_DSCP },
-    };
-
+    struct mf_bitmap wc = wild_or_nonmatchable_fields(features);
     struct ofp10_table_stats *out;
-    const struct wc_map *p;
 
     out = ofpbuf_put_zeros(buf, sizeof *out);
-    out->table_id = in->table_id;
-    ovs_strlcpy(out->name, in->name, sizeof out->name);
-    out->wildcards = 0;
-    for (p = wc_map; p < &wc_map[ARRAY_SIZE(wc_map)]; p++) {
-        if (bitmap_is_set(in->wildcards.bm, p->mf)) {
-            out->wildcards |= htonl(p->wc10);
+    out->table_id = features->table_id;
+    ovs_strlcpy(out->name, features->name, sizeof out->name);
+    out->wildcards = mf_bitmap_to_of10(&wc);
+    out->max_entries = htonl(features->max_entries);
+    out->active_count = htonl(stats->active_count);
+    put_32aligned_be64(&out->lookup_count, htonll(stats->lookup_count));
+    put_32aligned_be64(&out->matched_count, htonll(stats->matched_count));
+}
+
+struct ofp11_wc_map {
+    enum ofp11_flow_match_fields wc11;
+    enum mf_field_id mf;
+};
+
+static const struct ofp11_wc_map ofp11_wc_map[] = {
+    { OFPFMF11_IN_PORT,     MFF_IN_PORT },
+    { OFPFMF11_DL_VLAN,     MFF_VLAN_VID },
+    { OFPFMF11_DL_VLAN_PCP, MFF_VLAN_PCP },
+    { OFPFMF11_DL_TYPE,     MFF_ETH_TYPE },
+    { OFPFMF11_NW_TOS,      MFF_IP_DSCP },
+    { OFPFMF11_NW_PROTO,    MFF_IP_PROTO },
+    { OFPFMF11_TP_SRC,      MFF_TCP_SRC },
+    { OFPFMF11_TP_DST,      MFF_TCP_DST },
+    { OFPFMF11_MPLS_LABEL,  MFF_MPLS_LABEL },
+    { OFPFMF11_MPLS_TC,     MFF_MPLS_TC },
+    /* I don't know what OFPFMF11_TYPE means. */
+    { OFPFMF11_DL_SRC,      MFF_ETH_SRC },
+    { OFPFMF11_DL_DST,      MFF_ETH_DST },
+    { OFPFMF11_NW_SRC,      MFF_IPV4_SRC },
+    { OFPFMF11_NW_DST,      MFF_IPV4_DST },
+    { OFPFMF11_METADATA,    MFF_METADATA },
+};
+
+static ovs_be32
+mf_bitmap_to_of11(const struct mf_bitmap *fields)
+{
+    const struct ofp11_wc_map *p;
+    uint32_t wc11 = 0;
+
+    for (p = ofp11_wc_map; p < &ofp11_wc_map[ARRAY_SIZE(ofp11_wc_map)]; p++) {
+        if (bitmap_is_set(fields->bm, p->mf)) {
+            wc11 |= p->wc11;
         }
     }
-    out->max_entries = htonl(in->max_entries);
-    out->active_count = htonl(in->active_count);
-    put_32aligned_be64(&out->lookup_count, htonll(in->lookup_count));
-    put_32aligned_be64(&out->matched_count, htonll(in->matched_count));
+    return htonl(wc11);
 }
 
-static ovs_be32
-fields_to_ofp11_flow_match_fields(const struct mf_bitmap *fields)
-{
-    struct map {
-        enum ofp11_flow_match_fields fmf11;
-        enum mf_field_id mf;
-    };
-
-    static const struct map map[] = {
-        { OFPFMF11_IN_PORT,     MFF_IN_PORT },
-        { OFPFMF11_DL_VLAN,     MFF_VLAN_VID },
-        { OFPFMF11_DL_VLAN_PCP, MFF_VLAN_PCP },
-        { OFPFMF11_DL_TYPE,     MFF_ETH_TYPE },
-        { OFPFMF11_NW_TOS,      MFF_IP_DSCP },
-        { OFPFMF11_NW_PROTO,    MFF_IP_PROTO },
-        { OFPFMF11_TP_SRC,      MFF_TCP_SRC },
-        { OFPFMF11_TP_DST,      MFF_TCP_DST },
-        { OFPFMF11_MPLS_LABEL,  MFF_MPLS_LABEL },
-        { OFPFMF11_MPLS_TC,     MFF_MPLS_TC },
-        /* I don't know what OFPFMF11_TYPE means. */
-        { OFPFMF11_DL_SRC,      MFF_ETH_SRC },
-        { OFPFMF11_DL_DST,      MFF_ETH_DST },
-        { OFPFMF11_NW_SRC,      MFF_IPV4_SRC },
-        { OFPFMF11_NW_DST,      MFF_IPV4_DST },
-        { OFPFMF11_METADATA,    MFF_METADATA },
-    };
-
-    const struct map *p;
-    uint32_t fmf11;
-
-    fmf11 = 0;
-    for (p = map; p < &map[ARRAY_SIZE(map)]; p++) {
-        if (bitmap_is_set(fields->bm, p->mf)) {
-            fmf11 |= p->fmf11;
+static struct mf_bitmap
+mf_bitmap_from_of11(ovs_be32 wc11)
+{
+    struct mf_bitmap fields = MF_BITMAP_INITIALIZER;
+    const struct ofp11_wc_map *p;
+
+    for (p = ofp11_wc_map; p < &ofp11_wc_map[ARRAY_SIZE(ofp11_wc_map)]; p++) {
+        if (wc11 & htonl(p->wc11)) {
+            bitmap_set1(fields.bm, p->mf);
         }
     }
-    return htonl(fmf11);
+    return fields;
 }
 
 static void
-ofputil_put_ofp11_table_stats(const struct ofputil_table_stats *in,
+ofputil_put_ofp11_table_stats(const struct ofputil_table_stats *stats,
+                              const struct ofputil_table_features *features,
                               struct ofpbuf *buf)
 {
+    struct mf_bitmap wc = wild_or_nonmatchable_fields(features);
     struct ofp11_table_stats *out;
 
     out = ofpbuf_put_zeros(buf, sizeof *out);
-    out->table_id = in->table_id;
-    ovs_strlcpy(out->name, in->name, sizeof out->name);
-    out->wildcards = fields_to_ofp11_flow_match_fields(&in->wildcards);
-    out->match = fields_to_ofp11_flow_match_fields(&in->match);
-    out->instructions = ovsinst_bitmap_to_openflow(in->ovsinsts,
-                                                   OFP11_VERSION);
-    out->write_actions = ofpact_bitmap_to_openflow(in->write_ofpacts,
-                                                   OFP11_VERSION);
-    out->apply_actions = ofpact_bitmap_to_openflow(in->apply_ofpacts,
-                                                   OFP11_VERSION);
-    out->config = htonl(in->config);
-    out->max_entries = htonl(in->max_entries);
-    out->active_count = htonl(in->active_count);
-    out->lookup_count = htonll(in->lookup_count);
-    out->matched_count = htonll(in->matched_count);
+    out->table_id = features->table_id;
+    ovs_strlcpy(out->name, features->name, sizeof out->name);
+    out->wildcards = mf_bitmap_to_of11(&wc);
+    out->match = mf_bitmap_to_of11(&features->match);
+    out->instructions = ovsinst_bitmap_to_openflow(
+        features->nonmiss.instructions, OFP11_VERSION);
+    out->write_actions = ofpact_bitmap_to_openflow(
+        features->nonmiss.write.ofpacts, OFP11_VERSION);
+    out->apply_actions = ofpact_bitmap_to_openflow(
+        features->nonmiss.apply.ofpacts, OFP11_VERSION);
+    out->config = htonl(features->miss_config);
+    out->max_entries = htonl(features->max_entries);
+    out->active_count = htonl(stats->active_count);
+    out->lookup_count = htonll(stats->lookup_count);
+    out->matched_count = htonll(stats->matched_count);
 }
 
 static ovs_be64
@@ -5115,84 +5229,277 @@ mf_bitmap_to_oxm_bitmap(const struct mf_bitmap *fields,
     return htonll(oxm_bitmap);
 }
 
+static struct mf_bitmap
+mf_bitmap_from_oxm_bitmap(ovs_be64 oxm_bitmap, enum ofp_version version)
+{
+    struct mf_bitmap fields = MF_BITMAP_INITIALIZER;
+
+    for (enum mf_field_id id = 0; id < MFF_N_IDS; id++) {
+        const struct mf_field *f = mf_from_id(id);
+        uint32_t oxm = f->oxm_header;
+        uint32_t vendor = NXM_VENDOR(oxm);
+        int field = NXM_FIELD(oxm);
+
+        if (version >= f->oxm_version
+            && vendor == OFPXMC12_OPENFLOW_BASIC
+            && field < 64
+            && oxm_bitmap & htonll(UINT64_C(1) << field)) {
+            bitmap_set1(fields.bm, id);
+        }
+    }
+    return fields;
+}
+
 static void
-ofputil_put_ofp12_table_stats(const struct ofputil_table_stats *in,
+ofputil_put_ofp12_table_stats(const struct ofputil_table_stats *stats,
+                              const struct ofputil_table_features *features,
                               struct ofpbuf *buf)
 {
     struct ofp12_table_stats *out;
 
     out = ofpbuf_put_zeros(buf, sizeof *out);
-    out->table_id = in->table_id;
-    ovs_strlcpy(out->name, in->name, sizeof out->name);
-    out->match = mf_bitmap_to_oxm_bitmap(&in->match, OFP12_VERSION);
-    out->wildcards = mf_bitmap_to_oxm_bitmap(&in->wildcards, OFP12_VERSION);
-    out->write_actions = ofpact_bitmap_to_openflow(in->write_ofpacts,
-                                                   OFP12_VERSION);
-    out->apply_actions = ofpact_bitmap_to_openflow(in->apply_ofpacts,
-                                                   OFP12_VERSION);
-    out->write_setfields = mf_bitmap_to_oxm_bitmap(&in->write_setfields,
-                                                   OFP12_VERSION);
-    out->apply_setfields = mf_bitmap_to_oxm_bitmap(&in->apply_setfields,
-                                                   OFP12_VERSION);
-    out->metadata_match = in->metadata_match;
-    out->metadata_write = in->metadata_write;
-    out->instructions = ovsinst_bitmap_to_openflow(in->ovsinsts,
-                                                   OFP12_VERSION);
-    out->config = htonl(in->config);
-    out->max_entries = htonl(in->max_entries);
-    out->active_count = htonl(in->active_count);
-    out->lookup_count = htonll(in->lookup_count);
-    out->matched_count = htonll(in->matched_count);
+    out->table_id = features->table_id;
+    ovs_strlcpy(out->name, features->name, sizeof out->name);
+    out->match = mf_bitmap_to_oxm_bitmap(&features->match, OFP12_VERSION);
+    out->wildcards = mf_bitmap_to_oxm_bitmap(&features->wildcard,
+                                             OFP12_VERSION);
+    out->write_actions = ofpact_bitmap_to_openflow(
+        features->nonmiss.write.ofpacts, OFP12_VERSION);
+    out->apply_actions = ofpact_bitmap_to_openflow(
+        features->nonmiss.apply.ofpacts, OFP12_VERSION);
+    out->write_setfields = mf_bitmap_to_oxm_bitmap(
+        &features->nonmiss.write.set_fields, OFP12_VERSION);
+    out->apply_setfields = mf_bitmap_to_oxm_bitmap(
+        &features->nonmiss.apply.set_fields, OFP12_VERSION);
+    out->metadata_match = features->metadata_match;
+    out->metadata_write = features->metadata_write;
+    out->instructions = ovsinst_bitmap_to_openflow(
+        features->nonmiss.instructions, OFP12_VERSION);
+    out->config = ofputil_table_miss_to_config(features->miss_config,
+                                               OFP12_VERSION);
+    out->max_entries = htonl(features->max_entries);
+    out->active_count = htonl(stats->active_count);
+    out->lookup_count = htonll(stats->lookup_count);
+    out->matched_count = htonll(stats->matched_count);
 }
 
 static void
-ofputil_put_ofp13_table_stats(const struct ofputil_table_stats *in,
+ofputil_put_ofp13_table_stats(const struct ofputil_table_stats *stats,
                               struct ofpbuf *buf)
 {
     struct ofp13_table_stats *out;
 
-    out = ofpbuf_put_uninit(buf, sizeof *out);
-    out->table_id = in->table_id;
-    out->active_count = htonl(in->active_count);
-    out->lookup_count = htonll(in->lookup_count);
-    out->matched_count = htonll(in->matched_count);
+    out = ofpbuf_put_zeros(buf, sizeof *out);
+    out->table_id = stats->table_id;
+    out->active_count = htonl(stats->active_count);
+    out->lookup_count = htonll(stats->lookup_count);
+    out->matched_count = htonll(stats->matched_count);
 }
 
 struct ofpbuf *
-ofputil_encode_table_stats_reply(const struct ofputil_table_stats stats[],
-                                 int n, const struct ofp_header *request)
+ofputil_encode_table_stats_reply(const struct ofp_header *request)
 {
-    struct ofpbuf *reply;
-    int i;
+    return ofpraw_alloc_stats_reply(request, 0);
+}
 
-    reply = ofpraw_alloc_stats_reply(request, n * sizeof *stats);
+void
+ofputil_append_table_stats_reply(struct ofpbuf *reply,
+                                 const struct ofputil_table_stats *stats,
+                                 const struct ofputil_table_features *features)
+{
+    struct ofp_header *oh = ofpbuf_l2(reply);
 
-    for (i = 0; i < n; i++) {
-        switch ((enum ofp_version) request->version) {
-        case OFP10_VERSION:
-            ofputil_put_ofp10_table_stats(&stats[i], reply);
-            break;
+    ovs_assert(stats->table_id == features->table_id);
 
-        case OFP11_VERSION:
-            ofputil_put_ofp11_table_stats(&stats[i], reply);
-            break;
+    switch ((enum ofp_version) oh->version) {
+    case OFP10_VERSION:
+        ofputil_put_ofp10_table_stats(stats, features, reply);
+        break;
 
-        case OFP12_VERSION:
-            ofputil_put_ofp12_table_stats(&stats[i], reply);
-            break;
+    case OFP11_VERSION:
+        ofputil_put_ofp11_table_stats(stats, features, reply);
+        break;
 
-        case OFP13_VERSION:
-        case OFP14_VERSION:
-        case OFP15_VERSION:
-            ofputil_put_ofp13_table_stats(&stats[i], reply);
-            break;
+    case OFP12_VERSION:
+        ofputil_put_ofp12_table_stats(stats, features, reply);
+        break;
 
-        default:
-            OVS_NOT_REACHED();
-        }
+    case OFP13_VERSION:
+    case OFP14_VERSION:
+    case OFP15_VERSION:
+        ofputil_put_ofp13_table_stats(stats, reply);
+        break;
+
+    default:
+        OVS_NOT_REACHED();
     }
+}
 
-    return reply;
+static int
+ofputil_decode_ofp10_table_stats(struct ofpbuf *msg,
+                                 struct ofputil_table_stats *stats,
+                                 struct ofputil_table_features *features)
+{
+    struct ofp10_table_stats *ots;
+
+    ots = ofpbuf_try_pull(msg, sizeof *ots);
+    if (!ots) {
+        return OFPERR_OFPBRC_BAD_LEN;
+    }
+
+    features->table_id = ots->table_id;
+    ovs_strlcpy(features->name, ots->name, sizeof features->name);
+    features->max_entries = ntohl(ots->max_entries);
+    features->match = features->wildcard = mf_bitmap_from_of10(ots->wildcards);
+
+    stats->table_id = ots->table_id;
+    stats->active_count = ntohl(ots->active_count);
+    stats->lookup_count = ntohll(get_32aligned_be64(&ots->lookup_count));
+    stats->matched_count = ntohll(get_32aligned_be64(&ots->matched_count));
+
+    return 0;
+}
+
+static int
+ofputil_decode_ofp11_table_stats(struct ofpbuf *msg,
+                                 struct ofputil_table_stats *stats,
+                                 struct ofputil_table_features *features)
+{
+    struct ofp11_table_stats *ots;
+
+    ots = ofpbuf_try_pull(msg, sizeof *ots);
+    if (!ots) {
+        return OFPERR_OFPBRC_BAD_LEN;
+    }
+
+    features->table_id = ots->table_id;
+    ovs_strlcpy(features->name, ots->name, sizeof features->name);
+    features->max_entries = ntohl(ots->max_entries);
+    features->nonmiss.instructions = ovsinst_bitmap_from_openflow(
+        ots->instructions, OFP11_VERSION);
+    features->nonmiss.write.ofpacts = ofpact_bitmap_from_openflow(
+        ots->write_actions, OFP11_VERSION);
+    features->nonmiss.apply.ofpacts = ofpact_bitmap_from_openflow(
+        ots->write_actions, OFP11_VERSION);
+    features->miss = features->nonmiss;
+    features->miss_config = ofputil_table_miss_from_config(ots->config,
+                                                           OFP11_VERSION);
+    features->match = mf_bitmap_from_of11(ots->match);
+    features->wildcard = mf_bitmap_from_of11(ots->wildcards);
+    bitmap_or(features->match.bm, features->wildcard.bm, MFF_N_IDS);
+
+    stats->table_id = ots->table_id;
+    stats->active_count = ntohl(ots->active_count);
+    stats->lookup_count = ntohll(ots->lookup_count);
+    stats->matched_count = ntohll(ots->matched_count);
+
+    return 0;
+}
+
+static int
+ofputil_decode_ofp12_table_stats(struct ofpbuf *msg,
+                                 struct ofputil_table_stats *stats,
+                                 struct ofputil_table_features *features)
+{
+    struct ofp12_table_stats *ots;
+
+    ots = ofpbuf_try_pull(msg, sizeof *ots);
+    if (!ots) {
+        return OFPERR_OFPBRC_BAD_LEN;
+    }
+
+    features->table_id = ots->table_id;
+    ovs_strlcpy(features->name, ots->name, sizeof features->name);
+    features->metadata_match = ots->metadata_match;
+    features->metadata_write = ots->metadata_write;
+    features->miss_config = ofputil_table_miss_from_config(ots->config,
+                                                           OFP12_VERSION);
+    features->max_entries = ntohl(ots->max_entries);
+
+    features->nonmiss.instructions = ovsinst_bitmap_from_openflow(
+        ots->instructions, OFP12_VERSION);
+    features->nonmiss.write.ofpacts = ofpact_bitmap_from_openflow(
+        ots->write_actions, OFP12_VERSION);
+    features->nonmiss.apply.ofpacts = ofpact_bitmap_from_openflow(
+        ots->apply_actions, OFP12_VERSION);
+    features->nonmiss.write.set_fields = mf_bitmap_from_oxm_bitmap(
+        ots->write_setfields, OFP12_VERSION);
+    features->nonmiss.apply.set_fields = mf_bitmap_from_oxm_bitmap(
+        ots->apply_setfields, OFP12_VERSION);
+    features->miss = features->nonmiss;
+
+    features->match = mf_bitmap_from_oxm_bitmap(ots->match, OFP12_VERSION);
+    features->wildcard = mf_bitmap_from_oxm_bitmap(ots->wildcards,
+                                                   OFP12_VERSION);
+    bitmap_or(features->match.bm, features->wildcard.bm, MFF_N_IDS);
+
+    stats->table_id = ots->table_id;
+    stats->active_count = ntohl(ots->active_count);
+    stats->lookup_count = ntohll(ots->lookup_count);
+    stats->matched_count = ntohll(ots->matched_count);
+
+    return 0;
+}
+
+static int
+ofputil_decode_ofp13_table_stats(struct ofpbuf *msg,
+                                 struct ofputil_table_stats *stats,
+                                 struct ofputil_table_features *features)
+{
+    struct ofp13_table_stats *ots;
+
+    ots = ofpbuf_try_pull(msg, sizeof *ots);
+    if (!ots) {
+        return OFPERR_OFPBRC_BAD_LEN;
+    }
+
+    features->table_id = ots->table_id;
+
+    stats->table_id = ots->table_id;
+    stats->active_count = ntohl(ots->active_count);
+    stats->lookup_count = ntohll(ots->lookup_count);
+    stats->matched_count = ntohll(ots->matched_count);
+
+    return 0;
+}
+
+int
+ofputil_decode_table_stats_reply(struct ofpbuf *msg,
+                                 struct ofputil_table_stats *stats,
+                                 struct ofputil_table_features *features)
+{
+    const struct ofp_header *oh;
+
+    if (!msg->frame) {
+        ofpraw_pull_assert(msg);
+    }
+    oh = msg->frame;
+
+    if (!ofpbuf_size(msg)) {
+        return EOF;
+    }
+
+    memset(stats, 0, sizeof *stats);
+    memset(features, 0, sizeof *features);
+
+    switch ((enum ofp_version) oh->version) {
+    case OFP10_VERSION:
+        return ofputil_decode_ofp10_table_stats(msg, stats, features);
+
+    case OFP11_VERSION:
+        return ofputil_decode_ofp11_table_stats(msg, stats, features);
+
+    case OFP12_VERSION:
+        return ofputil_decode_ofp12_table_stats(msg, stats, features);
+
+    case OFP13_VERSION:
+    case OFP14_VERSION:
+    case OFP15_VERSION:
+        return ofputil_decode_ofp13_table_stats(msg, stats, features);
+
+    default:
+        OVS_NOT_REACHED();
+    }
 }
 
 /* ofputil_flow_monitor_request */
diff --git a/lib/ofp-util.h b/lib/ofp-util.h
index 6c7559a..b9d2ae8 100644
--- a/lib/ofp-util.h
+++ b/lib/ofp-util.h
@@ -578,10 +578,38 @@ enum ofperr ofputil_decode_port_mod(const struct ofp_header *,
 struct ofpbuf *ofputil_encode_port_mod(const struct ofputil_port_mod *,
                                        enum ofputil_protocol);
 
+/* Abstract version of OFPTC11_TABLE_MISS_*.
+ *
+ * OpenFlow 1.0 always sends packets that miss to the controller.
+ *
+ * OpenFlow 1.1 and 1.2 can configure table miss behavior via a "table-mod"
+ * that specifies "send to controller", "miss", or "drop".
+ *
+ * OpenFlow 1.3 and later never sends packets that miss to the controller.
+ */
+enum ofputil_table_miss {
+    /* Protocol-specific default behavior.  On OpenFlow 1.0 through 1.2
+     * connections, the packet is sent to the controller, and on OpenFlow 1.3
+     * and later connections, the packet is dropped.
+     *
+     * This is also used as a result of decoding OpenFlow 1.3+ "config" values
+     * in table-mods, to indicate that no table-miss was specified. */
+    OFPUTIL_TABLE_MISS_DEFAULT,    /* Protocol default behavior. */
+
+    /* These constants have the same meanings as those in OpenFlow with the
+     * same names. */
+    OFPUTIL_TABLE_MISS_CONTROLLER, /* Send to controller. */
+    OFPUTIL_TABLE_MISS_CONTINUE,   /* Go to next table, like OF1.0. */
+    OFPUTIL_TABLE_MISS_DROP,       /* Drop the packet. */
+};
+
+ovs_be32 ofputil_table_miss_to_config(enum ofputil_table_miss,
+                                      enum ofp_version);
+
 /* Abstract ofp_table_mod. */
 struct ofputil_table_mod {
     uint8_t table_id;         /* ID of the table, 0xff indicates all tables. */
-    enum ofp_table_config config;
+    enum ofputil_table_miss miss_config;
 };
 
 enum ofperr ofputil_decode_table_mod(const struct ofp_header *,
@@ -596,7 +624,7 @@ struct ofputil_table_features {
     char name[OFP_MAX_TABLE_NAME_LEN];
     ovs_be64 metadata_match;  /* Bits of metadata table can match. */
     ovs_be64 metadata_write;  /* Bits of metadata table can write. */
-    uint32_t config;          /* Bitmap of OFPTC_* values */
+    enum ofputil_table_miss miss_config;
     uint32_t max_entries;     /* Max number of entries supported. */
 
     /* Table features related to instructions.  There are two instances:
@@ -767,31 +795,28 @@ struct ofpbuf *ofputil_encode_role_status(
 
 enum ofperr ofputil_decode_role_status(const struct ofp_header *oh,
                                        struct ofputil_role_status *rs);
-/* Abstract table stats. */
-struct ofputil_table_stats {
-    uint8_t table_id;
-    char name[OFP_MAX_TABLE_NAME_LEN];
-    ovs_be64 metadata_match;    /* Bits of metadata table can match. */
-    ovs_be64 metadata_write;    /* Bits of metadata table can write. */
-    uint32_t config;            /* Bitmap of OFPTC_* values */
-    uint32_t max_entries;       /* Max number of entries supported. */
-
-    struct mf_bitmap match;     /* Fields table can match. */
-    struct mf_bitmap wildcards; /* Fields table can wildcard. */
-    uint64_t write_ofpacts;     /* OFPACT_* supported on Write-Actions. */
-    uint64_t apply_ofpacts;     /* OFPACT_* supported on Apply-Actions. */
-    struct mf_bitmap write_setfields; /* Fields that can be set in W-A. */
-    struct mf_bitmap apply_setfields; /* Fields that can be set in A-A. */
-    uint32_t ovsinsts;          /* Bitmap of OVSINST_* values supported. */
 
+/* Abstract table stats.
+ *
+ * This corresponds to the OpenFlow 1.3 table statistics structure, which only
+ * includes actual statistics.  In earlier versions of OpenFlow, several
+ * members describe table features, so this structure has to be paired with
+ * struct ofputil_table_features to get all information. */
+struct ofputil_table_stats {
+    uint8_t table_id;           /* Identifier of table. */
     uint32_t active_count;      /* Number of active entries. */
     uint64_t lookup_count;      /* Number of packets looked up in table. */
     uint64_t matched_count;     /* Number of packets that hit table. */
 };
 
-struct ofpbuf *ofputil_encode_table_stats_reply(
-    const struct ofputil_table_stats[], int n,
-    const struct ofp_header *request);
+struct ofpbuf *ofputil_encode_table_stats_reply(const struct ofp_header *rq);
+void ofputil_append_table_stats_reply(struct ofpbuf *reply,
+                                      const struct ofputil_table_stats *,
+                                      const struct ofputil_table_features *);
+
+int ofputil_decode_table_stats_reply(struct ofpbuf *reply,
+                                     struct ofputil_table_stats *,
+                                     struct ofputil_table_features *);
 
 /* Queue configuration request. */
 struct ofpbuf *ofputil_encode_queue_get_config_request(enum ofp_version,
diff --git a/ofproto/connmgr.c b/ofproto/connmgr.c
index 89af6b6..0c8cc72 100644
--- a/ofproto/connmgr.c
+++ b/ofproto/connmgr.c
@@ -1480,14 +1480,11 @@ ofconn_wants_packet_in_on_miss(struct ofconn *ofconn,
         enum ofputil_protocol protocol = ofconn_get_protocol(ofconn);
 
         if (protocol != OFPUTIL_P_NONE
-            && ofputil_protocol_to_ofp_version(protocol) >= OFP13_VERSION) {
-            enum ofproto_table_config config;
-
-            config = ofproto_table_get_config(ofconn->connmgr->ofproto,
-                                              pin->up.table_id);
-            if (config == OFPROTO_TABLE_MISS_DEFAULT) {
-                return false;
-            }
+            && ofputil_protocol_to_ofp_version(protocol) >= OFP13_VERSION
+            && (ofproto_table_get_miss_config(ofconn->connmgr->ofproto,
+                                              pin->up.table_id)
+                == OFPUTIL_TABLE_MISS_DEFAULT)) {
+            return false;
         }
     }
     return true;
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index 92d5718..7bf53a4 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -1499,27 +1499,23 @@ flush(struct ofproto *ofproto_)
 }
 
 static void
-get_features(struct ofproto *ofproto_ OVS_UNUSED,
-             bool *arp_match_ip, uint64_t *ofpacts)
+query_tables(struct ofproto *ofproto,
+             struct ofputil_table_features *features,
+             struct ofputil_table_stats *stats)
 {
-    *arp_match_ip = true;
-    *ofpacts = (UINT64_C(1) << N_OFPACTS) - 1;
-}
-
-static void
-get_tables(struct ofproto *ofproto, struct ofputil_table_stats *stats)
-{
-    int i;
+    strcpy(features->name, "classifier");
 
-    strcpy(stats->name, "classifier");
+    if (stats) {
+        int i;
 
-    for (i = 0; i < ofproto->n_tables; i++) {
-        unsigned long missed, matched;
+        for (i = 0; i < ofproto->n_tables; i++) {
+            unsigned long missed, matched;
 
-        atomic_read(&ofproto->tables[i].n_matched, &matched);
-        stats[i].matched_count = matched;
-        atomic_read(&ofproto->tables[i].n_missed, &missed);
-        stats[i].lookup_count = matched + missed;
+            atomic_read(&ofproto->tables[i].n_matched, &matched);
+            stats[i].matched_count = matched;
+            atomic_read(&ofproto->tables[i].n_missed, &missed);
+            stats[i].lookup_count = matched + missed;
+        }
     }
 }
 
@@ -3438,17 +3434,17 @@ rule_dpif_lookup_from_table(struct ofproto_dpif *ofproto,
         } else if (!honor_table_miss) {
             return RULE_DPIF_LOOKUP_VERDICT_CONTROLLER;
         } else {
-            switch (ofproto_table_get_config(&ofproto->up, *table_id)) {
-            case OFPROTO_TABLE_MISS_CONTINUE:
+            switch (ofproto_table_get_miss_config(&ofproto->up, *table_id)) {
+            case OFPUTIL_TABLE_MISS_CONTINUE:
                 break;
 
-            case OFPROTO_TABLE_MISS_CONTROLLER:
+            case OFPUTIL_TABLE_MISS_CONTROLLER:
                 return RULE_DPIF_LOOKUP_VERDICT_CONTROLLER;
 
-            case OFPROTO_TABLE_MISS_DROP:
+            case OFPUTIL_TABLE_MISS_DROP:
                 return RULE_DPIF_LOOKUP_VERDICT_DROP;
 
-            case OFPROTO_TABLE_MISS_DEFAULT:
+            case OFPUTIL_TABLE_MISS_DEFAULT:
                 return RULE_DPIF_LOOKUP_VERDICT_DEFAULT;
             }
         }
@@ -5054,8 +5050,7 @@ const struct ofproto_class ofproto_dpif_class = {
     NULL,                       /* get_memory_usage. */
     type_get_memory_usage,
     flush,
-    get_features,
-    get_tables,
+    query_tables,
     port_alloc,
     port_construct,
     port_destruct,
diff --git a/ofproto/ofproto-provider.h b/ofproto/ofproto-provider.h
index 4691d87..d490679 100644
--- a/ofproto/ofproto-provider.h
+++ b/ofproto/ofproto-provider.h
@@ -241,8 +241,8 @@ struct oftable {
     struct hmap eviction_groups_by_id;
     struct heap eviction_groups_by_size;
 
-    /* Table config: contains enum ofproto_table_config; accessed atomically. */
-    atomic_uint config;
+    /* Table configuration. */
+    ATOMIC(enum ofputil_table_miss) miss_config;
 
     atomic_ulong n_matched;
     atomic_ulong n_missed;
@@ -768,70 +768,49 @@ struct ofproto_class {
      * than to do it one by one. */
     void (*flush)(struct ofproto *ofproto);
 
-    /* Helper for the OpenFlow OFPT_FEATURES_REQUEST request.
+    /* Helper for the OpenFlow OFPT_TABLE_FEATURES request.
      *
-     * The implementation should store true in '*arp_match_ip' if the switch
-     * supports matching IP addresses inside ARP requests and replies, false
-     * otherwise.
-     *
-     * The implementation should store in '*ofpacts' a bitmap of the supported
-     * OFPACT_* actions. */
-    void (*get_features)(struct ofproto *ofproto,
-                         bool *arp_match_ip,
-                         uint64_t *ofpacts);
-
-    /* Helper for the OpenFlow OFPST_TABLE statistics request.
-     *
-     * The 'stats' array contains 'ofproto->n_tables' elements.  Each element is
-     * initialized as:
+     * The 'features' array contains 'ofproto->n_tables' elements.  Each
+     * element is initialized as:
      *
      *   - 'table_id' to the array index.
      *
      *   - 'name' to "table#" where # is the table ID.
      *
-     *   - 'match' and 'wildcards' to all fields.
-     *
-     *   - 'write_actions' and 'apply_actions' to all actions.
-     *
-     *   - 'write_setfields' and 'apply_setfields' to all writable fields.
-     *
      *   - 'metadata_match' and 'metadata_write' to OVS_BE64_MAX.
      *
-     *   - 'ovsinsts' to all instructions.
-     *
      *   - 'config' to the table miss configuration.
      *
      *   - 'max_entries' to 1,000,000.
      *
-     *   - 'active_count' to the classifier_count() for the table.
+     *   - Both 'nonmiss' and 'miss' to:
      *
-     *   - 'lookup_count' and 'matched_count' to 0.
+     *     * 'next' to all 1-bits for all later tables.
      *
-     * The implementation should update any members in each element for which
-     * it has better values:
+     *     * 'instructions' to all instructions.
      *
-     *   - 'name' to a more meaningful name.
+     *     * 'write' and 'apply' both to:
      *
-     *   - 'wildcards' to the set of wildcards actually supported by the table
-     *     (if it doesn't support all OpenFlow wildcards).
+     *       - 'ofpacts': All actions.
      *
-     *   - 'ovsinsts' to the set of instructions actually supported by the
-     *     table.
+     *       - 'set_fields': All fields.
      *
-     *   - 'write_actions' to set the write actions actually supported by
-     *     the table (if it doesn't support all OpenFlow actions).
+     *   - 'match', 'mask', and 'wildcard' to all fields.
      *
-     *   - 'apply_actions' to set the apply actions actually supported by
-     *     the table (if it doesn't support all OpenFlow actions).
+     * If 'stats' is nonnull, it also contains 'ofproto->n_tables' elements.
+     * Each element is initialized as:
      *
-     *   - 'write_setfields' to set the write setfields actually supported by
-     *     the table.
+     *   - 'table_id' to the array index.
+     *
+     *   - 'active_count' to the classifier_count() for the table.
      *
-     *   - 'apply_setfields' to set the apply setfields actually supported by
-     *     the table.
+     *   - 'lookup_count' and 'matched_count' to 0.
+     *
+     * The implementation should update any members in each element for which
+     * it has better values:
      *
-     *   - 'max_entries' to the maximum number of flows actually supported by
-     *     the hardware.
+     *   - Any member of 'features' to better describe the implementation's
+     *     capabilities.
      *
      *   - 'lookup_count' to the number of packets looked up in this flow table
      *     so far.
@@ -839,8 +818,9 @@ struct ofproto_class {
      *   - 'matched_count' to the number of packets looked up in this flow
      *     table so far that matched one of the flow entries.
      */
-    void (*get_tables)(struct ofproto *ofproto,
-                       struct ofputil_table_stats *stats);
+    void (*query_tables)(struct ofproto *ofproto,
+                         struct ofputil_table_features *features,
+                         struct ofputil_table_stats *stats);
 
 /* ## ---------------- ## */
 /* ## ofport Functions ## */
diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
index 1c9c412..6bcec1e 100644
--- a/ofproto/ofproto.c
+++ b/ofproto/ofproto.c
@@ -2777,6 +2777,112 @@ handle_echo_request(struct ofconn *ofconn, const struct ofp_header *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 = MF_BITMAP_INITIALIZER;
+    struct mf_bitmap match = MF_BITMAP_INITIALIZER;
+    struct mf_bitmap mask = MF_BITMAP_INITIALIZER;
+
+    struct ofputil_table_features *features;
+    struct ofputil_table_stats *stats;
+    int i;
+
+    for (i = 0; i < MFF_N_IDS; i++) {
+        const struct mf_field *mf = mf_from_id(i);
+
+        if (mf->writable) {
+            bitmap_set1(rw_fields.bm, i);
+        }
+        if (mf->oxm_header || mf->nxm_header) {
+            bitmap_set1(match.bm, i);
+            if (mf->maskable == MFM_FULLY) {
+                bitmap_set1(mask.bm, 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(&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)
 {
@@ -2786,9 +2892,7 @@ handle_features_request(struct ofconn *ofconn, const struct ofp_header *oh)
     bool arp_match_ip;
     struct ofpbuf *b;
 
-    ofproto->ofproto_class->get_features(ofproto, &arp_match_ip,
-                                         &features.ofpacts);
-    ovs_assert(features.ofpacts & (UINT64_C(1) << OFPACT_OUTPUT));
+    query_switch_features(ofproto, &arp_match_ip, &features.ofpacts);
 
     features.datapath_id = ofproto->datapath_id;
     features.n_buffers = pktbuf_capacity();
@@ -3063,70 +3167,23 @@ static enum ofperr
 handle_table_stats_request(struct ofconn *ofconn,
                            const struct ofp_header *request)
 {
-    struct mf_bitmap rw_fields = MF_BITMAP_INITIALIZER;
-    struct ofproto *p = ofconn_get_ofproto(ofconn);
+    struct ofproto *ofproto = ofconn_get_ofproto(ofconn);
+    struct ofputil_table_features *features;
     struct ofputil_table_stats *stats;
-    struct ofpbuf *msg;
-    int n_tables;
+    struct ofpbuf *reply;
     size_t i;
 
-    for (i = 0; i < MFF_N_IDS; i++) {
-        if (mf_from_id(i)->writable) {
-            bitmap_set1(rw_fields.bm, i);
-        }
-    }
-
-    /* Set up default values.
-     *
-     * ofp12_table_stats is used as a generic structure as
-     * it is able to hold all the fields for ofp10_table_stats
-     * and ofp11_table_stats (and of course itself).
-     */
-    stats = xcalloc(p->n_tables, sizeof *stats);
-    for (i = 0; i < p->n_tables; i++) {
-        unsigned int config;
-
-        stats[i].table_id = i;
-        sprintf(stats[i].name, "table%"PRIuSIZE, i);
-        bitmap_set_multiple(stats[i].match.bm, 0, MFF_N_IDS, 1);
-        bitmap_set_multiple(stats[i].wildcards.bm, 0, MFF_N_IDS, 1);
-        stats[i].write_ofpacts = (UINT64_C(1) << N_OFPACTS) - 1;
-        stats[i].apply_ofpacts  = (UINT64_C(1) << N_OFPACTS) - 1;
-        stats[i].write_setfields = rw_fields;
-        stats[i].apply_setfields = rw_fields;
-        stats[i].metadata_match = OVS_BE64_MAX;
-        stats[i].metadata_write = OVS_BE64_MAX;
-        stats[i].ovsinsts = (1u << N_OVS_INSTRUCTIONS) - 1;
-        atomic_read(&p->tables[i].config, &config);
-        stats[i].config = config;
-        stats[i].max_entries = 1000000; /* An arbitrary big number. */
-        stats[i].active_count = classifier_count(&p->tables[i].cls);
-    }
-
-    p->ofproto_class->get_tables(p, stats);
-
-    /* Post-process the tables, dropping hidden tables. */
-    n_tables = p->n_tables;
-    for (i = 0; i < p->n_tables; i++) {
-        const struct oftable *table = &p->tables[i];
+    query_tables(ofproto, &features, &stats);
 
-        if (table->flags & OFTABLE_HIDDEN) {
-            n_tables = i;
-            break;
-        }
-
-        if (table->name) {
-            ovs_strzcpy(stats[i].name, table->name, sizeof stats[i].name);
-        }
-
-        if (table->max_flows < stats[i].max_entries) {
-            stats[i].max_entries = table->max_flows;
+    reply = ofputil_encode_table_stats_reply(request);
+    for (i = 0; i < ofproto->n_tables; i++) {
+        if (!(ofproto->tables[i].flags & OFTABLE_HIDDEN)) {
+            ofputil_append_table_stats_reply(reply, &stats[i], &features[i]);
         }
     }
+    ofconn_send_reply(ofconn, reply);
 
-    msg = ofputil_encode_table_stats_reply(stats, n_tables, request);
-    ofconn_send_reply(ofconn, msg);
-
+    free(features);
     free(stats);
 
     return 0;
@@ -5725,35 +5782,30 @@ handle_group_mod(struct ofconn *ofconn, const struct ofp_header *oh)
     }
 }
 
-enum ofproto_table_config
-ofproto_table_get_config(const struct ofproto *ofproto, uint8_t table_id)
+enum ofputil_table_miss
+ofproto_table_get_miss_config(const struct ofproto *ofproto, uint8_t table_id)
 {
-    unsigned int value;
-    atomic_read(&ofproto->tables[table_id].config, &value);
-    return (enum ofproto_table_config)value;
+    enum ofputil_table_miss value;
+    atomic_read(&ofproto->tables[table_id].miss_config, &value);
+    return value;
 }
 
 static enum ofperr
 table_mod(struct ofproto *ofproto, const struct ofputil_table_mod *tm)
 {
-    /* Only accept currently supported configurations */
-    if (tm->config & ~OFPTC11_TABLE_MISS_MASK) {
-        return OFPERR_OFPTMFC_BAD_CONFIG;
-    }
-
-    if (tm->table_id == OFPTT_ALL) {
-        int i;
-        for (i = 0; i < ofproto->n_tables; i++) {
-            atomic_store(&ofproto->tables[i].config,
-                         (unsigned int)tm->config);
-        }
-    } else if (!check_table_id(ofproto, tm->table_id)) {
+    if (!check_table_id(ofproto, tm->table_id)) {
         return OFPERR_OFPTMFC_BAD_TABLE;
-    } else {
-        atomic_store(&ofproto->tables[tm->table_id].config,
-                     (unsigned int)tm->config);
+    } else if (tm->miss_config != OFPUTIL_TABLE_MISS_DEFAULT) {
+        if (tm->table_id == OFPTT_ALL) {
+            int i;
+            for (i = 0; i < ofproto->n_tables; i++) {
+                atomic_store(&ofproto->tables[i].miss_config, tm->miss_config);
+            }
+        } else {
+            atomic_store(&ofproto->tables[tm->table_id].miss_config,
+                         tm->miss_config);
+        }
     }
-
     return 0;
 }
 
@@ -6341,7 +6393,7 @@ oftable_init(struct oftable *table)
     memset(table, 0, sizeof *table);
     classifier_init(&table->cls, flow_segment_u32s);
     table->max_flows = UINT_MAX;
-    atomic_init(&table->config, (unsigned int)OFPROTO_TABLE_MISS_DEFAULT);
+    atomic_init(&table->miss_config, OFPUTIL_TABLE_MISS_DEFAULT);
 
     classifier_set_prefix_fields(&table->cls, default_prefix_fields,
                                  ARRAY_SIZE(default_prefix_fields));
diff --git a/ofproto/ofproto.h b/ofproto/ofproto.h
index c71662e..97617a3 100644
--- a/ofproto/ofproto.h
+++ b/ofproto/ofproto.h
@@ -432,24 +432,8 @@ int ofproto_port_set_realdev(struct ofproto *, ofp_port_t vlandev_ofp_port,
 
 /* Table configuration */
 
-enum ofproto_table_config {
-    /* Send to controller. */
-    OFPROTO_TABLE_MISS_CONTROLLER = OFPTC11_TABLE_MISS_CONTROLLER,
-
-    /* Continue to the next table in the pipeline (OpenFlow 1.0 behavior). */
-    OFPROTO_TABLE_MISS_CONTINUE   = OFPTC11_TABLE_MISS_CONTINUE,
-
-    /* Drop the packet. */
-    OFPROTO_TABLE_MISS_DROP       = OFPTC11_TABLE_MISS_DROP,
-
-    /* The default miss behaviour for the OpenFlow version of the controller a
-     * packet_in message would be sent to..  For pre-OF1.3 controllers, send
-     * packet_in to controller.  For OF1.3+ controllers, drop. */
-    OFPROTO_TABLE_MISS_DEFAULT    = 3,
-};
-
-enum ofproto_table_config ofproto_table_get_config(const struct ofproto *,
-                                                   uint8_t table_id);
+enum ofputil_table_miss ofproto_table_get_miss_config(const struct ofproto *,
+                                                      uint8_t table_id);
 
 #ifdef  __cplusplus
 }
diff --git a/tests/ofp-print.at b/tests/ofp-print.at
index 3e35baa..ca3951a 100644
--- a/tests/ofp-print.at
+++ b/tests/ofp-print.at
@@ -1057,13 +1057,12 @@ OFPT_TABLE_MOD (OF1.2) (xid=0x2): table_id=2, flow_miss_config=continue
 ])
 AT_CLEANUP
 
-# ofp_table_mod.config is actually "reserved for future use" in OF1.3.
 AT_SETUP([OFPT_TABLE_MOD - OF1.3])
 AT_KEYWORDS([ofp-print])
 AT_CHECK([ovs-ofctl ofp-print "\
 04 11 00 10 00 00 00 02 02 00 00 00 00 00 00 00 \
 " 3], [0], [dnl
-OFPT_TABLE_MOD (OF1.3) (xid=0x2): table_id=2, flow_miss_config=controller
+OFPT_TABLE_MOD (OF1.3) (xid=0x2): table_id=2
 ])
 AT_CLEANUP
 
@@ -1072,7 +1071,7 @@ AT_KEYWORDS([ofp-print])
 AT_CHECK([ovs-ofctl ofp-print "\
 05 11 00 10 00 00 00 02 02 00 00 00 00 00 00 00 \
 " 3], [0], [dnl
-OFPT_TABLE_MOD (OF1.4) (xid=0x2): table_id=2, flow_miss_config=controller
+OFPT_TABLE_MOD (OF1.4) (xid=0x2): table_id=2
 ])
 AT_CLEANUP
 
@@ -1384,34 +1383,85 @@ AT_CHECK([ovs-ofctl ofp-print "\
 00 3f ff ff 00 10 00 00 00 00 00 0b 00 00 00 00 \
 00 00 00 00 00 00 00 00 00 00 00 00 \
 "], [0], [dnl
-OFPST_TABLE reply (xid=0x1): 1 tables
-  0: classifier: wild=0x3fffff, max=1048576, active=11
-               lookup=0, matched=0
+OFPST_TABLE reply (xid=0x1):
+  table 0 ("classifier"):
+    active=11, lookup=0, matched=0
+    max_entries=1048576
+    matching:
+      in_port: exact match or wildcard
+      eth_src: exact match or wildcard
+      eth_dst: exact match or wildcard
+      eth_type: exact match or wildcard
+      vlan_vid: exact match or wildcard
+      vlan_pcp: exact match or wildcard
+      ip_src: exact match or wildcard
+      ip_dst: exact match or wildcard
+      nw_proto: exact match or wildcard
+      nw_tos: exact match or wildcard
+      tcp_src: exact match or wildcard
+      tcp_dst: exact match or wildcard
 ])
 AT_CLEANUP
 
 AT_SETUP([OFPST_TABLE reply - OF1.2])
 AT_KEYWORDS([ofp-print OFPT_STATS_REPLY])
-(mid="wild=0xfffffffff, max=1000000,"
- tail="
-               match=0xfffffffff, instructions=0x00000007, config=0x00000000
-               write_actions=0x00000000, apply_actions=0x00000000
-               write_setfields=0x0000000fffffffff
-               apply_setfields=0x0000000fffffffff
-               metadata_match=0x0000000000000000
-               metadata_write=0x0000000000000000"
- echo "OFPST_TABLE reply (OF1.2) (xid=0x2): 255 tables
-  0: classifier: $mid active=1
-               lookup=74614, matched=106024$tail"
+(tail="
+    config=controller
+    max_entries=1000000
+    instructions (table miss and others):
+      instructions: write_metadata,goto_table
+      Write-Actions and Apply-Actions features:
+        supported on Set-Field: metadata in_port_oxm eth_src eth_dst eth_type vlan_vid vlan_pcp mpls_label mpls_tc ip_src ip_dst ipv6_src ipv6_dst ipv6_label nw_proto ip_dscp nw_ecn arp_op arp_spa arp_tpa arp_sha arp_tha tcp_src tcp_dst udp_src udp_dst sctp_src sctp_dst icmp_type icmp_code icmpv6_type icmpv6_code nd_target nd_sll nd_tll
+    matching:
+      metadata: exact match or wildcard
+      in_port_oxm: exact match or wildcard
+      eth_src: exact match or wildcard
+      eth_dst: exact match or wildcard
+      eth_type: exact match or wildcard
+      vlan_vid: exact match or wildcard
+      vlan_pcp: exact match or wildcard
+      mpls_label: exact match or wildcard
+      mpls_tc: exact match or wildcard
+      ip_src: exact match or wildcard
+      ip_dst: exact match or wildcard
+      ipv6_src: exact match or wildcard
+      ipv6_dst: exact match or wildcard
+      ipv6_label: exact match or wildcard
+      nw_proto: exact match or wildcard
+      ip_dscp: exact match or wildcard
+      nw_ecn: exact match or wildcard
+      arp_op: exact match or wildcard
+      arp_spa: exact match or wildcard
+      arp_tpa: exact match or wildcard
+      arp_sha: exact match or wildcard
+      arp_tha: exact match or wildcard
+      tcp_src: exact match or wildcard
+      tcp_dst: exact match or wildcard
+      udp_src: exact match or wildcard
+      udp_dst: exact match or wildcard
+      sctp_src: exact match or wildcard
+      sctp_dst: exact match or wildcard
+      icmp_type: exact match or wildcard
+      icmp_code: exact match or wildcard
+      icmpv6_type: exact match or wildcard
+      icmpv6_code: exact match or wildcard
+      nd_target: exact match or wildcard
+      nd_sll: exact match or wildcard
+      nd_tll: exact match or wildcard"
+ echo "OFPST_TABLE reply (OF1.2) (xid=0x2):
+  table 0 (\"classifier\"):
+    active=1, lookup=74614, matched=106024$tail"
  x=1
  while test $x -lt 254; do
-   printf "  %d: %-8s: $mid active=0
-               lookup=0, matched=0$tail
+   printf "
+  table %d (\"%s\"):
+    active=0, lookup=0, matched=0$tail
 " $x table$x
    x=`expr $x + 1`
  done
- echo "  254: table254: $mid active=2
-               lookup=0, matched=0$tail") > expout
+ echo "
+  table 254 (\"table254\"):
+    active=2, lookup=0, matched=0$tail") > expout
 
 (pad32="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
  pad7="00 00 00 00 00 00 00 "
@@ -1453,9 +1503,12 @@ AT_CHECK([ovs-ofctl ofp-print "\
 00 00 00 00 00 00 01 00 01 00 00 00 00 00 00 0c \
 00 00 00 00 00 00 02 01 00 00 00 00 00 00 01 01 \
 "], [0], [dnl
-OFPST_TABLE reply (OF1.3) (xid=0x1): 2 tables
-  0: active=11, lookup=512, matched=256
-  1: active=12, lookup=513, matched=257
+OFPST_TABLE reply (OF1.3) (xid=0x1):
+  table 0:
+    active=11, lookup=512, matched=256
+
+  table 1:
+    active=12, lookup=513, matched=257
 ])
 AT_CLEANUP
 
@@ -2270,17 +2323,15 @@ f5 f6 f7 f8 f9 fa fb fc fd 00 00 00 00 00 00 00 \
 80 00 30 06 80 00 32 06 80 00 1a 02 80 00 1c 02 \
 80 00 1e 02 80 00 20 02 80 00 22 02 80 00 24 02 \
 "], [0], [OFPST_TABLE_FEATURES reply (OF1.3) (xid=0xd5):
-  table 0:
-    name="table0"
+  table 0 ("table0"):
     metadata: match=0xffffffffffffffff write=0xffffffffffffffff
-    config=Unknown
     max_entries=1000000
     instructions (table miss and others):
       next tables: 1-253
       instructions: apply_actions,clear_actions,write_actions,write_metadata,goto_table
       Write-Actions and Apply-Actions features:
         actions: output group set_field strip_vlan push_vlan mod_nw_ttl dec_ttl set_mpls_ttl dec_mpls_ttl push_mpls pop_mpls set_queue
-        supported on Set-Field: tun_id,tun_src,tun_dst,metadata,in_port,in_port_oxm,pkt_mark,reg0,reg1,reg2,reg3,reg4,reg5,reg6,reg7,eth_src,eth_dst,vlan_tci,vlan_vid,vlan_pcp,mpls_label,mpls_tc,ip_src,ip_dst,ipv6_src,ipv6_dst,nw_tos,ip_dscp,nw_ecn,nw_ttl,arp_op,arp_spa,arp_tpa,arp_sha,arp_tha,tcp_src,tcp_dst,udp_src,udp_dst,sctp_src,sctp_dst
+        supported on Set-Field: tun_id tun_src tun_dst metadata in_port in_port_oxm pkt_mark reg0 reg1 reg2 reg3 reg4 reg5 reg6 reg7 eth_src eth_dst vlan_tci vlan_vid vlan_pcp mpls_label mpls_tc ip_src ip_dst ipv6_src ipv6_dst nw_tos ip_dscp nw_ecn nw_ttl arp_op arp_spa arp_tpa arp_sha arp_tha tcp_src tcp_dst udp_src udp_dst sctp_src sctp_dst
     matching:
       tun_id: exact match or wildcard
       tun_src: exact match or wildcard
diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
index 0253cb0..7d0b466 100644
--- a/tests/ofproto-dpif.at
+++ b/tests/ofproto-dpif.at
@@ -5548,11 +5548,14 @@ AT_CHECK([ovs-ofctl dump-flows br0 | ofctl_strip | sort], [0], [dnl
 NXST_FLOW reply:
 ])
 
-(echo "OFPST_TABLE reply (OF1.3) (xid=0x2): 254 tables"
- echo "  0: active=1, lookup=0, matched=0"
+(echo "OFPST_TABLE reply (OF1.3) (xid=0x2):
+  table 0:
+    active=1, lookup=0, matched=0"
  x=1
  while test $x -lt 254; do
-   echo "  $x: active=0, lookup=0, matched=0"
+   echo "
+  table $x:
+    active=0, lookup=0, matched=0"
    x=`expr $x + 1`
  done) > expout
 AT_CHECK([ovs-ofctl -O OpenFlow13 dump-tables br0 ], [0], [expout])
@@ -5560,7 +5563,6 @@ AT_CHECK([ovs-ofctl -O OpenFlow13 dump-tables br0 ], [0], [expout])
 OVS_VSWITCHD_STOP
 AT_CLEANUP
 
-
 AT_SETUP([ofproto-dpif packet-out controller (patch port)])
 OVS_VSWITCHD_START(
   [-- \
@@ -5592,19 +5594,24 @@ NXT_PACKET_IN (xid=0x0): cookie=0x0 total_len=14 in_port=1 (via no_match) data_l
 metadata=0,in_port=0,vlan_tci=0x0000,dl_src=50:54:00:00:00:09,dl_dst=50:54:00:00:00:0a,dl_type=0x1234
 ])
 
-(echo "OFPST_TABLE reply (OF1.3) (xid=0x2): 254 tables"
+(printf "OFPST_TABLE reply (OF1.3) (xid=0x2):"
  x=0
  while test $x -lt 254; do
-   echo "  $x: active=0, lookup=0, matched=0"
+   echo "
+  table $x:
+    active=0, lookup=0, matched=0"
    x=`expr $x + 1`
  done) > expout
 AT_CHECK([ovs-ofctl -O OpenFlow13 dump-tables br0 ], [0], [expout])
 
-(echo "OFPST_TABLE reply (OF1.3) (xid=0x2): 254 tables"
- echo "  0: active=0, lookup=3, matched=0"
+(echo "OFPST_TABLE reply (OF1.3) (xid=0x2):
+  table 0:
+    active=0, lookup=3, matched=0"
  x=1
  while test $x -lt 254; do
-   echo "  $x: active=0, lookup=0, matched=0"
+   echo "
+  table $x:
+    active=0, lookup=0, matched=0"
    x=`expr $x + 1`
  done) > expout
 AT_CHECK([ovs-ofctl -O OpenFlow13 dump-tables br1 ], [0], [expout])
@@ -5651,12 +5658,17 @@ AT_CHECK([ovs-ofctl -O OpenFlow13 dump-flows br0 | ofctl_strip | sort], [0], [dn
 OFPST_FLOW reply (OF1.3):
 ])
 
-(echo "OFPST_TABLE reply (OF1.3) (xid=0x2): 254 tables"
- echo "  0: active=1, lookup=3, matched=3"
- echo "  1: active=1, lookup=3, matched=3"
+(echo "OFPST_TABLE reply (OF1.3) (xid=0x2):
+  table 0:
+    active=1, lookup=3, matched=3
+
+  table 1:
+    active=1, lookup=3, matched=3"
  x=2
  while test $x -lt 254; do
-   echo "  $x: active=0, lookup=0, matched=0"
+   echo "
+  table $x:
+    active=0, lookup=0, matched=0"
    x=`expr $x + 1`
  done) > expout
 AT_CHECK([ovs-ofctl -O OpenFlow13 dump-tables br0 ], [0], [expout])
@@ -5669,8 +5681,8 @@ AT_SETUP([ofproto-dpif packet-out table-miss (continue)])
 OVS_VSWITCHD_START
 ADD_OF_PORTS([br0], 1, 2)
 
-AT_CHECK([ovs-ofctl -O OpenFlow13 add-flow br0 'table=1 dl_dst=50:54:00:00:00:0a actions=controller'])
-AT_CHECK([ovs-ofctl -O OpenFlow13 mod-table br0 all continue])
+AT_CHECK([ovs-ofctl -O OpenFlow11 add-flow br0 'table=1 dl_dst=50:54:00:00:00:0a actions=controller'])
+AT_CHECK([ovs-ofctl -O OpenFlow11 mod-table br0 all continue])
 
 AT_CAPTURE_FILE([ofctl_monitor.log])
 AT_CHECK([ovs-ofctl monitor br0 65534 invalid_ttl --detach --no-chdir --pidfile 2> ofctl_monitor.log])
@@ -5694,17 +5706,22 @@ metadata=0,in_port=0,vlan_tci=0x0000,dl_src=50:54:00:00:00:09,dl_dst=50:54:00:00
 
 AT_CHECK([ovs-appctl time/warp 5000], [0], [ignore])
 
-AT_CHECK([ovs-ofctl -O OpenFlow13 dump-flows br0 | ofctl_strip | sort], [0], [dnl
+AT_CHECK([ovs-ofctl -O OpenFlow11 dump-flows br0 | ofctl_strip | sort], [0], [dnl
  table=1, n_packets=3, n_bytes=180, dl_dst=50:54:00:00:00:0a actions=CONTROLLER:65535
-OFPST_FLOW reply (OF1.3):
+OFPST_FLOW reply (OF1.1):
 ])
 
-(echo "OFPST_TABLE reply (OF1.3) (xid=0x2): 254 tables"
- echo "  0: active=0, lookup=3, matched=0"
- echo "  1: active=1, lookup=3, matched=3"
+(echo "OFPST_TABLE reply (OF1.3) (xid=0x2):
+  table 0:
+    active=0, lookup=3, matched=0
+
+  table 1:
+    active=1, lookup=3, matched=3"
  x=2
  while test $x -lt 254; do
-   echo "  $x: active=0, lookup=0, matched=0"
+   echo "
+  table $x:
+    active=0, lookup=0, matched=0"
    x=`expr $x + 1`
  done) > expout
 AT_CHECK([ovs-ofctl -O OpenFlow13 dump-tables br0 ], [0], [expout])
diff --git a/tests/ofproto.at b/tests/ofproto.at
index 5eef15b..42072cb 100644
--- a/tests/ofproto.at
+++ b/tests/ofproto.at
@@ -1028,15 +1028,30 @@ AT_CLEANUP
 AT_SETUP([ofproto - flow table configuration (OpenFlow 1.0)])
 OVS_VSWITCHD_START
 # Check the default configuration.
-(echo "OFPST_TABLE reply (xid=0x2): 254 tables
-  0: classifier: wild=0x3fffff, max=1000000, active=0
-               lookup=0, matched=0"
- x=1
+(printf "OFPST_TABLE reply (xid=0x2):"
+ x=0
+ name=classifier
  while test $x -lt 254; do
-   printf "  %d: %-8s: wild=0x3fffff, max=1000000, active=0
-               lookup=0, matched=0
-" $x table$x
+   printf "
+  table %d (\"%s\"):
+    active=0, lookup=0, matched=0
+    max_entries=1000000
+    matching:
+      in_port: exact match or wildcard
+      eth_src: exact match or wildcard
+      eth_dst: exact match or wildcard
+      eth_type: exact match or wildcard
+      vlan_vid: exact match or wildcard
+      vlan_pcp: exact match or wildcard
+      ip_src: exact match or wildcard
+      ip_dst: exact match or wildcard
+      nw_proto: exact match or wildcard
+      nw_tos: exact match or wildcard
+      tcp_src: exact match or wildcard
+      tcp_dst: exact match or wildcard
+" $x $name
    x=`expr $x + 1`
+   name=table$x
  done) > expout
 AT_CHECK([ovs-ofctl dump-tables br0], [0], [expout])
 # Change the configuration.
@@ -1051,12 +1066,8 @@ AT_CHECK(
 ])
 # Check that the configuration was updated.
 mv expout orig-expout
-(echo "OFPST_TABLE reply (xid=0x2): 254 tables
-  0: main    : wild=0x3fffff, max=1000000, active=0
-               lookup=0, matched=0
-  1: table1  : wild=0x3fffff, max=  1024, active=0
-               lookup=0, matched=0"
- tail -n +6 orig-expout) > expout
+sed -e 's/classifier/main/
+21s/1000000/1024/' orig-expout > expout
 AT_CHECK([ovs-ofctl dump-tables br0], [0], [expout])
 OVS_VSWITCHD_STOP
 AT_CLEANUP
@@ -1064,22 +1075,59 @@ AT_CLEANUP
 AT_SETUP([ofproto - flow table configuration (OpenFlow 1.2)])
 OVS_VSWITCHD_START
 # Check the default configuration.
-(mid="wild=0x1ffffffffd, max=1000000,"
- tail="
-               lookup=0, matched=0
-               match=0x1ffffffffd, instructions=0x0000003e, config=0x00000003
-               write_actions=0x03ff8001, apply_actions=0x03ff8001
-               write_setfields=0x0000000c0fe7fbdd
-               apply_setfields=0x0000000c0fe7fbdd
-               metadata_match=0xffffffffffffffff
-               metadata_write=0xffffffffffffffff"
- echo "OFPST_TABLE reply (OF1.2) (xid=0x2): 254 tables
-  0: classifier: $mid active=0$tail"
- x=1
+(printf "OFPST_TABLE reply (OF1.2) (xid=0x2):"
+ x=0
+ name=classifier
  while test $x -lt 254; do
-   printf "  %d: %-8s: $mid active=0$tail
-" $x table$x
+   echo "
+  table $x (\"$name\"):
+    active=0, lookup=0, matched=0
+    metadata: match=0xffffffffffffffff write=0xffffffffffffffff
+    config=controller
+    max_entries=1000000
+    instructions (table miss and others):
+      instructions: apply_actions,clear_actions,write_actions,write_metadata,goto_table
+      Write-Actions and Apply-Actions features:
+        actions: output group set_field strip_vlan push_vlan mod_nw_ttl dec_ttl set_mpls_ttl dec_mpls_ttl push_mpls pop_mpls set_queue
+        supported on Set-Field: metadata in_port_oxm eth_src eth_dst vlan_vid vlan_pcp mpls_label mpls_tc ip_src ip_dst ipv6_src ipv6_dst ip_dscp nw_ecn arp_op arp_spa arp_tpa arp_sha arp_tha tcp_src tcp_dst udp_src udp_dst sctp_src sctp_dst
+    matching:
+      metadata: exact match or wildcard
+      in_port_oxm: exact match or wildcard
+      eth_src: exact match or wildcard
+      eth_dst: exact match or wildcard
+      eth_type: exact match or wildcard
+      vlan_vid: exact match or wildcard
+      vlan_pcp: exact match or wildcard
+      mpls_label: exact match or wildcard
+      mpls_tc: exact match or wildcard
+      ip_src: exact match or wildcard
+      ip_dst: exact match or wildcard
+      ipv6_src: exact match or wildcard
+      ipv6_dst: exact match or wildcard
+      ipv6_label: exact match or wildcard
+      nw_proto: exact match or wildcard
+      ip_dscp: exact match or wildcard
+      nw_ecn: exact match or wildcard
+      arp_op: exact match or wildcard
+      arp_spa: exact match or wildcard
+      arp_tpa: exact match or wildcard
+      arp_sha: exact match or wildcard
+      arp_tha: exact match or wildcard
+      tcp_src: exact match or wildcard
+      tcp_dst: exact match or wildcard
+      udp_src: exact match or wildcard
+      udp_dst: exact match or wildcard
+      sctp_src: exact match or wildcard
+      sctp_dst: exact match or wildcard
+      icmp_type: exact match or wildcard
+      icmp_code: exact match or wildcard
+      icmpv6_type: exact match or wildcard
+      icmpv6_code: exact match or wildcard
+      nd_target: exact match or wildcard
+      nd_sll: exact match or wildcard
+      nd_tll: exact match or wildcard"
    x=`expr $x + 1`
+   name=table$x
  done) > expout
 AT_CHECK([ovs-ofctl -O OpenFlow12 dump-tables br0], [0], [expout])
 # Change the configuration.
@@ -1094,11 +1142,8 @@ AT_CHECK(
 ])
 # Check that the configuration was updated.
 mv expout orig-expout
-(echo "OFPST_TABLE reply (OF1.2) (xid=0x2): 254 tables
-  0: main    : wild=0x1ffffffffd, max=1000000, active=0"
- tail -n +3 orig-expout | head -7
- echo "  1: table1  : wild=0x1ffffffffd, max=  1024, active=0"
- tail -n +11 orig-expout) > expout
+sed 's/classifier/main/
+53s/1000000/1024/' < orig-expout > expout
 AT_CHECK([ovs-ofctl -O OpenFlow12 dump-tables br0], [0], [expout])
 OVS_VSWITCHD_STOP
 AT_CLEANUP
-- 
1.7.10.4




More information about the dev mailing list