2
0
mirror of https://github.com/openvswitch/ovs synced 2025-09-02 07:15:17 +00:00

netdev: Dynamic per-port Flow API.

Current issues with Flow API:

* OVS calls offloading functions regardless of successful
  flow API initialization. (ex. on init_flow_api failure)
* Static initilaization of Flow API for a netdev_class forbids
  having different offloading types for different instances
  of netdev with the same netdev_class. (ex. different vports in
  'system' and 'netdev' datapaths at the same time)

Solution:

* Move Flow API from the netdev_class to netdev instance.
* Make Flow API dynamic, i.e. probe the APIs and choose the
  suitable one.

Side effects:

* Flow API providers localized as possible in their modules.
* Now we have an ability to make runtime checks. For example,
  we could check if particular device supports features we
  need, like if dpdk device supports RSS+MARK action.

Signed-off-by: Ilya Maximets <i.maximets@samsung.com>
Acked-by: Roi Dayan <roid@mellanox.com>
This commit is contained in:
Ilya Maximets
2019-05-07 12:24:07 +03:00
parent 7a65e5a925
commit 5fc5c50f3d
17 changed files with 398 additions and 232 deletions

View File

@@ -137,6 +137,7 @@ lib_libopenvswitch_la_SOURCES = \
lib/namemap.c \ lib/namemap.c \
lib/netdev-dpdk.h \ lib/netdev-dpdk.h \
lib/netdev-dummy.c \ lib/netdev-dummy.c \
lib/netdev-offload-provider.h \
lib/netdev-provider.h \ lib/netdev-provider.h \
lib/netdev-rte-offloads.h \ lib/netdev-rte-offloads.h \
lib/netdev-vport.c \ lib/netdev-vport.c \
@@ -393,7 +394,6 @@ lib_libopenvswitch_la_SOURCES += \
lib/netdev-linux.c \ lib/netdev-linux.c \
lib/netdev-linux.h \ lib/netdev-linux.h \
lib/netdev-tc-offloads.c \ lib/netdev-tc-offloads.c \
lib/netdev-tc-offloads.h \
lib/netlink-conntrack.c \ lib/netlink-conntrack.c \
lib/netlink-conntrack.h \ lib/netlink-conntrack.h \
lib/netlink-notifier.c \ lib/netlink-notifier.c \

View File

@@ -34,6 +34,7 @@
#include "dirs.h" #include "dirs.h"
#include "fatal-signal.h" #include "fatal-signal.h"
#include "netdev-dpdk.h" #include "netdev-dpdk.h"
#include "netdev-rte-offloads.h"
#include "openvswitch/dynamic-string.h" #include "openvswitch/dynamic-string.h"
#include "openvswitch/vlog.h" #include "openvswitch/vlog.h"
#include "ovs-numa.h" #include "ovs-numa.h"
@@ -454,6 +455,7 @@ dpdk_init__(const struct smap *ovs_other_config)
/* Finally, register the dpdk classes */ /* Finally, register the dpdk classes */
netdev_dpdk_register(); netdev_dpdk_register();
netdev_dpdk_flow_api_register();
return true; return true;
} }

View File

@@ -4212,6 +4212,27 @@ unlock:
return err; return err;
} }
bool
netdev_dpdk_flow_api_supported(struct netdev *netdev)
{
struct netdev_dpdk *dev;
bool ret = false;
if (!is_dpdk_class(netdev->netdev_class)) {
goto out;
}
dev = netdev_dpdk_cast(netdev);
ovs_mutex_lock(&dev->mutex);
if (dev->type == DPDK_DEV_ETH) {
/* TODO: Check if we able to offload some minimal flow. */
ret = true;
}
ovs_mutex_unlock(&dev->mutex);
out:
return ret;
}
int int
netdev_dpdk_rte_flow_destroy(struct netdev *netdev, netdev_dpdk_rte_flow_destroy(struct netdev *netdev,
struct rte_flow *rte_flow, struct rte_flow *rte_flow,
@@ -4276,8 +4297,7 @@ netdev_dpdk_rte_flow_create(struct netdev *netdev,
.get_features = netdev_dpdk_get_features, \ .get_features = netdev_dpdk_get_features, \
.get_status = netdev_dpdk_get_status, \ .get_status = netdev_dpdk_get_status, \
.reconfigure = netdev_dpdk_reconfigure, \ .reconfigure = netdev_dpdk_reconfigure, \
.rxq_recv = netdev_dpdk_rxq_recv, \ .rxq_recv = netdev_dpdk_rxq_recv
DPDK_FLOW_OFFLOAD_API
static const struct netdev_class dpdk_class = { static const struct netdev_class dpdk_class = {
.type = "dpdk", .type = "dpdk",

View File

@@ -34,6 +34,9 @@ struct rte_flow_action;
void netdev_dpdk_register(void); void netdev_dpdk_register(void);
void free_dpdk_buf(struct dp_packet *); void free_dpdk_buf(struct dp_packet *);
bool netdev_dpdk_flow_api_supported(struct netdev *);
int int
netdev_dpdk_rte_flow_destroy(struct netdev *netdev, netdev_dpdk_rte_flow_destroy(struct netdev *netdev,
struct rte_flow *rte_flow, struct rte_flow *rte_flow,

View File

@@ -24,6 +24,7 @@
#include "dp-packet.h" #include "dp-packet.h"
#include "dpif-netdev.h" #include "dpif-netdev.h"
#include "flow.h" #include "flow.h"
#include "netdev-offload-provider.h"
#include "netdev-provider.h" #include "netdev-provider.h"
#include "netdev-vport.h" #include "netdev-vport.h"
#include "odp-util.h" #include "odp-util.h"
@@ -1523,10 +1524,6 @@ exit:
return error ? -1 : 0; return error ? -1 : 0;
} }
#define DUMMY_FLOW_OFFLOAD_API \
.flow_put = netdev_dummy_flow_put, \
.flow_del = netdev_dummy_flow_del
#define NETDEV_DUMMY_CLASS_COMMON \ #define NETDEV_DUMMY_CLASS_COMMON \
.run = netdev_dummy_run, \ .run = netdev_dummy_run, \
.wait = netdev_dummy_wait, \ .wait = netdev_dummy_wait, \
@@ -1559,8 +1556,7 @@ exit:
.rxq_dealloc = netdev_dummy_rxq_dealloc, \ .rxq_dealloc = netdev_dummy_rxq_dealloc, \
.rxq_recv = netdev_dummy_rxq_recv, \ .rxq_recv = netdev_dummy_rxq_recv, \
.rxq_wait = netdev_dummy_rxq_wait, \ .rxq_wait = netdev_dummy_rxq_wait, \
.rxq_drain = netdev_dummy_rxq_drain, \ .rxq_drain = netdev_dummy_rxq_drain
DUMMY_FLOW_OFFLOAD_API
static const struct netdev_class dummy_class = { static const struct netdev_class dummy_class = {
NETDEV_DUMMY_CLASS_COMMON, NETDEV_DUMMY_CLASS_COMMON,
@@ -1578,6 +1574,20 @@ static const struct netdev_class dummy_pmd_class = {
.is_pmd = true, .is_pmd = true,
.reconfigure = netdev_dummy_reconfigure .reconfigure = netdev_dummy_reconfigure
}; };
static int
netdev_dummy_offloads_init_flow_api(struct netdev *netdev)
{
return is_dummy_class(netdev->netdev_class) ? 0 : EOPNOTSUPP;
}
static const struct netdev_flow_api netdev_dummy_offloads = {
.type = "dummy",
.flow_put = netdev_dummy_flow_put,
.flow_del = netdev_dummy_flow_del,
.init_flow_api = netdev_dummy_offloads_init_flow_api,
};
/* Helper functions. */ /* Helper functions. */
@@ -2024,5 +2034,7 @@ netdev_dummy_register(enum dummy_level level)
netdev_register_provider(&dummy_internal_class); netdev_register_provider(&dummy_internal_class);
netdev_register_provider(&dummy_pmd_class); netdev_register_provider(&dummy_pmd_class);
netdev_register_flow_api_provider(&netdev_dummy_offloads);
netdev_vport_tunnel_register(); netdev_vport_tunnel_register();
} }

View File

@@ -55,7 +55,6 @@
#include "hash.h" #include "hash.h"
#include "openvswitch/hmap.h" #include "openvswitch/hmap.h"
#include "netdev-provider.h" #include "netdev-provider.h"
#include "netdev-tc-offloads.h"
#include "netdev-vport.h" #include "netdev-vport.h"
#include "netlink-notifier.h" #include "netlink-notifier.h"
#include "netlink-socket.h" #include "netlink-socket.h"
@@ -3321,7 +3320,6 @@ exit:
const struct netdev_class netdev_linux_class = { const struct netdev_class netdev_linux_class = {
NETDEV_LINUX_CLASS_COMMON, NETDEV_LINUX_CLASS_COMMON,
LINUX_FLOW_OFFLOAD_API,
.type = "system", .type = "system",
.construct = netdev_linux_construct, .construct = netdev_linux_construct,
.get_stats = netdev_linux_get_stats, .get_stats = netdev_linux_get_stats,
@@ -3341,7 +3339,6 @@ const struct netdev_class netdev_tap_class = {
const struct netdev_class netdev_internal_class = { const struct netdev_class netdev_internal_class = {
NETDEV_LINUX_CLASS_COMMON, NETDEV_LINUX_CLASS_COMMON,
LINUX_FLOW_OFFLOAD_API,
.type = "internal", .type = "internal",
.construct = netdev_linux_construct, .construct = netdev_linux_construct,
.get_stats = netdev_internal_get_stats, .get_stats = netdev_internal_get_stats,

View File

@@ -29,14 +29,4 @@ int netdev_linux_ethtool_set_flag(struct netdev *netdev, uint32_t flag,
const char *flag_name, bool enable); const char *flag_name, bool enable);
int linux_get_ifindex(const char *netdev_name); int linux_get_ifindex(const char *netdev_name);
#define LINUX_FLOW_OFFLOAD_API \
.flow_flush = netdev_tc_flow_flush, \
.flow_dump_create = netdev_tc_flow_dump_create, \
.flow_dump_destroy = netdev_tc_flow_dump_destroy, \
.flow_dump_next = netdev_tc_flow_dump_next, \
.flow_put = netdev_tc_flow_put, \
.flow_get = netdev_tc_flow_get, \
.flow_del = netdev_tc_flow_del, \
.init_flow_api = netdev_tc_init_flow_api
#endif /* netdev-linux.h */ #endif /* netdev-linux.h */

View File

@@ -0,0 +1,99 @@
/*
* Copyright (c) 2009, 2010, 2011, 2012, 2013, 2014, 2016 Nicira, Inc.
* Copyright (c) 2019 Samsung Electronics Co.,Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef NETDEV_FLOW_API_PROVIDER_H
#define NETDEV_FLOW_API_PROVIDER_H 1
#include "flow.h"
#include "openvswitch/types.h"
#include "packets.h"
#ifdef __cplusplus
extern "C" {
#endif
struct netdev_flow_api {
char *type;
/* Flush all offloaded flows from a netdev.
* Return 0 if successful, otherwise returns a positive errno value. */
int (*flow_flush)(struct netdev *);
/* Flow dumping interface.
*
* This is the back-end for the flow dumping interface described in
* dpif.h. Please read the comments there first, because this code
* closely follows it.
*
* On success returns 0 and allocates data, on failure returns
* positive errno. */
int (*flow_dump_create)(struct netdev *, struct netdev_flow_dump **dump);
int (*flow_dump_destroy)(struct netdev_flow_dump *);
/* Returns true if there are more flows to dump.
* 'rbuffer' is used as a temporary buffer and needs to be pre allocated
* by the caller. While there are more flows the same 'rbuffer'
* should be provided. 'wbuffer' is used to store dumped actions and needs
* to be pre allocated by the caller. */
bool (*flow_dump_next)(struct netdev_flow_dump *, struct match *,
struct nlattr **actions,
struct dpif_flow_stats *stats,
struct dpif_flow_attrs *attrs, ovs_u128 *ufid,
struct ofpbuf *rbuffer, struct ofpbuf *wbuffer);
/* Offload the given flow on netdev.
* To modify a flow, use the same ufid.
* 'actions' are in netlink format, as with struct dpif_flow_put.
* 'info' is extra info needed to offload the flow.
* 'stats' is populated according to the rules set out in the description
* above 'struct dpif_flow_put'.
* Return 0 if successful, otherwise returns a positive errno value. */
int (*flow_put)(struct netdev *, struct match *, struct nlattr *actions,
size_t actions_len, const ovs_u128 *ufid,
struct offload_info *info, struct dpif_flow_stats *);
/* Queries a flow specified by ufid on netdev.
* Fills output buffer as 'wbuffer' in flow_dump_next, which
* needs to be be pre allocated.
* Return 0 if successful, otherwise returns a positive errno value. */
int (*flow_get)(struct netdev *, struct match *, struct nlattr **actions,
const ovs_u128 *ufid, struct dpif_flow_stats *,
struct dpif_flow_attrs *, struct ofpbuf *wbuffer);
/* Delete a flow specified by ufid from netdev.
* 'stats' is populated according to the rules set out in the description
* above 'struct dpif_flow_del'.
* Return 0 if successful, otherwise returns a positive errno value. */
int (*flow_del)(struct netdev *, const ovs_u128 *ufid,
struct dpif_flow_stats *);
/* Initializies the netdev flow api.
* Return 0 if successful, otherwise returns a positive errno value. */
int (*init_flow_api)(struct netdev *);
};
int netdev_register_flow_api_provider(const struct netdev_flow_api *);
int netdev_unregister_flow_api_provider(const char *type);
#ifdef __linux__
extern const struct netdev_flow_api netdev_tc_offloads;
#endif
#ifdef __cplusplus
}
#endif
#endif /* NETDEV_FLOW_API_PROVIDER_H */

View File

@@ -23,6 +23,7 @@
#include "netdev.h" #include "netdev.h"
#include "openvswitch/list.h" #include "openvswitch/list.h"
#include "ovs-numa.h" #include "ovs-numa.h"
#include "ovs-rcu.h"
#include "packets.h" #include "packets.h"
#include "seq.h" #include "seq.h"
#include "openvswitch/shash.h" #include "openvswitch/shash.h"
@@ -93,6 +94,9 @@ struct netdev {
int n_rxq; int n_rxq;
struct shash_node *node; /* Pointer to element in global map. */ struct shash_node *node; /* Pointer to element in global map. */
struct ovs_list saved_flags_list; /* Contains "struct netdev_saved_flags". */ struct ovs_list saved_flags_list; /* Contains "struct netdev_saved_flags". */
/* Functions to control flow offloading. */
OVSRCU_TYPE(const struct netdev_flow_api *) flow_api;
struct netdev_hw_info hw_info; /* offload-capable netdev info */ struct netdev_hw_info hw_info; /* offload-capable netdev info */
}; };
@@ -822,69 +826,6 @@ struct netdev_class {
/* Discards all packets waiting to be received from 'rx'. */ /* Discards all packets waiting to be received from 'rx'. */
int (*rxq_drain)(struct netdev_rxq *rx); int (*rxq_drain)(struct netdev_rxq *rx);
/* ## -------------------------------- ## */
/* ## netdev flow offloading functions ## */
/* ## -------------------------------- ## */
/* If a particular netdev class does not support offloading flows,
* all these function pointers must be NULL. */
/* Flush all offloaded flows from a netdev.
* Return 0 if successful, otherwise returns a positive errno value. */
int (*flow_flush)(struct netdev *);
/* Flow dumping interface.
*
* This is the back-end for the flow dumping interface described in
* dpif.h. Please read the comments there first, because this code
* closely follows it.
*
* On success returns 0 and allocates data, on failure returns
* positive errno. */
int (*flow_dump_create)(struct netdev *, struct netdev_flow_dump **dump);
int (*flow_dump_destroy)(struct netdev_flow_dump *);
/* Returns true if there are more flows to dump.
* 'rbuffer' is used as a temporary buffer and needs to be pre allocated
* by the caller. While there are more flows the same 'rbuffer'
* should be provided. 'wbuffer' is used to store dumped actions and needs
* to be pre allocated by the caller. */
bool (*flow_dump_next)(struct netdev_flow_dump *, struct match *,
struct nlattr **actions,
struct dpif_flow_stats *stats,
struct dpif_flow_attrs *attrs, ovs_u128 *ufid,
struct ofpbuf *rbuffer, struct ofpbuf *wbuffer);
/* Offload the given flow on netdev.
* To modify a flow, use the same ufid.
* 'actions' are in netlink format, as with struct dpif_flow_put.
* 'info' is extra info needed to offload the flow.
* 'stats' is populated according to the rules set out in the description
* above 'struct dpif_flow_put'.
* Return 0 if successful, otherwise returns a positive errno value. */
int (*flow_put)(struct netdev *, struct match *, struct nlattr *actions,
size_t actions_len, const ovs_u128 *ufid,
struct offload_info *info, struct dpif_flow_stats *);
/* Queries a flow specified by ufid on netdev.
* Fills output buffer as 'wbuffer' in flow_dump_next, which
* needs to be be pre allocated.
* Return 0 if successful, otherwise returns a positive errno value. */
int (*flow_get)(struct netdev *, struct match *, struct nlattr **actions,
const ovs_u128 *ufid, struct dpif_flow_stats *,
struct dpif_flow_attrs *, struct ofpbuf *wbuffer);
/* Delete a flow specified by ufid from netdev.
* 'stats' is populated according to the rules set out in the description
* above 'struct dpif_flow_del'.
* Return 0 if successful, otherwise returns a positive errno value. */
int (*flow_del)(struct netdev *, const ovs_u128 *ufid,
struct dpif_flow_stats *);
/* Initializies the netdev flow api.
* Return 0 if successful, otherwise returns a positive errno value. */
int (*init_flow_api)(struct netdev *);
/* Get a block_id from the netdev. /* Get a block_id from the netdev.
* Returns the block_id or 0 if none exists for netdev. */ * Returns the block_id or 0 if none exists for netdev. */
uint32_t (*get_block_id)(struct netdev *); uint32_t (*get_block_id)(struct netdev *);

View File

@@ -21,6 +21,7 @@
#include "cmap.h" #include "cmap.h"
#include "dpif-netdev.h" #include "dpif-netdev.h"
#include "netdev-offload-provider.h"
#include "netdev-provider.h" #include "netdev-provider.h"
#include "openvswitch/match.h" #include "openvswitch/match.h"
#include "openvswitch/vlog.h" #include "openvswitch/vlog.h"
@@ -29,6 +30,23 @@
VLOG_DEFINE_THIS_MODULE(netdev_rte_offloads); VLOG_DEFINE_THIS_MODULE(netdev_rte_offloads);
/* Thread-safety
* =============
*
* Below API is NOT thread safe in following terms:
*
* - The caller must be sure that none of these functions will be called
* simultaneously. Even for different 'netdev's.
*
* - The caller must be sure that 'netdev' will not be destructed/deallocated.
*
* - The caller must be sure that 'netdev' configuration will not be changed.
* For example, simultaneous call of 'netdev_reconfigure()' for the same
* 'netdev' is forbidden.
*
* For current implementation all above restrictions could be fulfilled by
* taking the datapath 'port_mutex' in lib/dpif-netdev.c. */
/* /*
* A mapping from ufid to dpdk rte_flow. * A mapping from ufid to dpdk rte_flow.
*/ */
@@ -689,7 +707,7 @@ netdev_rte_offloads_destroy_flow(struct netdev *netdev,
return ret; return ret;
} }
int static int
netdev_rte_offloads_flow_put(struct netdev *netdev, struct match *match, netdev_rte_offloads_flow_put(struct netdev *netdev, struct match *match,
struct nlattr *actions, size_t actions_len, struct nlattr *actions, size_t actions_len,
const ovs_u128 *ufid, struct offload_info *info, const ovs_u128 *ufid, struct offload_info *info,
@@ -719,7 +737,7 @@ netdev_rte_offloads_flow_put(struct netdev *netdev, struct match *match,
actions_len, ufid, info); actions_len, ufid, info);
} }
int static int
netdev_rte_offloads_flow_del(struct netdev *netdev, const ovs_u128 *ufid, netdev_rte_offloads_flow_del(struct netdev *netdev, const ovs_u128 *ufid,
struct dpif_flow_stats *stats OVS_UNUSED) struct dpif_flow_stats *stats OVS_UNUSED)
{ {
@@ -731,3 +749,21 @@ netdev_rte_offloads_flow_del(struct netdev *netdev, const ovs_u128 *ufid,
return netdev_rte_offloads_destroy_flow(netdev, ufid, rte_flow); return netdev_rte_offloads_destroy_flow(netdev, ufid, rte_flow);
} }
static int
netdev_rte_offloads_init_flow_api(struct netdev *netdev)
{
return netdev_dpdk_flow_api_supported(netdev) ? 0 : EOPNOTSUPP;
}
static const struct netdev_flow_api netdev_dpdk_offloads = {
.type = "dpdk_flow_api",
.flow_put = netdev_rte_offloads_flow_put,
.flow_del = netdev_rte_offloads_flow_del,
.init_flow_api = netdev_rte_offloads_init_flow_api,
};
void netdev_dpdk_flow_api_register(void)
{
netdev_register_flow_api_provider(&netdev_dpdk_offloads);
}

View File

@@ -14,44 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
#ifndef NETDEV_VPORT_OFFLOADS_H #ifndef NETDEV_DPDK_OFFLOADS_H
#define NETDEV_VPORT_OFFLOADS_H 1 #define NETDEV_DPDK_OFFLOADS_H 1
#include "openvswitch/types.h" void netdev_dpdk_flow_api_register(void);
struct netdev;
struct match;
struct nlattr;
struct offload_info;
struct dpif_flow_stats;
/* Thread-safety
* =============
*
* Below API is NOT thread safe in following terms:
*
* - The caller must be sure that none of these functions will be called
* simultaneously. Even for different 'netdev's.
*
* - The caller must be sure that 'netdev' will not be destructed/deallocated.
*
* - The caller must be sure that 'netdev' configuration will not be changed.
* For example, simultaneous call of 'netdev_reconfigure()' for the same
* 'netdev' is forbidden.
*
* For current implementation all above restrictions could be fulfilled by
* taking the datapath 'port_mutex' in lib/dpif-netdev.c. */
int netdev_rte_offloads_flow_put(struct netdev *netdev, struct match *match,
struct nlattr *actions, size_t actions_len,
const ovs_u128 *ufid,
struct offload_info *info,
struct dpif_flow_stats *stats);
int netdev_rte_offloads_flow_del(struct netdev *netdev, const ovs_u128 *ufid,
struct dpif_flow_stats *stats);
#define DPDK_FLOW_OFFLOAD_API \
.flow_put = netdev_rte_offloads_flow_put, \
.flow_del = netdev_rte_offloads_flow_del
#endif /* netdev-rte-offloads.h */ #endif /* netdev-rte-offloads.h */

View File

@@ -16,7 +16,6 @@
*/ */
#include <config.h> #include <config.h>
#include "netdev-tc-offloads.h"
#include <errno.h> #include <errno.h>
#include <linux/if_ether.h> #include <linux/if_ether.h>
@@ -31,6 +30,8 @@
#include "openvswitch/util.h" #include "openvswitch/util.h"
#include "openvswitch/vlog.h" #include "openvswitch/vlog.h"
#include "netdev-linux.h" #include "netdev-linux.h"
#include "netdev-offload-provider.h"
#include "netdev-provider.h"
#include "netlink.h" #include "netlink.h"
#include "netlink-socket.h" #include "netlink-socket.h"
#include "odp-netlink.h" #include "odp-netlink.h"
@@ -350,7 +351,7 @@ get_block_id_from_netdev(struct netdev *netdev)
return 0; return 0;
} }
int static int
netdev_tc_flow_flush(struct netdev *netdev) netdev_tc_flow_flush(struct netdev *netdev)
{ {
enum tc_qdisc_hook hook = get_tc_qdisc_hook(netdev); enum tc_qdisc_hook hook = get_tc_qdisc_hook(netdev);
@@ -368,7 +369,7 @@ netdev_tc_flow_flush(struct netdev *netdev)
return tc_flush(ifindex, block_id, hook); return tc_flush(ifindex, block_id, hook);
} }
int static int
netdev_tc_flow_dump_create(struct netdev *netdev, netdev_tc_flow_dump_create(struct netdev *netdev,
struct netdev_flow_dump **dump_out) struct netdev_flow_dump **dump_out)
{ {
@@ -395,7 +396,7 @@ netdev_tc_flow_dump_create(struct netdev *netdev,
return 0; return 0;
} }
int static int
netdev_tc_flow_dump_destroy(struct netdev_flow_dump *dump) netdev_tc_flow_dump_destroy(struct netdev_flow_dump *dump)
{ {
nl_dump_done(dump->nl_dump); nl_dump_done(dump->nl_dump);
@@ -729,7 +730,7 @@ parse_tc_flower_to_match(struct tc_flower *flower,
return 0; return 0;
} }
bool static bool
netdev_tc_flow_dump_next(struct netdev_flow_dump *dump, netdev_tc_flow_dump_next(struct netdev_flow_dump *dump,
struct match *match, struct match *match,
struct nlattr **actions, struct nlattr **actions,
@@ -1082,7 +1083,7 @@ flower_match_to_tun_opt(struct tc_flower *flower, const struct flow_tnl *tnl,
flower->mask.tunnel.metadata.present.len = tnl->metadata.present.len; flower->mask.tunnel.metadata.present.len = tnl->metadata.present.len;
} }
int static int
netdev_tc_flow_put(struct netdev *netdev, struct match *match, netdev_tc_flow_put(struct netdev *netdev, struct match *match,
struct nlattr *actions, size_t actions_len, struct nlattr *actions, size_t actions_len,
const ovs_u128 *ufid, struct offload_info *info, const ovs_u128 *ufid, struct offload_info *info,
@@ -1379,7 +1380,7 @@ netdev_tc_flow_put(struct netdev *netdev, struct match *match,
return err; return err;
} }
int static int
netdev_tc_flow_get(struct netdev *netdev OVS_UNUSED, netdev_tc_flow_get(struct netdev *netdev OVS_UNUSED,
struct match *match, struct match *match,
struct nlattr **actions, struct nlattr **actions,
@@ -1434,7 +1435,7 @@ netdev_tc_flow_get(struct netdev *netdev OVS_UNUSED,
return 0; return 0;
} }
int static int
netdev_tc_flow_del(struct netdev *netdev OVS_UNUSED, netdev_tc_flow_del(struct netdev *netdev OVS_UNUSED,
const ovs_u128 *ufid, const ovs_u128 *ufid,
struct dpif_flow_stats *stats) struct dpif_flow_stats *stats)
@@ -1554,7 +1555,7 @@ probe_tc_block_support(int ifindex)
} }
} }
int static int
netdev_tc_init_flow_api(struct netdev *netdev) netdev_tc_init_flow_api(struct netdev *netdev)
{ {
static struct ovsthread_once multi_mask_once = OVSTHREAD_ONCE_INITIALIZER; static struct ovsthread_once multi_mask_once = OVSTHREAD_ONCE_INITIALIZER;
@@ -1566,8 +1567,8 @@ netdev_tc_init_flow_api(struct netdev *netdev)
ifindex = netdev_get_ifindex(netdev); ifindex = netdev_get_ifindex(netdev);
if (ifindex < 0) { if (ifindex < 0) {
VLOG_ERR_RL(&error_rl, "init: failed to get ifindex for %s: %s", VLOG_INFO("init: failed to get ifindex for %s: %s",
netdev_get_name(netdev), ovs_strerror(-ifindex)); netdev_get_name(netdev), ovs_strerror(-ifindex));
return -ifindex; return -ifindex;
} }
@@ -1588,8 +1589,8 @@ netdev_tc_init_flow_api(struct netdev *netdev)
error = tc_add_del_qdisc(ifindex, true, block_id, hook); error = tc_add_del_qdisc(ifindex, true, block_id, hook);
if (error && error != EEXIST) { if (error && error != EEXIST) {
VLOG_ERR("failed adding ingress qdisc required for offloading: %s", VLOG_INFO("failed adding ingress qdisc required for offloading: %s",
ovs_strerror(error)); ovs_strerror(error));
return error; return error;
} }
@@ -1597,3 +1598,15 @@ netdev_tc_init_flow_api(struct netdev *netdev)
return 0; return 0;
} }
const struct netdev_flow_api netdev_tc_offloads = {
.type = "linux_tc",
.flow_flush = netdev_tc_flow_flush,
.flow_dump_create = netdev_tc_flow_dump_create,
.flow_dump_destroy = netdev_tc_flow_dump_destroy,
.flow_dump_next = netdev_tc_flow_dump_next,
.flow_put = netdev_tc_flow_put,
.flow_get = netdev_tc_flow_get,
.flow_del = netdev_tc_flow_del,
.init_flow_api = netdev_tc_init_flow_api,
};

View File

@@ -1,44 +0,0 @@
/*
* Copyright (c) 2016 Mellanox Technologies, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef NETDEV_TC_OFFLOADS_H
#define NETDEV_TC_OFFLOADS_H 1
#include "netdev-provider.h"
int netdev_tc_flow_flush(struct netdev *);
int netdev_tc_flow_dump_create(struct netdev *, struct netdev_flow_dump **);
int netdev_tc_flow_dump_destroy(struct netdev_flow_dump *);
bool netdev_tc_flow_dump_next(struct netdev_flow_dump *, struct match *,
struct nlattr **actions,
struct dpif_flow_stats *,
struct dpif_flow_attrs *,
ovs_u128 *ufid,
struct ofpbuf *rbuffer,
struct ofpbuf *wbuffer);
int netdev_tc_flow_put(struct netdev *, struct match *,
struct nlattr *actions, size_t actions_len,
const ovs_u128 *, struct offload_info *,
struct dpif_flow_stats *);
int netdev_tc_flow_get(struct netdev *, struct match *,
struct nlattr **actions, const ovs_u128 *,
struct dpif_flow_stats *,
struct dpif_flow_attrs *, struct ofpbuf *);
int netdev_tc_flow_del(struct netdev *, const ovs_u128 *,
struct dpif_flow_stats *);
int netdev_tc_init_flow_api(struct netdev *);
#endif /* netdev-tc-offloads.h */

View File

@@ -47,7 +47,6 @@
#include "unaligned.h" #include "unaligned.h"
#include "unixctl.h" #include "unixctl.h"
#include "openvswitch/vlog.h" #include "openvswitch/vlog.h"
#include "netdev-tc-offloads.h"
#ifdef __linux__ #ifdef __linux__
#include "netdev-linux.h" #include "netdev-linux.h"
#endif #endif
@@ -1116,10 +1115,8 @@ netdev_vport_get_ifindex(const struct netdev *netdev_)
} }
#define NETDEV_VPORT_GET_IFINDEX netdev_vport_get_ifindex #define NETDEV_VPORT_GET_IFINDEX netdev_vport_get_ifindex
#define NETDEV_FLOW_OFFLOAD_API , LINUX_FLOW_OFFLOAD_API
#else /* !__linux__ */ #else /* !__linux__ */
#define NETDEV_VPORT_GET_IFINDEX NULL #define NETDEV_VPORT_GET_IFINDEX NULL
#define NETDEV_FLOW_OFFLOAD_API
#endif /* __linux__ */ #endif /* __linux__ */
#define VPORT_FUNCTIONS_COMMON \ #define VPORT_FUNCTIONS_COMMON \
@@ -1133,8 +1130,7 @@ netdev_vport_get_ifindex(const struct netdev *netdev_)
.get_etheraddr = netdev_vport_get_etheraddr, \ .get_etheraddr = netdev_vport_get_etheraddr, \
.get_stats = netdev_vport_get_stats, \ .get_stats = netdev_vport_get_stats, \
.get_pt_mode = netdev_vport_get_pt_mode, \ .get_pt_mode = netdev_vport_get_pt_mode, \
.update_flags = netdev_vport_update_flags \ .update_flags = netdev_vport_update_flags
NETDEV_FLOW_OFFLOAD_API
#define TUNNEL_FUNCTIONS_COMMON \ #define TUNNEL_FUNCTIONS_COMMON \
VPORT_FUNCTIONS_COMMON, \ VPORT_FUNCTIONS_COMMON, \

View File

@@ -39,6 +39,7 @@
#include "fatal-signal.h" #include "fatal-signal.h"
#include "hash.h" #include "hash.h"
#include "openvswitch/list.h" #include "openvswitch/list.h"
#include "netdev-offload-provider.h"
#include "netdev-provider.h" #include "netdev-provider.h"
#include "netdev-vport.h" #include "netdev-vport.h"
#include "odp-netlink.h" #include "odp-netlink.h"
@@ -98,6 +99,22 @@ struct netdev_registered_class {
static bool netdev_flow_api_enabled = false; static bool netdev_flow_api_enabled = false;
/* Protects 'netdev_flow_apis'. */
static struct ovs_mutex netdev_flow_api_provider_mutex = OVS_MUTEX_INITIALIZER;
/* Contains 'struct netdev_registered_flow_api's. */
static struct cmap netdev_flow_apis = CMAP_INITIALIZER;
struct netdev_registered_flow_api {
struct cmap_node cmap_node; /* In 'netdev_flow_apis', by flow_api->type. */
const struct netdev_flow_api *flow_api;
/* Number of references: one for the flow_api itself and one for every
* instance of the netdev that uses it. */
struct ovs_refcount refcnt;
};
/* This is set pretty low because we probably won't learn anything from the /* This is set pretty low because we probably won't learn anything from the
* additional log messages. */ * additional log messages. */
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20); static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
@@ -146,6 +163,8 @@ netdev_initialize(void)
netdev_register_provider(&netdev_internal_class); netdev_register_provider(&netdev_internal_class);
netdev_register_provider(&netdev_tap_class); netdev_register_provider(&netdev_tap_class);
netdev_vport_tunnel_register(); netdev_vport_tunnel_register();
netdev_register_flow_api_provider(&netdev_tc_offloads);
#endif #endif
#if defined(__FreeBSD__) || defined(__NetBSD__) #if defined(__FreeBSD__) || defined(__NetBSD__)
netdev_register_provider(&netdev_tap_class); netdev_register_provider(&netdev_tap_class);
@@ -279,6 +298,87 @@ netdev_unregister_provider(const char *type)
return error; return error;
} }
static struct netdev_registered_flow_api *
netdev_lookup_flow_api(const char *type)
{
struct netdev_registered_flow_api *rfa;
CMAP_FOR_EACH_WITH_HASH (rfa, cmap_node, hash_string(type, 0),
&netdev_flow_apis) {
if (!strcmp(type, rfa->flow_api->type)) {
return rfa;
}
}
return NULL;
}
/* Registers a new netdev flow api provider. */
int
netdev_register_flow_api_provider(const struct netdev_flow_api *new_flow_api)
OVS_EXCLUDED(netdev_flow_api_provider_mutex)
{
int error = 0;
if (!new_flow_api->init_flow_api) {
VLOG_WARN("attempted to register invalid flow api provider: %s",
new_flow_api->type);
error = EINVAL;
}
ovs_mutex_lock(&netdev_flow_api_provider_mutex);
if (netdev_lookup_flow_api(new_flow_api->type)) {
VLOG_WARN("attempted to register duplicate flow api provider: %s",
new_flow_api->type);
error = EEXIST;
} else {
struct netdev_registered_flow_api *rfa;
rfa = xmalloc(sizeof *rfa);
cmap_insert(&netdev_flow_apis, &rfa->cmap_node,
hash_string(new_flow_api->type, 0));
rfa->flow_api = new_flow_api;
ovs_refcount_init(&rfa->refcnt);
VLOG_DBG("netdev: flow API '%s' registered.", new_flow_api->type);
}
ovs_mutex_unlock(&netdev_flow_api_provider_mutex);
return error;
}
/* Unregisters a netdev flow api provider. 'type' must have been previously
* registered and not currently be in use by any netdevs. After unregistration
* netdev flow api of that type cannot be used for netdevs. (However, the
* provider may still be accessible from other threads until the next RCU grace
* period, so the caller must not free or re-register the same netdev_flow_api
* until that has passed.) */
int
netdev_unregister_flow_api_provider(const char *type)
OVS_EXCLUDED(netdev_flow_api_provider_mutex)
{
struct netdev_registered_flow_api *rfa;
int error;
ovs_mutex_lock(&netdev_flow_api_provider_mutex);
rfa = netdev_lookup_flow_api(type);
if (!rfa) {
VLOG_WARN("attempted to unregister a flow api provider that is not "
"registered: %s", type);
error = EAFNOSUPPORT;
} else if (ovs_refcount_unref(&rfa->refcnt) != 1) {
ovs_refcount_ref(&rfa->refcnt);
VLOG_WARN("attempted to unregister in use flow api provider: %s",
type);
error = EBUSY;
} else {
cmap_remove(&netdev_flow_apis, &rfa->cmap_node,
hash_string(rfa->flow_api->type, 0));
ovsrcu_postpone(free, rfa);
error = 0;
}
ovs_mutex_unlock(&netdev_flow_api_provider_mutex);
return error;
}
/* Clears 'types' and enumerates the types of all currently registered netdev /* Clears 'types' and enumerates the types of all currently registered netdev
* providers into it. The caller must first initialize the sset. */ * providers into it. The caller must first initialize the sset. */
void void
@@ -414,6 +514,7 @@ netdev_open(const char *name, const char *type, struct netdev **netdevp)
netdev->reconfigure_seq = seq_create(); netdev->reconfigure_seq = seq_create();
netdev->last_reconfigure_seq = netdev->last_reconfigure_seq =
seq_read(netdev->reconfigure_seq); seq_read(netdev->reconfigure_seq);
ovsrcu_set(&netdev->flow_api, NULL);
netdev->hw_info.oor = false; netdev->hw_info.oor = false;
netdev->node = shash_add(&netdev_shash, name, netdev); netdev->node = shash_add(&netdev_shash, name, netdev);
@@ -562,6 +663,8 @@ netdev_unref(struct netdev *dev)
ovs_assert(dev->ref_cnt); ovs_assert(dev->ref_cnt);
if (!--dev->ref_cnt) { if (!--dev->ref_cnt) {
const struct netdev_class *class = dev->netdev_class; const struct netdev_class *class = dev->netdev_class;
const struct netdev_flow_api *flow_api =
ovsrcu_get(const struct netdev_flow_api *, &dev->flow_api);
struct netdev_registered_class *rc; struct netdev_registered_class *rc;
dev->netdev_class->destruct(dev); dev->netdev_class->destruct(dev);
@@ -576,6 +679,12 @@ netdev_unref(struct netdev *dev)
rc = netdev_lookup_class(class->type); rc = netdev_lookup_class(class->type);
ovs_refcount_unref(&rc->refcnt); ovs_refcount_unref(&rc->refcnt);
if (flow_api) {
struct netdev_registered_flow_api *rfa =
netdev_lookup_flow_api(flow_api->type);
ovs_refcount_unref(&rfa->refcnt);
}
} else { } else {
ovs_mutex_unlock(&netdev_mutex); ovs_mutex_unlock(&netdev_mutex);
} }
@@ -2148,34 +2257,58 @@ netdev_reconfigure(struct netdev *netdev)
: EOPNOTSUPP); : EOPNOTSUPP);
} }
static int
netdev_assign_flow_api(struct netdev *netdev)
{
struct netdev_registered_flow_api *rfa;
CMAP_FOR_EACH (rfa, cmap_node, &netdev_flow_apis) {
if (!rfa->flow_api->init_flow_api(netdev)) {
ovs_refcount_ref(&rfa->refcnt);
ovsrcu_set(&netdev->flow_api, rfa->flow_api);
VLOG_INFO("%s: Assigned flow API '%s'.",
netdev_get_name(netdev), rfa->flow_api->type);
return 0;
}
VLOG_DBG("%s: flow API '%s' is not suitable.",
netdev_get_name(netdev), rfa->flow_api->type);
}
VLOG_INFO("%s: No suitable flow API found.", netdev_get_name(netdev));
return -1;
}
int int
netdev_flow_flush(struct netdev *netdev) netdev_flow_flush(struct netdev *netdev)
{ {
const struct netdev_class *class = netdev->netdev_class; const struct netdev_flow_api *flow_api =
ovsrcu_get(const struct netdev_flow_api *, &netdev->flow_api);
return (class->flow_flush return (flow_api && flow_api->flow_flush)
? class->flow_flush(netdev) ? flow_api->flow_flush(netdev)
: EOPNOTSUPP); : EOPNOTSUPP;
} }
int int
netdev_flow_dump_create(struct netdev *netdev, struct netdev_flow_dump **dump) netdev_flow_dump_create(struct netdev *netdev, struct netdev_flow_dump **dump)
{ {
const struct netdev_class *class = netdev->netdev_class; const struct netdev_flow_api *flow_api =
ovsrcu_get(const struct netdev_flow_api *, &netdev->flow_api);
return (class->flow_dump_create return (flow_api && flow_api->flow_dump_create)
? class->flow_dump_create(netdev, dump) ? flow_api->flow_dump_create(netdev, dump)
: EOPNOTSUPP); : EOPNOTSUPP;
} }
int int
netdev_flow_dump_destroy(struct netdev_flow_dump *dump) netdev_flow_dump_destroy(struct netdev_flow_dump *dump)
{ {
const struct netdev_class *class = dump->netdev->netdev_class; const struct netdev_flow_api *flow_api =
ovsrcu_get(const struct netdev_flow_api *, &dump->netdev->flow_api);
return (class->flow_dump_destroy return (flow_api && flow_api->flow_dump_destroy)
? class->flow_dump_destroy(dump) ? flow_api->flow_dump_destroy(dump)
: EOPNOTSUPP); : EOPNOTSUPP;
} }
bool bool
@@ -2184,12 +2317,13 @@ netdev_flow_dump_next(struct netdev_flow_dump *dump, struct match *match,
struct dpif_flow_attrs *attrs, ovs_u128 *ufid, struct dpif_flow_attrs *attrs, ovs_u128 *ufid,
struct ofpbuf *rbuffer, struct ofpbuf *wbuffer) struct ofpbuf *rbuffer, struct ofpbuf *wbuffer)
{ {
const struct netdev_class *class = dump->netdev->netdev_class; const struct netdev_flow_api *flow_api =
ovsrcu_get(const struct netdev_flow_api *, &dump->netdev->flow_api);
return (class->flow_dump_next return (flow_api && flow_api->flow_dump_next)
? class->flow_dump_next(dump, match, actions, stats, attrs, ? flow_api->flow_dump_next(dump, match, actions, stats, attrs,
ufid, rbuffer, wbuffer) ufid, rbuffer, wbuffer)
: false); : false;
} }
int int
@@ -2198,12 +2332,13 @@ netdev_flow_put(struct netdev *netdev, struct match *match,
const ovs_u128 *ufid, struct offload_info *info, const ovs_u128 *ufid, struct offload_info *info,
struct dpif_flow_stats *stats) struct dpif_flow_stats *stats)
{ {
const struct netdev_class *class = netdev->netdev_class; const struct netdev_flow_api *flow_api =
ovsrcu_get(const struct netdev_flow_api *, &netdev->flow_api);
return (class->flow_put return (flow_api && flow_api->flow_put)
? class->flow_put(netdev, match, actions, act_len, ufid, ? flow_api->flow_put(netdev, match, actions, act_len, ufid,
info, stats) info, stats)
: EOPNOTSUPP); : EOPNOTSUPP;
} }
int int
@@ -2212,36 +2347,43 @@ netdev_flow_get(struct netdev *netdev, struct match *match,
struct dpif_flow_stats *stats, struct dpif_flow_stats *stats,
struct dpif_flow_attrs *attrs, struct ofpbuf *buf) struct dpif_flow_attrs *attrs, struct ofpbuf *buf)
{ {
const struct netdev_class *class = netdev->netdev_class; const struct netdev_flow_api *flow_api =
ovsrcu_get(const struct netdev_flow_api *, &netdev->flow_api);
return (class->flow_get return (flow_api && flow_api->flow_get)
? class->flow_get(netdev, match, actions, ufid, stats, attrs, buf) ? flow_api->flow_get(netdev, match, actions, ufid,
: EOPNOTSUPP); stats, attrs, buf)
: EOPNOTSUPP;
} }
int int
netdev_flow_del(struct netdev *netdev, const ovs_u128 *ufid, netdev_flow_del(struct netdev *netdev, const ovs_u128 *ufid,
struct dpif_flow_stats *stats) struct dpif_flow_stats *stats)
{ {
const struct netdev_class *class = netdev->netdev_class; const struct netdev_flow_api *flow_api =
ovsrcu_get(const struct netdev_flow_api *, &netdev->flow_api);
return (class->flow_del return (flow_api && flow_api->flow_del)
? class->flow_del(netdev, ufid, stats) ? flow_api->flow_del(netdev, ufid, stats)
: EOPNOTSUPP); : EOPNOTSUPP;
} }
int int
netdev_init_flow_api(struct netdev *netdev) netdev_init_flow_api(struct netdev *netdev)
{ {
const struct netdev_class *class = netdev->netdev_class;
if (!netdev_is_flow_api_enabled()) { if (!netdev_is_flow_api_enabled()) {
return EOPNOTSUPP; return EOPNOTSUPP;
} }
return (class->init_flow_api if (ovsrcu_get(const struct netdev_flow_api *, &netdev->flow_api)) {
? class->init_flow_api(netdev) return 0;
: EOPNOTSUPP); }
if (netdev_assign_flow_api(netdev)) {
return EOPNOTSUPP;
}
return 0;
} }
uint32_t uint32_t
@@ -2573,7 +2715,6 @@ netdev_is_offload_rebalance_policy_enabled(void)
return netdev_offload_rebalance_policy; return netdev_offload_rebalance_policy;
} }
#ifdef __linux__
static void static void
netdev_ports_flow_init(void) netdev_ports_flow_init(void)
{ {
@@ -2597,8 +2738,10 @@ netdev_set_flow_api_enabled(const struct smap *ovs_other_config)
VLOG_INFO("netdev: Flow API Enabled"); VLOG_INFO("netdev: Flow API Enabled");
#ifdef __linux__
tc_set_policy(smap_get_def(ovs_other_config, "tc-policy", tc_set_policy(smap_get_def(ovs_other_config, "tc-policy",
TC_POLICY_DEFAULT)); TC_POLICY_DEFAULT));
#endif
if (smap_get_bool(ovs_other_config, "offload-rebalance", false)) { if (smap_get_bool(ovs_other_config, "offload-rebalance", false)) {
netdev_offload_rebalance_policy = true; netdev_offload_rebalance_policy = true;
@@ -2610,9 +2753,3 @@ netdev_set_flow_api_enabled(const struct smap *ovs_other_config)
} }
} }
} }
#else
void
netdev_set_flow_api_enabled(const struct smap *ovs_other_config OVS_UNUSED)
{
}
#endif

View File

@@ -361,9 +361,9 @@ AT_CLEANUP
m4_define([DPIF_NETDEV_FLOW_HW_OFFLOAD], m4_define([DPIF_NETDEV_FLOW_HW_OFFLOAD],
[AT_SETUP([dpif-netdev - partial hw offload - $1]) [AT_SETUP([dpif-netdev - partial hw offload - $1])
AT_SKIP_IF([test "$IS_WIN32" = "yes" || test "$IS_BSD" = "yes"])
OVS_VSWITCHD_START( OVS_VSWITCHD_START(
[add-port br0 p1 -- set interface p1 type=$1 ofport_request=1 options:pstream=punix:$OVS_RUNDIR/p1.sock -- \ [add-port br0 p1 -- \
set interface p1 type=$1 ofport_request=1 options:pstream=punix:$OVS_RUNDIR/p1.sock options:ifindex=1 -- \
set bridge br0 datapath-type=dummy \ set bridge br0 datapath-type=dummy \
other-config:datapath-id=1234 fail-mode=secure], [], [], other-config:datapath-id=1234 fail-mode=secure], [], [],
[m4_if([$1], [dummy-pmd], [--dummy-numa="0,0,0,0,1,1,1,1"], [])]) [m4_if([$1], [dummy-pmd], [--dummy-numa="0,0,0,0,1,1,1,1"], [])])

View File

@@ -173,7 +173,6 @@ m4_define([_OVS_VSWITCHD_START],
/ofproto|INFO|datapath ID changed to fedcba9876543210/d /ofproto|INFO|datapath ID changed to fedcba9876543210/d
/dpdk|INFO|DPDK Disabled - Use other_config:dpdk-init to enable/d /dpdk|INFO|DPDK Disabled - Use other_config:dpdk-init to enable/d
/netlink_socket|INFO|netlink: could not enable listening to all nsid/d /netlink_socket|INFO|netlink: could not enable listening to all nsid/d
/netdev: Flow API/d
/probe tc:/d /probe tc:/d
/tc: Using policy/d']]) /tc: Using policy/d']])
]) ])