From: Justin Pettit Date: Fri, 13 Feb 2009 18:36:44 +0000 (-0800) Subject: Support multiple NetFlow collectors. X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=84f08393a85d3994fc26e434cdeabb24e66da5fb;p=openvswitch Support multiple NetFlow collectors. 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. --- diff --git a/lib/automake.mk b/lib/automake.mk index d1cccd9e..f629a1f1 100644 --- a/lib/automake.mk +++ b/lib/automake.mk @@ -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 index 00000000..8aa420d3 --- /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 . + * + * In addition, as a special exception, Nicira Networks gives permission + * to link the code of its release of vswitchd with the OpenSSL project's + * "OpenSSL" library (or with modified versions of it that use the same + * license as the "OpenSSL" library), and distribute the linked + * executables. You must obey the GNU General Public License in all + * respects for all of the code used other than "OpenSSL". If you modify + * this file, you may extend this exception to your version of the file, + * but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. + */ + +#include +#include "cfg.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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); +} + +#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))); +} diff --git a/lib/cfg.h b/lib/cfg.h new file mode 100644 index 00000000..cd2a2856 --- /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 . + * + * 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 +#include +#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 */ diff --git a/secchan/executer.c b/secchan/executer.c index 964a8b94..fb26ecd3 100644 --- a/secchan/executer.c +++ b/secchan/executer.c @@ -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 diff --git a/secchan/fail-open.c b/secchan/fail-open.c index eb28bc38..27295277 100644 --- a/secchan/fail-open.c +++ b/secchan/fail-open.c @@ -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 diff --git a/secchan/flow-end.c b/secchan/flow-end.c index 82b0186f..dd8a9c08 100644 --- a/secchan/flow-end.c +++ b/secchan/flow-end.c @@ -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 -#include "flow-end.h" #include #include #include @@ -40,27 +39,36 @@ #include #include #include + #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; inetflow_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; inetflow_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= 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; inetflow_fds[i] = -1; } + fe->send_ofp_exp = false; add_hook(secchan, &flow_end_hook_class, fe); - - send_nx_flow_end_config(fe); } diff --git a/secchan/flow-end.h b/secchan/flow-end.h index 3fb1f8f8..4ffad59a 100644 --- a/secchan/flow-end.h +++ b/secchan/flow-end.h @@ -34,9 +34,12 @@ #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 */ diff --git a/secchan/in-band.c b/secchan/in-band.c index 46109daf..0ff29d1e 100644 --- a/secchan/in-band.c +++ b/secchan/in-band.c @@ -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 diff --git a/secchan/port-watcher.c b/secchan/port-watcher.c index 1d8d69d4..feebd897 100644 --- a/secchan/port-watcher.c +++ b/secchan/port-watcher.c @@ -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 diff --git a/secchan/ratelimit.c b/secchan/ratelimit.c index 7a1e4951..f03a2866 100644 --- a/secchan/ratelimit.c +++ b/secchan/ratelimit.c @@ -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 diff --git a/secchan/secchan.8.in b/secchan/secchan.8.in index 18d1ef3e..8921a42a 100644 --- a/secchan/secchan.8.in +++ b/secchan/secchan.8.in @@ -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), diff --git a/secchan/secchan.c b/secchan/secchan.c index c17d02ca..a7fd4638 100644 --- a/secchan/secchan.c +++ b/secchan/secchan.c @@ -41,6 +41,7 @@ #include #include +#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" diff --git a/secchan/secchan.h b/secchan/secchan.h index b5948976..09439a0c 100644 --- a/secchan/secchan.h +++ b/secchan/secchan.h @@ -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 *); diff --git a/secchan/snat.c b/secchan/snat.c index b27372cb..310a9069 100644 --- a/secchan/snat.c +++ b/secchan/snat.c @@ -271,6 +271,7 @@ static struct hook_class snat_hook_class = { NULL, /* periodic_cb */ NULL, /* wait_cb */ NULL, /* closing_cb */ + NULL, /* reconfigure_cb */ }; void diff --git a/secchan/status.c b/secchan/status.c index cf228123..c3e95aff 100644 --- a/secchan/status.c +++ b/secchan/status.c @@ -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 diff --git a/secchan/stp-secchan.c b/secchan/stp-secchan.c index 152595e9..3e8a7d77 100644 --- a/secchan/stp-secchan.c +++ b/secchan/stp-secchan.c @@ -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 diff --git a/vswitchd/automake.mk b/vswitchd/automake.mk index cc87027e..e6c68b8c 100644 --- a/vswitchd/automake.mk +++ b/vswitchd/automake.mk @@ -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 \ diff --git a/vswitchd/bridge.c b/vswitchd/bridge.c index f4f351c0..d4289981 100644 --- a/vswitchd/bridge.c +++ b/vswitchd/bridge.c @@ -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; iname, "%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 index 8aa420d3..00000000 --- a/vswitchd/cfg.c +++ /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 . - * - * In addition, as a special exception, Nicira Networks gives permission - * to link the code of its release of vswitchd with the OpenSSL project's - * "OpenSSL" library (or with modified versions of it that use the same - * license as the "OpenSSL" library), and distribute the linked - * executables. You must obey the GNU General Public License in all - * respects for all of the code used other than "OpenSSL". If you modify - * this file, you may extend this exception to your version of the file, - * but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. - */ - -#include -#include "cfg.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#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); -} - -#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))); -} diff --git a/vswitchd/cfg.h b/vswitchd/cfg.h deleted file mode 100644 index cd2a2856..00000000 --- a/vswitchd/cfg.h +++ /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 . - * - * 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 -#include -#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 */ diff --git a/vswitchd/vswitchd.c b/vswitchd/vswitchd.c index c7e5c862..c83a22bf 100644 --- a/vswitchd/vswitchd.c +++ b/vswitchd/vswitchd.c @@ -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 " diff --git a/vswitchd/vswitchd.conf.5 b/vswitchd/vswitchd.conf.5 index 3d1bccad..d5a8c9ca 100644 --- a/vswitchd/vswitchd.conf.5 +++ b/vswitchd/vswitchd.conf.5 @@ -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: