/* Copyright (c) 2008, 2009 Nicira Networks, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* In addition, as a special exception, Nicira Networks gives permission
* to link the code of its release of vswitchd with the OpenSSL project's
* "OpenSSL" library (or with modified versions of it that use the same
* license as the "OpenSSL" library), and distribute the linked
* executables. You must obey the GNU General Public License in all
* respects for all of the code used other than "OpenSSL". If you modify
* this file, you may extend this exception to your version of the file,
* but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version.
*
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "command-line.h"
#include "daemon.h"
#include "dynamic-string.h"
#include "ezio.h"
#include "fatal-signal.h"
#include "netdev.h"
#include "ofpbuf.h"
#include "openflow/nicira-ext.h"
#include "openflow/openflow.h"
#include "packets.h"
#include "poll-loop.h"
#include "process.h"
#include "random.h"
#include "rconn.h"
#include "socket-util.h"
#include "svec.h"
#include "timeval.h"
#include "util.h"
#include "vconn.h"
#include "xtoxll.h"
#define THIS_MODULE VLM_switchui
#include "vlog.h"
static void parse_options(int argc, char *argv[]);
static void usage(void);
static void initialize_terminal(void);
static void restore_terminal(void *aux);
enum priority {
P_STATUS = 5,
P_PROGRESS = 10,
P_WARNING = 15,
P_ERROR = 20,
P_FATAL = 25
};
struct message;
static void emit(struct message **, enum priority, const char *, ...)
PRINTF_FORMAT(3, 4);
static void emit_function(struct message **, enum priority,
void (*function)(void *aux), void *aux);
static int shown(struct message **);
static void clear_messages(void);
static bool empty_message(const struct message *);
static struct message *best_message(void);
static struct message *next_message(struct message *);
static struct message *prev_message(struct message *);
static void put_message(const struct message *);
static void message_shown(struct message *);
static void age_messages(void);
struct pair {
char *name;
char *value;
};
struct dict {
struct pair *pairs;
size_t n, max;
};
static void dict_init(struct dict *);
static void dict_add(struct dict *, const char *name, const char *value);
static void dict_add_nocopy(struct dict *, char *name, char *value);
static void dict_delete(struct dict *, const char *name);
static void dict_parse(struct dict *, const char *data, size_t nbytes);
static void dict_free(struct dict *);
static bool dict_lookup(const struct dict *,
const char *name, const char **value);
static int dict_get_int(const struct dict *, const char *name, int def);
static bool dict_get_bool(const struct dict *, const char *name, bool def);
static const char *dict_get_string(const struct dict *,
const char *name, const char *def);
static uint32_t dict_get_ip(const struct dict *, const char *name);
static void addf(const char *format, ...) PRINTF_FORMAT(1, 2);
static void fetch_status(struct rconn *, struct dict *, long long int timeout);
static bool parse_reply(void *, struct dict *, uint32_t xid);
static void compose_messages(const struct dict *, struct rconn *rconn);
static void show_flows(struct rconn *);
static void show_dpid_ip(struct rconn *, const struct dict *);
static void show_secchan_state(const struct dict *);
static void show_fail_open_state(const struct dict *);
static void show_discovery_state(const struct dict *);
static void show_remote_state(const struct dict *);
static void show_data_rates(struct rconn *, const struct dict *);
static void init_reboot_notifier(void);
static bool show_reboot_state(void);
static void show_string(const char *string);
static void block_until(long long timeout);
static void menu(const struct dict *);
static void drain_keyboard_buffer(void);
static const char *progress(void);
int
main(int argc, char *argv[])
{
struct rconn *rconn;
struct message *msg;
int countdown = 5;
bool user_selected;
bool debug_mode;
/* Tracking keystroke repeat counts. */
int last_key = 0;
long long int last_key_time = 0;
int repeat_count = 0;
set_program_name(argv[0]);
time_init();
vlog_init();
parse_options(argc, argv);
signal(SIGPIPE, SIG_IGN);
vlog_set_levels(VLM_ANY_MODULE, VLF_CONSOLE, VLL_EMER);
init_reboot_notifier();
argc -= optind;
argv += optind;
if (argc != 1) {
ovs_fatal(0, "exactly one non-option argument required; "
"use --help for help");
}
rconn = rconn_new(argv[0], 5, 5);
die_if_already_running();
daemonize();
initialize_terminal();
fatal_signal_add_hook(restore_terminal, NULL, true);
msg = NULL;
countdown = 0;
user_selected = false;
debug_mode = false;
for (;;) {
struct dict dict;
long long timeout = time_msec() + 1000;
clear_messages();
dict_init(&dict);
fetch_status(rconn, &dict, timeout);
dict_add(&dict, "debug", debug_mode ? "true" : "false");
compose_messages(&dict, rconn);
if (countdown) {
if (!empty_message(msg)) {
countdown--;
} else {
msg = user_selected ? next_message(msg) : best_message();
countdown = 5;
}
} else {
msg = best_message();
countdown = 5;
user_selected = false;
}
if (!user_selected) {
message_shown(msg);
}
do {
for (;;) {
int c = getch();
if (c == ERR) {
break;
}
if (c != last_key || time_msec() > last_key_time + 250) {
repeat_count = 0;
}
last_key = c;
last_key_time = time_msec();
repeat_count++;
if (c == KEY_DOWN || c == KEY_UP) {
msg = (c == KEY_DOWN ? next_message(msg)
: prev_message(msg));
countdown = 5;
user_selected = true;
} else if (c == '\r' || c == '\n') {
countdown = 60;
user_selected = true;
if (repeat_count >= 20) {
debug_mode = !debug_mode;
show_string(debug_mode
? "Debug Mode\nEnabled"
: "Debug Mode\nDisabled");
}
} else if (c == '\b' || c == '\x7f' ||
c == '\x1b' || c == KEY_BACKSPACE || c == KEY_DC) {
menu(&dict);
drain_keyboard_buffer();
break;
}
}
erase();
curs_set(0);
move(0, 0);
put_message(msg);
refresh();
poll_fd_wait(STDIN_FILENO, POLLIN);
poll_timer_wait(timeout - time_msec());
poll_block();
} while (time_msec() < timeout);
age_messages();
dict_free(&dict);
}
return 0;
}
static void
compose_messages(const struct dict *dict, struct rconn *rconn)
{
if (!show_reboot_state()) {
show_flows(rconn);
show_dpid_ip(rconn, dict);
show_secchan_state(dict);
show_fail_open_state(dict);
show_discovery_state(dict);
show_remote_state(dict);
show_data_rates(rconn, dict);
}
}
struct put_flows_data {
struct rconn *rconn;
uint32_t xid;
uint32_t flow_count;
bool got_reply;
};
static void
parse_flow_reply(void *data, struct put_flows_data *pfd)
{
struct ofp_header *oh;
struct ofp_stats_reply *rpy;
struct ofp_aggregate_stats_reply *asr;
const size_t min_size = sizeof *rpy + sizeof *asr;
oh = data;
if (ntohs(oh->length) < min_size) {
VLOG_WARN("reply is too short (%"PRIu16")", ntohs(oh->length));
return;
}
if (oh->xid != pfd->xid) {
VLOG_WARN("xid 0x%08"PRIx32" != expected 0x%08"PRIx32,
oh->xid, pfd->xid);
return;
}
if (oh->type != OFPT_STATS_REPLY) {
VLOG_WARN("reply is wrong type %"PRIu8, oh->type);
return;
}
rpy = data;
if (rpy->type != htons(OFPST_AGGREGATE)) {
VLOG_WARN("reply has wrong stat type ID %08"PRIx16, rpy->type);
return;
}
asr = (struct ofp_aggregate_stats_reply *) rpy->body;
pfd->flow_count = ntohl(asr->flow_count);
pfd->got_reply = true;
}
static bool
have_icons(void)
{
const char *dico = tigetstr("dico");
return dico && dico != (const char *) -1;
}
static void
set_icon(int num, int r0, int r1, int r2, int r3, int r4, int r5, int r6,
int r7)
{
if (have_icons()) {
putp(tparm(tigetstr("dico"), num, r0, r1, r2, r3, r4, r5, r6, r7));
}
}
static void
set_repeated_icon(int num, int row)
{
set_icon(num, row, row, row, row, row, row, row, row);
}
#if 0
static void
set_brick_icon(int num, int n_solid)
{
const static int rows[6] = {_____, X____, XX___, XXX__, XXXX_, XXXXX};
set_repeated_icon(num, rows[n_solid < 0 ? 0
: n_solid > 5 ? 5
: n_solid]);
}
#endif
static int
icon_char(int num, int alternate)
{
return have_icons() ? 0x80 | num | A_ALTCHARSET : alternate;
}
static void
put_icon(int num, char alternate)
{
addch(icon_char(num, alternate));
}
#if 0
static void
bar_graph(int n_chars, int n_pixels)
{
int i;
if (n_pixels < 0) {
n_pixels = 0;
} else if (n_pixels > n_chars * 5) {
n_pixels = n_chars * 5;
}
if (n_pixels > 5) {
set_brick_icon(0, 5);
for (i = 0; i < n_pixels / 5; i++) {
put_icon(0, "#");
}
}
if (n_pixels % 5) {
set_brick_icon(1, n_pixels % 5);
put_icon(1, "#");
}
}
#endif
static void
put_flows(void *pfd_)
{
struct put_flows_data *pfd = pfd_;
static struct rconn_packet_counter *counter;
char host[64];
if (!counter) {
counter = rconn_packet_counter_create();
}
if (!pfd->xid) {
struct ofp_stats_request *rq;
struct ofp_aggregate_stats_request *asr;
struct ofpbuf *b;
pfd->xid = random_uint32();
rq = make_openflow_xid(sizeof *rq, OFPT_STATS_REQUEST,
pfd->xid, &b);
rq->type = htons(OFPST_AGGREGATE);
rq->flags = htons(0);
asr = ofpbuf_put_uninit(b, sizeof *asr);
memset(asr, 0, sizeof *asr);
asr->match.wildcards = htonl(OFPFW_ALL);
asr->table_id = 0xff;
asr->out_port = htons(OFPP_NONE);
update_openflow_length(b);
rconn_send_with_limit(pfd->rconn, b, counter, 10);
}
if (!pfd->got_reply) {
int i;
rconn_run(pfd->rconn);
for (i = 0; i < 50; i++) {
struct ofpbuf *b;
b = rconn_recv(pfd->rconn);
if (!b) {
break;
}
parse_flow_reply(b->data, pfd);
ofpbuf_delete(b);
if (pfd->got_reply) {
break;
}
}
}
gethostname(host, sizeof host);
host[sizeof host - 1] = '\0';
if (strlen(host) + 6 <= 16) {
addf("Host: %s\n", host);
} else {
addf("%s\n", host);
}
if (pfd->got_reply) {
addf("Flows: %"PRIu32, pfd->flow_count);
}
if (!pfd->got_reply) {
rconn_run_wait(pfd->rconn);
rconn_recv_wait(pfd->rconn);
}
}
static void
show_flows(struct rconn *rconn)
{
static struct message *m;
static struct put_flows_data pfd;
memset(&pfd, 0, sizeof pfd);
pfd.rconn = rconn;
emit_function(&m, P_STATUS, put_flows, &pfd);
}
struct put_dpid_ip_data {
struct rconn *rconn;
uint32_t xid;
uint64_t dpid;
char ip[16];
bool got_reply;
};
static void
parse_dp_reply(void *data, struct put_dpid_ip_data *pdid)
{
struct ofp_switch_features *osf;
struct ofp_header *oh;
oh = data;
if (ntohs(oh->length) < sizeof *osf) {
VLOG_WARN("reply is too short (%"PRIu16")", ntohs(oh->length));
return;
}
if (oh->xid != pdid->xid) {
VLOG_WARN("xid 0x%08"PRIx32" != expected 0x%08"PRIx32,
oh->xid, pdid->xid);
return;
}
if (oh->type != OFPT_FEATURES_REPLY) {
VLOG_WARN("reply is wrong type %"PRIu8, oh->type);
return;
}
osf = data;
pdid->dpid = ntohll(osf->datapath_id);
pdid->got_reply = true;
}
static void
put_dpid_id(void *pdid_)
{
struct put_dpid_ip_data *pdid = pdid_;
static struct rconn_packet_counter *counter;
if (!counter) {
counter = rconn_packet_counter_create();
}
if (!pdid->xid) {
struct ofp_header *oh;
struct ofpbuf *b;
pdid->xid = random_uint32();
oh = make_openflow_xid(sizeof *oh, OFPT_FEATURES_REQUEST,
pdid->xid, &b);
rconn_send_with_limit(pdid->rconn, b, counter, 10);
}
if (!pdid->got_reply) {
int i;
rconn_run(pdid->rconn);
for (i = 0; i < 50; i++) {
struct ofpbuf *b;
b = rconn_recv(pdid->rconn);
if (!b) {
break;
}
parse_dp_reply(b->data, pdid);
ofpbuf_delete(b);
if (pdid->got_reply) {
break;
}
}
}
addf("DP: ");
if (pdid->got_reply) {
addf("%012"PRIx64, pdid->dpid);
}
addf("\nIP: %s", pdid->ip);
if (!pdid->got_reply) {
rconn_run_wait(pdid->rconn);
rconn_recv_wait(pdid->rconn);
}
}
static void
show_dpid_ip(struct rconn *rconn, const struct dict *dict)
{
static struct message *m;
static struct put_dpid_ip_data pdid;
const char *is_connected, *local_ip;
dict_lookup(dict, "local.is-connected", &is_connected);
dict_lookup(dict, "in-band.local-ip", &local_ip);
if (!is_connected && !local_ip) {
/* If we're not connected to the datapath and don't have a local IP,
* then we won't have anything useful to show anyhow. */
return;
}
memset(&pdid, 0, sizeof pdid);
pdid.rconn = rconn;
ovs_strlcpy(pdid.ip, local_ip ? local_ip : "", sizeof pdid.ip);
emit_function(&m, P_STATUS, put_dpid_id, &pdid);
}
static size_t
dict_find(const struct dict *dict, const char *name)
{
size_t i;
for (i = 0; i < dict->n; i++) {
const struct pair *p = &dict->pairs[i];
if (!strcmp(p->name, name)) {
return i;
}
}
return SIZE_MAX;
}
static bool
dict_lookup(const struct dict *dict, const char *name, const char **value)
{
size_t idx = dict_find(dict, name);
if (idx != SIZE_MAX) {
*value = dict->pairs[idx].value;
return true;
} else {
*value = NULL;
return false;
}
}
static const char *
dict_get(const struct dict *dict, const char *name)
{
const char *value;
return dict_lookup(dict, name, &value) ? value : NULL;
}
static int
dict_get_int(const struct dict *dict, const char *name, int def)
{
const char *value;
return dict_lookup(dict, name, &value) ? atoi(value) : def;
}
static bool
dict_get_bool(const struct dict *dict, const char *name, bool def)
{
const char *value;
if (dict_lookup(dict, name, &value)) {
if (!strcmp(value, "true")) {
return true;
}
if (!strcmp(value, "false")) {
return false;
}
}
return def;
}
static const char *
dict_get_string(const struct dict *dict, const char *name, const char *def)
{
const char *value;
return dict_lookup(dict, name, &value) ? value : def;
}
static uint32_t
dict_get_ip(const struct dict *dict, const char *name)
{
struct in_addr in;
return (inet_aton(dict_get_string(dict, name, ""), &in) ? in.s_addr
: htonl(0));
}
static void
addf(const char *format, ...)
{
char buf[128];
va_list args;
va_start(args, format);
vsnprintf(buf, sizeof buf, format, args);
va_end(args);
addstr(buf);
}
static void
show_secchan_state(const struct dict *dict)
{
static struct message *msg;
const char *is_connected;
if (!dict_lookup(dict, "remote.is-connected", &is_connected)) {
/* Secchan not running or not responding. */
emit(&msg, P_ERROR, "Switch disabled");
}
}
static const char *
discovery_state_label(const char *name)
{
static struct dict *states;
if (!states) {
states = xmalloc(sizeof *states);
dict_init(states);
dict_add(states, "INIT", "Init");
dict_add(states, "INIT_REBOOT", "Init");
dict_add(states, "REBOOTING", "Init");
dict_add(states, "SELECTING", "Searching");
dict_add(states, "REQUESTING", "Requesting");
dict_add(states, "BOUND", "Got");
dict_add(states, "RENEWING", "Renewing");
dict_add(states, "REBINDING", "Rebinding");
dict_add(states, "RELEASED", "Released");
}
return dict_get_string(states, name, "Error");
}
static void
show_discovery_state(const struct dict *dict)
{
static struct message *m_bound, *m_other;
struct message **m;
const char *state, *ip;
enum priority priority;
int state_elapsed;
state = dict_get_string(dict, "discovery.state", NULL);
if (!state) {
return;
}
ip = dict_get_string(dict, "discovery.ip", NULL);
state_elapsed = dict_get_int(dict, "discovery.state-elapsed", 0);
if (!strcmp(state, "BOUND")) {
m = &m_bound;
priority = P_STATUS;
} else {
m = &m_other;
priority = P_PROGRESS;
}
emit(m, priority, "Discovery %s\n%s",
progress(), discovery_state_label(state));
if (ip) {
emit(m, priority, " %s", ip);
}
}
static void
human_time(int seconds, char *buf, size_t size)
{
const char *sign = "";
if (seconds < 0) {
sign = "-";
seconds = seconds == INT_MIN ? INT_MAX : -seconds;
}
if (seconds <= 60) {
snprintf(buf, size, "%s%d s", sign, seconds);
} else if (seconds <= 60 * 60) {
snprintf(buf, size, "%s%d min", sign, seconds / 60);
} else if (seconds <= 60 * 60 * 24 * 2) {
snprintf(buf, size, "%s%d h", sign, seconds / 60 / 60);
} else {
snprintf(buf, size, "%s%d days", sign, seconds / 60 / 60 / 24);
}
}
static void
show_fail_open_state(const struct dict *dict)
{
static struct message *m;
int cur_duration, trigger_duration;
if (!dict_get_bool(dict, "fail-open.triggered", false)) {
return;
}
trigger_duration = dict_get_int(dict, "fail-open.trigger-duration", 0);
cur_duration = dict_get_int(dict, "fail-open.current-duration", 0);
if (shown(&m) < 5) {
emit(&m, P_WARNING, "Failed open %s\nafter %d secs",
progress(), trigger_duration);
} else {
char buf[16];
human_time(cur_duration - trigger_duration, buf, sizeof buf);
emit(&m, P_WARNING, "In fail open for\n%s now %s", buf, progress());
}
}
static const char *
progress(void)
{
return "..." + (3 - (unsigned int) time_now() % 4);
}
static void
show_remote_state(const struct dict *dict)
{
bool debug_mode = dict_get_bool(dict, "debug", false);
const char *state, *is_connected;
state = dict_get_string(dict, "remote.state", NULL);
if (!state) {
return;
}
is_connected = dict_get_string(dict, "remote.is-connected", "false");
if (!strcmp(is_connected, "true")) {
if (debug_mode) {
static struct message *m_connected;
char buf[16];
human_time(dict_get_int(dict, "remote.last-connection", 0),
buf, sizeof buf);
emit(&m_connected, P_STATUS,
"Connected for\nlast %s %s", buf, progress());
}
if (!strcmp(state, "IDLE")) {
static struct message *m_idle;
emit(&m_idle, P_PROGRESS, "Sent idle probe");
}
if (debug_mode) {
const char *name = dict_get_string(dict, "remote.name", NULL);
if (name) {
static struct message *m_name;
emit(&m_name, P_STATUS, "Connected to\n%s", name);
}
}
} else {
int elapsed, backoff;
const char *name, *error;
elapsed = dict_get_int(dict, "remote.state-elapsed", 0);
backoff = dict_get_int(dict, "remote.backoff", 0);
name = dict_get_string(dict, "remote.name", "unknown");
state = dict_get_string(dict, "remote.state", "VOID");
error = dict_get_string(dict, "remote.last-connect-error", NULL);
if (!strcmp(state, "VOID")) {
static struct message *m;
emit(&m, P_PROGRESS, "Controller not\nfound");
} else if (!strcmp(state, "BACKOFF")) {
static struct message *m[3];
char buf[16];
if (error) {
emit(&m[0], P_PROGRESS, "Connect failed:\n%s", error);
}
emit(&m[2], P_STATUS, "Last connected\n%s ago", buf);
emit(&m[1], P_PROGRESS,
"Disconnected\nReconnect in %d", backoff - elapsed);
human_time(dict_get_int(dict, "remote.last-connection", 0),
buf, sizeof buf);
} else if (!strcmp(state, "CONNECTING")) {
static struct message *m;
emit(&m, P_PROGRESS, "Connecting %s\n%s", progress(), name);
}
}
}
static void
fetch_status(struct rconn *rconn, struct dict *dict, long long timeout)
{
static struct rconn_packet_counter *counter;
static uint32_t xid;
struct nicira_header *rq;
struct ofpbuf *b;
int retval;
if (!counter) {
counter = rconn_packet_counter_create();
}
if (!xid) {
xid = random_uint32();
}
rq = make_openflow_xid(sizeof *rq, OFPT_VENDOR, ++xid, &b);
rq->vendor = htonl(NX_VENDOR_ID);
rq->subtype = htonl(NXT_STATUS_REQUEST);
retval = rconn_send_with_limit(rconn, b, counter, 10);
if (retval) {
/* continue into the loop so that we pause for a while */
}
while (time_msec() < timeout) {
int i;
rconn_run(rconn);
for (i = 0; i < 50; i++) {
struct ofpbuf *b;
bool got_reply;
b = rconn_recv(rconn);
if (!b) {
break;
}
got_reply = parse_reply(b->data, dict, xid);
ofpbuf_delete(b);
if (got_reply) {
return;
}
}
rconn_run_wait(rconn);
rconn_recv_wait(rconn);
poll_timer_wait(timeout - time_msec());
poll_block();
}
}
static bool
parse_reply(void *data, struct dict *dict, uint32_t xid)
{
struct ofp_header *oh;
struct nicira_header *rpy;
oh = data;
if (ntohs(oh->length) < sizeof *rpy) {
VLOG_WARN("reply is too short (%"PRIu16")", ntohs(oh->length));
return false;
}
if (oh->xid != xid) {
VLOG_WARN("xid 0x%08"PRIx32" != expected 0x%08"PRIx32, oh->xid, xid);
return false;
}
if (oh->type != OFPT_VENDOR) {
VLOG_WARN("reply is wrong type %"PRIu8, oh->type);
return false;
}
rpy = data;
if (rpy->vendor != htonl(NX_VENDOR_ID)) {
VLOG_WARN("reply has wrong vendor ID %08"PRIx32, rpy->vendor);
return false;
}
if (rpy->subtype != htonl(NXT_STATUS_REPLY)) {
VLOG_WARN("reply has wrong subtype %08"PRIx32, rpy->subtype);
return false;
}
dict_parse(dict, (const char *) (rpy + 1),
ntohs(oh->length) - sizeof *rpy);
return true;
}
static void
dict_parse(struct dict *dict, const char *data, size_t nbytes)
{
char *save_ptr = NULL;
char *copy, *name;
copy = xmemdup0(data, nbytes);
for (name = strtok_r(copy, "=", &save_ptr); name;
name = strtok_r(NULL, "=", &save_ptr))
{
char *value = strtok_r(NULL, "\n", &save_ptr);
if (!value) {
break;
}
dict_add(dict, name, value);
}
free(copy);
}
static void
dict_init(struct dict *dict)
{
dict->n = 0;
dict->max = 16;
dict->pairs = xmalloc(sizeof *dict->pairs * dict->max);
}
static void
dict_add(struct dict *dict, const char *name, const char *value)
{
dict_add_nocopy(dict, xstrdup(name), xstrdup(value));
}
static void
dict_add_nocopy(struct dict *dict, char *name, char *value)
{
struct pair *p;
if (dict->n >= dict->max) {
dict->max *= 2;
dict->pairs = xrealloc(dict->pairs, sizeof *dict->pairs * dict->max);
}
p = &dict->pairs[dict->n++];
p->name = name;
p->value = value;
}
static void
dict_delete(struct dict *dict, const char *name)
{
size_t idx;
while ((idx = dict_find(dict, name)) != SIZE_MAX) {
struct pair *pair = &dict->pairs[idx];
free(pair->name);
free(pair->value);
dict->pairs[idx] = dict->pairs[--dict->n];
}
}
static void
dict_free(struct dict *dict)
{
if (dict) {
size_t i;
for (i = 0; i < dict->n; i++) {
free(dict->pairs[i].name);
free(dict->pairs[i].value);
}
free(dict->pairs);
}
}
static void
initialize_terminal(void)
{
initscr();
cbreak();
noecho();
nonl();
intrflush(stdscr, FALSE);
keypad(stdscr, TRUE);
nodelay(stdscr, TRUE);
typeahead(-1);
scrollok(stdscr, TRUE);
}
static void
restore_terminal(void *aux UNUSED)
{
endwin();
}
struct byte_count {
long long int when;
uint64_t tx_bytes;
};
struct show_rates_data {
struct rconn *rconn;
uint32_t xid;
struct byte_count prev, now;
bool got_reply;
};
static void
parse_port_reply(void *data, struct show_rates_data *rates)
{
struct ofp_header *oh;
struct ofp_stats_reply *rpy;
struct ofp_port_stats *ops;
size_t n_ports;
size_t i;
oh = data;
if (ntohs(oh->length) < sizeof *rpy) {
VLOG_WARN("reply is too short (%"PRIu16")", ntohs(oh->length));
return;
}
if (oh->xid != rates->xid) {
VLOG_WARN("xid 0x%08"PRIx32" != expected 0x%08"PRIx32,
oh->xid, rates->xid);
return;
}
if (oh->type != OFPT_STATS_REPLY) {
VLOG_WARN("reply is wrong type %"PRIu8, oh->type);
return;
}
rpy = data;
if (rpy->type != htons(OFPST_PORT)) {
VLOG_WARN("reply has wrong stat type ID %08"PRIx16, rpy->type);
return;
}
n_ports = ((ntohs(oh->length) - offsetof(struct ofp_stats_reply, body))
/ sizeof *ops);
ops = (struct ofp_port_stats *) rpy->body;
rates->prev = rates->now;
rates->now.when = time_msec();
rates->now.tx_bytes = UINT64_MAX;
for (i = 0; i < n_ports; i++, ops++) {
if (ops->tx_bytes != htonll(UINT64_MAX)) {
if (rates->now.tx_bytes == UINT64_MAX) {
rates->now.tx_bytes = 0;
}
rates->now.tx_bytes += ntohll(ops->tx_bytes);
}
}
rates->got_reply = true;
}
static void
dump_graph(const bool graph[80])
{
signed char icons[32];
int n_icons = 3;
int i;
memset(icons, -1, sizeof icons);
for (i = 0; i < 16; i++) {
uint8_t row;
int j;
row = 0;
for (j = 0; j < 5; j++) {
row = (row << 1) | graph[i * 5 + j];
}
if (!row) {
addch(' ');
continue;
}
if (icons[row] < 0) {
if (n_icons >= 8) {
addch('X');
continue;
}
set_repeated_icon(n_icons, row);
icons[row] = n_icons++;
}
put_icon(icons[row], row == 0x1f ? '#' : ' ');
}
}
static void
do_show_data_rates(void *rates_)
{
struct show_rates_data *rates = rates_;
static struct rconn_packet_counter *counter;
bool graph[80];
if (!counter) {
counter = rconn_packet_counter_create();
}
if (!rates->xid) {
struct ofp_stats_request *rq;
struct ofpbuf *b;
rates->xid = random_uint32();
rq = make_openflow_xid(sizeof *rq, OFPT_STATS_REQUEST,
rates->xid, &b);
rq->type = htons(OFPST_PORT);
rq->flags = htons(0);
rconn_send_with_limit(rates->rconn, b, counter, 10);
}
if (!rates->got_reply) {
int i;
rconn_run(rates->rconn);
for (i = 0; i < 50; i++) {
struct ofpbuf *b;
b = rconn_recv(rates->rconn);
if (!b) {
break;
}
parse_port_reply(b->data, rates);
ofpbuf_delete(b);
if (rates->got_reply) {
break;
}
}
}
set_icon(0,
e_____,
e_____,
e_____,
e__X__,
e__X__,
e__X_X,
e__XX_,
e__X_X);
set_icon(1,
e_____,
e_____,
e_____,
eX___X,
eXX_XX,
eX_X_X,
eX___X,
eX___X);
set_icon(2,
e_____,
e_____,
e_____,
e_XXX_,
eX____,
eX_XXX,
eX___X,
e_XXX_);
memset(graph, 0, sizeof graph);
graph[24] = 1;
graph[48] = 1;
graph[72] = 1;
addstr("TX: ");
put_icon(0, 'k');
addstr(" ");
put_icon(1, 'M');
addstr(" ");
put_icon(2, 'G');
addch('\n');
if (rates->now.tx_bytes != UINT64_MAX
&& rates->prev.tx_bytes != UINT64_MAX
&& rates->now.when - rates->prev.when > 500
&& time_msec() - rates->now.when < 2000)
{
uint64_t bits = (rates->now.tx_bytes - rates->prev.tx_bytes) * 8;
uint64_t msecs = rates->now.when - rates->prev.when;
double bps = (double) bits * 1000.0 / msecs;
int pixels = bps > 0 ? log(bps) / log(10.0) * 8 + .5 : 0;
if (pixels < 0) {
pixels = 0;
} else if (pixels > 80) {
pixels = 80;
}
memset(graph, 1, pixels);
}
dump_graph(graph);
if (!rates->got_reply) {
rconn_run_wait(rates->rconn);
rconn_recv_wait(rates->rconn);
}
}
static void
show_data_rates(struct rconn *rconn, const struct dict *dict)
{
static struct message *m;
static struct show_rates_data rates;
const char *is_connected, *local_ip;
static bool inited = false;
dict_lookup(dict, "local.is-connected", &is_connected);
dict_lookup(dict, "in-band.local-ip", &local_ip);
if (!is_connected && !local_ip) {
/* If we're not connected to the datapath and don't have a local IP,
* then we won't have anything useful to show anyhow. */
return;
}
rates.rconn = rconn;
rates.xid = 0;
rates.got_reply = false;
if (!inited) {
rates.now.tx_bytes = UINT64_MAX;
rates.prev.tx_bytes = UINT64_MAX;
inited = true;
}
emit_function(&m, P_STATUS, do_show_data_rates, &rates);
}
struct message {
/* Content. */
void (*function)(void *aux);
void *aux;
char string[128];
size_t index;
enum priority priority;
int age;
int shown;
};
static struct message **messages;
static size_t n_messages, allocated_messages;
static struct message *
allocate_message(struct message **msgp)
{
if (!*msgp) {
/* Allocate and initialize message. */
*msgp = xcalloc(1, sizeof **msgp);
(*msgp)->index = n_messages;
/* Add to list of messages. */
if (n_messages >= allocated_messages) {
allocated_messages = 2 * allocated_messages + 1;
messages = xrealloc(messages,
sizeof *messages * allocated_messages);
}
messages[n_messages++] = *msgp;
}
return *msgp;
}
static void
emit(struct message **msgp, enum priority priority, const char *format, ...)
{
struct message *msg = allocate_message(msgp);
va_list args;
size_t length;
msg->priority = priority;
va_start(args, format);
length = strlen(msg->string);
vsnprintf(msg->string + length, sizeof msg->string - length, format, args);
va_end(args);
}
static void
emit_function(struct message **msgp, enum priority priority,
void (*function)(void *aux), void *aux)
{
struct message *msg = allocate_message(msgp);
msg->priority = priority;
msg->function = function;
msg->aux = aux;
}
static int
shown(struct message **msgp)
{
struct message *msg = allocate_message(msgp);
return msg->shown;
}
static void
clear_messages(void)
{
size_t i;
for (i = 0; i < n_messages; i++) {
struct message *msg = messages[i];
msg->string[0] = '\0';
msg->function = NULL;
}
}
static struct message *
best_message(void)
{
struct message *best_msg;
int best_score;
size_t i;
best_score = INT_MIN;
best_msg = NULL;
for (i = 0; i < n_messages; i++) {
struct message *msg = messages[i];
int score;
if (empty_message(msg)) {
continue;
}
score = msg->priority;
if (!msg->shown) {
score += msg->age;
} else {
score -= msg->shown;
}
if (score > best_score) {
best_score = score;
best_msg = msg;
}
}
return best_msg;
}
static void
message_shown(struct message *msg)
{
if (msg && msg->shown++ > 3600) {
msg->shown = 0;
}
}
static bool
empty_message(const struct message *msg)
{
return !msg || (!msg->string[0] && !msg->function);
}
static struct message *get_message(size_t index)
{
assert(index <= n_messages || index == SIZE_MAX);
return (index < n_messages ? messages[index]
: index == SIZE_MAX ? messages[n_messages - 1]
: messages[0]);
}
static struct message *
next_message(struct message *msg)
{
struct message *p;
for (p = get_message(msg->index + 1); p != msg;
p = get_message(p->index + 1)) {
if (!empty_message(p)) {
break;
}
}
return p;
}
static struct message *
prev_message(struct message *msg)
{
struct message *p;
for (p = get_message(msg->index - 1); p != msg;
p = get_message(p->index - 1)) {
if (!empty_message(p)) {
break;
}
}
return p;
}
static void
put_message(const struct message *m)
{
if (m->string[0]) {
addstr(m->string);
} else if (m->function) {
m->function(m->aux);
}
}
static void
age_messages(void)
{
size_t i;
int load;
load = 0;
for (i = 0; i < n_messages; i++) {
struct message *msg = messages[i];
if (!empty_message(msg)) {
load++;
}
}
for (i = 0; i < n_messages; i++) {
struct message *msg = messages[i];
if (empty_message(msg)) {
msg->age = msg->shown = 0;
} else {
if (msg->age && msg->age % 60 == 0) {
msg->shown -= MAX(0, 5 - (load + 6) / 12);
if (msg->shown < 0) {
msg->shown = 0;
}
}
if (msg->age++ > 3600) {
msg->age = 0;
}
}
}
}
/* Set by SIGUSR1 handler. */
static volatile sig_atomic_t sigusr1_triggered;
/* The time after which we stop indicating that the switch is rebooting.
* (This is just in case the reboot fails.) */
static time_t reboot_deadline = TIME_MIN;
static void sigusr1_handler(int);
static void
init_reboot_notifier(void)
{
signal(SIGUSR1, sigusr1_handler);
}
static void
sigusr1_handler(int signr UNUSED)
{
sigusr1_triggered = true;
}
static bool
show_reboot_state(void)
{
if (sigusr1_triggered) {
reboot_deadline = time_now() + 30;
sigusr1_triggered = false;
}
if (time_now() < reboot_deadline) {
static struct message *msg;
emit(&msg, P_FATAL, "Rebooting");
return true;
}
return false;
}
struct menu_item {
char *text;
void (*f)(const struct dict *);
int id;
bool enabled;
int toggle;
};
struct menu {
struct menu_item **items;
size_t n_items, allocated_items;
};
static void menu_init(struct menu *);
static void menu_free(struct menu *);
static struct menu_item *menu_add_item(struct menu *, const char *text, ...)
PRINTF_FORMAT(2, 3);
static int menu_show(const struct menu *, int start, bool select);
static void cmd_shell(const struct dict *);
static void cmd_show_version(const struct dict *);
static void cmd_configure(const struct dict *);
static void cmd_setup_pki(const struct dict *);
static void cmd_browse_status(const struct dict *);
static void cmd_show_motto(const struct dict *);
static void
menu_init(struct menu *menu)
{
memset(menu, 0, sizeof *menu);
}
static void
menu_free(struct menu *menu)
{
size_t i;
for (i = 0; i < menu->n_items; i++) {
struct menu_item *item = menu->items[i];
free(item->text);
free(item);
}
free(menu->items);
}
static struct menu_item *
menu_add_item(struct menu *menu, const char *text, ...)
{
struct menu_item *item;
va_list args;
if (menu->n_items >= menu->allocated_items) {
menu->allocated_items = 2 * menu->allocated_items + 1;
menu->items = xrealloc(menu->items,
sizeof *menu->items * menu->allocated_items);
}
item = menu->items[menu->n_items++] = xmalloc(sizeof *item);
va_start(args, text);
item->text = xvasprintf(text, args);
va_end(args);
item->f = NULL;
item->id = -1;
item->enabled = true;
item->toggle = -1;
return item;
}
static void
menu(const struct dict *dict)
{
bool debug_mode = dict_get_bool(dict, "debug", false);
struct menu menu;
int choice;
menu_init(&menu);
menu_add_item(&menu, "Exit");
menu_add_item(&menu, "Show Version")->f = cmd_show_version;
menu_add_item(&menu, "Configure")->f = cmd_configure;
menu_add_item(&menu, "Setup PKI")->f = cmd_setup_pki;
if (debug_mode) {
menu_add_item(&menu, "Browse Status")->f = cmd_browse_status;
menu_add_item(&menu, "Shell")->f = cmd_shell;
menu_add_item(&menu, "Show Motto")->f = cmd_show_motto;
}
choice = menu_show(&menu, 0, true);
if (choice >= 0) {
void (*f)(const struct dict *) = menu.items[choice]->f;
if (f) {
(f)(dict);
}
}
menu_free(&menu);
}
static int
menu_show(const struct menu *menu, int start, bool select)
{
long long int adjust = LLONG_MAX;
int min = 0, max = MAX(menu->n_items - 2, 0);
int pos, selection;
set_icon(0,
eXX___,
eXXX__,
eXXXX_,
eXXXXX,
eXXXX_,
eXXX__,
eXX___,
e_____);
set_icon(1,
eXXXXX,
eX___X,
eX___X,
eX___X,
eX___X,
eX___X,
eXXXXX,
e_____);
set_icon(2,
eXXXXX,
eX___X,
eXX_XX,
eX_X_X,
eXX_XX,
eX___X,
eXXXXX,
e_____);
if (menu->n_items) {
pos = MIN(menu->n_items - 1, MAX(0, start));
selection = pos;
} else {
pos = 0;
selection = -1;
}
for (;;) {
int key;
while ((key = getch()) != ERR) {
switch (key) {
case KEY_UP:
if (select && selection > 0) {
selection--;
if (selection >= pos) {
break;
}
}
if (pos >= min) {
pos--;
}
break;
case KEY_DOWN:
if (select && selection < menu->n_items - 1) {
selection++;
if (selection <= pos + 1) {
break;
}
}
if (pos <= max) {
pos++;
}
break;
case '\r': case '\n':
if (select && selection >= 0 && selection < menu->n_items) {
struct menu_item *item = menu->items[selection];
if (!item->enabled) {
show_string("Item disabled");
break;
} else if (item->toggle >= 0) {
item->toggle = !item->toggle;
break;
}
}
return selection;
case '\b': case '\x7f': case '\x1b':
case KEY_BACKSPACE: case KEY_DC:
return -1;
}
adjust = time_msec() + 1000;
}
if (time_msec() >= adjust && menu->n_items > 1) {
if (pos < min) {
pos = min;
} else if (pos > max) {
pos = max;
}
}
erase();
curs_set(0);
move(0, 0);
if (!menu->n_items) {
addstr("[Empty]");
} else {
int idx;
for (idx = pos; idx < pos + 2; idx++) {
size_t width = 40;
if (select) {
width--;
if (selection == idx) {
put_icon(0, '>');
} else {
addch(' ');
}
}
if (idx < 0) {
addstr("[Top]");
} else if (idx >= menu->n_items) {
addstr("[Bottom]");
} else {
const struct menu_item *item = menu->items[idx];
size_t length = strlen(item->text);
if (!item->enabled) {
width -= 2;
addch('(');
}
if (item->toggle >= 0) {
if (have_icons()) {
addch(icon_char(item->toggle ? 2 : 1, 0));
width--;
} else {
addstr(item->toggle ? "[X]" : "[ ]");
width -= 3;
}
}
addnstr(item->text, MIN(width, length));
if (!item->enabled) {
addch(')');
}
}
if (idx == pos) {
addch('\n');
}
}
}
refresh();
if (pos < min || pos > max) {
poll_timer_wait(adjust - time_msec());
}
poll_fd_wait(STDIN_FILENO, POLLIN);
poll_block();
}
}
static int
menu_show2(const struct menu *menu, int start, bool select)
{
int pos;
if (menu->n_items) {
pos = MIN(menu->n_items - 1, MAX(0, start));
} else {
pos = -1;
}
set_icon(0,
e__X__,
e_XXX_,
eXXXXX,
e__X__,
e__X__,
e__X__,
e__X__,
e__X__);
set_icon(1,
e__X__,
e__X__,
e__X__,
e__X__,
e__X__,
eXXXXX,
e_XXX_,
e__X__);
for (;;) {
int key;
while ((key = getch()) != ERR) {
switch (key) {
case KEY_UP:
if (pos > 0) {
pos--;
}
break;
case KEY_DOWN:
if (menu->n_items > 0 && pos < menu->n_items - 1) {
pos++;
}
break;
case '\r': case '\n':
if (select && !menu->items[pos]->enabled) {
show_string("Item disabled");
break;
}
return pos;
case '\b': case '\x7f': case '\x1b':
case KEY_BACKSPACE: case KEY_DC:
return -1;
}
}
erase();
curs_set(0);
move(0, 0);
if (pos == -1) {
addstr("[Empty]");
} else {
const struct menu_item *item = menu->items[pos];
const char *line1 = item->text;
size_t len1 = strcspn(line1, "\n");
const char *line2 = line1[len1] ? &line1[len1 + 1] : "";
size_t len2 = strcspn(line2, "\n");
size_t width = 39 - 2 * !item->enabled;
/* First line. */
addch(pos > 0 ? icon_char(0, '^') : ' ');
if (!item->enabled && len1) {
addch('(');
}
addnstr(line1, MIN(len1, width));
if (!item->enabled && len1) {
addch(')');
}
addch('\n');
/* Second line. */
addch(pos < menu->n_items - 1 ? icon_char(1, 'V') : ' ');
if (!item->enabled && len2) {
addch('(');
}
addnstr(line2, MIN(len2, width));
if (!item->enabled && len2) {
addch(')');
}
}
refresh();
poll_fd_wait(STDIN_FILENO, POLLIN);
poll_block();
}
}
static bool
yesno(const char *title, bool def)
{
bool answer = def;
set_icon(0,
eXX___,
eXXX__,
eXXXX_,
eXXXXX,
eXXXX_,
eXXX__,
eXX___,
e_____);
for (;;) {
int key;
while ((key = getch()) != ERR) {
switch (key) {
case KEY_UP:
case KEY_DOWN:
case KEY_LEFT:
case KEY_RIGHT:
answer = !answer;
break;
case 'y': case 'Y':
answer = true;
break;
case 'n': case 'N':
answer = false;
break;
case '\r': case '\n':
return answer;
}
}
erase();
curs_set(0);
move(0, 0);
addstr(title);
move(0, 12);
addch(answer ? icon_char(0, '>') : ' ');
addstr("Yes");
move(1, 12);
addch(!answer ? icon_char(0, '>') : ' ');
addstr("No");
refresh();
poll_fd_wait(STDIN_FILENO, POLLIN);
poll_block();
}
}
static void
cmd_show_version(const struct dict *dict UNUSED)
{
show_string(VERSION BUILDNR);
}
static void
cmd_browse_status(const struct dict *dict)
{
struct menu menu;
size_t i;
menu_init(&menu);
for (i = 0; i < dict->n; i++) {
const struct pair *p = &dict->pairs[i];
menu_add_item(&menu, "%s = %s", p->name, p->value);
}
menu_show(&menu, 0, false);
menu_free(&menu);
}
static void
cmd_shell(const struct dict *dict UNUSED)
{
const char *home;
erase();
refresh();
endwin();
printf("Type ^D to exit\n");
fflush(stdout);
putenv("PS1=#");
putenv("PS2=>");
putenv("PS3=?");
putenv("PS4=+");
home = getenv("HOME");
if (home) {
chdir(home);
}
system("/bin/sh");
initialize_terminal();
}
static void
cmd_show_motto(const struct dict *dict UNUSED)
{
show_string("\"Just Add Ice\"");
}
static void
show_string(const char *string)
{
VLOG_INFO("%s", string);
erase();
curs_set(0);
move(0, 0);
addstr(string);
refresh();
block_until(time_msec() + 5000);
}
static void
block_until(long long timeout)
{
while (timeout > time_msec()) {
poll_timer_wait(timeout - time_msec());
poll_block();
}
drain_keyboard_buffer();
}
static void
drain_keyboard_buffer(void)
{
while (getch() != ERR) {
continue;
}
}
static int
read_vars(const char *cmd, struct dict *dict)
{
struct ds ds;
FILE *stream;
int status;
stream = popen(cmd, "r");
if (!stream) {
VLOG_ERR("popen(\"%s\") failed: %s", cmd, strerror(errno));
return errno;
}
dict_init(dict);
ds_init(&ds);
while (!ds_get_line(&ds, stream)) {
const char *s = ds_cstr(&ds);
const char *equals = strchr(s, '=');
if (equals) {
dict_add_nocopy(dict,
xmemdup0(s, equals - s), xstrdup(equals + 1));
}
}
status = pclose(stream);
if (status) {
char *msg = process_status_msg(status);
VLOG_ERR("pclose(\"%s\") reported subprocess failure: %s",
cmd, msg);
free(msg);
dict_free(dict);
return ECHILD;
}
return 0;
}
static bool
run_and_report_failure(char **argv, const char *title)
{
int null_fds[3] = {0, 1, 2};
int status;
int retval;
char *s;
s = process_escape_args(argv);
VLOG_INFO("starting subprocess: %s", s);
free(s);
retval = process_run(argv, NULL, 0, null_fds, 3, &status);
if (retval) {
char *s = xasprintf("%s:\n%s", title, strerror(retval));
show_string(s);
free(s);
return false;
} else if (status) {
char *msg = process_status_msg(status);
char *s = xasprintf("%s:\n%s", title, msg);
show_string(s);
free(msg);
free(s);
return false;
} else {
VLOG_INFO("subprocess exited with status 0");
return true;
}
}
static int
do_load_config(const char *file_name, struct dict *dict)
{
struct dict auto_vars;
int retval;
char *cmd;
size_t i;
/* Get the list of the variables that the shell sets automatically. */
retval = read_vars("set -a && env", &auto_vars);
if (retval) {
return retval;
}
/* Get the variables from 'file_name'. */
cmd = xasprintf("set -a && . '%s' && env", file_name);
retval = read_vars(cmd, dict);
free(cmd);
if (retval) {
dict_free(&auto_vars);
return retval;
}
/* Subtract. */
for (i = 0; i < auto_vars.n; i++) {
dict_delete(dict, auto_vars.pairs[i].name);
}
dict_free(&auto_vars);
return 0;
}
static bool
load_config(struct dict *dict)
{
static const char default_file[] = "/etc/default/openflow-switch";
int retval = do_load_config(default_file, dict);
if (!retval) {
return true;
} else {
char *s = xasprintf("Cfg load failed:\n%s", strerror(retval));
show_string(s);
free(s);
return false;
}
}
static bool
save_config(const struct svec *settings)
{
struct svec argv;
size_t i;
bool ok;
VLOG_INFO("Saving configuration:");
for (i = 0; i < settings->n; i++) {
VLOG_INFO("%s", settings->names[i]);
}
svec_init(&argv);
svec_add(&argv, "/usr/share/openvswitch/commands/reconfigure");
svec_append(&argv, settings);
svec_terminate(&argv);
ok = run_and_report_failure(argv.names, "Save failed");
if (ok) {
long long int timeout = time_msec() + 5000;
erase();
curs_set(0);
move(0, 0);
addstr("Saved.\nRestarting...");
refresh();
svec_clear(&argv);
svec_add(&argv, "/bin/sh");
svec_add(&argv, "-c");
svec_add(&argv,
"/etc/init.d/openflow-switch restart >/dev/null 2>&1");
svec_terminate(&argv);
ok = run_and_report_failure(argv.names, "Restart failed");
if (ok) {
block_until(timeout);
}
}
svec_destroy(&argv);
if (ok) {
VLOG_INFO("Save completed successfully");
} else {
VLOG_WARN("Save failed");
}
return ok;
}
static int
match(pcre *re, const char *string, int length)
{
int ovec[999];
int retval;
retval = pcre_exec(re, NULL, string, length, 0, PCRE_PARTIAL,
ovec, ARRAY_SIZE(ovec));
if (retval >= 0) {
if (ovec[0] >= 0 && ovec[1] >= length) {
/* 're' matched all of 'string'. */
return 0;
} else {
/* 're' matched the initial part of 'string' but not all of it. */
return PCRE_ERROR_NOMATCH;
}
} else {
return retval;
}
}
static void
figure_choices(pcre *re, struct ds *s, int pos, struct ds *choices)
{
struct ds tmp;
int retval;
char c;
ds_clear(choices);
/* See whether the current string is a complete match. */
if (!match(re, s->string, pos)) {
ds_put_char(choices, '\n');
}
/* Then try all the other possibilities. */
ds_init(&tmp);
ds_put_buffer(&tmp, s->string, pos);
for (c = 0x20; c < 0x7f; c++) {
ds_put_char(&tmp, c);
retval = match(re, tmp.string, pos + 1);
if (retval == PCRE_ERROR_PARTIAL || !retval) {
ds_put_char(choices, c);
}
tmp.length--;
}
ds_destroy(&tmp);
if (!choices->length) {
ds_put_char(choices, '\n');
}
}
static void
figure_completion(pcre *re, struct ds *s)
{
for (;;) {
int found = -1;
int c;
/* See whether the current string is a complete match. */
if (!match(re, s->string, s->length)) {
return;
}
for (c = 0x20; c < 0x7f; c++) {
int retval;
ds_put_char(s, c);
retval = match(re, s->string, s->length);
s->length--;
if (retval == PCRE_ERROR_PARTIAL || !retval) {
if (found != -1) {
return;
}
found = c;
}
}
if (found == -1) {
return;
}
ds_put_char(s, found);
}
}
#define OCTET_RE "([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])"
#define IP_RE "("OCTET_RE"\\."OCTET_RE"\\."OCTET_RE"\\."OCTET_RE")"
#define PORT_RE \
"([0-9]|" \
"[1-9][0-9]|" \
"[1-9][0-9][0-9]|" \
"[1-9][0-9][0-9][0-9]|" \
"[1-5][0-9][0-9][0-9][0-9]|" \
"6[1-4][0-9][0-9][0-9]|" \
"65[1-4][0-9][0-9]|" \
"655[1-2][0-9]|" \
"6553[1-5])"
#define XOCTET_RE "[0-9A-F][0-9A-F]"
#define MAC_RE \
XOCTET_RE":"XOCTET_RE":"XOCTET_RE":"\
XOCTET_RE":"XOCTET_RE":"XOCTET_RE
#define NUM100_TO_99999_RE \
"([1-9][0-9][0-9]|" \
"[1-9][0-9][0-9][0-9]|" \
"[1-9][0-9][0-9][0-9][0-9])"
#define NUM5_TO_99999_RE \
"([5-9]|" \
"[1-9][0-9]|" \
"[1-9][0-9][0-9]|" \
"[1-9][0-9][0-9][0-9]|" \
"[1-9][0-9][0-9][0-9][0-9])"
#define NUM1_TO_99999_RE \
"([1-9]|" \
"[1-9][0-9]|" \
"[1-9][0-9][0-9]|" \
"[1-9][0-9][0-9][0-9]|" \
"[1-9][0-9][0-9][0-9][0-9])"
static char *
prompt(const char *prompt, const char *initial, const char *pattern)
{
struct ds ds;
int pos, chidx;
struct ds choices;
const char *error;
int erroffset;
pcre *re;
int retval;
int okpartial;
char *p;
set_icon(0,
e____X,
e____X,
e__X_X,
e_X__X,
eXXXXX,
e_X___,
e__X__,
e_____);
re = pcre_compile(pattern, PCRE_ANCHORED, &error, &erroffset, NULL);
if (!re) {
VLOG_ERR("PCRE error for pattern \"%s\" at offset %d: %s",
pattern, erroffset, error);
return xstrdup(initial);
}
retval = pcre_fullinfo(re, NULL, PCRE_INFO_OKPARTIAL, &okpartial);
assert(!retval);
assert(okpartial);
pos = 0;
ds_init(&ds);
ds_put_cstr(&ds, initial);
ds_init(&choices);
figure_choices(re, &ds, pos, &choices);
p = memchr(choices.string, initial[0], choices.length);
chidx = p ? p - choices.string : 0;
for (;;) {
int c, key;
while ((key = getch()) != ERR) {
switch (key) {
case KEY_UP:
if (choices.length > 1) {
if (++chidx >= choices.length) {
chidx = 0;
}
ds.string[pos] = choices.string[chidx];
ds_truncate(&ds, pos + 1);
figure_completion(re, &ds);
}
break;
case KEY_DOWN:
if (choices.length > 1) {
if (--chidx < 0) {
chidx = choices.length - 1;
}
ds.string[pos] = choices.string[chidx];
ds_truncate(&ds, pos + 1);
figure_completion(re, &ds);
}
break;
case '\r': case '\n':
if (choices.string[chidx] == '\n') {
ds_truncate(&ds, pos);
return ds_cstr(&ds);
} else {
if (pos >= ds.length) {
pos++;
ds_put_char(&ds, choices.string[chidx]);
figure_choices(re, &ds, pos, &choices);
chidx = 0;
figure_completion(re, &ds);
} else {
pos = ds.length;
figure_choices(re, &ds, pos, &choices);
chidx = 0;
figure_completion(re, &ds);
}
}
break;
case '\f':
ds_truncate(&ds, pos + 1);
figure_choices(re, &ds, pos, &choices);
chidx = 0;
break;
case '\b': case '\x7f': case '\x1b':
case KEY_BACKSPACE: case KEY_DC:
if (pos) {
pos--;
} else {
return xstrdup(initial);
}
figure_choices(re, &ds, pos, &choices);
chidx = 0;
if (pos < ds.length) {
p = memchr(choices.string, ds.string[pos],
choices.length);
if (p) {
chidx = p - choices.string;
}
}
break;
default:
if (key >= 0x20 && key < 0x7f) {
/* Check whether 'key' is valid and toggle case if
* necessary. */
if (!memchr(choices.string, key, choices.length)) {
if (memchr(choices.string, toupper(key),
choices.length)) {
key = toupper(key);
} else if (memchr(choices.string, tolower(key),
choices.length)) {
key = tolower(key);
} else {
break;
}
}
/* Insert 'key' and advance the position. */
if (pos >= ds.length) {
ds_put_char(&ds, key);
} else {
ds.string[pos] = key;
}
pos++;
if (choices.string[chidx] != key) {
ds_truncate(&ds, pos);
}
figure_choices(re, &ds, pos, &choices);
chidx = 0;
if (pos < ds.length) {
p = memchr(choices.string, ds.string[pos],
choices.length);
if (p) {
chidx = p - choices.string;
}
}
figure_completion(re, &ds);
}
}
}
erase();
curs_set(1);
move(0, 0);
addnstr(prompt, MIN(40, strlen(prompt)));
c = choices.string[chidx];
move(1, 0);
addstr(ds_cstr(&ds));
move(1, pos);
if (c == '\n') {
put_icon(0, '$');
} else {
addch(c);
}
move(1, pos);
refresh();
poll_fd_wait(STDIN_FILENO, POLLIN);
poll_block();
}
}
static void
prompt_ip(const char *title, uint32_t *ip)
{
char *in = xasprintf(IP_FMT, IP_ARGS(ip));
char *out = prompt(title, in, "^"IP_RE"$");
*ip = inet_addr(out);
free(in);
free(out);
}
static void
abbreviate_netdevs(const struct svec *netdevs, struct ds *abbrev)
{
size_t i;
ds_init(abbrev);
for (i = 0; i < netdevs->n; ) {
size_t i_len = strlen(netdevs->names[i]);
size_t j;
for (j = i + 1; j < netdevs->n; j++) {
size_t j_len = strlen(netdevs->names[j]);
if (!i_len || !j_len || i_len != j_len
|| memcmp(netdevs->names[i], netdevs->names[j], i_len - 1)) {
break;
}
}
if (abbrev->length) {
ds_put_char(abbrev, ' ');
}
if (j - i == 1) {
ds_put_cstr(abbrev, netdevs->names[i]);
} else {
size_t k;
ds_put_buffer(abbrev, netdevs->names[i], i_len - 1);
ds_put_char(abbrev, '[');
for (k = i; k < j; k++) {
ds_put_char(abbrev, netdevs->names[k][i_len - 1]);
}
ds_put_char(abbrev, ']');
}
i = j;
}
}
static void
choose_netdevs(struct svec *choices)
{
struct svec netdevs;
struct menu menu;
size_t i;
netdev_enumerate(&netdevs);
svec_sort(&netdevs);
menu_init(&menu);
menu_add_item(&menu, "Exit");
for (i = 0; i < netdevs.n; i++) {
const char *name = netdevs.names[i];
struct menu_item *item;
struct netdev *netdev;
int retval;
if (!strncmp(name, "wmaster", strlen("wmaster"))
|| !strncmp(name, "of", strlen("of"))
|| !strcmp(name, "lo")) {
continue;
}
retval = netdev_open(name, NETDEV_ETH_TYPE_NONE, &netdev);
if (!retval) {
bool exclude = netdev_get_in4(netdev, NULL);
netdev_close(netdev);
if (exclude) {
continue;
}
}
item = menu_add_item(&menu, "%s", name);
item->toggle = svec_contains(choices, name);
}
if (menu.n_items > 1) {
menu_show(&menu, 0, true);
} else {
show_string("No available\nbridge ports");
}
svec_clear(choices);
for (i = 0; i < menu.n_items; i++) {
struct menu_item *item = menu.items[i];
if (item->toggle > 0) {
svec_add(choices, item->text);
}
}
menu_free(&menu);
}
static bool
is_datapath_id_in_dmi(void)
{
FILE *dmidecode;
char line[256];
bool is_in_dmi;
dmidecode = popen("dmidecode -s system-uuid", "r");
if (!dmidecode) {
return false;
}
is_in_dmi = fgets(line, sizeof line, dmidecode) && strstr(line, "-002320");
fclose(dmidecode);
return is_in_dmi;
}
struct switch_config {
struct svec netdevs;
enum { DISCOVERY, IN_BAND } mode;
uint32_t switch_ip;
uint32_t switch_mask;
uint32_t switch_gw;
enum { FAIL_DROP, FAIL_SWITCH } disconnected;
bool stp;
int rate_limit;
int inactivity_probe;
int max_backoff;
char *controller_vconn;
char *datapath_id;
};
static const char *
disconnected_string(int value)
{
#define FAIL_SWITCH_STRING "Switch packets"
#define FAIL_DROP_STRING "Drop packets"
return value == FAIL_SWITCH ? FAIL_SWITCH_STRING : FAIL_DROP_STRING;
}
static void
cmd_configure(const struct dict *dict UNUSED)
{
bool debug_mode = dict_get_bool(dict, "debug", false);
struct dict config_dict;
struct switch_config config;
int start;
if (!load_config(&config_dict)) {
return;
}
svec_init(&config.netdevs);
svec_parse_words(&config.netdevs,
dict_get_string(&config_dict, "NETDEVS", ""));
config.mode = (!strcmp(dict_get_string(&config_dict, "MODE", "discovery"),
"in-band") ? IN_BAND : DISCOVERY);
config.switch_ip = dict_get_ip(&config_dict, "SWITCH_IP");
config.switch_mask = dict_get_ip(&config_dict, "SWITCH_NETMASK");
config.switch_gw = dict_get_ip(&config_dict, "SWITCH_GATEWAY");
config.controller_vconn = xstrdup(dict_get_string(&config_dict,
"CONTROLLER", ""));
config.disconnected = (!strcmp(dict_get_string(&config_dict,
"DISCONNECTED_MODE", ""),
"switch")
? FAIL_SWITCH : FAIL_DROP);
config.stp = !strcmp(dict_get_string(&config_dict, "stp", ""), "yes");
config.rate_limit = dict_get_int(&config_dict, "RATE_LIMIT", -1);
config.inactivity_probe = dict_get_int(&config_dict, "INACTIVITY_PROBE",
-1);
config.max_backoff = dict_get_int(&config_dict, "MAX_BACKOFF", -1);
if (is_datapath_id_in_dmi()) {
config.datapath_id = xstrdup("DMI");
} else {
const char *dpid = dict_get(&config_dict, "DATAPATH_ID");
if (dpid) {
struct ds ds = DS_EMPTY_INITIALIZER;
const char *cp;
for (cp = dpid; *cp != '\0'; cp++) {
if (*cp != ':') {
ds_put_char(&ds, toupper((unsigned char) *cp));
}
}
config.datapath_id = ds_cstr(&ds);
} else {
config.datapath_id = xstrdup("Random");
}
}
dict_free(&config_dict);
start = 0;
while (start != -1) {
enum {
MENU_EXIT,
MENU_NETDEVS,
MENU_MODE,
MENU_IP,
MENU_NETMASK,
MENU_GATEWAY,
MENU_CONTROLLER,
MENU_DISCONNECTED_MODE,
MENU_DATAPATH_ID,
MENU_STP,
MENU_RATE_LIMIT,
MENU_INACTIVITY_PROBE,
MENU_MAX_BACKOFF,
};
struct ds ports;
struct menu_item *item;
struct menu menu;
char *in, *out;
uint32_t ip;
menu_init(&menu);
/* Exit. */
item = menu_add_item(&menu, "Exit");
item->id = MENU_EXIT;
/* Bridge Ports. */
abbreviate_netdevs(&config.netdevs, &ports);
item = menu_add_item(&menu, "Bridge Ports:\n%s", ds_cstr(&ports));
item->id = MENU_NETDEVS;
ds_destroy(&ports);
/* Mode. */
item = menu_add_item(&menu, "Mode:\n%s",
(config.mode == DISCOVERY
? "Discovery" : "In-Band"));
item->id = MENU_MODE;
/* IP address. */
if (config.switch_ip == htonl(0)) {
item = menu_add_item(&menu, "Switch IP Addr:\nDHCP");
} else {
item = menu_add_item(&menu, "Switch IP Addr:\n"IP_FMT,
IP_ARGS(&config.switch_ip));
}
item->id = MENU_IP;
item->enabled = config.mode == IN_BAND;
/* Netmask. */
item = menu_add_item(&menu, "Switch Netmask:\n"IP_FMT,
IP_ARGS(&config.switch_mask));
item->id = MENU_NETMASK;
item->enabled = config.mode == IN_BAND && config.switch_ip != htonl(0);
/* Gateway. */
item = menu_add_item(&menu, "Switch Gateway:\n"IP_FMT,
IP_ARGS(&config.switch_gw));
item->id = MENU_GATEWAY;
item->enabled = config.mode == IN_BAND && config.switch_ip != htonl(0);
/* Controller. */
item = menu_add_item(&menu, "Controller:\n%s",
config.controller_vconn);
item->id = MENU_CONTROLLER;
item->enabled = config.mode == IN_BAND;
/* Disconnected mode. */
item = menu_add_item(&menu, "If disconnected:\n%s\n",
disconnected_string(config.disconnected));
item->id = MENU_DISCONNECTED_MODE;
/* Datapath ID. */
item = menu_add_item(&menu, "Datapath ID:\n%s", config.datapath_id);
item->id = MENU_DATAPATH_ID;
item->enabled = strcmp(config.datapath_id, "DMI");
/* Spanning tree protocol. */
if (debug_mode) {
item = menu_add_item(&menu, "802.1D-1998 STP:\n%s",
config.stp ? "Enabled" : "Disabled");
item->id = MENU_STP;
}
/* Rate-limiting. */
if (debug_mode) {
if (config.rate_limit < 0) {
item = menu_add_item(&menu, "Ctlr rate limit:\nDisabled");
} else {
item = menu_add_item(&menu, "Ctlr rate limit:\n%d/s",
config.rate_limit);
}
item->id = MENU_RATE_LIMIT;
}
/* Inactivity probe. */
if (debug_mode) {
if (config.inactivity_probe < 0) {
item = menu_add_item(&menu, "Activity probe:\nDefault");
} else {
item = menu_add_item(&menu, "Activity probe:\n%d s",
config.inactivity_probe);
}
item->id = MENU_INACTIVITY_PROBE;
}
/* Max backoff. */
if (debug_mode) {
if (config.max_backoff < 0) {
item = menu_add_item(&menu, "Max backoff:\nDefault");
} else {
item = menu_add_item(&menu, "Max backoff:\n%d s",
config.max_backoff);
}
item->id = MENU_MAX_BACKOFF;
}
start = menu_show2(&menu, start, true);
menu_free(&menu);
in = out = NULL;
switch (start) {
case MENU_EXIT:
start = -1;
break;
case MENU_NETDEVS:
choose_netdevs(&config.netdevs);
break;
case MENU_MODE:
out = prompt("Mode:",
config.mode == DISCOVERY ? "Discovery" : "In-Band",
"^(Discovery|In-Band)$");
config.mode = !strcmp(out, "Discovery") ? DISCOVERY : IN_BAND;
free(out);
break;
case MENU_IP:
in = (config.switch_ip == htonl(0) ? xstrdup("DHCP")
: xasprintf(IP_FMT, IP_ARGS(&config.switch_ip)));
out = prompt("Switch IP:", in, "^(DHCP|"IP_RE")$");
ip = strcmp(out, "DHCP") ? inet_addr(out) : htonl(0);
free(in);
free(out);
if (ip != config.switch_ip) {
config.switch_ip = ip;
if (ip != htonl(0)) {
uint32_t mask = guess_netmask(ip);
if (mask) {
config.switch_mask = mask;
config.switch_gw = (ip & mask) | htonl(1);
}
}
}
break;
case MENU_NETMASK:
prompt_ip("Switch Netmask:", &config.switch_mask);
break;
case MENU_GATEWAY:
prompt_ip("Switch Gateway:", &config.switch_gw);
break;
case MENU_CONTROLLER:
out = prompt("Controller:", config.controller_vconn,
"^(tcp|ssl):"IP_RE"(:"PORT_RE")?$");
free(config.controller_vconn);
config.controller_vconn = out;
break;
case MENU_DISCONNECTED_MODE:
out = prompt("If disconnected",
disconnected_string(config.disconnected),
"^("FAIL_DROP_STRING"|"FAIL_SWITCH_STRING")$");
config.disconnected = (!strcmp(out, FAIL_DROP_STRING)
? FAIL_DROP : FAIL_SWITCH);
free(out);
break;
case MENU_DATAPATH_ID:
out = prompt("Datapath ID:", config.datapath_id,
"^Random|"MAC_RE"$");
free(config.datapath_id);
config.datapath_id = out;
break;
case MENU_STP:
out = prompt("802.1D-1998 STP:",
config.stp ? "Enabled" : "Disabled",
"^(Enabled|Disabled)$");
config.stp = !strcmp(out, "Enabled");
free(out);
break;
case MENU_RATE_LIMIT:
in = (config.rate_limit < 0
? xstrdup("Disabled")
: xasprintf("%d/s", config.rate_limit));
out = prompt("Ctlr rate limit:", in,
"^(Disabled|("NUM100_TO_99999_RE")/s)$");
free(in);
config.rate_limit = isdigit(out[0]) ? atoi(out) : -1;
free(out);
break;
case MENU_INACTIVITY_PROBE:
in = (config.inactivity_probe < 0
? xstrdup("Default")
: xasprintf("%d s", config.inactivity_probe));
out = prompt("Activity probe:", in,
"^(Default|("NUM5_TO_99999_RE") s)$");
free(in);
config.inactivity_probe = isdigit(out[0]) ? atoi(out) : -1;
free(out);
break;
case MENU_MAX_BACKOFF:
in = (config.max_backoff < 0
? xstrdup("Default")
: xasprintf("%d s", config.max_backoff));
out = prompt("Max backoff:", in,
"^(Default|("NUM1_TO_99999_RE") s)$");
free(in);
config.max_backoff = isdigit(out[0]) ? atoi(out) : -1;
free(out);
break;
}
}
if (yesno("Save\nChanges?", false)) {
struct svec set;
char *netdevs;
svec_init(&set);
netdevs = svec_join(&config.netdevs, " ", "");
svec_add_nocopy(&set, xasprintf("NETDEVS=%s", netdevs));
free(netdevs);
svec_add(&set,
config.mode == IN_BAND ? "MODE=in-band" : "MODE=discovery");
if (config.mode == IN_BAND) {
if (config.switch_ip == htonl(0)) {
svec_add(&set, "SWITCH_IP=dhcp");
} else {
svec_add_nocopy(&set, xasprintf("SWITCH_IP="IP_FMT,
IP_ARGS(&config.switch_ip)));
svec_add_nocopy(&set,
xasprintf("SWITCH_NETMASK="IP_FMT,
IP_ARGS(&config.switch_mask)));
svec_add_nocopy(&set, xasprintf("SWITCH_GATEWAY="IP_FMT,
IP_ARGS(&config.switch_gw)));
svec_add_nocopy(&set, xasprintf("CONTROLLER=%s",
config.controller_vconn));
}
}
svec_add(&set, (config.disconnected == FAIL_DROP
? "DISCONNECTED_MODE=drop"
: "DISCONNECTED_MODE=switch"));
svec_add_nocopy(&set, xasprintf("STP=%s", config.stp ? "yes" : "no"));
if (config.rate_limit < 0) {
svec_add(&set, "RATE_LIMIT=");
} else {
svec_add_nocopy(&set,
xasprintf("RATE_LIMIT=%d", config.rate_limit));
}
if (config.inactivity_probe < 0) {
svec_add(&set, "INACTIVITY_PROBE=");
} else {
svec_add_nocopy(&set, xasprintf("INACTIVITY_PROBE=%d",
config.inactivity_probe));
}
if (config.max_backoff < 0) {
svec_add(&set, "MAX_BACKOFF=");
} else {
svec_add_nocopy(&set, xasprintf("MAX_BACKOFF=%d",
config.max_backoff));
}
save_config(&set);
svec_destroy(&set);
}
svec_destroy(&config.netdevs);
free(config.controller_vconn);
free(config.datapath_id);
}
static void
cmd_setup_pki(const struct dict *dict UNUSED)
{
static const char def_privkey_file[]
= "/etc/openflow-switch/of0-privkey.pem";
static const char def_cert_file[] = "/etc/openflow-switch/of0-cert.pem";
static const char def_cacert_file[] = "/etc/openflow-switch/cacert.pem";
struct dict config_dict;
const char *privkey_file, *cert_file, *cacert_file;
bool bootstrap;
struct stat s;
struct svec set;
bool has_keys;
if (!load_config(&config_dict)) {
return;
}
privkey_file = dict_get_string(&config_dict, "PRIVKEY", def_privkey_file);
cert_file = dict_get_string(&config_dict, "CERT", def_cert_file);
cacert_file = dict_get_string(&config_dict, "CACERT", def_cacert_file);
bootstrap = !strcmp(dict_get_string(&config_dict, "CACERT_MODE", "secure"),
"bootstrap");
has_keys = !stat(privkey_file, &s) && !stat(cert_file, &s);
if (!has_keys
? yesno("Generate\nkeys?", true)
: yesno("Generate\nnew keys?", false)) {
struct svec argv;
bool ok;
privkey_file = def_privkey_file;
cert_file = def_cert_file;
svec_init(&argv);
svec_parse_words(&argv, "sh -c 'cd /etc/openflow-switch "
"&& ovs-pki --force req of0"
"&& ovs-pki --force self-sign of0'");
svec_terminate(&argv);
ok = run_and_report_failure(argv.names, "Key gen failed");
svec_destroy(&argv);
if (!ok) {
return;
}
has_keys = true;
}
if (!has_keys) {
return;
}
if (stat(cacert_file, &s) && errno == ENOENT) {
bootstrap = yesno("Bootstrap\nCA cert?", bootstrap);
} else if (yesno("Replace\nCA cert?", false)) {
unlink(cacert_file);
bootstrap = true;
}
svec_init(&set);
svec_add_nocopy(&set, xasprintf("PRIVKEY=%s", privkey_file));
svec_add_nocopy(&set, xasprintf("CERT=%s", cert_file));
svec_add_nocopy(&set, xasprintf("CACERT=%s", cacert_file));
svec_add_nocopy(&set, xasprintf("CACERT_MODE=%s",
bootstrap ? "bootstrap" : "secure"));
save_config(&set);
svec_destroy(&set);
}
static void
parse_options(int argc, char *argv[])
{
enum {
OPT_DUMMY = UCHAR_MAX + 1,
VLOG_OPTION_ENUMS
};
static struct option long_options[] = {
{"verbose", optional_argument, 0, 'v'},
{"help", no_argument, 0, 'h'},
{"version", no_argument, 0, 'V'},
DAEMON_LONG_OPTIONS,
VLOG_LONG_OPTIONS,
{0, 0, 0, 0},
};
char *short_options = long_options_to_short_options(long_options);
for (;;) {
int c;
c = getopt_long(argc, argv, short_options, long_options, NULL);
if (c == -1) {
break;
}
switch (c) {
case 'h':
usage();
case 'V':
OVS_PRINT_VERSION(OFP_VERSION, OFP_VERSION);
exit(EXIT_SUCCESS);
VLOG_OPTION_HANDLERS
DAEMON_OPTION_HANDLERS
case '?':
exit(EXIT_FAILURE);
default:
abort();
}
}
free(short_options);
}
static void
usage(void)
{
printf("%s: OpenFlow switch monitoring user interface\n"
"usage: %s [OPTIONS] SWITCH\n"
"where SWITCH is an active OpenFlow connection method.\n",
program_name, program_name);
vconn_usage(true, false, false);
printf("\nOptions:\n"
" -v, --verbose=MODULE:FACILITY:LEVEL configure logging levels\n"
" -v, --verbose set maximum verbosity level\n"
" -h, --help display this help message\n"
" -V, --version display version information\n");
exit(EXIT_SUCCESS);
}