#define THIS_MODULE VLM_netdev
#include "vlog.h"
-static const struct netdev_class *netdev_classes[] = {
+static const struct netdev_class *base_netdev_classes[] = {
&netdev_linux_class,
&netdev_tap_class,
&netdev_gre_class,
};
-static int n_netdev_classes = ARRAY_SIZE(netdev_classes);
+
+static struct shash netdev_classes = SHASH_INITIALIZER(&netdev_classes);
/* All created network devices. */
static struct shash netdev_dev_shash = SHASH_INITIALIZER(&netdev_dev_shash);
* additional log messages. */
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
-static void restore_all_flags(void *aux);
+static void close_all_netdevs(void *aux UNUSED);
static int restore_flags(struct netdev *netdev);
+void update_device_args(struct netdev_dev *, const struct shash *args);
-/* Attempts to initialize the netdev module. Returns 0 if successful,
- * otherwise a positive errno value.
- *
- * Calling this function is optional. If not called explicitly, it will
- * automatically be called upon the first attempt to open or create a
- * network device. */
-int
+static void
netdev_initialize(void)
{
static int status = -1;
+
if (status < 0) {
- int i, j;
+ int i;
- fatal_signal_add_hook(restore_all_flags, NULL, true);
+ fatal_signal_add_hook(close_all_netdevs, NULL, NULL, true);
status = 0;
- for (i = j = 0; i < n_netdev_classes; i++) {
- const struct netdev_class *class = netdev_classes[i];
- if (class->init) {
- int retval = class->init();
- if (!retval) {
- netdev_classes[j++] = class;
- } else {
- VLOG_ERR("failed to initialize %s network device "
- "class: %s", class->type, strerror(retval));
- if (!status) {
- status = retval;
- }
- }
- } else {
- netdev_classes[j++] = class;
- }
+ for (i = 0; i < ARRAY_SIZE(base_netdev_classes); i++) {
+ netdev_register_provider(base_netdev_classes[i]);
}
- n_netdev_classes = j;
}
- return status;
}
/* Performs periodic work needed by all the various kinds of netdevs.
void
netdev_run(void)
{
- int i;
- for (i = 0; i < n_netdev_classes; i++) {
- const struct netdev_class *class = netdev_classes[i];
+ struct shash_node *node;
+ SHASH_FOR_EACH(node, &netdev_classes) {
+ const struct netdev_class *class = node->data;
if (class->run) {
class->run();
}
void
netdev_wait(void)
{
- int i;
- for (i = 0; i < n_netdev_classes; i++) {
- const struct netdev_class *class = netdev_classes[i];
+ struct shash_node *node;
+ SHASH_FOR_EACH(node, &netdev_classes) {
+ const struct netdev_class *class = node->data;
if (class->wait) {
class->wait();
}
}
}
-static int
-create_device(struct netdev_options *options, struct netdev_dev **netdev_devp)
+/* Initializes and registers a new netdev provider. After successful
+ * registration, new netdevs of that type can be opened using netdev_open(). */
+int
+netdev_register_provider(const struct netdev_class *new_class)
{
- int i;
+ struct netdev_class *new_provider;
- if (!options->may_create) {
- VLOG_WARN("attempted to create a device that may not be created: %s",
- options->name);
- return ENODEV;
+ if (shash_find(&netdev_classes, new_class->type)) {
+ VLOG_WARN("attempted to register duplicate netdev provider: %s",
+ new_class->type);
+ return EEXIST;
}
- if (!options->type || strlen(options->type) == 0) {
- /* Default to system. */
- options->type = "system";
+ if (new_class->init) {
+ int error = new_class->init();
+ if (error) {
+ VLOG_ERR("failed to initialize %s network device class: %s",
+ new_class->type, strerror(error));
+ return error;
+ }
}
- for (i = 0; i < n_netdev_classes; i++) {
- const struct netdev_class *class = netdev_classes[i];
+ new_provider = xmalloc(sizeof *new_provider);
+ memcpy(new_provider, new_class, sizeof *new_provider);
+
+ shash_add(&netdev_classes, new_class->type, new_provider);
+
+ return 0;
+}
+
+/* Unregisters a netdev provider. 'type' must have been previously
+ * registered and not currently be in use by any netdevs. After unregistration
+ * new netdevs of that type cannot be opened using netdev_open(). */
+int
+netdev_unregister_provider(const char *type)
+{
+ struct shash_node *del_node, *netdev_dev_node;
+
+ del_node = shash_find(&netdev_classes, type);
+ if (!del_node) {
+ VLOG_WARN("attempted to unregister a netdev provider that is not "
+ "registered: %s", type);
+ return EAFNOSUPPORT;
+ }
- if (!strcmp(options->type, class->type)) {
- return class->create(options->name, options->type, options->args,
- netdev_devp);
+ SHASH_FOR_EACH(netdev_dev_node, &netdev_dev_shash) {
+ struct netdev_dev *netdev_dev = netdev_dev_node->data;
+ if (!strcmp(netdev_dev->class->type, type)) {
+ VLOG_WARN("attempted to unregister in use netdev provider: %s",
+ type);
+ return EBUSY;
}
}
- VLOG_WARN("could not create netdev %s of unknown type %s", options->name,
- options->type);
- return EINVAL;
+ shash_delete(&netdev_classes, del_node);
+ free(del_node->data);
+
+ return 0;
}
-static uint32_t
-shash_hash(const struct shash *shash)
+/* Clears 'types' and enumerates the types of all currently registered netdev
+ * providers into it. The caller must first initialize the svec. */
+void
+netdev_enumerate_types(struct svec *types)
{
- int hash = 0;
struct shash_node *node;
- uint32_t entry_hash;
- SHASH_FOR_EACH(node, shash) {
- entry_hash = hash_string(node->name, 0);
- entry_hash ^= hash_string(node->data, 10);
- hash ^= hash_int(entry_hash, 0);
+ netdev_initialize();
+ svec_clear(types);
+
+ SHASH_FOR_EACH(node, &netdev_classes) {
+ const struct netdev_class *netdev_class = node->data;
+ svec_add(types, netdev_class->type);
}
+}
+
+/* Compares 'args' to those used to those used by 'dev'. Returns true
+ * if the arguments are the same, false otherwise. Does not update the
+ * values stored in 'dev'. */
+static bool
+compare_device_args(const struct netdev_dev *dev, const struct shash *args)
+{
+ const struct shash_node **new_args;
+ bool result = true;
+ int i;
- return hash;
+ if (shash_count(args) != dev->n_args) {
+ return false;
+ }
+
+ new_args = shash_sort(args);
+ for (i = 0; i < dev->n_args; i++) {
+ if (strcmp(dev->args[i].key, new_args[i]->name) ||
+ strcmp(dev->args[i].value, new_args[i]->data)) {
+ result = false;
+ goto finish;
+ }
+ }
+
+finish:
+ free(new_args);
+ return result;
+}
+
+static int
+compare_args(const void *a_, const void *b_)
+{
+ const struct arg *a = a_;
+ const struct arg *b = b_;
+ return strcmp(a->key, b->key);
+}
+
+void
+update_device_args(struct netdev_dev *dev, const struct shash *args)
+{
+ struct shash_node *node;
+ int i;
+
+ if (dev->n_args) {
+ for (i = 0; i < dev->n_args; i++) {
+ free(dev->args[i].key);
+ free(dev->args[i].value);
+ }
+
+ free(dev->args);
+ dev->n_args = 0;
+ }
+
+ if (!args || shash_is_empty(args)) {
+ return;
+ }
+
+ dev->n_args = shash_count(args);
+ dev->args = xmalloc(dev->n_args * sizeof *dev->args);
+
+ i = 0;
+ SHASH_FOR_EACH(node, args) {
+ dev->args[i].key = xstrdup(node->name);
+ dev->args[i].value = xstrdup(node->data);
+ i++;
+ }
+
+ qsort(dev->args, dev->n_args, sizeof *dev->args, compare_args);
+}
+
+static int
+create_device(struct netdev_options *options, struct netdev_dev **netdev_devp)
+{
+ struct netdev_class *netdev_class;
+
+ if (!options->may_create) {
+ VLOG_WARN("attempted to create a device that may not be created: %s",
+ options->name);
+ return ENODEV;
+ }
+
+ if (!options->type || strlen(options->type) == 0) {
+ /* Default to system. */
+ options->type = "system";
+ }
+
+ netdev_class = shash_find_data(&netdev_classes, options->type);
+ if (!netdev_class) {
+ VLOG_WARN("could not create netdev %s of unknown type %s",
+ options->name, options->type);
+ return EAFNOSUPPORT;
+ }
+
+ return netdev_class->create(options->name, options->type, options->args,
+ netdev_devp);
}
/* Opens the network device named 'name' (e.g. "eth0") and returns zero if
if (error) {
return error;
}
-
- netdev_dev->args_hash = shash_hash(options->args);
+ update_device_args(netdev_dev, options->args);
} else if (options->may_open) {
- if (!shash_is_empty(options->args)) {
- uint32_t args_hash = shash_hash(options->args);
+ if (!shash_is_empty(options->args) &&
+ !compare_device_args(netdev_dev, options->args)) {
- if (args_hash != netdev_dev->args_hash) {
- VLOG_WARN("attempted to open already created netdev with "
- "different arguments: %s", options->name);
- return EINVAL;
- }
+ VLOG_WARN("%s: attempted to open already created netdev with "
+ "different arguments", options->name);
+ return EINVAL;
}
} else {
- VLOG_WARN("attempted to create a netdev device with bound name: %s",
+ VLOG_WARN("%s: attempted to create a netdev device with bound name",
options->name);
return EEXIST;
}
}
if (netdev_dev->class->reconfigure) {
- uint32_t args_hash = shash_hash(args);
-
- if (netdev_dev->args_hash != args_hash) {
- netdev_dev->args_hash = args_hash;
+ if (!compare_device_args(netdev_dev, args)) {
+ update_device_args(netdev_dev, args);
return netdev_dev->class->reconfigure(netdev_dev, args);
}
+ } else if (!shash_is_empty(args)) {
+ VLOG_WARN("%s: arguments provided to device that does not have a "
+ "reconfigure function", netdev_get_name(netdev));
}
return 0;
}
}
-/* Initializes 'svec' with a list of the names of all known network devices. */
+/* Clears 'svec' and enumerates the names of all known network devices. */
int
netdev_enumerate(struct svec *svec)
{
- int error;
- int i;
-
- svec_init(svec);
+ struct shash_node *node;
+ int error = 0;
netdev_initialize();
+ svec_clear(svec);
- error = 0;
- for (i = 0; i < n_netdev_classes; i++) {
- const struct netdev_class *class = netdev_classes[i];
- if (class->enumerate) {
- int retval = class->enumerate(svec);
+ SHASH_FOR_EACH(node, &netdev_classes) {
+ const struct netdev_class *netdev_class = node->data;
+ if (netdev_class->enumerate) {
+ int retval = netdev_class->enumerate(svec);
if (retval) {
VLOG_WARN("failed to enumerate %s network devices: %s",
- class->type, strerror(retval));
+ netdev_class->type, strerror(retval));
if (!error) {
error = retval;
}
}
}
}
+
return error;
}
netdev_find_dev_by_in4(const struct in_addr *in4)
{
struct netdev *netdev;
- struct svec dev_list;
+ struct svec dev_list = SVEC_EMPTY_INITIALIZER;
size_t i;
netdev_enumerate(&dev_list);
{
assert(!shash_find(&netdev_dev_shash, name));
+ memset(netdev_dev, 0, sizeof *netdev_dev);
netdev_dev->class = class;
- netdev_dev->ref_cnt = 0;
netdev_dev->name = xstrdup(name);
netdev_dev->node = shash_add(&netdev_dev_shash, name, netdev_dev);
}
assert(!netdev_dev->ref_cnt);
shash_delete(&netdev_dev_shash, netdev_dev->node);
+ update_device_args(netdev_dev, NULL);
if (destroy) {
netdev_dev->class->destroy(netdev_dev);
return netdev_dev->name;
}
+/* Returns the netdev_dev with 'name' or NULL if there is none.
+ *
+ * The caller must not free the returned value. */
+struct netdev_dev *
+netdev_dev_from_name(const char *name)
+{
+ return shash_find_data(&netdev_dev_shash, name);
+}
+
+/* Fills 'device_list' with devices that match 'class'.
+ *
+ * The caller is responsible for initializing and destroying 'device_list'
+ * but the contained netdev_devs must not be freed. */
+void
+netdev_dev_get_devices(const struct netdev_class *class,
+ struct shash *device_list)
+{
+ struct shash_node *node;
+ SHASH_FOR_EACH (node, &netdev_dev_shash) {
+ struct netdev_dev *dev = node->data;
+
+ if (dev->class == class) {
+ shash_add(device_list, node->name, node->data);
+ }
+ }
+}
+
/* Initializes 'netdev' as a instance of the netdev_dev.
*
* This function adds 'netdev' to a netdev-owned linked list, so it is very
void
netdev_init(struct netdev *netdev, struct netdev_dev *netdev_dev)
{
+ memset(netdev, 0, sizeof *netdev);
netdev->netdev_dev = netdev_dev;
- netdev->save_flags = 0;
- netdev->changed_flags = 0;
list_push_back(&netdev_list, &netdev->node);
}
return 0;
}
-/* Retores all the flags on all network devices that we modified. Called from
- * a signal handler, so it does not attempt to report error conditions. */
+/* Close all netdevs on shutdown so they can do any needed cleanup such as
+ * destroying devices, restoring flags, etc. */
static void
-restore_all_flags(void *aux UNUSED)
+close_all_netdevs(void *aux UNUSED)
{
- struct netdev *netdev;
- LIST_FOR_EACH (netdev, struct netdev, node, &netdev_list) {
- restore_flags(netdev);
+ struct netdev *netdev, *next;
+ LIST_FOR_EACH_SAFE(netdev, next, struct netdev, node, &netdev_list) {
+ netdev_close(netdev);
}
}