Support multiple NetFlow collectors.
authorJustin Pettit <jpettit@nicira.com>
Fri, 13 Feb 2009 18:36:44 +0000 (10:36 -0800)
committerJustin Pettit <jpettit@nicira.com>
Fri, 13 Feb 2009 18:36:44 +0000 (10:36 -0800)
Add support for sending NetFlow messages to up to eight different
collectors.  With these changes, secchan now reads configuration files
using the same syntax as vswitchd.  This address Redmine feature #901.

22 files changed:
lib/automake.mk
lib/cfg.c [new file with mode: 0644]
lib/cfg.h [new file with mode: 0644]
secchan/executer.c
secchan/fail-open.c
secchan/flow-end.c
secchan/flow-end.h
secchan/in-band.c
secchan/port-watcher.c
secchan/ratelimit.c
secchan/secchan.8.in
secchan/secchan.c
secchan/secchan.h
secchan/snat.c
secchan/status.c
secchan/stp-secchan.c
vswitchd/automake.mk
vswitchd/bridge.c
vswitchd/cfg.c [deleted file]
vswitchd/cfg.h [deleted file]
vswitchd/vswitchd.c
vswitchd/vswitchd.conf.5

index d1cccd9eada7752ad9c5605937b650ca9a067d97..f629a1f1d42d33d4b1d88ae84df1f9d7753daf7a 100644 (file)
@@ -5,6 +5,8 @@ lib_libopenflow_a_SOURCES = \
        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 \
diff --git a/lib/cfg.c b/lib/cfg.c
new file mode 100644 (file)
index 0000000..8aa420d
--- /dev/null
+++ b/lib/cfg.c
@@ -0,0 +1,779 @@
+/* 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(&section);
+    va_start(args, section_);
+    ds_put_format_valist(&section, section_, args);
+    ds_put_char(&section, '.');
+    va_end(args);
+
+    p = find_key_le(ds_cstr(&section));
+    retval = *p && !strncmp(section.string, *p, section.length);
+
+    ds_destroy(&section);
+    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(&section);
+    va_start(args, section_);
+    ds_put_format_valist(&section, section_, args);
+    ds_put_char(&section, '.');
+    va_end(args);
+
+    svec_clear(svec);
+    for (p = find_key_le(ds_cstr(&section));
+         *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(&section);
+}
+
+/* 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(&section);
+
+    /* 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(&section, 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(&section, '.');
+        ds_put_buffer(&section, 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(&section);
+
+error:
+    ds_destroy(&section);
+    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)));
+}
diff --git a/lib/cfg.h b/lib/cfg.h
new file mode 100644 (file)
index 0000000..cd2a285
--- /dev/null
+++ b/lib/cfg.h
@@ -0,0 +1,75 @@
+/* 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 */
index 964a8b94c00ad12c1c819e8e3acc49582f785c85..fb26ecd3f8fc2042eae14c2086f289986eac29a2 100644 (file)
@@ -478,6 +478,7 @@ static struct hook_class executer_hook_class = {
     executer_periodic_cb,       /* periodic_cb */
     executer_wait_cb,           /* wait_cb */
     executer_closing_cb,        /* closing_cb */
+    NULL,                       /* reconfigure_cb */
 };
 
 void
index eb28bc38b4a64eb93af69d6cc912dffe1a42ba78..272952772f6e90f6b9430596660e69d0912727ef 100644 (file)
@@ -137,6 +137,7 @@ static struct hook_class fail_open_hook_class = {
     fail_open_periodic_cb,      /* periodic_cb */
     fail_open_wait_cb,          /* wait_cb */
     NULL,                       /* closing_cb */
+    NULL,                       /* reconfigure_cb */
 };
 
 void
index 82b0186f23a825a4b8db40ad75ba51ce0b9566b4..dd8a9c086d398aa5369739925fc7b8da627f9cf1 100644 (file)
@@ -1,4 +1,4 @@
-/* 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
@@ -32,7 +32,6 @@
  */
 
 #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
@@ -126,6 +134,7 @@ send_netflow_msg(const struct nx_flow_end *nfe, struct flow_end_data *fe)
     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. */
@@ -182,7 +191,12 @@ send_netflow_msg(const struct nx_flow_end *nfe, struct flow_end_data *fe)
     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++;
 }
 
@@ -222,7 +236,7 @@ send_nx_flow_end_config(const struct flow_end_data *fe)
     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;
@@ -250,7 +264,7 @@ flow_end_local_packet_cb(struct relay *r, void *flow_end_)
         return false;
     }
 
-    if (fe->netflow_fd >= 0) {
+    if (fe->netflow_fds[0] >= 0) {
         send_netflow_msg(nfe, fe);
     }
 
@@ -289,37 +303,77 @@ flow_end_remote_packet_cb(struct relay *r, void *flow_end_)
     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);
 }
index 3fb1f8f8aab1e15ce21df92af77b73807c321ce9..4ffad59aff071b3999d3f9a75e66e6b77164aa30 100644 (file)
 #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 */
index 46109daf19379e3f562c937dce2e179d368dc34f..0ff29d1e84fe49db0930269e21eeaf6a0feff1d5 100644 (file)
@@ -1,4 +1,4 @@
-/* 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
@@ -310,6 +310,7 @@ static struct hook_class in_band_hook_class = {
     in_band_periodic_cb,        /* periodic_cb */
     in_band_wait_cb,            /* wait_cb */
     NULL,                       /* closing_cb */
+    NULL,                       /* reconfigure_cb */
 };
 
 void
index 1d8d69d4096c4b10a1a66c5336f7a1c33b35e335..feebd8974684ba02d56033e2e1832fd2b16c7a34 100644 (file)
@@ -594,6 +594,7 @@ static struct hook_class port_watcher_hook_class = {
     port_watcher_periodic_cb,                            /* periodic_cb */
     port_watcher_wait_cb,                                /* wait_cb */
     NULL,                                                /* closing_cb */
+    NULL,                                                /* reconfigure_cb */
 };
 
 void
index 7a1e4951157ffc219169835c321cbb0272efe2c7..f03a28660ed8f7dfb3139642cbb221cbdaf49375 100644 (file)
@@ -1,4 +1,4 @@
-/* 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
@@ -240,6 +240,7 @@ static struct hook_class rate_limit_hook_class = {
     rate_limit_periodic_cb,     /* periodic_cb */
     rate_limit_wait_cb,         /* wait_cb */
     NULL,                       /* closing_cb */
+    NULL,                       /* reconfigure_cb */
 };
 
 void
index 18d1ef3eb9c8299abc656a39063c8d9fc4567dbf..8921a42a9e5e2f4052d116ea9d25303d2e555241 100644 (file)
@@ -1,6 +1,6 @@
 .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
@@ -195,6 +195,25 @@ the local port network device, and start the DHCP client afterward.
 .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
@@ -235,7 +254,7 @@ When controller discovery is not performed, this option has no effect.
 
 .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
@@ -366,11 +385,6 @@ at the switch.  The default is \fB--no-stp\fR in this distribution,
 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
@@ -481,4 +495,5 @@ require the controller to send the CA certificate, but
 .BR controller (8),
 .BR ofp-pki (8),
 .BR udatapath (8),
-.BR vlogconf (8)
+.BR vlogconf (8),
+.BR vswitchd.conf (5),
index c17d02cac0bb9614ebbbcb795d0dea4369997ef6..a7fd463878edb91b92fe814b30878996e4a2d294 100644 (file)
@@ -41,6 +41,7 @@
 #include <signal.h>
 #include <string.h>
 
+#include "cfg.h"
 #include "command-line.h"
 #include "compiler.h"
 #include "daemon.h"
@@ -59,6 +60,7 @@
 #include "poll-loop.h"
 #include "ratelimit.h"
 #include "rconn.h"
+#include "signals.h"
 #ifdef SUPPORT_SNAT
 #include "snat.h"
 #endif
@@ -86,6 +88,7 @@ struct secchan {
 
 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;
 
@@ -115,6 +118,7 @@ main(int argc, char *argv[])
     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;
@@ -130,6 +134,7 @@ main(int argc, char *argv[])
     vlog_init();
     parse_options(argc, argv, &s);
     signal(SIGPIPE, SIG_IGN);
+    sighup = signal_register(SIGHUP);
 
     secchan.hooks = NULL;
     secchan.n_hooks = 0;
@@ -214,7 +219,7 @@ main(int argc, char *argv[])
 #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);
     }
@@ -232,10 +237,16 @@ main(int argc, char *argv[])
         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);
@@ -294,12 +305,26 @@ main(int argc, char *argv[])
         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)
 {
@@ -578,6 +603,8 @@ parse_options(int argc, char *argv[], struct settings *s)
     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,
@@ -590,14 +617,15 @@ parse_options(int argc, char *argv[], struct settings *s)
         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},
@@ -611,7 +639,6 @@ parse_options(int argc, char *argv[], struct settings *s)
         {"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'},
@@ -642,8 +669,8 @@ parse_options(int argc, char *argv[], struct settings *s)
     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);
@@ -656,11 +683,15 @@ parse_options(int argc, char *argv[], struct settings *s)
             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")) {
@@ -743,11 +774,12 @@ parse_options(int argc, char *argv[], struct settings *s)
             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':
@@ -852,11 +884,14 @@ usage(void)
            "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"
@@ -870,7 +905,6 @@ usage(void)
            "  --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"
index b5948976bbba7efe3990f403b8c28a1a4114dad6..09439a0c7430699f729d06553da3d6e59c573e86 100644 (file)
@@ -1,4 +1,4 @@
-/* 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
@@ -53,6 +53,9 @@ enum fail_mode {
 
 /* 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? */
@@ -85,9 +88,6 @@ struct settings {
     /* 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 {
@@ -123,6 +123,7 @@ struct hook_class {
     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 *);
index b27372cb9458a8e932114fac2f8607cb66ff84f1..310a9069ec0639ea49d08124fa2e8b7a18f84307 100644 (file)
@@ -271,6 +271,7 @@ static struct hook_class snat_hook_class = {
     NULL,                       /* periodic_cb */
     NULL,                       /* wait_cb */
     NULL,                       /* closing_cb */
+    NULL,                       /* reconfigure_cb */
 };
 
 void
index cf22812360ed48ee024beb82d6ea2ae45ae13388..c3e95aff06d4c90e77d61196a6883f515b5dbdd7 100644 (file)
@@ -1,4 +1,4 @@
-/* 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
@@ -171,6 +171,7 @@ static struct hook_class switch_status_hook_class = {
     NULL,                           /* periodic_cb */
     NULL,                           /* wait_cb */
     NULL,                           /* closing_cb */
+    NULL,                           /* reconfigure_cb */
 };
 
 void
index 152595e9100dbe546c93fc4d1aa1e0968bc9ad82..3e8a7d772650022a538f18c60a9d99a24245fd00 100644 (file)
@@ -269,6 +269,7 @@ static struct hook_class stp_hook_class = {
     stp_periodic_cb,            /* periodic_cb */
     stp_wait_cb,                /* wait_cb */
     NULL,                       /* closing_cb */
+    NULL,                       /* reconfigure_cb */
 };
 
 void
index cc87027e08c3ea2efacdd87034bca1434d5ccd78..e6c68b8c17f7ebbb92d3042eff25c13bf1e82d81 100644 (file)
@@ -8,8 +8,6 @@ vswitchd_vswitchd_SOURCES = \
        vswitchd/brcompat.h \
        vswitchd/bridge.c \
        vswitchd/bridge.h \
-       vswitchd/cfg.c \
-       vswitchd/cfg.h \
        vswitchd/flowtrack.c \
        vswitchd/flowtrack.h \
        vswitchd/stats.c \
index f4f351c0568d835d9e562fec6b7f7a5c578e8fbe..d42899819a0c206b04e52bff3c92e90d7fd25870 100644 (file)
@@ -68,6 +68,8 @@
 #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. */
@@ -149,10 +151,6 @@ struct bridge {
     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;
@@ -509,6 +507,14 @@ log_secchan_died(enum vlog_level level, struct bridge *br, bool expected)
     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)
 {
@@ -566,6 +572,7 @@ console_logging_enabled(void)
 static void
 start_secchan(struct bridge *br)
 {
+    int i;
     struct svec argv;
     int sockets[2];
     struct stat s;
@@ -610,6 +617,11 @@ start_secchan(struct bridge *br)
     /* 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"));
@@ -622,9 +634,6 @@ start_secchan(struct bridge *br)
         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");
@@ -697,7 +706,6 @@ bridge_destroy(struct bridge *br)
         }
         process_destroy(br->secchan);
         rconn_destroy(br->rconn);
-        free(br->netflow_host);
         free(br->controller);
         svec_destroy(&br->secchan_opts);
         ft_destroy(br->ft);
@@ -833,10 +841,8 @@ bridge_reconfigure_one(struct bridge *br)
 {
     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);
@@ -914,17 +920,6 @@ bridge_reconfigure_one(struct bridge *br)
     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) {
@@ -945,6 +940,9 @@ bridge_reconfigure_one(struct bridge *br)
     }
 
     mirror_reconfigure(br);
+
+    /* Force secchan to reconfigure itself. */
+    hup_secchan(br);
 }
 
 static void
diff --git a/vswitchd/cfg.c b/vswitchd/cfg.c
deleted file mode 100644 (file)
index 8aa420d..0000000
+++ /dev/null
@@ -1,779 +0,0 @@
-/* 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(&section);
-    va_start(args, section_);
-    ds_put_format_valist(&section, section_, args);
-    ds_put_char(&section, '.');
-    va_end(args);
-
-    p = find_key_le(ds_cstr(&section));
-    retval = *p && !strncmp(section.string, *p, section.length);
-
-    ds_destroy(&section);
-    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(&section);
-    va_start(args, section_);
-    ds_put_format_valist(&section, section_, args);
-    ds_put_char(&section, '.');
-    va_end(args);
-
-    svec_clear(svec);
-    for (p = find_key_le(ds_cstr(&section));
-         *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(&section);
-}
-
-/* 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(&section);
-
-    /* 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(&section, 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(&section, '.');
-        ds_put_buffer(&section, 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(&section);
-
-error:
-    ds_destroy(&section);
-    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)));
-}
diff --git a/vswitchd/cfg.h b/vswitchd/cfg.h
deleted file mode 100644 (file)
index cd2a285..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-/* 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 */
index c7e5c86223d41c0623231c3c205b3d9a917bc054..c83a22bf3ccee599e998fd0b9ec97348a3f6ec99 100644 (file)
@@ -45,6 +45,7 @@
 #include "poll-loop.h"
 #include "process.h"
 #include "signals.h"
+#include "svec.h"
 #include "timeval.h"
 #include "util.h"
 #include "vconn-ssl.h"
@@ -60,6 +61,7 @@ static void usage(void) NO_RETURN;
 static void reconfigure(void);
 
 static bool brc_enabled = false;
+struct svec config_files;
 
 int
 main(int argc, char *argv[])
@@ -71,6 +73,7 @@ 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);
@@ -156,6 +159,7 @@ parse_options(int argc, char *argv[])
 
         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 "
index 3d1bccad8407d582fc995ba2f5036284f798180e..d5a8c9ca4f1608bcab21b31dd76583273ca95e1d 100644 (file)
@@ -4,10 +4,10 @@
 .  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
@@ -337,10 +337,10 @@ set to 64:
 .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: