From: Justin Pettit Date: Wed, 11 Mar 2009 06:59:36 +0000 (-0700) Subject: First cut of management control protocol. X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8d21e57e41220ddd692824c4770a08e3236a0394;p=openvswitch First cut of management control protocol. Defines a management control protocol between the switch and NOX. Currently, this is only used by vswitchd. It allows the configuration and monitoring of a switch as a whole, as opposed to the flow table view provided by OpenFlow. To enable, add the appropriate "mgmt" keys to "vswitchd.conf". Better docs will be forthcoming... --- diff --git a/include/openflow/nicira-ext.h b/include/openflow/nicira-ext.h index 0d07584c..db96767f 100644 --- a/include/openflow/nicira-ext.h +++ b/include/openflow/nicira-ext.h @@ -41,6 +41,9 @@ enum nicira_type { /* Remote command execution reply, sent when the command's execution * completes. The reply body is struct nx_command_reply. */ NXT_COMMAND_REPLY, + + /* Management protocol. See "openflow-mgmt.h". */ + NXT_MGMT, }; struct nicira_header { diff --git a/include/openflow/openflow-mgmt.h b/include/openflow/openflow-mgmt.h new file mode 100644 index 00000000..c03482a5 --- /dev/null +++ b/include/openflow/openflow-mgmt.h @@ -0,0 +1,210 @@ +/* Copyright (c) 2009 The Board of Trustees of The Leland Stanford + * Junior University + * + * We are making the OpenFlow specification and associated documentation + * (Software) available for public use and benefit with the expectation + * that others will use, modify and enhance the Software and contribute + * those enhancements back to the community. However, since we would + * like to make the Software available for broadest use, with as few + * restrictions as possible permission is hereby granted, free of + * charge, to any person obtaining a copy of this Software to deal in + * the Software under the copyrights without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * The name and trademarks of copyright holder(s) may NOT be used in + * advertising or publicity pertaining to the Software or any + * derivatives without specific, written prior permission. + */ + +#ifndef OPENFLOW_OPENFLOW_MGMT_H +#define OPENFLOW_OPENFLOW_MGMT_H 1 + +#include "openflow/nicira-ext.h" + +enum ofmp_type { + OFMPT_CAPABILITY_REQUEST, + OFMPT_CAPABILITY_REPLY, + OFMPT_RESOURCES_REQUEST, + OFMPT_RESOURCES_UPDATE, + OFMPT_CONFIG_REQUEST, + OFMPT_CONFIG_UPDATE, + OFMPT_CONFIG_UPDATE_ACK, + OFMPT_ERROR +}; + +/* Header on all OpenFlow management packets. */ +struct ofmp_header { + struct nicira_header header; + uint16_t type; /* One of OFMPT_* above. */ + uint8_t pad[2]; +}; +OFP_ASSERT(sizeof(struct ofmp_header) == sizeof(struct nicira_header) + 4); + + +/* Generic TLV header. */ +struct ofmp_tlv { + uint16_t type; /* Type of value (one of OFMPTLV_*). */ + uint16_t len; /* Length of TLV (includes this header). */ + uint8_t data[0]; /* Value of data as defined by type and length. */ +}; +OFP_ASSERT(sizeof(struct ofmp_tlv) == 4); + +/* Universal TLV terminator. Used to indicate end of TLV list. */ +struct ofmp_tlv_end { + uint16_t type; /* Type is 0. */ + uint16_t len; /* Length is 4. */ +}; +OFP_ASSERT(sizeof(struct ofmp_tlv_end) == 4); + + +/* Bitmask of capability description styles. */ +enum ofmp_capability_format { + OFMPCAF_SIMPLE = 0 << 0, /* "vswitchd.conf" style. */ +}; + +/* Body of capbility request. + * + * OFMPT_CAPABILITY_REQUEST (controller -> switch) */ +struct ofmp_capability_request { + struct ofmp_header header; + uint32_t format; /* One of OFMPCAF_*. */ +}; +OFP_ASSERT(sizeof(struct ofmp_capability_request) == 24); + +/* Body of reply to capability request. + * + * OFMPT_CAPABILITY_REPLY (switch -> controller). */ +struct ofmp_capability_reply { + struct ofmp_header header; + uint32_t format; /* One of OFMPCAF_*. */ + uint64_t mgmt_id; /* Management ID. */ + uint8_t data[0]; +}; +OFP_ASSERT(sizeof(struct ofmp_capability_reply) == 32); + + +/* Resource TLV for datapath description. */ +struct ofmptsr_dp { + uint16_t type; /* OFMPTSR_DP. */ + uint16_t len; /* 28. */ + uint64_t dp_id; /* Datapath ID. */ + uint8_t name[OFP_MAX_PORT_NAME_LEN]; /* Null-terminated name. */ +}; +OFP_ASSERT(sizeof(struct ofmptsr_dp) == 28); + +/* TLV types for switch resource descriptions. */ +enum ofmp_switch_resources { + OFMPTSR_END = 0, /* Terminator. */ + OFMPTSR_DP, /* Datapath. */ +}; + +/* Body of resources request. + * + * OFMPT_RESOURCES_REQUEST (controller -> switch) */ +struct ofmp_resources_request { + struct ofmp_header header; +}; + +/* Body of capbility update. Sent in response to a resources request or + * sent asynchronously when resources change on the switch. + * + * OFMPT_RESOURCES_UPDATE (switch -> controller) */ +struct ofmp_resources_update { + struct ofmp_header header; + uint8_t data[0]; +}; +OFP_ASSERT(sizeof(struct ofmp_resources_update) == 20); + + +/* Bitmask of capability description styles. */ +enum ofmp_config_format { + OFMPCOF_SIMPLE = 0 << 0, /* "vswitchd.conf" style. */ +}; + +#define CONFIG_COOKIE_LEN 20 + +/* Body of configuration request. + * + * OFMPT_CONFIG_REQUEST (controller -> switch) */ +struct ofmp_config_request { + struct ofmp_header header; + uint32_t format; /* One of OFMPCOF_*. */ +}; +OFP_ASSERT(sizeof(struct ofmp_config_request) == 24); + +/* Body of configuration update. Sent in response to a configuration + * request from the controller. May be sent asynchronously by either + * the controller or switch to modify configuration or notify of + * changes, respectively. If sent by the controller, the switch must + * respond with a OFMPT_CONFIG_UPDATE_ACK. + * + * OFMPT_CONFIG_UPDATE (switch <-> controller) */ +struct ofmp_config_update { + struct ofmp_header header; + uint32_t format; /* One of OFMPCOF_*. */ + uint8_t cookie[CONFIG_COOKIE_LEN]; /* Cookie of config attempting to be + * replaced by this update. */ + uint8_t data[0]; +}; +OFP_ASSERT(sizeof(struct ofmp_config_update) == 44); + +/* Bitmask of configuration update ack flags. */ +enum ofmp_config_update_ack_flags { + OFMPCUAF_SUCCESS = 1 << 0, /* Config succeeded. */ +}; + +/* Body of configuration update ack. Sent in response to a configuration + * udpate request. + * + * OFMPT_CONFIG_UPDATE_ACK (switch -> controller) */ +struct ofmp_config_update_ack { + struct ofmp_header header; + uint32_t format; /* One of OFMPCOF_*. */ + uint32_t flags; /* One of OFMPCUAF_*. */ + uint8_t cookie[CONFIG_COOKIE_LEN]; /* Cookie of current configuration + * being used in the switch. */ +}; +OFP_ASSERT(sizeof(struct ofmp_config_update_ack) == 48); + +/* Values for 'type' in ofmp_error_msg. */ +enum ofmp_error_type { + OFMPET_BAD_CONFIG /* Problem with configuration. */ +}; + +/* ofmp_error_msg 'code' values for OFMPET_BAD_CONFIG. 'data' contains + * at least the first 64 bytes of the failed request. */ +enum ofmp_bad_config_code { + OFMPBCC_BUSY, /* Config updating, try again. */ + OFMPBCC_OLD_COOKIE, /* Config has changed. */ +}; + +/* Body of error message. May be sent by either the switch or the + * controller to indicate some error condition. + * + * OFMPT_ERROR (switch <-> controller) */ +struct ofmp_error_msg { + struct ofmp_header header; + + uint16_t type; /* One of OFMPET_*. */ + uint16_t code; /* Code depending on 'type'. */ + uint8_t data[0]; /* Variable-length data. Interpreted based + on the type and code. */ +}; +OFP_ASSERT(sizeof(struct ofmp_error_msg) == 24); + +#endif /* openflow/openflow-mgmt.h */ diff --git a/lib/automake.mk b/lib/automake.mk index 704e0e75..18afc694 100644 --- a/lib/automake.mk +++ b/lib/automake.mk @@ -41,6 +41,8 @@ lib_libopenflow_a_SOURCES = \ lib/learning-switch.h \ lib/list.c \ lib/list.h \ + lib/lockfile.c \ + lib/lockfile.h \ lib/mac-learning.c \ lib/mac-learning.h \ lib/netdev.c \ @@ -67,6 +69,8 @@ lib_libopenflow_a_SOURCES = \ lib/rconn.c \ lib/rconn.h \ lib/sat-math.h \ + lib/sha1.c \ + lib/sha1.h \ lib/shash.c \ lib/shash.h \ lib/signals.c \ diff --git a/lib/cfg.c b/lib/cfg.c index 5994ad3e..559b8a69 100644 --- a/lib/cfg.c +++ b/lib/cfg.c @@ -37,6 +37,8 @@ #include #include #include "dynamic-string.h" +#include "lockfile.h" +#include "ofpbuf.h" #include "packets.h" #include "svec.h" #include "util.h" @@ -49,13 +51,26 @@ * "bridge.a.controller.in-band", if it existed, and I'm really not certain * that the fix didn't break other things. */ -/* List of configuration files. */ -static struct svec cfg_files = SVEC_EMPTY_INITIALIZER; +/* Configuration file name. */ +static char *cfg_name; + +/* Put the temporary file in the same directory as cfg_name, so that + * they are guaranteed to be in the same file system and therefore we can + * rename() tmp_name over cfg_name. */ +static char *tmp_name; + +/* Lock information. */ +static char *lock_name; +int lock_fd; + +/* Flag to indicate whether local modifications have been made. */ +bool dirty; + +uint8_t cfg_cookie[CFG_COOKIE_LEN]; /* Current configuration. Maintained in sorted order. */ static struct svec cfg = SVEC_EMPTY_INITIALIZER; -static void read_directory(const char *dir_name); static bool has_double_dot(const char *key, size_t len); static bool is_valid_key(const char *key, size_t len, const char *file_name, int line_number, @@ -64,7 +79,6 @@ static char *parse_section(const char *file_name, int line_number, const char *); static void parse_setting(const char *file_name, int line_number, const char *section, const char *); -static void read_file(const char *file_name); static int compare_key(const char *a, const char *b); static char **find_key_le(const char *key); static char **find_key_ge(const char *key); @@ -78,6 +92,14 @@ static const char *extract_value(const char *key); static const char *get_nth_value(int idx, const char *key); static bool is_type(const char *s, enum cfg_flags); +#define CC_ALPHA "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +#define CC_DIGIT "0123456789" +#define CC_ALNUM CC_ALPHA CC_DIGIT +#define CC_SPACE " \t\r\n\v" + +#define CC_FILE_NAME CC_ALNUM "._-" +#define CC_KEY CC_ALNUM "._-@$:+" + /* Adds 'file_name' to the set of files or directories that are read by * cfg_read(). Returns 0 on success, otherwise a positive errno value if * 'file_name' cannot be opened. @@ -91,10 +113,17 @@ static bool is_type(const char *s, enum cfg_flags); * This function does not actually read the named file or directory. Use * cfg_read() to (re)read all the configuration files. */ int -cfg_add_file(const char *file_name) +cfg_set_file(const char *file_name) { int fd; + if (cfg_name) { + free(cfg_name); + free(lock_name); + free(tmp_name); + cfg_name = lock_name = tmp_name = NULL; + } + /* Make sure that we can open this file or directory for reading. */ fd = open(file_name, O_RDONLY); if (fd < 0) { @@ -103,8 +132,33 @@ cfg_add_file(const char *file_name) close(fd); /* Add it to the list. */ - VLOG_INFO("using \"%s\" as a configuration file", file_name); - svec_add(&cfg_files, file_name); + VLOG_INFO("using \"%s\" as the configuration file", file_name); + cfg_name = xstrdup(file_name); + lock_name = xasprintf("%s.~lock~", file_name); + tmp_name = xasprintf("%s.~tmp~", file_name); + return 0; +} + +static int +update_cookie(void) +{ + int i; + SHA1Context context; + + if (SHA1Reset(&context) != shaSuccess) { + return -1; + } + for (i = 0; i < cfg.n; i++) { + if (SHA1Input(&context, (uint8_t *)cfg.names[i], + strlen(cfg.names[i])) != shaSuccess) { + return -1; + } + SHA1Input(&context, (uint8_t *)"\n", 1); + } + if (SHA1Result(&context, cfg_cookie) != shaSuccess) { + return -1; + } + return 0; } @@ -114,29 +168,66 @@ cfg_add_file(const char *file_name) void cfg_read(void) { - size_t i; + struct ds ds; + FILE *file; + char *section; + int line_number; + + + if (!cfg_name) { + return; + } /* Clear old configuration data. */ svec_clear(&cfg); /* Read new configuration. */ - VLOG_INFO("reading configuration..."); - for (i = 0; i < cfg_files.n; i++) { - const char *fn = cfg_files.names[i]; - struct stat s; - - VLOG_DBG("reading \"%s\"", fn); - if (stat(fn, &s) < 0) { - VLOG_WARN("failed to stat \"%s\": %s", fn, strerror(errno)); - } else if (S_ISDIR(s.st_mode)) { - read_directory(fn); - } else if (S_ISREG(s.st_mode)) { - read_file(fn); + VLOG_INFO("reading configuration from %s", cfg_name); + + file = fopen(cfg_name, "r"); + if (!file) { + VLOG_ERR("failed to open \"%s\": %s", cfg_name, strerror(errno)); + return; + } + + ds_init(&ds); + section = NULL; + line_number = 0; + while (!ds_get_line(&ds, file)) { + const char *s = ds_cstr(&ds); + size_t indent = strspn(s, CC_SPACE); + + line_number++; + s += indent; + if (*s == '#' || *s == '\0') { + /* Ignore comments and lines that contain only white space. */ + } else if (*s == '[') { + if (!indent) { + free(section); + section = parse_section(cfg_name, line_number, s); + } else { + VLOG_ERR("%s:%d: ignoring indented section header", + cfg_name, line_number); + } + } else if (indent && !section) { + VLOG_ERR("%s:%d: ignoring indented line outside any section", + cfg_name, line_number); } else { - VLOG_WARN("\"%s\" is not a regular file or a directory, ignoring", - fn); + if (!indent) { + free(section); + section = NULL; + } + parse_setting(cfg_name, line_number, section, s); } } + ds_destroy(&ds); + free(section); + + svec_sort(&cfg); + svec_terminate(&cfg); + update_cookie(); + + fclose(file); if (VLOG_IS_DBG_ENABLED()) { size_t i; @@ -148,6 +239,154 @@ cfg_read(void) } } +int +cfg_get_cookie(uint8_t *cookie) +{ + if (dirty) { + update_cookie(); + } + + memcpy(cookie, cfg_cookie, sizeof(cfg_cookie)); + return 0; +} + +void +cfg_unlock(void) +{ + remove_lockfile(lock_fd, lock_name); + lock_fd = -1; +} + +/* Config may change, so caller responsible for notifying others if it + * did. Returns positive errno. */ +int +cfg_lock(uint8_t *cookie) +{ + int fd; + uint8_t curr_cookie[CFG_COOKIE_LEN]; + + /* Put the lock file in the same directory as cfg_name, so that + * they are guaranteed to be in the same file system. */ + fd = create_lockfile(lock_name); + if (fd < 0) { + /* Couldn't lock config file */ + return -fd; + } + lock_fd = fd; + + cfg_read(); + + if (cookie) { + cfg_get_cookie(curr_cookie); + + if (memcmp(curr_cookie, cookie, sizeof *curr_cookie)) { + /* Configuration has changed, so reject. */ + cfg_unlock(); + return EINVAL; + } + } + + return 0; +} + +/* Write 'new_cfg' into the configuration file. If 'cookie' is not null, + * then the write only occurs if it matches the current configuration's + * cookie. If successful, the old configuration is returned in 'new_cfg'. + * Returns 0 if successful, otherwise a negative errno value. */ +int +cfg_write(void) +{ + size_t i; + int fd; + + + svec_sort(&cfg); + + fd = open(tmp_name, O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR|S_IWUSR); + if (fd == -1) { + VLOG_WARN("could not open temp config file for writing: %s", + strerror(errno)); + return errno; + } + + for (i = 0; i < cfg.n; i++) { + int retval; + const char *entry = cfg.names[i]; + retval = write(fd, entry, strlen(entry)); + if (retval != strlen(entry)) { + VLOG_WARN("problem writing to temp config file %d: %s", + retval, strerror(errno)); + close(fd); + return errno; + } + retval = write(fd, "\n", 1); + if (retval != 1) { + VLOG_WARN("problem writing to temp config file %d: %s", + retval, strerror(errno)); + close(fd); + return errno; + } + } + close(fd); + + if (rename(tmp_name, cfg_name) < 0) { + VLOG_WARN("could not rename temp config file: %s", + strerror(errno)); + return errno; + } + + dirty = false; + + return 0; +} + +int +cfg_write_data(uint8_t *data, size_t len) +{ + int fd; + int retval; + + fd = open(tmp_name, O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR|S_IWUSR); + if (fd == -1) { + VLOG_WARN("could not open temp config file for writing: %s", + strerror(errno)); + return errno; + } + + while (len > 0) { + retval = write(fd, data, len); + if (retval < 0) { + close(fd); + return errno; + } + len -= retval; + } + + close(fd); + + if (rename(tmp_name, cfg_name) < 0) { + VLOG_WARN("could not rename temp config file: %s", + strerror(errno)); + return errno; + } + + dirty = false; + cfg_read(); + + return 0; +} + +void +cfg_buf_put(struct ofpbuf *buffer) +{ + int i; + + for (i = 0; i < cfg.n; i++) { + ofpbuf_put(buffer, cfg.names[i], strlen(cfg.names[i])); + ofpbuf_put(buffer, "\n", 1); + } +} + /* Formats the printf()-style format string in the parameter 'format', which * must be the function's last parameter, into string variable 'dst'. The * function is responsible for freeing 'dst'. */ @@ -262,6 +501,49 @@ cfg_get_subsections(struct svec *svec, const char *section_, ...) ds_destroy(§ion); } +void +cfg_add_entry(const char *entry_, ...) +{ + char *entry; + + FORMAT_KEY(entry_, entry); + svec_add_nocopy(&cfg, entry); + dirty = true; +} + +void +cfg_del_entry(const char *entry_, ...) +{ + char *entry; + + FORMAT_KEY(entry_, entry); + svec_del(&cfg, entry); + free(entry); + dirty = true; +} + +void +cfg_del_section(const char *section_, ...) +{ + struct ds section; + va_list args; + char **p; + + ds_init(§ion); + va_start(args, section_); + ds_put_format_valist(§ion, section_, args); + ds_put_char(§ion, '.'); + va_end(args); + + for (p = find_key_le(ds_cstr(§ion)); + *p && !strncmp(section.string, *p, section.length); + p++) { + svec_del(&cfg, *p); + } + ds_destroy(§ion); + dirty = true; +} + /* Returns the value numbered 'idx' of 'key'. Returns a null pointer if 'idx' * is greater than or equal to cfg_count(key). The caller must not modify or * free the returned string or retain its value beyond the next call to @@ -449,41 +731,6 @@ cfg_get_all_keys(struct svec *svec, const char *key_, ...) free(key); } -#define CC_ALPHA "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" -#define CC_DIGIT "0123456789" -#define CC_ALNUM CC_ALPHA CC_DIGIT -#define CC_SPACE " \t\r\n\v" - -#define CC_FILE_NAME CC_ALNUM "._-" -#define CC_KEY CC_ALNUM "._-@$:+" - -static void -read_directory(const char *dir_name) -{ - DIR *dir; - struct dirent *de; - - dir = opendir(dir_name); - if (!dir) { - VLOG_ERR("failed to open \"%s\" as a directory: %s", - dir_name, strerror(errno)); - return; - } - - while ((de = readdir(dir)) != NULL) { - const char *name = de->d_name; - if (name[0] != '.' && name[strspn(name, CC_FILE_NAME)] == '\0') { - char *file_name = xasprintf("%s/%s", dir_name, name); - VLOG_DBG("reading %s in %s", name, dir_name); - read_file(file_name); - free(file_name); - } else { - VLOG_DBG("ignoring %s in %s", name, dir_name); - } - } - closedir(dir); -} - static bool has_double_dot(const char *key, size_t len) { @@ -631,63 +878,6 @@ done: ds_destroy(&value); } -static void -read_file(const char *file_name) -{ - struct ds ds; - FILE *file; - char *section; - int line_number; - - /* XXX should record st_dev, st_ino and make sure that we don't read the - * same file twice, otherwise all the pairs from that file will get - * doubled. */ - - file = fopen(file_name, "r"); - if (!file) { - VLOG_ERR("failed to open \"%s\": %s", file_name, strerror(errno)); - return; - } - - ds_init(&ds); - section = NULL; - line_number = 0; - while (!ds_get_line(&ds, file)) { - const char *s = ds_cstr(&ds); - size_t indent = strspn(s, CC_SPACE); - - line_number++; - s += indent; - if (*s == '#' || *s == '\0') { - /* Ignore comments and lines that contain only white space. */ - } else if (*s == '[') { - if (!indent) { - free(section); - section = parse_section(file_name, line_number, s); - } else { - VLOG_ERR("%s:%d: ignoring indented section header", - file_name, line_number); - } - } else if (indent && !section) { - VLOG_ERR("%s:%d: ignoring indented line outside any section", - file_name, line_number); - } else { - if (!indent) { - free(section); - section = NULL; - } - parse_setting(file_name, line_number, section, s); - } - } - ds_destroy(&ds); - free(section); - - svec_sort(&cfg); - svec_terminate(&cfg); - - fclose(file); -} - static int compare_key(const char *a, const char *b) { diff --git a/lib/cfg.h b/lib/cfg.h index a4139adf..cf937f2e 100644 --- a/lib/cfg.h +++ b/lib/cfg.h @@ -30,13 +30,24 @@ #include #include +#include #include "compiler.h" +#include "sha1.h" struct svec; +struct ofpbuf; -int cfg_add_file(const char *file_name); +int cfg_set_file(const char *file_name); void cfg_read(void); +int cfg_lock(uint8_t *cookie); +void cfg_unlock(void); +int cfg_write(void); +int cfg_write_data(uint8_t *data, size_t len); +#define CFG_COOKIE_LEN SHA1HashSize +int cfg_get_cookie(uint8_t *cookie); + +void cfg_buf_put(struct ofpbuf *buffer); void cfg_get_subsections(struct svec *, const char *, ...) PRINTF_FORMAT(2, 3); enum cfg_flags { @@ -56,6 +67,10 @@ enum cfg_flags { }; void cfg_register(const char *key_spec, enum cfg_flags); +void cfg_add_entry(const char *key, ...) PRINTF_FORMAT(1, 2); +void cfg_del_entry(const char *key, ...) PRINTF_FORMAT(1, 2); +void cfg_del_section(const char *key, ...) PRINTF_FORMAT(1, 2); + bool cfg_has(const char *key, ...) PRINTF_FORMAT(1, 2); bool cfg_is_valid(enum cfg_flags, const char *key, ...) PRINTF_FORMAT(2, 3); bool cfg_has_section(const char *key, ...) PRINTF_FORMAT(1, 2); diff --git a/lib/lockfile.c b/lib/lockfile.c new file mode 100644 index 00000000..736a67c5 --- /dev/null +++ b/lib/lockfile.c @@ -0,0 +1,160 @@ +/* Copyright (C) 2008, 2009 Nicira Networks, Inc. All rights reserved. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "lockfile.h" + +#define THIS_MODULE VLM_lockfile +#include "vlog.h" + +static int +fcntl_lock(int fd) +{ + struct flock l; + memset(&l, 0, sizeof l); + l.l_type = F_WRLCK; + l.l_whence = SEEK_SET; + l.l_start = 0; + l.l_len = 0; + return fcntl(fd, F_SETLK, &l) == -1 ? errno : 0; +} + +/* Remove the lockfile with 'name'. If the 'fd' argument is not -1, it + * will be closed. */ +int +remove_lockfile(int fd, const char *name) +{ + char buffer[BUFSIZ]; + ssize_t n; + pid_t pid; + + if (fd != -1) { + close(fd); + } + + /* Remove existing lockfile. */ + fd = open(name, O_RDWR); + if (fd < 0) { + if (errno == ENOENT) { + return 0; + } else { + VLOG_ERR("%s: open: %s", name, strerror(errno)); + return -errno; + } + } + + /* Read lockfile. */ + n = read(fd, buffer, sizeof buffer - 1); + if (n < 0) { + int error = errno; + VLOG_ERR("%s: read: %s", name, strerror(error)); + close(fd); + return -error; + } + buffer[n] = '\0'; + if (n == 4 && memchr(buffer, '\0', n)) { + int32_t x; + memcpy(&x, buffer, sizeof x); + pid = x; + } else if (n >= 0) { + pid = strtol(buffer, NULL, 10); + } + if (pid <= 0) { + close(fd); + VLOG_WARN("%s: format not recognized, treating as locked.", name); + return -EACCES; + } + + /* Is lockfile fresh? */ + if (strstr(buffer, "fcntl")) { + int retval = fcntl_lock(fd); + if (retval) { + int error = errno; + close(fd); + VLOG_ERR("%s: device is locked (via fcntl): %s", + name, strerror(retval)); + return -error; + } else { + VLOG_WARN("%s: removing stale lockfile (checked via fcntl)", name); + } + } else { + if (!(kill(pid, 0) < 0 && errno == ESRCH)) { + close(fd); + VLOG_ERR("%s: device is locked (without fcntl)", name); + return -EACCES; + } else { + VLOG_WARN("%s: removing stale lockfile (without fcntl)", name); + } + } + close(fd); + + /* Remove stale lockfile. */ + if (unlink(name)) { + VLOG_ERR("%s: unlink: %s", name, strerror(errno)); + return -errno; + } + return 0; +} + +/* Attempt to create the lockfile. On error, return -1 with errno set + * to an appropriate value. On success, return a file descriptor. */ +int +create_lockfile(const char *name) +{ + const char *username; + char buffer[BUFSIZ]; + struct passwd *pwd; + mode_t old_umask; + uid_t uid; + int fd; + + /* Remove an existing lockfile if it was created by us. */ + int retval = remove_lockfile(-1, name); + if (!retval) { + return retval; + } + + /* Create file. */ + old_umask = umask(022); + fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0666); + if (fd < 0) { + int error = errno; + VLOG_ERR("%s: create: %s", name, strerror(error)); + umask(old_umask); + return -error; + } + umask(old_umask); + + /* Lock file. */ + if (fcntl_lock(fd)) { + int error = errno; + close(fd); + VLOG_ERR("%s: cannot lock: %s", name, strerror(error)); + return -error; + } + + /* Write to file. */ + uid = getuid(); + pwd = getpwuid(uid); + username = pwd ? pwd->pw_name : "unknown"; + snprintf(buffer, sizeof buffer, "%10ld %s %.20s fcntl\n", + (long int) getpid(), program_name, username); + if (write(fd, buffer, strlen(buffer)) != strlen(buffer)) { + int error = errno; + VLOG_ERR("%s: write: %s", name, strerror(error)); + close(fd); + unlink(name); + return -error; + } + + return fd; +} diff --git a/lib/lockfile.h b/lib/lockfile.h new file mode 100644 index 00000000..645fd3d9 --- /dev/null +++ b/lib/lockfile.h @@ -0,0 +1,40 @@ +/* Copyright (c) 2008, 2009 The Board of Trustees of The Leland Stanford + * Junior University + * + * We are making the OpenFlow specification and associated documentation + * (Software) available for public use and benefit with the expectation + * that others will use, modify and enhance the Software and contribute + * those enhancements back to the community. However, since we would + * like to make the Software available for broadest use, with as few + * restrictions as possible permission is hereby granted, free of + * charge, to any person obtaining a copy of this Software to deal in + * the Software under the copyrights without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * The name and trademarks of copyright holder(s) may NOT be used in + * advertising or publicity pertaining to the Software or any + * derivatives without specific, written prior permission. + */ + +#ifndef LOCKFILE_H +#define LOCKFILE_H 1 + +int create_lockfile(const char *name); +int remove_lockfile(int fd, const char *name); + +#endif /* lockfile.h */ diff --git a/lib/sha1.c b/lib/sha1.c new file mode 100644 index 00000000..88963392 --- /dev/null +++ b/lib/sha1.c @@ -0,0 +1,384 @@ +/* + * sha1.c + * + * Description: + * This file implements the Secure Hashing Algorithm 1 as + * defined in FIPS PUB 180-1 published April 17, 1995. + * + * The SHA-1, produces a 160-bit message digest for a given + * data stream. It should take about 2**n steps to find a + * message with the same digest as a given message and + * 2**(n/2) to find any two messages with the same digest, + * when n is the digest size in bits. Therefore, this + * algorithm can serve as a means of providing a + * "fingerprint" for a message. + * + * Portability Issues: + * SHA-1 is defined in terms of 32-bit "words". This code + * uses (included via "sha1.h" to define 32 and 8 + * bit unsigned integer types. If your C compiler does not + * support 32 bit unsigned integers, this code is not + * appropriate. + * + * Caveats: + * SHA-1 is designed to work with messages less than 2^64 bits + * long. Although SHA-1 allows a message digest to be generated + * for messages of any number of bits less than 2^64, this + * implementation only works with messages with a length that is + * a multiple of the size of an 8-bit character. + * + */ + +#include "sha1.h" + +/* + * Define the SHA1 circular left shift macro + */ +#define SHA1CircularShift(bits,word) \ + (((word) << (bits)) | ((word) >> (32-(bits)))) + +/* Local Function Prototyptes */ +void SHA1PadMessage(SHA1Context *); +void SHA1ProcessMessageBlock(SHA1Context *); + +/* + * SHA1Reset + * + * Description: + * This function will initialize the SHA1Context in preparation + * for computing a new SHA1 message digest. + * + * Parameters: + * context: [in/out] + * The context to reset. + * + * Returns: + * sha Error Code. + * + */ +int SHA1Reset(SHA1Context *context) +{ + if (!context) + { + return shaNull; + } + + context->Length_Low = 0; + context->Length_High = 0; + context->Message_Block_Index = 0; + + context->Intermediate_Hash[0] = 0x67452301; + context->Intermediate_Hash[1] = 0xEFCDAB89; + context->Intermediate_Hash[2] = 0x98BADCFE; + context->Intermediate_Hash[3] = 0x10325476; + context->Intermediate_Hash[4] = 0xC3D2E1F0; + + context->Computed = 0; + context->Corrupted = 0; + + return shaSuccess; +} + +/* + * SHA1Result + * + * Description: + * This function will return the 160-bit message digest into the + * Message_Digest array provided by the caller. + * NOTE: The first octet of hash is stored in the 0th element, + * the last octet of hash in the 19th element. + * + * Parameters: + * context: [in/out] + * The context to use to calculate the SHA-1 hash. + * Message_Digest: [out] + * Where the digest is returned. + * + * Returns: + * sha Error Code. + * + */ +int SHA1Result( SHA1Context *context, + uint8_t Message_Digest[SHA1HashSize]) +{ + int i; + + if (!context || !Message_Digest) + { + return shaNull; + } + + if (context->Corrupted) + { + return context->Corrupted; + } + + if (!context->Computed) + { + SHA1PadMessage(context); + for(i=0; i<64; ++i) + { + /* message may be sensitive, clear it out */ + context->Message_Block[i] = 0; + } + context->Length_Low = 0; /* and clear length */ + context->Length_High = 0; + context->Computed = 1; + } + + for(i = 0; i < SHA1HashSize; ++i) + { + Message_Digest[i] = context->Intermediate_Hash[i>>2] + >> 8 * ( 3 - ( i & 0x03 ) ); + } + + return shaSuccess; +} + +/* + * SHA1Input + * + * Description: + * This function accepts an array of octets as the next portion + * of the message. + * + * Parameters: + * context: [in/out] + * The SHA context to update + * message_array: [in] + * An array of characters representing the next portion of + * the message. + * length: [in] + * The length of the message in message_array + * + * Returns: + * sha Error Code. + * + */ +int SHA1Input( SHA1Context *context, + const uint8_t *message_array, + unsigned length) +{ + if (!length) + { + return shaSuccess; + } + + if (!context || !message_array) + { + return shaNull; + } + + if (context->Computed) + { + context->Corrupted = shaStateError; + return shaStateError; + } + + if (context->Corrupted) + { + return context->Corrupted; + } + while(length-- && !context->Corrupted) + { + context->Message_Block[context->Message_Block_Index++] = + (*message_array & 0xFF); + + context->Length_Low += 8; + if (context->Length_Low == 0) + { + context->Length_High++; + if (context->Length_High == 0) + { + /* Message is too long */ + context->Corrupted = 1; + } + } + + if (context->Message_Block_Index == 64) + { + SHA1ProcessMessageBlock(context); + } + + message_array++; + } + + return shaSuccess; +} + +/* + * SHA1ProcessMessageBlock + * + * Description: + * This function will process the next 512 bits of the message + * stored in the Message_Block array. + * + * Parameters: + * None. + * + * Returns: + * Nothing. + * + * Comments: + * Many of the variable names in this code, especially the + * single character names, were used because those were the + * names used in the publication. + * + * + */ +void SHA1ProcessMessageBlock(SHA1Context *context) +{ + const uint32_t K[] = { /* Constants defined in SHA-1 */ + 0x5A827999, + 0x6ED9EBA1, + 0x8F1BBCDC, + 0xCA62C1D6 + }; + int t; /* Loop counter */ + uint32_t temp; /* Temporary word value */ + uint32_t W[80]; /* Word sequence */ + uint32_t A, B, C, D, E; /* Word buffers */ + + /* + * Initialize the first 16 words in the array W + */ + for(t = 0; t < 16; t++) + { + W[t] = context->Message_Block[t * 4] << 24; + W[t] |= context->Message_Block[t * 4 + 1] << 16; + W[t] |= context->Message_Block[t * 4 + 2] << 8; + W[t] |= context->Message_Block[t * 4 + 3]; + } + + for(t = 16; t < 80; t++) + { + W[t] = SHA1CircularShift(1,W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]); + } + + A = context->Intermediate_Hash[0]; + B = context->Intermediate_Hash[1]; + C = context->Intermediate_Hash[2]; + D = context->Intermediate_Hash[3]; + E = context->Intermediate_Hash[4]; + + for(t = 0; t < 20; t++) + { + temp = SHA1CircularShift(5,A) + + ((B & C) | ((~B) & D)) + E + W[t] + K[0]; + E = D; + D = C; + C = SHA1CircularShift(30,B); + B = A; + A = temp; + } + + for(t = 20; t < 40; t++) + { + temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[1]; + E = D; + D = C; + C = SHA1CircularShift(30,B); + B = A; + A = temp; + } + + for(t = 40; t < 60; t++) + { + temp = SHA1CircularShift(5,A) + + ((B & C) | (B & D) | (C & D)) + E + W[t] + K[2]; + E = D; + D = C; + C = SHA1CircularShift(30,B); + B = A; + A = temp; + } + + for(t = 60; t < 80; t++) + { + temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[3]; + E = D; + D = C; + C = SHA1CircularShift(30,B); + B = A; + A = temp; + } + + context->Intermediate_Hash[0] += A; + context->Intermediate_Hash[1] += B; + context->Intermediate_Hash[2] += C; + context->Intermediate_Hash[3] += D; + context->Intermediate_Hash[4] += E; + + context->Message_Block_Index = 0; +} + + +/* + * SHA1PadMessage + * + * Description: + * According to the standard, the message must be padded to an even + * 512 bits. The first padding bit must be a '1'. The last 64 + * bits represent the length of the original message. All bits in + * between should be 0. This function will pad the message + * according to those rules by filling the Message_Block array + * accordingly. It will also call the ProcessMessageBlock function + * provided appropriately. When it returns, it can be assumed that + * the message digest has been computed. + * + * Parameters: + * context: [in/out] + * The context to pad + * ProcessMessageBlock: [in] + * The appropriate SHA*ProcessMessageBlock function + * Returns: + * Nothing. + * + */ + +void SHA1PadMessage(SHA1Context *context) +{ + /* + * Check to see if the current message block is too small to hold + * the initial padding bits and length. If so, we will pad the + * block, process it, and then continue padding into a second + * block. + */ + if (context->Message_Block_Index > 55) + { + context->Message_Block[context->Message_Block_Index++] = 0x80; + while(context->Message_Block_Index < 64) + { + context->Message_Block[context->Message_Block_Index++] = 0; + } + + SHA1ProcessMessageBlock(context); + + while(context->Message_Block_Index < 56) + { + context->Message_Block[context->Message_Block_Index++] = 0; + } + } + else + { + context->Message_Block[context->Message_Block_Index++] = 0x80; + while(context->Message_Block_Index < 56) + { + context->Message_Block[context->Message_Block_Index++] = 0; + } + } + + /* + * Store the message length as the last 8 octets + */ + context->Message_Block[56] = context->Length_High >> 24; + context->Message_Block[57] = context->Length_High >> 16; + context->Message_Block[58] = context->Length_High >> 8; + context->Message_Block[59] = context->Length_High; + context->Message_Block[60] = context->Length_Low >> 24; + context->Message_Block[61] = context->Length_Low >> 16; + context->Message_Block[62] = context->Length_Low >> 8; + context->Message_Block[63] = context->Length_Low; + + SHA1ProcessMessageBlock(context); +} diff --git a/lib/sha1.h b/lib/sha1.h new file mode 100644 index 00000000..05a5473b --- /dev/null +++ b/lib/sha1.h @@ -0,0 +1,71 @@ +/* + * sha1.h + * + * Description: + * This is the header file for code which implements the Secure + * Hashing Algorithm 1 as defined in FIPS PUB 180-1 published + * April 17, 1995. + * + * Many of the variable names in this code, especially the + * single character names, were used because those were the names + * used in the publication. + * + * Please read the file sha1.c for more information. + * + */ +#ifndef _SHA1_H_ +#define _SHA1_H_ + +#include +/* + * If you do not have the ISO standard stdint.h header file, then you + * must typdef the following: + * name meaning + * uint32_t unsigned 32 bit integer + * uint8_t unsigned 8 bit integer (i.e., unsigned char) + * int_least16_t integer of >= 16 bits + * + */ + +#ifndef _SHA_enum_ +#define _SHA_enum_ +enum +{ + shaSuccess = 0, + shaNull, /* Null pointer parameter */ + shaInputTooLong, /* input data too long */ + shaStateError /* called Input after Result */ +}; +#endif +#define SHA1HashSize 20 + +/* + * This structure will hold context information for the SHA-1 + * hashing operation + */ +typedef struct SHA1Context +{ + uint32_t Intermediate_Hash[SHA1HashSize/4]; /* Message Digest */ + + uint32_t Length_Low; /* Message length in bits */ + uint32_t Length_High; /* Message length in bits */ + + /* Index into message block array */ + int_least16_t Message_Block_Index; + uint8_t Message_Block[64]; /* 512-bit message blocks */ + + int Computed; /* Is the digest computed? */ + int Corrupted; /* Is the message digest corrupted? */ +} SHA1Context; + +/* + * Function Prototypes + */ +int SHA1Reset( SHA1Context *); +int SHA1Input( SHA1Context *, + const uint8_t *, + unsigned int); +int SHA1Result( SHA1Context *, + uint8_t Message_Digest[SHA1HashSize]); + +#endif diff --git a/lib/svec.c b/lib/svec.c index bbf4b353..2d9ddf98 100644 --- a/lib/svec.c +++ b/lib/svec.c @@ -82,6 +82,20 @@ svec_add(struct svec *svec, const char *name) svec_add_nocopy(svec, xstrdup(name)); } +void +svec_del(struct svec *svec, const char *name) +{ + size_t offset; + + offset = svec_find(svec, name); + if (offset != SIZE_MAX) { + free(svec->names[offset]); + svec->names[offset] = svec->names[svec->n-1]; + svec->n--; + svec_sort(svec); + } +} + static void svec_expand(struct svec *svec) { diff --git a/lib/svec.h b/lib/svec.h index 542e8687..c9c42c8d 100644 --- a/lib/svec.h +++ b/lib/svec.h @@ -51,6 +51,7 @@ void svec_destroy(struct svec *); void svec_clear(struct svec *); void svec_add(struct svec *, const char *); void svec_add_nocopy(struct svec *, char *); +void svec_del(struct svec *, const char *); void svec_append(struct svec *, const struct svec *); void svec_terminate(struct svec *); void svec_sort(struct svec *); diff --git a/lib/vlog-modules.def b/lib/vlog-modules.def index 4e3f6e4c..573da753 100644 --- a/lib/vlog-modules.def +++ b/lib/vlog-modules.def @@ -20,7 +20,9 @@ VLOG_MODULE(flow) VLOG_MODULE(in_band) VLOG_MODULE(leak_checker) VLOG_MODULE(learning_switch) +VLOG_MODULE(lockfile) VLOG_MODULE(mac_learning) +VLOG_MODULE(mgmt) VLOG_MODULE(netdev) VLOG_MODULE(netflow) VLOG_MODULE(netlink) diff --git a/secchan/main.c b/secchan/main.c index 79c62b80..2bf4b0c6 100644 --- a/secchan/main.c +++ b/secchan/main.c @@ -123,7 +123,7 @@ struct ofsettings { char *command_dir; /* Directory that contains commands. */ }; -static void reconfigure(struct ofproto *, const char *br_name); +static void reconfigure(struct ofproto *, struct ofsettings *); static void parse_options(int argc, char *argv[], struct ofsettings *); static void usage(void) NO_RETURN; @@ -197,10 +197,10 @@ main(int argc, char *argv[]) } } - reconfigure(ofproto, s.br_name); + reconfigure(ofproto, &s); while (ofproto_is_alive(ofproto)) { if (signal_poll(sighup)) { - reconfigure(ofproto, s.br_name); + reconfigure(ofproto, &s); } error = ofproto_run(ofproto); @@ -217,17 +217,20 @@ main(int argc, char *argv[]) } static void -reconfigure(struct ofproto *ofproto, const char *br_name) +reconfigure(struct ofproto *ofproto, struct ofsettings *s) { cfg_read(); - if (br_name) { + if (s->br_name) { struct svec collectors; svec_init(&collectors); - cfg_get_all_keys(&collectors, "netflow.%s.host", br_name); + cfg_get_all_keys(&collectors, "netflow.%s.host", s->br_name); ofproto_set_netflow(ofproto, &collectors); svec_destroy(&collectors); } + + /* xxx Changing this should probably force reconnect to NOX! */ + ofproto_set_mgmt_id(ofproto, cfg_get_mac(0, "vswitchd.mgmt.id")); } /* User interface. */ @@ -451,10 +454,10 @@ parse_options(int argc, char *argv[], struct ofsettings *s) break; case 'F': - error = cfg_add_file(optarg); + error = cfg_set_file(optarg); if (error) { - ofp_fatal(error, "failed to add configuration file or " - "directory \"%s\"", optarg); + ofp_fatal(error, "failed to add configuration file \"%s\"", + optarg); } break; diff --git a/secchan/ofproto.c b/secchan/ofproto.c index 9a49a111..f58368a8 100644 --- a/secchan/ofproto.c +++ b/secchan/ofproto.c @@ -53,6 +53,7 @@ #include "openflow/datapath-protocol.h" #include "openflow/nicira-ext.h" #include "openflow/openflow.h" +#include "openflow/openflow-mgmt.h" #include "packets.h" #include "pinsched.h" #include "pktbuf.h" @@ -140,6 +141,7 @@ struct ofproto { /* Settings. */ uint64_t datapath_id; /* Datapath ID. */ uint64_t fallback_dpid; /* Datapath ID if no better choice found. */ + uint64_t mgmt_id; /* Management channel identifier. */ char *manufacturer; /* Manufacturer. */ char *hardware; /* Hardware. */ char *software; /* Software version. */ @@ -307,6 +309,12 @@ ofproto_set_datapath_id(struct ofproto *p, uint64_t datapath_id) } } +void +ofproto_set_mgmt_id(struct ofproto *p, uint64_t mgmt_id) +{ + p->mgmt_id = mgmt_id; +} + void ofproto_set_probe_interval(struct ofproto *p, int probe_interval) { @@ -2406,6 +2414,58 @@ handle_flow_mod(struct ofproto *p, struct ofconn *ofconn, } } +static void +send_capability_reply(struct ofproto *p, struct ofconn *ofconn, uint32_t xid) +{ + struct ofmp_capability_reply *ocr; + struct ofpbuf *b; + char capabilities[] = "com.nicira.mgmt.manager=false\n"; + + ocr = make_openflow_xid(sizeof(*ocr), OFPT_VENDOR, xid, &b); + ocr->header.header.vendor = htonl(NX_VENDOR_ID); + ocr->header.header.subtype = htonl(NXT_MGMT); + ocr->header.type = htons(OFMPT_CAPABILITY_REPLY); + + ocr->format = htonl(OFMPCOF_SIMPLE); + ocr->mgmt_id = htonll(p->mgmt_id); + + ofpbuf_put(b, capabilities, strlen(capabilities)); + + queue_tx(b, ofconn); +} + +static int +handle_ofmp(struct ofproto *p, struct ofconn *ofconn, + struct ofmp_header *ofmph) +{ + size_t msg_len = ntohs(ofmph->header.header.length); + if (msg_len < sizeof(*ofmph)) { + VLOG_WARN_RL(&rl, "dropping short managment message: %d\n", msg_len); + return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LENGTH); + } + + if (ofmph->type == htons(OFMPT_CAPABILITY_REQUEST)) { + struct ofmp_capability_request *ofmpcr; + + if (msg_len < sizeof(struct ofmp_capability_request)) { + VLOG_WARN_RL(&rl, "dropping short capability request: %d\n", + msg_len); + return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LENGTH); + } + + ofmpcr = (struct ofmp_capability_request *)ofmph; + if (ofmpcr->format != htonl(OFMPCAF_SIMPLE)) { + /* xxx Find a better type than bad subtype */ + return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_SUBTYPE); + } + + send_capability_reply(p, ofconn, ofmph->header.header.xid); + return 0; + } else { + return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_SUBTYPE); + } +} + static int handle_vendor(struct ofproto *p, struct ofconn *ofconn, void *msg) { @@ -2439,7 +2499,11 @@ handle_vendor(struct ofproto *p, struct ofconn *ofconn, void *msg) return executer_handle_request(p->executer, ofconn->rconn, msg); } break; + + case NXT_MGMT: + return handle_ofmp(p, ofconn, msg); } + return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_SUBTYPE); } diff --git a/secchan/ofproto.h b/secchan/ofproto.h index c9a5d8ac..306da524 100644 --- a/secchan/ofproto.h +++ b/secchan/ofproto.h @@ -67,6 +67,7 @@ bool ofproto_is_alive(const struct ofproto *); /* Configuration. */ void ofproto_set_datapath_id(struct ofproto *, uint64_t datapath_id); +void ofproto_set_mgmt_id(struct ofproto *, uint64_t mgmt_id); void ofproto_set_probe_interval(struct ofproto *, int probe_interval); void ofproto_set_max_backoff(struct ofproto *, int max_backoff); void ofproto_set_desc(struct ofproto *, diff --git a/secchan/secchan.8.in b/secchan/secchan.8.in index de8fb70e..23a2bd58 100644 --- a/secchan/secchan.8.in +++ b/secchan/secchan.8.in @@ -1,6 +1,6 @@ .ds PN secchan -.TH secchan 8 "February 2009" "OpenFlow" "OpenFlow Manual" +.TH secchan 8 "March 2009" "OpenFlow" "OpenFlow Manual" .SH NAME secchan \- secure channel connecting an OpenFlow datapath to a controller @@ -197,14 +197,8 @@ the local port network device, and start the DHCP client afterward. .SH OPTIONS .SS "Configuration Options" .TP -\fB-F \fIfile\fR|\fIdir\fR, \fB--config=\fIfile\fR|\fIdir\fR -The \fB-F\fR or \fB--config\fR option specifies a configuration file -or directory. If a regular file is named, then \fBsecchan\fR reads -that file. If a directory is named, then \fBsecchan\fR reads all the -files in that directory whose names consist entirely of English -letters, digits, and the special characters \fB._-\fR and do not begin -with \fB.\fR. -.IP +\fB-F \fIfile\fR, \fB--config=\fIfile\fR +The \fB-F\fR or \fB--config\fR option specifies a configuration file. For a description of the configuration syntax, see \fBvswitchd.conf\fR(5). Currently, only the NetFlow section applies to \fBsecchan\fR. diff --git a/tests/test-classifier.c b/tests/test-classifier.c index 9df259f3..2e768bdf 100644 --- a/tests/test-classifier.c +++ b/tests/test-classifier.c @@ -48,6 +48,7 @@ #include #include #include "flow.h" +#include #include "packets.h" #undef NDEBUG diff --git a/vswitchd/automake.mk b/vswitchd/automake.mk index 9a52500d..75dabfa9 100644 --- a/vswitchd/automake.mk +++ b/vswitchd/automake.mk @@ -8,6 +8,8 @@ vswitchd_vswitchd_SOURCES = \ vswitchd/brcompat.h \ vswitchd/bridge.c \ vswitchd/bridge.h \ + vswitchd/mgmt.c \ + vswitchd/mgmt.h \ vswitchd/vswitchd.c vswitchd_vswitchd_LDADD = \ secchan/libsecchan.a \ diff --git a/vswitchd/brcompat.c b/vswitchd/brcompat.c index 6e98c29d..c187858d 100644 --- a/vswitchd/brcompat.c +++ b/vswitchd/brcompat.c @@ -58,18 +58,18 @@ #include "timeval.h" #include "util.h" #include "vlog-socket.h" +#include "vswitchd.h" #include "vlog.h" #define THIS_MODULE VLM_brcompat +extern bool brc_enabled; + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 60); /* Netlink socket to kernel datapath */ struct nl_sock *nl_sock; -/* Pointer to configuration file name */ -static const char *config_name; - /* xxx Just hangs if datapath is rmmod/insmod. Learn to reconnect? */ /* The Generic Netlink family number used for bridge compatibility. */ @@ -144,57 +144,6 @@ static const struct nl_policy brc_dp_policy[] = { [BRC_GENL_A_DP_NAME] = { .type = NL_A_STRING }, }; -/* Write 'new_cfg' into the configuration file. Returns 0 if successful, - * otherwise a positive errno value. */ -static int -brc_write_config(struct svec *new_cfg) -{ - char *tmp_name; - int fd; - size_t i; - - svec_sort(new_cfg); - svec_unique(new_cfg); - - /* Put the temporary file in the same directory as config_name, so that - * they are guaranteed to be in the same file system and therefore we can - * rename() tmp_name over config_name, and put "~" in its name so that - * cfg.c will ignore it if it is reading all the files in that - * directory. */ - tmp_name = xasprintf("%s.~tmp~", config_name); - - fd = open(tmp_name, O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR|S_IWUSR); - if (fd == -1) { - VLOG_WARN_RL(&rl, "could not open temp config file for writing: %s", - strerror(errno)); - free(tmp_name); - return errno; - } - - for (i = 0; i < new_cfg->n; i++) { - int retval; - const char *entry = new_cfg->names[i]; - retval = write(fd, entry, strlen(entry)); - if (retval != strlen(entry)) { - VLOG_WARN_RL(&rl, "problem writing to temp config file %d: %s", - retval, strerror(errno)); - free(tmp_name); - return errno; - } - } - close(fd); - - if (rename(tmp_name, config_name) < 0) { - VLOG_WARN_RL(&rl, "could not rename temp config file: %s", - strerror(errno)); - free(tmp_name); - return errno; - } - free(tmp_name); - - return 0; -} - /* Modify the existing configuration according to 'act'. The configuration * file will be modified to reflect these changes. The caller is * responsible for causing vswitchd to actually re-read its configuration. */ @@ -202,55 +151,37 @@ void brc_modify_config(const char *dp_name, const char *port_name, enum bmc_action act) { - struct svec new_cfg; - struct svec old_br; - struct svec old_keys; - size_t i, j; + if (!brc_enabled) { + return; + } - if (!config_name) { + if (cfg_lock(NULL)) { + /* Couldn't lock config file. */ + /* xxx Handle this better */ return; } - svec_init(&new_cfg); - svec_init(&old_br); - svec_init(&old_keys); - cfg_get_subsections(&old_br, "bridge"); - for (i = 0; i < old_br.n; i++) { - /* If we're deleting the datapath, skip over its current config. */ - if ((act == BMC_DEL_DP) && !strcmp(old_br.names[i], dp_name)) { - continue; - } + switch (act) { - cfg_get_all_keys(&old_keys, "bridge.%s.port", old_br.names[i]); - for (j = 0; j < old_keys.n; j++) { - if ((act == BMC_DEL_PORT) - && !strcmp(old_br.names[i], dp_name) - && !strcmp(old_keys.names[j], port_name)) { - continue; - } - svec_add_nocopy(&new_cfg, - xasprintf("bridge.%s.port = %s\n", - old_br.names[i], old_keys.names[j])); - } - } - svec_destroy(&old_br); - svec_destroy(&old_keys); + case BMC_ADD_DP: + cfg_add_entry("bridge.%s.port=%s", dp_name, dp_name); + break; - /* Always add the datapath's own device. */ - if (act == BMC_ADD_DP) { - svec_add_nocopy(&new_cfg, - xasprintf("bridge.%s.port = %s\n", dp_name, dp_name)); - } - - if (act == BMC_ADD_PORT) { - svec_add_nocopy(&new_cfg, xasprintf("bridge.%s.port = %s\n", - dp_name, port_name)); - } + case BMC_ADD_PORT: + cfg_add_entry("bridge.%s.port=%s", dp_name, port_name); + break; - brc_write_config(&new_cfg); - svec_destroy(&new_cfg); + case BMC_DEL_DP: + cfg_del_section("bridge.%s", dp_name); + break; - cfg_read(); + case BMC_DEL_PORT: + cfg_del_entry("bridge.%s.port=%s", dp_name, port_name); + break; + } + + cfg_write(); + cfg_unlock(); } static int @@ -262,7 +193,7 @@ brc_add_dp(const char *dp_name) brc_modify_config(dp_name, NULL, BMC_ADD_DP); - bridge_reconfigure(); + reconfigure(); if (!bridge_exists(dp_name)) { return EINVAL; @@ -280,7 +211,7 @@ brc_del_dp(const char *dp_name) brc_modify_config(dp_name, NULL, BMC_DEL_DP); - bridge_reconfigure(); + reconfigure(); if (bridge_exists(dp_name)) { return EINVAL; @@ -355,7 +286,7 @@ brc_handle_port_cmd(struct ofpbuf *buffer, bool add) } /* Force vswitchd to reconfigure itself. */ - bridge_reconfigure(); + reconfigure(); return 0; } @@ -434,12 +365,10 @@ brc_wait(void) } void -brc_init(const char *file_name) +brc_init(void) { if (brc_open(&nl_sock)) { ofp_fatal(0, "could not open brcompat socket. Check " "\"brcompat\" kernel module."); } - - config_name = file_name; } diff --git a/vswitchd/brcompat.h b/vswitchd/brcompat.h index 70d282ec..e503e609 100644 --- a/vswitchd/brcompat.h +++ b/vswitchd/brcompat.h @@ -36,7 +36,7 @@ enum bmc_action { BMC_DEL_PORT }; -void brc_init(const char *); +void brc_init(void); void brc_wait(void); void brc_run(void); void brc_modify_config(const char *dp_name, const char *port_name, diff --git a/vswitchd/bridge.c b/vswitchd/bridge.c index fc46d021..b3d552d2 100644 --- a/vswitchd/bridge.c +++ b/vswitchd/bridge.c @@ -74,6 +74,8 @@ struct dst { uint16_t dp_ifidx; }; +extern uint64_t mgmt_id; + struct iface { struct port *port; /* Containing port. */ size_t port_ifidx; /* Index within containing port. */ @@ -264,6 +266,7 @@ bridge_init(void) bridge_reconfigure(); } +#ifdef HAVE_OPENSSL static bool config_string_change(const char *key, char **valuep) { @@ -277,7 +280,6 @@ config_string_change(const char *key, char **valuep) } } -#ifdef HAVE_OPENSSL static void bridge_configure_ssl(void) { @@ -691,6 +693,13 @@ bridge_exists(const char *name) return bridge_lookup(name) ? true : false; } +uint64_t +bridge_get_datapathid(const char *name) +{ + struct bridge *br = bridge_lookup(name); + return br ? ofproto_get_datapath_id(br->ofproto) : 0; +} + static int if_up(const char *netdev_name) { @@ -745,6 +754,8 @@ bridge_reconfigure_one(struct bridge *br) } cfg_get_all_keys(&new_ports, "bridge.%s.port", br->name); + ofproto_set_mgmt_id(br->ofproto, mgmt_id); + /* Get rid of deleted ports and add new ports. */ svec_sort(&old_ports); assert(svec_is_unique(&old_ports)); @@ -806,6 +817,14 @@ bridge_reconfigure_one(struct bridge *br) pfx = xasprintf("bridge.%s.controller", br->name); controller = cfg_get_string(0, "%s", pfx); ctl = controller ? xstrdup(controller) : NULL; + + /* If a controller is not specified for the bridge, try using the + * management channel's settings. */ + if (!ctl) { + controller = cfg_get_string(0, "mgmt.controller"); + ctl = controller ? xstrdup(controller) : NULL; + } + if (ctl) { const char *fail_mode; diff --git a/vswitchd/bridge.h b/vswitchd/bridge.h index f8afeb82..b6c600ee 100644 --- a/vswitchd/bridge.h +++ b/vswitchd/bridge.h @@ -35,5 +35,6 @@ void bridge_reconfigure(void); int bridge_run(void); void bridge_wait(void); bool bridge_exists(const char *); +uint64_t bridge_get_datapathid(const char *name); #endif /* bridge.h */ diff --git a/vswitchd/mgmt.c b/vswitchd/mgmt.c new file mode 100644 index 00000000..4f7009cb --- /dev/null +++ b/vswitchd/mgmt.c @@ -0,0 +1,667 @@ +/* Copyright (c) 2009 Nicira Networks + * + * 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 "bridge.h" +#include "cfg.h" +#include "list.h" +#include "mgmt.h" +#include "openflow/nicira-ext.h" +#include "openflow/openflow.h" +#include "openflow/openflow-mgmt.h" +#include "ofpbuf.h" +#include "packets.h" +#include "rconn.h" +#include "svec.h" +#include "vconn.h" +#include "vswitchd.h" +#include "xtoxll.h" + +#define THIS_MODULE VLM_mgmt +#include "vlog.h" + +#define MAX_BACKOFF_DEFAULT 15 +#define PROBE_INTERVAL_DEFAULT 15 + +static struct svec mgmt_cfg; +static uint8_t cfg_cookie[CFG_COOKIE_LEN]; +static struct rconn *mgmt_rconn; +static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(60, 60); +static struct svec capabilities; +uint64_t mgmt_id; + + +#define TXQ_LIMIT 128 /* Max number of packets to queue for tx. */ +struct rconn_packet_counter *txqlen; /* # pkts queued for tx on mgmt_rconn. */ + +static uint64_t pick_fallback_mgmt_id(void); +static void send_config_update(uint32_t xid, bool use_xid); +static void send_resources_update(uint32_t xid, bool use_xid); + +void +mgmt_init(void) +{ + txqlen = rconn_packet_counter_create(); + + svec_init(&mgmt_cfg); + svec_init(&capabilities); + svec_add_nocopy(&capabilities, + xasprintf("com.nicira.mgmt.manager=true\n")); + + mgmt_id = cfg_get_mac(0, "mgmt.id"); + if (!mgmt_id) { + /* Randomly generate a mgmt id */ + mgmt_id = pick_fallback_mgmt_id(); + } +} + +#ifdef HAVE_OPENSSL +static bool +config_string_change(const char *key, char **valuep) +{ + const char *value = cfg_get_string(0, "%s", key); + if (value && (!*valuep || strcmp(value, *valuep))) { + free(*valuep); + *valuep = xstrdup(value); + return true; + } else { + return false; + } +} + +static void +bridge_configure_ssl(void) +{ + /* XXX SSL should be configurable on a per-bridge basis. + * XXX should be possible to de-configure SSL. */ + static char *private_key_file; + static char *certificate_file; + static char *cacert_file; + + if (config_string_change("ssl.private-key", &private_key_file)) { + vconn_ssl_set_private_key_file(private_key_file); + } + + if (config_string_change("ssl.certificate", &certificate_file)) { + vconn_ssl_set_certificate_file(certificate_file); + } + + if (config_string_change("ssl.ca-cert", &cacert_file)) { + vconn_ssl_set_ca_cert_file(cacert_file, + cfg_get_bool(0, "ssl.bootstrap-ca-cert")); + } +} +#endif + +void +mgmt_reconfigure(void) +{ + struct svec new_cfg; + uint8_t new_cookie[CFG_COOKIE_LEN]; + bool cfg_updated = false; + const char *controller_name; + int max_backoff; + int probe_interval; + int retval; + + if (!cfg_has_section("mgmt")) { + if (mgmt_rconn) { + rconn_destroy(mgmt_rconn); + mgmt_rconn = NULL; + } + return; + } + + /* If this is an established connection, send a resources update. */ + /* xxx This is wasteful if there were no resource changes!!! */ + if (mgmt_rconn) { + send_resources_update(0, false); + } + + cfg_get_cookie(new_cookie); + if (memcmp(cfg_cookie, new_cookie, sizeof(cfg_cookie))) { + memcpy(cfg_cookie, new_cookie, sizeof(cfg_cookie)); + cfg_updated = true; + } + + svec_init(&new_cfg); + cfg_get_subsections(&new_cfg, "mgmt"); + if (svec_equal(&mgmt_cfg, &new_cfg)) { + /* Reconnecting to the controller causes the config file to be + * resent automatically. If we're not reconnecting and the + * config file has changed, we need to notify the controller of + * changes. */ + if (cfg_updated) { + send_config_update(0, false); + } + return; + } + + controller_name = cfg_get_string(0, "mgmt.controller"); + if (!controller_name) { + VLOG_ERR("no controller specified for managment"); + return; + } + + max_backoff = cfg_get_int(0, "mgmt.max-backoff"); + if (max_backoff < 1) { + max_backoff = MAX_BACKOFF_DEFAULT; + } else if (max_backoff > 3600) { + max_backoff = 3600; + } + + probe_interval = cfg_get_int(0, "mgmt.probe-interval"); + if (probe_interval < 5) { + probe_interval = MAX_BACKOFF_DEFAULT; + } + + /* xxx If this changes, we need to restart bridges to use new id, + * xxx but they need the id before the connect to controller, but we + * xxx need their dpids. */ + mgmt_id = cfg_get_mac(0, "mgmt.id"); + if (!mgmt_id) { + /* Randomly generate a mgmt id */ + mgmt_id = pick_fallback_mgmt_id(); + } + + svec_swap(&new_cfg, &mgmt_cfg); + svec_destroy(&new_cfg); + +#ifdef HAVE_OPENSSL + /* Configure SSL. */ + bridge_configure_ssl(); +#endif + + if (mgmt_rconn) { + rconn_destroy(mgmt_rconn); + mgmt_rconn = NULL; + } + mgmt_rconn = rconn_create(probe_interval, max_backoff); + if (controller_name) { + retval = rconn_connect(mgmt_rconn, controller_name); + if (retval == EAFNOSUPPORT) { + VLOG_ERR("no support for %s vconn", controller_name); + } + } +} + +static int +send_openflow_buffer(struct ofpbuf *buffer) +{ + int retval; + + update_openflow_length(buffer); + retval = rconn_send_with_limit(mgmt_rconn, buffer, txqlen, TXQ_LIMIT); + if (retval) { + VLOG_WARN_RL(&rl, "send to %s failed: %s", + rconn_get_name(mgmt_rconn), strerror(retval)); + } + return retval; +} + +static void +send_features_reply(uint32_t xid) +{ + struct ofpbuf *buffer; + struct ofp_switch_features *ofr; + + ofr = make_openflow_xid(sizeof *ofr, OFPT_FEATURES_REPLY, xid, &buffer); + ofr->datapath_id = 0; + ofr->n_tables = 0; + ofr->n_buffers = 0; + ofr->capabilities = 0; + ofr->actions = 0; + send_openflow_buffer(buffer); +} + +static void * +make_ofmp_xid(size_t ofmp_len, uint16_t type, uint32_t xid, + struct ofpbuf **bufferp) +{ + struct ofmp_header *oh; + + oh = make_openflow_xid(ofmp_len, OFPT_VENDOR, xid, bufferp); + oh->header.vendor = htonl(NX_VENDOR_ID); + oh->header.subtype = htonl(NXT_MGMT); + oh->type = htons(type); + + return oh; +} + +static void * +make_ofmp(size_t ofmp_len, uint16_t type, struct ofpbuf **bufferp) +{ + struct ofmp_header *oh; + + oh = make_openflow(ofmp_len, OFPT_VENDOR, bufferp); + oh->header.vendor = htonl(NX_VENDOR_ID); + oh->header.subtype = htonl(NXT_MGMT); + oh->type = htons(type); + + return oh; +} + +static void +send_capability_reply(uint32_t xid) +{ + int i; + struct ofpbuf *buffer; + struct ofmp_capability_reply *ofmpcr; + + ofmpcr = make_ofmp_xid(sizeof *ofmpcr, OFMPT_CAPABILITY_REPLY, + xid, &buffer); + ofmpcr->format = htonl(OFMPCOF_SIMPLE); + ofmpcr->mgmt_id = htonll(mgmt_id); + for (i=0; itype = htons(OFMPTSR_DP); + dp_tlv->len = htons(sizeof(*dp_tlv)); + + dp_tlv->dp_id = htonll(dp_id); + memcpy(dp_tlv->name, br_list.names[i], strlen(br_list.names[i])+1); + } + + /* Put end marker. */ + tlv = ofpbuf_put_zeros(buffer, sizeof(*tlv)); + tlv->type = htons(OFMPTSR_END); + tlv->len = htons(sizeof(*tlv)); + send_openflow_buffer(buffer); +} + +static void +send_config_update(uint32_t xid, bool use_xid) +{ + struct ofpbuf *buffer; + struct ofmp_config_update *ofmpcu; + + if (use_xid) { + ofmpcu = make_ofmp_xid(sizeof *ofmpcu, OFMPT_CONFIG_UPDATE, + xid, &buffer); + } else { + ofmpcu = make_ofmp(sizeof *ofmpcu, OFMPT_CONFIG_UPDATE, &buffer); + } + + ofmpcu->format = htonl(OFMPCOF_SIMPLE); + memcpy(ofmpcu->cookie, cfg_cookie, sizeof(ofmpcu->cookie)); + cfg_buf_put(buffer); + send_openflow_buffer(buffer); +} + +static void +send_config_update_ack(uint32_t xid, bool success) +{ + struct ofpbuf *buffer; + struct ofmp_config_update_ack *ofmpcua; + + ofmpcua = make_ofmp_xid(sizeof *ofmpcua, OFMPT_CONFIG_UPDATE_ACK, + xid, &buffer); + + ofmpcua->format = htonl(OFMPCOF_SIMPLE); + if (success) { + ofmpcua->flags = htonl(OFMPCUAF_SUCCESS); + } + cfg_get_cookie(ofmpcua->cookie); + send_openflow_buffer(buffer); +} + +static void +send_ofmp_error_msg(uint32_t xid, uint16_t type, uint16_t code, + const void *data, size_t len) +{ + struct ofpbuf *buffer; + struct ofmp_error_msg *oem; + + oem = make_ofmp_xid(sizeof(*oem)+len, OFMPT_ERROR, xid, &buffer); + oem->type = htons(type); + oem->code = htons(code); + memcpy(oem->data, data, len); + send_openflow_buffer(buffer); +} + +static void +send_error_msg(uint32_t xid, uint16_t type, uint16_t code, + const void *data, size_t len) +{ + struct ofpbuf *buffer; + struct ofp_error_msg *oem; + + oem = make_openflow_xid(sizeof(*oem)+len, OFPT_ERROR, xid, &buffer); + oem->type = htons(type); + oem->code = htons(code); + memcpy(oem->data, data, len); + send_openflow_buffer(buffer); +} + +static int +recv_echo_request(uint32_t xid UNUSED, const void *msg) +{ + const struct ofp_header *rq = msg; + send_openflow_buffer(make_echo_reply(rq)); + return 0; +} + +static int +recv_features_request(uint32_t xid, const void *msg UNUSED) +{ + send_features_reply(xid); + return 0; +} + +static int +recv_set_config(uint32_t xid UNUSED, const void *msg UNUSED) +{ + /* Nothing to configure! */ + return 0; +} + +static int +recv_ofmp_capability_request(uint32_t xid, const struct ofmp_header *ofmph) +{ + struct ofmp_capability_request *ofmpcr; + + if (htons(ofmph->header.header.length) != sizeof(*ofmpcr)) { + /* xxx Send error */ + return -EINVAL; + } + + ofmpcr = (struct ofmp_capability_request *)ofmph; + if (ofmpcr->format != htonl(OFMPCAF_SIMPLE)) { + /* xxx Send error */ + return -EINVAL; + } + + send_capability_reply(xid); + + return 0; +} + +static int +recv_ofmp_resources_request(uint32_t xid, const void *msg UNUSED) +{ + send_resources_update(xid, true); + return 0; +} + +static int +recv_ofmp_config_request(uint32_t xid, const struct ofmp_header *ofmph) +{ + struct ofmp_config_request *ofmpcr; + + if (htons(ofmph->header.header.length) != sizeof(*ofmpcr)) { + /* xxx Send error */ + return -EINVAL; + } + + ofmpcr = (struct ofmp_config_request *)ofmph; + if (ofmpcr->format != htonl(OFMPCOF_SIMPLE)) { + /* xxx Send error */ + return -EINVAL; + } + + send_config_update(xid, true); + + return 0; +} + +static int +recv_ofmp_config_update(uint32_t xid, const struct ofmp_header *ofmph) +{ + struct ofmp_config_update *ofmpcu; + int data_len; + + data_len = htons(ofmph->header.header.length) - sizeof(*ofmpcu); + if (data_len <= sizeof(*ofmpcu)) { + /* xxx Send error. */ + return -EINVAL; + } + + ofmpcu = (struct ofmp_config_update *)ofmph; + if (ofmpcu->format != htonl(OFMPCOF_SIMPLE)) { + /* xxx Send error */ + return -EINVAL; + } + + /* Check if the supplied cookie matches our current understanding of + * it. If they don't match, tell the controller and let it sort + * things out. */ + if (cfg_lock(ofmpcu->cookie)) { + /* xxx cfg_lock can fail for other reasons, such as being + * xxx locked... */ + /* xxx This error message should probably send diff info. */ + VLOG_WARN_RL(&rl, "config update failed due to bad cookie\n"); + //send_config_update_ack(xid, false); + send_ofmp_error_msg(xid, OFMPET_BAD_CONFIG, OFMPBCC_OLD_COOKIE, + ofmph, htons(ofmph->header.header.length)); + return 0; + } + + /* xxx We should probably do more sanity checking than this. */ + + cfg_write_data(ofmpcu->data, data_len); + cfg_unlock(); + + /* Send the ACK before running reconfigure, since our management + * connection settings may have changed. */ + send_config_update_ack(xid, true); + + reconfigure(); + + + return 0; +} + +static +int recv_ofmp(uint32_t xid, struct ofmp_header *ofmph) +{ + /* xxx Should sanity-check for min/max length */ + switch (ntohs(ofmph->type)) + { + case OFMPT_CAPABILITY_REQUEST: + return recv_ofmp_capability_request(xid, ofmph); + case OFMPT_RESOURCES_REQUEST: + return recv_ofmp_resources_request(xid, ofmph); + case OFMPT_CONFIG_REQUEST: + return recv_ofmp_config_request(xid, ofmph); + case OFMPT_CONFIG_UPDATE: + return recv_ofmp_config_update(xid, ofmph); + default: + VLOG_WARN_RL(&rl, "unknown mgmt message: %d", + ntohs(ofmph->type)); + return -EINVAL; + } +} + +static int +recv_nx_msg(uint32_t xid, const void *oh) +{ + const struct nicira_header *nh = oh; + + switch (ntohl(nh->subtype)) { + + case NXT_MGMT: + return recv_ofmp(xid, (struct ofmp_header *)oh); + + default: + send_error_msg(xid, OFPET_BAD_REQUEST, OFPBRC_BAD_SUBTYPE, + oh, htons(nh->header.length)); + return -EINVAL; + } +} + +static int +recv_vendor(uint32_t xid, const void *oh) +{ + const struct ofp_vendor_header *ovh = oh; + + switch (ntohl(ovh->vendor)) + { + case NX_VENDOR_ID: + return recv_nx_msg(xid, oh); + + default: + VLOG_WARN_RL(&rl, "unknown vendor: 0x%x", ntohl(ovh->vendor)); + send_error_msg(xid, OFPET_BAD_REQUEST, OFPBRC_BAD_VENDOR, + oh, ntohs(ovh->header.length)); + return -EINVAL; + } +} + +static int +handle_msg(uint32_t xid, const void *msg, size_t length) +{ + int (*handler)(uint32_t, const void *); + struct ofp_header *oh; + size_t min_size; + + /* Check encapsulated length. */ + oh = (struct ofp_header *) msg; + if (ntohs(oh->length) > length) { + return -EINVAL; + } + assert(oh->version == OFP_VERSION); + + /* Figure out how to handle it. */ + switch (oh->type) { + case OFPT_ECHO_REQUEST: + min_size = sizeof(struct ofp_header); + handler = recv_echo_request; + break; + case OFPT_ECHO_REPLY: + return 0; + case OFPT_FEATURES_REQUEST: + min_size = sizeof(struct ofp_header); + handler = recv_features_request; + break; + case OFPT_SET_CONFIG: + min_size = sizeof(struct ofp_switch_config); + handler = recv_set_config; + break; + case OFPT_VENDOR: + min_size = sizeof(struct ofp_vendor_header); + handler = recv_vendor; + break; + default: + VLOG_WARN_RL(&rl, "unknown openflow type: %d", oh->type); + send_error_msg(xid, OFPET_BAD_REQUEST, OFPBRC_BAD_TYPE, + msg, length); + return -EINVAL; + } + + /* Handle it. */ + if (length < min_size) { + return -EFAULT; + } + return handler(xid, msg); +} + +void +mgmt_run(void) +{ + int i; + + if (!mgmt_rconn) { + return; + } + + rconn_run(mgmt_rconn); + + /* Do some processing, but cap it at a reasonable amount so that + * other processing doesn't starve. */ + for (i=0; i<50; i++) { + struct ofpbuf *buffer; + struct ofp_header *oh; + + buffer = rconn_recv(mgmt_rconn); + if (!buffer) { + break; + } + + if (buffer->size >= sizeof *oh) { + oh = buffer->data; + handle_msg(oh->xid, buffer->data, buffer->size); + ofpbuf_delete(buffer); + } else { + VLOG_WARN_RL(&rl, "received too-short OpenFlow message"); + } + } +} + +void +mgmt_wait(void) +{ + if (!mgmt_rconn) { + return; + } + + rconn_run_wait(mgmt_rconn); + rconn_recv_wait(mgmt_rconn); +} + +static uint64_t +pick_fallback_mgmt_id(void) +{ + uint8_t ea[ETH_ADDR_LEN]; + eth_addr_random(ea); + ea[0] = 0x00; /* Set Nicira OUI. */ + ea[1] = 0x23; + ea[2] = 0x20; + return eth_addr_to_uint64(ea); +} diff --git a/vswitchd/mgmt.h b/vswitchd/mgmt.h new file mode 100644 index 00000000..ef1cd55b --- /dev/null +++ b/vswitchd/mgmt.h @@ -0,0 +1,36 @@ +/* Copyright (c) 2009 Nicira Networks + * + * 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. + */ + +#ifndef VSWITCHD_MGMT_H +#define VSWITCHD_MGMT_H 1 + +void mgmt_init(void); +void mgmt_reconfigure(void); +void mgmt_run(void); +void mgmt_wait(void); +uint64_t mgmt_get_mgmt_id(void); + +#endif /* mgmt.h */ diff --git a/vswitchd/vswitchd.8.in b/vswitchd/vswitchd.8.in index 9a157192..39fd1bab 100644 --- a/vswitchd/vswitchd.8.in +++ b/vswitchd/vswitchd.8.in @@ -1,24 +1,28 @@ .ds PN vswitchd . -.TH vswitchd 8 "December 2008" "OpenFlow" "OpenFlow Manual" +.TH vswitchd 8 "March 2009" "OpenFlow" "OpenFlow Manual" . .SH NAME vswitchd \- OpenFlow-based virtual switch daemon . .SH SYNOPSIS .B vswitchd -\fB--config=\fIfile\fR|\fIdir\fR\&... -[\fIoption\fR]\&... +[\fI--brcompat\fR]\& +\fIconfig\fR . .SH DESCRIPTION A daemon that manages and controls any number of OpenFlow-based virtual switches on the local machine. .PP +The mandatory \fIconfig\fR argument specifies a configuration file. +For a description of \fBvswitchd\fR configuration syntax, see +\fBvswitchd.conf\fR(5). +.PP At startup or upon receipt of a \fBSIGHUP\fR signal, \fBvswitchd\fR -reads the configuration files or directories specified on the command -line. It sets up OpenFlow datapaths and then operates switching -across each bridge described in its configuration files. If a logfile -was specified on the command line it will also be opened or reopened. +reads the configuration file. It sets up OpenFlow datapaths and then +operates switching across each bridge described in its configuration +files. If a logfile was specified on the command line it will also +be opened or reopened. .PP \fBvswitchd\fR virtual switches may be configured with any of the following features: @@ -63,33 +67,13 @@ OpenFlow source distribution for instructions on how to build and load the OpenFlow kernel module. .PP .SH OPTIONS -. -At least one of the following options is required: -. -.TP -\fB-F \fIfile\fR|\fIdir\fR, \fB--config=\fIfile\fR|\fIdir\fR -. -The \fB-F\fR or \fB--config\fR option specifies a configuration file -or directory. If a regular file is named, then \fBvswitchd\fR reads -that file. If a directory is named, then \fBvswitchd\fR reads all the -files in that directory whose names consist entirely of English -letters, digits, and the special characters \fB._-\fR and do not begin -with \fB.\fR. -.IP -For a description of \fBvswitchd\fR configuration syntax, see -\fBvswitchd.conf\fR(5). -. -.PP -The rest of \fBvswitchd\fR's options are truly optional: .TP -\fB-b \fIfile\fR, \fB--brcompat=\fIfile\fR +\fB--brcompat\fR . -The \fB-b\fR or \fB--brcompat\fR option puts \fBvswitchd\fR into bridge -compatibility mode. This means it listens for bridge ioctl commands +The \fB--brcompat\fR option puts \fBvswitchd\fR into bridge compatibility +mode. This means it listens for bridge ioctl commands (e.g., those generated by the \fBbrctl\fR program) to add or remove -datapaths and the interfaces that attach to them. The \fIfile\fR -argument should be the master list for Bridge Configuration and be one -of the files included in the \fB--config\fR option. +datapaths and the interfaces that attach to them. . .IP "" This option requires the \fBbrcompat_mod.ko\fR kernel module to be diff --git a/vswitchd/vswitchd.c b/vswitchd/vswitchd.c index c9e4bbc0..113555a7 100644 --- a/vswitchd/vswitchd.c +++ b/vswitchd/vswitchd.c @@ -42,6 +42,7 @@ #include "daemon.h" #include "fault.h" #include "leak-checker.h" +#include "mgmt.h" #include "poll-loop.h" #include "process.h" #include "signals.h" @@ -51,6 +52,7 @@ #include "vconn-ssl.h" #include "vconn.h" #include "vlog-socket.h" +#include "vswitchd.h" #include "vlog.h" #define THIS_MODULE VLM_vswitchd @@ -58,10 +60,8 @@ static void parse_options(int argc, char *argv[]); static void usage(void) NO_RETURN; -static void reconfigure(void); - -static bool brc_enabled = false; -struct svec config_files; +bool brc_enabled = false; +char *config_file; int main(int argc, char *argv[]) @@ -74,7 +74,6 @@ main(int argc, char *argv[]) register_fault_handlers(); time_init(); vlog_init(); - svec_init(&config_files); parse_options(argc, argv); signal(SIGPIPE, SIG_IGN); sighup = signal_register(SIGHUP); @@ -89,7 +88,9 @@ main(int argc, char *argv[]) } cfg_read(); + mgmt_init(); bridge_init(); + mgmt_reconfigure(); need_reconfigure = false; for (;;) { @@ -98,6 +99,7 @@ main(int argc, char *argv[]) vlog_reopen_log_file(); reconfigure(); } + mgmt_run(); if (bridge_run()) { need_reconfigure = true; } @@ -108,6 +110,7 @@ main(int argc, char *argv[]) } signal_wait(sighup); + mgmt_wait(); bridge_wait(); poll_block(); } @@ -115,24 +118,25 @@ main(int argc, char *argv[]) return 0; } -static void +void reconfigure(void) { cfg_read(); bridge_reconfigure(); + mgmt_reconfigure(); } static void parse_options(int argc, char *argv[]) { enum { + OPT_BRCOMPAT = UCHAR_MAX + 1, OPT_PEER_CA_CERT, VLOG_OPTION_ENUMS, LEAK_CHECKER_OPTION_ENUMS }; static struct option long_options[] = { - {"brcompat", required_argument, 0, 'b'}, - {"config", required_argument, 0, 'F'}, + {"brcompat", no_argument, 0, OPT_BRCOMPAT}, {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'V'}, DAEMON_LONG_OPTIONS, @@ -145,10 +149,9 @@ parse_options(int argc, char *argv[]) {0, 0, 0, 0}, }; char *short_options = long_options_to_short_options(long_options); - bool configured = false; + int error; for (;;) { - int error; int c; c = getopt_long(argc, argv, short_options, long_options, NULL); @@ -157,19 +160,9 @@ parse_options(int argc, char *argv[]) } switch (c) { - case 'b': + case OPT_BRCOMPAT: brc_enabled = true; - brc_init(optarg); - break; - - case 'F': - configured = true; - svec_add(&config_files, optarg); - error = cfg_add_file(optarg); - if (error) { - ofp_fatal(error, "failed to add configuration file or " - "directory \"%s\"", optarg); - } + brc_init(); break; case 'H': @@ -201,11 +194,19 @@ parse_options(int argc, char *argv[]) } free(short_options); - if (!configured) { - ofp_fatal(0, "at least one -F or --config option is required"); + argc -= optind; + argv += optind; + + if (argc != 1) { + ofp_fatal(0, "config file is only non-option argument; " + "use --help for usage"); } - if (optind < argc) { - ofp_fatal(0, "non-option arguments not accepted; use --help for help"); + + config_file = argv[0]; + error = cfg_set_file(config_file); + if (error) { + ofp_fatal(error, "failed to add configuration file \"%s\"", + optarg); } } @@ -213,12 +214,11 @@ static void usage(void) { printf("%s: virtual switch daemon\n" - "usage: %s [OPTIONS]\n", + "usage: %s [OPTIONS] CONFIG\n" + "CONFIG is a configuration file in vswitchd.conf(5) format.\n", program_name, program_name); - printf("\nConfiguration options (must specify at least one):\n" - " -F, --config=FILE|DIR reads configuration from FILE or DIR\n" - "\nCompatibility options:\n" - " -b, --brcompat=FILE run with bridge compatibility hooks\n"); + printf("\nCompatibility options:\n" + " --brcompat run with bridge compatibility hooks\n"); daemon_usage(); vlog_usage(); printf("\nOther options:\n" diff --git a/vswitchd/vswitchd.conf.5 b/vswitchd/vswitchd.conf.5 index 2632726b..6792a43b 100644 --- a/vswitchd/vswitchd.conf.5 +++ b/vswitchd/vswitchd.conf.5 @@ -10,9 +10,8 @@ vswitchd.conf \- configuration file for \fBvswitchd\fR . .SH DESCRIPTION -\fBvswitchd\fR(8), the virtual switch daemon, is configured using one -or more configuration files named on the command line. This manual -page describes the syntax of these configuration files. +This manual page describes the syntax for the configuration file used +by \fBvswitchd\fR(8), the virtual switch daemon. .PP The configuration file is based on key-value pairs, which are given one per line in the form \fIkey\fB=\fIvalue\fR. Each \fIkey\fR diff --git a/vswitchd/vswitchd.h b/vswitchd/vswitchd.h new file mode 100644 index 00000000..a9617afb --- /dev/null +++ b/vswitchd/vswitchd.h @@ -0,0 +1,32 @@ +/* Copyright (c) 2009 Nicira Networks + * + * 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. + */ + +#ifndef VSWITCHD_H +#define VSWITCHD_H 1 + +void reconfigure(void); + +#endif /* vswitchd.h */