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

niti1489 at gmail.com niti1489 at gmail.com
Wed Dec 23 08:07:12 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>
---
 include/openflow/openflow-1.5.h | 168 +++++++++++++++
 lib/ofp-actions.c               |  23 ++
 lib/ofp-actions.h               |   4 +-
 lib/ofp-msgs.h                  |  16 +-
 lib/ofp-parse.c                 |  24 +++
 lib/ofp-parse.h                 |   6 +
 lib/ofp-print.c                 |   6 +
 lib/ofp-util.c                  | 460 +++++++++++++++++++++++++++++++++++-----
 lib/ofp-util.h                  |  16 +-
 ofproto/ofproto-provider.h      |   6 +
 ofproto/ofproto.c               |  57 ++++-
 tests/ofp-print.at              | 222 +++++++++++++++++++
 tests/ofproto.at                | 233 ++++++++++++++++++++
 utilities/ovs-ofctl.c           |  36 +++-
 14 files changed, 1206 insertions(+), 71 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-actions.c b/lib/ofp-actions.c
index f900aa9..3137861 100644
--- a/lib/ofp-actions.c
+++ b/lib/ofp-actions.c
@@ -7828,3 +7828,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->ofpat == 0 || x->ofpat == 22) {
+            continue;
+        } else {
+            *ofpacts_bitmap |= (UINT64_C(1) << x->ofpact);
+        }
+    }
+}
diff --git a/lib/ofp-actions.h b/lib/ofp-actions.h
index 18c7395..3f0fde4 100644
--- a/lib/ofp-actions.h
+++ b/lib/ofp-actions.h
@@ -1021,5 +1021,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 6770fa4..ea1195d 100644
--- a/lib/ofp-msgs.h
+++ b/lib/ofp-msgs.h
@@ -372,12 +372,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+ (14): void. */
     OFPRAW_OFPST14_TABLE_DESC_REQUEST,
 
@@ -628,9 +634,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 fdc30d9..4e91851 100644
--- a/lib/ofp-parse.c
+++ b/lib/ofp-parse.c
@@ -1695,3 +1695,27 @@ 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 930b01a..2939993 100644
--- a/lib/ofp-print.c
+++ b/lib/ofp-print.c
@@ -2851,6 +2851,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 126b555..8d08902 100644
--- a/lib/ofp-util.c
+++ b/lib/ofp-util.c
@@ -4507,6 +4507,7 @@ ofputil_encode_port_mod(const struct ofputil_port_mod *pm,
 
     return b;
 }
+
 
 /* Table features. */
 
@@ -4628,25 +4629,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;
@@ -4654,10 +4640,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) {
@@ -4694,6 +4676,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;
@@ -4795,10 +4778,241 @@ 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;
+    } 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 = parse_action_bitmap(&payload, oh->version,
+                                        &tf->nonmiss.write.ofpacts);
+            break;
+
+        case OFPTFPT15_WRITE_ACTIONS_MISS:
+            error = parse_action_bitmap(&payload, oh->version,
+                                        &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;
 
@@ -4810,10 +5024,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();
     }
@@ -4822,11 +5048,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;
@@ -4840,11 +5084,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;
 
@@ -4854,12 +5117,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)
 {
@@ -4878,17 +5141,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);
@@ -4910,19 +5205,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_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_fields_property(reply, &tf->match, &tf->mask,
-                        OFPTFPT13_MATCH, version);
-    put_fields_property(reply, &tf->wildcard, NULL,
-                        OFPTFPT13_WILDCARDS, 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)
@@ -6052,6 +6398,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:
@@ -9781,3 +10128,10 @@ ofputil_encode_get_async_config(const struct ofp_header *oh,
 
     return buf;
 }
+
+uint8_t egress_table_id;
+
+void set_egress_table_id(uint8_t table_id)
+{
+    egress_table_id = table_id;
+}
diff --git a/lib/ofp-util.h b/lib/ofp-util.h
index c0541d4..b16b1fb 100644
--- a/lib/ofp-util.h
+++ b/lib/ofp-util.h
@@ -745,6 +745,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.
@@ -801,7 +810,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);
 
@@ -1323,4 +1334,7 @@ enum ofperr ofputil_decode_requestforward(const struct ofp_header *,
                                           struct ofputil_requestforward *);
 void ofputil_destroy_requestforward(struct ofputil_requestforward *);
 
+extern uint8_t egress_table_id;
+void set_egress_table_id(uint8_t table_id);
+
 #endif /* ofp-util.h */
diff --git a/ofproto/ofproto-provider.h b/ofproto/ofproto-provider.h
index 2a6bd91..af5268b 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,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 a5ce04e..99ffe2e 100644
--- a/ofproto/ofproto.c
+++ b/ofproto/ofproto.c
@@ -3123,7 +3123,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();
@@ -3155,14 +3156,23 @@ 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 && egress_table_id &&
+            f->table_id >= egress_table_id) {
+            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->nonmiss.apply.ofpacts = (UINT64_C(1) << N_OFPACTS) - 1;
+        f->nonmiss.apply.set_fields = f->nonmiss.write.set_fields;
         f->miss = f->nonmiss;
 
         f->match = match;
         f->mask = mask;
         f->wildcard = match;
+        if (ofproto->tables[i].is_first_egress) {
+            f->features = OFPTFF_FIRST_EGRESS;
+        }
     }
 
     if (statsp) {
@@ -3199,14 +3209,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) ||
@@ -3229,7 +3240,7 @@ 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();
@@ -3541,7 +3552,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++) {
@@ -3565,15 +3576,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].is_first_egress) {
+                        ofproto->tables[i].is_first_egress = false;
+                        break;
+                    }
+                }
+                ovs_mutex_lock(&ofproto_mutex);
+                ofproto->tables[tf.table_id].is_first_egress = true;
+                ofproto->first_egress_table_id = tf.table_id;
+                set_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 ed9ffdb..c816d7f 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 52e1ab4..29e321a 100644
--- a/tests/ofproto.at
+++ b/tests/ofproto.at
@@ -1919,6 +1919,239 @@ 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 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
+      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
+    (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 0d57f85..e2ab612 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"
@@ -733,9 +734,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
@@ -808,6 +809,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;
@@ -3847,6 +3877,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