diff options
Diffstat (limited to 'extras/ezio/ovs-switchui.c')
-rw-r--r-- | extras/ezio/ovs-switchui.c | 3026 |
1 files changed, 3026 insertions, 0 deletions
diff --git a/extras/ezio/ovs-switchui.c b/extras/ezio/ovs-switchui.c new file mode 100644 index 000000000..6fbf25238 --- /dev/null +++ b/extras/ezio/ovs-switchui.c @@ -0,0 +1,3026 @@ +/* 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 <http://www.gnu.org/licenses/>. + * + * 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 <config.h> +#include <arpa/inet.h> +#include <assert.h> +#include <ctype.h> +#include <curses.h> +#include <errno.h> +#include <getopt.h> +#include <inttypes.h> +#include <math.h> +#include <pcre.h> +#include <signal.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <term.h> +#include <unistd.h> +#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); +} |