[ovs-dev,1/2,v3] ofctl: This patch add support for setting the first egress table for egress processing.
diff mbox

Message ID 1455173683-20015-1-git-send-email-niti.rohilla@tcs.com
State Changes Requested
Headers show

Commit Message

niti1489@gmail.com Feb. 11, 2016, 6:54 a.m. UTC
From: Niti Rohilla <niti.rohilla@tcs.com>

"ovs-ofctl set-first-egress-table <SWITCH> <table_id>" can be used to set first egress
table.
This patch enhances TABLE_FEATURES_REQUEST message to set the first egress table and
TABLE_FEATURES_REPLY to identify the table which is configured as first egress table.

Signed-off-by: Niti Rohilla <niti.rohilla@tcs.com>
---
Difference between v2->v3
- Rebased with latest master

 include/openflow/openflow-1.3.h |   3 +-
 include/openflow/openflow-1.5.h |   7 ++
 lib/ofp-actions.c               |  23 ++++
 lib/ofp-actions.h               |   4 +-
 lib/ofp-msgs.h                  |   2 +-
 lib/ofp-parse.c                 |  25 +++++
 lib/ofp-parse.h                 |   6 ++
 lib/ofp-print.c                 |   6 ++
 lib/ofp-util.c                  |  74 +++++++++++--
 lib/ofp-util.h                  |  15 ++-
 ofproto/ofproto-provider.h      |   5 +
 ofproto/ofproto.c               |  55 ++++++++--
 tests/ofp-print.at              | 222 ++++++++++++++++++++++++++++++++++++++
 tests/ofproto.at                | 230 ++++++++++++++++++++++++++++++++++++++++
 utilities/ovs-ofctl.c           |  36 ++++++-
 15 files changed, 690 insertions(+), 23 deletions(-)

Comments

Ben Pfaff Feb. 23, 2016, 4:16 p.m. UTC | #1
On Thu, Feb 11, 2016 at 12:24:43PM +0530, niti1489@gmail.com wrote:
> From: Niti Rohilla <niti.rohilla@tcs.com>
> 
> "ovs-ofctl set-first-egress-table <SWITCH> <table_id>" can be used to set first egress
> table.
> This patch enhances TABLE_FEATURES_REQUEST message to set the first egress table and
> TABLE_FEATURES_REPLY to identify the table which is configured as first egress table.
> 
> Signed-off-by: Niti Rohilla <niti.rohilla@tcs.com>

There's a major problem here: it stores the first egress table in a
global variable, in function ofputil_egress_table_id().  That's not
acceptable.  It will not work properly when there is more than one
bridge.

The function ofpact_put_ofp15_write_actions() has a comment that doesn't
describe what it actually does, and it can be better written as just 
    f->nonmiss.write.ofpacts &= ~((UINT64_C(1) << OFPACT_OUTPUT) |
                                  (UINT64_C(1) << OFPACT_GROUP));

Patch
diff mbox

diff --git a/include/openflow/openflow-1.3.h b/include/openflow/openflow-1.3.h
index a521995..aa15d56 100644
--- a/include/openflow/openflow-1.3.h
+++ b/include/openflow/openflow-1.3.h
@@ -221,7 +221,8 @@  struct ofp13_table_features {
     ovs_be16 length;          /* Length is padded to 64 bits. */
     uint8_t table_id;         /* Identifier of table. Lower numbered tables
                                  are consulted first. */
-    uint8_t pad[5];           /* Align to 64-bits. */
+    uint8_t command;          /* One of OFPTFC_* (OF1.5+). */
+    ovs_be32 features;        /* Bitmap of OFPTFF_* values (OF1.5+). */
     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. */
diff --git a/include/openflow/openflow-1.5.h b/include/openflow/openflow-1.5.h
index 0c478d1..3c51361 100644
--- a/include/openflow/openflow-1.5.h
+++ b/include/openflow/openflow-1.5.h
@@ -149,4 +149,11 @@  struct ofp15_group_desc_stats {
 };
 OFP_ASSERT(sizeof(struct ofp15_group_desc_stats) == 16);
 
+/* Flags of features supported by the table. */
+enum ofp15_table_feature_flag {
+    OFPTFF_INGRESS_TABLE = 1 << 0, /* Can be configured as ingress table. */
+    OFPTFF_EGRESS_TABLE  = 1 << 1, /* Can be configured as egress table. */
+    OFPTFF_FIRST_EGRESS  = 1 << 4, /* Is the first egress table. */
+};
+
 #endif /* openflow/openflow-1.5.h */
diff --git a/lib/ofp-actions.c b/lib/ofp-actions.c
index 9b75526..74a2ec1 100644
--- a/lib/ofp-actions.c
+++ b/lib/ofp-actions.c
@@ -7789,3 +7789,26 @@  pad_ofpat(struct ofpbuf *openflow, size_t start_ofs)
     oah->len = htons(openflow->size - start_ofs);
 }
 
+/* If first egress table is set then flow tables used for egress
+ * processing must forbid the use of output action or group action in
+ * write-action instruction and must advertise the same in flow table
+ * features as mentioned in the specification.
+ *
+ * OFPAT_* values, i.e. 0 for OFPACT_OUTPUT and 22 for OFPACT_GROUP is
+ * used for comparison.
+ */
+
+void
+ofpact_put_ofp15_write_actions(uint64_t *ofpacts_bitmap,
+                               enum ofp_version version)
+{
+    const struct ofpact_map *x;
+
+    for (x = get_ofpact_map(version); x->ofpat >= 0; x++) {
+        if (x->ofpact == OFPACT_OUTPUT || x->ofpact == OFPACT_GROUP) {
+            continue;
+        } else {
+            *ofpacts_bitmap |= (UINT64_C(1) << x->ofpact);
+        }
+    }
+}
diff --git a/lib/ofp-actions.h b/lib/ofp-actions.h
index 5dec177..9276daf 100644
--- a/lib/ofp-actions.h
+++ b/lib/ofp-actions.h
@@ -1013,5 +1013,7 @@  enum ofperr ovs_instruction_type_from_inst_type(
 ovs_be32 ovsinst_bitmap_to_openflow(uint32_t ovsinst_bitmap, enum ofp_version);
 uint32_t ovsinst_bitmap_from_openflow(ovs_be32 ofpit_bitmap,
                                       enum ofp_version);
-
+void
+ofpact_put_ofp15_write_actions(uint64_t *ofpacts_bitmap,
+                               enum ofp_version version);
 #endif /* ofp-actions.h */
diff --git a/lib/ofp-msgs.h b/lib/ofp-msgs.h
index a15efb6..6e84798 100644
--- a/lib/ofp-msgs.h
+++ b/lib/ofp-msgs.h
@@ -374,7 +374,7 @@  enum ofpraw {
     /* OFPST 1.3+ (11): struct ofp13_meter_features. */
     OFPRAW_OFPST13_METER_FEATURES_REPLY,
 
-    /* OFPST 1.3+ (12): void. */
+    /* OFPST 1.3+ (12): struct ofp13_table_features[]. */
     OFPRAW_OFPST13_TABLE_FEATURES_REQUEST,
 
     /* OFPST 1.3+ (12): struct ofp13_table_features, uint8_t[8][]. */
diff --git a/lib/ofp-parse.c b/lib/ofp-parse.c
index 5ef3892..f131adb 100644
--- a/lib/ofp-parse.c
+++ b/lib/ofp-parse.c
@@ -1704,3 +1704,28 @@  parse_ofp_tlv_table_mod_str(struct ofputil_tlv_table_mod *ttm,
 
     return NULL;
 }
+
+/* Convert 'table_id' and table feature flag 'OFPTFF_FIRST_EGRESS' into 'tf'
+ * for sending a set_table_features command to a switch.
+ *
+ * Stores a bitmap of the OpenFlow versions that are usable for 'tf' into
+ * '*usable_versions'.
+ *
+ * Returns NULL if successful, otherwise a malloc()'d string describing the
+ * error.  The caller is responsible for freeing the returned string. */
+char * OVS_WARN_UNUSED_RESULT
+parse_ofp_table_features(struct ofputil_table_features *tf,
+                         const char *table_id,
+                         uint32_t *usable_versions)
+{
+    char *error = str_to_u8(table_id, "table_id", &tf->table_id);
+    if (error) {
+        return error;
+    }
+
+    *usable_versions = (1u << OFP15_VERSION);
+
+    tf->features |= OFPTFF_FIRST_EGRESS;
+
+    return NULL;
+}
diff --git a/lib/ofp-parse.h b/lib/ofp-parse.h
index a1c147b..fba2efd 100644
--- a/lib/ofp-parse.h
+++ b/lib/ofp-parse.h
@@ -36,6 +36,7 @@  struct ofputil_meter_mod;
 struct ofputil_table_mod;
 struct ofputil_tlv_table_mod;
 struct simap;
+struct ofputil_table_features;
 enum ofputil_protocol;
 
 char *parse_ofp_str(struct ofputil_flow_mod *, int command, const char *str_,
@@ -52,6 +53,11 @@  char *parse_ofp_table_mod(struct ofputil_table_mod *,
                           uint32_t *usable_versions)
     OVS_WARN_UNUSED_RESULT;
 
+char *parse_ofp_table_features(struct ofputil_table_features *tf,
+                               const char *table_id,
+                               uint32_t *usable_versions)
+    OVS_WARN_UNUSED_RESULT;
+
 char *parse_ofp_flow_mod_file(const char *file_name, int command,
                               struct ofputil_flow_mod **fms, size_t *n_fms,
                               enum ofputil_protocol *usable_protocols)
diff --git a/lib/ofp-print.c b/lib/ofp-print.c
index 5af4bf0..ab79065 100644
--- a/lib/ofp-print.c
+++ b/lib/ofp-print.c
@@ -2782,6 +2782,12 @@  ofp_print_table_features(struct ds *s,
 
     }
 
+    if (features->features >= 0) {
+        ds_put_format(s, "    features: %s\n",
+                     (features->features & OFPTFF_FIRST_EGRESS) ?
+                      "first egress table" : "none");
+    }
+
     if (features->max_entries) {
         ds_put_format(s, "    max_entries=%"PRIu32"\n", features->max_entries);
     }
diff --git a/lib/ofp-util.c b/lib/ofp-util.c
index aa4d2f3..bf1a28f 100644
--- a/lib/ofp-util.c
+++ b/lib/ofp-util.c
@@ -4725,12 +4725,16 @@  ofputil_decode_table_features(struct ofpbuf *msg,
     struct ofp13_table_features *otf;
     struct ofpbuf properties;
     unsigned int len;
+    enum ofpraw raw;
+    enum ofperr error;
 
-    memset(tf, 0, sizeof *tf);
-
-    if (!msg->header) {
-        ofpraw_pull_assert(msg);
+    error = (msg->header ? ofpraw_decode(&raw, msg->header)
+             : ofpraw_pull(&raw, msg));
+    if (error) {
+        return error;
     }
+
+    memset(tf, 0, sizeof *tf);
     oh = msg->header;
 
     if (!msg->size) {
@@ -4754,6 +4758,20 @@  ofputil_decode_table_features(struct ofpbuf *msg,
         return OFPERR_OFPTFFC_BAD_TABLE;
     }
 
+    if (oh->version >= OFP15_VERSION &&
+        raw == OFPRAW_OFPST13_TABLE_FEATURES_REQUEST) {
+
+        /* Return an error if TABLE_FEATURES_REQUEST attempts to set table 0
+         * as first egress table. */
+        if (tf->table_id == 0) {
+            return OFPERR_OFPTFFC_BAD_TABLE;
+        }
+
+        /* Return an error if TABLE_FEATURES_REQUEST contain properties. */
+        if (properties.size > 0) {
+            return OFPERR_OFPBPC_BAD_LEN;
+        }
+    }
     ovs_strlcpy(tf->name, otf->name, OFP_MAX_TABLE_NAME_LEN);
     tf->metadata_match = otf->metadata_match;
     tf->metadata_write = otf->metadata_write;
@@ -4767,7 +4785,19 @@  ofputil_decode_table_features(struct ofpbuf *msg,
         tf->supports_vacancy_events = -1;
     }
     tf->max_entries = ntohl(otf->max_entries);
-
+    if (oh->version >= OFP15_VERSION) {
+        /* Return an error if any flag other than OFPTFF_FIRST_EGRESS is set.
+         */
+        uint32_t features = ntohl(otf->features);
+        if ((features & OFPTFF_FIRST_EGRESS) != 0) {
+            tf->features |= OFPTFF_FIRST_EGRESS;
+        } else if ((features & OFPTFF_INGRESS_TABLE) != 0 ||
+                   (features & OFPTFF_EGRESS_TABLE) != 0 ) {
+            return OFPERR_OFPBFC_BAD_FLAGS;
+        }
+    } else {
+        tf->features = -1;
+    }
     while (properties.size > 0) {
         struct ofpbuf payload;
         enum ofperr error;
@@ -4869,7 +4899,8 @@  ofputil_decode_table_features(struct ofpbuf *msg,
 /* Encodes and returns a request to obtain the table features of a switch.
  * The message is encoded for OpenFlow version 'ofp_version'. */
 struct ofpbuf *
-ofputil_encode_table_features_request(enum ofp_version ofp_version)
+ofputil_encode_table_features_request(const struct ofputil_table_features *tf,
+                                      enum ofp_version ofp_version)
 {
     struct ofpbuf *request = NULL;
 
@@ -4881,10 +4912,22 @@  ofputil_encode_table_features_request(enum ofp_version ofp_version)
                      "(\'-O OpenFlow13\')");
     case OFP13_VERSION:
     case OFP14_VERSION:
-    case OFP15_VERSION:
         request = ofpraw_alloc(OFPRAW_OFPST13_TABLE_FEATURES_REQUEST,
                                ofp_version, 0);
         break;
+    case OFP15_VERSION: {
+	struct ofp13_table_features *otf;
+
+        request = ofpraw_alloc(OFPRAW_OFPST13_TABLE_FEATURES_REQUEST,
+                               ofp_version, 0);
+        if (tf != NULL && (tf->features & OFPTFF_FIRST_EGRESS)) {
+            otf = ofpbuf_put_zeros(request, sizeof *otf);
+            otf->table_id = tf->table_id;
+            otf->features = htonl(tf->features);
+            otf->length = htons(sizeof *otf);
+        }
+    }
+    break;
     default:
         OVS_NOT_REACHED();
     }
@@ -4973,7 +5016,11 @@  ofputil_append_table_features_reply(const struct ofputil_table_features *tf,
         }
     }
     otf->max_entries = htonl(tf->max_entries);
-
+    if (version >= OFP15_VERSION) {
+        if ((tf->features & OFPTFF_FIRST_EGRESS) != 0) {
+            otf->features |= htonl(OFPTFF_FIRST_EGRESS);
+        }
+    }
     put_table_instruction_features(reply, &tf->nonmiss, 0, version);
     put_table_instruction_features(reply, &tf->miss, 1, version);
 
@@ -6075,6 +6122,7 @@  ofputil_decode_table_stats_reply(struct ofpbuf *msg,
     memset(features, 0, sizeof *features);
     features->supports_eviction = -1;
     features->supports_vacancy_events = -1;
+    features->features = -1;
 
     switch ((enum ofp_version) oh->version) {
     case OFP10_VERSION:
@@ -9812,3 +9860,13 @@  ofputil_async_cfg_default(enum ofp_version version)
         .slave[OAM_PORT_STATUS] = OFPPR_BITS,
     };
 }
+
+uint8_t ofputil_egress_table_id(uint8_t table_id)
+{
+    static uint8_t egress_table_id = 0;
+
+    if (table_id > 0) {
+        egress_table_id = table_id;
+    }
+    return egress_table_id;
+}
diff --git a/lib/ofp-util.h b/lib/ofp-util.h
index 866e1bc..6e5e117 100644
--- a/lib/ofp-util.h
+++ b/lib/ofp-util.h
@@ -767,6 +767,15 @@  struct ofputil_table_features {
     int supports_eviction;               /* OF1.4+ only. */
     int supports_vacancy_events;         /* OF1.4+ only. */
 
+    /* The features field is a bitmap of OFPTFF_* values that defines how the
+     * flow table can be used and what are its basic features.
+     *
+     * 'features' is relevant only for Openflow 1.5 and later only. For 1.5,
+     * it will be OFPTFF_FIRST_EGRESS if first egress table is set, otherwise
+     * 0. For other versions, they are decoded as -1 and ignored for encoding.
+     */
+    int features;                  /* OF1.5+ only. */
+
     /* Table features related to instructions.  There are two instances:
      *
      *   - 'miss' reports features available in the table miss flow.
@@ -823,7 +832,9 @@  int ofputil_decode_table_desc(struct ofpbuf *,
                               struct ofputil_table_desc *,
                               enum ofp_version);
 
-struct ofpbuf *ofputil_encode_table_features_request(enum ofp_version);
+struct ofpbuf *
+ofputil_encode_table_features_request(const struct ofputil_table_features *,
+                                      enum ofp_version);
 
 struct ofpbuf *ofputil_encode_table_desc_request(enum ofp_version);
 
@@ -1356,4 +1367,6 @@  enum ofperr ofputil_decode_requestforward(const struct ofp_header *,
                                           struct ofputil_requestforward *);
 void ofputil_destroy_requestforward(struct ofputil_requestforward *);
 
+uint8_t ofputil_egress_table_id(uint8_t table_id);
+
 #endif /* ofp-util.h */
diff --git a/ofproto/ofproto-provider.h b/ofproto/ofproto-provider.h
index 5fa03b5..1da43b3 100644
--- a/ofproto/ofproto-provider.h
+++ b/ofproto/ofproto-provider.h
@@ -95,6 +95,7 @@  struct ofproto {
     int n_tables;
     cls_version_t tables_version;  /* Controls which rules are visible to
                                     * table lookups. */
+    uint8_t first_egress_table_id; /* Contains first egress table id. */
 
     /* Rules indexed on their cookie values, in all flow tables. */
     struct hindex cookies OVS_GUARDED_BY(ofproto_mutex);
@@ -267,6 +268,10 @@  struct oftable {
 
     atomic_ulong n_matched;
     atomic_ulong n_missed;
+
+    /* The features field is a bitmap that defines how the flow table can be
+     * used and what are its basic features. */
+    enum ofp15_table_feature_flag features;
 };
 
 /* Assigns TABLE to each oftable, in turn, in OFPROTO.
diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
index e1efedb..dc31b7c 100644
--- a/ofproto/ofproto.c
+++ b/ofproto/ofproto.c
@@ -3115,7 +3115,8 @@  handle_echo_request(struct ofconn *ofconn, const struct ofp_header *oh)
 static void
 query_tables(struct ofproto *ofproto,
              struct ofputil_table_features **featuresp,
-             struct ofputil_table_stats **statsp)
+             struct ofputil_table_stats **statsp,
+             enum ofp_version version)
 {
     struct mf_bitmap rw_fields = oxm_writable_fields();
     struct mf_bitmap match = oxm_matchable_fields();
@@ -3147,7 +3148,12 @@  query_tables(struct ofproto *ofproto,
         if (!more_tables) {
             f->nonmiss.instructions &= ~(1u << OVSINST_OFPIT11_GOTO_TABLE);
         }
-        f->nonmiss.write.ofpacts = (UINT64_C(1) << N_OFPACTS) - 1;
+        if (version >= OFP15_VERSION && ofputil_egress_table_id(0) &&
+            f->table_id >= ofputil_egress_table_id(0)) {
+            ofpact_put_ofp15_write_actions(&f->nonmiss.write.ofpacts, version);
+        } else {
+            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;
@@ -3155,6 +3161,9 @@  query_tables(struct ofproto *ofproto,
         f->match = match;
         f->mask = mask;
         f->wildcard = match;
+        if (ofproto->tables[i].features & OFPTFF_FIRST_EGRESS) {
+            f->features |= OFPTFF_FIRST_EGRESS;
+        }
     }
 
     if (statsp) {
@@ -3191,14 +3200,15 @@  query_tables(struct ofproto *ofproto,
 
 static void
 query_switch_features(struct ofproto *ofproto,
-                      bool *arp_match_ip, uint64_t *ofpacts)
+                      bool *arp_match_ip, uint64_t *ofpacts,
+                      enum ofp_version version)
 {
     struct ofputil_table_features *features, *f;
 
     *arp_match_ip = false;
     *ofpacts = 0;
 
-    query_tables(ofproto, &features, NULL);
+    query_tables(ofproto, &features, NULL, version);
     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) ||
@@ -3221,7 +3231,8 @@  handle_features_request(struct ofconn *ofconn, const struct ofp_header *oh)
     bool arp_match_ip;
     struct ofpbuf *b;
 
-    query_switch_features(ofproto, &arp_match_ip, &features.ofpacts);
+    query_switch_features(ofproto, &arp_match_ip, &features.ofpacts,
+                          oh->version);
 
     features.datapath_id = ofproto->datapath_id;
     features.n_buffers = pktbuf_capacity();
@@ -3528,7 +3539,7 @@  handle_table_stats_request(struct ofconn *ofconn,
     struct ofpbuf *reply;
     size_t i;
 
-    query_tables(ofproto, &features, &stats);
+    query_tables(ofproto, &features, &stats, request->version);
 
     reply = ofputil_encode_table_stats_reply(request);
     for (i = 0; i < ofproto->n_tables; i++) {
@@ -3552,15 +3563,41 @@  handle_table_features_request(struct ofconn *ofconn,
     struct ofputil_table_features *features;
     struct ovs_list replies;
     struct ofpbuf msg;
+    enum ofperr error = 0;
     size_t i;
 
     ofpbuf_use_const(&msg, request, ntohs(request->length));
     ofpraw_pull_assert(&msg);
-    if (msg.size || ofpmp_more(request)) {
-        return OFPERR_OFPTFFC_EPERM;
+
+    if (request->version < OFP15_VERSION) {
+        if (msg.size || ofpmp_more(request)) {
+            return OFPERR_OFPTFFC_EPERM;
+        }
+    } else {
+        if (msg.size) {
+            struct ofputil_table_features tf;
+            error = ofputil_decode_table_features(&msg, &tf, false);
+            if (error) {
+                return error;
+            }
+            if ((tf.features & OFPTFF_FIRST_EGRESS) != 0) {
+                for (i = 0; i < ofproto->n_tables; i++) {
+                    if (ofproto->tables[i].features & OFPTFF_FIRST_EGRESS) {
+                        ofproto->tables[i].features &= ~OFPTFF_FIRST_EGRESS;
+                        break;
+                    }
+                }
+                ovs_mutex_lock(&ofproto_mutex);
+                ofproto->tables[tf.table_id].features |= OFPTFF_FIRST_EGRESS;
+                ofproto->first_egress_table_id = tf.table_id;
+                ofputil_egress_table_id(tf.table_id);
+                ovs_mutex_unlock(&ofproto_mutex);
+            }
+            return 0;
+        }
     }
 
-    query_tables(ofproto, &features, NULL);
+    query_tables(ofproto, &features, NULL, request->version);
 
     ofpmp_init(&replies, request);
     for (i = 0; i < ofproto->n_tables; i++) {
diff --git a/tests/ofp-print.at b/tests/ofp-print.at
index c791cb2..3b33ed7 100644
--- a/tests/ofp-print.at
+++ b/tests/ofp-print.at
@@ -2475,6 +2475,228 @@  f5 f6 f7 f8 f9 fa fb fc fd 00 00 00 00 00 00 00 \
 ])
 AT_CLEANUP
 
+AT_SETUP([OFPST_TABLE_FEATURES request - OF1.5])
+AT_KEYWORDS([ofp-print OFPT_STATS_REQUEST])
+AT_CHECK([ovs-ofctl ofp-print "\
+06 13 09 40 00 00 00 d5 00 0c 00 01 00 00 00 00 \
+09 30 00 00 00 00 00 00 74 61 62 6c 65 30 00 00 \
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 \
+00 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff \
+ff ff ff ff ff ff ff ff 00 00 00 03 00 0f 42 40 \
+00 00 00 2c 00 01 00 08 00 00 00 00 00 02 00 08 \
+00 00 00 00 00 03 00 08 00 00 00 00 00 04 00 08 \
+00 00 00 00 00 05 00 08 00 00 00 00 00 00 00 00 \
+00 01 00 2c 00 01 00 08 00 00 00 00 00 02 00 08 \
+00 00 00 00 00 03 00 08 00 00 00 00 00 04 00 08 \
+00 00 00 00 00 05 00 08 00 00 00 00 00 00 00 00 \
+00 02 01 01 01 02 03 04 05 06 07 08 09 0a 0b 0c \
+0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c \
+1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c \
+2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c \
+3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c \
+4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c \
+5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c \
+6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c \
+7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c \
+8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c \
+9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac \
+ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc \
+bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc \
+cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc \
+dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec \
+ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc \
+fd 00 00 00 00 00 00 00 00 03 01 01 01 02 03 04 \
+05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 \
+15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 \
+25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 \
+35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 \
+45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 \
+55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 \
+65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 \
+75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 \
+85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 \
+95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 \
+a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 \
+b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 \
+c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 \
+d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 \
+e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 \
+f5 f6 f7 f8 f9 fa fb fc fd 00 00 00 00 00 00 00 \
+00 04 00 84 00 00 00 08 00 00 00 00 00 0b 00 08 \
+00 00 00 00 00 0c 00 08 00 00 00 00 00 0f 00 08 \
+00 00 00 00 00 10 00 08 00 00 00 00 00 11 00 08 \
+00 00 00 00 00 12 00 08 00 00 00 00 00 13 00 08 \
+00 00 00 00 00 14 00 08 00 00 00 00 00 15 00 08 \
+00 00 00 00 00 16 00 08 00 00 00 00 00 17 00 08 \
+00 00 00 00 00 18 00 08 00 00 00 00 00 19 00 08 \
+00 00 00 00 00 1a 00 08 00 00 00 00 00 1b 00 08 \
+00 00 00 00 00 00 00 00 00 05 00 84 00 00 00 08 \
+00 00 00 00 00 0b 00 08 00 00 00 00 00 0c 00 08 \
+00 00 00 00 00 0f 00 08 00 00 00 00 00 10 00 08 \
+00 00 00 00 00 11 00 08 00 00 00 00 00 12 00 08 \
+00 00 00 00 00 13 00 08 00 00 00 00 00 14 00 08 \
+00 00 00 00 00 15 00 08 00 00 00 00 00 16 00 08 \
+00 00 00 00 00 17 00 08 00 00 00 00 00 18 00 08 \
+00 00 00 00 00 19 00 08 00 00 00 00 00 1a 00 08 \
+00 00 00 00 00 1b 00 08 00 00 00 00 00 00 00 00 \
+00 06 00 84 00 00 00 08 00 00 00 00 00 0b 00 08 \
+00 00 00 00 00 0c 00 08 00 00 00 00 00 0f 00 08 \
+00 00 00 00 00 10 00 08 00 00 00 00 00 11 00 08 \
+00 00 00 00 00 12 00 08 00 00 00 00 00 13 00 08 \
+00 00 00 00 00 14 00 08 00 00 00 00 00 15 00 08 \
+00 00 00 00 00 16 00 08 00 00 00 00 00 17 00 08 \
+00 00 00 00 00 18 00 08 00 00 00 00 00 19 00 08 \
+00 00 00 00 00 1a 00 08 00 00 00 00 00 1b 00 08 \
+00 00 00 00 00 00 00 00 00 07 00 84 00 00 00 08 \
+00 00 00 00 00 0b 00 08 00 00 00 00 00 0c 00 08 \
+00 00 00 00 00 0f 00 08 00 00 00 00 00 10 00 08 \
+00 00 00 00 00 11 00 08 00 00 00 00 00 12 00 08 \
+00 00 00 00 00 13 00 08 00 00 00 00 00 14 00 08 \
+00 00 00 00 00 15 00 08 00 00 00 00 00 16 00 08 \
+00 00 00 00 00 17 00 08 00 00 00 00 00 18 00 08 \
+00 00 00 00 00 19 00 08 00 00 00 00 00 1a 00 08 \
+00 00 00 00 00 1b 00 08 00 00 00 00 00 00 00 00 \
+00 08 00 dc 80 00 4c 08 00 01 3e 04 00 01 40 04 \
+80 00 04 08 00 00 00 02 80 00 00 04 00 01 42 04 \
+00 01 00 04 00 01 02 04 00 01 04 04 00 01 06 04 \
+00 01 08 04 00 01 0a 04 00 01 0c 04 00 01 0e 04 \
+80 00 08 06 80 00 06 06 80 00 0a 02 00 00 08 02 \
+80 00 0c 02 80 00 0e 01 80 00 44 04 80 00 46 01 \
+80 00 48 01 80 00 16 04 80 00 18 04 80 00 34 10 \
+80 00 36 10 80 00 38 04 80 00 14 01 00 00 0a 01 \
+80 00 10 01 80 00 12 01 00 01 3a 01 00 01 34 01 \
+80 00 2a 02 80 00 2c 04 80 00 2e 04 80 00 30 06 \
+80 00 32 06 80 00 1a 02 80 00 1c 02 00 01 44 02 \
+80 00 1e 02 80 00 20 02 80 00 22 02 80 00 24 02 \
+80 00 26 01 80 00 28 01 80 00 3a 01 80 00 3c 01 \
+80 00 3e 10 80 00 40 06 80 00 42 06 00 00 00 00 \
+00 0a 00 dc 80 00 4c 08 00 01 3e 04 00 01 40 04 \
+80 00 04 08 00 00 00 02 80 00 00 04 00 01 42 04 \
+00 01 00 04 00 01 02 04 00 01 04 04 00 01 06 04 \
+00 01 08 04 00 01 0a 04 00 01 0c 04 00 01 0e 04 \
+80 00 08 06 80 00 06 06 80 00 0a 02 00 00 08 02 \
+80 00 0c 02 80 00 0e 01 80 00 44 04 80 00 46 01 \
+80 00 48 01 80 00 16 04 80 00 18 04 80 00 34 10 \
+80 00 36 10 80 00 38 04 80 00 14 01 00 00 0a 01 \
+80 00 10 01 80 00 12 01 00 01 3a 01 00 01 34 01 \
+80 00 2a 02 80 00 2c 04 80 00 2e 04 80 00 30 06 \
+80 00 32 06 80 00 1a 02 80 00 1c 02 00 01 44 02 \
+80 00 1e 02 80 00 20 02 80 00 22 02 80 00 24 02 \
+80 00 26 01 80 00 28 01 80 00 3a 01 80 00 3c 01 \
+80 00 3e 10 80 00 40 06 80 00 42 06 00 00 00 00 \
+00 0c 00 a8 80 00 4c 08 00 01 3e 04 00 01 40 04 \
+80 00 04 08 00 00 00 02 80 00 00 04 00 01 42 04 \
+00 01 00 04 00 01 02 04 00 01 04 04 00 01 06 04 \
+00 01 08 04 00 01 0a 04 00 01 0c 04 00 01 0e 04 \
+80 00 08 06 80 00 06 06 00 00 08 02 80 00 0c 02 \
+80 00 0e 01 80 00 44 04 80 00 46 01 80 00 16 04 \
+80 00 18 04 80 00 34 10 80 00 36 10 00 00 0a 01 \
+80 00 10 01 80 00 12 01 00 01 3a 01 80 00 2a 02 \
+80 00 2c 04 80 00 2e 04 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 00 0d 00 a8 80 00 4c 08 \
+00 01 3e 04 00 01 40 04 80 00 04 08 00 00 00 02 \
+80 00 00 04 00 01 42 04 00 01 00 04 00 01 02 04 \
+00 01 04 04 00 01 06 04 00 01 08 04 00 01 0a 04 \
+00 01 0c 04 00 01 0e 04 80 00 08 06 80 00 06 06 \
+00 00 08 02 80 00 0c 02 80 00 0e 01 80 00 44 04 \
+80 00 46 01 80 00 16 04 80 00 18 04 80 00 34 10 \
+80 00 36 10 00 00 0a 01 80 00 10 01 80 00 12 01 \
+00 01 3a 01 80 00 2a 02 80 00 2c 04 80 00 2e 04 \
+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 \
+00 0e 00 a8 80 00 4c 08 00 01 3e 04 00 01 40 04 \
+80 00 04 08 00 00 00 02 80 00 00 04 00 01 42 04 \
+00 01 00 04 00 01 02 04 00 01 04 04 00 01 06 04 \
+00 01 08 04 00 01 0a 04 00 01 0c 04 00 01 0e 04 \
+80 00 08 06 80 00 06 06 00 00 08 02 80 00 0c 02 \
+80 00 0e 01 80 00 44 04 80 00 46 01 80 00 16 04 \
+80 00 18 04 80 00 34 10 80 00 36 10 00 00 0a 01 \
+80 00 10 01 80 00 12 01 00 01 3a 01 80 00 2a 02 \
+80 00 2c 04 80 00 2e 04 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 00 0f 00 a8 80 00 4c 08 \
+00 01 3e 04 00 01 40 04 80 00 04 08 00 00 00 02 \
+80 00 00 04 00 01 42 04 00 01 00 04 00 01 02 04 \
+00 01 04 04 00 01 06 04 00 01 08 04 00 01 0a 04 \
+00 01 0c 04 00 01 0e 04 80 00 08 06 80 00 06 06 \
+00 00 08 02 80 00 0c 02 80 00 0e 01 80 00 44 04 \
+80 00 46 01 80 00 16 04 80 00 18 04 80 00 34 10 \
+80 00 36 10 00 00 0a 01 80 00 10 01 80 00 12 01 \
+00 01 3a 01 80 00 2a 02 80 00 2c 04 80 00 2e 04 \
+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.5) (xid=0xd5):
+  table 0 ("table0"):
+    metadata: match=0xffffffffffffffff write=0xffffffffffffffff
+    eviction: not supported
+    vacancy events: not supported
+    features: none
+    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
+    matching:
+      tun_id: exact match or wildcard
+      tun_src: exact match or wildcard
+      tun_dst: exact match or wildcard
+      metadata: exact match or wildcard
+      in_port: exact match or wildcard
+      in_port_oxm: exact match or wildcard
+      pkt_mark: exact match or wildcard
+      reg0: exact match or wildcard
+      reg1: exact match or wildcard
+      reg2: exact match or wildcard
+      reg3: exact match or wildcard
+      reg4: exact match or wildcard
+      reg5: exact match or wildcard
+      reg6: exact match or wildcard
+      reg7: exact match or wildcard
+      eth_src: exact match or wildcard
+      eth_dst: exact match or wildcard
+      eth_type: exact match or wildcard
+      vlan_tci: 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
+      mpls_bos: 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
+      nw_tos: exact match or wildcard
+      ip_dscp: exact match or wildcard
+      nw_ecn: exact match or wildcard
+      nw_ttl: exact match or wildcard
+      ip_frag: 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
+      tcp_flags: 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
+])
+AT_CLEANUP
+
 AT_SETUP([OFPT_BARRIER_REQUEST - OF1.0])
 AT_KEYWORDS([ofp-print])
 AT_CHECK([ovs-ofctl ofp-print '01 12 00 08 00 00 00 01'], [0], [dnl
diff --git a/tests/ofproto.at b/tests/ofproto.at
index ea3e073..762bba6 100644
--- a/tests/ofproto.at
+++ b/tests/ofproto.at
@@ -1975,6 +1975,236 @@  AT_CHECK([ovs-ofctl -O OpenFlow13 dump-table-features br0], [0], [expout])
 OVS_VSWITCHD_STOP
 AT_CLEANUP
 
+AT_SETUP([ofproto - table features (OpenFlow 1.5)])
+OVS_VSWITCHD_START
+head_table () {
+    printf '  table 0 ("%s"):
+    metadata: match=0xffffffffffffffff write=0xffffffffffffffff
+    eviction: not supported
+    vacancy events: not supported
+    features: none
+    max_entries=1000000
+    instructions (table miss and others):
+      next tables: 1-253
+      instructions: meter,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 tun_ipv6_src tun_ipv6_dst tun_flags tun_gbp_id tun_gbp_flags tun_metadata0 tun_metadata1 tun_metadata2 tun_metadata3 tun_metadata4 tun_metadata5 tun_metadata6 tun_metadata7 tun_metadata8 tun_metadata9 tun_metadata10 tun_metadata11 tun_metadata12 tun_metadata13 tun_metadata14 tun_metadata15 tun_metadata16 tun_metadata17 tun_metadata18 tun_metadata19 tun_metadata20 tun_metadata21 tun_metadata22 tun_metadata23 tun_metadata24 tun_metadata25 tun_metadata26 tun_metadata27 tun_metadata28 tun_metadata29 tun_metadata30 tun_metadata31 tun_metadata32 tun_metadata33 tun_metadata34 tun_metadata35 tun_metadata36 tun_metadata37 tun_metadata38 tun_metadata39 tun_metadata40 tun_metadata41 tun_metadata42 tun_metadata43 tun_metadata44 tun_metadata45 tun_metadata46 tun_metadata47 tun_metadata48 tun_metadata49 tun_metadata50 tun_metadata51 tun_metadata52 tun_metadata53 tun_metadata54 tun_metadata55 tun_metadata56 tun_metadata57 tun_metadata5
 8 tun_metadata59 tun_metadata60 tun_metadata61 tun_metadata62 tun_metadata63 metadata in_port in_port_oxm pkt_mark ct_mark ct_label reg0 reg1 reg2 reg3 reg4 reg5 reg6 reg7 xreg0 xreg1 xreg2 xreg3 eth_src eth_dst vlan_tci vlan_vid vlan_pcp mpls_label mpls_tc ip_src ip_dst ipv6_src ipv6_dst ipv6_label 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 icmp_type icmp_code icmpv6_type icmpv6_code nd_target nd_sll nd_tll
+    matching:
+      dp_hash: arbitrary mask
+      recirc_id: exact match or wildcard
+      conj_id: exact match or wildcard
+      tun_id: arbitrary mask
+      tun_src: arbitrary mask
+      tun_dst: arbitrary mask
+      tun_ipv6_src: arbitrary mask
+      tun_ipv6_dst: arbitrary mask
+      tun_flags: arbitrary mask
+      tun_gbp_id: arbitrary mask
+      tun_gbp_flags: arbitrary mask
+      tun_metadata0: arbitrary mask
+      tun_metadata1: arbitrary mask
+      tun_metadata2: arbitrary mask
+      tun_metadata3: arbitrary mask
+      tun_metadata4: arbitrary mask
+      tun_metadata5: arbitrary mask
+      tun_metadata6: arbitrary mask
+      tun_metadata7: arbitrary mask
+      tun_metadata8: arbitrary mask
+      tun_metadata9: arbitrary mask
+      tun_metadata10: arbitrary mask
+      tun_metadata11: arbitrary mask
+      tun_metadata12: arbitrary mask
+      tun_metadata13: arbitrary mask
+      tun_metadata14: arbitrary mask
+      tun_metadata15: arbitrary mask
+      tun_metadata16: arbitrary mask
+      tun_metadata17: arbitrary mask
+      tun_metadata18: arbitrary mask
+      tun_metadata19: arbitrary mask
+      tun_metadata20: arbitrary mask
+      tun_metadata21: arbitrary mask
+      tun_metadata22: arbitrary mask
+      tun_metadata23: arbitrary mask
+      tun_metadata24: arbitrary mask
+      tun_metadata25: arbitrary mask
+      tun_metadata26: arbitrary mask
+      tun_metadata27: arbitrary mask
+      tun_metadata28: arbitrary mask
+      tun_metadata29: arbitrary mask
+      tun_metadata30: arbitrary mask
+      tun_metadata31: arbitrary mask
+      tun_metadata32: arbitrary mask
+      tun_metadata33: arbitrary mask
+      tun_metadata34: arbitrary mask
+      tun_metadata35: arbitrary mask
+      tun_metadata36: arbitrary mask
+      tun_metadata37: arbitrary mask
+      tun_metadata38: arbitrary mask
+      tun_metadata39: arbitrary mask
+      tun_metadata40: arbitrary mask
+      tun_metadata41: arbitrary mask
+      tun_metadata42: arbitrary mask
+      tun_metadata43: arbitrary mask
+      tun_metadata44: arbitrary mask
+      tun_metadata45: arbitrary mask
+      tun_metadata46: arbitrary mask
+      tun_metadata47: arbitrary mask
+      tun_metadata48: arbitrary mask
+      tun_metadata49: arbitrary mask
+      tun_metadata50: arbitrary mask
+      tun_metadata51: arbitrary mask
+      tun_metadata52: arbitrary mask
+      tun_metadata53: arbitrary mask
+      tun_metadata54: arbitrary mask
+      tun_metadata55: arbitrary mask
+      tun_metadata56: arbitrary mask
+      tun_metadata57: arbitrary mask
+      tun_metadata58: arbitrary mask
+      tun_metadata59: arbitrary mask
+      tun_metadata60: arbitrary mask
+      tun_metadata61: arbitrary mask
+      tun_metadata62: arbitrary mask
+      tun_metadata63: arbitrary mask
+      metadata: arbitrary mask
+      in_port: exact match or wildcard
+      in_port_oxm: exact match or wildcard
+      actset_output: exact match or wildcard
+      pkt_mark: arbitrary mask
+      ct_state: arbitrary mask
+      ct_zone: exact match or wildcard
+      ct_mark: arbitrary mask
+      ct_label: arbitrary mask
+      reg0: arbitrary mask
+      reg1: arbitrary mask
+      reg2: arbitrary mask
+      reg3: arbitrary mask
+      reg4: arbitrary mask
+      reg5: arbitrary mask
+      reg6: arbitrary mask
+      reg7: arbitrary mask
+      xreg0: arbitrary mask
+      xreg1: arbitrary mask
+      xreg2: arbitrary mask
+      xreg3: arbitrary mask
+      eth_src: arbitrary mask
+      eth_dst: arbitrary mask
+      eth_type: exact match or wildcard
+      vlan_tci: arbitrary mask
+      vlan_vid: arbitrary mask
+      vlan_pcp: exact match or wildcard
+      mpls_label: exact match or wildcard
+      mpls_tc: exact match or wildcard
+      mpls_bos: exact match or wildcard
+      ip_src: arbitrary mask
+      ip_dst: arbitrary mask
+      ipv6_src: arbitrary mask
+      ipv6_dst: arbitrary mask
+      ipv6_label: arbitrary mask
+      nw_proto: exact match or wildcard
+      nw_tos: exact match or wildcard
+      ip_dscp: exact match or wildcard
+      nw_ecn: exact match or wildcard
+      nw_ttl: exact match or wildcard
+      ip_frag: arbitrary mask
+      arp_op: exact match or wildcard
+      arp_spa: arbitrary mask
+      arp_tpa: arbitrary mask
+      arp_sha: arbitrary mask
+      arp_tha: arbitrary mask
+      tcp_src: arbitrary mask
+      tcp_dst: arbitrary mask
+      tcp_flags: arbitrary mask
+      udp_src: arbitrary mask
+      udp_dst: arbitrary mask
+      sctp_src: arbitrary mask
+      sctp_dst: arbitrary mask
+      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: arbitrary mask
+      nd_sll: arbitrary mask
+      nd_tll: arbitrary mask
+
+' $1
+}
+ditto() {
+    printf '  table %d ("%s"):
+    metadata: match=0xffffffffffffffff write=0xffffffffffffffff
+    eviction: not supported
+    vacancy events: not supported
+    features: none
+    max_entries=%d
+    instructions (table miss and others):
+      next tables: %d-253
+      (same instructions)
+      (same actions)
+    (same matching)
+
+' $1 $2 $3 `expr $1 + 1`
+}
+tail_tables() {
+echo '  table 252 ("table252"):
+    metadata: match=0xffffffffffffffff write=0xffffffffffffffff
+    eviction: not supported
+    vacancy events: not supported
+    features: none
+    max_entries=1000000
+    instructions (table miss and others):
+      next tables: 253
+      (same instructions)
+      (same actions)
+    (same matching)
+
+  table 253 ("table253"):
+    metadata: match=0xffffffffffffffff write=0xffffffffffffffff
+    eviction: not supported
+    vacancy events: not supported
+    features: none
+    max_entries=1000000
+    instructions (table miss and others):
+      instructions: meter,apply_actions,clear_actions,write_actions,write_metadata
+      (same actions)
+    (same matching)
+'
+}
+first_egress_table() {
+echo '  table 251 ("table251"):
+    metadata: match=0xffffffffffffffff write=0xffffffffffffffff
+    eviction: not supported
+    vacancy events: not supported
+    features: first egress table
+    max_entries=1000000
+    instructions (table miss and others):
+      next tables: 252-253
+      (same instructions)
+      Write-Actions and Apply-Actions features:
+        actions: 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 tun_ipv6_src tun_ipv6_dst tun_flags tun_gbp_id tun_gbp_flags tun_metadata0 tun_metadata1 tun_metadata2 tun_metadata3 tun_metadata4 tun_metadata5 tun_metadata6 tun_metadata7 tun_metadata8 tun_metadata9 tun_metadata10 tun_metadata11 tun_metadata12 tun_metadata13 tun_metadata14 tun_metadata15 tun_metadata16 tun_metadata17 tun_metadata18 tun_metadata19 tun_metadata20 tun_metadata21 tun_metadata22 tun_metadata23 tun_metadata24 tun_metadata25 tun_metadata26 tun_metadata27 tun_metadata28 tun_metadata29 tun_metadata30 tun_metadata31 tun_metadata32 tun_metadata33 tun_metadata34 tun_metadata35 tun_metadata36 tun_metadata37 tun_metadata38 tun_metadata39 tun_metadata40 tun_metadata41 tun_metadata42 tun_metadata43 tun_metadata44 tun_metadata45 tun_metadata46 tun_metadata47 tun_metadata48 tun_metadata49 tun_metadata50 tun_metadata51 tun_metadata52 tun_metadata53 tun_metadata54 tun_metadata55 tun_metadata56 tun_metadata57 tun_metadata5
 8 tun_metadata59 tun_metadata60 tun_metadata61 tun_metadata62 tun_metadata63 metadata in_port in_port_oxm pkt_mark ct_mark ct_label reg0 reg1 reg2 reg3 reg4 reg5 reg6 reg7 xreg0 xreg1 xreg2 xreg3 eth_src eth_dst vlan_tci vlan_vid vlan_pcp mpls_label mpls_tc ip_src ip_dst ipv6_src ipv6_dst ipv6_label 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 icmp_type icmp_code icmpv6_type icmpv6_code nd_target nd_sll nd_tll
+    (same matching)
+'
+}
+(head_table classifier
+ for i in `seq 1 251`; do
+     ditto $i table$i 1000000
+ done
+ tail_tables) > expout
+AT_CHECK([ovs-ofctl -O OpenFlow15 dump-table-features br0], [0], [expout])
+# Set first egress table.
+ovs-ofctl -O Openflow15 set-first-egress-table br0 251
+
+# Check that the configuration was updated.
+(head_table classifier
+ for i in `seq 1 250`; do
+     ditto $i table$i 1000000
+ done
+ first_egress_table
+ tail_tables) > expout
+AT_CHECK([ovs-ofctl -O OpenFlow15 dump-table-features br0], [0], [expout])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
 AT_SETUP([ofproto - table description (OpenFlow 1.4)])
 OVS_VSWITCHD_START
 (x=0
diff --git a/utilities/ovs-ofctl.c b/utilities/ovs-ofctl.c
index 96d6c89..106fdde 100644
--- a/utilities/ovs-ofctl.c
+++ b/utilities/ovs-ofctl.c
@@ -343,6 +343,7 @@  usage(void)
            "  dump-desc SWITCH            print switch description\n"
            "  dump-tables SWITCH          print table stats\n"
            "  dump-table-features SWITCH  print table features\n"
+           "  set-first-egress-table SWITCH TABLE  set first egress table\n"
            "  dump-table-desc SWITCH      print table description (OF1.4+)\n"
            "  mod-port SWITCH IFACE ACT   modify port behavior\n"
            "  mod-table SWITCH MOD        modify flow table behavior\n"
@@ -713,9 +714,9 @@  ofctl_dump_table_features(struct ovs_cmdl_context *ctx)
 {
     struct ofpbuf *request;
     struct vconn *vconn;
-
+    struct ofputil_table_features *tf = NULL;
     open_vconn(ctx->argv[1], &vconn);
-    request = ofputil_encode_table_features_request(vconn_get_version(vconn));
+    request = ofputil_encode_table_features_request(tf, vconn_get_version(vconn));
 
     /* The following is similar to dump_trivial_transaction(), but it
      * maintains the previous 'ofputil_table_features' from one stats reply
@@ -788,6 +789,35 @@  ofctl_dump_table_features(struct ovs_cmdl_context *ctx)
 }
 
 static void
+ofctl_set_first_egress_table(struct ovs_cmdl_context *ctx)
+{
+    uint32_t usable_versions;
+    struct ofputil_table_features tf;
+    struct vconn *vconn;
+    char *error;
+
+    error = parse_ofp_table_features(&tf, ctx->argv[2], &usable_versions);
+    if (error) {
+        ovs_fatal(0, "%s", error);
+    }
+
+    uint32_t allowed_versions = get_allowed_ofp_versions();
+    if (!(allowed_versions & usable_versions)) {
+        struct ds versions = DS_EMPTY_INITIALIZER;
+        ofputil_format_version_bitmap_names(&versions, usable_versions);
+        ovs_fatal(0, "set_first_egress_table '%s' requires one of the OpenFlow "
+                  "versions %s (use -O)",
+                  ctx->argv[2], ds_cstr(&versions));
+    }
+    mask_allowed_ofp_versions(usable_versions);
+
+    open_vconn(ctx->argv[1], &vconn);
+    transact_noreply(vconn, ofputil_encode_table_features_request(&tf,
+                                            vconn_get_version(vconn)));
+    vconn_close(vconn);
+}
+
+static void
 ofctl_dump_table_desc(struct ovs_cmdl_context *ctx)
 {
     struct ofpbuf *request;
@@ -3875,6 +3905,8 @@  static const struct ovs_cmdl_command all_commands[] = {
       1, 1, ofctl_dump_tables },
     { "dump-table-features", "switch",
       1, 1, ofctl_dump_table_features },
+    { "set-first-egress-table", "switch table",
+      2, 2, ofctl_set_first_egress_table },
     { "dump-table-desc", "switch",
       1, 1, ofctl_dump_table_desc },
     { "dump-flows", "switch",