2009-07-08 13:19:16 -07:00
|
|
|
|
/*
|
2013-05-10 08:55:25 -07:00
|
|
|
|
* Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013 Nicira, Inc.
|
2009-07-08 13:19:16 -07:00
|
|
|
|
*
|
2009-06-15 15:11:30 -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:
|
2009-07-08 13:19:16 -07:00
|
|
|
|
*
|
2009-06-15 15:11:30 -07:00
|
|
|
|
* 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.
|
2009-07-08 13:19:16 -07:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <config.h>
|
|
|
|
|
#include <arpa/inet.h>
|
|
|
|
|
#include <errno.h>
|
|
|
|
|
#include <getopt.h>
|
|
|
|
|
#include <inttypes.h>
|
2010-05-26 10:05:19 -07:00
|
|
|
|
#include <sys/socket.h>
|
2009-07-08 13:19:16 -07:00
|
|
|
|
#include <net/if.h>
|
|
|
|
|
#include <netinet/in.h>
|
|
|
|
|
#include <signal.h>
|
|
|
|
|
#include <stdarg.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
#include <sys/stat.h>
|
|
|
|
|
#include <sys/time.h>
|
|
|
|
|
|
|
|
|
|
#include "command-line.h"
|
|
|
|
|
#include "compiler.h"
|
|
|
|
|
#include "dirs.h"
|
|
|
|
|
#include "dpif.h"
|
|
|
|
|
#include "dynamic-string.h"
|
2011-11-17 10:24:05 -08:00
|
|
|
|
#include "flow.h"
|
2009-07-08 13:19:16 -07:00
|
|
|
|
#include "netdev.h"
|
2011-11-17 10:24:05 -08:00
|
|
|
|
#include "netlink.h"
|
2009-07-08 13:19:16 -07:00
|
|
|
|
#include "odp-util.h"
|
2011-11-11 15:22:56 -08:00
|
|
|
|
#include "ofpbuf.h"
|
2011-11-17 10:24:05 -08:00
|
|
|
|
#include "packets.h"
|
2010-12-03 14:41:38 -08:00
|
|
|
|
#include "shash.h"
|
2012-05-22 10:32:02 -07:00
|
|
|
|
#include "simap.h"
|
2012-05-22 03:47:36 -07:00
|
|
|
|
#include "smap.h"
|
2011-03-25 13:00:13 -07:00
|
|
|
|
#include "sset.h"
|
2009-07-08 13:19:16 -07:00
|
|
|
|
#include "timeval.h"
|
|
|
|
|
#include "util.h"
|
|
|
|
|
#include "vlog.h"
|
2010-07-16 11:02:49 -07:00
|
|
|
|
|
2013-03-15 13:47:53 -07:00
|
|
|
|
/* -s, --statistics: Print port/flow statistics? */
|
2011-05-04 13:58:10 -07:00
|
|
|
|
static bool print_statistics;
|
2011-04-28 13:02:15 -07:00
|
|
|
|
|
2013-03-15 13:47:53 -07:00
|
|
|
|
/* --clear: Reset existing statistics to zero when modifying a flow? */
|
|
|
|
|
static bool zero_statistics;
|
|
|
|
|
|
|
|
|
|
/* --may-create: Allow mod-flows command to create a new flow? */
|
|
|
|
|
static bool may_create;
|
|
|
|
|
|
2013-09-23 13:02:10 -07:00
|
|
|
|
/* -m, --more: Increase output verbosity. */
|
2011-11-17 10:24:05 -08:00
|
|
|
|
static int verbosity;
|
|
|
|
|
|
2013-07-19 10:04:47 -07:00
|
|
|
|
static const struct command *get_all_commands(void);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
|
|
|
|
static void usage(void) NO_RETURN;
|
|
|
|
|
static void parse_options(int argc, char *argv[]);
|
|
|
|
|
|
2009-10-23 11:49:39 -07:00
|
|
|
|
int
|
|
|
|
|
main(int argc, char *argv[])
|
2009-07-08 13:19:16 -07:00
|
|
|
|
{
|
|
|
|
|
set_program_name(argv[0]);
|
|
|
|
|
parse_options(argc, argv);
|
|
|
|
|
signal(SIGPIPE, SIG_IGN);
|
2013-07-19 10:04:47 -07:00
|
|
|
|
run_command(argc - optind, argv + optind, get_all_commands());
|
2009-07-08 13:19:16 -07:00
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
parse_options(int argc, char *argv[])
|
|
|
|
|
{
|
2010-01-04 18:55:00 -08:00
|
|
|
|
enum {
|
2013-03-15 13:47:53 -07:00
|
|
|
|
OPT_CLEAR = UCHAR_MAX + 1,
|
|
|
|
|
OPT_MAY_CREATE,
|
2010-01-04 18:55:00 -08:00
|
|
|
|
VLOG_OPTION_ENUMS
|
|
|
|
|
};
|
2013-04-23 16:40:56 -07:00
|
|
|
|
static const struct option long_options[] = {
|
2011-05-04 13:49:42 -07:00
|
|
|
|
{"statistics", no_argument, NULL, 's'},
|
2013-03-15 13:47:53 -07:00
|
|
|
|
{"clear", no_argument, NULL, OPT_CLEAR},
|
|
|
|
|
{"may-create", no_argument, NULL, OPT_MAY_CREATE},
|
2011-11-17 10:24:05 -08:00
|
|
|
|
{"more", no_argument, NULL, 'm'},
|
2011-05-04 13:49:42 -07:00
|
|
|
|
{"timeout", required_argument, NULL, 't'},
|
|
|
|
|
{"help", no_argument, NULL, 'h'},
|
|
|
|
|
{"version", no_argument, NULL, 'V'},
|
2010-01-04 18:55:00 -08:00
|
|
|
|
VLOG_LONG_OPTIONS,
|
2011-05-04 13:49:42 -07:00
|
|
|
|
{NULL, 0, NULL, 0},
|
2009-07-08 13:19:16 -07:00
|
|
|
|
};
|
|
|
|
|
char *short_options = long_options_to_short_options(long_options);
|
|
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
|
unsigned long int timeout;
|
|
|
|
|
int c;
|
|
|
|
|
|
|
|
|
|
c = getopt_long(argc, argv, short_options, long_options, NULL);
|
|
|
|
|
if (c == -1) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (c) {
|
2011-04-28 13:02:15 -07:00
|
|
|
|
case 's':
|
|
|
|
|
print_statistics = true;
|
|
|
|
|
break;
|
|
|
|
|
|
2013-03-15 13:47:53 -07:00
|
|
|
|
case OPT_CLEAR:
|
|
|
|
|
zero_statistics = true;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case OPT_MAY_CREATE:
|
|
|
|
|
may_create = true;
|
|
|
|
|
break;
|
|
|
|
|
|
2011-11-17 10:24:05 -08:00
|
|
|
|
case 'm':
|
|
|
|
|
verbosity++;
|
|
|
|
|
break;
|
|
|
|
|
|
2009-07-08 13:19:16 -07:00
|
|
|
|
case 't':
|
|
|
|
|
timeout = strtoul(optarg, NULL, 10);
|
|
|
|
|
if (timeout <= 0) {
|
|
|
|
|
ovs_fatal(0, "value %s on -t or --timeout is not at least 1",
|
|
|
|
|
optarg);
|
|
|
|
|
} else {
|
|
|
|
|
time_alarm(timeout);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'h':
|
|
|
|
|
usage();
|
|
|
|
|
|
|
|
|
|
case 'V':
|
2011-08-02 12:16:44 -07:00
|
|
|
|
ovs_print_version(0, 0);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
exit(EXIT_SUCCESS);
|
|
|
|
|
|
2010-01-04 18:55:00 -08:00
|
|
|
|
VLOG_OPTION_HANDLERS
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
|
|
|
|
case '?':
|
|
|
|
|
exit(EXIT_FAILURE);
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
abort();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
free(short_options);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
usage(void)
|
|
|
|
|
{
|
|
|
|
|
printf("%s: Open vSwitch datapath management utility\n"
|
|
|
|
|
"usage: %s [OPTIONS] COMMAND [ARG...]\n"
|
|
|
|
|
" add-dp DP [IFACE...] add new datapath DP (with IFACEs)\n"
|
|
|
|
|
" del-dp DP delete local datapath DP\n"
|
|
|
|
|
" add-if DP IFACE... add each IFACE as a port on DP\n"
|
2011-10-17 11:24:37 -07:00
|
|
|
|
" set-if DP IFACE... reconfigure each IFACE within DP\n"
|
2009-07-08 13:19:16 -07:00
|
|
|
|
" del-if DP IFACE... delete each IFACE from DP\n"
|
2009-08-04 15:13:40 -07:00
|
|
|
|
" dump-dps display names of all datapaths\n"
|
2009-07-08 13:19:16 -07:00
|
|
|
|
" show show basic info on all datapaths\n"
|
|
|
|
|
" show DP... show basic info on each DP\n"
|
2013-09-24 10:23:49 -07:00
|
|
|
|
" dump-flows [DP] display flows in DP\n"
|
|
|
|
|
" add-flow [DP] FLOW ACTIONS add FLOW with ACTIONS to DP\n"
|
|
|
|
|
" mod-flow [DP] FLOW ACTIONS change FLOW actions to ACTIONS in DP\n"
|
|
|
|
|
" del-flow [DP] FLOW delete FLOW from DP\n"
|
|
|
|
|
" del-flows [DP] delete all flows from DP\n"
|
2011-10-17 11:24:37 -07:00
|
|
|
|
"Each IFACE on add-dp, add-if, and set-if may be followed by\n"
|
|
|
|
|
"comma-separated options. See ovs-dpctl(8) for syntax, or the\n"
|
2013-09-24 10:23:49 -07:00
|
|
|
|
"Interface table in ovs-vswitchd.conf.db(5) for an options list.\n"
|
|
|
|
|
"For COMMAND dump-flows, add-flow, mod-flow, del-flow and\n"
|
|
|
|
|
"del-flows, DP is optional if there is only one datapath.\n",
|
2009-07-08 13:19:16 -07:00
|
|
|
|
program_name, program_name);
|
|
|
|
|
vlog_usage();
|
2013-03-15 13:47:53 -07:00
|
|
|
|
printf("\nOptions for show and mod-flow:\n"
|
|
|
|
|
" -s, --statistics print statistics for port or flow\n"
|
2013-08-03 12:23:14 -07:00
|
|
|
|
"\nOptions for dump-flows:\n"
|
|
|
|
|
" -m, --more increase verbosity of output\n"
|
2013-03-15 13:47:53 -07:00
|
|
|
|
"\nOptions for mod-flow:\n"
|
|
|
|
|
" --may-create create flow if it doesn't exist\n"
|
|
|
|
|
" --clear reset existing stats to zero\n"
|
2012-11-29 16:14:53 -08:00
|
|
|
|
"\nOther options:\n"
|
2009-07-08 13:19:16 -07:00
|
|
|
|
" -t, --timeout=SECS give up after SECS seconds\n"
|
|
|
|
|
" -h, --help display this help message\n"
|
|
|
|
|
" -V, --version display version information\n");
|
|
|
|
|
exit(EXIT_SUCCESS);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void run(int retval, const char *message, ...)
|
|
|
|
|
PRINTF_FORMAT(2, 3);
|
|
|
|
|
|
|
|
|
|
static void run(int retval, const char *message, ...)
|
|
|
|
|
{
|
|
|
|
|
if (retval) {
|
|
|
|
|
va_list args;
|
|
|
|
|
|
|
|
|
|
va_start(args, message);
|
2011-03-31 14:50:58 -07:00
|
|
|
|
ovs_fatal_valist(retval, message, args);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-07-08 04:38:36 -07:00
|
|
|
|
static void dpctl_add_if(int argc, char *argv[]);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
|
|
|
|
static int if_up(const char *netdev_name)
|
|
|
|
|
{
|
|
|
|
|
struct netdev *netdev;
|
|
|
|
|
int retval;
|
|
|
|
|
|
2011-08-05 14:18:06 -07:00
|
|
|
|
retval = netdev_open(netdev_name, "system", &netdev);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
if (!retval) {
|
2013-05-10 08:55:25 -07:00
|
|
|
|
retval = netdev_turn_flags_on(netdev, NETDEV_UP, NULL);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
netdev_close(netdev);
|
|
|
|
|
}
|
|
|
|
|
return retval;
|
|
|
|
|
}
|
|
|
|
|
|
2012-10-12 17:46:06 -07:00
|
|
|
|
/* Retrieve the name of the datapath if exactly one exists. The caller
|
|
|
|
|
* is responsible for freeing the returned string. If there is not one
|
|
|
|
|
* datapath, aborts with an error message. */
|
|
|
|
|
static char *
|
|
|
|
|
get_one_dp(void)
|
|
|
|
|
{
|
|
|
|
|
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) {
|
|
|
|
|
ovs_fatal(0, "no datapaths exist");
|
|
|
|
|
} else if (count > 1) {
|
|
|
|
|
ovs_fatal(0, "multiple datapaths, specify one");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return dp_name;
|
|
|
|
|
}
|
|
|
|
|
|
2010-01-22 14:37:10 -05:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2009-07-08 13:19:16 -07:00
|
|
|
|
static void
|
2012-07-08 04:38:36 -07:00
|
|
|
|
dpctl_add_dp(int argc OVS_UNUSED, char *argv[])
|
2009-07-08 13:19:16 -07:00
|
|
|
|
{
|
2009-06-16 10:09:10 -07:00
|
|
|
|
struct dpif *dpif;
|
2010-01-22 14:37:10 -05:00
|
|
|
|
run(parsed_dpif_open(argv[1], true, &dpif), "add_dp");
|
2009-06-16 10:09:10 -07:00
|
|
|
|
dpif_close(dpif);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
if (argc > 2) {
|
2012-07-08 04:38:36 -07:00
|
|
|
|
dpctl_add_if(argc, argv);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2012-07-08 04:38:36 -07:00
|
|
|
|
dpctl_del_dp(int argc OVS_UNUSED, char *argv[])
|
2009-07-08 13:19:16 -07:00
|
|
|
|
{
|
2009-06-16 10:09:10 -07:00
|
|
|
|
struct dpif *dpif;
|
2010-01-22 14:37:10 -05:00
|
|
|
|
run(parsed_dpif_open(argv[1], false, &dpif), "opening datapath");
|
2009-06-16 10:09:10 -07:00
|
|
|
|
run(dpif_delete(dpif), "del_dp");
|
|
|
|
|
dpif_close(dpif);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2012-07-08 04:38:36 -07:00
|
|
|
|
dpctl_add_if(int argc OVS_UNUSED, char *argv[])
|
2009-07-08 13:19:16 -07:00
|
|
|
|
{
|
|
|
|
|
bool failure = false;
|
2009-06-16 10:09:10 -07:00
|
|
|
|
struct dpif *dpif;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
int i;
|
|
|
|
|
|
2010-01-22 14:37:10 -05:00
|
|
|
|
run(parsed_dpif_open(argv[1], false, &dpif), "opening datapath");
|
2009-07-08 13:19:16 -07:00
|
|
|
|
for (i = 2; i < argc; i++) {
|
2011-08-05 14:18:06 -07:00
|
|
|
|
const char *name, *type;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
char *save_ptr = NULL;
|
netdev: Decouple creating and configuring network devices.
Until now, each call to netdev_open() for a particular network device
had to either specify a set of network device arguments that was either
empty or (for devices that already existed) equal to the existing device's
configuration. Unfortunately, the definition of "equality" in the latter
case was mostly done in terms of strict equality of string-to-string maps,
which caused problems in cases where, for example, one set of arguments
specified the default value of an optional argument explicitly and the
other omitted it.
The netdev interface does have provisions for defining equality other ways,
but this had only been done in one case that was especially problematic in
practice. One way to solve this particular problem would be to carefully
define equality in all the problematic cases.
This commit takes another approach based on the realization that there is
really no need to do any comparisons. Instead, it removes configuration
at netdev_open() time entirely, because almost all of netdev_open()'s
callers are not interested in creating and configuring a netdev. Most of
them just want to open a configured device and use it. Therefore, this
commit stops providing any configuration arguments to netdev_open() and the
provider functions that it calls. Instead, a caller that does want to
configure a device does so after it opens it, by calling
netdev_set_config().
This change allows us to simplify the netdev interface a bit. There is no
longer any need to implement argument comparisons. As a result, there is
also no need for "struct netdev_dev" to keep track of configuration at all.
Instead, the network devices that have configuration keep track of it in
their own internal form.
This new interface does mean that it becomes possible to accidentally
create and try to use an unconfigured netdev that requires configuration.
Bug #6677.
Reported-by: Paul Ingram <paul@nicira.com>
2011-08-08 12:49:17 -07:00
|
|
|
|
struct netdev *netdev = NULL;
|
2012-05-22 03:47:36 -07:00
|
|
|
|
struct smap args;
|
2013-06-19 16:58:44 -07:00
|
|
|
|
odp_port_t port_no = ODPP_NONE;
|
2010-12-03 14:41:38 -08:00
|
|
|
|
char *option;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
int error;
|
|
|
|
|
|
2011-08-05 14:18:06 -07:00
|
|
|
|
name = strtok_r(argv[i], ",", &save_ptr);
|
|
|
|
|
type = "system";
|
2010-12-03 14:41:38 -08:00
|
|
|
|
|
2011-08-05 14:18:06 -07:00
|
|
|
|
if (!name) {
|
2009-07-08 13:19:16 -07:00
|
|
|
|
ovs_error(0, "%s is not a valid network device name", argv[i]);
|
2011-10-17 11:24:37 -07:00
|
|
|
|
failure = true;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2012-05-22 03:47:36 -07:00
|
|
|
|
smap_init(&args);
|
2011-03-04 12:46:37 -08:00
|
|
|
|
while ((option = strtok_r(NULL, ",", &save_ptr)) != NULL) {
|
2010-12-03 14:41:38 -08:00
|
|
|
|
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")) {
|
2011-08-05 14:18:06 -07:00
|
|
|
|
type = value;
|
2012-07-27 23:59:23 -07:00
|
|
|
|
} else if (!strcmp(key, "port_no")) {
|
2013-06-19 16:58:44 -07:00
|
|
|
|
port_no = u32_to_odp(atoi(value));
|
2012-05-22 03:47:36 -07:00
|
|
|
|
} else if (!smap_add_once(&args, key, value)) {
|
2010-12-03 14:41:38 -08:00
|
|
|
|
ovs_error(0, "duplicate \"%s\" option", key);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-08-05 14:18:06 -07:00
|
|
|
|
error = netdev_open(name, type, &netdev);
|
2010-12-03 14:41:38 -08:00
|
|
|
|
if (error) {
|
2011-08-05 14:18:06 -07:00
|
|
|
|
ovs_error(error, "%s: failed to open network device", name);
|
netdev: Decouple creating and configuring network devices.
Until now, each call to netdev_open() for a particular network device
had to either specify a set of network device arguments that was either
empty or (for devices that already existed) equal to the existing device's
configuration. Unfortunately, the definition of "equality" in the latter
case was mostly done in terms of strict equality of string-to-string maps,
which caused problems in cases where, for example, one set of arguments
specified the default value of an optional argument explicitly and the
other omitted it.
The netdev interface does have provisions for defining equality other ways,
but this had only been done in one case that was especially problematic in
practice. One way to solve this particular problem would be to carefully
define equality in all the problematic cases.
This commit takes another approach based on the realization that there is
really no need to do any comparisons. Instead, it removes configuration
at netdev_open() time entirely, because almost all of netdev_open()'s
callers are not interested in creating and configuring a netdev. Most of
them just want to open a configured device and use it. Therefore, this
commit stops providing any configuration arguments to netdev_open() and the
provider functions that it calls. Instead, a caller that does want to
configure a device does so after it opens it, by calling
netdev_set_config().
This change allows us to simplify the netdev interface a bit. There is no
longer any need to implement argument comparisons. As a result, there is
also no need for "struct netdev_dev" to keep track of configuration at all.
Instead, the network devices that have configuration keep track of it in
their own internal form.
This new interface does mean that it becomes possible to accidentally
create and try to use an unconfigured netdev that requires configuration.
Bug #6677.
Reported-by: Paul Ingram <paul@nicira.com>
2011-08-08 12:49:17 -07:00
|
|
|
|
goto next;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
error = netdev_set_config(netdev, &args);
|
|
|
|
|
if (error) {
|
2011-08-05 14:18:06 -07:00
|
|
|
|
ovs_error(error, "%s: failed to configure network device", name);
|
netdev: Decouple creating and configuring network devices.
Until now, each call to netdev_open() for a particular network device
had to either specify a set of network device arguments that was either
empty or (for devices that already existed) equal to the existing device's
configuration. Unfortunately, the definition of "equality" in the latter
case was mostly done in terms of strict equality of string-to-string maps,
which caused problems in cases where, for example, one set of arguments
specified the default value of an optional argument explicitly and the
other omitted it.
The netdev interface does have provisions for defining equality other ways,
but this had only been done in one case that was especially problematic in
practice. One way to solve this particular problem would be to carefully
define equality in all the problematic cases.
This commit takes another approach based on the realization that there is
really no need to do any comparisons. Instead, it removes configuration
at netdev_open() time entirely, because almost all of netdev_open()'s
callers are not interested in creating and configuring a netdev. Most of
them just want to open a configured device and use it. Therefore, this
commit stops providing any configuration arguments to netdev_open() and the
provider functions that it calls. Instead, a caller that does want to
configure a device does so after it opens it, by calling
netdev_set_config().
This change allows us to simplify the netdev interface a bit. There is no
longer any need to implement argument comparisons. As a result, there is
also no need for "struct netdev_dev" to keep track of configuration at all.
Instead, the network devices that have configuration keep track of it in
their own internal form.
This new interface does mean that it becomes possible to accidentally
create and try to use an unconfigured netdev that requires configuration.
Bug #6677.
Reported-by: Paul Ingram <paul@nicira.com>
2011-08-08 12:49:17 -07:00
|
|
|
|
goto next;
|
2010-12-03 14:41:38 -08:00
|
|
|
|
}
|
netdev: Decouple creating and configuring network devices.
Until now, each call to netdev_open() for a particular network device
had to either specify a set of network device arguments that was either
empty or (for devices that already existed) equal to the existing device's
configuration. Unfortunately, the definition of "equality" in the latter
case was mostly done in terms of strict equality of string-to-string maps,
which caused problems in cases where, for example, one set of arguments
specified the default value of an optional argument explicitly and the
other omitted it.
The netdev interface does have provisions for defining equality other ways,
but this had only been done in one case that was especially problematic in
practice. One way to solve this particular problem would be to carefully
define equality in all the problematic cases.
This commit takes another approach based on the realization that there is
really no need to do any comparisons. Instead, it removes configuration
at netdev_open() time entirely, because almost all of netdev_open()'s
callers are not interested in creating and configuring a netdev. Most of
them just want to open a configured device and use it. Therefore, this
commit stops providing any configuration arguments to netdev_open() and the
provider functions that it calls. Instead, a caller that does want to
configure a device does so after it opens it, by calling
netdev_set_config().
This change allows us to simplify the netdev interface a bit. There is no
longer any need to implement argument comparisons. As a result, there is
also no need for "struct netdev_dev" to keep track of configuration at all.
Instead, the network devices that have configuration keep track of it in
their own internal form.
This new interface does mean that it becomes possible to accidentally
create and try to use an unconfigured netdev that requires configuration.
Bug #6677.
Reported-by: Paul Ingram <paul@nicira.com>
2011-08-08 12:49:17 -07:00
|
|
|
|
|
2012-07-27 23:59:23 -07:00
|
|
|
|
error = dpif_port_add(dpif, netdev, &port_no);
|
netdev: Decouple creating and configuring network devices.
Until now, each call to netdev_open() for a particular network device
had to either specify a set of network device arguments that was either
empty or (for devices that already existed) equal to the existing device's
configuration. Unfortunately, the definition of "equality" in the latter
case was mostly done in terms of strict equality of string-to-string maps,
which caused problems in cases where, for example, one set of arguments
specified the default value of an optional argument explicitly and the
other omitted it.
The netdev interface does have provisions for defining equality other ways,
but this had only been done in one case that was especially problematic in
practice. One way to solve this particular problem would be to carefully
define equality in all the problematic cases.
This commit takes another approach based on the realization that there is
really no need to do any comparisons. Instead, it removes configuration
at netdev_open() time entirely, because almost all of netdev_open()'s
callers are not interested in creating and configuring a netdev. Most of
them just want to open a configured device and use it. Therefore, this
commit stops providing any configuration arguments to netdev_open() and the
provider functions that it calls. Instead, a caller that does want to
configure a device does so after it opens it, by calling
netdev_set_config().
This change allows us to simplify the netdev interface a bit. There is no
longer any need to implement argument comparisons. As a result, there is
also no need for "struct netdev_dev" to keep track of configuration at all.
Instead, the network devices that have configuration keep track of it in
their own internal form.
This new interface does mean that it becomes possible to accidentally
create and try to use an unconfigured netdev that requires configuration.
Bug #6677.
Reported-by: Paul Ingram <paul@nicira.com>
2011-08-08 12:49:17 -07:00
|
|
|
|
if (error) {
|
2011-08-05 14:18:06 -07:00
|
|
|
|
ovs_error(error, "adding %s to %s failed", name, argv[1]);
|
netdev: Decouple creating and configuring network devices.
Until now, each call to netdev_open() for a particular network device
had to either specify a set of network device arguments that was either
empty or (for devices that already existed) equal to the existing device's
configuration. Unfortunately, the definition of "equality" in the latter
case was mostly done in terms of strict equality of string-to-string maps,
which caused problems in cases where, for example, one set of arguments
specified the default value of an optional argument explicitly and the
other omitted it.
The netdev interface does have provisions for defining equality other ways,
but this had only been done in one case that was especially problematic in
practice. One way to solve this particular problem would be to carefully
define equality in all the problematic cases.
This commit takes another approach based on the realization that there is
really no need to do any comparisons. Instead, it removes configuration
at netdev_open() time entirely, because almost all of netdev_open()'s
callers are not interested in creating and configuring a netdev. Most of
them just want to open a configured device and use it. Therefore, this
commit stops providing any configuration arguments to netdev_open() and the
provider functions that it calls. Instead, a caller that does want to
configure a device does so after it opens it, by calling
netdev_set_config().
This change allows us to simplify the netdev interface a bit. There is no
longer any need to implement argument comparisons. As a result, there is
also no need for "struct netdev_dev" to keep track of configuration at all.
Instead, the network devices that have configuration keep track of it in
their own internal form.
This new interface does mean that it becomes possible to accidentally
create and try to use an unconfigured netdev that requires configuration.
Bug #6677.
Reported-by: Paul Ingram <paul@nicira.com>
2011-08-08 12:49:17 -07:00
|
|
|
|
goto next;
|
|
|
|
|
}
|
|
|
|
|
|
2011-08-05 14:18:06 -07:00
|
|
|
|
error = if_up(name);
|
netdev: Decouple creating and configuring network devices.
Until now, each call to netdev_open() for a particular network device
had to either specify a set of network device arguments that was either
empty or (for devices that already existed) equal to the existing device's
configuration. Unfortunately, the definition of "equality" in the latter
case was mostly done in terms of strict equality of string-to-string maps,
which caused problems in cases where, for example, one set of arguments
specified the default value of an optional argument explicitly and the
other omitted it.
The netdev interface does have provisions for defining equality other ways,
but this had only been done in one case that was especially problematic in
practice. One way to solve this particular problem would be to carefully
define equality in all the problematic cases.
This commit takes another approach based on the realization that there is
really no need to do any comparisons. Instead, it removes configuration
at netdev_open() time entirely, because almost all of netdev_open()'s
callers are not interested in creating and configuring a netdev. Most of
them just want to open a configured device and use it. Therefore, this
commit stops providing any configuration arguments to netdev_open() and the
provider functions that it calls. Instead, a caller that does want to
configure a device does so after it opens it, by calling
netdev_set_config().
This change allows us to simplify the netdev interface a bit. There is no
longer any need to implement argument comparisons. As a result, there is
also no need for "struct netdev_dev" to keep track of configuration at all.
Instead, the network devices that have configuration keep track of it in
their own internal form.
This new interface does mean that it becomes possible to accidentally
create and try to use an unconfigured netdev that requires configuration.
Bug #6677.
Reported-by: Paul Ingram <paul@nicira.com>
2011-08-08 12:49:17 -07:00
|
|
|
|
|
|
|
|
|
next:
|
|
|
|
|
netdev_close(netdev);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
if (error) {
|
|
|
|
|
failure = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2009-06-16 10:09:10 -07:00
|
|
|
|
dpif_close(dpif);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
if (failure) {
|
|
|
|
|
exit(EXIT_FAILURE);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-10-17 11:24:37 -07:00
|
|
|
|
static void
|
2012-07-08 04:38:36 -07:00
|
|
|
|
dpctl_set_if(int argc, char *argv[])
|
2011-10-17 11:24:37 -07:00
|
|
|
|
{
|
|
|
|
|
bool failure = false;
|
|
|
|
|
struct dpif *dpif;
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
run(parsed_dpif_open(argv[1], false, &dpif), "opening datapath");
|
|
|
|
|
for (i = 2; i < argc; i++) {
|
|
|
|
|
struct netdev *netdev = NULL;
|
|
|
|
|
struct dpif_port dpif_port;
|
|
|
|
|
char *save_ptr = NULL;
|
|
|
|
|
char *type = NULL;
|
|
|
|
|
const char *name;
|
2012-05-22 03:47:36 -07:00
|
|
|
|
struct smap args;
|
2013-06-19 16:58:44 -07:00
|
|
|
|
odp_port_t port_no;
|
2011-10-17 11:24:37 -07:00
|
|
|
|
char *option;
|
|
|
|
|
int error;
|
|
|
|
|
|
|
|
|
|
name = strtok_r(argv[i], ",", &save_ptr);
|
|
|
|
|
if (!name) {
|
|
|
|
|
ovs_error(0, "%s is not a valid network device name", argv[i]);
|
|
|
|
|
failure = true;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Get the port's type from the datapath. */
|
|
|
|
|
error = dpif_port_query_by_name(dpif, name, &dpif_port);
|
|
|
|
|
if (error) {
|
|
|
|
|
ovs_error(error, "%s: failed to query port in %s", name, argv[1]);
|
|
|
|
|
goto next;
|
|
|
|
|
}
|
|
|
|
|
type = xstrdup(dpif_port.type);
|
2012-07-27 23:59:23 -07:00
|
|
|
|
port_no = dpif_port.port_no;
|
2011-10-17 11:24:37 -07:00
|
|
|
|
dpif_port_destroy(&dpif_port);
|
|
|
|
|
|
|
|
|
|
/* Retrieve its existing configuration. */
|
|
|
|
|
error = netdev_open(name, type, &netdev);
|
|
|
|
|
if (error) {
|
|
|
|
|
ovs_error(error, "%s: failed to open network device", name);
|
|
|
|
|
goto next;
|
|
|
|
|
}
|
|
|
|
|
|
2012-05-22 03:47:36 -07:00
|
|
|
|
smap_init(&args);
|
2011-10-17 11:24:37 -07:00
|
|
|
|
error = netdev_get_config(netdev, &args);
|
|
|
|
|
if (error) {
|
|
|
|
|
ovs_error(error, "%s: failed to fetch configuration", name);
|
|
|
|
|
goto next;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 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)) {
|
|
|
|
|
ovs_error(0, "%s: can't change type from %s to %s",
|
|
|
|
|
name, type, value);
|
|
|
|
|
failure = true;
|
|
|
|
|
}
|
2012-07-27 23:59:23 -07:00
|
|
|
|
} else if (!strcmp(key, "port_no")) {
|
2013-06-19 16:58:44 -07:00
|
|
|
|
if (port_no != u32_to_odp(atoi(value))) {
|
2012-07-27 23:59:23 -07:00
|
|
|
|
ovs_error(0, "%s: can't change port number from "
|
|
|
|
|
"%"PRIu32" to %d",
|
|
|
|
|
name, port_no, atoi(value));
|
|
|
|
|
failure = true;
|
|
|
|
|
}
|
2011-10-17 11:24:37 -07:00
|
|
|
|
} else if (value[0] == '\0') {
|
2012-05-22 03:47:36 -07:00
|
|
|
|
smap_remove(&args, key);
|
2011-10-17 11:24:37 -07:00
|
|
|
|
} else {
|
2012-05-22 03:47:36 -07:00
|
|
|
|
smap_replace(&args, key, value);
|
2011-10-17 11:24:37 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Update configuration. */
|
|
|
|
|
error = netdev_set_config(netdev, &args);
|
|
|
|
|
smap_destroy(&args);
|
|
|
|
|
if (error) {
|
|
|
|
|
ovs_error(error, "%s: failed to configure network device", name);
|
|
|
|
|
goto next;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
next:
|
|
|
|
|
free(type);
|
|
|
|
|
netdev_close(netdev);
|
|
|
|
|
if (error) {
|
|
|
|
|
failure = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
dpif_close(dpif);
|
|
|
|
|
if (failure) {
|
|
|
|
|
exit(EXIT_FAILURE);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2009-07-08 13:19:16 -07:00
|
|
|
|
static bool
|
2013-06-19 16:58:44 -07:00
|
|
|
|
get_port_number(struct dpif *dpif, const char *name, odp_port_t *port)
|
2009-07-08 13:19:16 -07:00
|
|
|
|
{
|
2011-01-23 18:48:02 -08:00
|
|
|
|
struct dpif_port dpif_port;
|
2010-12-27 12:46:48 -08:00
|
|
|
|
|
2011-01-23 18:48:02 -08:00
|
|
|
|
if (!dpif_port_query_by_name(dpif, name, &dpif_port)) {
|
|
|
|
|
*port = dpif_port.port_no;
|
|
|
|
|
dpif_port_destroy(&dpif_port);
|
2010-12-27 12:46:48 -08:00
|
|
|
|
return true;
|
|
|
|
|
} else {
|
|
|
|
|
ovs_error(0, "no port named %s", name);
|
|
|
|
|
return false;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2012-07-08 04:38:36 -07:00
|
|
|
|
dpctl_del_if(int argc OVS_UNUSED, char *argv[])
|
2009-07-08 13:19:16 -07:00
|
|
|
|
{
|
|
|
|
|
bool failure = false;
|
2009-06-16 10:09:10 -07:00
|
|
|
|
struct dpif *dpif;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
int i;
|
|
|
|
|
|
2010-01-22 14:37:10 -05:00
|
|
|
|
run(parsed_dpif_open(argv[1], false, &dpif), "opening datapath");
|
2009-07-08 13:19:16 -07:00
|
|
|
|
for (i = 2; i < argc; i++) {
|
|
|
|
|
const char *name = argv[i];
|
2013-06-19 16:58:44 -07:00
|
|
|
|
odp_port_t port;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
int error;
|
|
|
|
|
|
|
|
|
|
if (!name[strspn(name, "0123456789")]) {
|
2013-06-19 16:58:44 -07:00
|
|
|
|
port = u32_to_odp(atoi(name));
|
2009-06-16 10:09:10 -07:00
|
|
|
|
} else if (!get_port_number(dpif, name, &port)) {
|
2009-07-08 13:19:16 -07:00
|
|
|
|
failure = true;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2009-06-16 10:09:10 -07:00
|
|
|
|
error = dpif_port_del(dpif, port);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
if (error) {
|
|
|
|
|
ovs_error(error, "deleting port %s from %s failed", name, argv[1]);
|
|
|
|
|
failure = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2009-06-16 10:09:10 -07:00
|
|
|
|
dpif_close(dpif);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
if (failure) {
|
|
|
|
|
exit(EXIT_FAILURE);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-28 13:02:15 -07:00
|
|
|
|
static void
|
|
|
|
|
print_stat(const char *leader, uint64_t value)
|
|
|
|
|
{
|
|
|
|
|
fputs(leader, stdout);
|
|
|
|
|
if (value != UINT64_MAX) {
|
|
|
|
|
printf("%"PRIu64, value);
|
|
|
|
|
} else {
|
|
|
|
|
putchar('?');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
print_human_size(uint64_t value)
|
|
|
|
|
{
|
|
|
|
|
if (value == UINT64_MAX) {
|
|
|
|
|
/* Nothing to do. */
|
|
|
|
|
} else if (value >= 1024ULL * 1024 * 1024 * 1024) {
|
|
|
|
|
printf(" (%.1f TiB)", value / (1024.0 * 1024 * 1024 * 1024));
|
|
|
|
|
} else if (value >= 1024ULL * 1024 * 1024) {
|
|
|
|
|
printf(" (%.1f GiB)", value / (1024.0 * 1024 * 1024));
|
|
|
|
|
} else if (value >= 1024ULL * 1024) {
|
|
|
|
|
printf(" (%.1f MiB)", value / (1024.0 * 1024));
|
|
|
|
|
} else if (value >= 1024) {
|
|
|
|
|
printf(" (%.1f KiB)", value / 1024.0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2009-07-08 13:19:16 -07:00
|
|
|
|
static void
|
|
|
|
|
show_dpif(struct dpif *dpif)
|
|
|
|
|
{
|
datapath: Change listing ports to use an iterator concept.
One of the goals for Open vSwitch is to decouple kernel and userspace
software, so that either one can be upgraded or rolled back independent of
the other. To do this in full generality, it must be possible to add new
features to the kernel vport layer without changing userspace software. In
turn, that means that the odp_port structure must become variable-length.
This does not, however, fit in well with the ODP_PORT_LIST ioctl in its
current form, because that would require userspace to know how much space
to allocate for each port in advance, or to allocate as much space as
could possibly be needed. Neither choice is very attractive.
This commit prepares for a different solution, by replacing ODP_PORT_LIST
by a new ioctl ODP_VPORT_DUMP that retrieves information about a single
vport from the datapath on each call. It is much cleaner to allocate the
maximum amount of space for a single vport than to do so for possibly a
large number of vports.
It would be faster to retrieve a number of vports in batch instead of just
one at a time, but that will naturally happen later when the kernel
datapath interface is changed to use Netlink, so this patch does not bother
with it.
The Netlink version won't need to take the starting port number from
userspace, since Netlink sockets can keep track of that state as part
of their "dump" feature.
Signed-off-by: Ben Pfaff <blp@nicira.com>
Acked-by: Jesse Gross <jesse@nicira.com>
2011-01-10 13:12:12 -08:00
|
|
|
|
struct dpif_port_dump dump;
|
2011-01-23 18:48:02 -08:00
|
|
|
|
struct dpif_port dpif_port;
|
2011-10-05 11:18:13 -07:00
|
|
|
|
struct dpif_dp_stats stats;
|
2011-09-15 19:36:17 -07:00
|
|
|
|
struct netdev *netdev;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
2009-06-16 11:00:22 -07:00
|
|
|
|
printf("%s:\n", dpif_name(dpif));
|
2009-07-08 13:19:16 -07:00
|
|
|
|
if (!dpif_get_dp_stats(dpif, &stats)) {
|
Implement new fragment handling policy.
Until now, OVS has handled IP fragments more awkwardly than necessary. It
has not been possible to match on L4 headers, even in fragments with offset
0 where they are actually present. This means that there was no way to
implement ACLs that treat, say, different TCP ports differently, on
fragmented traffic; instead, all decisions for fragment forwarding had to
be made on the basis of L2 and L3 headers alone.
This commit improves the situation significantly. It is still not possible
to match on L4 headers in fragments with nonzero offset, because that
information is simply not present in such fragments, but this commit adds
the ability to match on L4 headers for fragments with zero offset. This
means that it becomes possible to implement ACLs that drop such "first
fragments" on the basis of L4 headers. In practice, that effectively
blocks even fragmented traffic on an L4 basis, because the receiving IP
stack cannot reassemble a full packet when the first fragment is missing.
This commit works by adding a new "fragment type" to the kernel flow match
and making it available through OpenFlow as a new NXM field named
NXM_NX_IP_FRAG. Because OpenFlow 1.0 explicitly says that the L4 fields
are always 0 for IP fragments, it adds a new OpenFlow fragment handling
mode that fills in the L4 fields for "first fragments". It also enhances
ovs-ofctl to allow users to configure this new fragment handling mode and
to parse the new field.
Signed-off-by: Ben Pfaff <blp@nicira.com>
Bug #7557.
2011-10-19 21:33:44 -07:00
|
|
|
|
printf("\tlookups: hit:%"PRIu64" missed:%"PRIu64" lost:%"PRIu64"\n"
|
|
|
|
|
"\tflows: %"PRIu64"\n",
|
|
|
|
|
stats.n_hit, stats.n_missed, stats.n_lost, stats.n_flows);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
}
|
2011-01-23 18:48:02 -08:00
|
|
|
|
DPIF_PORT_FOR_EACH (&dpif_port, &dump, dpif) {
|
|
|
|
|
printf("\tport %u: %s", dpif_port.port_no, dpif_port.name);
|
2010-12-18 01:07:06 -08:00
|
|
|
|
|
2011-01-23 18:48:02 -08:00
|
|
|
|
if (strcmp(dpif_port.type, "system")) {
|
2010-12-29 15:20:33 -08:00
|
|
|
|
int error;
|
|
|
|
|
|
2011-01-23 18:48:02 -08:00
|
|
|
|
printf (" (%s", dpif_port.type);
|
2010-12-29 15:20:33 -08:00
|
|
|
|
|
2011-08-05 14:18:06 -07:00
|
|
|
|
error = netdev_open(dpif_port.name, dpif_port.type, &netdev);
|
2010-12-29 15:20:33 -08:00
|
|
|
|
if (!error) {
|
2012-05-22 03:47:36 -07:00
|
|
|
|
struct smap config;
|
netdev: Decouple creating and configuring network devices.
Until now, each call to netdev_open() for a particular network device
had to either specify a set of network device arguments that was either
empty or (for devices that already existed) equal to the existing device's
configuration. Unfortunately, the definition of "equality" in the latter
case was mostly done in terms of strict equality of string-to-string maps,
which caused problems in cases where, for example, one set of arguments
specified the default value of an optional argument explicitly and the
other omitted it.
The netdev interface does have provisions for defining equality other ways,
but this had only been done in one case that was especially problematic in
practice. One way to solve this particular problem would be to carefully
define equality in all the problematic cases.
This commit takes another approach based on the realization that there is
really no need to do any comparisons. Instead, it removes configuration
at netdev_open() time entirely, because almost all of netdev_open()'s
callers are not interested in creating and configuring a netdev. Most of
them just want to open a configured device and use it. Therefore, this
commit stops providing any configuration arguments to netdev_open() and the
provider functions that it calls. Instead, a caller that does want to
configure a device does so after it opens it, by calling
netdev_set_config().
This change allows us to simplify the netdev interface a bit. There is no
longer any need to implement argument comparisons. As a result, there is
also no need for "struct netdev_dev" to keep track of configuration at all.
Instead, the network devices that have configuration keep track of it in
their own internal form.
This new interface does mean that it becomes possible to accidentally
create and try to use an unconfigured netdev that requires configuration.
Bug #6677.
Reported-by: Paul Ingram <paul@nicira.com>
2011-08-08 12:49:17 -07:00
|
|
|
|
|
2012-05-22 03:47:36 -07:00
|
|
|
|
smap_init(&config);
|
netdev: Decouple creating and configuring network devices.
Until now, each call to netdev_open() for a particular network device
had to either specify a set of network device arguments that was either
empty or (for devices that already existed) equal to the existing device's
configuration. Unfortunately, the definition of "equality" in the latter
case was mostly done in terms of strict equality of string-to-string maps,
which caused problems in cases where, for example, one set of arguments
specified the default value of an optional argument explicitly and the
other omitted it.
The netdev interface does have provisions for defining equality other ways,
but this had only been done in one case that was especially problematic in
practice. One way to solve this particular problem would be to carefully
define equality in all the problematic cases.
This commit takes another approach based on the realization that there is
really no need to do any comparisons. Instead, it removes configuration
at netdev_open() time entirely, because almost all of netdev_open()'s
callers are not interested in creating and configuring a netdev. Most of
them just want to open a configured device and use it. Therefore, this
commit stops providing any configuration arguments to netdev_open() and the
provider functions that it calls. Instead, a caller that does want to
configure a device does so after it opens it, by calling
netdev_set_config().
This change allows us to simplify the netdev interface a bit. There is no
longer any need to implement argument comparisons. As a result, there is
also no need for "struct netdev_dev" to keep track of configuration at all.
Instead, the network devices that have configuration keep track of it in
their own internal form.
This new interface does mean that it becomes possible to accidentally
create and try to use an unconfigured netdev that requires configuration.
Bug #6677.
Reported-by: Paul Ingram <paul@nicira.com>
2011-08-08 12:49:17 -07:00
|
|
|
|
error = netdev_get_config(netdev, &config);
|
|
|
|
|
if (!error) {
|
2012-05-22 03:47:36 -07:00
|
|
|
|
const struct smap_node **nodes;
|
netdev: Decouple creating and configuring network devices.
Until now, each call to netdev_open() for a particular network device
had to either specify a set of network device arguments that was either
empty or (for devices that already existed) equal to the existing device's
configuration. Unfortunately, the definition of "equality" in the latter
case was mostly done in terms of strict equality of string-to-string maps,
which caused problems in cases where, for example, one set of arguments
specified the default value of an optional argument explicitly and the
other omitted it.
The netdev interface does have provisions for defining equality other ways,
but this had only been done in one case that was especially problematic in
practice. One way to solve this particular problem would be to carefully
define equality in all the problematic cases.
This commit takes another approach based on the realization that there is
really no need to do any comparisons. Instead, it removes configuration
at netdev_open() time entirely, because almost all of netdev_open()'s
callers are not interested in creating and configuring a netdev. Most of
them just want to open a configured device and use it. Therefore, this
commit stops providing any configuration arguments to netdev_open() and the
provider functions that it calls. Instead, a caller that does want to
configure a device does so after it opens it, by calling
netdev_set_config().
This change allows us to simplify the netdev interface a bit. There is no
longer any need to implement argument comparisons. As a result, there is
also no need for "struct netdev_dev" to keep track of configuration at all.
Instead, the network devices that have configuration keep track of it in
their own internal form.
This new interface does mean that it becomes possible to accidentally
create and try to use an unconfigured netdev that requires configuration.
Bug #6677.
Reported-by: Paul Ingram <paul@nicira.com>
2011-08-08 12:49:17 -07:00
|
|
|
|
size_t i;
|
|
|
|
|
|
2012-05-22 03:47:36 -07:00
|
|
|
|
nodes = smap_sort(&config);
|
|
|
|
|
for (i = 0; i < smap_count(&config); i++) {
|
|
|
|
|
const struct smap_node *node = nodes[i];
|
|
|
|
|
printf("%c %s=%s", i ? ',' : ':', node->key,
|
|
|
|
|
node->value);
|
netdev: Decouple creating and configuring network devices.
Until now, each call to netdev_open() for a particular network device
had to either specify a set of network device arguments that was either
empty or (for devices that already existed) equal to the existing device's
configuration. Unfortunately, the definition of "equality" in the latter
case was mostly done in terms of strict equality of string-to-string maps,
which caused problems in cases where, for example, one set of arguments
specified the default value of an optional argument explicitly and the
other omitted it.
The netdev interface does have provisions for defining equality other ways,
but this had only been done in one case that was especially problematic in
practice. One way to solve this particular problem would be to carefully
define equality in all the problematic cases.
This commit takes another approach based on the realization that there is
really no need to do any comparisons. Instead, it removes configuration
at netdev_open() time entirely, because almost all of netdev_open()'s
callers are not interested in creating and configuring a netdev. Most of
them just want to open a configured device and use it. Therefore, this
commit stops providing any configuration arguments to netdev_open() and the
provider functions that it calls. Instead, a caller that does want to
configure a device does so after it opens it, by calling
netdev_set_config().
This change allows us to simplify the netdev interface a bit. There is no
longer any need to implement argument comparisons. As a result, there is
also no need for "struct netdev_dev" to keep track of configuration at all.
Instead, the network devices that have configuration keep track of it in
their own internal form.
This new interface does mean that it becomes possible to accidentally
create and try to use an unconfigured netdev that requires configuration.
Bug #6677.
Reported-by: Paul Ingram <paul@nicira.com>
2011-08-08 12:49:17 -07:00
|
|
|
|
}
|
|
|
|
|
free(nodes);
|
|
|
|
|
} else {
|
|
|
|
|
printf(", could not retrieve configuration (%s)",
|
2013-06-24 10:54:49 -07:00
|
|
|
|
ovs_strerror(error));
|
2010-12-29 15:20:33 -08:00
|
|
|
|
}
|
2012-05-22 03:47:36 -07:00
|
|
|
|
smap_destroy(&config);
|
2010-12-29 15:20:33 -08:00
|
|
|
|
|
|
|
|
|
netdev_close(netdev);
|
|
|
|
|
} else {
|
2013-06-24 10:54:49 -07:00
|
|
|
|
printf(": open failed (%s)", ovs_strerror(error));
|
2010-12-29 15:20:33 -08:00
|
|
|
|
}
|
|
|
|
|
putchar(')');
|
|
|
|
|
}
|
|
|
|
|
putchar('\n');
|
2011-04-28 13:02:15 -07:00
|
|
|
|
|
|
|
|
|
if (print_statistics) {
|
2011-09-15 19:36:17 -07:00
|
|
|
|
struct netdev_stats s;
|
|
|
|
|
int error;
|
|
|
|
|
|
|
|
|
|
error = netdev_open(dpif_port.name, dpif_port.type, &netdev);
|
|
|
|
|
if (error) {
|
2013-06-24 10:54:49 -07:00
|
|
|
|
printf(", open failed (%s)", ovs_strerror(error));
|
2011-09-15 19:36:17 -07:00
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
error = netdev_get_stats(netdev, &s);
|
|
|
|
|
if (error) {
|
2013-06-24 10:54:49 -07:00
|
|
|
|
printf(", could not retrieve stats (%s)", ovs_strerror(error));
|
2011-09-15 19:36:17 -07:00
|
|
|
|
continue;
|
|
|
|
|
}
|
2011-04-28 13:02:15 -07:00
|
|
|
|
|
2011-09-15 19:36:17 -07:00
|
|
|
|
netdev_close(netdev);
|
|
|
|
|
print_stat("\t\tRX packets:", s.rx_packets);
|
|
|
|
|
print_stat(" errors:", s.rx_errors);
|
|
|
|
|
print_stat(" dropped:", s.rx_dropped);
|
|
|
|
|
print_stat(" overruns:", s.rx_over_errors);
|
|
|
|
|
print_stat(" frame:", s.rx_frame_errors);
|
2011-04-28 13:02:15 -07:00
|
|
|
|
printf("\n");
|
|
|
|
|
|
2011-09-15 19:36:17 -07:00
|
|
|
|
print_stat("\t\tTX packets:", s.tx_packets);
|
|
|
|
|
print_stat(" errors:", s.tx_errors);
|
|
|
|
|
print_stat(" dropped:", s.tx_dropped);
|
|
|
|
|
print_stat(" aborted:", s.tx_aborted_errors);
|
|
|
|
|
print_stat(" carrier:", s.tx_carrier_errors);
|
2011-04-28 13:02:15 -07:00
|
|
|
|
printf("\n");
|
|
|
|
|
|
2011-09-15 19:36:17 -07:00
|
|
|
|
print_stat("\t\tcollisions:", s.collisions);
|
2011-04-28 13:02:15 -07:00
|
|
|
|
printf("\n");
|
|
|
|
|
|
2011-09-15 19:36:17 -07:00
|
|
|
|
print_stat("\t\tRX bytes:", s.rx_bytes);
|
|
|
|
|
print_human_size(s.rx_bytes);
|
|
|
|
|
print_stat(" TX bytes:", s.tx_bytes);
|
|
|
|
|
print_human_size(s.tx_bytes);
|
2011-04-28 13:02:15 -07:00
|
|
|
|
printf("\n");
|
|
|
|
|
}
|
2009-07-08 13:19:16 -07:00
|
|
|
|
}
|
|
|
|
|
dpif_close(dpif);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2012-07-08 04:38:36 -07:00
|
|
|
|
dpctl_show(int argc, char *argv[])
|
2009-07-08 13:19:16 -07:00
|
|
|
|
{
|
|
|
|
|
bool failure = false;
|
|
|
|
|
if (argc > 1) {
|
|
|
|
|
int i;
|
|
|
|
|
for (i = 1; i < argc; i++) {
|
|
|
|
|
const char *name = argv[i];
|
2009-06-16 10:09:10 -07:00
|
|
|
|
struct dpif *dpif;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
int error;
|
|
|
|
|
|
2010-01-22 14:37:10 -05:00
|
|
|
|
error = parsed_dpif_open(name, false, &dpif);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
if (!error) {
|
2009-06-16 10:09:10 -07:00
|
|
|
|
show_dpif(dpif);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
} else {
|
|
|
|
|
ovs_error(error, "opening datapath %s failed", name);
|
|
|
|
|
failure = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2011-03-25 13:00:13 -07:00
|
|
|
|
struct sset types;
|
2011-01-05 15:33:09 -08:00
|
|
|
|
const char *type;
|
|
|
|
|
|
2011-03-25 13:00:13 -07:00
|
|
|
|
sset_init(&types);
|
2011-01-05 15:33:09 -08:00
|
|
|
|
dp_enumerate_types(&types);
|
2011-03-25 13:00:13 -07:00
|
|
|
|
SSET_FOR_EACH (type, &types) {
|
|
|
|
|
struct sset names;
|
2011-01-05 15:33:09 -08:00
|
|
|
|
const char *name;
|
|
|
|
|
|
2011-03-25 13:00:13 -07:00
|
|
|
|
sset_init(&names);
|
2011-01-05 15:33:09 -08:00
|
|
|
|
if (dp_enumerate_names(type, &names)) {
|
2009-07-08 13:19:16 -07:00
|
|
|
|
failure = true;
|
2011-01-05 15:33:09 -08:00
|
|
|
|
continue;
|
|
|
|
|
}
|
2011-03-25 13:00:13 -07:00
|
|
|
|
SSET_FOR_EACH (name, &names) {
|
2011-01-05 15:33:09 -08:00
|
|
|
|
struct dpif *dpif;
|
|
|
|
|
int error;
|
|
|
|
|
|
|
|
|
|
error = dpif_open(name, type, &dpif);
|
|
|
|
|
if (!error) {
|
|
|
|
|
show_dpif(dpif);
|
|
|
|
|
} else {
|
|
|
|
|
ovs_error(error, "opening datapath %s failed", name);
|
|
|
|
|
failure = true;
|
|
|
|
|
}
|
2009-07-08 13:19:16 -07:00
|
|
|
|
}
|
2011-03-25 13:00:13 -07:00
|
|
|
|
sset_destroy(&names);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
}
|
2011-03-25 13:00:13 -07:00
|
|
|
|
sset_destroy(&types);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
}
|
|
|
|
|
if (failure) {
|
|
|
|
|
exit(EXIT_FAILURE);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2009-08-04 15:13:40 -07:00
|
|
|
|
static void
|
2012-07-08 04:38:36 -07:00
|
|
|
|
dpctl_dump_dps(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
|
2009-08-04 15:13:40 -07:00
|
|
|
|
{
|
2011-03-25 13:00:13 -07:00
|
|
|
|
struct sset dpif_names, dpif_types;
|
|
|
|
|
const char *type;
|
2010-01-22 14:37:10 -05:00
|
|
|
|
int error = 0;
|
2009-08-04 15:13:40 -07:00
|
|
|
|
|
2011-03-25 13:00:13 -07:00
|
|
|
|
sset_init(&dpif_names);
|
|
|
|
|
sset_init(&dpif_types);
|
2010-01-22 14:37:10 -05:00
|
|
|
|
dp_enumerate_types(&dpif_types);
|
2009-08-04 15:13:40 -07:00
|
|
|
|
|
2011-03-25 13:00:13 -07:00
|
|
|
|
SSET_FOR_EACH (type, &dpif_types) {
|
|
|
|
|
const char *name;
|
2010-01-22 14:37:10 -05:00
|
|
|
|
int retval;
|
|
|
|
|
|
2011-03-25 13:00:13 -07:00
|
|
|
|
retval = dp_enumerate_names(type, &dpif_names);
|
2010-01-22 14:37:10 -05:00
|
|
|
|
if (retval) {
|
|
|
|
|
error = retval;
|
|
|
|
|
}
|
|
|
|
|
|
2011-03-25 13:00:13 -07:00
|
|
|
|
SSET_FOR_EACH (name, &dpif_names) {
|
2010-01-22 14:37:10 -05:00
|
|
|
|
struct dpif *dpif;
|
2011-03-25 13:00:13 -07:00
|
|
|
|
if (!dpif_open(name, type, &dpif)) {
|
2010-01-22 14:37:10 -05:00
|
|
|
|
printf("%s\n", dpif_name(dpif));
|
|
|
|
|
dpif_close(dpif);
|
|
|
|
|
}
|
2009-08-04 15:13:40 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-03-25 13:00:13 -07:00
|
|
|
|
sset_destroy(&dpif_names);
|
|
|
|
|
sset_destroy(&dpif_types);
|
2009-08-04 15:13:40 -07:00
|
|
|
|
if (error) {
|
|
|
|
|
exit(EXIT_FAILURE);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2009-07-08 13:19:16 -07:00
|
|
|
|
static void
|
2012-10-12 17:46:06 -07:00
|
|
|
|
dpctl_dump_flows(int argc, char *argv[])
|
2009-07-08 13:19:16 -07:00
|
|
|
|
{
|
2011-01-26 07:11:50 -08:00
|
|
|
|
const struct dpif_flow_stats *stats;
|
2011-01-26 07:03:39 -08:00
|
|
|
|
const struct nlattr *actions;
|
2013-09-23 22:58:46 -07:00
|
|
|
|
struct dpif_flow_dump flow_dump;
|
2011-01-26 07:03:39 -08:00
|
|
|
|
const struct nlattr *key;
|
2013-06-19 07:15:10 +00:00
|
|
|
|
const struct nlattr *mask;
|
2013-09-23 22:58:46 -07:00
|
|
|
|
struct dpif_port dpif_port;
|
|
|
|
|
struct dpif_port_dump port_dump;
|
|
|
|
|
struct hmap portno_names;
|
2011-01-26 07:03:39 -08:00
|
|
|
|
size_t actions_len;
|
2009-06-16 10:09:10 -07:00
|
|
|
|
struct dpif *dpif;
|
2011-01-26 07:03:39 -08:00
|
|
|
|
size_t key_len;
|
2013-06-19 07:15:10 +00:00
|
|
|
|
size_t mask_len;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
struct ds ds;
|
2012-10-12 17:46:06 -07:00
|
|
|
|
char *name;
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
2012-10-12 17:46:06 -07:00
|
|
|
|
name = (argc == 2) ? xstrdup(argv[1]) : get_one_dp();
|
|
|
|
|
run(parsed_dpif_open(name, false, &dpif), "opening datapath");
|
|
|
|
|
free(name);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
2013-09-23 22:58:46 -07:00
|
|
|
|
hmap_init(&portno_names);
|
|
|
|
|
DPIF_PORT_FOR_EACH (&dpif_port, &port_dump, dpif) {
|
|
|
|
|
odp_portno_names_set(&portno_names, dpif_port.port_no, dpif_port.name);
|
|
|
|
|
}
|
|
|
|
|
|
2009-07-08 13:19:16 -07:00
|
|
|
|
ds_init(&ds);
|
2013-09-23 22:58:46 -07:00
|
|
|
|
dpif_flow_dump_start(&flow_dump, dpif);
|
|
|
|
|
while (dpif_flow_dump_next(&flow_dump, &key, &key_len,
|
2013-06-19 07:15:10 +00:00
|
|
|
|
&mask, &mask_len,
|
2011-01-26 07:03:39 -08:00
|
|
|
|
&actions, &actions_len, &stats)) {
|
datapath: Change listing flows to use an iterator concept.
One of the goals for Open vSwitch is to decouple kernel and userspace
software, so that either one can be upgraded or rolled back independent of
the other. To do this in full generality, it must be possible to change
the kernel's idea of the flow key separately from the userspace version.
In turn, that means that flow keys must become variable-length. This does
not, however, fit in well with the ODP_FLOW_LIST ioctl in its current form,
because that would require userspace to know how much space to allocate
for each flow's key in advance, or to allocate as much space as could
possibly be needed. Neither choice is very attractive.
This commit prepares for a different solution, by replacing ODP_FLOW_LIST
by a new ioctl ODP_FLOW_DUMP that retrieves a single flow from the datapath
on each call. It is much cleaner to allocate the maximum amount of space
for a single flow key than to do so for possibly a very large number of
flow keys.
As a side effect, this patch also fixes a race condition that sometimes
made "ovs-dpctl dump-flows" print an error: previously, flows were listed
and then their actions were retrieved, which left a window in which
ovs-vswitchd could delete the flow. Now dumping a flow and its actions is
a single step, closing that window.
Dumping all of the flows in a datapath is no longer an atomic step, so now
it is possible to miss some flows or see a single flow twice during
iteration, if the flow table is modified by another process. It doesn't
look like this should be a problem for ovs-vswitchd.
It would be faster to retrieve a number of flows in batch instead of just
one at a time, but that will naturally happen later when the kernel
datapath interface is changed to use Netlink, so this patch does not bother
with it.
Signed-off-by: Ben Pfaff <blp@nicira.com>
Acked-by: Jesse Gross <jesse@nicira.com>
2010-12-28 10:39:52 -08:00
|
|
|
|
ds_clear(&ds);
|
2013-09-23 22:58:46 -07:00
|
|
|
|
odp_flow_format(key, key_len, mask, mask_len, &portno_names, &ds,
|
|
|
|
|
verbosity);
|
2011-01-26 07:03:39 -08:00
|
|
|
|
ds_put_cstr(&ds, ", ");
|
2013-06-19 07:15:10 +00:00
|
|
|
|
|
2011-01-26 07:11:50 -08:00
|
|
|
|
dpif_flow_stats_format(stats, &ds);
|
2011-01-26 07:03:39 -08:00
|
|
|
|
ds_put_cstr(&ds, ", actions:");
|
|
|
|
|
format_odp_actions(&ds, actions, actions_len);
|
datapath: Change listing flows to use an iterator concept.
One of the goals for Open vSwitch is to decouple kernel and userspace
software, so that either one can be upgraded or rolled back independent of
the other. To do this in full generality, it must be possible to change
the kernel's idea of the flow key separately from the userspace version.
In turn, that means that flow keys must become variable-length. This does
not, however, fit in well with the ODP_FLOW_LIST ioctl in its current form,
because that would require userspace to know how much space to allocate
for each flow's key in advance, or to allocate as much space as could
possibly be needed. Neither choice is very attractive.
This commit prepares for a different solution, by replacing ODP_FLOW_LIST
by a new ioctl ODP_FLOW_DUMP that retrieves a single flow from the datapath
on each call. It is much cleaner to allocate the maximum amount of space
for a single flow key than to do so for possibly a very large number of
flow keys.
As a side effect, this patch also fixes a race condition that sometimes
made "ovs-dpctl dump-flows" print an error: previously, flows were listed
and then their actions were retrieved, which left a window in which
ovs-vswitchd could delete the flow. Now dumping a flow and its actions is
a single step, closing that window.
Dumping all of the flows in a datapath is no longer an atomic step, so now
it is possible to miss some flows or see a single flow twice during
iteration, if the flow table is modified by another process. It doesn't
look like this should be a problem for ovs-vswitchd.
It would be faster to retrieve a number of flows in batch instead of just
one at a time, but that will naturally happen later when the kernel
datapath interface is changed to use Netlink, so this patch does not bother
with it.
Signed-off-by: Ben Pfaff <blp@nicira.com>
Acked-by: Jesse Gross <jesse@nicira.com>
2010-12-28 10:39:52 -08:00
|
|
|
|
printf("%s\n", ds_cstr(&ds));
|
2009-07-08 13:19:16 -07:00
|
|
|
|
}
|
2013-09-23 22:58:46 -07:00
|
|
|
|
dpif_flow_dump_done(&flow_dump);
|
|
|
|
|
odp_portno_names_destroy(&portno_names);
|
|
|
|
|
hmap_destroy(&portno_names);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
ds_destroy(&ds);
|
2009-06-16 10:09:10 -07:00
|
|
|
|
dpif_close(dpif);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
}
|
|
|
|
|
|
2013-03-15 13:47:53 -07:00
|
|
|
|
static void
|
|
|
|
|
dpctl_put_flow(int argc, char *argv[], enum dpif_flow_put_flags flags)
|
|
|
|
|
{
|
|
|
|
|
const char *key_s = argv[argc - 2];
|
|
|
|
|
const char *actions_s = argv[argc - 1];
|
|
|
|
|
struct dpif_flow_stats stats;
|
2013-09-23 22:58:46 -07:00
|
|
|
|
struct dpif_port dpif_port;
|
|
|
|
|
struct dpif_port_dump port_dump;
|
2013-03-15 13:47:53 -07:00
|
|
|
|
struct ofpbuf actions;
|
|
|
|
|
struct ofpbuf key;
|
2013-06-19 07:15:10 +00:00
|
|
|
|
struct ofpbuf mask;
|
2013-03-15 13:47:53 -07:00
|
|
|
|
struct dpif *dpif;
|
2013-06-19 07:15:10 +00:00
|
|
|
|
struct ds s;
|
2013-03-15 13:47:53 -07:00
|
|
|
|
char *dp_name;
|
2013-09-23 22:58:46 -07:00
|
|
|
|
struct simap port_names;
|
|
|
|
|
|
|
|
|
|
dp_name = argc == 4 ? xstrdup(argv[1]) : get_one_dp();
|
|
|
|
|
run(parsed_dpif_open(dp_name, false, &dpif), "opening datapath");
|
|
|
|
|
free(dp_name);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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));
|
|
|
|
|
}
|
2013-03-15 13:47:53 -07:00
|
|
|
|
|
2013-06-19 07:15:10 +00:00
|
|
|
|
ds_init(&s);
|
2013-03-15 13:47:53 -07:00
|
|
|
|
ofpbuf_init(&key, 0);
|
2013-06-19 07:15:10 +00:00
|
|
|
|
ofpbuf_init(&mask, 0);
|
2013-09-23 22:58:46 -07:00
|
|
|
|
run(odp_flow_from_string(key_s, &port_names, &key, &mask),
|
|
|
|
|
"parsing flow key");
|
|
|
|
|
|
|
|
|
|
simap_destroy(&port_names);
|
2013-03-15 13:47:53 -07:00
|
|
|
|
|
|
|
|
|
ofpbuf_init(&actions, 0);
|
|
|
|
|
run(odp_actions_from_string(actions_s, NULL, &actions), "parsing actions");
|
|
|
|
|
|
|
|
|
|
run(dpif_flow_put(dpif, flags,
|
|
|
|
|
key.data, key.size,
|
2013-06-19 07:15:10 +00:00
|
|
|
|
mask.size == 0 ? NULL : mask.data, mask.size,
|
2013-03-15 13:47:53 -07:00
|
|
|
|
actions.data, actions.size,
|
|
|
|
|
print_statistics ? &stats : NULL),
|
|
|
|
|
"updating flow table");
|
|
|
|
|
|
|
|
|
|
ofpbuf_uninit(&key);
|
2013-06-19 07:15:10 +00:00
|
|
|
|
ofpbuf_uninit(&mask);
|
2013-03-15 13:47:53 -07:00
|
|
|
|
ofpbuf_uninit(&actions);
|
|
|
|
|
|
|
|
|
|
if (print_statistics) {
|
|
|
|
|
struct ds s;
|
|
|
|
|
|
|
|
|
|
ds_init(&s);
|
|
|
|
|
dpif_flow_stats_format(&stats, &s);
|
|
|
|
|
puts(ds_cstr(&s));
|
|
|
|
|
ds_destroy(&s);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
dpctl_add_flow(int argc, char *argv[])
|
|
|
|
|
{
|
|
|
|
|
dpctl_put_flow(argc, argv, DPIF_FP_CREATE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
dpctl_mod_flow(int argc OVS_UNUSED, char *argv[])
|
|
|
|
|
{
|
|
|
|
|
enum dpif_flow_put_flags flags;
|
|
|
|
|
|
|
|
|
|
flags = DPIF_FP_MODIFY;
|
|
|
|
|
if (may_create) {
|
|
|
|
|
flags |= DPIF_FP_CREATE;
|
|
|
|
|
}
|
|
|
|
|
if (zero_statistics) {
|
|
|
|
|
flags |= DPIF_FP_ZERO_STATS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dpctl_put_flow(argc, argv, flags);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
dpctl_del_flow(int argc, char *argv[])
|
|
|
|
|
{
|
|
|
|
|
const char *key_s = argv[argc - 1];
|
|
|
|
|
struct dpif_flow_stats stats;
|
2013-09-23 22:58:46 -07:00
|
|
|
|
struct dpif_port dpif_port;
|
|
|
|
|
struct dpif_port_dump port_dump;
|
2013-03-15 13:47:53 -07:00
|
|
|
|
struct ofpbuf key;
|
2013-06-19 07:15:10 +00:00
|
|
|
|
struct ofpbuf mask; /* To be ignored. */
|
2013-03-15 13:47:53 -07:00
|
|
|
|
struct dpif *dpif;
|
|
|
|
|
char *dp_name;
|
2013-09-23 22:58:46 -07:00
|
|
|
|
struct simap port_names;
|
2013-03-15 13:47:53 -07:00
|
|
|
|
|
2013-09-23 13:11:41 -07:00
|
|
|
|
dp_name = argc == 3 ? xstrdup(argv[1]) : get_one_dp();
|
2013-03-15 13:47:53 -07:00
|
|
|
|
run(parsed_dpif_open(dp_name, false, &dpif), "opening datapath");
|
|
|
|
|
free(dp_name);
|
|
|
|
|
|
2013-09-23 22:58:46 -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);
|
|
|
|
|
run(odp_flow_from_string(key_s, &port_names, &key, &mask), "parsing flow key");
|
|
|
|
|
|
2013-03-15 13:47:53 -07:00
|
|
|
|
run(dpif_flow_del(dpif,
|
|
|
|
|
key.data, key.size,
|
|
|
|
|
print_statistics ? &stats : NULL), "deleting flow");
|
|
|
|
|
|
2013-09-23 22:58:46 -07:00
|
|
|
|
simap_destroy(&port_names);
|
2013-03-15 13:47:53 -07:00
|
|
|
|
ofpbuf_uninit(&key);
|
2013-06-19 07:15:10 +00:00
|
|
|
|
ofpbuf_uninit(&mask);
|
2013-03-15 13:47:53 -07:00
|
|
|
|
|
|
|
|
|
if (print_statistics) {
|
|
|
|
|
struct ds s;
|
|
|
|
|
|
|
|
|
|
ds_init(&s);
|
|
|
|
|
dpif_flow_stats_format(&stats, &s);
|
|
|
|
|
puts(ds_cstr(&s));
|
|
|
|
|
ds_destroy(&s);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2009-07-08 13:19:16 -07:00
|
|
|
|
static void
|
2012-10-12 17:46:06 -07:00
|
|
|
|
dpctl_del_flows(int argc, char *argv[])
|
2009-07-08 13:19:16 -07:00
|
|
|
|
{
|
2009-06-16 10:09:10 -07:00
|
|
|
|
struct dpif *dpif;
|
2012-10-12 17:46:06 -07:00
|
|
|
|
char *name;
|
|
|
|
|
|
|
|
|
|
name = (argc == 2) ? xstrdup(argv[1]) : get_one_dp();
|
|
|
|
|
run(parsed_dpif_open(name, false, &dpif), "opening datapath");
|
|
|
|
|
free(name);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
2009-06-16 10:09:10 -07:00
|
|
|
|
run(dpif_flow_flush(dpif), "deleting all flows");
|
|
|
|
|
dpif_close(dpif);
|
2009-07-08 13:19:16 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2012-07-08 04:38:36 -07:00
|
|
|
|
dpctl_help(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
|
2009-07-08 13:19:16 -07:00
|
|
|
|
{
|
|
|
|
|
usage();
|
|
|
|
|
}
|
2011-11-11 15:22:56 -08:00
|
|
|
|
|
|
|
|
|
/* Undocumented commands for unit testing. */
|
|
|
|
|
|
|
|
|
|
static void
|
2012-07-08 04:38:36 -07:00
|
|
|
|
dpctl_parse_actions(int argc, char *argv[])
|
2011-11-11 15:22:56 -08:00
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
for (i = 1; i < argc; i++) {
|
|
|
|
|
struct ofpbuf actions;
|
|
|
|
|
struct ds s;
|
|
|
|
|
|
|
|
|
|
ofpbuf_init(&actions, 0);
|
|
|
|
|
run(odp_actions_from_string(argv[i], NULL, &actions),
|
|
|
|
|
"odp_actions_from_string");
|
|
|
|
|
|
|
|
|
|
ds_init(&s);
|
|
|
|
|
format_odp_actions(&s, actions.data, actions.size);
|
|
|
|
|
puts(ds_cstr(&s));
|
|
|
|
|
ds_destroy(&s);
|
|
|
|
|
|
|
|
|
|
ofpbuf_uninit(&actions);
|
|
|
|
|
}
|
|
|
|
|
}
|
2009-07-08 13:19:16 -07:00
|
|
|
|
|
2011-11-17 10:24:05 -08:00
|
|
|
|
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;
|
|
|
|
|
|
2012-11-06 13:14:55 -08:00
|
|
|
|
ovs_assert(bytes % NL_A_U32_SIZE == 0);
|
2011-11-17 10:24:05 -08:00
|
|
|
|
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;
|
2013-07-22 15:47:19 -07:00
|
|
|
|
sort_output_actions__(first_output,
|
|
|
|
|
ALIGNED_CAST(struct nlattr *, end));
|
2011-11-17 10:24:05 -08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 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 void
|
2012-07-08 04:38:36 -07:00
|
|
|
|
dpctl_normalize_actions(int argc, char *argv[])
|
2011-11-17 10:24:05 -08:00
|
|
|
|
{
|
2012-05-22 10:32:02 -07:00
|
|
|
|
struct simap port_names;
|
2011-11-17 10:24:05 -08:00
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
ds_init(&s);
|
|
|
|
|
|
2012-05-22 10:32:02 -07:00
|
|
|
|
simap_init(&port_names);
|
2011-11-17 10:24:05 -08:00
|
|
|
|
for (i = 3; i < argc; i++) {
|
|
|
|
|
char name[16];
|
|
|
|
|
int number;
|
|
|
|
|
int n = -1;
|
|
|
|
|
|
|
|
|
|
if (sscanf(argv[i], "%15[^=]=%d%n", name, &number, &n) > 0 && n > 0) {
|
2011-11-17 15:26:00 -08:00
|
|
|
|
uintptr_t n = number;
|
2012-05-22 10:32:02 -07:00
|
|
|
|
simap_put(&port_names, name, n);
|
2011-11-17 10:24:05 -08:00
|
|
|
|
} else {
|
|
|
|
|
ovs_fatal(0, "%s: expected NAME=NUMBER", argv[i]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Parse flow key. */
|
|
|
|
|
ofpbuf_init(&keybuf, 0);
|
2013-06-19 07:15:10 +00:00
|
|
|
|
run(odp_flow_from_string(argv[1], &port_names, &keybuf, NULL),
|
2011-11-17 10:24:05 -08:00
|
|
|
|
"odp_flow_key_from_string");
|
|
|
|
|
|
|
|
|
|
ds_clear(&s);
|
2013-09-23 22:58:46 -07:00
|
|
|
|
odp_flow_format(keybuf.data, keybuf.size, NULL, 0, NULL, &s, verbosity);
|
2011-11-17 10:24:05 -08:00
|
|
|
|
printf("input flow: %s\n", ds_cstr(&s));
|
|
|
|
|
|
|
|
|
|
run(odp_flow_key_to_flow(keybuf.data, keybuf.size, &flow),
|
|
|
|
|
"odp_flow_key_to_flow");
|
|
|
|
|
ofpbuf_uninit(&keybuf);
|
|
|
|
|
|
|
|
|
|
/* Parse actions. */
|
|
|
|
|
ofpbuf_init(&odp_actions, 0);
|
|
|
|
|
run(odp_actions_from_string(argv[2], &port_names, &odp_actions),
|
|
|
|
|
"odp_actions_from_string");
|
2013-09-23 13:07:24 -07:00
|
|
|
|
simap_destroy(&port_names);
|
2011-11-17 10:24:05 -08:00
|
|
|
|
|
|
|
|
|
if (verbosity) {
|
|
|
|
|
ds_clear(&s);
|
|
|
|
|
format_odp_actions(&s, odp_actions.data, odp_actions.size);
|
|
|
|
|
printf("input actions: %s\n", ds_cstr(&s));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hmap_init(&actions_per_flow);
|
|
|
|
|
NL_ATTR_FOR_EACH (a, left, odp_actions.data, odp_actions.size) {
|
2013-01-25 16:22:07 +09:00
|
|
|
|
const struct ovs_action_push_vlan *push;
|
|
|
|
|
switch(nl_attr_type(a)) {
|
|
|
|
|
case OVS_ACTION_ATTR_POP_VLAN:
|
2011-11-17 10:24:05 -08:00
|
|
|
|
flow.vlan_tci = htons(0);
|
|
|
|
|
continue;
|
|
|
|
|
|
2013-01-25 16:22:07 +09:00
|
|
|
|
case OVS_ACTION_ATTR_PUSH_VLAN:
|
2011-11-17 10:24:05 -08:00
|
|
|
|
push = nl_attr_get_unspec(a, sizeof *push);
|
|
|
|
|
flow.vlan_tci = push->vlan_tci;
|
|
|
|
|
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;
|
|
|
|
|
}
|
2012-11-06 13:14:55 -08:00
|
|
|
|
ovs_assert(i == n_afs);
|
2011-11-17 10:24:05 -08:00
|
|
|
|
|
|
|
|
|
qsort(afs, n_afs, sizeof *afs, compare_actions_for_flow);
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < n_afs; i++) {
|
|
|
|
|
const struct actions_for_flow *af = afs[i];
|
|
|
|
|
|
|
|
|
|
sort_output_actions(af->actions.data, af->actions.size);
|
|
|
|
|
|
|
|
|
|
if (af->flow.vlan_tci != htons(0)) {
|
|
|
|
|
printf("vlan(vid=%"PRIu16",pcp=%d): ",
|
|
|
|
|
vlan_tci_to_vid(af->flow.vlan_tci),
|
|
|
|
|
vlan_tci_to_pcp(af->flow.vlan_tci));
|
|
|
|
|
} else {
|
|
|
|
|
printf("no vlan: ");
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-27 06:55:19 +09:00
|
|
|
|
if (eth_type_mpls(af->flow.dl_type)) {
|
2013-01-25 16:22:07 +09:00
|
|
|
|
printf("mpls(label=%"PRIu32",tc=%d,ttl=%d): ",
|
|
|
|
|
mpls_lse_to_label(af->flow.mpls_lse),
|
|
|
|
|
mpls_lse_to_tc(af->flow.mpls_lse),
|
|
|
|
|
mpls_lse_to_ttl(af->flow.mpls_lse));
|
|
|
|
|
} else {
|
|
|
|
|
printf("no mpls: ");
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-17 10:24:05 -08:00
|
|
|
|
ds_clear(&s);
|
|
|
|
|
format_odp_actions(&s, af->actions.data, af->actions.size);
|
|
|
|
|
puts(ds_cstr(&s));
|
|
|
|
|
}
|
|
|
|
|
ds_destroy(&s);
|
|
|
|
|
}
|
|
|
|
|
|
2009-10-23 11:49:39 -07:00
|
|
|
|
static const struct command all_commands[] = {
|
2012-07-08 04:38:36 -07:00
|
|
|
|
{ "add-dp", 1, INT_MAX, dpctl_add_dp },
|
|
|
|
|
{ "del-dp", 1, 1, dpctl_del_dp },
|
|
|
|
|
{ "add-if", 2, INT_MAX, dpctl_add_if },
|
|
|
|
|
{ "del-if", 2, INT_MAX, dpctl_del_if },
|
|
|
|
|
{ "set-if", 2, INT_MAX, dpctl_set_if },
|
|
|
|
|
{ "dump-dps", 0, 0, dpctl_dump_dps },
|
|
|
|
|
{ "show", 0, INT_MAX, dpctl_show },
|
2012-10-12 17:46:06 -07:00
|
|
|
|
{ "dump-flows", 0, 1, dpctl_dump_flows },
|
2013-03-15 13:47:53 -07:00
|
|
|
|
{ "add-flow", 2, 3, dpctl_add_flow },
|
|
|
|
|
{ "mod-flow", 2, 3, dpctl_mod_flow },
|
|
|
|
|
{ "del-flow", 1, 2, dpctl_del_flow },
|
2012-10-12 17:46:06 -07:00
|
|
|
|
{ "del-flows", 0, 1, dpctl_del_flows },
|
2012-07-08 04:38:36 -07:00
|
|
|
|
{ "help", 0, INT_MAX, dpctl_help },
|
2011-11-11 15:22:56 -08:00
|
|
|
|
|
|
|
|
|
/* Undocumented commands for testing. */
|
2012-07-08 04:38:36 -07:00
|
|
|
|
{ "parse-actions", 1, INT_MAX, dpctl_parse_actions },
|
|
|
|
|
{ "normalize-actions", 2, INT_MAX, dpctl_normalize_actions },
|
2011-11-11 15:22:56 -08:00
|
|
|
|
|
2009-07-08 13:19:16 -07:00
|
|
|
|
{ NULL, 0, 0, NULL },
|
|
|
|
|
};
|
2013-07-19 10:04:47 -07:00
|
|
|
|
|
|
|
|
|
static const struct command *get_all_commands(void)
|
|
|
|
|
{
|
|
|
|
|
return all_commands;
|
|
|
|
|
}
|