lib/backtrace.h \
lib/bitmap.c \
lib/bitmap.h \
+ lib/cfg.c \
+ lib/cfg.h \
lib/command-line.c \
lib/command-line.h \
lib/compiler.h \
--- /dev/null
+/* Copyright (c) 2008, 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 <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 "cfg.h"
+#include <arpa/inet.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <netinet/in.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include "dynamic-string.h"
+#include "packets.h"
+#include "svec.h"
+#include "util.h"
+
+#define THIS_MODULE VLM_cfg
+#include "vlog.h"
+
+/* List of configuration files. */
+static struct svec cfg_files = SVEC_EMPTY_INITIALIZER;
+
+/* 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,
+ const char *id);
+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);
+static char *find_key(const char *);
+static bool parse_mac(const char *, uint8_t mac[6]);
+static bool is_key(const char *);
+static bool is_int(const char *);
+static bool is_bool(const char *);
+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);
+
+/* 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.
+ *
+ * If 'file_name' names a file, then cfg_read() will read it. If 'file_name'
+ * names a directory, then cfg_read() will read all of the files in that
+ * directory whose names consist entirely of the English letters, digits,
+ * periods, underscores, and hyphens and do not begin with a period.
+ * Subdirectories are not processed recursively.
+ *
+ * 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)
+{
+ int fd;
+
+ /* Make sure that we can open this file or directory for reading. */
+ fd = open(file_name, O_RDONLY);
+ if (fd < 0) {
+ return errno;
+ }
+ close(fd);
+
+ /* Add it to the list. */
+ VLOG_INFO("using \"%s\" as a configuration file", file_name);
+ svec_add(&cfg_files, file_name);
+ return 0;
+}
+
+/* Reads all of the configuration files or directories that have been added
+ * with cfg_add_file(), merges their content. Any previous configuration is
+ * replaced. */
+void
+cfg_read(void)
+{
+ size_t i;
+
+ /* 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);
+ } else {
+ VLOG_WARN("\"%s\" is not a regular file or a directory, ignoring",
+ fn);
+ }
+ }
+
+ if (VLOG_IS_DBG_ENABLED()) {
+ size_t i;
+
+ VLOG_DBG("new configuration:");
+ for (i = 0; i < cfg.n; i++) {
+ VLOG_DBG("%s", cfg.names[i]);
+ }
+ }
+}
+
+/* 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'. */
+#define FORMAT_KEY(FORMAT, DST) \
+ do { \
+ va_list args__; \
+ va_start(args__, FORMAT); \
+ (DST) = xvasprintf(FORMAT, args__); \
+ va_end(args__); \
+ } while (0)
+
+/* Returns true if the configuration includes a key named 'key'. */
+bool
+cfg_has(const char *key_, ...)
+{
+ char *key;
+ bool retval;
+
+ FORMAT_KEY(key_, key);
+ retval = find_key(key) != NULL;
+ free(key);
+ return retval;
+}
+
+bool
+cfg_is_valid(enum cfg_flags flags, const char *key_, ...)
+{
+ char *key, **first, **last, **p;
+ size_t n;
+ bool retval;
+
+ FORMAT_KEY(key_, key);
+ first = find_key_le(key);
+ last = find_key_ge(key);
+ n = last - first;
+ retval = ((!(flags & CFG_REQUIRED) || n)
+ && (!(flags & CFG_MULTIPLE) || n <= 1));
+ for (p = first; retval && p < last; p++) {
+ retval = is_type(strchr(*p, '=') + 1, flags);
+ }
+ free(key);
+ return retval;
+}
+
+/* Returns true if the configuration includes at least one key whose name
+ * begins with 'section' followed by a dot. */
+bool
+cfg_has_section(const char *section_, ...)
+{
+ struct ds section;
+ bool retval;
+ 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);
+
+ p = find_key_le(ds_cstr(§ion));
+ retval = *p && !strncmp(section.string, *p, section.length);
+
+ ds_destroy(§ion);
+ return retval;
+}
+
+/* Returns the number of values for the given 'key'. The return value is 0 if
+ * no values exist for 'key'. */
+int
+cfg_count(const char *key_, ...)
+{
+ char *key;
+ int retval;
+
+ FORMAT_KEY(key_, key);
+ retval = find_key_ge(key) - find_key_le(key);
+ free(key);
+ return retval;
+}
+
+/* Fills 'svec' with all of the immediate subsections of 'section'. For
+ * example, if 'section' is "bridge" and keys bridge.a, bridge.b, bridge.b.c,
+ * and bridge.c.x.y.z exist, then 'svec' would be initialized to a, b, and
+ * c. The caller must first initialize 'svec'. */
+void
+cfg_get_subsections(struct svec *svec, 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);
+
+ svec_clear(svec);
+ for (p = find_key_le(ds_cstr(§ion));
+ *p && !strncmp(section.string, *p, section.length);
+ p++) {
+ const char *ss = *p + section.length;
+ size_t ss_len = strcspn(ss, ".=");
+ svec_add_nocopy(svec, xmemdup0(ss, ss_len));
+ }
+ svec_unique(svec);
+ ds_destroy(§ion);
+}
+
+/* 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
+ * cfg_read(). */
+const char *
+cfg_get_string(int idx, const char *key_, ...)
+{
+ const char *retval;
+ char *key;
+
+ FORMAT_KEY(key_, key);
+ retval = get_nth_value(idx, key);
+ free(key);
+ return retval;
+}
+
+/* Returns the value numbered 'idx' of 'key'. Returns a null pointer if 'idx'
+ * is greater than or equal to cfg_count(key) or if the value 'idx' of 'key' is
+ * not a valid key. The caller must not modify or free the returned string or
+ * retain its value beyond the next call to cfg_read(). */
+const char *
+cfg_get_key(int idx, const char *key_, ...)
+{
+ const char *value, *retval;
+ char *key;
+
+ FORMAT_KEY(key_, key);
+ value = get_nth_value(idx, key);
+ retval = value && is_key(value) ? value : NULL;
+ free(key);
+ return retval;
+}
+
+/* Returns the value numbered 'idx' of 'key', converted to an integer. Returns
+ * 0 if 'idx' is greater than or equal to cfg_count(key) or if the value 'idx'
+ * of 'key' is not a valid integer. */
+int
+cfg_get_int(int idx, const char *key_, ...)
+{
+ const char *value;
+ int retval;
+ char *key;
+
+ FORMAT_KEY(key_, key);
+ value = get_nth_value(idx, key);
+ retval = value && is_int(value) ? atoi(value) : 0;
+ free(key);
+ return retval;
+}
+
+/* Returns the value numbered 'idx' of 'key', converted to a boolean value.
+ * Returns 0 if 'idx' is greater than or equal to cfg_count(key) or if the
+ * value 'idx' of 'key' is not a valid boolean. */
+bool
+cfg_get_bool(int idx, const char *key_, ...)
+{
+ const char *value;
+ bool retval;
+ char *key;
+
+ FORMAT_KEY(key_, key);
+ value = get_nth_value(idx, key);
+ retval = value && is_bool(value) ? !strcmp(value, "true") : false;
+ free(key);
+ return retval;
+}
+
+/* Returns the value numbered 'idx' of 'key', converted to an IP address in
+ * network byte order. Returns 0 if 'idx' is greater than or equal to
+ * cfg_count(key) or if the value 'idx' of 'key' is not a valid IP address (as
+ * determined by inet_aton()). */
+uint32_t
+cfg_get_ip(int idx, const char *key_, ...)
+{
+ struct in_addr addr;
+ const char *value;
+ char *key;
+
+ FORMAT_KEY(key_, key);
+ value = get_nth_value(idx, key);
+ if (!value || !inet_aton(value, &addr)) {
+ addr.s_addr = htonl(0);
+ }
+ free(key);
+ return addr.s_addr;
+}
+
+/* Returns the value numbered 'idx' of 'key', converted to an MAC address in
+ * host byte order. Returns 0 if 'idx' is greater than or equal to
+ * cfg_count(key) or if the value 'idx' of 'key' is not a valid MAC address in
+ * the format "##:##:##:##:##:##". */
+uint64_t
+cfg_get_mac(int idx, const char *key_, ...)
+{
+ uint8_t mac[ETH_ADDR_LEN];
+ const char *value;
+ char *key;
+
+ FORMAT_KEY(key_, key);
+ value = get_nth_value(idx, key);
+ if (!value || !parse_mac(value, mac)) {
+ memset(mac, 0, sizeof mac);
+ }
+ free(key);
+ return eth_addr_to_uint64(mac);
+}
+
+/* Returns the value numbered 'idx' of 'key', converted to an integer. Returns
+ * -1 if 'idx' is greater than or equal to cfg_count(key) or if the value 'idx'
+ * of 'key' is not a valid integer between 0 and 4095. */
+int
+cfg_get_vlan(int idx, const char *key_, ...)
+{
+ const char *value;
+ int retval;
+ char *key;
+
+ FORMAT_KEY(key_, key);
+ value = get_nth_value(idx, key);
+ if (value && is_int(value)) {
+ retval = atoi(value);
+ if (retval < 0 || retval > 4095) {
+ retval = -1;
+ }
+ } else {
+ retval = -1;
+ }
+ free(key);
+ return retval;
+}
+
+/* Fills 'svec' with all of the string values of 'key'. The caller must
+ * first initialize 'svec'. */
+void
+cfg_get_all_strings(struct svec *svec, const char *key_, ...)
+{
+ char **p, **q;
+ char *key;
+
+ FORMAT_KEY(key_, key);
+ svec_clear(svec);
+ for (p = find_key_le(key), q = find_key_ge(key); p < q; p++) {
+ svec_add(svec, extract_value(*p));
+ }
+ free(key);
+}
+
+/* Fills 'svec' with all of the values of 'key' that are valid keys.
+ * Values of 'key' that are not valid keys are omitted. The caller
+ * must first initialize 'svec'. */
+void
+cfg_get_all_keys(struct svec *svec, const char *key_, ...)
+{
+ char **p, **q;
+ char *key;
+
+ FORMAT_KEY(key_, key);
+ svec_clear(svec);
+ for (p = find_key_le(key), q = find_key_ge(key); p < q; p++) {
+ const char *value = extract_value(*p);
+ if (is_key(value)) {
+ svec_add(svec, value);
+ }
+ }
+ free(key);
+}
+\f
+#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)
+{
+ if (len >= 2) {
+ size_t i;
+
+ for (i = 0; i < len - 1; i++) {
+ if (key[i] == '.' && key[i + 1] == '.') {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+static bool
+is_valid_key(const char *key, size_t len,
+ const char *file_name, int line_number, const char *id)
+{
+ if (!len) {
+ VLOG_ERR("%s:%d: missing %s name", file_name, line_number, id);
+ return false;
+ } else if (key[0] == '.') {
+ VLOG_ERR("%s:%d: %s name \"%.*s\" begins with invalid character '.'",
+ file_name, line_number, id, (int) len, key);
+ return false;
+ } else if (key[len - 1] == '.') {
+ VLOG_ERR("%s:%d: %s name \"%.*s\" ends with invalid character '.'",
+ file_name, line_number, id, (int) len, key);
+ return false;
+ } else if (has_double_dot(key, len)) {
+ VLOG_ERR("%s:%d: %s name \"%.*s\" contains '..', which is not allowed",
+ file_name, line_number, id, (int) len, key);
+ return false;
+ } else {
+ return true;
+ }
+}
+
+static char *
+parse_section(const char *file_name, int line_number, const char *s)
+{
+ struct ds section;
+ size_t len;
+
+ ds_init(§ion);
+
+ /* Skip [ and any white space. */
+ s++;
+ s += strspn(s, CC_SPACE);
+
+ /* Obtain the section name. */
+ len = strspn(s, CC_KEY);
+ if (!is_valid_key(s, len, file_name, line_number, "section")) {
+ goto error;
+ }
+ ds_put_buffer(§ion, s, len);
+ s += len;
+
+ /* Obtain the subsection name, if any. */
+ s += strspn(s, CC_SPACE);
+ if (*s == '"') {
+ s++;
+ len = strspn(s, CC_KEY);
+ if (!is_valid_key(s, len, file_name, line_number, "subsection")) {
+ goto error;
+ }
+ ds_put_char(§ion, '.');
+ ds_put_buffer(§ion, s, len);
+ s += len;
+ if (*s != '"') {
+ VLOG_ERR("%s:%d: missing '\"' following subsection name",
+ file_name, line_number);
+ goto error;
+ }
+ s++;
+ s += strspn(s, CC_SPACE);
+ }
+
+ /* Check for ]. */
+ if (*s != ']') {
+ VLOG_ERR("%s:%d: missing ']' following section name",
+ file_name, line_number);
+ goto error;
+ }
+ s++;
+ s += strspn(s, CC_SPACE);
+ if (*s != '\0') {
+ VLOG_ERR("%s:%d: trailing garbage following ']'",
+ file_name, line_number);
+ goto error;
+ }
+
+ return ds_cstr(§ion);
+
+error:
+ ds_destroy(§ion);
+ return NULL;
+}
+
+static void
+parse_setting(const char *file_name, int line_number, const char *section,
+ const char *s)
+{
+ struct ds key = DS_EMPTY_INITIALIZER;
+ struct ds value = DS_EMPTY_INITIALIZER;
+ size_t len;
+
+ if (section) {
+ ds_put_format(&key, "%s.", section);
+ }
+
+ /* Obtain the key. */
+ len = strspn(s, CC_KEY);
+ if (!len) {
+ VLOG_ERR("%s:%d: missing key name", file_name, line_number);
+ goto done;
+ }
+ if (!is_valid_key(s, len, file_name, line_number, "key")) {
+ goto done;
+ }
+ ds_put_buffer(&key, s, len);
+ s += len;
+
+ /* Skip the '='. */
+ s += strspn(s, CC_SPACE);
+ if (*s != '=') {
+ VLOG_ERR("%s:%d: missing '=' following key", file_name, line_number);
+ goto done;
+ }
+ s++;
+ s += strspn(s, CC_SPACE);
+
+ /* Obtain the value. */
+ ds_put_cstr(&value, s);
+ while (value.length > 0 && strchr(CC_SPACE, ds_last(&value))) {
+ value.length--;
+ }
+
+ /* Add the setting. */
+ svec_add_nocopy(&cfg, xasprintf("%s=%s", ds_cstr(&key), ds_cstr(&value)));
+
+done:
+ ds_destroy(&key);
+ 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)
+{
+ for (;;) {
+ int ac = *a == '=' ? '\0' : *a;
+ int bc = *b == '=' ? '\0' : *b;
+ if (ac != bc) {
+ return ac < bc ? -1 : 1;
+ } else if (!ac) {
+ return 0;
+ }
+ a++;
+ b++;
+ }
+}
+
+/* Returns the address of the greatest configuration string with a key less
+ * than or equal to 'key'. Returns the address of the null terminator if all
+ * configuration strings are greater than 'key'. */
+static char **
+find_key_le(const char *key)
+{
+ int low = 0;
+ int len = cfg.n;
+ while (len > 0) {
+ int half = len >> 1;
+ int middle = low + half;
+ if (compare_key(cfg.names[middle], key) < 0) {
+ low = middle + 1;
+ len -= half + 1;
+ } else {
+ len = half;
+ }
+ }
+ return &cfg.names[low];
+}
+
+/* Returns the address of the least configuration string with a key greater
+ * than or equal to 'key'. Returns the address of the null terminator if all
+ * configuration strings are less than 'key'. */
+static char **
+find_key_ge(const char *key)
+{
+ int low = 0;
+ int len = cfg.n;
+ while (len > 0) {
+ int half = len >> 1;
+ int middle = low + half;
+ if (compare_key(cfg.names[middle], key) > 0) {
+ len = half;
+ } else {
+ low = middle + 1;
+ len -= half + 1;
+ }
+ }
+ return &cfg.names[low];
+}
+
+static char *
+find_key(const char *key)
+{
+ char **p = find_key_le(key);
+ return p < &cfg.names[cfg.n] && !compare_key(*p, key) ? *p : NULL;
+}
+
+static bool
+parse_mac(const char *s, uint8_t mac[6])
+{
+ return sscanf(s, "%"SCNx8":%"SCNx8":%"SCNx8":%"SCNx8":%"SCNx8":%"SCNx8,
+ &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]) == 6;
+}
+
+static bool
+is_key(const char *s)
+{
+ /* XXX needs to check the same things as is_valid_key() too. */
+ return *s && s[strspn(s, CC_KEY)] == '\0';
+}
+
+static bool
+is_int(const char *s)
+{
+ return *s && s[strspn(s, CC_DIGIT)] == '\0';
+}
+
+static bool
+is_bool(const char *s)
+{
+ return !strcmp(s, "true") || !strcmp(s, "false");
+}
+
+static const char *
+extract_value(const char *key)
+{
+ const char *p = strchr(key, '=');
+ return p ? p + 1 : NULL;
+}
+
+static const char *
+get_nth_value(int idx, const char *key)
+{
+ char **p = find_key_le(key);
+ char **q = find_key_ge(key);
+ return idx < q - p ? extract_value(p[idx]) : NULL;
+}
+
+static bool
+is_type(const char *s, enum cfg_flags flags)
+{
+ uint8_t mac[ETH_ADDR_LEN];
+ struct in_addr addr;
+
+ return (flags & CFG_STRING
+ || (flags & CFG_KEY && is_key(s))
+ || (flags & CFG_INT && is_int(s))
+ || (flags & CFG_BOOL && is_bool(s))
+ || (flags & CFG_IP && inet_aton(s, &addr))
+ || (flags & CFG_MAC && parse_mac(s, mac)));
+}
--- /dev/null
+/* Copyright (c) 2008, 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 <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.
+ */
+
+
+#ifndef VSWITCHD_CFG_H
+#define VSWITCHD_CFG_H 1
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "compiler.h"
+
+struct svec;
+
+int cfg_add_file(const char *file_name);
+void cfg_read(void);
+
+void cfg_get_subsections(struct svec *, const char *, ...) PRINTF_FORMAT(2, 3);
+
+enum cfg_flags {
+ /* Types allowed. */
+ CFG_STRING = 1 << 0, /* Arbitrary content. */
+ CFG_KEY = 1 << 0, /* Valid key name. */
+ CFG_INT = 1 << 2, /* Integer value. */
+ CFG_BOOL = 1 << 3, /* Boolean. */
+ CFG_IP = 1 << 4, /* IPv4 address. */
+ CFG_MAC = 1 << 5, /* MAC address. */
+ CFG_VLAN = 1 << 6, /* Integer in range 0...4095. */
+
+ /* Number allowed. */
+ CFG_REQUIRED = 1 << 6, /* At least one value allowed. */
+ CFG_MULTIPLE = 1 << 7 /* More than one value allowed. */
+};
+void cfg_register(const char *key_spec, enum cfg_flags);
+
+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);
+int cfg_count(const char *key, ...) PRINTF_FORMAT(1, 2);
+
+const char *cfg_get_string(int idx, const char *key, ...) PRINTF_FORMAT(2, 3);
+const char *cfg_get_key(int idx, const char *key, ...) PRINTF_FORMAT(2, 3);
+int cfg_get_int(int idx, const char *key, ...) PRINTF_FORMAT(2, 3);
+bool cfg_get_bool(int idx, const char *key, ...) PRINTF_FORMAT(2, 3);
+uint32_t cfg_get_ip(int idx, const char *key, ...) PRINTF_FORMAT(2, 3);
+uint64_t cfg_get_mac(int idx, const char *key, ...) PRINTF_FORMAT(2, 3);
+int cfg_get_vlan(int idx, const char *key, ...) PRINTF_FORMAT(2, 3);
+
+void cfg_get_all_strings(struct svec *, const char *key, ...)
+ PRINTF_FORMAT(2, 3);
+void cfg_get_all_keys(struct svec *, const char *key, ...) PRINTF_FORMAT(2, 3);
+
+#endif /* vswitchd/conf.h */
executer_periodic_cb, /* periodic_cb */
executer_wait_cb, /* wait_cb */
executer_closing_cb, /* closing_cb */
+ NULL, /* reconfigure_cb */
};
void
fail_open_periodic_cb, /* periodic_cb */
fail_open_wait_cb, /* wait_cb */
NULL, /* closing_cb */
+ NULL, /* reconfigure_cb */
};
void
-/* Copyright (c) 2008 The Board of Trustees of The Leland Stanford
+/* Copyright (c) 2008, 2009 The Board of Trustees of The Leland Stanford
* Junior University
*
* We are making the OpenFlow specification and associated documentation
*/
#include <config.h>
-#include "flow-end.h"
#include <errno.h>
#include <arpa/inet.h>
#include <inttypes.h>
#include <unistd.h>
#include <sys/time.h>
#include <time.h>
+
#include "openflow/nicira-ext.h"
#include "openflow/openflow.h"
-#include "secchan.h"
+#include "cfg.h"
+#include "flow-end.h"
+#include "netflow.h"
#include "ofpbuf.h"
-#include "vconn.h"
#include "rconn.h"
+#include "secchan.h"
#include "socket-util.h"
+#include "svec.h"
+#include "vconn.h"
#include "xtoxll.h"
-#include "netflow.h"
#define THIS_MODULE VLM_flow_end
#include "vlog.h"
+
+#define MAX_COLLECTORS 8
+
struct flow_end_data {
+ const struct settings *s;
+
struct rconn *remote_rconn;
struct rconn *local_rconn;
- bool send_ofp_exp; /* Send OpenFlow 'flow expired' messages? */
+ bool send_ofp_exp; /* Send OpenFlow 'flow expired' messages? */
- int netflow_fd; /* Socket for NetFlow collector. */
- uint32_t netflow_cnt; /* Flow sequence number for NetFlow. */
+ int netflow_fds[MAX_COLLECTORS]; /* Sockets for NetFlow collectors. */
+ uint32_t netflow_cnt; /* Flow sequence number for NetFlow. */
};
static int
uint8_t buf[sizeof(*nf_hdr) + sizeof(*nf_rec)];
uint8_t *p = buf;
struct timeval now;
+ int i;
/* We only send NetFlow messages for fully specified IP flows; any
* entry with a wildcard is ignored. */
nf_rec->src_mask = 0;
nf_rec->dst_mask = 0;
- send(fe->netflow_fd, buf, sizeof(buf), 0);
+ for (i=0; i<MAX_COLLECTORS; i++) {
+ if (fe->netflow_fds[i] == -1) {
+ break;
+ }
+ send(fe->netflow_fds[i], buf, sizeof(buf), 0);
+ }
fe->netflow_cnt++;
}
nfec = make_openflow(sizeof(*nfec), OFPT_VENDOR, &b);
nfec->header.vendor = htonl(NX_VENDOR_ID);
nfec->header.subtype = htonl(NXT_FLOW_END_CONFIG);
- if ((fe->send_ofp_exp == false) && (fe->netflow_fd < 0)) {
+ if ((fe->send_ofp_exp == false) && (fe->netflow_fds[0] < 0)) {
nfec->enable = 0;
} else {
nfec->enable = 1;
return false;
}
- if (fe->netflow_fd >= 0) {
+ if (fe->netflow_fds[0] >= 0) {
send_netflow_msg(nfe, fe);
}
return false;
}
+static void
+flow_end_reconfigure_cb(void *flow_end_)
+{
+ int i, nf_idx=0;
+ struct flow_end_data *fe = flow_end_;
+ struct svec collectors;
+
+ /* Configure NetFlow collectors. */
+ for (i=0; i<MAX_COLLECTORS; i++) {
+ if (fe->netflow_fds[i] >= 0) {
+ close(fe->netflow_fds[i]);
+ fe->netflow_fds[i] = -1;
+ }
+ }
+
+ svec_init(&collectors);
+ cfg_get_all_keys(&collectors, "netflow.%s.host", fe->s->br_name);
+ svec_sort(&collectors);
+ if (!svec_is_unique(&collectors)) {
+ VLOG_WARN("%s specified twice as netflow collector",
+ svec_get_duplicate(&collectors));
+ svec_unique(&collectors);
+ }
+
+ for (i=0; i<collectors.n; i++) {
+ if (nf_idx >= MAX_COLLECTORS) {
+ VLOG_WARN("too many netflow collectors specified, ignoring %s\n",
+ collectors.names[i]);
+ continue;
+ }
+
+ fe->netflow_fds[nf_idx] = udp_open(collectors.names[i]);
+ if (fe->netflow_fds[nf_idx] < 0) {
+ VLOG_WARN("couldn't open connection to collector, ignoring %s\n",
+ collectors.names[i]);
+ } else {
+ nf_idx++;
+ }
+ }
+
+ if (nf_idx > 0) {
+ send_nx_flow_end_config(fe);
+ }
+}
+
static struct hook_class flow_end_hook_class = {
flow_end_local_packet_cb, /* local_packet_cb */
flow_end_remote_packet_cb, /* remote_packet_cb */
NULL, /* periodic_cb */
NULL, /* wait_cb */
NULL, /* closing_cb */
+ flow_end_reconfigure_cb, /* reconfigure_cb */
};
void
-flow_end_start(struct secchan *secchan, char *netflow_dst,
+flow_end_start(struct secchan *secchan, const struct settings *settings,
struct rconn *local, struct rconn *remote)
{
+ int i;
struct flow_end_data *fe;
fe = xcalloc(1, sizeof *fe);
+ fe->s = settings;
fe->remote_rconn = remote;
fe->local_rconn = local;
- if (netflow_dst) {
- fe->netflow_fd = udp_open(netflow_dst);
- if (fe->netflow_fd < 0) {
- ofp_fatal(0, "NetFlow setup failed");
- }
- fe->send_ofp_exp = true;
- } else {
- fe->netflow_fd = -1;
- fe->send_ofp_exp = false;
+ for (i=0; i<MAX_COLLECTORS; i++) {
+ fe->netflow_fds[i] = -1;
}
+ fe->send_ofp_exp = false;
add_hook(secchan, &flow_end_hook_class, fe);
-
- send_nx_flow_end_config(fe);
}
#ifndef FLOW_END_H
#define FLOW_END_H 1
+
struct secchan;
+struct settings;
struct rconn;
-void flow_end_start(struct secchan *, char *, struct rconn *, struct rconn *);
+void flow_end_start(struct secchan *, const struct settings *,
+ struct rconn *, struct rconn *);
#endif /* flow-end.h */
-/* Copyright (c) 2008 The Board of Trustees of The Leland Stanford
+/* Copyright (c) 2008, 2009 The Board of Trustees of The Leland Stanford
* Junior University
*
* We are making the OpenFlow specification and associated documentation
in_band_periodic_cb, /* periodic_cb */
in_band_wait_cb, /* wait_cb */
NULL, /* closing_cb */
+ NULL, /* reconfigure_cb */
};
void
port_watcher_periodic_cb, /* periodic_cb */
port_watcher_wait_cb, /* wait_cb */
NULL, /* closing_cb */
+ NULL, /* reconfigure_cb */
};
void
-/* Copyright (c) 2008 The Board of Trustees of The Leland Stanford
+/* Copyright (c) 2008, 2009 The Board of Trustees of The Leland Stanford
* Junior University
*
* We are making the OpenFlow specification and associated documentation
rate_limit_periodic_cb, /* periodic_cb */
rate_limit_wait_cb, /* wait_cb */
NULL, /* closing_cb */
+ NULL, /* reconfigure_cb */
};
void
.ds PN secchan
-.TH secchan 8 "October 2008" "OpenFlow" "OpenFlow Manual"
+.TH secchan 8 "February 2009" "OpenFlow" "OpenFlow Manual"
.SH NAME
secchan \- secure channel connecting an OpenFlow datapath to a controller
.RE
.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
+For a description of the configuration syntax, see \fBvswitchd.conf\fR(5).
+Currently, only the NetFlow section applies to \fBsecchan\fR.
+
+.TP
+\fB--br-name=\fIname\fR
+When processing the configuration files specified with the \fB--config\fR
+option, use \fIname\fR as the bridge identifier to look for applicable
+lines.
+
.SS "Controller Discovery Options"
.TP
\fB--accept-vconn=\fIregex\fR
.SS "Networking Options"
.TP
-\fB-F\fR, \fB--fail=\fR[\fBopen\fR|\fBclosed\fR]
+\fB--fail=\fR[\fBopen\fR|\fBclosed\fR]
The controller is, ordinarily, responsible for setting up all flows on
the OpenFlow switch. Thus, if the connection to the controller fails,
no new network connections can be set up. If the connection to the
because bugs in the STP implementation are still being worked out.
The default will change to \fB--stp\fR at some point in the future.
-.TP
-\fB--netflow=\fIhost\fB:\fIport\fR
-When flows end on the switch, send NetFlow v5 messages to
-\fIhost\fR on UDP \fIport\fR.
-
.SS "Rate-Limiting Options"
These options configure how the switch applies a ``token bucket'' to
.BR controller (8),
.BR ofp-pki (8),
.BR udatapath (8),
-.BR vlogconf (8)
+.BR vlogconf (8),
+.BR vswitchd.conf (5),
#include <signal.h>
#include <string.h>
+#include "cfg.h"
#include "command-line.h"
#include "compiler.h"
#include "daemon.h"
#include "poll-loop.h"
#include "ratelimit.h"
#include "rconn.h"
+#include "signals.h"
#ifdef SUPPORT_SNAT
#include "snat.h"
#endif
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(60, 60);
+static void reconfigure(struct secchan *);
static void parse_options(int argc, char *argv[], struct settings *);
static void usage(void) NO_RETURN;
struct pvconn *listeners[MAX_MGMT];
size_t n_listeners;
+ struct signal *sighup;
char *local_rconn_name;
struct rconn *async_rconn, *local_rconn, *remote_rconn;
struct relay *controller_relay;
vlog_init();
parse_options(argc, argv, &s);
signal(SIGPIPE, SIG_IGN);
+ sighup = signal_register(SIGHUP);
secchan.hooks = NULL;
secchan.n_hooks = 0;
#ifdef SUPPORT_SNAT
snat_start(&secchan, pw);
#endif
- flow_end_start(&secchan, s.netflow_dst, local_rconn, remote_rconn);
+ flow_end_start(&secchan, &s, local_rconn, remote_rconn);
if (s.enable_stp) {
stp_start(&secchan, pw, local_rconn, remote_rconn);
}
executer_start(&secchan, &s);
}
+ reconfigure(&secchan);
+
while (s.discovery || rconn_is_alive(remote_rconn)) {
struct relay *r, *n;
size_t i;
+ if (signal_poll(sighup)) {
+ reconfigure(&secchan);
+ }
+
/* Do work. */
LIST_FOR_EACH_SAFE (r, n, struct relay, node, &relays) {
relay_run(r, &secchan);
if (discovery) {
discovery_wait(discovery);
}
+ signal_wait(sighup);
poll_block();
}
return 0;
}
+static void
+reconfigure(struct secchan *secchan)
+{
+ int i;
+
+ cfg_read();
+ for (i = 0; i < secchan->n_hooks; i++) {
+ if (secchan->hooks[i].class->reconfigure_cb) {
+ secchan->hooks[i].class->reconfigure_cb(secchan->hooks[i].aux);
+ }
+ }
+}
+
static struct pvconn *
open_passive_vconn(const char *name)
{
enum {
OPT_ACCEPT_VCONN = UCHAR_MAX + 1,
OPT_NO_RESOLV_CONF,
+ OPT_BR_NAME,
+ OPT_FAIL_MODE,
OPT_INACTIVITY_PROBE,
OPT_MAX_IDLE,
OPT_MAX_BACKOFF,
OPT_IN_BAND,
OPT_COMMAND_ACL,
OPT_COMMAND_DIR,
- OPT_NETFLOW,
VLOG_OPTION_ENUMS,
LEAK_CHECKER_OPTION_ENUMS
};
static struct option long_options[] = {
{"accept-vconn", required_argument, 0, OPT_ACCEPT_VCONN},
{"no-resolv-conf", no_argument, 0, OPT_NO_RESOLV_CONF},
- {"fail", required_argument, 0, 'F'},
+ {"config", required_argument, 0, 'F'},
+ {"br-name", required_argument, 0, OPT_BR_NAME},
+ {"fail", required_argument, 0, OPT_FAIL_MODE},
{"inactivity-probe", required_argument, 0, OPT_INACTIVITY_PROBE},
{"max-idle", required_argument, 0, OPT_MAX_IDLE},
{"max-backoff", required_argument, 0, OPT_MAX_BACKOFF},
{"in-band", no_argument, 0, OPT_IN_BAND},
{"command-acl", required_argument, 0, OPT_COMMAND_ACL},
{"command-dir", required_argument, 0, OPT_COMMAND_DIR},
- {"netflow", required_argument, 0, OPT_NETFLOW},
{"verbose", optional_argument, 0, 'v'},
{"help", no_argument, 0, 'h'},
{"version", no_argument, 0, 'V'},
s->in_band = true;
s->command_acl = "";
s->command_dir = xasprintf("%s/commands", ofp_pkgdatadir);
- s->netflow_dst = NULL;
for (;;) {
+ int error;
int c;
c = getopt_long(argc, argv, short_options, long_options, NULL);
accept_re = optarg[0] == '^' ? optarg : xasprintf("^%s", optarg);
break;
+ case OPT_BR_NAME:
+ s->br_name = optarg;
+ break;
+
case OPT_NO_RESOLV_CONF:
s->update_resolv_conf = false;
break;
- case 'F':
+ case OPT_FAIL_MODE:
if (!strcmp(optarg, "open")) {
s->fail_mode = FAIL_OPEN;
} else if (!strcmp(optarg, "closed")) {
s->command_dir = optarg;
break;
- case OPT_NETFLOW:
- if (s->netflow_dst) {
- ofp_fatal(0, "--netflow may only be specified once");
+ case 'F':
+ error = cfg_add_file(optarg);
+ if (error) {
+ ofp_fatal(error, "failed to add configuration file or "
+ "directory \"%s\"", optarg);
}
- s->netflow_dst = optarg;
break;
case 'l':
"omitted, then secchan performs controller discovery.\n",
program_name, program_name);
vconn_usage(true, true, true);
- printf("\nController discovery options:\n"
+ printf("\nConfiguration options:\n"
+ " -F, --config=FILE|DIR reads configuration from FILE or DIR\n"
+ " --br-name=NAME bridge name to use for configuration\n"
+ "\nController discovery options:\n"
" --accept-vconn=REGEX accept matching discovered controllers\n"
" --no-resolv-conf do not update /etc/resolv.conf\n"
"\nNetworking options:\n"
- " -F, --fail=open|closed when controller connection fails:\n"
+ " --fail=open|closed when controller connection fails:\n"
" closed: drop all packets\n"
" open (default): act as learning switch\n"
" --inactivity-probe=SECS time between inactivity probes\n"
" --out-of-band controller connection is out-of-band\n"
" --stp enable 802.1D Spanning Tree Protocol\n"
" --no-stp disable 802.1D Spanning Tree Protocol\n"
- " --netflow=HOST:PORT send NetFlow v5 messages when flows end\n"
"\nRate-limiting of \"packet-in\" messages to the controller:\n"
" --rate-limit[=PACKETS] max rate, in packets/s (default: 1000)\n"
" --burst-limit=BURST limit on packet credit for idle time\n"
-/* Copyright (c) 2008 The Board of Trustees of The Leland Stanford
+/* Copyright (c) 2008, 2009 The Board of Trustees of The Leland Stanford
* Junior University
*
* We are making the OpenFlow specification and associated documentation
/* Settings that may be configured by the user. */
struct settings {
+ /* Configuration. */
+ const char *br_name; /* Bridge name to use for configuration lookup */
+
/* Overall mode of operation. */
bool discovery; /* Discover the controller automatically? */
bool in_band; /* Connect to controller in-band? */
/* Remote command execution. */
char *command_acl; /* Command white/blacklist, as shell globs. */
char *command_dir; /* Directory that contains commands. */
-
- /* NetFlow logging. */
- char *netflow_dst; /* Host and port to send NetFlow traffic. */
};
struct half {
void (*periodic_cb)(void *aux);
void (*wait_cb)(void *aux);
void (*closing_cb)(struct relay *, void *aux);
+ void (*reconfigure_cb)(void *aux);
};
void add_hook(struct secchan *, const struct hook_class *, void *);
NULL, /* periodic_cb */
NULL, /* wait_cb */
NULL, /* closing_cb */
+ NULL, /* reconfigure_cb */
};
void
-/* Copyright (c) 2008 The Board of Trustees of The Leland Stanford
+/* Copyright (c) 2008, 2009 The Board of Trustees of The Leland Stanford
* Junior University
*
* We are making the OpenFlow specification and associated documentation
NULL, /* periodic_cb */
NULL, /* wait_cb */
NULL, /* closing_cb */
+ NULL, /* reconfigure_cb */
};
void
stp_periodic_cb, /* periodic_cb */
stp_wait_cb, /* wait_cb */
NULL, /* closing_cb */
+ NULL, /* reconfigure_cb */
};
void
vswitchd/brcompat.h \
vswitchd/bridge.c \
vswitchd/bridge.h \
- vswitchd/cfg.c \
- vswitchd/cfg.h \
vswitchd/flowtrack.c \
vswitchd/flowtrack.h \
vswitchd/stats.c \
#define THIS_MODULE VLM_bridge
#include "vlog.h"
+extern struct svec config_files;
+
struct iface {
struct port *port; /* Containing port. */
size_t port_ifidx; /* Index within containing port. */
bool sent_config_request; /* Successfully sent config request? */
bool sent_features_request; /* Successfully sent features request? */
- /* Support for NetFlow. */
- char *netflow_host; /* NULL if no NetFlow logging; otherwise a
- * "host:port" string. */
-
/* Support for remote controllers. */
char *controller; /* NULL if there is no remote controller;
* "discover" to do controller discovery;
free(status);
}
+static void
+hup_secchan(struct bridge *br)
+{
+ if (br->sc_state == SC_RUNNING) {
+ process_kill(br->secchan, SIGHUP);
+ }
+}
+
static void
kill_secchan(struct bridge *br)
{
static void
start_secchan(struct bridge *br)
{
+ int i;
struct svec argv;
int sockets[2];
struct stat s;
/* Assemble command-line arguments. */
svec_init(&argv);
svec_add(&argv, "secchan");
+ svec_add_nocopy(&argv, xasprintf("--br-name=%s", br->name));
+ for (i=0; i<config_files.n; i++) {
+ svec_add_nocopy(&argv, xasprintf("--config=%s",
+ config_files.names[i]));
+ }
svec_add_nocopy(&argv, xasprintf("-vPATTERN:console:%s|secchan-%s|%s",
"%d{%b %d %H:%M:%S}", br->name,
"%c|%p|%m"));
svec_add_nocopy(&argv, xasprintf("--log-file=%s.secchan-%s",
vlog_get_log_file(), br->name));
}
- if (br->netflow_host) {
- svec_add_nocopy(&argv, xasprintf("--netflow=%s", br->netflow_host));
- }
if (!br->controller) {
svec_add(&argv, "--out-of-band");
svec_add(&argv, "--max-backoff=1");
}
process_destroy(br->secchan);
rconn_destroy(br->rconn);
- free(br->netflow_host);
free(br->controller);
svec_destroy(&br->secchan_opts);
ft_destroy(br->ft);
{
struct svec old_ports, new_ports, ifaces;
const char *controller;
- const char *netflow_host;
size_t i, j;
char *ctl;
- char *nf;
/* Collect old and new ports. */
svec_init(&old_ports);
free(br->controller);
br->controller = ctl;
- /* Configure NetFlow. */
- netflow_host = cfg_get_string(0, "netflow.%s.host", br->name);
- nf = netflow_host ? xstrdup(netflow_host) : NULL;
- if ((nf == NULL) != (br->netflow_host == NULL)
- || (nf && br->netflow_host && strcmp(nf, br->netflow_host))) {
- br->sc_retries = 0;
- kill_secchan(br);
- }
- free(br->netflow_host);
- br->netflow_host = nf;
-
/* Allow arbitrary secchan options if a remote controller is configured. */
svec_clear(&br->secchan_opts);
if (ctl) {
}
mirror_reconfigure(br);
+
+ /* Force secchan to reconfigure itself. */
+ hup_secchan(br);
}
static void
+++ /dev/null
-/* Copyright (c) 2008, 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 <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 "cfg.h"
-#include <arpa/inet.h>
-#include <dirent.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <inttypes.h>
-#include <netinet/in.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include "dynamic-string.h"
-#include "packets.h"
-#include "svec.h"
-#include "util.h"
-
-#define THIS_MODULE VLM_cfg
-#include "vlog.h"
-
-/* List of configuration files. */
-static struct svec cfg_files = SVEC_EMPTY_INITIALIZER;
-
-/* 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,
- const char *id);
-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);
-static char *find_key(const char *);
-static bool parse_mac(const char *, uint8_t mac[6]);
-static bool is_key(const char *);
-static bool is_int(const char *);
-static bool is_bool(const char *);
-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);
-
-/* 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.
- *
- * If 'file_name' names a file, then cfg_read() will read it. If 'file_name'
- * names a directory, then cfg_read() will read all of the files in that
- * directory whose names consist entirely of the English letters, digits,
- * periods, underscores, and hyphens and do not begin with a period.
- * Subdirectories are not processed recursively.
- *
- * 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)
-{
- int fd;
-
- /* Make sure that we can open this file or directory for reading. */
- fd = open(file_name, O_RDONLY);
- if (fd < 0) {
- return errno;
- }
- close(fd);
-
- /* Add it to the list. */
- VLOG_INFO("using \"%s\" as a configuration file", file_name);
- svec_add(&cfg_files, file_name);
- return 0;
-}
-
-/* Reads all of the configuration files or directories that have been added
- * with cfg_add_file(), merges their content. Any previous configuration is
- * replaced. */
-void
-cfg_read(void)
-{
- size_t i;
-
- /* 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);
- } else {
- VLOG_WARN("\"%s\" is not a regular file or a directory, ignoring",
- fn);
- }
- }
-
- if (VLOG_IS_DBG_ENABLED()) {
- size_t i;
-
- VLOG_DBG("new configuration:");
- for (i = 0; i < cfg.n; i++) {
- VLOG_DBG("%s", cfg.names[i]);
- }
- }
-}
-
-/* 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'. */
-#define FORMAT_KEY(FORMAT, DST) \
- do { \
- va_list args__; \
- va_start(args__, FORMAT); \
- (DST) = xvasprintf(FORMAT, args__); \
- va_end(args__); \
- } while (0)
-
-/* Returns true if the configuration includes a key named 'key'. */
-bool
-cfg_has(const char *key_, ...)
-{
- char *key;
- bool retval;
-
- FORMAT_KEY(key_, key);
- retval = find_key(key) != NULL;
- free(key);
- return retval;
-}
-
-bool
-cfg_is_valid(enum cfg_flags flags, const char *key_, ...)
-{
- char *key, **first, **last, **p;
- size_t n;
- bool retval;
-
- FORMAT_KEY(key_, key);
- first = find_key_le(key);
- last = find_key_ge(key);
- n = last - first;
- retval = ((!(flags & CFG_REQUIRED) || n)
- && (!(flags & CFG_MULTIPLE) || n <= 1));
- for (p = first; retval && p < last; p++) {
- retval = is_type(strchr(*p, '=') + 1, flags);
- }
- free(key);
- return retval;
-}
-
-/* Returns true if the configuration includes at least one key whose name
- * begins with 'section' followed by a dot. */
-bool
-cfg_has_section(const char *section_, ...)
-{
- struct ds section;
- bool retval;
- 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);
-
- p = find_key_le(ds_cstr(§ion));
- retval = *p && !strncmp(section.string, *p, section.length);
-
- ds_destroy(§ion);
- return retval;
-}
-
-/* Returns the number of values for the given 'key'. The return value is 0 if
- * no values exist for 'key'. */
-int
-cfg_count(const char *key_, ...)
-{
- char *key;
- int retval;
-
- FORMAT_KEY(key_, key);
- retval = find_key_ge(key) - find_key_le(key);
- free(key);
- return retval;
-}
-
-/* Fills 'svec' with all of the immediate subsections of 'section'. For
- * example, if 'section' is "bridge" and keys bridge.a, bridge.b, bridge.b.c,
- * and bridge.c.x.y.z exist, then 'svec' would be initialized to a, b, and
- * c. The caller must first initialize 'svec'. */
-void
-cfg_get_subsections(struct svec *svec, 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);
-
- svec_clear(svec);
- for (p = find_key_le(ds_cstr(§ion));
- *p && !strncmp(section.string, *p, section.length);
- p++) {
- const char *ss = *p + section.length;
- size_t ss_len = strcspn(ss, ".=");
- svec_add_nocopy(svec, xmemdup0(ss, ss_len));
- }
- svec_unique(svec);
- ds_destroy(§ion);
-}
-
-/* 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
- * cfg_read(). */
-const char *
-cfg_get_string(int idx, const char *key_, ...)
-{
- const char *retval;
- char *key;
-
- FORMAT_KEY(key_, key);
- retval = get_nth_value(idx, key);
- free(key);
- return retval;
-}
-
-/* Returns the value numbered 'idx' of 'key'. Returns a null pointer if 'idx'
- * is greater than or equal to cfg_count(key) or if the value 'idx' of 'key' is
- * not a valid key. The caller must not modify or free the returned string or
- * retain its value beyond the next call to cfg_read(). */
-const char *
-cfg_get_key(int idx, const char *key_, ...)
-{
- const char *value, *retval;
- char *key;
-
- FORMAT_KEY(key_, key);
- value = get_nth_value(idx, key);
- retval = value && is_key(value) ? value : NULL;
- free(key);
- return retval;
-}
-
-/* Returns the value numbered 'idx' of 'key', converted to an integer. Returns
- * 0 if 'idx' is greater than or equal to cfg_count(key) or if the value 'idx'
- * of 'key' is not a valid integer. */
-int
-cfg_get_int(int idx, const char *key_, ...)
-{
- const char *value;
- int retval;
- char *key;
-
- FORMAT_KEY(key_, key);
- value = get_nth_value(idx, key);
- retval = value && is_int(value) ? atoi(value) : 0;
- free(key);
- return retval;
-}
-
-/* Returns the value numbered 'idx' of 'key', converted to a boolean value.
- * Returns 0 if 'idx' is greater than or equal to cfg_count(key) or if the
- * value 'idx' of 'key' is not a valid boolean. */
-bool
-cfg_get_bool(int idx, const char *key_, ...)
-{
- const char *value;
- bool retval;
- char *key;
-
- FORMAT_KEY(key_, key);
- value = get_nth_value(idx, key);
- retval = value && is_bool(value) ? !strcmp(value, "true") : false;
- free(key);
- return retval;
-}
-
-/* Returns the value numbered 'idx' of 'key', converted to an IP address in
- * network byte order. Returns 0 if 'idx' is greater than or equal to
- * cfg_count(key) or if the value 'idx' of 'key' is not a valid IP address (as
- * determined by inet_aton()). */
-uint32_t
-cfg_get_ip(int idx, const char *key_, ...)
-{
- struct in_addr addr;
- const char *value;
- char *key;
-
- FORMAT_KEY(key_, key);
- value = get_nth_value(idx, key);
- if (!value || !inet_aton(value, &addr)) {
- addr.s_addr = htonl(0);
- }
- free(key);
- return addr.s_addr;
-}
-
-/* Returns the value numbered 'idx' of 'key', converted to an MAC address in
- * host byte order. Returns 0 if 'idx' is greater than or equal to
- * cfg_count(key) or if the value 'idx' of 'key' is not a valid MAC address in
- * the format "##:##:##:##:##:##". */
-uint64_t
-cfg_get_mac(int idx, const char *key_, ...)
-{
- uint8_t mac[ETH_ADDR_LEN];
- const char *value;
- char *key;
-
- FORMAT_KEY(key_, key);
- value = get_nth_value(idx, key);
- if (!value || !parse_mac(value, mac)) {
- memset(mac, 0, sizeof mac);
- }
- free(key);
- return eth_addr_to_uint64(mac);
-}
-
-/* Returns the value numbered 'idx' of 'key', converted to an integer. Returns
- * -1 if 'idx' is greater than or equal to cfg_count(key) or if the value 'idx'
- * of 'key' is not a valid integer between 0 and 4095. */
-int
-cfg_get_vlan(int idx, const char *key_, ...)
-{
- const char *value;
- int retval;
- char *key;
-
- FORMAT_KEY(key_, key);
- value = get_nth_value(idx, key);
- if (value && is_int(value)) {
- retval = atoi(value);
- if (retval < 0 || retval > 4095) {
- retval = -1;
- }
- } else {
- retval = -1;
- }
- free(key);
- return retval;
-}
-
-/* Fills 'svec' with all of the string values of 'key'. The caller must
- * first initialize 'svec'. */
-void
-cfg_get_all_strings(struct svec *svec, const char *key_, ...)
-{
- char **p, **q;
- char *key;
-
- FORMAT_KEY(key_, key);
- svec_clear(svec);
- for (p = find_key_le(key), q = find_key_ge(key); p < q; p++) {
- svec_add(svec, extract_value(*p));
- }
- free(key);
-}
-
-/* Fills 'svec' with all of the values of 'key' that are valid keys.
- * Values of 'key' that are not valid keys are omitted. The caller
- * must first initialize 'svec'. */
-void
-cfg_get_all_keys(struct svec *svec, const char *key_, ...)
-{
- char **p, **q;
- char *key;
-
- FORMAT_KEY(key_, key);
- svec_clear(svec);
- for (p = find_key_le(key), q = find_key_ge(key); p < q; p++) {
- const char *value = extract_value(*p);
- if (is_key(value)) {
- svec_add(svec, value);
- }
- }
- free(key);
-}
-\f
-#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)
-{
- if (len >= 2) {
- size_t i;
-
- for (i = 0; i < len - 1; i++) {
- if (key[i] == '.' && key[i + 1] == '.') {
- return true;
- }
- }
- }
- return false;
-}
-
-static bool
-is_valid_key(const char *key, size_t len,
- const char *file_name, int line_number, const char *id)
-{
- if (!len) {
- VLOG_ERR("%s:%d: missing %s name", file_name, line_number, id);
- return false;
- } else if (key[0] == '.') {
- VLOG_ERR("%s:%d: %s name \"%.*s\" begins with invalid character '.'",
- file_name, line_number, id, (int) len, key);
- return false;
- } else if (key[len - 1] == '.') {
- VLOG_ERR("%s:%d: %s name \"%.*s\" ends with invalid character '.'",
- file_name, line_number, id, (int) len, key);
- return false;
- } else if (has_double_dot(key, len)) {
- VLOG_ERR("%s:%d: %s name \"%.*s\" contains '..', which is not allowed",
- file_name, line_number, id, (int) len, key);
- return false;
- } else {
- return true;
- }
-}
-
-static char *
-parse_section(const char *file_name, int line_number, const char *s)
-{
- struct ds section;
- size_t len;
-
- ds_init(§ion);
-
- /* Skip [ and any white space. */
- s++;
- s += strspn(s, CC_SPACE);
-
- /* Obtain the section name. */
- len = strspn(s, CC_KEY);
- if (!is_valid_key(s, len, file_name, line_number, "section")) {
- goto error;
- }
- ds_put_buffer(§ion, s, len);
- s += len;
-
- /* Obtain the subsection name, if any. */
- s += strspn(s, CC_SPACE);
- if (*s == '"') {
- s++;
- len = strspn(s, CC_KEY);
- if (!is_valid_key(s, len, file_name, line_number, "subsection")) {
- goto error;
- }
- ds_put_char(§ion, '.');
- ds_put_buffer(§ion, s, len);
- s += len;
- if (*s != '"') {
- VLOG_ERR("%s:%d: missing '\"' following subsection name",
- file_name, line_number);
- goto error;
- }
- s++;
- s += strspn(s, CC_SPACE);
- }
-
- /* Check for ]. */
- if (*s != ']') {
- VLOG_ERR("%s:%d: missing ']' following section name",
- file_name, line_number);
- goto error;
- }
- s++;
- s += strspn(s, CC_SPACE);
- if (*s != '\0') {
- VLOG_ERR("%s:%d: trailing garbage following ']'",
- file_name, line_number);
- goto error;
- }
-
- return ds_cstr(§ion);
-
-error:
- ds_destroy(§ion);
- return NULL;
-}
-
-static void
-parse_setting(const char *file_name, int line_number, const char *section,
- const char *s)
-{
- struct ds key = DS_EMPTY_INITIALIZER;
- struct ds value = DS_EMPTY_INITIALIZER;
- size_t len;
-
- if (section) {
- ds_put_format(&key, "%s.", section);
- }
-
- /* Obtain the key. */
- len = strspn(s, CC_KEY);
- if (!len) {
- VLOG_ERR("%s:%d: missing key name", file_name, line_number);
- goto done;
- }
- if (!is_valid_key(s, len, file_name, line_number, "key")) {
- goto done;
- }
- ds_put_buffer(&key, s, len);
- s += len;
-
- /* Skip the '='. */
- s += strspn(s, CC_SPACE);
- if (*s != '=') {
- VLOG_ERR("%s:%d: missing '=' following key", file_name, line_number);
- goto done;
- }
- s++;
- s += strspn(s, CC_SPACE);
-
- /* Obtain the value. */
- ds_put_cstr(&value, s);
- while (value.length > 0 && strchr(CC_SPACE, ds_last(&value))) {
- value.length--;
- }
-
- /* Add the setting. */
- svec_add_nocopy(&cfg, xasprintf("%s=%s", ds_cstr(&key), ds_cstr(&value)));
-
-done:
- ds_destroy(&key);
- 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)
-{
- for (;;) {
- int ac = *a == '=' ? '\0' : *a;
- int bc = *b == '=' ? '\0' : *b;
- if (ac != bc) {
- return ac < bc ? -1 : 1;
- } else if (!ac) {
- return 0;
- }
- a++;
- b++;
- }
-}
-
-/* Returns the address of the greatest configuration string with a key less
- * than or equal to 'key'. Returns the address of the null terminator if all
- * configuration strings are greater than 'key'. */
-static char **
-find_key_le(const char *key)
-{
- int low = 0;
- int len = cfg.n;
- while (len > 0) {
- int half = len >> 1;
- int middle = low + half;
- if (compare_key(cfg.names[middle], key) < 0) {
- low = middle + 1;
- len -= half + 1;
- } else {
- len = half;
- }
- }
- return &cfg.names[low];
-}
-
-/* Returns the address of the least configuration string with a key greater
- * than or equal to 'key'. Returns the address of the null terminator if all
- * configuration strings are less than 'key'. */
-static char **
-find_key_ge(const char *key)
-{
- int low = 0;
- int len = cfg.n;
- while (len > 0) {
- int half = len >> 1;
- int middle = low + half;
- if (compare_key(cfg.names[middle], key) > 0) {
- len = half;
- } else {
- low = middle + 1;
- len -= half + 1;
- }
- }
- return &cfg.names[low];
-}
-
-static char *
-find_key(const char *key)
-{
- char **p = find_key_le(key);
- return p < &cfg.names[cfg.n] && !compare_key(*p, key) ? *p : NULL;
-}
-
-static bool
-parse_mac(const char *s, uint8_t mac[6])
-{
- return sscanf(s, "%"SCNx8":%"SCNx8":%"SCNx8":%"SCNx8":%"SCNx8":%"SCNx8,
- &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]) == 6;
-}
-
-static bool
-is_key(const char *s)
-{
- /* XXX needs to check the same things as is_valid_key() too. */
- return *s && s[strspn(s, CC_KEY)] == '\0';
-}
-
-static bool
-is_int(const char *s)
-{
- return *s && s[strspn(s, CC_DIGIT)] == '\0';
-}
-
-static bool
-is_bool(const char *s)
-{
- return !strcmp(s, "true") || !strcmp(s, "false");
-}
-
-static const char *
-extract_value(const char *key)
-{
- const char *p = strchr(key, '=');
- return p ? p + 1 : NULL;
-}
-
-static const char *
-get_nth_value(int idx, const char *key)
-{
- char **p = find_key_le(key);
- char **q = find_key_ge(key);
- return idx < q - p ? extract_value(p[idx]) : NULL;
-}
-
-static bool
-is_type(const char *s, enum cfg_flags flags)
-{
- uint8_t mac[ETH_ADDR_LEN];
- struct in_addr addr;
-
- return (flags & CFG_STRING
- || (flags & CFG_KEY && is_key(s))
- || (flags & CFG_INT && is_int(s))
- || (flags & CFG_BOOL && is_bool(s))
- || (flags & CFG_IP && inet_aton(s, &addr))
- || (flags & CFG_MAC && parse_mac(s, mac)));
-}
+++ /dev/null
-/* Copyright (c) 2008, 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 <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.
- */
-
-
-#ifndef VSWITCHD_CFG_H
-#define VSWITCHD_CFG_H 1
-
-#include <stdbool.h>
-#include <stdint.h>
-#include "compiler.h"
-
-struct svec;
-
-int cfg_add_file(const char *file_name);
-void cfg_read(void);
-
-void cfg_get_subsections(struct svec *, const char *, ...) PRINTF_FORMAT(2, 3);
-
-enum cfg_flags {
- /* Types allowed. */
- CFG_STRING = 1 << 0, /* Arbitrary content. */
- CFG_KEY = 1 << 0, /* Valid key name. */
- CFG_INT = 1 << 2, /* Integer value. */
- CFG_BOOL = 1 << 3, /* Boolean. */
- CFG_IP = 1 << 4, /* IPv4 address. */
- CFG_MAC = 1 << 5, /* MAC address. */
- CFG_VLAN = 1 << 6, /* Integer in range 0...4095. */
-
- /* Number allowed. */
- CFG_REQUIRED = 1 << 6, /* At least one value allowed. */
- CFG_MULTIPLE = 1 << 7 /* More than one value allowed. */
-};
-void cfg_register(const char *key_spec, enum cfg_flags);
-
-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);
-int cfg_count(const char *key, ...) PRINTF_FORMAT(1, 2);
-
-const char *cfg_get_string(int idx, const char *key, ...) PRINTF_FORMAT(2, 3);
-const char *cfg_get_key(int idx, const char *key, ...) PRINTF_FORMAT(2, 3);
-int cfg_get_int(int idx, const char *key, ...) PRINTF_FORMAT(2, 3);
-bool cfg_get_bool(int idx, const char *key, ...) PRINTF_FORMAT(2, 3);
-uint32_t cfg_get_ip(int idx, const char *key, ...) PRINTF_FORMAT(2, 3);
-uint64_t cfg_get_mac(int idx, const char *key, ...) PRINTF_FORMAT(2, 3);
-int cfg_get_vlan(int idx, const char *key, ...) PRINTF_FORMAT(2, 3);
-
-void cfg_get_all_strings(struct svec *, const char *key, ...)
- PRINTF_FORMAT(2, 3);
-void cfg_get_all_keys(struct svec *, const char *key, ...) PRINTF_FORMAT(2, 3);
-
-#endif /* vswitchd/conf.h */
#include "poll-loop.h"
#include "process.h"
#include "signals.h"
+#include "svec.h"
#include "timeval.h"
#include "util.h"
#include "vconn-ssl.h"
static void reconfigure(void);
static bool brc_enabled = false;
+struct svec config_files;
int
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);
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 "
. ns
. TP \$1
..
-.TH vswitchd.conf 5 "December 2008" "OpenFlow" "OpenFlow Manual"
+.TH vswitchd.conf 5 "February 2009" "OpenFlow" "OpenFlow Manual"
.
.SH NAME
-vswitchd.conf \- configuration file for \fBvswitchd.conf\fR
+vswitchd.conf \- configuration file for \fBvswitchd\fR
.
.SH DESCRIPTION
\fBvswitchd\fR(8), the virtual switch daemon, is configured using one
.SS "NetFlow v5 Flow Logging"
NetFlow provides a number of details about terminating flows, such as the
principals involved and duration. A bridge may be configured to send
-NetFlow v5 records to a collector when flows end. To enable, define the
-key \fBnetflow.\fIbridge\fB.host\fR to a NetFlow collector in the form
-\fIhost\fB:\fIport\fR. Records from \fIbridge\fR will be sent to
-\fIhost\fR on UDP \fIport\fR.
+NetFlow v5 records to up to eight collectors when flows end. To enable,
+define the key \fBnetflow.\fIbridge\fB.host\fR for each NetFlow collector
+in the form \fIhost\fB:\fIport\fR. Records from \fIbridge\fR will be sent
+to each \fIhost\fR on UDP \fIport\fR.
.PP
The following syntax sends NetFlow records for \fBmybr\fR to the NetFlow
collector \fBnflow.example.com\fR on UDP port \fB9995\fR: