[ovs-dev] [PATCH v2] ofctl: This patch add support for setting the first egress table for egress processing.

niti1489 at gmail.com niti1489 at gmail.com
Wed Sep 9 08:37:42 UTC 2015


From: Niti Rohilla <niti.rohilla at 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 at tcs.com>
---
Difference between v1->v2:
- Rebased with latest master
- Extra line has been removed
 include/openflow/openflow-1.5.h | 168 ++++++++++++++
 lib/ofp-msgs.h                  |  16 +-
 lib/ofp-parse.c                 |  24 ++
 lib/ofp-parse.h                 |   6 +
 lib/ofp-print.c                 |   6 +
 lib/ofp-util.c                  | 500 +++++++++++++++++++++++++++++++++++-----
 lib/ofp-util.h                  |  13 +-
 ofproto/ofproto-provider.h      |   5 +
 ofproto/ofproto.c               |  29 ++-
 tests/ofp-print.at              | 222 ++++++++++++++++++
 tests/ofproto.at                | 227 ++++++++++++++++++
 utilities/ovs-ofctl.c           |  36 ++-
 12 files changed, 1190 insertions(+), 62 deletions(-)

diff --git a/include/openflow/openflow-1.5.h b/include/openflow/openflow-1.5.h
index b3deb2d..20cc0b8 100644
--- a/include/openflow/openflow-1.5.h
+++ b/include/openflow/openflow-1.5.h
@@ -167,4 +167,172 @@ struct ofp15_group_desc_stats {
 };
 OFP_ASSERT(sizeof(struct ofp15_group_desc_stats) == 16);
 
+/* Table Features request commands */
+enum ofp15_table_features_command {
+    OFPTFC_REPLACE = 0,    /* Replace full pipeline. */
+    OFPTFC_MODIFY  = 1,    /* Modify flow tables capabilities. */
+    OFPTFC_ENABLE  = 2,    /* Enable flow tables in the pipeline. */
+    OFPTFC_DISABLE = 3,    /* Disable flow tables in pipeline. */
+};
+
+/* 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. */
+};
+
+/* Common header for all Table Feature Properties */
+struct ofp15_table_feature_prop_header {
+    ovs_be16    type;   /* One of OFPTFPT_*. */
+    ovs_be16    length; /* Length in bytes of this property. */
+};
+OFP_ASSERT(sizeof(struct ofp15_table_feature_prop_header) == 4);
+
+/* Body for ofp_multipart_request of type OFPMP_TABLE_FEATURES./
+ * Body of reply to OFPMP_TABLE_FEATURES request. */
+struct ofp15_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 command;          /* One of OFPTFC_*. */
+    ovs_be32 features;        /* Bitmap of OFPTFF_* values. */
+    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. */
+
+    /* In OF1.3 this field was named 'config' and it was useless because OF1.3
+     * did not define any OFPTC_* bits.
+     *
+     * OF1.4 renamed this field to 'capabilities' and added OFPTC14_EVICTION
+     * and OFPTC14_VACANCY_EVENTS. */
+    ovs_be32 capabilities;    /* Bitmap of OFPTC_* values */
+
+    ovs_be32 max_entries;     /* Max number of entries supported. */
+
+    /* Table Feature Property list */
+    /* struct ofp15_table_feature_prop_header properties[0]; */
+};
+OFP_ASSERT(sizeof(struct ofp15_table_features) == 64);
+
+/* Table Feature property types.
+ * Low order bit cleared indicates a property for a regular Flow Entry.
+ * Low order bit set indicates a property for the Table-Miss Flow Entry. */
+enum ofp15_table_feature_prop_type {
+    OFPTFPT15_INSTRUCTIONS         = 0, /* Instructions property. */
+    OFPTFPT15_INSTRUCTIONS_MISS    = 1, /* Instructions for table-miss. */
+    OFPTFPT15_NEXT_TABLES          = 2, /* Next Table property. */
+    OFPTFPT15_NEXT_TABLES_MISS     = 3, /* Next Table for table-miss. */
+    OFPTFPT15_WRITE_ACTIONS        = 4, /* Write Actions property. */
+    OFPTFPT15_WRITE_ACTIONS_MISS   = 5, /* Write Actions for table-miss. */
+    OFPTFPT15_APPLY_ACTIONS        = 6, /* Apply Actions property. */
+    OFPTFPT15_APPLY_ACTIONS_MISS   = 7, /* Apply Actions for table-miss. */
+    OFPTFPT15_MATCH                = 8, /* Match property. */
+    OFPTFPT15_WILDCARDS            = 10, /* Wildcards property. */
+    OFPTFPT15_WRITE_SETFIELD       = 12, /* Write Set-Field property. */
+    OFPTFPT15_WRITE_SETFIELD_MISS  = 13, /* Write Set-Field for table-miss. */
+    OFPTFPT15_APPLY_SETFIELD       = 14, /* Apply Set-Field property. */
+    OFPTFPT15_APPLY_SETFIELD_MISS  = 15, /* Apply Set-Field for table-miss. */
+    OFPTFPT15_TABLE_SYNC_FROM      = 16, /* Table synchronisation property. */
+    OFPTFPT15_WRITE_COPYFIELD      = 18, /* Write Copy-Field property. */
+    OFPTFPT15_WRITE_COPYFIELD_MISS = 19, /* Write Copy-Field for table-miss. */
+    OFPTFPT15_APPLY_COPYFIELD      = 20, /* Apply Copy-Field property. */
+    OFPTFPT15_APPLY_COPYFIELD_MISS = 21, /* Apply Copy-Field for table-miss. */
+    OFPTFPT15_PACKET_TYPES         = 22, /* Packet types property. */
+    OFPTFPT15_EXPERIMENTER         = 0xFFFE, /* Experimenter property. */
+    OFPTFPT15_EXPERIMENTER_MISS    = 0xFFFF, /* Experimenter for table-miss. */
+};
+
+/* Instructions property */
+struct ofp15_table_feature_prop_instructions {
+    ovs_be16    type;    /* One of OFPTFPT15_INSTRUCTIONS,
+                          * OFPTFPT15_INSTRUCTIONS_MISS. */
+    ovs_be16    length;  /* Length in bytes of this property. */
+    /* Followed by:
+     *   - Exactly (length - 4) bytes containing the instruction ids, then
+     *   - Exactly (length + 7)/8*8 - (length) (between 0 and 7)
+     *     bytes of all-zero bytes */
+    /* struct ofp11_instruction instruction_ids[0];  List of instructions
+                                                     without any data */
+};
+OFP_ASSERT(sizeof(struct ofp15_table_feature_prop_instructions) == 4);
+
+/* Next Tables property */
+struct ofp15_table_feature_prop_next_tables {
+    ovs_be16    type;   /* One of OFPTFPT15_NEXT_TABLES,
+                         * OFPTFPT15_NEXT_TABLES_MISS.
+                         * OFPTFPT15_TABLE_SYNC_FROM. */
+    ovs_be16    length; /* Length in bytes of this property. */
+    /* Followed by:
+     *   - Exactly (length - 4) bytes containing the table_ids, then
+     *   - Exactly (length + 7)/8*8 - (length) (between 0 and 7)
+     *     bytes of all-zero bytes */
+    /* uint8_t     next_table_ids[0]; */
+};
+OFP_ASSERT(sizeof(struct ofp15_table_feature_prop_next_tables) == 4);
+
+/* Actions property */
+struct ofp15_table_feature_prop_actions {
+    ovs_be16    type;   /* One of OFPTFPT15_WRITE_ACTIONS,
+                         * OFPTFPT15_WRITE_ACTIONS_MISS,
+                         * OFPTFPT15_APPLY_ACTIONS,
+                         * OFPTFPT15_APPLY_ACTIONS_MISS. */
+    ovs_be16    length; /* Length in bytes of this property. */
+    /* Followed by:
+     *   - Exactly (length - 4) bytes containing the action_ids, then
+     *   - Exactly (length + 7)/8*8 - (length) (between 0 and 7)
+     *     bytes of all-zero bytes */
+    /* struct ofp_action_header action_ids[0];     List of actions
+                                                   without any data */
+};
+OFP_ASSERT(sizeof(struct ofp15_table_feature_prop_actions) == 4);
+
+/* Match, Wildcard or Set-Field property */
+struct ofp15_table_feature_prop_oxm {
+    ovs_be16    type;   /* One of OFPTFPT15_MATCH, OFPTFPT15_WILDCARDS,
+                         * OFPTFPT15_WRITE_SETFIELD,
+                         * OFPTFPT15_WRITE_SETFIELD_MISS,
+                         * OFPTFPT15_APPLY_SETFIELD,
+                         * OFPTFPT15_APPLY_SETFIELD_MISS.
+                         * OFPTFPT15_WRITE_COPYFIELD,
+                         * OFPTFPT15_WRITE_COPYFIELD_MISS,
+                         * OFPTFPT15_APPLY_COPYFIELD,
+                         * OFPTFPT15_APPLY_COPYFIELD_MISS. */
+    ovs_be16    length; /* Length in bytes of this property. */
+    /* Followed by:
+     *   - Exactly (length - 4) bytes containing the oxm_ids, then
+     *   - Exactly (length + 7)/8*8 - (length) (between 0 and 7)
+     *     bytes of all-zero bytes */
+    /* ovs_be32    oxm_ids[0];     Array of OXM headers */
+};
+OFP_ASSERT(sizeof(struct ofp15_table_feature_prop_oxm) == 4);
+
+/* Packet types property */
+struct ofp15_table_feature_prop_oxm_values {
+    ovs_be16 type;    /* OFPTFPT15_PACKET_TYPES. */
+    ovs_be16 length;  /* Length in bytes of this property. */
+    /* Followed by:
+     *   - Exactly (length - 4) bytes containing the oxm values, then
+     *   - Exactly (length + 7)/8*8 - (length) (between 0 and 7)
+     *     bytes of all-zero bytes */
+    /*uint32_t oxm_values[0];     Array of OXM values */
+};
+OFP_ASSERT(sizeof(struct ofp15_table_feature_prop_oxm_values) == 4);
+
+/* Experimenter table feature property */
+struct ofp15_table_feature_prop_experimenter {
+    ovs_be16    type;     /* One of OFPTFPT15_EXPERIMENTER,
+                           * OFPTFPT15_EXPERIMENTER_MISS. */
+    ovs_be16    length;   /* Length in bytes of this property. */
+    ovs_be32    experimenter; /* Experimenter ID which takes the same form
+                               * as in struct ofp_experimenter_header. */
+    ovs_be32    exp_type;     /* Experimenter defined. */
+    /* Followed by:
+     *   - Exactly (length - 12) bytes containing the experimenter data, then
+     *   - Exactly (length + 7)/8*8 - (length) (between 0 and 7)
+     *     bytes of all-zero bytes */
+    /* ovs_be32    experimenter_data[0]; */
+};
+OFP_ASSERT(sizeof(struct ofp15_table_feature_prop_experimenter) == 12);
+
 #endif /* openflow/openflow-1.5.h */
diff --git a/lib/ofp-msgs.h b/lib/ofp-msgs.h
index 8558e58..38b850f 100644
--- a/lib/ofp-msgs.h
+++ b/lib/ofp-msgs.h
@@ -369,12 +369,18 @@ enum ofpraw {
     /* OFPST 1.3+ (11): struct ofp13_meter_features. */
     OFPRAW_OFPST13_METER_FEATURES_REPLY,
 
-    /* OFPST 1.3+ (12): void. */
+    /* OFPST 1.3-1.4 (12): void. */
     OFPRAW_OFPST13_TABLE_FEATURES_REQUEST,
 
-    /* OFPST 1.3+ (12): struct ofp13_table_features, uint8_t[8][]. */
+    /* OFPST 1.5+ (12): struct ofp15_table_features[]. */
+    OFPRAW_OFPST15_TABLE_FEATURES_REQUEST,
+
+    /* OFPST 1.3-1.4 (12): struct ofp13_table_features, uint8_t[8][]. */
     OFPRAW_OFPST13_TABLE_FEATURES_REPLY,
 
+    /* OFPST 1.5+ (12): struct ofp15_table_features, uint8_t[8][]. */
+    OFPRAW_OFPST15_TABLE_FEATURES_REPLY,
+
     /* OFPST 1.4+ (15): void. */
     OFPRAW_OFPST14_TABLE_DESC_REQUEST,
 
@@ -622,9 +628,11 @@ enum ofptype {
 
     OFPTYPE_METER_FEATURES_STATS_REPLY, /* OFPRAW_OFPST13_METER_FEATURES_REPLY. */
 
-    OFPTYPE_TABLE_FEATURES_STATS_REQUEST, /* OFPRAW_OFPST13_TABLE_FEATURES_REQUEST. */
+    OFPTYPE_TABLE_FEATURES_STATS_REQUEST, /* OFPRAW_OFPST13_TABLE_FEATURES_REQUEST.
+                                           * OFPRAW_OFPST15_TABLE_FEATURES_REQUEST. */
 
-    OFPTYPE_TABLE_FEATURES_STATS_REPLY, /* OFPRAW_OFPST13_TABLE_FEATURES_REPLY. */
+    OFPTYPE_TABLE_FEATURES_STATS_REPLY, /* OFPRAW_OFPST13_TABLE_FEATURES_REPLY.
+                                         * OFPRAW_OFPST15_TABLE_FEATURES_REPLY. */
 
     OFPTYPE_TABLE_DESC_REQUEST,      /* OFPRAW_OFPST14_TABLE_DESC_REQUEST. */
 
diff --git a/lib/ofp-parse.c b/lib/ofp-parse.c
index eaaa8ba..10e15b2 100644
--- a/lib/ofp-parse.c
+++ b/lib/ofp-parse.c
@@ -1652,3 +1652,27 @@ parse_ofp_geneve_table_mod_str(struct ofputil_geneve_table_mod *gtm,
 
     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 b64a32e..ca87303 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_geneve_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 6e32d4d..2a7bc37 100644
--- a/lib/ofp-print.c
+++ b/lib/ofp-print.c
@@ -2792,6 +2792,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 5331f8c..8100f54 100644
--- a/lib/ofp-util.c
+++ b/lib/ofp-util.c
@@ -4454,6 +4454,12 @@ ofputil_encode_port_mod(const struct ofputil_port_mod *pm,
 
     return b;
 }
+
+/* Variable to store the table-id of the first egress table. If it is set to
+ * 0 it means that egress processing is not enabled.
+ */
+int first_egress_table = 0;
+
 
 /* Table features. */
 
@@ -4494,6 +4500,47 @@ parse_action_bitmap(struct ofpbuf *payload, enum ofp_version ofp_version,
 }
 
 static enum ofperr
+parse15_action_bitmap(struct ofpbuf *payload, enum ofp_version ofp_version,
+                      uint8_t table_id, uint64_t *ofpacts)
+{
+    uint32_t types = 0;
+
+    while (payload->size > 0) {
+        uint16_t type;
+        enum ofperr error;
+
+        error = ofputil_pull_property__(payload, NULL, 1, &type);
+        if (error) {
+            return error;
+        }
+
+        /* If first egress table is set then flow tables used for egress
+         * processing must forbid the use of output actior group action in
+         * write-action instrcution 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 as ofpact_type cannot be compared directly with
+         * 'type', which is of type ofpat.
+         */
+        if (type < CHAR_BIT * sizeof types) {
+            if (first_egress_table && first_egress_table <= table_id) {
+                if (type == 0 || type == 22) {
+                    continue;
+                } else {
+                    types |= 1u << type;
+                }
+            } else {
+                    types |= 1u << type;
+            }
+        }
+    }
+
+    *ofpacts = ofpact_bitmap_from_openflow(htonl(types), ofp_version);
+    return 0;
+}
+
+static enum ofperr
 parse_instruction_ids(struct ofpbuf *payload, bool loose, uint32_t *insts)
 {
     *insts = 0;
@@ -4575,25 +4622,10 @@ parse_oxms(struct ofpbuf *payload, bool loose,
     return 0;
 }
 
-/* Converts an OFPMP_TABLE_FEATURES request or reply in 'msg' into an abstract
- * ofputil_table_features in 'tf'.
- *
- * If 'loose' is true, this function ignores properties and values that it does
- * not understand, as a controller would want to do when interpreting
- * capabilities provided by a switch.  If 'loose' is false, this function
- * treats unknown properties and values as an error, as a switch would want to
- * do when interpreting a configuration request made by a controller.
- *
- * A single OpenFlow message can specify features for multiple tables.  Calling
- * this function multiple times for a single 'msg' iterates through the tables
- * in the message.  The caller must initially leave 'msg''s layer pointers null
- * and not modify them between calls.
- *
- * Returns 0 if successful, EOF if no tables were left in this 'msg', otherwise
- * a positive "enum ofperr" value. */
-int
-ofputil_decode_table_features(struct ofpbuf *msg,
-                              struct ofputil_table_features *tf, bool loose)
+static int
+ofputil_decode_ofpst13_table_features(struct ofpbuf *msg,
+                                      struct ofputil_table_features *tf,
+                                      bool loose)
 {
     const struct ofp_header *oh;
     struct ofp13_table_features *otf;
@@ -4601,10 +4633,6 @@ ofputil_decode_table_features(struct ofpbuf *msg,
     unsigned int len;
 
     memset(tf, 0, sizeof *tf);
-
-    if (!msg->header) {
-        ofpraw_pull_assert(msg);
-    }
     oh = msg->header;
 
     if (!msg->size) {
@@ -4641,6 +4669,7 @@ ofputil_decode_table_features(struct ofpbuf *msg,
         tf->supports_vacancy_events = -1;
     }
     tf->max_entries = ntohl(otf->max_entries);
+    tf->features = -1;
 
     while (properties.size > 0) {
         struct ofpbuf payload;
@@ -4742,10 +4771,242 @@ ofputil_decode_table_features(struct ofpbuf *msg,
     return 0;
 }
 
+static int
+ofputil_decode_ofpst15_table_features(struct ofpbuf *msg,
+                                      struct ofputil_table_features *tf,
+                                      bool loose, enum ofpraw raw)
+{
+    const struct ofp_header *oh;
+    struct ofp15_table_features *otf;
+    struct ofpbuf properties;
+    unsigned int len;
+    uint32_t features;
+    uint32_t caps;
+
+    memset(tf, 0, sizeof *tf);
+    oh = msg->header;
+
+    if (!msg->size) {
+        return EOF;
+    }
+
+    if (msg->size < sizeof *otf) {
+        return OFPERR_OFPBPC_BAD_LEN;
+    }
+
+    otf = msg->data;
+    len = ntohs(otf->length);
+    if (len < sizeof *otf || len % 8 || len > msg->size) {
+        return OFPERR_OFPBPC_BAD_LEN;
+    }
+    ofpbuf_use_const(&properties, ofpbuf_pull(msg, len), len);
+    ofpbuf_pull(&properties, sizeof *otf);
+
+    tf->table_id = otf->table_id;
+    if (tf->table_id == OFPTT_ALL) {
+        return OFPERR_OFPTFFC_BAD_TABLE;
+    }
+
+    if (raw == OFPRAW_OFPST15_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;
+    tf->miss_config = OFPUTIL_TABLE_MISS_DEFAULT;
+
+    caps = ntohl(otf->capabilities);
+    tf->supports_eviction = (caps & OFPTC14_EVICTION) != 0;
+    tf->supports_vacancy_events = (caps & OFPTC14_VACANCY_EVENTS) != 0;
+
+    tf->max_entries = ntohl(otf->max_entries);
+
+    /* Return an error if any flag other than OFPTFF_FIRST_EGRESS is set. */
+    features = ntohl(otf->features);
+    if ((features & OFPTFF_FIRST_EGRESS) != 0) {
+        tf->features = OFPTFF_FIRST_EGRESS;
+        first_egress_table = tf->table_id;
+    } else if ((features & OFPTFF_INGRESS_TABLE) != 0 ||
+               (features & OFPTFF_EGRESS_TABLE) != 0 ) {
+        return OFPERR_OFPBFC_BAD_FLAGS;
+    }
+
+    while (properties.size > 0) {
+        struct ofpbuf payload;
+        enum ofperr error;
+        uint16_t type;
+
+        error = pull_table_feature_property(&properties, &payload, &type);
+        if (error) {
+            return error;
+        }
+
+        switch ((enum ofp15_table_feature_prop_type) type) {
+        case OFPTFPT15_INSTRUCTIONS:
+            error = parse_instruction_ids(&payload, loose,
+                                          &tf->nonmiss.instructions);
+            break;
+
+        case OFPTFPT15_INSTRUCTIONS_MISS:
+            error = parse_instruction_ids(&payload, loose,
+                                          &tf->miss.instructions);
+            break;
+
+        case OFPTFPT15_NEXT_TABLES:
+            error = parse_table_features_next_table(&payload,
+                                                    tf->nonmiss.next);
+            break;
+
+        case OFPTFPT15_NEXT_TABLES_MISS:
+            error = parse_table_features_next_table(&payload, tf->miss.next);
+            break;
+
+        case OFPTFPT15_WRITE_ACTIONS:
+            error = parse15_action_bitmap(&payload, oh->version, tf->table_id,
+                                          &tf->nonmiss.write.ofpacts);
+            break;
+
+        case OFPTFPT15_WRITE_ACTIONS_MISS:
+            error = parse15_action_bitmap(&payload, oh->version, tf->table_id,
+                                          &tf->miss.write.ofpacts);
+            break;
+
+        case OFPTFPT15_APPLY_ACTIONS:
+            error = parse_action_bitmap(&payload, oh->version,
+                                        &tf->nonmiss.apply.ofpacts);
+            break;
+
+        case OFPTFPT15_APPLY_ACTIONS_MISS:
+            error = parse_action_bitmap(&payload, oh->version,
+                                        &tf->miss.apply.ofpacts);
+            break;
+
+        case OFPTFPT15_MATCH:
+            error = parse_oxms(&payload, loose, &tf->match, &tf->mask);
+            break;
+
+        case OFPTFPT15_WILDCARDS:
+            error = parse_oxms(&payload, loose, &tf->wildcard, NULL);
+            break;
+
+        case OFPTFPT15_WRITE_SETFIELD:
+            error = parse_oxms(&payload, loose,
+                               &tf->nonmiss.write.set_fields, NULL);
+            break;
+
+        case OFPTFPT15_WRITE_SETFIELD_MISS:
+            error = parse_oxms(&payload, loose,
+                               &tf->miss.write.set_fields, NULL);
+            break;
+
+        case OFPTFPT15_APPLY_SETFIELD:
+            error = parse_oxms(&payload, loose,
+                               &tf->nonmiss.apply.set_fields, NULL);
+            break;
+
+        case OFPTFPT15_APPLY_SETFIELD_MISS:
+            error = parse_oxms(&payload, loose,
+                               &tf->miss.apply.set_fields, NULL);
+            break;
+
+        case OFPTFPT15_TABLE_SYNC_FROM:
+        case OFPTFPT15_WRITE_COPYFIELD:
+        case OFPTFPT15_WRITE_COPYFIELD_MISS:
+        case OFPTFPT15_APPLY_COPYFIELD:
+        case OFPTFPT15_APPLY_COPYFIELD_MISS:
+        case OFPTFPT15_PACKET_TYPES:
+            log_property(loose, "unsupported table features property %"PRIu16,
+                         type);
+            break;
+
+        case OFPTFPT15_EXPERIMENTER:
+        case OFPTFPT15_EXPERIMENTER_MISS:
+        default:
+            log_property(loose, "unknown table features property %"PRIu16,
+                         type);
+            error = loose ? 0 : OFPERR_OFPBPC_BAD_TYPE;
+            break;
+        }
+        if (error) {
+            return error;
+        }
+    }
+
+    /* Fix inconsistencies:
+     *
+     *     - Turn on 'match' bits that are set in 'mask', because maskable
+     *       fields are matchable.
+     *
+     *     - Turn on 'wildcard' bits that are set in 'mask', because a field
+     *       that is arbitrarily maskable can be wildcarded entirely.
+     *
+     *     - Turn off 'wildcard' bits that are not in 'match', because a field
+     *       must be matchable for it to be meaningfully wildcarded. */
+    bitmap_or(tf->match.bm, tf->mask.bm, MFF_N_IDS);
+    bitmap_or(tf->wildcard.bm, tf->mask.bm, MFF_N_IDS);
+    bitmap_and(tf->wildcard.bm, tf->match.bm, MFF_N_IDS);
+
+    return 0;
+}
+
+/* Converts an OFPMP_TABLE_FEATURES request or reply in 'msg' into an abstract
+ * ofputil_table_features in 'tf'.
+ *
+ * If 'loose' is true, this function ignores properties and values that it does
+ * not understand, as a controller would want to do when interpreting
+ * capabilities provided by a switch.  If 'loose' is false, this function
+ * treats unknown properties and values as an error, as a switch would want to
+ * do when interpreting a configuration request made by a controller.
+ *
+ * A single OpenFlow message can specify features for multiple tables.  Calling
+ * this function multiple times for a single 'msg' iterates through the tables
+ * in the message.  The caller must initially leave 'msg''s layer pointers null
+ * and not modify them between calls.
+ *
+ * Returns 0 if successful, EOF if no tables were left in this 'msg', otherwise
+ * a positive "enum ofperr" value. */
+int
+ofputil_decode_table_features(struct ofpbuf *msg,
+                              struct ofputil_table_features *tf, bool loose)
+{
+    enum ofpraw raw;
+    enum ofperr error;
+    error = (msg->header ? ofpraw_decode(&raw, msg->header)
+             : ofpraw_pull(&raw, msg));
+    if (error) {
+        return error;
+    }
+
+    switch ((int)raw) {
+    case OFPRAW_OFPST13_TABLE_FEATURES_REQUEST:
+    case OFPRAW_OFPST13_TABLE_FEATURES_REPLY:
+        return ofputil_decode_ofpst13_table_features(msg, tf, loose);
+
+    case OFPRAW_OFPST15_TABLE_FEATURES_REQUEST:
+    case OFPRAW_OFPST15_TABLE_FEATURES_REPLY:
+        return ofputil_decode_ofpst15_table_features(msg, tf, loose, raw);
+
+    default:
+        OVS_NOT_REACHED();
+    }
+}
+
 /* 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;
 
@@ -4757,10 +5018,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 ofp15_table_features *otf;
+
+        request = ofpraw_alloc(OFPRAW_OFPST15_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();
     }
@@ -4769,11 +5042,29 @@ ofputil_encode_table_features_request(enum ofp_version ofp_version)
 }
 
 static void
-put_fields_property(struct ofpbuf *reply,
-                    const struct mf_bitmap *fields,
-                    const struct mf_bitmap *masks,
-                    enum ofp13_table_feature_prop_type property,
-                    enum ofp_version version)
+put_ofpst13_fields_property(struct ofpbuf *reply,
+                            const struct mf_bitmap *fields,
+                            const struct mf_bitmap *masks,
+                            enum ofp13_table_feature_prop_type property,
+                            enum ofp_version version)
+{
+    size_t start_ofs;
+    int field;
+
+    start_ofs = start_property(reply, property);
+    BITMAP_FOR_EACH_1 (field, MFF_N_IDS, fields->bm) {
+        nx_put_header(reply, field, version,
+                      masks && bitmap_is_set(masks->bm, field));
+    }
+    end_property(reply, start_ofs);
+}
+
+static void
+put_ofpst15_fields_property(struct ofpbuf *reply,
+                            const struct mf_bitmap *fields,
+                            const struct mf_bitmap *masks,
+                            enum ofp15_table_feature_prop_type property,
+                            enum ofp_version version)
 {
     size_t start_ofs;
     int field;
@@ -4787,11 +5078,30 @@ put_fields_property(struct ofpbuf *reply,
 }
 
 static void
-put_table_action_features(struct ofpbuf *reply,
-                          const struct ofputil_table_action_features *taf,
-                          enum ofp13_table_feature_prop_type actions_type,
-                          enum ofp13_table_feature_prop_type set_fields_type,
-                          int miss_offset, enum ofp_version version)
+put_ofpst13_table_action_features(
+    struct ofpbuf *reply, const struct ofputil_table_action_features *taf,
+    enum ofp13_table_feature_prop_type actions_type,
+    enum ofp13_table_feature_prop_type set_fields_type,
+    int miss_offset, enum ofp_version version)
+{
+    size_t start_ofs;
+
+    start_ofs = start_property(reply, actions_type + miss_offset);
+    put_bitmap_properties(reply,
+                          ntohl(ofpact_bitmap_to_openflow(taf->ofpacts,
+                                                          version)));
+    end_property(reply, start_ofs);
+
+    put_ofpst13_fields_property(reply, &taf->set_fields, NULL,
+                                set_fields_type + miss_offset, version);
+}
+
+static void
+put_ofpst15_table_action_features(
+    struct ofpbuf *reply, const struct ofputil_table_action_features *taf,
+    enum ofp15_table_feature_prop_type actions_type,
+    enum ofp15_table_feature_prop_type set_fields_type,
+    int miss_offset, enum ofp_version version)
 {
     size_t start_ofs;
 
@@ -4801,12 +5111,12 @@ put_table_action_features(struct ofpbuf *reply,
                                                           version)));
     end_property(reply, start_ofs);
 
-    put_fields_property(reply, &taf->set_fields, NULL,
-                        set_fields_type + miss_offset, version);
+    put_ofpst15_fields_property(reply, &taf->set_fields, NULL,
+                                set_fields_type + miss_offset, version);
 }
 
 static void
-put_table_instruction_features(
+put_ofpst13_table_instruction_features(
     struct ofpbuf *reply, const struct ofputil_table_instruction_features *tif,
     int miss_offset, enum ofp_version version)
 {
@@ -4825,17 +5135,49 @@ put_table_instruction_features(
     }
     end_property(reply, start_ofs);
 
-    put_table_action_features(reply, &tif->write,
-                              OFPTFPT13_WRITE_ACTIONS,
-                              OFPTFPT13_WRITE_SETFIELD, miss_offset, version);
-    put_table_action_features(reply, &tif->apply,
-                              OFPTFPT13_APPLY_ACTIONS,
-                              OFPTFPT13_APPLY_SETFIELD, miss_offset, version);
+    put_ofpst13_table_action_features(reply, &tif->write,
+                                      OFPTFPT13_WRITE_ACTIONS,
+                                      OFPTFPT13_WRITE_SETFIELD,
+                                      miss_offset, version);
+    put_ofpst13_table_action_features(reply, &tif->apply,
+                                      OFPTFPT13_APPLY_ACTIONS,
+                                      OFPTFPT13_APPLY_SETFIELD,
+                                      miss_offset, version);
 }
 
-void
-ofputil_append_table_features_reply(const struct ofputil_table_features *tf,
-                                    struct ovs_list *replies)
+static void
+put_ofpst15_table_instruction_features(
+    struct ofpbuf *reply, const struct ofputil_table_instruction_features *tif,
+    int miss_offset, enum ofp_version version)
+{
+    size_t start_ofs;
+    uint8_t table_id;
+
+    start_ofs = start_property(reply, OFPTFPT15_INSTRUCTIONS + miss_offset);
+    put_bitmap_properties(reply,
+                          ntohl(ovsinst_bitmap_to_openflow(tif->instructions,
+                                                           version)));
+    end_property(reply, start_ofs);
+
+    start_ofs = start_property(reply, OFPTFPT15_NEXT_TABLES + miss_offset);
+    BITMAP_FOR_EACH_1 (table_id, 255, tif->next) {
+        ofpbuf_put(reply, &table_id, 1);
+    }
+    end_property(reply, start_ofs);
+
+    put_ofpst15_table_action_features(reply, &tif->write,
+                                      OFPTFPT15_WRITE_ACTIONS,
+                                      OFPTFPT15_WRITE_SETFIELD,
+                                      miss_offset, version);
+    put_ofpst15_table_action_features(reply, &tif->apply,
+                                      OFPTFPT15_APPLY_ACTIONS,
+                                      OFPTFPT15_APPLY_SETFIELD,
+                                      miss_offset, version);
+}
+
+static void
+ofputil_ofpst13_table_features_reply(const struct ofputil_table_features *tf,
+                                     struct ovs_list *replies)
 {
     struct ofpbuf *reply = ofpbuf_from_list(list_back(replies));
     enum ofp_version version = ofpmp_version(replies);
@@ -4857,19 +5199,70 @@ ofputil_append_table_features_reply(const struct ofputil_table_features *tf,
     }
     otf->max_entries = htonl(tf->max_entries);
 
-    put_table_instruction_features(reply, &tf->nonmiss, 0, version);
-    put_table_instruction_features(reply, &tf->miss, 1, version);
+    put_ofpst13_table_instruction_features(reply, &tf->nonmiss, 0, version);
+    put_ofpst13_table_instruction_features(reply, &tf->miss, 1, version);
 
-    put_fields_property(reply, &tf->match, &tf->mask,
-                        OFPTFPT13_MATCH, version);
-    put_fields_property(reply, &tf->wildcard, NULL,
-                        OFPTFPT13_WILDCARDS, version);
+    put_ofpst13_fields_property(reply, &tf->match, &tf->mask,
+                                OFPTFPT13_MATCH, version);
+    put_ofpst13_fields_property(reply, &tf->wildcard, NULL,
+                                OFPTFPT13_WILDCARDS, version);
 
     otf = ofpbuf_at_assert(reply, start_ofs, sizeof *otf);
     otf->length = htons(reply->size - start_ofs);
     ofpmp_postappend(replies, start_ofs);
 }
 
+static void
+ofputil_ofpst15_table_features_reply(const struct ofputil_table_features *tf,
+                                     struct ovs_list *replies)
+{
+    struct ofpbuf *reply = ofpbuf_from_list(list_back(replies));
+    enum ofp_version version = ofpmp_version(replies);
+    size_t start_ofs = reply->size;
+    struct ofp15_table_features *otf;
+
+    otf = ofpbuf_put_zeros(reply, sizeof *otf);
+    otf->table_id = tf->table_id;
+    ovs_strlcpy(otf->name, tf->name, sizeof otf->name);
+    otf->metadata_match = tf->metadata_match;
+    otf->metadata_write = tf->metadata_write;
+    if (tf->supports_eviction) {
+        otf->capabilities |= htonl(OFPTC14_EVICTION);
+    }
+    if (tf->supports_vacancy_events) {
+        otf->capabilities |= htonl(OFPTC14_VACANCY_EVENTS);
+    }
+    otf->max_entries = htonl(tf->max_entries);
+
+    if ((tf->features & OFPTFF_FIRST_EGRESS) != 0) {
+        otf->features = htonl(OFPTFF_FIRST_EGRESS);
+    }
+    put_ofpst15_table_instruction_features(reply, &tf->nonmiss, 0, version);
+    put_ofpst15_table_instruction_features(reply, &tf->miss, 1, version);
+
+    put_ofpst15_fields_property(reply, &tf->match, &tf->mask,
+                                OFPTFPT15_MATCH, version);
+    put_ofpst15_fields_property(reply, &tf->wildcard, NULL,
+                                OFPTFPT15_WILDCARDS, version);
+
+    otf = ofpbuf_at_assert(reply, start_ofs, sizeof *otf);
+    otf->length = htons(reply->size - start_ofs);
+    ofpmp_postappend(replies, start_ofs);
+}
+
+void
+ofputil_append_table_features_reply(const struct ofputil_table_features *tf,
+                                    struct ovs_list *replies)
+{
+    enum ofp_version version = ofpmp_version(replies);
+
+    if (version < OFP15_VERSION) {
+        ofputil_ofpst13_table_features_reply(tf, replies);
+    } else {
+        ofputil_ofpst15_table_features_reply(tf, replies);
+    }
+}
+
 static enum ofperr
 parse_table_desc_eviction_property(struct ofpbuf *property,
                                    struct ofputil_table_desc *td)
@@ -5770,6 +6163,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:
diff --git a/lib/ofp-util.h b/lib/ofp-util.h
index 527a5ab..fc853c2 100644
--- a/lib/ofp-util.h
+++ b/lib/ofp-util.h
@@ -682,6 +682,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.
@@ -738,7 +747,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);
 
diff --git a/ofproto/ofproto-provider.h b/ofproto/ofproto-provider.h
index 117cd1f..ea4371e 100644
--- a/ofproto/ofproto-provider.h
+++ b/ofproto/ofproto-provider.h
@@ -258,6 +258,11 @@ struct oftable {
 
     atomic_ulong n_matched;
     atomic_ulong n_missed;
+
+   /* This flag indicates that this flow table is the first egress table, when
+    * a packet is output to a port, egress processing will start with this flow
+    * table. */
+    bool is_first_egress;
 };
 
 /* Assigns TABLE to each oftable, in turn, in OFPROTO.
diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
index e6c0351..aeecb78 100644
--- a/ofproto/ofproto.c
+++ b/ofproto/ofproto.c
@@ -3163,6 +3163,9 @@ query_tables(struct ofproto *ofproto,
         f->match = match;
         f->mask = mask;
         f->wildcard = match;
+        if (ofproto->tables[i].is_first_egress) {
+            f->features = OFPTFF_FIRST_EGRESS;
+        }
     }
 
     if (statsp) {
@@ -3553,12 +3556,34 @@ 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].is_first_egress) {
+                        ofproto->tables[i].is_first_egress = false;
+                        break;
+                    }
+                }
+                ofproto->tables[tf.table_id].is_first_egress = true;
+            }
+            return 0;
+        }
     }
 
     query_tables(ofproto, &features, NULL);
diff --git a/tests/ofp-print.at b/tests/ofp-print.at
index 35a6262..bd40aee 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 e3f08a8..2d524d9 100644
--- a/tests/ofproto.at
+++ b/tests/ofproto.at
@@ -1721,6 +1721,233 @@ 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_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_metadata58 tun_metadata59 tun_metad
 ata60 tun_metadata61 tun_metadata62 tun_metadata63 metadata in_port in_port_oxm pkt_mark 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 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_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
+      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 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_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_metadata58 tun_metadata59 tun_metad
 ata60 tun_metadata61 tun_metadata62 tun_metadata63 metadata in_port in_port_oxm pkt_mark 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 nd_target nd_sll nd_tll
+      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_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_metadata58 tun_metadata59 tun_metad
 ata60 tun_metadata61 tun_metadata62 tun_metadata63 metadata in_port in_port_oxm pkt_mark 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 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 75e84e2..1989137 100644
--- a/utilities/ovs-ofctl.c
+++ b/utilities/ovs-ofctl.c
@@ -338,6 +338,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"
@@ -728,9 +729,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_stats_transaction(), but it
      * maintains the previous 'ofputil_table_features' from one stats reply
@@ -803,6 +804,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, allowed_versions);
+        ovs_fatal(0, "set_first_egress_table '%s' requires one of the OpenFlow "
+                  "versions %s but none is enabled (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;
@@ -3694,6 +3724,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",
-- 
2.5.0




More information about the dev mailing list