2014-07-17 17:26:00 -07:00
|
|
|
|
/*
|
2017-05-30 07:38:18 -07:00
|
|
|
|
* Copyright (c) 2008-2017 Nicira, Inc.
|
2014-07-17 17:26:00 -07:00
|
|
|
|
*
|
|
|
|
|
* 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.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <config.h>
|
2017-11-06 14:42:32 -08:00
|
|
|
|
#include <sys/types.h>
|
|
|
|
|
#include <netinet/in.h>
|
2014-07-17 17:26:00 -07:00
|
|
|
|
#include <arpa/inet.h>
|
|
|
|
|
#include <errno.h>
|
|
|
|
|
#include <inttypes.h>
|
|
|
|
|
#include <sys/socket.h>
|
|
|
|
|
#include <net/if.h>
|
|
|
|
|
#include <stdarg.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
|
|
#include "command-line.h"
|
|
|
|
|
#include "compiler.h"
|
2015-10-28 11:38:00 -07:00
|
|
|
|
#include "ct-dpif.h"
|
2014-07-17 17:26:00 -07:00
|
|
|
|
#include "dirs.h"
|
|
|
|
|
#include "dpctl.h"
|
|
|
|
|
#include "dpif.h"
|
2016-03-03 10:20:46 -08:00
|
|
|
|
#include "openvswitch/dynamic-string.h"
|
2014-07-17 17:26:00 -07:00
|
|
|
|
#include "flow.h"
|
2016-04-04 21:32:06 -04:00
|
|
|
|
#include "openvswitch/match.h"
|
2014-07-17 17:26:00 -07:00
|
|
|
|
#include "netdev.h"
|
2015-02-03 17:08:13 -08:00
|
|
|
|
#include "netdev-dpdk.h"
|
2014-07-17 17:26:00 -07:00
|
|
|
|
#include "netlink.h"
|
|
|
|
|
#include "odp-util.h"
|
2016-03-25 14:10:24 -07:00
|
|
|
|
#include "openvswitch/ofpbuf.h"
|
2014-07-17 17:26:00 -07:00
|
|
|
|
#include "packets.h"
|
2016-07-12 16:37:34 -05:00
|
|
|
|
#include "openvswitch/shash.h"
|
2014-07-17 17:26:00 -07:00
|
|
|
|
#include "simap.h"
|
|
|
|
|
#include "smap.h"
|
|
|
|
|
#include "sset.h"
|
|
|
|
|
#include "timeval.h"
|
|
|
|
|
#include "unixctl.h"
|
|
|
|
|
#include "util.h"
|
2018-02-09 10:04:26 -08:00
|
|
|
|
#include "openvswitch/ofp-flow.h"
|
|
|
|
|
#include "openvswitch/ofp-port.h"
|
2017-06-18 09:46:54 +08:00
|
|
|
|
#include "openvswitch/vlog.h"
|
|
|
|
|
VLOG_DEFINE_THIS_MODULE(dpctl);
|
2014-07-17 17:26:00 -07:00
|
|
|
|
|
2014-10-16 13:26:07 -07:00
|
|
|
|
typedef int dpctl_command_handler(int argc, const char *argv[],
|
|
|
|
|
struct dpctl_params *);
|
|
|
|
|
struct dpctl_command {
|
|
|
|
|
const char *name;
|
|
|
|
|
const char *usage;
|
|
|
|
|
int min_args;
|
|
|
|
|
int max_args;
|
|
|
|
|
dpctl_command_handler *handler;
|
2016-08-15 18:47:29 +00:00
|
|
|
|
enum { DP_RO, DP_RW} mode;
|
2014-10-16 13:26:07 -07:00
|
|
|
|
};
|
|
|
|
|
static const struct dpctl_command *get_all_dpctl_commands(void);
|
2015-05-06 19:00:24 +01:00
|
|
|
|
static void dpctl_print(struct dpctl_params *dpctl_p, const char *fmt, ...)
|
|
|
|
|
OVS_PRINTF_FORMAT(2, 3);
|
|
|
|
|
static void dpctl_error(struct dpctl_params* dpctl_p, int err_no,
|
|
|
|
|
const char *fmt, ...)
|
|
|
|
|
OVS_PRINTF_FORMAT(3, 4);
|
2014-10-16 13:26:07 -07:00
|
|
|
|
|
2014-07-17 17:26:00 -07:00
|
|
|
|
static void
|
|
|
|
|
dpctl_puts(struct dpctl_params *dpctl_p, bool error, const char *string)
|
|
|
|
|
{
|
|
|
|
|
dpctl_p->output(dpctl_p->aux, error, string);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
dpctl_print(struct dpctl_params *dpctl_p, const char *fmt, ...)
|
|
|
|
|
{
|
|
|
|
|
char *string;
|
|
|
|
|
va_list args;
|
|
|
|
|
|
|
|
|
|
va_start(args, fmt);
|
|
|
|
|
string = xvasprintf(fmt, args);
|
|
|
|
|
va_end(args);
|
|
|
|
|
|
|
|
|
|
dpctl_puts(dpctl_p, false, string);
|
|
|
|
|
free(string);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
dpctl_error(struct dpctl_params* dpctl_p, int err_no, const char *fmt, ...)
|
|
|
|
|
{
|
|
|
|
|
const char *subprogram_name = get_subprogram_name();
|
|
|
|
|
struct ds ds = DS_EMPTY_INITIALIZER;
|
|
|
|
|
int save_errno = errno;
|
|
|
|
|
va_list args;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (subprogram_name[0]) {
|
|
|
|
|
ds_put_format(&ds, "%s(%s): ", program_name,subprogram_name);
|
|
|
|
|
} else {
|
|
|
|
|
ds_put_format(&ds, "%s: ", program_name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
va_start(args, fmt);
|
|
|
|
|
ds_put_format_valist(&ds, fmt, args);
|
|
|
|
|
va_end(args);
|
|
|
|
|
|
|
|
|
|
if (err_no != 0) {
|
|
|
|
|
ds_put_format(&ds, " (%s)", ovs_retval_to_string(err_no));
|
|
|
|
|
}
|
|
|
|
|
ds_put_cstr(&ds, "\n");
|
|
|
|
|
|
|
|
|
|
dpctl_puts(dpctl_p, true, ds_cstr(&ds));
|
|
|
|
|
|
|
|
|
|
ds_destroy(&ds);
|
|
|
|
|
|
|
|
|
|
errno = save_errno;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int dpctl_add_if(int argc, const char *argv[], struct dpctl_params *);
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
if_up(struct netdev *netdev)
|
|
|
|
|
{
|
|
|
|
|
return netdev_turn_flags_on(netdev, NETDEV_UP, NULL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Retrieve the name of the datapath if exactly one exists. The caller
|
2017-12-06 22:03:18 -08:00
|
|
|
|
* is responsible for freeing the returned string. If a single datapath
|
|
|
|
|
* name cannot be determined, returns NULL. */
|
2014-07-17 17:26:00 -07:00
|
|
|
|
static char *
|
|
|
|
|
get_one_dp(struct dpctl_params *dpctl_p)
|
|
|
|
|
{
|
|
|
|
|
struct sset types;
|
|
|
|
|
const char *type;
|
|
|
|
|
char *dp_name = NULL;
|
|
|
|
|
size_t count = 0;
|
|
|
|
|
|
|
|
|
|
sset_init(&types);
|
|
|
|
|
dp_enumerate_types(&types);
|
|
|
|
|
SSET_FOR_EACH (type, &types) {
|
|
|
|
|
struct sset names;
|
|
|
|
|
|
|
|
|
|
sset_init(&names);
|
|
|
|
|
if (!dp_enumerate_names(type, &names)) {
|
|
|
|
|
count += sset_count(&names);
|
|
|
|
|
if (!dp_name && count == 1) {
|
|
|
|
|
dp_name = xasprintf("%s@%s", type, SSET_FIRST(&names));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
sset_destroy(&names);
|
|
|
|
|
}
|
|
|
|
|
sset_destroy(&types);
|
|
|
|
|
|
|
|
|
|
if (!count) {
|
|
|
|
|
dpctl_error(dpctl_p, 0, "no datapaths exist");
|
|
|
|
|
} else if (count > 1) {
|
|
|
|
|
dpctl_error(dpctl_p, 0, "multiple datapaths, specify one");
|
|
|
|
|
free(dp_name);
|
|
|
|
|
dp_name = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return dp_name;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
parsed_dpif_open(const char *arg_, bool create, struct dpif **dpifp)
|
|
|
|
|
{
|
|
|
|
|
int result;
|
|
|
|
|
char *name, *type;
|
|
|
|
|
|
|
|
|
|
dp_parse_name(arg_, &name, &type);
|
|
|
|
|
|
|
|
|
|
if (create) {
|
|
|
|
|
result = dpif_create(name, type, dpifp);
|
|
|
|
|
} else {
|
|
|
|
|
result = dpif_open(name, type, dpifp);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
free(name);
|
|
|
|
|
free(type);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-07 16:20:59 -07:00
|
|
|
|
/* Open a dpif with an optional name argument.
|
|
|
|
|
*
|
|
|
|
|
* The datapath name is not a mandatory parameter for this command. If
|
|
|
|
|
* it is not specified -- so 'argc' < 'max_args' -- we retrieve it from
|
|
|
|
|
* the current setup, assuming only one exists. On success stores the
|
|
|
|
|
* opened dpif in '*dpifp'. */
|
|
|
|
|
static int
|
|
|
|
|
opt_dpif_open(int argc, const char *argv[], struct dpctl_params *dpctl_p,
|
|
|
|
|
uint8_t max_args, struct dpif **dpifp)
|
|
|
|
|
{
|
|
|
|
|
int error = 0;
|
|
|
|
|
char *dpname = argc >= max_args ? xstrdup(argv[1]) : get_one_dp(dpctl_p);
|
|
|
|
|
if (!dpname) {
|
|
|
|
|
error = EINVAL;
|
|
|
|
|
dpctl_error(dpctl_p, error, "datapath not found");
|
|
|
|
|
} else {
|
|
|
|
|
error = parsed_dpif_open(dpname, false, dpifp);
|
|
|
|
|
free(dpname);
|
|
|
|
|
if (error) {
|
|
|
|
|
dpctl_error(dpctl_p, error, "opening datapath");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-17 17:26:00 -07:00
|
|
|
|
static int
|
|
|
|
|
dpctl_add_dp(int argc, const char *argv[],
|
|
|
|
|
struct dpctl_params *dpctl_p)
|
|
|
|
|
{
|
|
|
|
|
struct dpif *dpif;
|
|
|
|
|
int error;
|
|
|
|
|
|
|
|
|
|
error = parsed_dpif_open(argv[1], true, &dpif);
|
|
|
|
|
if (error) {
|
|
|
|
|
dpctl_error(dpctl_p, error, "add_dp");
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
dpif_close(dpif);
|
|
|
|
|
if (argc > 2) {
|
|
|
|
|
error = dpctl_add_if(argc, argv, dpctl_p);
|
|
|
|
|
}
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
dpctl_del_dp(int argc OVS_UNUSED, const char *argv[],
|
|
|
|
|
struct dpctl_params *dpctl_p)
|
|
|
|
|
{
|
|
|
|
|
struct dpif *dpif;
|
|
|
|
|
int error;
|
|
|
|
|
|
|
|
|
|
error = parsed_dpif_open(argv[1], false, &dpif);
|
|
|
|
|
if (error) {
|
|
|
|
|
dpctl_error(dpctl_p, error, "opening datapath");
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
error = dpif_delete(dpif);
|
|
|
|
|
if (error) {
|
|
|
|
|
dpctl_error(dpctl_p, error, "del_dp");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dpif_close(dpif);
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
dpctl_add_if(int argc OVS_UNUSED, const char *argv[],
|
|
|
|
|
struct dpctl_params *dpctl_p)
|
|
|
|
|
{
|
|
|
|
|
struct dpif *dpif;
|
|
|
|
|
int i, error, lasterror = 0;
|
|
|
|
|
|
|
|
|
|
error = parsed_dpif_open(argv[1], false, &dpif);
|
|
|
|
|
if (error) {
|
|
|
|
|
dpctl_error(dpctl_p, error, "opening datapath");
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
for (i = 2; i < argc; i++) {
|
|
|
|
|
const char *name, *type;
|
|
|
|
|
char *save_ptr = NULL, *argcopy;
|
|
|
|
|
struct netdev *netdev = NULL;
|
|
|
|
|
struct smap args;
|
|
|
|
|
odp_port_t port_no = ODPP_NONE;
|
|
|
|
|
char *option;
|
|
|
|
|
|
|
|
|
|
argcopy = xstrdup(argv[i]);
|
|
|
|
|
name = strtok_r(argcopy, ",", &save_ptr);
|
|
|
|
|
type = "system";
|
|
|
|
|
|
|
|
|
|
if (!name) {
|
|
|
|
|
dpctl_error(dpctl_p, 0, "%s is not a valid network device name",
|
|
|
|
|
argv[i]);
|
|
|
|
|
error = EINVAL;
|
|
|
|
|
goto next;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
smap_init(&args);
|
|
|
|
|
while ((option = strtok_r(NULL, ",", &save_ptr)) != NULL) {
|
|
|
|
|
char *save_ptr_2 = NULL;
|
|
|
|
|
char *key, *value;
|
|
|
|
|
|
|
|
|
|
key = strtok_r(option, "=", &save_ptr_2);
|
|
|
|
|
value = strtok_r(NULL, "", &save_ptr_2);
|
|
|
|
|
if (!value) {
|
|
|
|
|
value = "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!strcmp(key, "type")) {
|
|
|
|
|
type = value;
|
|
|
|
|
} else if (!strcmp(key, "port_no")) {
|
|
|
|
|
port_no = u32_to_odp(atoi(value));
|
|
|
|
|
} else if (!smap_add_once(&args, key, value)) {
|
|
|
|
|
dpctl_error(dpctl_p, 0, "duplicate \"%s\" option", key);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
error = netdev_open(name, type, &netdev);
|
|
|
|
|
if (error) {
|
|
|
|
|
dpctl_error(dpctl_p, error, "%s: failed to open network device",
|
|
|
|
|
name);
|
|
|
|
|
goto next_destroy_args;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
error = netdev_set_config(netdev, &args, NULL);
|
|
|
|
|
if (error) {
|
|
|
|
|
goto next_destroy_args;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
error = dpif_port_add(dpif, netdev, &port_no);
|
|
|
|
|
if (error) {
|
|
|
|
|
dpctl_error(dpctl_p, error, "adding %s to %s failed", name,
|
|
|
|
|
argv[1]);
|
|
|
|
|
goto next_destroy_args;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
error = if_up(netdev);
|
|
|
|
|
if (error) {
|
|
|
|
|
dpctl_error(dpctl_p, error, "%s: failed bringing interface up",
|
|
|
|
|
name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
next_destroy_args:
|
|
|
|
|
netdev_close(netdev);
|
|
|
|
|
smap_destroy(&args);
|
|
|
|
|
next:
|
|
|
|
|
free(argcopy);
|
|
|
|
|
if (error) {
|
|
|
|
|
lasterror = error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
dpif_close(dpif);
|
|
|
|
|
|
|
|
|
|
return lasterror;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
dpctl_set_if(int argc, const char *argv[], struct dpctl_params *dpctl_p)
|
|
|
|
|
{
|
|
|
|
|
struct dpif *dpif;
|
|
|
|
|
int i, error, lasterror = 0;
|
|
|
|
|
|
|
|
|
|
error = parsed_dpif_open(argv[1], false, &dpif);
|
|
|
|
|
if (error) {
|
|
|
|
|
dpctl_error(dpctl_p, error, "opening datapath");
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
for (i = 2; i < argc; i++) {
|
|
|
|
|
struct netdev *netdev = NULL;
|
|
|
|
|
struct dpif_port dpif_port;
|
|
|
|
|
char *save_ptr = NULL;
|
|
|
|
|
char *type = NULL;
|
|
|
|
|
char *argcopy;
|
|
|
|
|
const char *name;
|
|
|
|
|
struct smap args;
|
|
|
|
|
odp_port_t port_no;
|
|
|
|
|
char *option;
|
2017-08-02 15:03:06 -07:00
|
|
|
|
|
|
|
|
|
error = 0;
|
2014-07-17 17:26:00 -07:00
|
|
|
|
|
|
|
|
|
argcopy = xstrdup(argv[i]);
|
|
|
|
|
name = strtok_r(argcopy, ",", &save_ptr);
|
|
|
|
|
if (!name) {
|
|
|
|
|
dpctl_error(dpctl_p, 0, "%s is not a valid network device name",
|
|
|
|
|
argv[i]);
|
|
|
|
|
goto next;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Get the port's type from the datapath. */
|
|
|
|
|
error = dpif_port_query_by_name(dpif, name, &dpif_port);
|
|
|
|
|
if (error) {
|
|
|
|
|
dpctl_error(dpctl_p, error, "%s: failed to query port in %s", name,
|
|
|
|
|
argv[1]);
|
|
|
|
|
goto next;
|
|
|
|
|
}
|
|
|
|
|
type = xstrdup(dpif_port.type);
|
|
|
|
|
port_no = dpif_port.port_no;
|
|
|
|
|
dpif_port_destroy(&dpif_port);
|
|
|
|
|
|
|
|
|
|
/* Retrieve its existing configuration. */
|
|
|
|
|
error = netdev_open(name, type, &netdev);
|
|
|
|
|
if (error) {
|
|
|
|
|
dpctl_error(dpctl_p, error, "%s: failed to open network device",
|
|
|
|
|
name);
|
|
|
|
|
goto next;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
smap_init(&args);
|
|
|
|
|
error = netdev_get_config(netdev, &args);
|
|
|
|
|
if (error) {
|
|
|
|
|
dpctl_error(dpctl_p, error, "%s: failed to fetch configuration",
|
|
|
|
|
name);
|
|
|
|
|
goto next_destroy_args;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Parse changes to configuration. */
|
|
|
|
|
while ((option = strtok_r(NULL, ",", &save_ptr)) != NULL) {
|
|
|
|
|
char *save_ptr_2 = NULL;
|
|
|
|
|
char *key, *value;
|
|
|
|
|
|
|
|
|
|
key = strtok_r(option, "=", &save_ptr_2);
|
|
|
|
|
value = strtok_r(NULL, "", &save_ptr_2);
|
|
|
|
|
if (!value) {
|
|
|
|
|
value = "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!strcmp(key, "type")) {
|
|
|
|
|
if (strcmp(value, type)) {
|
|
|
|
|
dpctl_error(dpctl_p, 0,
|
|
|
|
|
"%s: can't change type from %s to %s",
|
|
|
|
|
name, type, value);
|
|
|
|
|
error = EINVAL;
|
2015-04-15 11:13:04 -07:00
|
|
|
|
goto next_destroy_args;
|
2014-07-17 17:26:00 -07:00
|
|
|
|
}
|
|
|
|
|
} else if (!strcmp(key, "port_no")) {
|
|
|
|
|
if (port_no != u32_to_odp(atoi(value))) {
|
|
|
|
|
dpctl_error(dpctl_p, 0, "%s: can't change port number from"
|
|
|
|
|
" %"PRIu32" to %d", name, port_no, atoi(value));
|
|
|
|
|
error = EINVAL;
|
2015-04-15 11:13:04 -07:00
|
|
|
|
goto next_destroy_args;
|
2014-07-17 17:26:00 -07:00
|
|
|
|
}
|
|
|
|
|
} else if (value[0] == '\0') {
|
|
|
|
|
smap_remove(&args, key);
|
|
|
|
|
} else {
|
|
|
|
|
smap_replace(&args, key, value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Update configuration. */
|
2015-04-15 11:13:04 -07:00
|
|
|
|
char *err_s = NULL;
|
|
|
|
|
error = netdev_set_config(netdev, &args, &err_s);
|
|
|
|
|
if (err_s || error) {
|
2015-05-06 19:00:24 +01:00
|
|
|
|
dpctl_error(dpctl_p, error, "%s",
|
2015-04-15 11:13:04 -07:00
|
|
|
|
err_s ? err_s : "Error updating configuration");
|
|
|
|
|
free(err_s);
|
|
|
|
|
}
|
2014-07-17 17:26:00 -07:00
|
|
|
|
if (error) {
|
|
|
|
|
goto next_destroy_args;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
next_destroy_args:
|
|
|
|
|
smap_destroy(&args);
|
|
|
|
|
next:
|
|
|
|
|
netdev_close(netdev);
|
|
|
|
|
free(type);
|
|
|
|
|
free(argcopy);
|
|
|
|
|
if (error) {
|
|
|
|
|
lasterror = error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
dpif_close(dpif);
|
|
|
|
|
|
|
|
|
|
return lasterror;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
get_port_number(struct dpif *dpif, const char *name, odp_port_t *port,
|
|
|
|
|
struct dpctl_params *dpctl_p)
|
|
|
|
|
{
|
|
|
|
|
struct dpif_port dpif_port;
|
|
|
|
|
|
|
|
|
|
if (!dpif_port_query_by_name(dpif, name, &dpif_port)) {
|
|
|
|
|
*port = dpif_port.port_no;
|
|
|
|
|
dpif_port_destroy(&dpif_port);
|
|
|
|
|
return true;
|
|
|
|
|
} else {
|
|
|
|
|
dpctl_error(dpctl_p, 0, "no port named %s", name);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
dpctl_del_if(int argc, const char *argv[], struct dpctl_params *dpctl_p)
|
|
|
|
|
{
|
|
|
|
|
struct dpif *dpif;
|
|
|
|
|
int i, error, lasterror = 0;
|
|
|
|
|
|
|
|
|
|
error = parsed_dpif_open(argv[1], false, &dpif);
|
|
|
|
|
if (error) {
|
|
|
|
|
dpctl_error(dpctl_p, error, "opening datapath");
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
for (i = 2; i < argc; i++) {
|
|
|
|
|
const char *name = argv[i];
|
|
|
|
|
odp_port_t port;
|
|
|
|
|
|
|
|
|
|
if (!name[strspn(name, "0123456789")]) {
|
|
|
|
|
port = u32_to_odp(atoi(name));
|
|
|
|
|
} else if (!get_port_number(dpif, name, &port, dpctl_p)) {
|
|
|
|
|
lasterror = ENOENT;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-06 12:17:45 -08:00
|
|
|
|
error = dpif_port_del(dpif, port, false);
|
2014-07-17 17:26:00 -07:00
|
|
|
|
if (error) {
|
|
|
|
|
dpctl_error(dpctl_p, error, "deleting port %s from %s failed",
|
|
|
|
|
name, argv[1]);
|
|
|
|
|
lasterror = error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
dpif_close(dpif);
|
|
|
|
|
return lasterror;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
print_stat(struct dpctl_params *dpctl_p, const char *leader, uint64_t value)
|
|
|
|
|
{
|
|
|
|
|
dpctl_print(dpctl_p, "%s", leader);
|
|
|
|
|
if (value != UINT64_MAX) {
|
|
|
|
|
dpctl_print(dpctl_p, "%"PRIu64, value);
|
|
|
|
|
} else {
|
|
|
|
|
dpctl_print(dpctl_p, "?");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
print_human_size(struct dpctl_params *dpctl_p, uint64_t value)
|
|
|
|
|
{
|
|
|
|
|
if (value == UINT64_MAX) {
|
|
|
|
|
/* Nothing to do. */
|
|
|
|
|
} else if (value >= 1024ULL * 1024 * 1024 * 1024) {
|
|
|
|
|
dpctl_print(dpctl_p, " (%.1f TiB)",
|
|
|
|
|
value / (1024.0 * 1024 * 1024 * 1024));
|
|
|
|
|
} else if (value >= 1024ULL * 1024 * 1024) {
|
|
|
|
|
dpctl_print(dpctl_p, " (%.1f GiB)", value / (1024.0 * 1024 * 1024));
|
|
|
|
|
} else if (value >= 1024ULL * 1024) {
|
|
|
|
|
dpctl_print(dpctl_p, " (%.1f MiB)", value / (1024.0 * 1024));
|
|
|
|
|
} else if (value >= 1024) {
|
|
|
|
|
dpctl_print(dpctl_p, " (%.1f KiB)", value / 1024.0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-11 17:28:54 -07:00
|
|
|
|
/* qsort comparison function. */
|
|
|
|
|
static int
|
|
|
|
|
compare_port_nos(const void *a_, const void *b_)
|
|
|
|
|
{
|
|
|
|
|
const odp_port_t *ap = a_;
|
|
|
|
|
const odp_port_t *bp = b_;
|
|
|
|
|
uint32_t a = odp_to_u32(*ap);
|
|
|
|
|
uint32_t b = odp_to_u32(*bp);
|
|
|
|
|
|
|
|
|
|
return a < b ? -1 : a > b;
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-17 17:26:00 -07:00
|
|
|
|
static void
|
|
|
|
|
show_dpif(struct dpif *dpif, struct dpctl_params *dpctl_p)
|
|
|
|
|
{
|
|
|
|
|
struct dpif_port_dump dump;
|
|
|
|
|
struct dpif_port dpif_port;
|
|
|
|
|
struct dpif_dp_stats stats;
|
|
|
|
|
struct netdev *netdev;
|
|
|
|
|
|
|
|
|
|
dpctl_print(dpctl_p, "%s:\n", dpif_name(dpif));
|
|
|
|
|
if (!dpif_get_dp_stats(dpif, &stats)) {
|
2018-05-25 16:55:18 -07:00
|
|
|
|
dpctl_print(dpctl_p, " lookups: hit:%"PRIu64" missed:%"PRIu64
|
|
|
|
|
" lost:%"PRIu64"\n flows: %"PRIu64"\n",
|
2014-07-17 17:26:00 -07:00
|
|
|
|
stats.n_hit, stats.n_missed, stats.n_lost, stats.n_flows);
|
|
|
|
|
if (stats.n_masks != UINT32_MAX) {
|
|
|
|
|
uint64_t n_pkts = stats.n_hit + stats.n_missed;
|
|
|
|
|
double avg = n_pkts ? (double) stats.n_mask_hit / n_pkts : 0.0;
|
|
|
|
|
|
2018-05-25 16:55:18 -07:00
|
|
|
|
dpctl_print(dpctl_p, " masks: hit:%"PRIu64" total:%"PRIu32
|
2014-07-17 17:26:00 -07:00
|
|
|
|
" hit/pkt:%.2f\n",
|
|
|
|
|
stats.n_mask_hit, stats.n_masks, avg);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-11 17:28:54 -07:00
|
|
|
|
odp_port_t *port_nos = NULL;
|
|
|
|
|
size_t allocated_port_nos = 0, n_port_nos = 0;
|
2014-07-17 17:26:00 -07:00
|
|
|
|
DPIF_PORT_FOR_EACH (&dpif_port, &dump, dpif) {
|
2016-05-11 17:28:54 -07:00
|
|
|
|
if (n_port_nos >= allocated_port_nos) {
|
|
|
|
|
port_nos = x2nrealloc(port_nos, &allocated_port_nos,
|
|
|
|
|
sizeof *port_nos);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
port_nos[n_port_nos] = dpif_port.port_no;
|
|
|
|
|
n_port_nos++;
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-19 19:54:01 +01:00
|
|
|
|
if (port_nos) {
|
|
|
|
|
qsort(port_nos, n_port_nos, sizeof *port_nos, compare_port_nos);
|
|
|
|
|
}
|
2016-05-11 17:28:54 -07:00
|
|
|
|
|
|
|
|
|
for (int i = 0; i < n_port_nos; i++) {
|
|
|
|
|
if (dpif_port_query_by_number(dpif, port_nos[i], &dpif_port)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-25 16:55:18 -07:00
|
|
|
|
dpctl_print(dpctl_p, " port %u: %s",
|
2014-07-17 17:26:00 -07:00
|
|
|
|
dpif_port.port_no, dpif_port.name);
|
|
|
|
|
|
|
|
|
|
if (strcmp(dpif_port.type, "system")) {
|
|
|
|
|
int error;
|
|
|
|
|
|
|
|
|
|
dpctl_print(dpctl_p, " (%s", dpif_port.type);
|
|
|
|
|
|
|
|
|
|
error = netdev_open(dpif_port.name, dpif_port.type, &netdev);
|
|
|
|
|
if (!error) {
|
|
|
|
|
struct smap config;
|
|
|
|
|
|
|
|
|
|
smap_init(&config);
|
|
|
|
|
error = netdev_get_config(netdev, &config);
|
|
|
|
|
if (!error) {
|
2016-07-12 12:46:20 -07:00
|
|
|
|
const struct smap_node **nodes = smap_sort(&config);
|
|
|
|
|
for (size_t j = 0; j < smap_count(&config); j++) {
|
|
|
|
|
const struct smap_node *node = nodes[j];
|
|
|
|
|
dpctl_print(dpctl_p, "%c %s=%s", j ? ',' : ':',
|
2014-07-17 17:26:00 -07:00
|
|
|
|
node->key, node->value);
|
|
|
|
|
}
|
|
|
|
|
free(nodes);
|
|
|
|
|
} else {
|
|
|
|
|
dpctl_print(dpctl_p, ", could not retrieve configuration "
|
|
|
|
|
"(%s)", ovs_strerror(error));
|
|
|
|
|
}
|
|
|
|
|
smap_destroy(&config);
|
|
|
|
|
|
|
|
|
|
netdev_close(netdev);
|
|
|
|
|
} else {
|
|
|
|
|
dpctl_print(dpctl_p, ": open failed (%s)",
|
|
|
|
|
ovs_strerror(error));
|
|
|
|
|
}
|
|
|
|
|
dpctl_print(dpctl_p, ")");
|
|
|
|
|
}
|
|
|
|
|
dpctl_print(dpctl_p, "\n");
|
|
|
|
|
|
|
|
|
|
if (dpctl_p->print_statistics) {
|
|
|
|
|
struct netdev_stats s;
|
|
|
|
|
int error;
|
|
|
|
|
|
|
|
|
|
error = netdev_open(dpif_port.name, dpif_port.type, &netdev);
|
|
|
|
|
if (error) {
|
|
|
|
|
dpctl_print(dpctl_p, ", open failed (%s)",
|
|
|
|
|
ovs_strerror(error));
|
2016-05-11 17:28:54 -07:00
|
|
|
|
dpif_port_destroy(&dpif_port);
|
2014-07-17 17:26:00 -07:00
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
error = netdev_get_stats(netdev, &s);
|
|
|
|
|
if (!error) {
|
|
|
|
|
netdev_close(netdev);
|
2018-05-25 16:55:18 -07:00
|
|
|
|
print_stat(dpctl_p, " RX packets:", s.rx_packets);
|
2014-07-17 17:26:00 -07:00
|
|
|
|
print_stat(dpctl_p, " errors:", s.rx_errors);
|
|
|
|
|
print_stat(dpctl_p, " dropped:", s.rx_dropped);
|
|
|
|
|
print_stat(dpctl_p, " overruns:", s.rx_over_errors);
|
|
|
|
|
print_stat(dpctl_p, " frame:", s.rx_frame_errors);
|
|
|
|
|
dpctl_print(dpctl_p, "\n");
|
|
|
|
|
|
2018-05-25 16:55:18 -07:00
|
|
|
|
print_stat(dpctl_p, " TX packets:", s.tx_packets);
|
2014-07-17 17:26:00 -07:00
|
|
|
|
print_stat(dpctl_p, " errors:", s.tx_errors);
|
|
|
|
|
print_stat(dpctl_p, " dropped:", s.tx_dropped);
|
|
|
|
|
print_stat(dpctl_p, " aborted:", s.tx_aborted_errors);
|
|
|
|
|
print_stat(dpctl_p, " carrier:", s.tx_carrier_errors);
|
|
|
|
|
dpctl_print(dpctl_p, "\n");
|
|
|
|
|
|
2018-05-25 16:55:18 -07:00
|
|
|
|
print_stat(dpctl_p, " collisions:", s.collisions);
|
2014-07-17 17:26:00 -07:00
|
|
|
|
dpctl_print(dpctl_p, "\n");
|
|
|
|
|
|
2018-05-25 16:55:18 -07:00
|
|
|
|
print_stat(dpctl_p, " RX bytes:", s.rx_bytes);
|
2014-07-17 17:26:00 -07:00
|
|
|
|
print_human_size(dpctl_p, s.rx_bytes);
|
|
|
|
|
print_stat(dpctl_p, " TX bytes:", s.tx_bytes);
|
|
|
|
|
print_human_size(dpctl_p, s.tx_bytes);
|
|
|
|
|
dpctl_print(dpctl_p, "\n");
|
|
|
|
|
} else {
|
|
|
|
|
dpctl_print(dpctl_p, ", could not retrieve stats (%s)",
|
|
|
|
|
ovs_strerror(error));
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-05-11 17:28:54 -07:00
|
|
|
|
dpif_port_destroy(&dpif_port);
|
2014-07-17 17:26:00 -07:00
|
|
|
|
}
|
2016-05-11 17:28:54 -07:00
|
|
|
|
|
|
|
|
|
free(port_nos);
|
2015-05-06 19:00:25 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
typedef void (*dps_for_each_cb)(struct dpif *, struct dpctl_params *);
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
dps_for_each(struct dpctl_params *dpctl_p, dps_for_each_cb cb)
|
|
|
|
|
{
|
|
|
|
|
struct sset dpif_names = SSET_INITIALIZER(&dpif_names),
|
|
|
|
|
dpif_types = SSET_INITIALIZER(&dpif_types);
|
2015-05-06 19:00:26 +01:00
|
|
|
|
int error, openerror = 0, enumerror = 0;
|
2015-05-06 19:00:25 +01:00
|
|
|
|
const char *type, *name;
|
2015-05-06 19:00:26 +01:00
|
|
|
|
bool at_least_one = false;
|
2015-05-06 19:00:25 +01:00
|
|
|
|
|
|
|
|
|
dp_enumerate_types(&dpif_types);
|
|
|
|
|
|
|
|
|
|
SSET_FOR_EACH (type, &dpif_types) {
|
|
|
|
|
error = dp_enumerate_names(type, &dpif_names);
|
|
|
|
|
if (error) {
|
2015-05-06 19:00:26 +01:00
|
|
|
|
enumerror = error;
|
2015-05-06 19:00:25 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SSET_FOR_EACH (name, &dpif_names) {
|
|
|
|
|
struct dpif *dpif;
|
|
|
|
|
|
2015-05-06 19:00:26 +01:00
|
|
|
|
at_least_one = true;
|
2015-05-06 19:00:25 +01:00
|
|
|
|
error = dpif_open(name, type, &dpif);
|
|
|
|
|
if (!error) {
|
|
|
|
|
cb(dpif, dpctl_p);
|
|
|
|
|
dpif_close(dpif);
|
|
|
|
|
} else {
|
2015-05-06 19:00:26 +01:00
|
|
|
|
openerror = error;
|
2015-05-06 19:00:25 +01:00
|
|
|
|
dpctl_error(dpctl_p, error, "opening datapath %s failed",
|
|
|
|
|
name);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sset_destroy(&dpif_names);
|
|
|
|
|
sset_destroy(&dpif_types);
|
|
|
|
|
|
2015-05-06 19:00:26 +01:00
|
|
|
|
/* If there has been an error while opening a datapath it should be
|
|
|
|
|
* reported. Otherwise, we want to ignore the errors generated by
|
|
|
|
|
* dp_enumerate_names() if at least one datapath has been discovered,
|
|
|
|
|
* because they're not interesting for the user. This happens, for
|
|
|
|
|
* example, if OVS is using a userspace datapath and the kernel module
|
|
|
|
|
* is not loaded. */
|
|
|
|
|
if (openerror) {
|
|
|
|
|
return openerror;
|
|
|
|
|
} else {
|
|
|
|
|
return at_least_one ? 0 : enumerror;
|
|
|
|
|
}
|
2014-07-17 17:26:00 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
dpctl_show(int argc, const char *argv[], struct dpctl_params *dpctl_p)
|
|
|
|
|
{
|
|
|
|
|
int error, lasterror = 0;
|
|
|
|
|
if (argc > 1) {
|
|
|
|
|
int i;
|
|
|
|
|
for (i = 1; i < argc; i++) {
|
|
|
|
|
const char *name = argv[i];
|
|
|
|
|
struct dpif *dpif;
|
|
|
|
|
|
|
|
|
|
error = parsed_dpif_open(name, false, &dpif);
|
|
|
|
|
if (!error) {
|
|
|
|
|
show_dpif(dpif, dpctl_p);
|
2015-05-06 19:00:25 +01:00
|
|
|
|
dpif_close(dpif);
|
2014-07-17 17:26:00 -07:00
|
|
|
|
} else {
|
|
|
|
|
dpctl_error(dpctl_p, error, "opening datapath %s failed",
|
|
|
|
|
name);
|
|
|
|
|
lasterror = error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2015-05-06 19:00:25 +01:00
|
|
|
|
lasterror = dps_for_each(dpctl_p, show_dpif);
|
2014-07-17 17:26:00 -07:00
|
|
|
|
}
|
2015-05-06 19:00:25 +01:00
|
|
|
|
|
2014-07-17 17:26:00 -07:00
|
|
|
|
return lasterror;
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-06 19:00:25 +01:00
|
|
|
|
static void
|
|
|
|
|
dump_cb(struct dpif *dpif, struct dpctl_params *dpctl_p)
|
|
|
|
|
{
|
|
|
|
|
dpctl_print(dpctl_p, "%s\n", dpif_name(dpif));
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-17 17:26:00 -07:00
|
|
|
|
static int
|
|
|
|
|
dpctl_dump_dps(int argc OVS_UNUSED, const char *argv[] OVS_UNUSED,
|
|
|
|
|
struct dpctl_params *dpctl_p)
|
|
|
|
|
{
|
2015-05-06 19:00:25 +01:00
|
|
|
|
return dps_for_each(dpctl_p, dump_cb);
|
2014-07-17 17:26:00 -07:00
|
|
|
|
}
|
|
|
|
|
|
2014-12-05 14:45:51 -08:00
|
|
|
|
static void
|
|
|
|
|
format_dpif_flow(struct ds *ds, const struct dpif_flow *f, struct hmap *ports,
|
dpctl: Properly reflect a rule's offloaded to HW state
Previously, any rule that is offloaded via a netdev, not necessarily
to the HW, would be reported as "offloaded". This patch fixes this
misalignment, and introduces the 'dp' state, as follows:
rule is in HW via TC offload -> offloaded=yes dp:tc
rule is in not HW over TC DP -> offloaded=no dp:tc
rule is in not HW over OVS DP -> offloaded=no dp:ovs
To achieve this, the flows's 'offloaded' flag was encapsulated in a new
attrs struct, which contains the offloaded state of the flow and the
DP layer the flow is handled in, and instead of setting the flow's
'offloaded' state based solely on the type of dump it was acquired
via, for netdev flows it now sends the new attrs struct to be
collected along with the rest of the flow via the netdev, allowing
it to be set per flow.
For TC offloads, the offloaded state is set based on the 'in_hw' and
'not_in_hw' flags received from the TC as part of the flower. If no
such flag was received, due to lack of kernel support, it defaults
to true.
Signed-off-by: Gavi Teitz <gavi@mellanox.com>
Acked-by: Roi Dayan <roid@mellanox.com>
[simon: resolved conflict in lib/dpctl.man]
Signed-off-by: Simon Horman <simon.horman@netronome.com>
2018-06-07 09:36:59 +03:00
|
|
|
|
struct dpctl_params *dpctl_p)
|
2014-12-05 14:45:51 -08:00
|
|
|
|
{
|
2015-05-22 10:24:34 -07:00
|
|
|
|
if (dpctl_p->verbosity && f->ufid_present) {
|
|
|
|
|
odp_format_ufid(&f->ufid, ds);
|
|
|
|
|
ds_put_cstr(ds, ", ");
|
2014-12-05 14:45:51 -08:00
|
|
|
|
}
|
|
|
|
|
odp_flow_format(f->key, f->key_len, f->mask, f->mask_len, ports, ds,
|
|
|
|
|
dpctl_p->verbosity);
|
|
|
|
|
ds_put_cstr(ds, ", ");
|
|
|
|
|
|
|
|
|
|
dpif_flow_stats_format(&f->stats, ds);
|
dpctl: Properly reflect a rule's offloaded to HW state
Previously, any rule that is offloaded via a netdev, not necessarily
to the HW, would be reported as "offloaded". This patch fixes this
misalignment, and introduces the 'dp' state, as follows:
rule is in HW via TC offload -> offloaded=yes dp:tc
rule is in not HW over TC DP -> offloaded=no dp:tc
rule is in not HW over OVS DP -> offloaded=no dp:ovs
To achieve this, the flows's 'offloaded' flag was encapsulated in a new
attrs struct, which contains the offloaded state of the flow and the
DP layer the flow is handled in, and instead of setting the flow's
'offloaded' state based solely on the type of dump it was acquired
via, for netdev flows it now sends the new attrs struct to be
collected along with the rest of the flow via the netdev, allowing
it to be set per flow.
For TC offloads, the offloaded state is set based on the 'in_hw' and
'not_in_hw' flags received from the TC as part of the flower. If no
such flag was received, due to lack of kernel support, it defaults
to true.
Signed-off-by: Gavi Teitz <gavi@mellanox.com>
Acked-by: Roi Dayan <roid@mellanox.com>
[simon: resolved conflict in lib/dpctl.man]
Signed-off-by: Simon Horman <simon.horman@netronome.com>
2018-06-07 09:36:59 +03:00
|
|
|
|
if (dpctl_p->verbosity && f->attrs.offloaded) {
|
2017-06-13 18:03:50 +03:00
|
|
|
|
ds_put_cstr(ds, ", offloaded:yes");
|
|
|
|
|
}
|
dpctl: Properly reflect a rule's offloaded to HW state
Previously, any rule that is offloaded via a netdev, not necessarily
to the HW, would be reported as "offloaded". This patch fixes this
misalignment, and introduces the 'dp' state, as follows:
rule is in HW via TC offload -> offloaded=yes dp:tc
rule is in not HW over TC DP -> offloaded=no dp:tc
rule is in not HW over OVS DP -> offloaded=no dp:ovs
To achieve this, the flows's 'offloaded' flag was encapsulated in a new
attrs struct, which contains the offloaded state of the flow and the
DP layer the flow is handled in, and instead of setting the flow's
'offloaded' state based solely on the type of dump it was acquired
via, for netdev flows it now sends the new attrs struct to be
collected along with the rest of the flow via the netdev, allowing
it to be set per flow.
For TC offloads, the offloaded state is set based on the 'in_hw' and
'not_in_hw' flags received from the TC as part of the flower. If no
such flag was received, due to lack of kernel support, it defaults
to true.
Signed-off-by: Gavi Teitz <gavi@mellanox.com>
Acked-by: Roi Dayan <roid@mellanox.com>
[simon: resolved conflict in lib/dpctl.man]
Signed-off-by: Simon Horman <simon.horman@netronome.com>
2018-06-07 09:36:59 +03:00
|
|
|
|
if (dpctl_p->verbosity && f->attrs.dp_layer) {
|
|
|
|
|
ds_put_format(ds, ", dp:%s", f->attrs.dp_layer);
|
|
|
|
|
}
|
2014-12-05 14:45:51 -08:00
|
|
|
|
ds_put_cstr(ds, ", actions:");
|
2017-06-18 09:51:57 +08:00
|
|
|
|
format_odp_actions(ds, f->actions, f->actions_len, ports);
|
2014-12-05 14:45:51 -08:00
|
|
|
|
}
|
|
|
|
|
|
2018-07-25 13:52:42 -07:00
|
|
|
|
static char *supported_dump_types[] = {
|
|
|
|
|
"offloaded",
|
|
|
|
|
"ovs",
|
2017-06-13 18:03:49 +03:00
|
|
|
|
};
|
|
|
|
|
|
dpctl: Properly reflect a rule's offloaded to HW state
Previously, any rule that is offloaded via a netdev, not necessarily
to the HW, would be reported as "offloaded". This patch fixes this
misalignment, and introduces the 'dp' state, as follows:
rule is in HW via TC offload -> offloaded=yes dp:tc
rule is in not HW over TC DP -> offloaded=no dp:tc
rule is in not HW over OVS DP -> offloaded=no dp:ovs
To achieve this, the flows's 'offloaded' flag was encapsulated in a new
attrs struct, which contains the offloaded state of the flow and the
DP layer the flow is handled in, and instead of setting the flow's
'offloaded' state based solely on the type of dump it was acquired
via, for netdev flows it now sends the new attrs struct to be
collected along with the rest of the flow via the netdev, allowing
it to be set per flow.
For TC offloads, the offloaded state is set based on the 'in_hw' and
'not_in_hw' flags received from the TC as part of the flower. If no
such flag was received, due to lack of kernel support, it defaults
to true.
Signed-off-by: Gavi Teitz <gavi@mellanox.com>
Acked-by: Roi Dayan <roid@mellanox.com>
[simon: resolved conflict in lib/dpctl.man]
Signed-off-by: Simon Horman <simon.horman@netronome.com>
2018-06-07 09:36:59 +03:00
|
|
|
|
static bool
|
2018-07-25 13:52:42 -07:00
|
|
|
|
flow_passes_type_filter(const struct dpif_flow *f, char *type)
|
dpctl: Properly reflect a rule's offloaded to HW state
Previously, any rule that is offloaded via a netdev, not necessarily
to the HW, would be reported as "offloaded". This patch fixes this
misalignment, and introduces the 'dp' state, as follows:
rule is in HW via TC offload -> offloaded=yes dp:tc
rule is in not HW over TC DP -> offloaded=no dp:tc
rule is in not HW over OVS DP -> offloaded=no dp:ovs
To achieve this, the flows's 'offloaded' flag was encapsulated in a new
attrs struct, which contains the offloaded state of the flow and the
DP layer the flow is handled in, and instead of setting the flow's
'offloaded' state based solely on the type of dump it was acquired
via, for netdev flows it now sends the new attrs struct to be
collected along with the rest of the flow via the netdev, allowing
it to be set per flow.
For TC offloads, the offloaded state is set based on the 'in_hw' and
'not_in_hw' flags received from the TC as part of the flower. If no
such flag was received, due to lack of kernel support, it defaults
to true.
Signed-off-by: Gavi Teitz <gavi@mellanox.com>
Acked-by: Roi Dayan <roid@mellanox.com>
[simon: resolved conflict in lib/dpctl.man]
Signed-off-by: Simon Horman <simon.horman@netronome.com>
2018-06-07 09:36:59 +03:00
|
|
|
|
{
|
2018-07-25 13:52:42 -07:00
|
|
|
|
if (!strcmp(type, "offloaded")) {
|
|
|
|
|
return f->attrs.offloaded;
|
dpctl: Properly reflect a rule's offloaded to HW state
Previously, any rule that is offloaded via a netdev, not necessarily
to the HW, would be reported as "offloaded". This patch fixes this
misalignment, and introduces the 'dp' state, as follows:
rule is in HW via TC offload -> offloaded=yes dp:tc
rule is in not HW over TC DP -> offloaded=no dp:tc
rule is in not HW over OVS DP -> offloaded=no dp:ovs
To achieve this, the flows's 'offloaded' flag was encapsulated in a new
attrs struct, which contains the offloaded state of the flow and the
DP layer the flow is handled in, and instead of setting the flow's
'offloaded' state based solely on the type of dump it was acquired
via, for netdev flows it now sends the new attrs struct to be
collected along with the rest of the flow via the netdev, allowing
it to be set per flow.
For TC offloads, the offloaded state is set based on the 'in_hw' and
'not_in_hw' flags received from the TC as part of the flower. If no
such flag was received, due to lack of kernel support, it defaults
to true.
Signed-off-by: Gavi Teitz <gavi@mellanox.com>
Acked-by: Roi Dayan <roid@mellanox.com>
[simon: resolved conflict in lib/dpctl.man]
Signed-off-by: Simon Horman <simon.horman@netronome.com>
2018-06-07 09:36:59 +03:00
|
|
|
|
}
|
2018-07-25 13:52:42 -07:00
|
|
|
|
return true;
|
dpctl: Properly reflect a rule's offloaded to HW state
Previously, any rule that is offloaded via a netdev, not necessarily
to the HW, would be reported as "offloaded". This patch fixes this
misalignment, and introduces the 'dp' state, as follows:
rule is in HW via TC offload -> offloaded=yes dp:tc
rule is in not HW over TC DP -> offloaded=no dp:tc
rule is in not HW over OVS DP -> offloaded=no dp:ovs
To achieve this, the flows's 'offloaded' flag was encapsulated in a new
attrs struct, which contains the offloaded state of the flow and the
DP layer the flow is handled in, and instead of setting the flow's
'offloaded' state based solely on the type of dump it was acquired
via, for netdev flows it now sends the new attrs struct to be
collected along with the rest of the flow via the netdev, allowing
it to be set per flow.
For TC offloads, the offloaded state is set based on the 'in_hw' and
'not_in_hw' flags received from the TC as part of the flower. If no
such flag was received, due to lack of kernel support, it defaults
to true.
Signed-off-by: Gavi Teitz <gavi@mellanox.com>
Acked-by: Roi Dayan <roid@mellanox.com>
[simon: resolved conflict in lib/dpctl.man]
Signed-off-by: Simon Horman <simon.horman@netronome.com>
2018-06-07 09:36:59 +03:00
|
|
|
|
}
|
|
|
|
|
|
2017-06-18 09:46:54 +08:00
|
|
|
|
static struct hmap *
|
|
|
|
|
dpctl_get_portno_names(struct dpif *dpif, const struct dpctl_params *dpctl_p)
|
|
|
|
|
{
|
|
|
|
|
if (dpctl_p->names) {
|
|
|
|
|
struct hmap *portno_names = xmalloc(sizeof *portno_names);
|
|
|
|
|
hmap_init(portno_names);
|
|
|
|
|
|
|
|
|
|
struct dpif_port_dump port_dump;
|
|
|
|
|
struct dpif_port dpif_port;
|
|
|
|
|
DPIF_PORT_FOR_EACH (&dpif_port, &port_dump, dpif) {
|
|
|
|
|
odp_portno_names_set(portno_names, dpif_port.port_no,
|
|
|
|
|
dpif_port.name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return portno_names;
|
|
|
|
|
} else {
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
dpctl_free_portno_names(struct hmap *portno_names)
|
|
|
|
|
{
|
|
|
|
|
if (portno_names) {
|
|
|
|
|
odp_portno_names_destroy(portno_names);
|
|
|
|
|
hmap_destroy(portno_names);
|
|
|
|
|
free(portno_names);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-17 17:26:00 -07:00
|
|
|
|
static int
|
|
|
|
|
dpctl_dump_flows(int argc, const char *argv[], struct dpctl_params *dpctl_p)
|
|
|
|
|
{
|
|
|
|
|
struct dpif *dpif;
|
|
|
|
|
struct ds ds;
|
|
|
|
|
|
|
|
|
|
char *filter = NULL;
|
2018-07-25 13:52:42 -07:00
|
|
|
|
char *type = NULL;
|
2014-07-17 17:26:00 -07:00
|
|
|
|
struct flow flow_filter;
|
|
|
|
|
struct flow_wildcards wc_filter;
|
|
|
|
|
|
|
|
|
|
struct dpif_flow_dump_thread *flow_dump_thread;
|
|
|
|
|
struct dpif_flow_dump *flow_dump;
|
|
|
|
|
struct dpif_flow f;
|
2014-10-12 18:18:47 -07:00
|
|
|
|
int pmd_id = PMD_ID_NULL;
|
2017-06-13 18:03:49 +03:00
|
|
|
|
int lastargc = 0;
|
2014-07-17 17:26:00 -07:00
|
|
|
|
int error;
|
|
|
|
|
|
2017-06-13 18:03:49 +03:00
|
|
|
|
while (argc > 1 && lastargc != argc) {
|
|
|
|
|
lastargc = argc;
|
|
|
|
|
if (!strncmp(argv[argc - 1], "filter=", 7) && !filter) {
|
|
|
|
|
filter = xstrdup(argv[--argc] + 7);
|
2018-07-25 13:52:42 -07:00
|
|
|
|
} else if (!strncmp(argv[argc - 1], "type=", 5) && !type) {
|
|
|
|
|
type = xstrdup(argv[--argc] + 5);
|
2017-06-13 18:03:49 +03:00
|
|
|
|
}
|
2014-07-17 17:26:00 -07:00
|
|
|
|
}
|
2017-06-13 18:03:49 +03:00
|
|
|
|
|
2018-06-07 16:20:59 -07:00
|
|
|
|
error = opt_dpif_open(argc, argv, dpctl_p, 2, &dpif);
|
2014-07-17 17:26:00 -07:00
|
|
|
|
if (error) {
|
2017-06-13 18:03:49 +03:00
|
|
|
|
goto out_free;
|
2014-07-17 17:26:00 -07:00
|
|
|
|
}
|
|
|
|
|
|
2017-06-18 09:46:54 +08:00
|
|
|
|
struct hmap *portno_names = dpctl_get_portno_names(dpif, dpctl_p);
|
2014-07-17 17:26:00 -07:00
|
|
|
|
|
|
|
|
|
if (filter) {
|
2017-05-31 16:06:12 -07:00
|
|
|
|
struct ofputil_port_map port_map;
|
|
|
|
|
ofputil_port_map_init(&port_map);
|
2017-06-18 09:46:54 +08:00
|
|
|
|
|
|
|
|
|
struct dpif_port_dump port_dump;
|
|
|
|
|
struct dpif_port dpif_port;
|
2017-05-31 16:06:12 -07:00
|
|
|
|
DPIF_PORT_FOR_EACH (&dpif_port, &port_dump, dpif) {
|
|
|
|
|
ofputil_port_map_put(&port_map,
|
|
|
|
|
u16_to_ofp(odp_to_u32(dpif_port.port_no)),
|
|
|
|
|
dpif_port.name);
|
|
|
|
|
}
|
|
|
|
|
char *err = parse_ofp_exact_flow(&flow_filter, &wc_filter, NULL,
|
|
|
|
|
filter, &port_map);
|
|
|
|
|
ofputil_port_map_destroy(&port_map);
|
2014-07-17 17:26:00 -07:00
|
|
|
|
if (err) {
|
|
|
|
|
dpctl_error(dpctl_p, 0, "Failed to parse filter (%s)", err);
|
2017-05-30 07:38:18 -07:00
|
|
|
|
free(err);
|
2014-07-17 17:26:00 -07:00
|
|
|
|
error = EINVAL;
|
|
|
|
|
goto out_dpifclose;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-25 13:52:42 -07:00
|
|
|
|
if (type) {
|
|
|
|
|
error = EINVAL;
|
|
|
|
|
for (int i = 0; i < ARRAY_SIZE(supported_dump_types); i++) {
|
|
|
|
|
if (!strcmp(supported_dump_types[i], type)) {
|
|
|
|
|
error = 0;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (error) {
|
|
|
|
|
dpctl_error(dpctl_p, error, "Failed to parse type (%s)", type);
|
|
|
|
|
goto out_free;
|
|
|
|
|
}
|
2017-06-13 18:03:49 +03:00
|
|
|
|
}
|
|
|
|
|
|
2015-05-22 17:14:20 +01:00
|
|
|
|
/* Make sure that these values are different. PMD_ID_NULL means that the
|
|
|
|
|
* pmd is unspecified (e.g. because the datapath doesn't have different
|
|
|
|
|
* pmd threads), while NON_PMD_CORE_ID refers to every non pmd threads
|
|
|
|
|
* in the userspace datapath */
|
|
|
|
|
BUILD_ASSERT(PMD_ID_NULL != NON_PMD_CORE_ID);
|
|
|
|
|
|
2014-07-17 17:26:00 -07:00
|
|
|
|
ds_init(&ds);
|
2017-06-13 18:03:50 +03:00
|
|
|
|
memset(&f, 0, sizeof f);
|
2018-07-25 13:52:42 -07:00
|
|
|
|
flow_dump = dpif_flow_dump_create(dpif, false, (type ? type : "dpctl"));
|
2014-07-17 17:26:00 -07:00
|
|
|
|
flow_dump_thread = dpif_flow_dump_thread_create(flow_dump);
|
|
|
|
|
while (dpif_flow_dump_next(flow_dump_thread, &f, 1)) {
|
|
|
|
|
if (filter) {
|
|
|
|
|
struct flow flow;
|
|
|
|
|
struct flow_wildcards wc;
|
|
|
|
|
struct match match, match_filter;
|
|
|
|
|
struct minimatch minimatch;
|
|
|
|
|
|
|
|
|
|
odp_flow_key_to_flow(f.key, f.key_len, &flow);
|
2016-04-19 18:36:04 -07:00
|
|
|
|
odp_flow_key_to_mask(f.mask, f.mask_len, &wc, &flow);
|
2014-07-17 17:26:00 -07:00
|
|
|
|
match_init(&match, &flow, &wc);
|
|
|
|
|
|
|
|
|
|
match_init(&match_filter, &flow_filter, &wc);
|
|
|
|
|
match_init(&match_filter, &match_filter.flow, &wc_filter);
|
|
|
|
|
minimatch_init(&minimatch, &match_filter);
|
|
|
|
|
|
|
|
|
|
if (!minimatch_matches_flow(&minimatch, &match.flow)) {
|
|
|
|
|
minimatch_destroy(&minimatch);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
minimatch_destroy(&minimatch);
|
|
|
|
|
}
|
|
|
|
|
ds_clear(&ds);
|
2014-10-12 18:18:47 -07:00
|
|
|
|
/* If 'pmd_id' is specified, overlapping flows could be dumped from
|
|
|
|
|
* different pmd threads. So, separates dumps from different pmds
|
|
|
|
|
* by printing a title line. */
|
|
|
|
|
if (pmd_id != f.pmd_id) {
|
|
|
|
|
if (f.pmd_id == NON_PMD_CORE_ID) {
|
|
|
|
|
ds_put_format(&ds, "flow-dump from non-dpdk interfaces:\n");
|
|
|
|
|
} else {
|
|
|
|
|
ds_put_format(&ds, "flow-dump from pmd on cpu core: %d\n",
|
|
|
|
|
f.pmd_id);
|
|
|
|
|
}
|
|
|
|
|
pmd_id = f.pmd_id;
|
|
|
|
|
}
|
2018-07-25 13:52:42 -07:00
|
|
|
|
if (!type || flow_passes_type_filter(&f, type)) {
|
dpctl: Properly reflect a rule's offloaded to HW state
Previously, any rule that is offloaded via a netdev, not necessarily
to the HW, would be reported as "offloaded". This patch fixes this
misalignment, and introduces the 'dp' state, as follows:
rule is in HW via TC offload -> offloaded=yes dp:tc
rule is in not HW over TC DP -> offloaded=no dp:tc
rule is in not HW over OVS DP -> offloaded=no dp:ovs
To achieve this, the flows's 'offloaded' flag was encapsulated in a new
attrs struct, which contains the offloaded state of the flow and the
DP layer the flow is handled in, and instead of setting the flow's
'offloaded' state based solely on the type of dump it was acquired
via, for netdev flows it now sends the new attrs struct to be
collected along with the rest of the flow via the netdev, allowing
it to be set per flow.
For TC offloads, the offloaded state is set based on the 'in_hw' and
'not_in_hw' flags received from the TC as part of the flower. If no
such flag was received, due to lack of kernel support, it defaults
to true.
Signed-off-by: Gavi Teitz <gavi@mellanox.com>
Acked-by: Roi Dayan <roid@mellanox.com>
[simon: resolved conflict in lib/dpctl.man]
Signed-off-by: Simon Horman <simon.horman@netronome.com>
2018-06-07 09:36:59 +03:00
|
|
|
|
format_dpif_flow(&ds, &f, portno_names, dpctl_p);
|
|
|
|
|
dpctl_print(dpctl_p, "%s\n", ds_cstr(&ds));
|
|
|
|
|
}
|
2014-07-17 17:26:00 -07:00
|
|
|
|
}
|
|
|
|
|
dpif_flow_dump_thread_destroy(flow_dump_thread);
|
|
|
|
|
error = dpif_flow_dump_destroy(flow_dump);
|
|
|
|
|
|
|
|
|
|
if (error) {
|
|
|
|
|
dpctl_error(dpctl_p, error, "Failed to dump flows from datapath");
|
|
|
|
|
}
|
|
|
|
|
ds_destroy(&ds);
|
|
|
|
|
|
|
|
|
|
out_dpifclose:
|
2017-06-18 09:46:54 +08:00
|
|
|
|
dpctl_free_portno_names(portno_names);
|
2014-07-17 17:26:00 -07:00
|
|
|
|
dpif_close(dpif);
|
2017-06-13 18:03:49 +03:00
|
|
|
|
out_free:
|
2014-07-17 17:26:00 -07:00
|
|
|
|
free(filter);
|
2018-07-25 13:52:42 -07:00
|
|
|
|
free(type);
|
2014-07-17 17:26:00 -07:00
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
dpctl_put_flow(int argc, const char *argv[], enum dpif_flow_put_flags flags,
|
|
|
|
|
struct dpctl_params *dpctl_p)
|
|
|
|
|
{
|
|
|
|
|
const char *key_s = argv[argc - 2];
|
|
|
|
|
const char *actions_s = argv[argc - 1];
|
|
|
|
|
struct dpif_flow_stats stats;
|
|
|
|
|
struct dpif_port dpif_port;
|
|
|
|
|
struct dpif_port_dump port_dump;
|
|
|
|
|
struct ofpbuf actions;
|
|
|
|
|
struct ofpbuf key;
|
|
|
|
|
struct ofpbuf mask;
|
|
|
|
|
struct dpif *dpif;
|
2014-11-12 09:49:22 -08:00
|
|
|
|
ovs_u128 ufid;
|
|
|
|
|
bool ufid_present;
|
2014-07-17 17:26:00 -07:00
|
|
|
|
struct simap port_names;
|
2014-11-12 09:49:22 -08:00
|
|
|
|
int n, error;
|
2014-07-17 17:26:00 -07:00
|
|
|
|
|
2018-06-07 16:20:59 -07:00
|
|
|
|
error = opt_dpif_open(argc, argv, dpctl_p, 4, &dpif);
|
2014-07-17 17:26:00 -07:00
|
|
|
|
if (error) {
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
2014-11-12 09:49:22 -08:00
|
|
|
|
ufid_present = false;
|
|
|
|
|
n = odp_ufid_from_string(key_s, &ufid);
|
|
|
|
|
if (n < 0) {
|
|
|
|
|
dpctl_error(dpctl_p, -n, "parsing flow ufid");
|
|
|
|
|
return -n;
|
|
|
|
|
} else if (n) {
|
|
|
|
|
key_s += n;
|
|
|
|
|
ufid_present = true;
|
|
|
|
|
}
|
2014-07-17 17:26:00 -07:00
|
|
|
|
|
|
|
|
|
simap_init(&port_names);
|
|
|
|
|
DPIF_PORT_FOR_EACH (&dpif_port, &port_dump, dpif) {
|
|
|
|
|
simap_put(&port_names, dpif_port.name, odp_to_u32(dpif_port.port_no));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ofpbuf_init(&key, 0);
|
|
|
|
|
ofpbuf_init(&mask, 0);
|
|
|
|
|
error = odp_flow_from_string(key_s, &port_names, &key, &mask);
|
|
|
|
|
simap_destroy(&port_names);
|
|
|
|
|
if (error) {
|
|
|
|
|
dpctl_error(dpctl_p, error, "parsing flow key");
|
|
|
|
|
goto out_freekeymask;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ofpbuf_init(&actions, 0);
|
|
|
|
|
error = odp_actions_from_string(actions_s, NULL, &actions);
|
|
|
|
|
if (error) {
|
|
|
|
|
dpctl_error(dpctl_p, error, "parsing actions");
|
|
|
|
|
goto out_freeactions;
|
|
|
|
|
}
|
2014-10-12 18:18:47 -07:00
|
|
|
|
|
2016-11-15 15:40:49 -08:00
|
|
|
|
/* The flow will be added on all pmds currently in the datapath. */
|
|
|
|
|
error = dpif_flow_put(dpif, flags,
|
|
|
|
|
key.data, key.size,
|
|
|
|
|
mask.size == 0 ? NULL : mask.data,
|
|
|
|
|
mask.size, actions.data,
|
|
|
|
|
actions.size, ufid_present ? &ufid : NULL,
|
|
|
|
|
PMD_ID_NULL,
|
|
|
|
|
dpctl_p->print_statistics ? &stats : NULL);
|
|
|
|
|
|
2014-07-17 17:26:00 -07:00
|
|
|
|
if (error) {
|
|
|
|
|
dpctl_error(dpctl_p, error, "updating flow table");
|
|
|
|
|
goto out_freeactions;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dpctl_p->print_statistics) {
|
|
|
|
|
struct ds s;
|
|
|
|
|
|
|
|
|
|
ds_init(&s);
|
|
|
|
|
dpif_flow_stats_format(&stats, &s);
|
|
|
|
|
dpctl_print(dpctl_p, "%s\n", ds_cstr(&s));
|
|
|
|
|
ds_destroy(&s);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
out_freeactions:
|
|
|
|
|
ofpbuf_uninit(&actions);
|
|
|
|
|
out_freekeymask:
|
|
|
|
|
ofpbuf_uninit(&mask);
|
|
|
|
|
ofpbuf_uninit(&key);
|
|
|
|
|
dpif_close(dpif);
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
dpctl_add_flow(int argc, const char *argv[], struct dpctl_params *dpctl_p)
|
|
|
|
|
{
|
|
|
|
|
return dpctl_put_flow(argc, argv, DPIF_FP_CREATE, dpctl_p);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
dpctl_mod_flow(int argc, const char *argv[], struct dpctl_params *dpctl_p)
|
|
|
|
|
{
|
|
|
|
|
enum dpif_flow_put_flags flags;
|
|
|
|
|
|
|
|
|
|
flags = DPIF_FP_MODIFY;
|
|
|
|
|
if (dpctl_p->may_create) {
|
|
|
|
|
flags |= DPIF_FP_CREATE;
|
|
|
|
|
}
|
|
|
|
|
if (dpctl_p->zero_statistics) {
|
|
|
|
|
flags |= DPIF_FP_ZERO_STATS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return dpctl_put_flow(argc, argv, flags, dpctl_p);
|
|
|
|
|
}
|
|
|
|
|
|
2014-12-05 14:45:51 -08:00
|
|
|
|
static int
|
|
|
|
|
dpctl_get_flow(int argc, const char *argv[], struct dpctl_params *dpctl_p)
|
|
|
|
|
{
|
|
|
|
|
const char *key_s = argv[argc - 1];
|
|
|
|
|
struct dpif_flow flow;
|
|
|
|
|
struct dpif *dpif;
|
|
|
|
|
ovs_u128 ufid;
|
|
|
|
|
struct ofpbuf buf;
|
|
|
|
|
uint64_t stub[DPIF_FLOW_BUFSIZE / 8];
|
|
|
|
|
struct ds ds;
|
|
|
|
|
int n, error;
|
|
|
|
|
|
2018-06-07 16:20:59 -07:00
|
|
|
|
error = opt_dpif_open(argc, argv, dpctl_p, 3, &dpif);
|
2014-12-05 14:45:51 -08:00
|
|
|
|
if (error) {
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ofpbuf_use_stub(&buf, &stub, sizeof stub);
|
2017-06-18 09:46:54 +08:00
|
|
|
|
|
|
|
|
|
struct hmap *portno_names = dpctl_get_portno_names(dpif, dpctl_p);
|
2014-12-05 14:45:51 -08:00
|
|
|
|
|
|
|
|
|
n = odp_ufid_from_string(key_s, &ufid);
|
|
|
|
|
if (n <= 0) {
|
|
|
|
|
dpctl_error(dpctl_p, -n, "parsing flow ufid");
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-27 16:32:50 +03:00
|
|
|
|
/* In case of PMD will be returned flow from first PMD thread with match. */
|
2014-10-12 18:18:47 -07:00
|
|
|
|
error = dpif_flow_get(dpif, NULL, 0, &ufid, PMD_ID_NULL, &buf, &flow);
|
2014-12-05 14:45:51 -08:00
|
|
|
|
if (error) {
|
|
|
|
|
dpctl_error(dpctl_p, error, "getting flow");
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ds_init(&ds);
|
dpctl: Properly reflect a rule's offloaded to HW state
Previously, any rule that is offloaded via a netdev, not necessarily
to the HW, would be reported as "offloaded". This patch fixes this
misalignment, and introduces the 'dp' state, as follows:
rule is in HW via TC offload -> offloaded=yes dp:tc
rule is in not HW over TC DP -> offloaded=no dp:tc
rule is in not HW over OVS DP -> offloaded=no dp:ovs
To achieve this, the flows's 'offloaded' flag was encapsulated in a new
attrs struct, which contains the offloaded state of the flow and the
DP layer the flow is handled in, and instead of setting the flow's
'offloaded' state based solely on the type of dump it was acquired
via, for netdev flows it now sends the new attrs struct to be
collected along with the rest of the flow via the netdev, allowing
it to be set per flow.
For TC offloads, the offloaded state is set based on the 'in_hw' and
'not_in_hw' flags received from the TC as part of the flower. If no
such flag was received, due to lack of kernel support, it defaults
to true.
Signed-off-by: Gavi Teitz <gavi@mellanox.com>
Acked-by: Roi Dayan <roid@mellanox.com>
[simon: resolved conflict in lib/dpctl.man]
Signed-off-by: Simon Horman <simon.horman@netronome.com>
2018-06-07 09:36:59 +03:00
|
|
|
|
format_dpif_flow(&ds, &flow, portno_names, dpctl_p);
|
2014-12-05 14:45:51 -08:00
|
|
|
|
dpctl_print(dpctl_p, "%s\n", ds_cstr(&ds));
|
|
|
|
|
ds_destroy(&ds);
|
|
|
|
|
|
|
|
|
|
out:
|
2017-06-18 09:46:54 +08:00
|
|
|
|
dpctl_free_portno_names(portno_names);
|
2014-12-05 14:45:51 -08:00
|
|
|
|
ofpbuf_uninit(&buf);
|
|
|
|
|
dpif_close(dpif);
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-17 17:26:00 -07:00
|
|
|
|
static int
|
|
|
|
|
dpctl_del_flow(int argc, const char *argv[], struct dpctl_params *dpctl_p)
|
|
|
|
|
{
|
|
|
|
|
const char *key_s = argv[argc - 1];
|
|
|
|
|
struct dpif_flow_stats stats;
|
|
|
|
|
struct dpif_port dpif_port;
|
|
|
|
|
struct dpif_port_dump port_dump;
|
|
|
|
|
struct ofpbuf key;
|
|
|
|
|
struct ofpbuf mask; /* To be ignored. */
|
|
|
|
|
struct dpif *dpif;
|
2014-11-12 09:49:22 -08:00
|
|
|
|
ovs_u128 ufid;
|
|
|
|
|
bool ufid_present;
|
2014-07-17 17:26:00 -07:00
|
|
|
|
struct simap port_names;
|
2014-11-12 09:49:22 -08:00
|
|
|
|
int n, error;
|
2014-07-17 17:26:00 -07:00
|
|
|
|
|
2018-06-07 16:20:59 -07:00
|
|
|
|
error = opt_dpif_open(argc, argv, dpctl_p, 3, &dpif);
|
2014-07-17 17:26:00 -07:00
|
|
|
|
if (error) {
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
2014-11-12 09:49:22 -08:00
|
|
|
|
ufid_present = false;
|
|
|
|
|
n = odp_ufid_from_string(key_s, &ufid);
|
|
|
|
|
if (n < 0) {
|
|
|
|
|
dpctl_error(dpctl_p, -n, "parsing flow ufid");
|
|
|
|
|
return -n;
|
|
|
|
|
} else if (n) {
|
|
|
|
|
key_s += n;
|
|
|
|
|
ufid_present = true;
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-17 17:26:00 -07:00
|
|
|
|
simap_init(&port_names);
|
|
|
|
|
DPIF_PORT_FOR_EACH (&dpif_port, &port_dump, dpif) {
|
|
|
|
|
simap_put(&port_names, dpif_port.name, odp_to_u32(dpif_port.port_no));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ofpbuf_init(&key, 0);
|
|
|
|
|
ofpbuf_init(&mask, 0);
|
|
|
|
|
|
|
|
|
|
error = odp_flow_from_string(key_s, &port_names, &key, &mask);
|
|
|
|
|
if (error) {
|
|
|
|
|
dpctl_error(dpctl_p, error, "parsing flow key");
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
2016-11-15 15:40:49 -08:00
|
|
|
|
/* The flow will be deleted from all pmds currently in the datapath. */
|
|
|
|
|
error = dpif_flow_del(dpif, key.data, key.size,
|
|
|
|
|
ufid_present ? &ufid : NULL, PMD_ID_NULL,
|
|
|
|
|
dpctl_p->print_statistics ? &stats : NULL);
|
|
|
|
|
|
2014-07-17 17:26:00 -07:00
|
|
|
|
if (error) {
|
|
|
|
|
dpctl_error(dpctl_p, error, "deleting flow");
|
2014-11-12 09:49:22 -08:00
|
|
|
|
if (error == ENOENT && !ufid_present) {
|
|
|
|
|
struct ds s;
|
|
|
|
|
|
|
|
|
|
ds_init(&s);
|
|
|
|
|
ds_put_format(&s, "Perhaps you need to specify a UFID?");
|
|
|
|
|
dpctl_print(dpctl_p, "%s\n", ds_cstr(&s));
|
|
|
|
|
ds_destroy(&s);
|
|
|
|
|
}
|
2014-07-17 17:26:00 -07:00
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dpctl_p->print_statistics) {
|
|
|
|
|
struct ds s;
|
|
|
|
|
|
|
|
|
|
ds_init(&s);
|
|
|
|
|
dpif_flow_stats_format(&stats, &s);
|
|
|
|
|
dpctl_print(dpctl_p, "%s\n", ds_cstr(&s));
|
|
|
|
|
ds_destroy(&s);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
out:
|
|
|
|
|
ofpbuf_uninit(&mask);
|
|
|
|
|
ofpbuf_uninit(&key);
|
|
|
|
|
simap_destroy(&port_names);
|
|
|
|
|
dpif_close(dpif);
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
dpctl_del_flows(int argc, const char *argv[], struct dpctl_params *dpctl_p)
|
|
|
|
|
{
|
|
|
|
|
struct dpif *dpif;
|
|
|
|
|
|
2018-06-07 16:20:59 -07:00
|
|
|
|
int error = opt_dpif_open(argc, argv, dpctl_p, 2, &dpif);
|
2014-07-17 17:26:00 -07:00
|
|
|
|
if (error) {
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
error = dpif_flow_flush(dpif);
|
|
|
|
|
if (error) {
|
|
|
|
|
dpctl_error(dpctl_p, error, "deleting all flows");
|
|
|
|
|
}
|
|
|
|
|
dpif_close(dpif);
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
dpctl_help(int argc OVS_UNUSED, const char *argv[] OVS_UNUSED,
|
|
|
|
|
struct dpctl_params *dpctl_p)
|
|
|
|
|
{
|
|
|
|
|
if (dpctl_p->usage) {
|
|
|
|
|
dpctl_p->usage(dpctl_p->aux);
|
|
|
|
|
}
|
2014-10-16 13:26:07 -07:00
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
dpctl_list_commands(int argc OVS_UNUSED, const char *argv[] OVS_UNUSED,
|
|
|
|
|
struct dpctl_params *dpctl_p)
|
|
|
|
|
{
|
|
|
|
|
struct ds ds = DS_EMPTY_INITIALIZER;
|
|
|
|
|
const struct dpctl_command *commands = get_all_dpctl_commands();
|
|
|
|
|
|
|
|
|
|
ds_put_cstr(&ds, "The available commands are:\n");
|
|
|
|
|
for (; commands->name; commands++) {
|
|
|
|
|
const struct dpctl_command *c = commands;
|
|
|
|
|
|
|
|
|
|
ds_put_format(&ds, " %s%-23s %s\n", dpctl_p->is_appctl ? "dpctl/" : "",
|
|
|
|
|
c->name, c->usage);
|
|
|
|
|
}
|
|
|
|
|
dpctl_puts(dpctl_p, false, ds.string);
|
|
|
|
|
ds_destroy(&ds);
|
|
|
|
|
|
2014-07-17 17:26:00 -07:00
|
|
|
|
return 0;
|
|
|
|
|
}
|
2018-06-07 16:20:59 -07:00
|
|
|
|
|
2015-10-28 11:38:00 -07:00
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
dpctl_dump_conntrack(int argc, const char *argv[],
|
|
|
|
|
struct dpctl_params *dpctl_p)
|
|
|
|
|
{
|
|
|
|
|
struct ct_dpif_dump_state *dump;
|
|
|
|
|
struct ct_dpif_entry cte;
|
|
|
|
|
uint16_t zone, *pzone = NULL;
|
2017-08-01 20:12:03 -07:00
|
|
|
|
int tot_bkts;
|
2015-10-28 11:38:00 -07:00
|
|
|
|
struct dpif *dpif;
|
|
|
|
|
int error;
|
|
|
|
|
|
|
|
|
|
if (argc > 1 && ovs_scan(argv[argc - 1], "zone=%"SCNu16, &zone)) {
|
|
|
|
|
pzone = &zone;
|
|
|
|
|
argc--;
|
|
|
|
|
}
|
2018-06-07 16:20:59 -07:00
|
|
|
|
|
|
|
|
|
error = opt_dpif_open(argc, argv, dpctl_p, 2, &dpif);
|
2015-10-28 11:38:00 -07:00
|
|
|
|
if (error) {
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-01 20:12:03 -07:00
|
|
|
|
error = ct_dpif_dump_start(dpif, &dump, pzone, &tot_bkts);
|
2015-10-28 11:38:00 -07:00
|
|
|
|
if (error) {
|
|
|
|
|
dpctl_error(dpctl_p, error, "starting conntrack dump");
|
|
|
|
|
dpif_close(dpif);
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-26 10:37:09 +01:00
|
|
|
|
while (!(error = ct_dpif_dump_next(dump, &cte))) {
|
2015-10-28 11:38:00 -07:00
|
|
|
|
struct ds s = DS_EMPTY_INITIALIZER;
|
|
|
|
|
|
|
|
|
|
ct_dpif_format_entry(&cte, &s, dpctl_p->verbosity,
|
|
|
|
|
dpctl_p->print_statistics);
|
|
|
|
|
ct_dpif_entry_uninit(&cte);
|
|
|
|
|
|
|
|
|
|
dpctl_print(dpctl_p, "%s\n", ds_cstr(&s));
|
|
|
|
|
ds_destroy(&s);
|
|
|
|
|
}
|
2017-09-26 10:37:09 +01:00
|
|
|
|
if (error == EOF) {
|
|
|
|
|
/* Any CT entry was dumped with no issue. */
|
|
|
|
|
error = 0;
|
|
|
|
|
} else if (error) {
|
|
|
|
|
dpctl_error(dpctl_p, error, "dumping conntrack entry");
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-28 11:38:00 -07:00
|
|
|
|
ct_dpif_dump_done(dump);
|
|
|
|
|
dpif_close(dpif);
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-28 10:34:52 -07:00
|
|
|
|
static int
|
|
|
|
|
dpctl_flush_conntrack(int argc, const char *argv[],
|
|
|
|
|
struct dpctl_params *dpctl_p)
|
|
|
|
|
{
|
|
|
|
|
struct dpif *dpif;
|
dpctl: Support flush conntrack by conntrack 5-tuple
With this patch, "flush-conntrack" in ovs-dpctl and ovs-appctl accept
a conntrack 5-tuple to delete the conntrack entry specified by the 5-tuple.
For example, user can use the following command to flush a conntrack entry
in zone 5.
$ ovs-dpctl flush-conntrack zone=5 \
'ct_nw_src=10.1.1.2,ct_nw_dst=10.1.1.1,ct_nw_proto=17,ct_tp_src=2,ct_tp_dst=1'
$ ovs-appctl dpctl/flush-conntrack zone=5 \
'ct_nw_src=10.1.1.2,ct_nw_dst=10.1.1.1,ct_nw_proto=17,ct_tp_src=2,ct_tp_dst=1'
VMWare-BZ: #1983178
Signed-off-by: Yi-Hung Wei <yihung.wei@gmail.com>
Signed-off-by: Justin Pettit <jpettit@ovn.org>
2017-12-07 10:40:04 -08:00
|
|
|
|
struct ct_dpif_tuple tuple, *ptuple = NULL;
|
|
|
|
|
struct ds ds = DS_EMPTY_INITIALIZER;
|
2015-10-28 10:34:52 -07:00
|
|
|
|
uint16_t zone, *pzone = NULL;
|
|
|
|
|
char *name;
|
dpctl: Support flush conntrack by conntrack 5-tuple
With this patch, "flush-conntrack" in ovs-dpctl and ovs-appctl accept
a conntrack 5-tuple to delete the conntrack entry specified by the 5-tuple.
For example, user can use the following command to flush a conntrack entry
in zone 5.
$ ovs-dpctl flush-conntrack zone=5 \
'ct_nw_src=10.1.1.2,ct_nw_dst=10.1.1.1,ct_nw_proto=17,ct_tp_src=2,ct_tp_dst=1'
$ ovs-appctl dpctl/flush-conntrack zone=5 \
'ct_nw_src=10.1.1.2,ct_nw_dst=10.1.1.1,ct_nw_proto=17,ct_tp_src=2,ct_tp_dst=1'
VMWare-BZ: #1983178
Signed-off-by: Yi-Hung Wei <yihung.wei@gmail.com>
Signed-off-by: Justin Pettit <jpettit@ovn.org>
2017-12-07 10:40:04 -08:00
|
|
|
|
int error, i = 1;
|
|
|
|
|
bool got_dpif = false;
|
|
|
|
|
|
|
|
|
|
/* Parse datapath name. It is not a mandatory parameter for this command.
|
|
|
|
|
* If it is not specified, we retrieve it from the current setup,
|
|
|
|
|
* assuming only one exists. */
|
|
|
|
|
if (argc >= 2) {
|
|
|
|
|
error = parsed_dpif_open(argv[i], false, &dpif);
|
|
|
|
|
if (!error) {
|
|
|
|
|
got_dpif = true;
|
|
|
|
|
i++;
|
|
|
|
|
} else if (argc == 4) {
|
|
|
|
|
dpctl_error(dpctl_p, error, "invalid datapath");
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!got_dpif) {
|
|
|
|
|
name = get_one_dp(dpctl_p);
|
|
|
|
|
if (!name) {
|
|
|
|
|
return EINVAL;
|
|
|
|
|
}
|
|
|
|
|
error = parsed_dpif_open(name, false, &dpif);
|
|
|
|
|
free(name);
|
|
|
|
|
if (error) {
|
|
|
|
|
dpctl_error(dpctl_p, error, "opening datapath");
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-10-28 10:34:52 -07:00
|
|
|
|
|
dpctl: Support flush conntrack by conntrack 5-tuple
With this patch, "flush-conntrack" in ovs-dpctl and ovs-appctl accept
a conntrack 5-tuple to delete the conntrack entry specified by the 5-tuple.
For example, user can use the following command to flush a conntrack entry
in zone 5.
$ ovs-dpctl flush-conntrack zone=5 \
'ct_nw_src=10.1.1.2,ct_nw_dst=10.1.1.1,ct_nw_proto=17,ct_tp_src=2,ct_tp_dst=1'
$ ovs-appctl dpctl/flush-conntrack zone=5 \
'ct_nw_src=10.1.1.2,ct_nw_dst=10.1.1.1,ct_nw_proto=17,ct_tp_src=2,ct_tp_dst=1'
VMWare-BZ: #1983178
Signed-off-by: Yi-Hung Wei <yihung.wei@gmail.com>
Signed-off-by: Justin Pettit <jpettit@ovn.org>
2017-12-07 10:40:04 -08:00
|
|
|
|
/* Parse zone */
|
|
|
|
|
if (argc > i && ovs_scan(argv[i], "zone=%"SCNu16, &zone)) {
|
2015-10-28 10:34:52 -07:00
|
|
|
|
pzone = &zone;
|
dpctl: Support flush conntrack by conntrack 5-tuple
With this patch, "flush-conntrack" in ovs-dpctl and ovs-appctl accept
a conntrack 5-tuple to delete the conntrack entry specified by the 5-tuple.
For example, user can use the following command to flush a conntrack entry
in zone 5.
$ ovs-dpctl flush-conntrack zone=5 \
'ct_nw_src=10.1.1.2,ct_nw_dst=10.1.1.1,ct_nw_proto=17,ct_tp_src=2,ct_tp_dst=1'
$ ovs-appctl dpctl/flush-conntrack zone=5 \
'ct_nw_src=10.1.1.2,ct_nw_dst=10.1.1.1,ct_nw_proto=17,ct_tp_src=2,ct_tp_dst=1'
VMWare-BZ: #1983178
Signed-off-by: Yi-Hung Wei <yihung.wei@gmail.com>
Signed-off-by: Justin Pettit <jpettit@ovn.org>
2017-12-07 10:40:04 -08:00
|
|
|
|
i++;
|
2015-10-28 10:34:52 -07:00
|
|
|
|
}
|
dpctl: Support flush conntrack by conntrack 5-tuple
With this patch, "flush-conntrack" in ovs-dpctl and ovs-appctl accept
a conntrack 5-tuple to delete the conntrack entry specified by the 5-tuple.
For example, user can use the following command to flush a conntrack entry
in zone 5.
$ ovs-dpctl flush-conntrack zone=5 \
'ct_nw_src=10.1.1.2,ct_nw_dst=10.1.1.1,ct_nw_proto=17,ct_tp_src=2,ct_tp_dst=1'
$ ovs-appctl dpctl/flush-conntrack zone=5 \
'ct_nw_src=10.1.1.2,ct_nw_dst=10.1.1.1,ct_nw_proto=17,ct_tp_src=2,ct_tp_dst=1'
VMWare-BZ: #1983178
Signed-off-by: Yi-Hung Wei <yihung.wei@gmail.com>
Signed-off-by: Justin Pettit <jpettit@ovn.org>
2017-12-07 10:40:04 -08:00
|
|
|
|
/* Report error if there are more than one unparsed argument. */
|
|
|
|
|
if (argc - i > 1) {
|
|
|
|
|
ds_put_cstr(&ds, "invalid zone");
|
|
|
|
|
error = EINVAL;
|
|
|
|
|
goto error;
|
2015-10-28 10:34:52 -07:00
|
|
|
|
}
|
dpctl: Support flush conntrack by conntrack 5-tuple
With this patch, "flush-conntrack" in ovs-dpctl and ovs-appctl accept
a conntrack 5-tuple to delete the conntrack entry specified by the 5-tuple.
For example, user can use the following command to flush a conntrack entry
in zone 5.
$ ovs-dpctl flush-conntrack zone=5 \
'ct_nw_src=10.1.1.2,ct_nw_dst=10.1.1.1,ct_nw_proto=17,ct_tp_src=2,ct_tp_dst=1'
$ ovs-appctl dpctl/flush-conntrack zone=5 \
'ct_nw_src=10.1.1.2,ct_nw_dst=10.1.1.1,ct_nw_proto=17,ct_tp_src=2,ct_tp_dst=1'
VMWare-BZ: #1983178
Signed-off-by: Yi-Hung Wei <yihung.wei@gmail.com>
Signed-off-by: Justin Pettit <jpettit@ovn.org>
2017-12-07 10:40:04 -08:00
|
|
|
|
|
|
|
|
|
/* Parse ct tuple */
|
|
|
|
|
if (argc > i && ct_dpif_parse_tuple(&tuple, argv[i], &ds)) {
|
|
|
|
|
ptuple = &tuple;
|
|
|
|
|
i++;
|
|
|
|
|
}
|
|
|
|
|
/* Report error if there is an unparsed argument. */
|
|
|
|
|
if (argc - i) {
|
|
|
|
|
error = EINVAL;
|
|
|
|
|
goto error;
|
2015-10-28 10:34:52 -07:00
|
|
|
|
}
|
|
|
|
|
|
dpctl: Support flush conntrack by conntrack 5-tuple
With this patch, "flush-conntrack" in ovs-dpctl and ovs-appctl accept
a conntrack 5-tuple to delete the conntrack entry specified by the 5-tuple.
For example, user can use the following command to flush a conntrack entry
in zone 5.
$ ovs-dpctl flush-conntrack zone=5 \
'ct_nw_src=10.1.1.2,ct_nw_dst=10.1.1.1,ct_nw_proto=17,ct_tp_src=2,ct_tp_dst=1'
$ ovs-appctl dpctl/flush-conntrack zone=5 \
'ct_nw_src=10.1.1.2,ct_nw_dst=10.1.1.1,ct_nw_proto=17,ct_tp_src=2,ct_tp_dst=1'
VMWare-BZ: #1983178
Signed-off-by: Yi-Hung Wei <yihung.wei@gmail.com>
Signed-off-by: Justin Pettit <jpettit@ovn.org>
2017-12-07 10:40:04 -08:00
|
|
|
|
error = ct_dpif_flush(dpif, pzone, ptuple);
|
|
|
|
|
if (!error) {
|
|
|
|
|
dpif_close(dpif);
|
|
|
|
|
return 0;
|
|
|
|
|
} else {
|
|
|
|
|
ds_put_cstr(&ds, "failed to flush conntrack");
|
|
|
|
|
}
|
2015-10-28 10:34:52 -07:00
|
|
|
|
|
dpctl: Support flush conntrack by conntrack 5-tuple
With this patch, "flush-conntrack" in ovs-dpctl and ovs-appctl accept
a conntrack 5-tuple to delete the conntrack entry specified by the 5-tuple.
For example, user can use the following command to flush a conntrack entry
in zone 5.
$ ovs-dpctl flush-conntrack zone=5 \
'ct_nw_src=10.1.1.2,ct_nw_dst=10.1.1.1,ct_nw_proto=17,ct_tp_src=2,ct_tp_dst=1'
$ ovs-appctl dpctl/flush-conntrack zone=5 \
'ct_nw_src=10.1.1.2,ct_nw_dst=10.1.1.1,ct_nw_proto=17,ct_tp_src=2,ct_tp_dst=1'
VMWare-BZ: #1983178
Signed-off-by: Yi-Hung Wei <yihung.wei@gmail.com>
Signed-off-by: Justin Pettit <jpettit@ovn.org>
2017-12-07 10:40:04 -08:00
|
|
|
|
error:
|
|
|
|
|
dpctl_error(dpctl_p, error, "%s", ds_cstr(&ds));
|
|
|
|
|
ds_destroy(&ds);
|
2015-10-28 10:34:52 -07:00
|
|
|
|
dpif_close(dpif);
|
|
|
|
|
return error;
|
|
|
|
|
}
|
2017-06-23 13:28:21 +01:00
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
dpctl_ct_stats_show(int argc, const char *argv[],
|
|
|
|
|
struct dpctl_params *dpctl_p)
|
|
|
|
|
{
|
|
|
|
|
struct dpif *dpif;
|
|
|
|
|
struct ct_dpif_dump_state *dump;
|
|
|
|
|
struct ct_dpif_entry cte;
|
|
|
|
|
uint16_t zone, *pzone = NULL;
|
2017-08-01 20:12:03 -07:00
|
|
|
|
int tot_bkts;
|
2017-06-23 13:28:21 +01:00
|
|
|
|
int lastargc = 0;
|
|
|
|
|
|
|
|
|
|
int proto_stats[CT_STATS_MAX];
|
|
|
|
|
int tcp_conn_per_states[CT_DPIF_TCPS_MAX_NUM];
|
|
|
|
|
int error;
|
|
|
|
|
|
2018-06-13 18:29:49 -07:00
|
|
|
|
bool verbose = dpctl_p->verbosity;
|
|
|
|
|
|
2017-06-23 13:28:21 +01:00
|
|
|
|
while (argc > 1 && lastargc != argc) {
|
|
|
|
|
lastargc = argc;
|
|
|
|
|
if (!strncmp(argv[argc - 1], "verbose", 7)) {
|
2018-06-13 18:29:49 -07:00
|
|
|
|
/* Support "verbose" argument for backwards compatibility. */
|
2017-06-23 13:28:21 +01:00
|
|
|
|
verbose = true;
|
|
|
|
|
argc--;
|
|
|
|
|
} else if (!strncmp(argv[argc - 1], "zone=", 5)) {
|
|
|
|
|
if (ovs_scan(argv[argc - 1], "zone=%"SCNu16, &zone)) {
|
|
|
|
|
pzone = &zone;
|
|
|
|
|
argc--;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-07 16:20:59 -07:00
|
|
|
|
error = opt_dpif_open(argc, argv, dpctl_p, 2, &dpif);
|
2017-06-23 13:28:21 +01:00
|
|
|
|
if (error) {
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
memset(proto_stats, 0, sizeof(proto_stats));
|
|
|
|
|
memset(tcp_conn_per_states, 0, sizeof(tcp_conn_per_states));
|
2017-08-01 20:12:03 -07:00
|
|
|
|
error = ct_dpif_dump_start(dpif, &dump, pzone, &tot_bkts);
|
2017-06-23 13:28:21 +01:00
|
|
|
|
if (error) {
|
|
|
|
|
dpctl_error(dpctl_p, error, "starting conntrack dump");
|
|
|
|
|
dpif_close(dpif);
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int tot_conn = 0;
|
2017-09-26 10:37:09 +01:00
|
|
|
|
while (!(error = ct_dpif_dump_next(dump, &cte))) {
|
2017-06-23 13:28:21 +01:00
|
|
|
|
ct_dpif_entry_uninit(&cte);
|
|
|
|
|
tot_conn++;
|
|
|
|
|
switch (cte.tuple_orig.ip_proto) {
|
|
|
|
|
case IPPROTO_ICMP:
|
|
|
|
|
proto_stats[CT_STATS_ICMP]++;
|
|
|
|
|
break;
|
|
|
|
|
case IPPROTO_ICMPV6:
|
|
|
|
|
proto_stats[CT_STATS_ICMPV6]++;
|
|
|
|
|
break;
|
|
|
|
|
case IPPROTO_TCP:
|
|
|
|
|
proto_stats[CT_STATS_TCP]++;
|
|
|
|
|
uint8_t tcp_state;
|
|
|
|
|
/* We keep two separate tcp states, but we print just one. The
|
|
|
|
|
* Linux kernel connection tracker internally keeps only one state,
|
|
|
|
|
* so 'state_orig' and 'state_reply', will be the same. */
|
|
|
|
|
tcp_state = MAX(cte.protoinfo.tcp.state_orig,
|
|
|
|
|
cte.protoinfo.tcp.state_reply);
|
|
|
|
|
tcp_state = ct_dpif_coalesce_tcp_state(tcp_state);
|
|
|
|
|
tcp_conn_per_states[tcp_state]++;
|
|
|
|
|
break;
|
|
|
|
|
case IPPROTO_UDP:
|
|
|
|
|
proto_stats[CT_STATS_UDP]++;
|
|
|
|
|
break;
|
|
|
|
|
case IPPROTO_SCTP:
|
|
|
|
|
proto_stats[CT_STATS_SCTP]++;
|
|
|
|
|
break;
|
|
|
|
|
case IPPROTO_UDPLITE:
|
|
|
|
|
proto_stats[CT_STATS_UDPLITE]++;
|
|
|
|
|
break;
|
|
|
|
|
case IPPROTO_DCCP:
|
|
|
|
|
proto_stats[CT_STATS_DCCP]++;
|
|
|
|
|
break;
|
|
|
|
|
case IPPROTO_IGMP:
|
|
|
|
|
proto_stats[CT_STATS_IGMP]++;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
proto_stats[CT_STATS_OTHER]++;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-09-26 10:37:09 +01:00
|
|
|
|
if (error == EOF) {
|
|
|
|
|
/* All CT entries were dumped with no issue. */
|
|
|
|
|
error = 0;
|
|
|
|
|
} else if (error) {
|
|
|
|
|
dpctl_error(dpctl_p, error, "dumping conntrack entry");
|
|
|
|
|
/* Fall through to show any other info we collected. */
|
|
|
|
|
}
|
2017-06-23 13:28:21 +01:00
|
|
|
|
|
|
|
|
|
dpctl_print(dpctl_p, "Connections Stats:\n Total: %d\n", tot_conn);
|
|
|
|
|
if (proto_stats[CT_STATS_TCP]) {
|
2018-05-25 16:55:18 -07:00
|
|
|
|
dpctl_print(dpctl_p, " TCP: %d\n", proto_stats[CT_STATS_TCP]);
|
2017-06-23 13:28:21 +01:00
|
|
|
|
if (verbose) {
|
2018-05-25 16:55:18 -07:00
|
|
|
|
dpctl_print(dpctl_p, " Conn per TCP states:\n");
|
2017-06-23 13:28:21 +01:00
|
|
|
|
for (int i = 0; i < CT_DPIF_TCPS_MAX_NUM; i++) {
|
|
|
|
|
if (tcp_conn_per_states[i]) {
|
|
|
|
|
struct ds s = DS_EMPTY_INITIALIZER;
|
|
|
|
|
ct_dpif_format_tcp_stat(&s, i, tcp_conn_per_states[i]);
|
|
|
|
|
dpctl_print(dpctl_p, "%s\n", ds_cstr(&s));
|
|
|
|
|
ds_destroy(&s);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (proto_stats[CT_STATS_UDP]) {
|
2018-05-25 16:55:18 -07:00
|
|
|
|
dpctl_print(dpctl_p, " UDP: %d\n", proto_stats[CT_STATS_UDP]);
|
2017-06-23 13:28:21 +01:00
|
|
|
|
}
|
|
|
|
|
if (proto_stats[CT_STATS_UDPLITE]) {
|
2018-05-25 16:55:18 -07:00
|
|
|
|
dpctl_print(dpctl_p, " UDPLITE: %d\n", proto_stats[CT_STATS_UDPLITE]);
|
2017-06-23 13:28:21 +01:00
|
|
|
|
}
|
|
|
|
|
if (proto_stats[CT_STATS_SCTP]) {
|
2018-05-25 16:55:18 -07:00
|
|
|
|
dpctl_print(dpctl_p, " SCTP: %d\n", proto_stats[CT_STATS_SCTP]);
|
2017-06-23 13:28:21 +01:00
|
|
|
|
}
|
|
|
|
|
if (proto_stats[CT_STATS_ICMP]) {
|
2018-05-25 16:55:18 -07:00
|
|
|
|
dpctl_print(dpctl_p, " ICMP: %d\n", proto_stats[CT_STATS_ICMP]);
|
2017-06-23 13:28:21 +01:00
|
|
|
|
}
|
|
|
|
|
if (proto_stats[CT_STATS_DCCP]) {
|
2018-05-25 16:55:18 -07:00
|
|
|
|
dpctl_print(dpctl_p, " DCCP: %d\n", proto_stats[CT_STATS_DCCP]);
|
2017-06-23 13:28:21 +01:00
|
|
|
|
}
|
|
|
|
|
if (proto_stats[CT_STATS_IGMP]) {
|
2018-05-25 16:55:18 -07:00
|
|
|
|
dpctl_print(dpctl_p, " IGMP: %d\n", proto_stats[CT_STATS_IGMP]);
|
2017-06-23 13:28:21 +01:00
|
|
|
|
}
|
|
|
|
|
if (proto_stats[CT_STATS_OTHER]) {
|
2018-05-25 16:55:18 -07:00
|
|
|
|
dpctl_print(dpctl_p, " Other: %d\n", proto_stats[CT_STATS_OTHER]);
|
2017-06-23 13:28:21 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ct_dpif_dump_done(dump);
|
|
|
|
|
dpif_close(dpif);
|
|
|
|
|
return error;
|
|
|
|
|
}
|
2017-08-01 20:12:03 -07:00
|
|
|
|
|
|
|
|
|
#define CT_BKTS_GT "gt="
|
|
|
|
|
static int
|
|
|
|
|
dpctl_ct_bkts(int argc, const char *argv[],
|
|
|
|
|
struct dpctl_params *dpctl_p)
|
|
|
|
|
{
|
|
|
|
|
struct dpif *dpif;
|
|
|
|
|
struct ct_dpif_dump_state *dump;
|
|
|
|
|
struct ct_dpif_entry cte;
|
|
|
|
|
uint16_t gt = 0; /* Threshold: display value when greater than gt. */
|
|
|
|
|
uint16_t *pzone = NULL;
|
|
|
|
|
int tot_bkts = 0;
|
|
|
|
|
int error;
|
|
|
|
|
|
|
|
|
|
if (argc > 1 && !strncmp(argv[argc - 1], CT_BKTS_GT, strlen(CT_BKTS_GT))) {
|
|
|
|
|
if (ovs_scan(argv[argc - 1], CT_BKTS_GT"%"SCNu16, >)) {
|
|
|
|
|
argc--;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-07 16:20:59 -07:00
|
|
|
|
error = opt_dpif_open(argc, argv, dpctl_p, 2, &dpif);
|
2017-08-01 20:12:03 -07:00
|
|
|
|
if (error) {
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
error = ct_dpif_dump_start(dpif, &dump, pzone, &tot_bkts);
|
|
|
|
|
if (error) {
|
|
|
|
|
dpctl_error(dpctl_p, error, "starting conntrack dump");
|
|
|
|
|
dpif_close(dpif);
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
if (tot_bkts == -1) {
|
|
|
|
|
/* Command not available when called by kernel OvS. */
|
|
|
|
|
dpctl_print(dpctl_p,
|
|
|
|
|
"Command is available for UserSpace ConnTracker only.\n");
|
|
|
|
|
ct_dpif_dump_done(dump);
|
|
|
|
|
dpif_close(dpif);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dpctl_print(dpctl_p, "Total Buckets: %d\n", tot_bkts);
|
|
|
|
|
|
|
|
|
|
int tot_conn = 0;
|
|
|
|
|
uint32_t *conn_per_bkts = xzalloc(tot_bkts * sizeof(uint32_t));
|
|
|
|
|
|
2017-09-26 10:37:09 +01:00
|
|
|
|
while (!(error = ct_dpif_dump_next(dump, &cte))) {
|
2017-08-01 20:12:03 -07:00
|
|
|
|
ct_dpif_entry_uninit(&cte);
|
|
|
|
|
tot_conn++;
|
|
|
|
|
if (tot_bkts > 0) {
|
|
|
|
|
if (cte.bkt < tot_bkts) {
|
|
|
|
|
conn_per_bkts[cte.bkt]++;
|
|
|
|
|
} else {
|
|
|
|
|
dpctl_print(dpctl_p, "Bucket nr out of range: %d >= %d\n",
|
|
|
|
|
cte.bkt, tot_bkts);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-09-26 10:37:09 +01:00
|
|
|
|
if (error == EOF) {
|
|
|
|
|
/* All CT entries were dumped with no issue. */
|
|
|
|
|
error = 0;
|
|
|
|
|
} else if (error) {
|
|
|
|
|
dpctl_error(dpctl_p, error, "dumping conntrack entry");
|
|
|
|
|
/* Fall through and display all the collected info. */
|
|
|
|
|
}
|
2017-08-01 20:12:03 -07:00
|
|
|
|
|
|
|
|
|
dpctl_print(dpctl_p, "Current Connections: %d\n", tot_conn);
|
|
|
|
|
dpctl_print(dpctl_p, "\n");
|
|
|
|
|
if (tot_bkts && tot_conn) {
|
|
|
|
|
dpctl_print(dpctl_p, "+-----------+"
|
|
|
|
|
"-----------------------------------------+\n");
|
|
|
|
|
dpctl_print(dpctl_p, "| Buckets |"
|
|
|
|
|
" Connections per Buckets |\n");
|
|
|
|
|
dpctl_print(dpctl_p, "+-----------+"
|
|
|
|
|
"-----------------------------------------+");
|
|
|
|
|
#define NUM_BKTS_DIPLAYED_PER_ROW 8
|
|
|
|
|
for (int i = 0; i < tot_bkts; i++) {
|
|
|
|
|
if (i % NUM_BKTS_DIPLAYED_PER_ROW == 0) {
|
|
|
|
|
dpctl_print(dpctl_p, "\n %3d..%3d | ",
|
|
|
|
|
i, i + NUM_BKTS_DIPLAYED_PER_ROW - 1);
|
|
|
|
|
}
|
|
|
|
|
if (conn_per_bkts[i] > gt) {
|
|
|
|
|
dpctl_print(dpctl_p, "%5d", conn_per_bkts[i]);
|
|
|
|
|
} else {
|
|
|
|
|
dpctl_print(dpctl_p, "%5s", ".");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
dpctl_print(dpctl_p, "\n\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ct_dpif_dump_done(dump);
|
|
|
|
|
dpif_close(dpif);
|
|
|
|
|
free(conn_per_bkts);
|
|
|
|
|
return error;
|
|
|
|
|
}
|
2014-07-17 17:26:00 -07:00
|
|
|
|
|
2018-01-08 15:18:42 -08:00
|
|
|
|
static int
|
|
|
|
|
dpctl_ct_set_maxconns(int argc, const char *argv[],
|
|
|
|
|
struct dpctl_params *dpctl_p)
|
|
|
|
|
{
|
|
|
|
|
struct dpif *dpif;
|
2018-06-07 16:20:59 -07:00
|
|
|
|
int error = opt_dpif_open(argc, argv, dpctl_p, 3, &dpif);
|
2018-01-08 15:18:42 -08:00
|
|
|
|
if (!error) {
|
|
|
|
|
uint32_t maxconns;
|
|
|
|
|
if (ovs_scan(argv[argc - 1], "%"SCNu32, &maxconns)) {
|
|
|
|
|
error = ct_dpif_set_maxconns(dpif, maxconns);
|
|
|
|
|
|
|
|
|
|
if (!error) {
|
|
|
|
|
dpctl_print(dpctl_p, "setting maxconns successful");
|
|
|
|
|
} else {
|
|
|
|
|
dpctl_error(dpctl_p, error, "ct set maxconns failed");
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
error = EINVAL;
|
|
|
|
|
dpctl_error(dpctl_p, error, "maxconns missing or malformed");
|
|
|
|
|
}
|
|
|
|
|
dpif_close(dpif);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
dpctl_ct_get_maxconns(int argc, const char *argv[],
|
|
|
|
|
struct dpctl_params *dpctl_p)
|
|
|
|
|
{
|
|
|
|
|
struct dpif *dpif;
|
2018-06-07 16:20:59 -07:00
|
|
|
|
int error = opt_dpif_open(argc, argv, dpctl_p, 2, &dpif);
|
2018-01-08 15:18:42 -08:00
|
|
|
|
if (!error) {
|
|
|
|
|
uint32_t maxconns;
|
|
|
|
|
error = ct_dpif_get_maxconns(dpif, &maxconns);
|
|
|
|
|
|
|
|
|
|
if (!error) {
|
|
|
|
|
dpctl_print(dpctl_p, "%u\n", maxconns);
|
|
|
|
|
} else {
|
|
|
|
|
dpctl_error(dpctl_p, error, "maxconns could not be retrieved");
|
|
|
|
|
}
|
|
|
|
|
dpif_close(dpif);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-08 15:18:43 -08:00
|
|
|
|
static int
|
|
|
|
|
dpctl_ct_get_nconns(int argc, const char *argv[],
|
|
|
|
|
struct dpctl_params *dpctl_p)
|
|
|
|
|
{
|
|
|
|
|
struct dpif *dpif;
|
2018-06-07 16:20:59 -07:00
|
|
|
|
int error = opt_dpif_open(argc, argv, dpctl_p, 2, &dpif);
|
2018-01-08 15:18:43 -08:00
|
|
|
|
if (!error) {
|
|
|
|
|
uint32_t nconns;
|
|
|
|
|
error = ct_dpif_get_nconns(dpif, &nconns);
|
|
|
|
|
|
|
|
|
|
if (!error) {
|
|
|
|
|
dpctl_print(dpctl_p, "%u\n", nconns);
|
|
|
|
|
} else {
|
|
|
|
|
dpctl_error(dpctl_p, error, "nconns could not be retrieved");
|
|
|
|
|
}
|
|
|
|
|
dpif_close(dpif);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-17 17:26:00 -07:00
|
|
|
|
/* Undocumented commands for unit testing. */
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
dpctl_parse_actions(int argc, const char *argv[], struct dpctl_params* dpctl_p)
|
|
|
|
|
{
|
|
|
|
|
int i, error = 0;
|
|
|
|
|
|
|
|
|
|
for (i = 1; i < argc; i++) {
|
|
|
|
|
struct ofpbuf actions;
|
|
|
|
|
struct ds s;
|
|
|
|
|
|
|
|
|
|
ofpbuf_init(&actions, 0);
|
|
|
|
|
error = odp_actions_from_string(argv[i], NULL, &actions);
|
|
|
|
|
|
|
|
|
|
if (error) {
|
|
|
|
|
ofpbuf_uninit(&actions);
|
|
|
|
|
dpctl_error(dpctl_p, error, "odp_actions_from_string");
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ds_init(&s);
|
2017-06-18 09:51:57 +08:00
|
|
|
|
format_odp_actions(&s, actions.data, actions.size, NULL);
|
2014-07-17 17:26:00 -07:00
|
|
|
|
dpctl_print(dpctl_p, "%s\n", ds_cstr(&s));
|
|
|
|
|
ds_destroy(&s);
|
|
|
|
|
|
|
|
|
|
ofpbuf_uninit(&actions);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct actions_for_flow {
|
|
|
|
|
struct hmap_node hmap_node;
|
|
|
|
|
struct flow flow;
|
|
|
|
|
struct ofpbuf actions;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static struct actions_for_flow *
|
|
|
|
|
get_actions_for_flow(struct hmap *actions_per_flow, const struct flow *flow)
|
|
|
|
|
{
|
|
|
|
|
uint32_t hash = flow_hash(flow, 0);
|
|
|
|
|
struct actions_for_flow *af;
|
|
|
|
|
|
|
|
|
|
HMAP_FOR_EACH_WITH_HASH (af, hmap_node, hash, actions_per_flow) {
|
|
|
|
|
if (flow_equal(&af->flow, flow)) {
|
|
|
|
|
return af;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
af = xmalloc(sizeof *af);
|
|
|
|
|
af->flow = *flow;
|
|
|
|
|
ofpbuf_init(&af->actions, 0);
|
|
|
|
|
hmap_insert(actions_per_flow, &af->hmap_node, hash);
|
|
|
|
|
return af;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
compare_actions_for_flow(const void *a_, const void *b_)
|
|
|
|
|
{
|
|
|
|
|
struct actions_for_flow *const *a = a_;
|
|
|
|
|
struct actions_for_flow *const *b = b_;
|
|
|
|
|
|
|
|
|
|
return flow_compare_3way(&(*a)->flow, &(*b)->flow);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
compare_output_actions(const void *a_, const void *b_)
|
|
|
|
|
{
|
|
|
|
|
const struct nlattr *a = a_;
|
|
|
|
|
const struct nlattr *b = b_;
|
|
|
|
|
uint32_t a_port = nl_attr_get_u32(a);
|
|
|
|
|
uint32_t b_port = nl_attr_get_u32(b);
|
|
|
|
|
|
|
|
|
|
return a_port < b_port ? -1 : a_port > b_port;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
sort_output_actions__(struct nlattr *first, struct nlattr *end)
|
|
|
|
|
{
|
|
|
|
|
size_t bytes = (uint8_t *) end - (uint8_t *) first;
|
|
|
|
|
size_t n = bytes / NL_A_U32_SIZE;
|
|
|
|
|
|
|
|
|
|
ovs_assert(bytes % NL_A_U32_SIZE == 0);
|
|
|
|
|
qsort(first, n, NL_A_U32_SIZE, compare_output_actions);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
sort_output_actions(struct nlattr *actions, size_t length)
|
|
|
|
|
{
|
|
|
|
|
struct nlattr *first_output = NULL;
|
|
|
|
|
struct nlattr *a;
|
|
|
|
|
int left;
|
|
|
|
|
|
|
|
|
|
NL_ATTR_FOR_EACH (a, left, actions, length) {
|
|
|
|
|
if (nl_attr_type(a) == OVS_ACTION_ATTR_OUTPUT) {
|
|
|
|
|
if (!first_output) {
|
|
|
|
|
first_output = a;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (first_output) {
|
|
|
|
|
sort_output_actions__(first_output, a);
|
|
|
|
|
first_output = NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (first_output) {
|
|
|
|
|
uint8_t *end = (uint8_t *) actions + length;
|
|
|
|
|
sort_output_actions__(first_output,
|
|
|
|
|
ALIGNED_CAST(struct nlattr *, end));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* usage: "ovs-dpctl normalize-actions FLOW ACTIONS" where FLOW and ACTIONS
|
|
|
|
|
* have the syntax used by "ovs-dpctl dump-flows".
|
|
|
|
|
*
|
|
|
|
|
* This command prints ACTIONS in a format that shows what happens for each
|
|
|
|
|
* VLAN, independent of the order of the ACTIONS. For example, there is more
|
|
|
|
|
* than one way to output a packet on VLANs 9 and 11, but this command will
|
|
|
|
|
* print the same output for any form.
|
|
|
|
|
*
|
|
|
|
|
* The idea here generalizes beyond VLANs (e.g. to setting other fields) but
|
|
|
|
|
* so far the implementation only covers VLANs. */
|
|
|
|
|
static int
|
|
|
|
|
dpctl_normalize_actions(int argc, const char *argv[],
|
|
|
|
|
struct dpctl_params *dpctl_p)
|
|
|
|
|
{
|
|
|
|
|
struct simap port_names;
|
|
|
|
|
struct ofpbuf keybuf;
|
|
|
|
|
struct flow flow;
|
|
|
|
|
struct ofpbuf odp_actions;
|
|
|
|
|
struct hmap actions_per_flow;
|
|
|
|
|
struct actions_for_flow **afs;
|
|
|
|
|
struct actions_for_flow *af;
|
|
|
|
|
struct nlattr *a;
|
|
|
|
|
size_t n_afs;
|
|
|
|
|
struct ds s;
|
|
|
|
|
int left;
|
|
|
|
|
int i, error;
|
2017-03-01 17:47:59 -05:00
|
|
|
|
int encaps = 0;
|
2014-07-17 17:26:00 -07:00
|
|
|
|
|
|
|
|
|
ds_init(&s);
|
|
|
|
|
|
|
|
|
|
simap_init(&port_names);
|
|
|
|
|
for (i = 3; i < argc; i++) {
|
|
|
|
|
char name[16];
|
|
|
|
|
int number;
|
|
|
|
|
|
|
|
|
|
if (ovs_scan(argv[i], "%15[^=]=%d", name, &number)) {
|
|
|
|
|
uintptr_t n = number;
|
|
|
|
|
simap_put(&port_names, name, n);
|
|
|
|
|
} else {
|
|
|
|
|
dpctl_error(dpctl_p, 0, "%s: expected NAME=NUMBER", argv[i]);
|
|
|
|
|
error = EINVAL;
|
|
|
|
|
goto out;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Parse flow key. */
|
|
|
|
|
ofpbuf_init(&keybuf, 0);
|
|
|
|
|
error = odp_flow_from_string(argv[1], &port_names, &keybuf, NULL);
|
|
|
|
|
if (error) {
|
|
|
|
|
dpctl_error(dpctl_p, error, "odp_flow_key_from_string");
|
|
|
|
|
goto out_freekeybuf;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ds_clear(&s);
|
2015-03-02 17:29:44 -08:00
|
|
|
|
odp_flow_format(keybuf.data, keybuf.size, NULL, 0, NULL,
|
2014-07-17 17:26:00 -07:00
|
|
|
|
&s, dpctl_p->verbosity);
|
|
|
|
|
dpctl_print(dpctl_p, "input flow: %s\n", ds_cstr(&s));
|
|
|
|
|
|
2015-03-02 17:29:44 -08:00
|
|
|
|
error = odp_flow_key_to_flow(keybuf.data, keybuf.size, &flow);
|
2014-07-17 17:26:00 -07:00
|
|
|
|
if (error) {
|
|
|
|
|
dpctl_error(dpctl_p, error, "odp_flow_key_to_flow");
|
|
|
|
|
goto out_freekeybuf;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Parse actions. */
|
|
|
|
|
ofpbuf_init(&odp_actions, 0);
|
|
|
|
|
error = odp_actions_from_string(argv[2], &port_names, &odp_actions);
|
|
|
|
|
if (error) {
|
|
|
|
|
dpctl_error(dpctl_p, error, "odp_actions_from_string");
|
|
|
|
|
goto out_freeactions;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dpctl_p->verbosity) {
|
|
|
|
|
ds_clear(&s);
|
2017-06-18 09:51:57 +08:00
|
|
|
|
format_odp_actions(&s, odp_actions.data, odp_actions.size, NULL);
|
2014-07-17 17:26:00 -07:00
|
|
|
|
dpctl_print(dpctl_p, "input actions: %s\n", ds_cstr(&s));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hmap_init(&actions_per_flow);
|
2015-03-02 17:29:44 -08:00
|
|
|
|
NL_ATTR_FOR_EACH (a, left, odp_actions.data, odp_actions.size) {
|
2014-07-17 17:26:00 -07:00
|
|
|
|
const struct ovs_action_push_vlan *push;
|
|
|
|
|
switch(nl_attr_type(a)) {
|
|
|
|
|
case OVS_ACTION_ATTR_POP_VLAN:
|
2017-03-01 17:47:59 -05:00
|
|
|
|
flow_pop_vlan(&flow, NULL);
|
2014-07-17 17:26:00 -07:00
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
case OVS_ACTION_ATTR_PUSH_VLAN:
|
2017-03-01 17:47:59 -05:00
|
|
|
|
flow_push_vlan_uninit(&flow, NULL);
|
2014-07-17 17:26:00 -07:00
|
|
|
|
push = nl_attr_get_unspec(a, sizeof *push);
|
2017-03-01 17:47:59 -05:00
|
|
|
|
flow.vlans[0].tpid = push->vlan_tpid;
|
|
|
|
|
flow.vlans[0].tci = push->vlan_tci;
|
2014-07-17 17:26:00 -07:00
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
af = get_actions_for_flow(&actions_per_flow, &flow);
|
|
|
|
|
nl_msg_put_unspec(&af->actions, nl_attr_type(a),
|
|
|
|
|
nl_attr_get(a), nl_attr_get_size(a));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
n_afs = hmap_count(&actions_per_flow);
|
|
|
|
|
afs = xmalloc(n_afs * sizeof *afs);
|
|
|
|
|
i = 0;
|
|
|
|
|
HMAP_FOR_EACH (af, hmap_node, &actions_per_flow) {
|
|
|
|
|
afs[i++] = af;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ovs_assert(i == n_afs);
|
|
|
|
|
hmap_destroy(&actions_per_flow);
|
|
|
|
|
|
|
|
|
|
qsort(afs, n_afs, sizeof *afs, compare_actions_for_flow);
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < n_afs; i++) {
|
2017-08-02 15:03:06 -07:00
|
|
|
|
af = afs[i];
|
2015-03-02 17:29:44 -08:00
|
|
|
|
sort_output_actions(af->actions.data, af->actions.size);
|
2014-07-17 17:26:00 -07:00
|
|
|
|
|
2017-03-01 17:47:59 -05:00
|
|
|
|
for (encaps = 0; encaps < FLOW_MAX_VLAN_HEADERS; encaps ++) {
|
|
|
|
|
union flow_vlan_hdr *vlan = &af->flow.vlans[encaps];
|
|
|
|
|
if (vlan->tci != htons(0)) {
|
|
|
|
|
dpctl_print(dpctl_p, "vlan(");
|
|
|
|
|
if (vlan->tpid != htons(ETH_TYPE_VLAN)) {
|
|
|
|
|
dpctl_print(dpctl_p, "tpid=0x%04"PRIx16",", vlan->tpid);
|
|
|
|
|
}
|
|
|
|
|
dpctl_print(dpctl_p, "vid=%"PRIu16",pcp=%d): ",
|
|
|
|
|
vlan_tci_to_vid(vlan->tci),
|
|
|
|
|
vlan_tci_to_pcp(vlan->tci));
|
|
|
|
|
} else {
|
|
|
|
|
if (encaps == 0) {
|
|
|
|
|
dpctl_print(dpctl_p, "no vlan: ");
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
2014-07-17 17:26:00 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (eth_type_mpls(af->flow.dl_type)) {
|
|
|
|
|
dpctl_print(dpctl_p, "mpls(label=%"PRIu32",tc=%d,ttl=%d): ",
|
|
|
|
|
mpls_lse_to_label(af->flow.mpls_lse[0]),
|
|
|
|
|
mpls_lse_to_tc(af->flow.mpls_lse[0]),
|
|
|
|
|
mpls_lse_to_ttl(af->flow.mpls_lse[0]));
|
|
|
|
|
} else {
|
|
|
|
|
dpctl_print(dpctl_p, "no mpls: ");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ds_clear(&s);
|
2017-06-18 09:51:57 +08:00
|
|
|
|
format_odp_actions(&s, af->actions.data, af->actions.size, NULL);
|
2015-05-06 19:00:24 +01:00
|
|
|
|
dpctl_puts(dpctl_p, false, ds_cstr(&s));
|
2014-07-17 17:26:00 -07:00
|
|
|
|
|
|
|
|
|
ofpbuf_uninit(&af->actions);
|
|
|
|
|
free(af);
|
|
|
|
|
}
|
|
|
|
|
free(afs);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
out_freeactions:
|
|
|
|
|
ofpbuf_uninit(&odp_actions);
|
|
|
|
|
out_freekeybuf:
|
|
|
|
|
ofpbuf_uninit(&keybuf);
|
|
|
|
|
out:
|
|
|
|
|
simap_destroy(&port_names);
|
|
|
|
|
ds_destroy(&s);
|
|
|
|
|
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const struct dpctl_command all_commands[] = {
|
2016-12-06 11:40:29 -08:00
|
|
|
|
{ "add-dp", "dp [iface...]", 1, INT_MAX, dpctl_add_dp, DP_RW },
|
|
|
|
|
{ "del-dp", "dp", 1, 1, dpctl_del_dp, DP_RW },
|
|
|
|
|
{ "add-if", "dp iface...", 2, INT_MAX, dpctl_add_if, DP_RW },
|
|
|
|
|
{ "del-if", "dp iface...", 2, INT_MAX, dpctl_del_if, DP_RW },
|
|
|
|
|
{ "set-if", "dp iface...", 2, INT_MAX, dpctl_set_if, DP_RW },
|
2016-08-15 18:47:29 +00:00
|
|
|
|
{ "dump-dps", "", 0, 0, dpctl_dump_dps, DP_RO },
|
|
|
|
|
{ "show", "[dp...]", 0, INT_MAX, dpctl_show, DP_RO },
|
2017-06-23 13:28:20 +01:00
|
|
|
|
{ "dump-flows", "[dp] [filter=..] [type=..]",
|
|
|
|
|
0, 3, dpctl_dump_flows, DP_RO },
|
2016-12-06 11:40:29 -08:00
|
|
|
|
{ "add-flow", "[dp] flow actions", 2, 3, dpctl_add_flow, DP_RW },
|
|
|
|
|
{ "mod-flow", "[dp] flow actions", 2, 3, dpctl_mod_flow, DP_RW },
|
|
|
|
|
{ "get-flow", "[dp] ufid", 1, 2, dpctl_get_flow, DP_RO },
|
|
|
|
|
{ "del-flow", "[dp] flow", 1, 2, dpctl_del_flow, DP_RW },
|
2016-08-15 18:47:29 +00:00
|
|
|
|
{ "del-flows", "[dp]", 0, 1, dpctl_del_flows, DP_RW },
|
|
|
|
|
{ "dump-conntrack", "[dp] [zone=N]", 0, 2, dpctl_dump_conntrack, DP_RO },
|
dpctl: Support flush conntrack by conntrack 5-tuple
With this patch, "flush-conntrack" in ovs-dpctl and ovs-appctl accept
a conntrack 5-tuple to delete the conntrack entry specified by the 5-tuple.
For example, user can use the following command to flush a conntrack entry
in zone 5.
$ ovs-dpctl flush-conntrack zone=5 \
'ct_nw_src=10.1.1.2,ct_nw_dst=10.1.1.1,ct_nw_proto=17,ct_tp_src=2,ct_tp_dst=1'
$ ovs-appctl dpctl/flush-conntrack zone=5 \
'ct_nw_src=10.1.1.2,ct_nw_dst=10.1.1.1,ct_nw_proto=17,ct_tp_src=2,ct_tp_dst=1'
VMWare-BZ: #1983178
Signed-off-by: Yi-Hung Wei <yihung.wei@gmail.com>
Signed-off-by: Justin Pettit <jpettit@ovn.org>
2017-12-07 10:40:04 -08:00
|
|
|
|
{ "flush-conntrack", "[dp] [zone=N] [ct-tuple]", 0, 3,
|
|
|
|
|
dpctl_flush_conntrack, DP_RW },
|
2018-06-13 18:29:49 -07:00
|
|
|
|
{ "ct-stats-show", "[dp] [zone=N]",
|
2017-06-23 13:28:21 +01:00
|
|
|
|
0, 3, dpctl_ct_stats_show, DP_RO },
|
2017-08-01 20:12:03 -07:00
|
|
|
|
{ "ct-bkts", "[dp] [gt=N]", 0, 2, dpctl_ct_bkts, DP_RO },
|
2018-01-08 15:18:42 -08:00
|
|
|
|
{ "ct-set-maxconns", "[dp] maxconns", 1, 2, dpctl_ct_set_maxconns, DP_RW },
|
|
|
|
|
{ "ct-get-maxconns", "[dp]", 0, 1, dpctl_ct_get_maxconns, DP_RO },
|
2018-01-08 15:18:43 -08:00
|
|
|
|
{ "ct-get-nconns", "[dp]", 0, 1, dpctl_ct_get_nconns, DP_RO },
|
2016-08-15 18:47:29 +00:00
|
|
|
|
{ "help", "", 0, INT_MAX, dpctl_help, DP_RO },
|
|
|
|
|
{ "list-commands", "", 0, INT_MAX, dpctl_list_commands, DP_RO },
|
2014-07-17 17:26:00 -07:00
|
|
|
|
|
|
|
|
|
/* Undocumented commands for testing. */
|
2016-08-15 18:47:29 +00:00
|
|
|
|
{ "parse-actions", "actions", 1, INT_MAX, dpctl_parse_actions, DP_RO },
|
2017-06-23 13:28:20 +01:00
|
|
|
|
{ "normalize-actions", "actions",
|
|
|
|
|
2, INT_MAX, dpctl_normalize_actions, DP_RO },
|
2014-07-17 17:26:00 -07:00
|
|
|
|
|
2016-08-15 18:47:29 +00:00
|
|
|
|
{ NULL, NULL, 0, 0, NULL, DP_RO },
|
2014-07-17 17:26:00 -07:00
|
|
|
|
};
|
|
|
|
|
|
2014-10-16 13:26:07 -07:00
|
|
|
|
static const struct dpctl_command *get_all_dpctl_commands(void)
|
|
|
|
|
{
|
|
|
|
|
return all_commands;
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-17 17:26:00 -07:00
|
|
|
|
/* Runs the command designated by argv[0] within the command table specified by
|
|
|
|
|
* 'commands', which must be terminated by a command whose 'name' member is a
|
|
|
|
|
* null pointer. */
|
|
|
|
|
int
|
|
|
|
|
dpctl_run_command(int argc, const char *argv[], struct dpctl_params *dpctl_p)
|
|
|
|
|
{
|
|
|
|
|
const struct dpctl_command *p;
|
|
|
|
|
if (argc < 1) {
|
|
|
|
|
dpctl_error(dpctl_p, 0, "missing command name; use --help for help");
|
|
|
|
|
return EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (p = all_commands; p->name != NULL; p++) {
|
|
|
|
|
if (!strcmp(p->name, argv[0])) {
|
|
|
|
|
int n_arg = argc - 1;
|
|
|
|
|
if (n_arg < p->min_args) {
|
|
|
|
|
dpctl_error(dpctl_p, 0,
|
|
|
|
|
"'%s' command requires at least %d arguments",
|
|
|
|
|
p->name, p->min_args);
|
|
|
|
|
return EINVAL;
|
|
|
|
|
} else if (n_arg > p->max_args) {
|
|
|
|
|
dpctl_error(dpctl_p, 0,
|
|
|
|
|
"'%s' command takes at most %d arguments",
|
|
|
|
|
p->name, p->max_args);
|
|
|
|
|
return EINVAL;
|
|
|
|
|
} else {
|
2016-08-15 18:47:29 +00:00
|
|
|
|
if (p->mode == DP_RW && dpctl_p->read_only) {
|
|
|
|
|
dpctl_error(dpctl_p, 0,
|
|
|
|
|
"'%s' command does not work in read only mode",
|
|
|
|
|
p->name);
|
|
|
|
|
return EINVAL;
|
|
|
|
|
}
|
2014-07-17 17:26:00 -07:00
|
|
|
|
return p->handler(argc, argv, dpctl_p);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dpctl_error(dpctl_p, 0, "unknown command '%s'; use --help for help",
|
|
|
|
|
argv[0]);
|
|
|
|
|
return EINVAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
dpctl_unixctl_print(void *userdata, bool error OVS_UNUSED, const char *msg)
|
|
|
|
|
{
|
|
|
|
|
struct ds *ds = userdata;
|
|
|
|
|
ds_put_cstr(ds, msg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
dpctl_unixctl_handler(struct unixctl_conn *conn, int argc, const char *argv[],
|
|
|
|
|
void *aux)
|
|
|
|
|
{
|
|
|
|
|
struct ds ds = DS_EMPTY_INITIALIZER;
|
2015-04-15 11:13:04 -07:00
|
|
|
|
bool error = false;
|
2014-07-17 17:26:00 -07:00
|
|
|
|
|
2015-10-17 14:24:01 -07:00
|
|
|
|
struct dpctl_params dpctl_p = {
|
|
|
|
|
.is_appctl = true,
|
|
|
|
|
.output = dpctl_unixctl_print,
|
|
|
|
|
.aux = &ds,
|
|
|
|
|
};
|
2014-07-17 17:26:00 -07:00
|
|
|
|
|
|
|
|
|
/* Parse options (like getopt). Unfortunately it does
|
|
|
|
|
* not seem a good idea to call getopt_long() here, since it uses global
|
|
|
|
|
* variables */
|
2017-06-18 09:46:54 +08:00
|
|
|
|
bool set_names = false;
|
2015-04-15 11:13:04 -07:00
|
|
|
|
while (argc > 1 && !error) {
|
2014-07-17 17:26:00 -07:00
|
|
|
|
const char *arg = argv[1];
|
|
|
|
|
if (!strncmp(arg, "--", 2)) {
|
|
|
|
|
/* Long option */
|
|
|
|
|
if (!strcmp(arg, "--statistics")) {
|
|
|
|
|
dpctl_p.print_statistics = true;
|
|
|
|
|
} else if (!strcmp(arg, "--clear")) {
|
|
|
|
|
dpctl_p.zero_statistics = true;
|
|
|
|
|
} else if (!strcmp(arg, "--may-create")) {
|
|
|
|
|
dpctl_p.may_create = true;
|
|
|
|
|
} else if (!strcmp(arg, "--more")) {
|
|
|
|
|
dpctl_p.verbosity++;
|
2017-06-18 09:46:54 +08:00
|
|
|
|
} else if (!strcmp(arg, "--names")) {
|
|
|
|
|
dpctl_p.names = true;
|
|
|
|
|
set_names = true;
|
|
|
|
|
} else if (!strcmp(arg, "--no-names")) {
|
|
|
|
|
dpctl_p.names = false;
|
|
|
|
|
set_names = true;
|
2014-07-17 17:26:00 -07:00
|
|
|
|
} else {
|
|
|
|
|
ds_put_format(&ds, "Unrecognized option %s", argv[1]);
|
2015-04-15 11:13:04 -07:00
|
|
|
|
error = true;
|
2014-07-17 17:26:00 -07:00
|
|
|
|
}
|
|
|
|
|
} else if (arg[0] == '-' && arg[1] != '\0') {
|
|
|
|
|
/* Short option[s] */
|
|
|
|
|
const char *opt = &arg[1];
|
|
|
|
|
|
2015-04-15 11:13:04 -07:00
|
|
|
|
while (*opt && !error) {
|
2014-07-17 17:26:00 -07:00
|
|
|
|
switch (*opt) {
|
|
|
|
|
case 'm':
|
|
|
|
|
dpctl_p.verbosity++;
|
|
|
|
|
break;
|
|
|
|
|
case 's':
|
|
|
|
|
dpctl_p.print_statistics = true;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
ds_put_format(&ds, "Unrecognized option -%c", *opt);
|
2015-04-15 11:13:04 -07:00
|
|
|
|
error = true;
|
2014-07-17 17:26:00 -07:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
opt++;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
/* Doesn't start with -, not an option */
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-15 11:13:04 -07:00
|
|
|
|
if (error) {
|
2014-07-17 17:26:00 -07:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
argv++;
|
|
|
|
|
argc--;
|
|
|
|
|
}
|
2017-06-18 09:46:54 +08:00
|
|
|
|
if (!set_names) {
|
|
|
|
|
dpctl_p.names = dpctl_p.verbosity > 0;
|
|
|
|
|
}
|
|
|
|
|
VLOG_INFO("set_names=%d verbosity=%d names=%d", set_names,
|
|
|
|
|
dpctl_p.verbosity, dpctl_p.names);
|
2014-07-17 17:26:00 -07:00
|
|
|
|
|
2015-04-15 11:13:04 -07:00
|
|
|
|
if (!error) {
|
2015-10-17 14:24:01 -07:00
|
|
|
|
dpctl_command_handler *handler = (dpctl_command_handler *) aux;
|
2015-04-15 11:13:04 -07:00
|
|
|
|
error = handler(argc, argv, &dpctl_p) != 0;
|
2014-07-17 17:26:00 -07:00
|
|
|
|
}
|
|
|
|
|
|
2015-04-15 11:13:04 -07:00
|
|
|
|
if (error) {
|
|
|
|
|
unixctl_command_reply_error(conn, ds_cstr(&ds));
|
|
|
|
|
} else {
|
|
|
|
|
unixctl_command_reply(conn, ds_cstr(&ds));
|
|
|
|
|
}
|
2014-07-17 17:26:00 -07:00
|
|
|
|
|
|
|
|
|
ds_destroy(&ds);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
dpctl_unixctl_register(void)
|
|
|
|
|
{
|
|
|
|
|
const struct dpctl_command *p;
|
|
|
|
|
|
|
|
|
|
for (p = all_commands; p->name != NULL; p++) {
|
2015-10-17 14:24:01 -07:00
|
|
|
|
if (strcmp(p->name, "help")) {
|
|
|
|
|
char *cmd_name = xasprintf("dpctl/%s", p->name);
|
2016-12-06 01:01:21 -08:00
|
|
|
|
unixctl_command_register(cmd_name,
|
|
|
|
|
p->usage,
|
|
|
|
|
p->min_args,
|
|
|
|
|
p->max_args,
|
|
|
|
|
dpctl_unixctl_handler,
|
|
|
|
|
p->handler);
|
2015-10-17 14:24:01 -07:00
|
|
|
|
free(cmd_name);
|
|
|
|
|
}
|
2014-07-17 17:26:00 -07:00
|
|
|
|
}
|
|
|
|
|
}
|