[ovs-dev,PATCHv3] ofproto-dpif: Expose datapath capability to ovsdb.
diff mbox series

Message ID 1571342329-55425-1-git-send-email-u9012063@gmail.com
State Changes Requested
Headers show
Series
  • [ovs-dev,PATCHv3] ofproto-dpif: Expose datapath capability to ovsdb.
Related show

Commit Message

William Tu Oct. 17, 2019, 7:58 p.m. UTC
The patch adds support for fetching the datapath's capabilities
from the result of 'check_support()', and write the supported capability
to a new database column, called 'capabilities' under Datapath table.

To see how it works, run:
 # ovs-vsctl -- add-br br0 -- set Bridge br0 datapath_type=netdev
 # ovs-vsctl -- --id=@m create Datapath datapath_version=0 \
     'ct_zones={}' 'capabilities={}' \
     -- set Open_vSwitch . datapaths:"netdev"=@m

 # ovs-vsctl list-dp-cap netdev
 ufid=true sample_nesting=true clone=true tnl_push_pop=true \
 ct_orig_tuple=true ct_eventmask=true ct_state=true \
 ct_clear=true max_vlan_headers=1 recirc=true ct_label=true \
 max_hash_alg=1 ct_state_nat=true ct_timeout=true \
 ct_mark=true ct_orig_tuple6=true check_pkt_len=true \
 masked_set_action=true max_mpls_depth=3 trunc=true ct_zone=true

Signed-off-by: William Tu <u9012063@gmail.com>
---
v3:
    fix 32-bit build, reported by Greg
    travis: https://travis-ci.org/williamtu/ovs-travis/builds/599276267
v2:
	rebase to master
---
 ofproto/ofproto-dpif.c     | 51 +++++++++++++++++++++++++++++++
 ofproto/ofproto-provider.h |  2 ++
 ofproto/ofproto.c          | 12 ++++++++
 ofproto/ofproto.h          |  2 ++
 tests/ovs-vsctl.at         | 10 ++++++
 utilities/ovs-vsctl.8.in   |  6 ++++
 utilities/ovs-vsctl.c      | 28 +++++++++++++++++
 vswitchd/bridge.c          | 21 +++++++++++++
 vswitchd/vswitch.ovsschema |  5 ++-
 vswitchd/vswitch.xml       | 76 ++++++++++++++++++++++++++++++++++++++++++++++
 10 files changed, 212 insertions(+), 1 deletion(-)

Comments

Gregory Rose Oct. 17, 2019, 8:52 p.m. UTC | #1
On 10/17/2019 12:58 PM, William Tu wrote:
> The patch adds support for fetching the datapath's capabilities
> from the result of 'check_support()', and write the supported capability
> to a new database column, called 'capabilities' under Datapath table.
>
> To see how it works, run:
>   # ovs-vsctl -- add-br br0 -- set Bridge br0 datapath_type=netdev
>   # ovs-vsctl -- --id=@m create Datapath datapath_version=0 \
>       'ct_zones={}' 'capabilities={}' \
>       -- set Open_vSwitch . datapaths:"netdev"=@m
>
>   # ovs-vsctl list-dp-cap netdev
>   ufid=true sample_nesting=true clone=true tnl_push_pop=true \
>   ct_orig_tuple=true ct_eventmask=true ct_state=true \
>   ct_clear=true max_vlan_headers=1 recirc=true ct_label=true \
>   max_hash_alg=1 ct_state_nat=true ct_timeout=true \
>   ct_mark=true ct_orig_tuple6=true check_pkt_len=true \
>   masked_set_action=true max_mpls_depth=3 trunc=true ct_zone=true
>
> Signed-off-by: William Tu <u9012063@gmail.com>
> ---
> v3:
>      fix 32-bit build, reported by Greg
>      travis: https://travis-ci.org/williamtu/ovs-travis/builds/599276267

The only issue I saw in V2 was this 32 bit build failure, so with that 
fixed:
Tested-by: Greg Rose <gvrose8192@gmail.com>
Reviewed-by: Greg Rose <gvrose8192@gmail.com>

Thanks William!


> v2:
> 	rebase to master
> ---
>   ofproto/ofproto-dpif.c     | 51 +++++++++++++++++++++++++++++++
>   ofproto/ofproto-provider.h |  2 ++
>   ofproto/ofproto.c          | 12 ++++++++
>   ofproto/ofproto.h          |  2 ++
>   tests/ovs-vsctl.at         | 10 ++++++
>   utilities/ovs-vsctl.8.in   |  6 ++++
>   utilities/ovs-vsctl.c      | 28 +++++++++++++++++
>   vswitchd/bridge.c          | 21 +++++++++++++
>   vswitchd/vswitch.ovsschema |  5 ++-
>   vswitchd/vswitch.xml       | 76 ++++++++++++++++++++++++++++++++++++++++++++++
>   10 files changed, 212 insertions(+), 1 deletion(-)
>
> diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
> index 496a16c8a4af..5cee7295f52e 100644
> --- a/ofproto/ofproto-dpif.c
> +++ b/ofproto/ofproto-dpif.c
> @@ -5440,6 +5440,56 @@ ct_del_zone_timeout_policy(const char *datapath_type, uint16_t zone_id)
>       }
>   }
>   
> +static void
> +get_datapath_cap(const char *datapath_type, struct smap *cap)
> +{
> +    char *str_value;
> +    struct odp_support odp;
> +    struct dpif_backer_support s;
> +    struct dpif_backer *backer = shash_find_data(&all_dpif_backers,
> +                                                 datapath_type);
> +    if (!backer) {
> +        return;
> +    }
> +    s = backer->rt_support;
> +    odp = s.odp;
> +
> +    /* ODP_SUPPORT_FIELDS */
> +    str_value = xasprintf("%"PRIuSIZE, odp.max_vlan_headers);
> +    smap_add(cap, "max_vlan_headers", str_value);
> +    free(str_value);
> +
> +    str_value = xasprintf("%"PRIuSIZE, odp.max_mpls_depth);
> +    smap_add(cap, "max_mpls_depth", str_value);
> +    free(str_value);
> +
> +    smap_add(cap, "recirc", odp.recirc ? "true" : "false");
> +    smap_add(cap, "ct_state", odp.ct_state ? "true" : "false");
> +    smap_add(cap, "ct_zone", odp.ct_zone ? "true" : "false");
> +    smap_add(cap, "ct_mark", odp.ct_mark ? "true" : "false");
> +    smap_add(cap, "ct_label", odp.ct_label ? "true" : "false");
> +    smap_add(cap, "ct_state_nat", odp.ct_state_nat ? "true" : "false");
> +    smap_add(cap, "ct_orig_tuple", odp.ct_orig_tuple ? "true" : "false");
> +    smap_add(cap, "ct_orig_tuple6", odp.ct_orig_tuple6 ? "true" : "false");
> +
> +    /* DPIF_SUPPORT_FIELDS */
> +    smap_add(cap, "masked_set_action", s.masked_set_action ? "true" : "false");
> +    smap_add(cap, "tnl_push_pop", s.tnl_push_pop ? "true" : "false");
> +    smap_add(cap, "ufid", s.ufid ? "true" : "false");
> +    smap_add(cap, "trunc", s.trunc ? "true" : "false");
> +    smap_add(cap, "clone", s.clone ? "true" : "false");
> +    smap_add(cap, "sample_nesting", s.sample_nesting ? "true" : "false");
> +    smap_add(cap, "ct_eventmask", s.ct_eventmask ? "true" : "false");
> +    smap_add(cap, "ct_clear", s.ct_clear ? "true" : "false");
> +
> +    str_value = xasprintf("%"PRIuSIZE, s.max_hash_alg);
> +    smap_add(cap, "max_hash_alg", str_value);
> +    free(str_value);
> +
> +    smap_add(cap, "check_pkt_len", s.check_pkt_len ? "true" : "false");
> +    smap_add(cap, "ct_timeout", s.ct_timeout ? "true" : "false");
> +}
> +
>   /* Gets timeout policy name in 'backer' based on 'zone', 'dl_type' and
>    * 'nw_proto'.  Returns true if the zone-based timeout policy is configured.
>    * On success, stores the timeout policy name in 'tp_name', and sets
> @@ -6581,4 +6631,5 @@ const struct ofproto_class ofproto_dpif_class = {
>       ct_flush,                   /* ct_flush */
>       ct_set_zone_timeout_policy,
>       ct_del_zone_timeout_policy,
> +    get_datapath_cap,
>   };
> diff --git a/ofproto/ofproto-provider.h b/ofproto/ofproto-provider.h
> index c980e6bffff5..80c4fee06d01 100644
> --- a/ofproto/ofproto-provider.h
> +++ b/ofproto/ofproto-provider.h
> @@ -1889,6 +1889,8 @@ struct ofproto_class {
>       /* Deletes the timeout policy associated with 'zone' in datapath type
>        * 'dp_type'. */
>       void (*ct_del_zone_timeout_policy)(const char *dp_type, uint16_t zone);
> +    /* Get the datapath's capabilities. */
> +    void (*get_datapath_cap)(const char *dp_type, struct smap *caps);
>   };
>   
>   extern const struct ofproto_class ofproto_dpif_class;
> diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
> index b4249b0d8818..b3d68e74af3f 100644
> --- a/ofproto/ofproto.c
> +++ b/ofproto/ofproto.c
> @@ -954,6 +954,18 @@ ofproto_get_flow_restore_wait(void)
>       return flow_restore_wait;
>   }
>   
> +/* Retrieve datapath capabilities. */
> +void
> +ofproto_get_datapath_cap(const char *datapath_type, struct smap *dp_cap)
> +{
> +    datapath_type = ofproto_normalize_type(datapath_type);
> +    const struct ofproto_class *class = ofproto_class_find__(datapath_type);
> +
> +    if (class->get_datapath_cap) {
> +        class->get_datapath_cap(datapath_type, dp_cap);
> +    }
> +}
> +
>   /* Connection tracking configuration. */
>   void
>   ofproto_ct_set_zone_timeout_policy(const char *datapath_type, uint16_t zone_id,
> diff --git a/ofproto/ofproto.h b/ofproto/ofproto.h
> index 033c4cf93e94..48a9d602c214 100644
> --- a/ofproto/ofproto.h
> +++ b/ofproto/ofproto.h
> @@ -372,6 +372,8 @@ void ofproto_ct_set_zone_timeout_policy(const char *datapath_type,
>                                           struct simap *timeout_policy);
>   void ofproto_ct_del_zone_timeout_policy(const char *datapath_type,
>                                           uint16_t zone);
> +void ofproto_get_datapath_cap(const char *datapath_type,
> +                              struct smap *dp_cap);
>   
>   /* Configuration of ports. */
>   void ofproto_port_unregister(struct ofproto *, ofp_port_t ofp_port);
> diff --git a/tests/ovs-vsctl.at b/tests/ovs-vsctl.at
> index 4d55f89e4ae3..18b9cd7c89a5 100644
> --- a/tests/ovs-vsctl.at
> +++ b/tests/ovs-vsctl.at
> @@ -819,6 +819,10 @@ AT_CHECK([RUN_OVS_VSCTL([del-zone-tp netdev zone=1])])
>   AT_CHECK([RUN_OVS_VSCTL([--if-exists del-zone-tp netdev zone=1])])
>   AT_CHECK([RUN_OVS_VSCTL([list-zone-tp netdev])], [0], [Zone:2, Timeout Policies: icmp_first=2 icmp_reply=3
>   ])
> +
> +AT_CHECK([RUN_OVS_VSCTL([-- --id=@m create Datapath datapath_version=0 'capabilities={recirc=true}' -- set Open_vSwitch . datapaths:"system"=@m])], [0], [stdout])
> +AT_CHECK([RUN_OVS_VSCTL([list-dp-cap system])], [0], [recirc=true
> +])
>   OVS_VSCTL_CLEANUP
>   AT_CLEANUP
>   
> @@ -962,6 +966,12 @@ AT_CHECK([RUN_OVS_VSCTL([del-zone-tp netdev zone=11])],
>   ])
>   AT_CHECK([RUN_OVS_VSCTL([list-zone-tp netdev])], [0], [Zone:2, Timeout Policies: icmp_first=2 icmp_reply=3
>   ])
> +
> +AT_CHECK([RUN_OVS_VSCTL([-- --id=@m create Datapath datapath_version=0 'capabilities={recirc=true}' -- set Open_vSwitch . datapaths:"system"=@m])], [0], [stdout])
> +AT_CHECK([RUN_OVS_VSCTL([list-dp-cap nosystem])],
> +  [1], [], [ovs-vsctl: datapath "nosystem" record not found
> +])
> +
>   OVS_VSCTL_CLEANUP
>   AT_CLEANUP
>   
> diff --git a/utilities/ovs-vsctl.8.in b/utilities/ovs-vsctl.8.in
> index 14a8aa4a48ac..ff97922dd0c6 100644
> --- a/utilities/ovs-vsctl.8.in
> +++ b/utilities/ovs-vsctl.8.in
> @@ -379,6 +379,12 @@ delete a zone that does not exist has no effect.
>   .IP "\fBlist\-zone\-tp \fIdatapath\fR"
>   Prints the timeout policies of all zones in \fIdatapath\fR.
>   .
> +.SS "Datapath Capabilities Command"
> +The command query datapath capabilities.
> +.
> +.IP "\fBlist\-dp\-cap \fIdatapath\fR"
> +Prints the datapath's capabilities.
> +.
>   .SS "OpenFlow Controller Connectivity"
>   .
>   \fBovs\-vswitchd\fR can perform all configured bridging and switching
> diff --git a/utilities/ovs-vsctl.c b/utilities/ovs-vsctl.c
> index 7232471e68b9..bd3972636e66 100644
> --- a/utilities/ovs-vsctl.c
> +++ b/utilities/ovs-vsctl.c
> @@ -1363,6 +1363,31 @@ pre_get_zone(struct ctl_context *ctx)
>   }
>   
>   static void
> +pre_get_dp_cap(struct ctl_context *ctx)
> +{
> +    ovsdb_idl_add_column(ctx->idl, &ovsrec_open_vswitch_col_datapaths);
> +    ovsdb_idl_add_column(ctx->idl, &ovsrec_datapath_col_capabilities);
> +}
> +
> +static void
> +cmd_list_dp_cap(struct ctl_context *ctx)
> +{
> +    struct vsctl_context *vsctl_ctx = vsctl_context_cast(ctx);
> +    struct smap_node *node;
> +
> +    struct ovsrec_datapath *dp = find_datapath(vsctl_ctx, ctx->argv[1]);
> +    if (!dp) {
> +        ctl_fatal("datapath \"%s\" record not found", ctx->argv[1]);
> +    }
> +
> +    SMAP_FOR_EACH (node, &dp->capabilities) {
> +        ds_put_format(&ctx->output, "%s=%s ",node->key, node->value);
> +    }
> +    ds_chomp(&ctx->output, ' ');
> +    ds_put_char(&ctx->output, '\n');
> +}
> +
> +static void
>   cmd_add_br(struct ctl_context *ctx)
>   {
>       struct vsctl_context *vsctl_ctx = vsctl_context_cast(ctx);
> @@ -3112,6 +3137,9 @@ static const struct ctl_command_syntax vsctl_commands[] = {
>        "--if-exists", RW},
>       {"list-zone-tp", 1, 1, "", pre_get_zone, cmd_list_zone_tp, NULL, "", RO},
>   
> +    /* Datapath capabilities. */
> +    {"list-dp-cap", 1, 1, "", pre_get_dp_cap, cmd_list_dp_cap, NULL, "", RO},
> +
>       {NULL, 0, 0, NULL, NULL, NULL, NULL, NULL, RO},
>   };
>   
> diff --git a/vswitchd/bridge.c b/vswitchd/bridge.c
> index 9095ebf5d5e9..d402e39bdc68 100644
> --- a/vswitchd/bridge.c
> +++ b/vswitchd/bridge.c
> @@ -171,6 +171,7 @@ struct datapath {
>       struct hmap ct_zones;       /* Map of 'struct ct_zone' elements, indexed
>                                    * by 'zone'. */
>       struct hmap_node node;      /* Node in 'all_datapaths' hmap. */
> +    struct smap caps;           /* Capabilities. */
>       unsigned int last_used;     /* The last idl_seqno that this 'datapath'
>                                    * used in OVSDB. This number is used for
>                                    * garbage collection. */
> @@ -700,6 +701,7 @@ datapath_create(const char *type)
>       dp->type = xstrdup(type);
>       hmap_init(&dp->ct_zones);
>       hmap_insert(&all_datapaths, &dp->node, hash_string(type, 0));
> +    smap_init(&dp->caps);
>       return dp;
>   }
>   
> @@ -716,6 +718,7 @@ datapath_destroy(struct datapath *dp)
>           hmap_remove(&all_datapaths, &dp->node);
>           hmap_destroy(&dp->ct_zones);
>           free(dp->type);
> +        smap_destroy(&dp->caps);
>           free(dp);
>       }
>   }
> @@ -759,6 +762,23 @@ ct_zones_reconfigure(struct datapath *dp, struct ovsrec_datapath *dp_cfg)
>   }
>   
>   static void
> +dp_capability_reconfigure(struct datapath *dp,
> +                          struct ovsrec_datapath *dp_cfg)
> +{
> +    struct smap_node *node;
> +    struct smap cap;
> +
> +    smap_init(&cap);
> +    ofproto_get_datapath_cap(dp->type, &cap);
> +
> +    SMAP_FOR_EACH (node, &cap) {
> +        ovsrec_datapath_update_capabilities_setkey(dp_cfg, node->key,
> +                                                   node->value);
> +    }
> +    smap_destroy(&cap);
> +}
> +
> +static void
>   datapath_reconfigure(const struct ovsrec_open_vswitch *cfg)
>   {
>       struct datapath *dp, *next;
> @@ -771,6 +791,7 @@ datapath_reconfigure(const struct ovsrec_open_vswitch *cfg)
>           dp = datapath_lookup(dp_name);
>           if (!dp) {
>               dp = datapath_create(dp_name);
> +            dp_capability_reconfigure(dp, dp_cfg);
>           }
>           dp->last_used = idl_seqno;
>           ct_zones_reconfigure(dp, dp_cfg);
> diff --git a/vswitchd/vswitch.ovsschema b/vswitchd/vswitch.ovsschema
> index 02be5ddeec97..0666c8c76446 100644
> --- a/vswitchd/vswitch.ovsschema
> +++ b/vswitchd/vswitch.ovsschema
> @@ -1,6 +1,6 @@
>   {"name": "Open_vSwitch",
>    "version": "8.2.0",
> - "cksum": "4076590391 26298",
> + "cksum": "1076640191 26427",
>    "tables": {
>      "Open_vSwitch": {
>        "columns": {
> @@ -650,6 +650,9 @@
>                     "value": {"type": "uuid",
>                               "refTable": "CT_Zone"},
>                     "min": 0, "max": "unlimited"}},
> +       "capabilities": {
> +         "type": {"key": "string", "value": "string",
> +                  "min": 0, "max": "unlimited"}},
>          "external_ids": {
>            "type": {"key": "string", "value": "string",
>                     "min": 0, "max": "unlimited"}}}},
> diff --git a/vswitchd/vswitch.xml b/vswitchd/vswitch.xml
> index 01304a5ed2c2..6201f5fd7886 100644
> --- a/vswitchd/vswitch.xml
> +++ b/vswitchd/vswitch.xml
> @@ -5650,6 +5650,82 @@ ovs-vsctl add-port br0 p0 -- set Interface p0 type=patch options:peer=p1 \
>         connection tracking-related OpenFlow matches and actions).
>       </column>
>   
> +    <column name="capabilities" key="max_vlan_headers"
> +            type='{"type": "integer", "minInteger": 0}'>
> +      Maximum number of 802.1q VLAN headers to serialize in a mask.
> +    </column>
> +    <column name="capabilities" key="max_mpls_depth"
> +            type='{"type": "integer", "minInteger": 0}'>
> +      Maximum number of MPLS label stack entries to serialise in a mask.
> +    </column>
> +    <column name="capabilities" key="recirc" type='{"type": "boolean"}'>
> +      If this is true, then recirculation fields will always be serialised.
> +    </column>
> +    <column name="capabilities" key="ct_state" type='{"type": "boolean"}'>
> +      If true, datapath supports OVS_KEY_ATTR_CT_STATE.
> +    </column>
> +    <column name="capabilities" key="ct_zone" type='{"type": "boolean"}'>
> +      If true, datapath supports OVS_KEY_ATTR_CT_ZONE.
> +    </column>
> +    <column name="capabilities" key="ct_mark" type='{"type": "boolean"}'>
> +      If true, datapath supports OVS_KEY_ATTR_CT_MARK.
> +    </column>
> +    <column name="capabilities" key="ct_label" type='{"type": "boolean"}'>
> +      If true, datapath supports OVS_KEY_ATTR_CT_LABEL.
> +    </column>
> +    <column name="capabilities" key="ct_state_nat" type='{"type": "boolean"}'>
> +      If true, it means that the datapath supports the NAT bits in
> +      'ct_state'.  The above 'ct_state' member must be true for this
> +      to make sense.
> +    </column>
> +    <column name="capabilities" key="ct_orig_tuple"
> +            type='{"type": "boolean"}'>
> +      Conntrack original direction tuple matching * supported.
> +    </column>
> +    <column name="capabilities" key="ct_orig_tuple6"
> +            type='{"type": "boolean"}'>
> +      Conntrack original direction tuple6 matching * supported.
> +    </column>
> +    <column name="capabilities" key="masked_set_action"
> +            type='{"type": "boolean"}'>
> +      True if the datapath supports masked data in
> +      OVS_ACTION_ATTR_SET actions.
> +    </column>
> +    <column name="capabilities" key="tnl_push_pop" type='{"type": "boolean"}'>
> +      True if the datapath supports tnl_push and pop actions.
> +    </column>
> +    <column name="capabilities" key="ufid" type='{"type": "boolean"}'>
> +      True if the datapath supports OVS_FLOW_ATTR_UFID.
> +    </column>
> +    <column name="capabilities" key="trunc" type='{"type": "boolean"}'>
> +      True if the datapath supports OVS_ACTION_ATTR_TRUNC action.
> +    </column>
> +    <column name="capabilities" key="clone" type='{"type": "boolean"}'>
> +      True if the datapath supports OVS_ACTION_ATTR_CLONE action.
> +    </column>
> +    <column name="capabilities" key="sample_nesting"
> +            type='{"type": "integer", "minInteger": 0}'>
> +      Maximum level of nesting allowed by OVS_ACTION_ATTR_SAMPLE action.
> +    </column>
> +    <column name="capabilities" key="ct_eventmask" type='{"type": "boolean"}'>
> +      OVS_CT_ATTR_EVENTMASK supported by OVS_ACTION_ATTR_CT action.
> +    </column>
> +    <column name="capabilities" key="ct_clear" type='{"type": "boolean"}'>
> +      True if the datapath supports OVS_ACTION_ATTR_CT_CLEAR action.
> +    </column>
> +    <column name="capabilities" key="max_hash_alg"
> +            type='{"type": "integer", "minInteger": 0}'>
> +      Highest supported dp_hash algorithm.
> +    </column>
> +    <column name="capabilities" key="check_pkt_len"
> +            type='{"type": "boolean"}'>
> +      True if the datapath supports OVS_ACTION_ATTR_CHECK_PKT_LEN.
> +    </column>
> +    <column name="capabilities" key="ct_timeout" type='{"type": "boolean"}'>
> +      True if the datapath supports OVS_CT_ATTR_TIMEOUT in
> +      OVS_ACTION_ATTR_CTaction.
> +    </column>
> +
>       <group title="Common Columns">
>         The overall purpose of these columns is described under <code>Common
>         Columns</code> at the beginning of this document.

Patch
diff mbox series

diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index 496a16c8a4af..5cee7295f52e 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -5440,6 +5440,56 @@  ct_del_zone_timeout_policy(const char *datapath_type, uint16_t zone_id)
     }
 }
 
+static void
+get_datapath_cap(const char *datapath_type, struct smap *cap)
+{
+    char *str_value;
+    struct odp_support odp;
+    struct dpif_backer_support s;
+    struct dpif_backer *backer = shash_find_data(&all_dpif_backers,
+                                                 datapath_type);
+    if (!backer) {
+        return;
+    }
+    s = backer->rt_support;
+    odp = s.odp;
+
+    /* ODP_SUPPORT_FIELDS */
+    str_value = xasprintf("%"PRIuSIZE, odp.max_vlan_headers);
+    smap_add(cap, "max_vlan_headers", str_value);
+    free(str_value);
+
+    str_value = xasprintf("%"PRIuSIZE, odp.max_mpls_depth);
+    smap_add(cap, "max_mpls_depth", str_value);
+    free(str_value);
+
+    smap_add(cap, "recirc", odp.recirc ? "true" : "false");
+    smap_add(cap, "ct_state", odp.ct_state ? "true" : "false");
+    smap_add(cap, "ct_zone", odp.ct_zone ? "true" : "false");
+    smap_add(cap, "ct_mark", odp.ct_mark ? "true" : "false");
+    smap_add(cap, "ct_label", odp.ct_label ? "true" : "false");
+    smap_add(cap, "ct_state_nat", odp.ct_state_nat ? "true" : "false");
+    smap_add(cap, "ct_orig_tuple", odp.ct_orig_tuple ? "true" : "false");
+    smap_add(cap, "ct_orig_tuple6", odp.ct_orig_tuple6 ? "true" : "false");
+
+    /* DPIF_SUPPORT_FIELDS */
+    smap_add(cap, "masked_set_action", s.masked_set_action ? "true" : "false");
+    smap_add(cap, "tnl_push_pop", s.tnl_push_pop ? "true" : "false");
+    smap_add(cap, "ufid", s.ufid ? "true" : "false");
+    smap_add(cap, "trunc", s.trunc ? "true" : "false");
+    smap_add(cap, "clone", s.clone ? "true" : "false");
+    smap_add(cap, "sample_nesting", s.sample_nesting ? "true" : "false");
+    smap_add(cap, "ct_eventmask", s.ct_eventmask ? "true" : "false");
+    smap_add(cap, "ct_clear", s.ct_clear ? "true" : "false");
+
+    str_value = xasprintf("%"PRIuSIZE, s.max_hash_alg);
+    smap_add(cap, "max_hash_alg", str_value);
+    free(str_value);
+
+    smap_add(cap, "check_pkt_len", s.check_pkt_len ? "true" : "false");
+    smap_add(cap, "ct_timeout", s.ct_timeout ? "true" : "false");
+}
+
 /* Gets timeout policy name in 'backer' based on 'zone', 'dl_type' and
  * 'nw_proto'.  Returns true if the zone-based timeout policy is configured.
  * On success, stores the timeout policy name in 'tp_name', and sets
@@ -6581,4 +6631,5 @@  const struct ofproto_class ofproto_dpif_class = {
     ct_flush,                   /* ct_flush */
     ct_set_zone_timeout_policy,
     ct_del_zone_timeout_policy,
+    get_datapath_cap,
 };
diff --git a/ofproto/ofproto-provider.h b/ofproto/ofproto-provider.h
index c980e6bffff5..80c4fee06d01 100644
--- a/ofproto/ofproto-provider.h
+++ b/ofproto/ofproto-provider.h
@@ -1889,6 +1889,8 @@  struct ofproto_class {
     /* Deletes the timeout policy associated with 'zone' in datapath type
      * 'dp_type'. */
     void (*ct_del_zone_timeout_policy)(const char *dp_type, uint16_t zone);
+    /* Get the datapath's capabilities. */
+    void (*get_datapath_cap)(const char *dp_type, struct smap *caps);
 };
 
 extern const struct ofproto_class ofproto_dpif_class;
diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
index b4249b0d8818..b3d68e74af3f 100644
--- a/ofproto/ofproto.c
+++ b/ofproto/ofproto.c
@@ -954,6 +954,18 @@  ofproto_get_flow_restore_wait(void)
     return flow_restore_wait;
 }
 
+/* Retrieve datapath capabilities. */
+void
+ofproto_get_datapath_cap(const char *datapath_type, struct smap *dp_cap)
+{
+    datapath_type = ofproto_normalize_type(datapath_type);
+    const struct ofproto_class *class = ofproto_class_find__(datapath_type);
+
+    if (class->get_datapath_cap) {
+        class->get_datapath_cap(datapath_type, dp_cap);
+    }
+}
+
 /* Connection tracking configuration. */
 void
 ofproto_ct_set_zone_timeout_policy(const char *datapath_type, uint16_t zone_id,
diff --git a/ofproto/ofproto.h b/ofproto/ofproto.h
index 033c4cf93e94..48a9d602c214 100644
--- a/ofproto/ofproto.h
+++ b/ofproto/ofproto.h
@@ -372,6 +372,8 @@  void ofproto_ct_set_zone_timeout_policy(const char *datapath_type,
                                         struct simap *timeout_policy);
 void ofproto_ct_del_zone_timeout_policy(const char *datapath_type,
                                         uint16_t zone);
+void ofproto_get_datapath_cap(const char *datapath_type,
+                              struct smap *dp_cap);
 
 /* Configuration of ports. */
 void ofproto_port_unregister(struct ofproto *, ofp_port_t ofp_port);
diff --git a/tests/ovs-vsctl.at b/tests/ovs-vsctl.at
index 4d55f89e4ae3..18b9cd7c89a5 100644
--- a/tests/ovs-vsctl.at
+++ b/tests/ovs-vsctl.at
@@ -819,6 +819,10 @@  AT_CHECK([RUN_OVS_VSCTL([del-zone-tp netdev zone=1])])
 AT_CHECK([RUN_OVS_VSCTL([--if-exists del-zone-tp netdev zone=1])])
 AT_CHECK([RUN_OVS_VSCTL([list-zone-tp netdev])], [0], [Zone:2, Timeout Policies: icmp_first=2 icmp_reply=3
 ])
+
+AT_CHECK([RUN_OVS_VSCTL([-- --id=@m create Datapath datapath_version=0 'capabilities={recirc=true}' -- set Open_vSwitch . datapaths:"system"=@m])], [0], [stdout])
+AT_CHECK([RUN_OVS_VSCTL([list-dp-cap system])], [0], [recirc=true
+])
 OVS_VSCTL_CLEANUP
 AT_CLEANUP
 
@@ -962,6 +966,12 @@  AT_CHECK([RUN_OVS_VSCTL([del-zone-tp netdev zone=11])],
 ])
 AT_CHECK([RUN_OVS_VSCTL([list-zone-tp netdev])], [0], [Zone:2, Timeout Policies: icmp_first=2 icmp_reply=3
 ])
+
+AT_CHECK([RUN_OVS_VSCTL([-- --id=@m create Datapath datapath_version=0 'capabilities={recirc=true}' -- set Open_vSwitch . datapaths:"system"=@m])], [0], [stdout])
+AT_CHECK([RUN_OVS_VSCTL([list-dp-cap nosystem])],
+  [1], [], [ovs-vsctl: datapath "nosystem" record not found
+])
+
 OVS_VSCTL_CLEANUP
 AT_CLEANUP
 
diff --git a/utilities/ovs-vsctl.8.in b/utilities/ovs-vsctl.8.in
index 14a8aa4a48ac..ff97922dd0c6 100644
--- a/utilities/ovs-vsctl.8.in
+++ b/utilities/ovs-vsctl.8.in
@@ -379,6 +379,12 @@  delete a zone that does not exist has no effect.
 .IP "\fBlist\-zone\-tp \fIdatapath\fR"
 Prints the timeout policies of all zones in \fIdatapath\fR.
 .
+.SS "Datapath Capabilities Command"
+The command query datapath capabilities.
+.
+.IP "\fBlist\-dp\-cap \fIdatapath\fR"
+Prints the datapath's capabilities.
+.
 .SS "OpenFlow Controller Connectivity"
 .
 \fBovs\-vswitchd\fR can perform all configured bridging and switching
diff --git a/utilities/ovs-vsctl.c b/utilities/ovs-vsctl.c
index 7232471e68b9..bd3972636e66 100644
--- a/utilities/ovs-vsctl.c
+++ b/utilities/ovs-vsctl.c
@@ -1363,6 +1363,31 @@  pre_get_zone(struct ctl_context *ctx)
 }
 
 static void
+pre_get_dp_cap(struct ctl_context *ctx)
+{
+    ovsdb_idl_add_column(ctx->idl, &ovsrec_open_vswitch_col_datapaths);
+    ovsdb_idl_add_column(ctx->idl, &ovsrec_datapath_col_capabilities);
+}
+
+static void
+cmd_list_dp_cap(struct ctl_context *ctx)
+{
+    struct vsctl_context *vsctl_ctx = vsctl_context_cast(ctx);
+    struct smap_node *node;
+
+    struct ovsrec_datapath *dp = find_datapath(vsctl_ctx, ctx->argv[1]);
+    if (!dp) {
+        ctl_fatal("datapath \"%s\" record not found", ctx->argv[1]);
+    }
+
+    SMAP_FOR_EACH (node, &dp->capabilities) {
+        ds_put_format(&ctx->output, "%s=%s ",node->key, node->value);
+    }
+    ds_chomp(&ctx->output, ' ');
+    ds_put_char(&ctx->output, '\n');
+}
+
+static void
 cmd_add_br(struct ctl_context *ctx)
 {
     struct vsctl_context *vsctl_ctx = vsctl_context_cast(ctx);
@@ -3112,6 +3137,9 @@  static const struct ctl_command_syntax vsctl_commands[] = {
      "--if-exists", RW},
     {"list-zone-tp", 1, 1, "", pre_get_zone, cmd_list_zone_tp, NULL, "", RO},
 
+    /* Datapath capabilities. */
+    {"list-dp-cap", 1, 1, "", pre_get_dp_cap, cmd_list_dp_cap, NULL, "", RO},
+
     {NULL, 0, 0, NULL, NULL, NULL, NULL, NULL, RO},
 };
 
diff --git a/vswitchd/bridge.c b/vswitchd/bridge.c
index 9095ebf5d5e9..d402e39bdc68 100644
--- a/vswitchd/bridge.c
+++ b/vswitchd/bridge.c
@@ -171,6 +171,7 @@  struct datapath {
     struct hmap ct_zones;       /* Map of 'struct ct_zone' elements, indexed
                                  * by 'zone'. */
     struct hmap_node node;      /* Node in 'all_datapaths' hmap. */
+    struct smap caps;           /* Capabilities. */
     unsigned int last_used;     /* The last idl_seqno that this 'datapath'
                                  * used in OVSDB. This number is used for
                                  * garbage collection. */
@@ -700,6 +701,7 @@  datapath_create(const char *type)
     dp->type = xstrdup(type);
     hmap_init(&dp->ct_zones);
     hmap_insert(&all_datapaths, &dp->node, hash_string(type, 0));
+    smap_init(&dp->caps);
     return dp;
 }
 
@@ -716,6 +718,7 @@  datapath_destroy(struct datapath *dp)
         hmap_remove(&all_datapaths, &dp->node);
         hmap_destroy(&dp->ct_zones);
         free(dp->type);
+        smap_destroy(&dp->caps);
         free(dp);
     }
 }
@@ -759,6 +762,23 @@  ct_zones_reconfigure(struct datapath *dp, struct ovsrec_datapath *dp_cfg)
 }
 
 static void
+dp_capability_reconfigure(struct datapath *dp,
+                          struct ovsrec_datapath *dp_cfg)
+{
+    struct smap_node *node;
+    struct smap cap;
+
+    smap_init(&cap);
+    ofproto_get_datapath_cap(dp->type, &cap);
+
+    SMAP_FOR_EACH (node, &cap) {
+        ovsrec_datapath_update_capabilities_setkey(dp_cfg, node->key,
+                                                   node->value);
+    }
+    smap_destroy(&cap);
+}
+
+static void
 datapath_reconfigure(const struct ovsrec_open_vswitch *cfg)
 {
     struct datapath *dp, *next;
@@ -771,6 +791,7 @@  datapath_reconfigure(const struct ovsrec_open_vswitch *cfg)
         dp = datapath_lookup(dp_name);
         if (!dp) {
             dp = datapath_create(dp_name);
+            dp_capability_reconfigure(dp, dp_cfg);
         }
         dp->last_used = idl_seqno;
         ct_zones_reconfigure(dp, dp_cfg);
diff --git a/vswitchd/vswitch.ovsschema b/vswitchd/vswitch.ovsschema
index 02be5ddeec97..0666c8c76446 100644
--- a/vswitchd/vswitch.ovsschema
+++ b/vswitchd/vswitch.ovsschema
@@ -1,6 +1,6 @@ 
 {"name": "Open_vSwitch",
  "version": "8.2.0",
- "cksum": "4076590391 26298",
+ "cksum": "1076640191 26427",
  "tables": {
    "Open_vSwitch": {
      "columns": {
@@ -650,6 +650,9 @@ 
                   "value": {"type": "uuid",
                             "refTable": "CT_Zone"},
                   "min": 0, "max": "unlimited"}},
+       "capabilities": {
+         "type": {"key": "string", "value": "string",
+                  "min": 0, "max": "unlimited"}},
        "external_ids": {
          "type": {"key": "string", "value": "string",
                   "min": 0, "max": "unlimited"}}}},
diff --git a/vswitchd/vswitch.xml b/vswitchd/vswitch.xml
index 01304a5ed2c2..6201f5fd7886 100644
--- a/vswitchd/vswitch.xml
+++ b/vswitchd/vswitch.xml
@@ -5650,6 +5650,82 @@  ovs-vsctl add-port br0 p0 -- set Interface p0 type=patch options:peer=p1 \
       connection tracking-related OpenFlow matches and actions).
     </column>
 
+    <column name="capabilities" key="max_vlan_headers"
+            type='{"type": "integer", "minInteger": 0}'>
+      Maximum number of 802.1q VLAN headers to serialize in a mask.
+    </column>
+    <column name="capabilities" key="max_mpls_depth"
+            type='{"type": "integer", "minInteger": 0}'>
+      Maximum number of MPLS label stack entries to serialise in a mask.
+    </column>
+    <column name="capabilities" key="recirc" type='{"type": "boolean"}'>
+      If this is true, then recirculation fields will always be serialised.
+    </column>
+    <column name="capabilities" key="ct_state" type='{"type": "boolean"}'>
+      If true, datapath supports OVS_KEY_ATTR_CT_STATE.
+    </column>
+    <column name="capabilities" key="ct_zone" type='{"type": "boolean"}'>
+      If true, datapath supports OVS_KEY_ATTR_CT_ZONE.
+    </column>
+    <column name="capabilities" key="ct_mark" type='{"type": "boolean"}'>
+      If true, datapath supports OVS_KEY_ATTR_CT_MARK.
+    </column>
+    <column name="capabilities" key="ct_label" type='{"type": "boolean"}'>
+      If true, datapath supports OVS_KEY_ATTR_CT_LABEL.
+    </column>
+    <column name="capabilities" key="ct_state_nat" type='{"type": "boolean"}'>
+      If true, it means that the datapath supports the NAT bits in
+      'ct_state'.  The above 'ct_state' member must be true for this
+      to make sense.
+    </column>
+    <column name="capabilities" key="ct_orig_tuple"
+            type='{"type": "boolean"}'>
+      Conntrack original direction tuple matching * supported.
+    </column>
+    <column name="capabilities" key="ct_orig_tuple6"
+            type='{"type": "boolean"}'>
+      Conntrack original direction tuple6 matching * supported.
+    </column>
+    <column name="capabilities" key="masked_set_action"
+            type='{"type": "boolean"}'>
+      True if the datapath supports masked data in
+      OVS_ACTION_ATTR_SET actions.
+    </column>
+    <column name="capabilities" key="tnl_push_pop" type='{"type": "boolean"}'>
+      True if the datapath supports tnl_push and pop actions.
+    </column>
+    <column name="capabilities" key="ufid" type='{"type": "boolean"}'>
+      True if the datapath supports OVS_FLOW_ATTR_UFID.
+    </column>
+    <column name="capabilities" key="trunc" type='{"type": "boolean"}'>
+      True if the datapath supports OVS_ACTION_ATTR_TRUNC action.
+    </column>
+    <column name="capabilities" key="clone" type='{"type": "boolean"}'>
+      True if the datapath supports OVS_ACTION_ATTR_CLONE action.
+    </column>
+    <column name="capabilities" key="sample_nesting"
+            type='{"type": "integer", "minInteger": 0}'>
+      Maximum level of nesting allowed by OVS_ACTION_ATTR_SAMPLE action.
+    </column>
+    <column name="capabilities" key="ct_eventmask" type='{"type": "boolean"}'>
+      OVS_CT_ATTR_EVENTMASK supported by OVS_ACTION_ATTR_CT action.
+    </column>
+    <column name="capabilities" key="ct_clear" type='{"type": "boolean"}'>
+      True if the datapath supports OVS_ACTION_ATTR_CT_CLEAR action.
+    </column>
+    <column name="capabilities" key="max_hash_alg"
+            type='{"type": "integer", "minInteger": 0}'>
+      Highest supported dp_hash algorithm.
+    </column>
+    <column name="capabilities" key="check_pkt_len"
+            type='{"type": "boolean"}'>
+      True if the datapath supports OVS_ACTION_ATTR_CHECK_PKT_LEN.
+    </column>
+    <column name="capabilities" key="ct_timeout" type='{"type": "boolean"}'>
+      True if the datapath supports OVS_CT_ATTR_TIMEOUT in
+      OVS_ACTION_ATTR_CTaction.
+    </column>
+
     <group title="Common Columns">
       The overall purpose of these columns is described under <code>Common
       Columns</code> at the beginning of this document.