From: Justin Pettit Date: Tue, 1 Dec 2009 09:14:33 +0000 (-0800) Subject: netdev: Allow explicit creation of netdev objects X-Git-Tag: v0.99.0~11 X-Git-Url: http://git.onelab.eu/?a=commitdiff_plain;h=6c88d577e83db12f73df12be8fc575419b011fda;p=sliver-openvswitch.git netdev: Allow explicit creation of netdev objects This change adds netdev_create() and netdev_destroy() functions to allow the creation of network devices through the netdev library. Previously, network devices had to already exist or be created on demand through netdev_open(). This caused problems such as not being able to specify TAP devices as ports in ovs-vswitchd, which this patch fixes. This also lays the groundwork for adding GRE and VDE support. --- diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c index 35724d9bf..c4b5a9949 100644 --- a/lib/dpif-netdev.c +++ b/lib/dpif-netdev.c @@ -373,9 +373,13 @@ do_add_port(struct dp_netdev *dp, const char *devname, uint16_t flags, if (!internal) { error = netdev_open(devname, NETDEV_ETH_TYPE_ANY, &netdev); } else { - char *tapname = xasprintf("tap:%s", devname); - error = netdev_open(tapname, NETDEV_ETH_TYPE_ANY, &netdev); - free(tapname); + error = netdev_create(devname, "tap", NULL); + if (!error) { + error = netdev_open(devname, NETDEV_ETH_TYPE_ANY, &netdev); + if (error) { + netdev_destroy(devname); + } + } } if (error) { return error; @@ -468,6 +472,7 @@ static int do_del_port(struct dp_netdev *dp, uint16_t port_no) { struct dp_netdev_port *port; + char *name; int error; error = get_port_by_number(dp, port_no, &port); @@ -480,7 +485,12 @@ do_del_port(struct dp_netdev *dp, uint16_t port_no) dp->n_ports--; dp->serial++; + name = xstrdup(netdev_get_name(port->netdev)); netdev_close(port->netdev); + if (port->internal) { + netdev_destroy(name); + } + free(name); free(port); return 0; diff --git a/lib/netdev-linux.c b/lib/netdev-linux.c index b8e8015d4..61994c2fe 100644 --- a/lib/netdev-linux.c +++ b/lib/netdev-linux.c @@ -67,6 +67,14 @@ #define ADVERTISED_Asym_Pause (1 << 14) #endif +/* Provider-specific netdev object. Netdev objects are devices that are + * created by the netdev library through a netdev_create() call. */ +struct netdev_obj_linux { + struct netdev_obj netdev_obj; + + int tap_fd; /* File descriptor for TAP device. */ +}; + struct netdev_linux { struct netdev netdev; @@ -142,6 +150,13 @@ static int set_etheraddr(const char *netdev_name, int hwaddr_family, static int get_stats_via_netlink(int ifindex, struct netdev_stats *stats); static int get_stats_via_proc(const char *netdev_name, struct netdev_stats *stats); +static struct netdev_obj_linux * +netdev_obj_linux_cast(const struct netdev_obj *netdev_obj) +{ + netdev_obj_assert_class(netdev_obj, &netdev_linux_class); + return CONTAINER_OF(netdev_obj, struct netdev_obj_linux, netdev_obj); +} + static struct netdev_linux * netdev_linux_cast(const struct netdev *netdev) { @@ -194,9 +209,77 @@ netdev_linux_cache_cb(const struct rtnetlink_change *change, } } +/* Creates the netdev object of 'type' with 'name'. */ static int -netdev_linux_open(const char *name, char *suffix, int ethertype, - struct netdev **netdevp) +netdev_linux_create(const char *name, const char *type, + const struct shash *args, bool created) +{ + struct netdev_obj_linux *netdev_obj; + static const char tap_dev[] = "/dev/net/tun"; + struct ifreq ifr; + int error; + + if (!shash_is_empty(args)) { + VLOG_WARN("arguments for %s devices should be empty", type); + } + + /* Create the name binding in the netdev library for this object. */ + netdev_obj = xcalloc(1, sizeof *netdev_obj); + netdev_obj_init(&netdev_obj->netdev_obj, name, &netdev_linux_class, + created); + netdev_obj->tap_fd = -1; + + if (strcmp(type, "tap")) { + return 0; + } + + /* Open tap device. */ + netdev_obj->tap_fd = open(tap_dev, O_RDWR); + if (netdev_obj->tap_fd < 0) { + error = errno; + VLOG_WARN("opening \"%s\" failed: %s", tap_dev, strerror(error)); + goto error; + } + + /* Create tap device. */ + ifr.ifr_flags = IFF_TAP | IFF_NO_PI; + strncpy(ifr.ifr_name, name, sizeof ifr.ifr_name); + if (ioctl(netdev_obj->tap_fd, TUNSETIFF, &ifr) == -1) { + VLOG_WARN("%s: creating tap device failed: %s", name, + strerror(errno)); + error = errno; + goto error; + } + + /* Make non-blocking. */ + error = set_nonblocking(netdev_obj->tap_fd); + if (error) { + goto error; + } + + return 0; + +error: + netdev_destroy(name); + return error; +} + +/* Destroys the netdev object 'netdev_obj_'. */ +static void +netdev_linux_destroy(struct netdev_obj *netdev_obj_) +{ + struct netdev_obj_linux *netdev_obj = netdev_obj_linux_cast(netdev_obj_); + + if (netdev_obj->tap_fd >= 0) { + close(netdev_obj->tap_fd); + } + free(netdev_obj); + + return; +} + +static int +netdev_linux_open(const char *name, int ethertype, struct netdev **netdevp) { struct netdev_linux *netdev; enum netdev_flags flags; @@ -204,10 +287,10 @@ netdev_linux_open(const char *name, char *suffix, int ethertype, /* Allocate network device. */ netdev = xcalloc(1, sizeof *netdev); - netdev_init(&netdev->netdev, suffix, &netdev_linux_class); + netdev_init(&netdev->netdev, name, &netdev_linux_class); netdev->netdev_fd = -1; netdev->tap_fd = -1; - netdev->cache = shash_find_data(&cache_map, suffix); + netdev->cache = shash_find_data(&cache_map, name); if (!netdev->cache) { if (shash_is_empty(&cache_map)) { int error = rtnetlink_notifier_register( @@ -218,14 +301,14 @@ netdev_linux_open(const char *name, char *suffix, int ethertype, } } netdev->cache = xmalloc(sizeof *netdev->cache); - netdev->cache->shash_node = shash_add(&cache_map, suffix, + netdev->cache->shash_node = shash_add(&cache_map, name, netdev->cache); netdev->cache->valid = 0; netdev->cache->ref_cnt = 0; } netdev->cache->ref_cnt++; - if (!strncmp(name, "tap:", 4)) { + if (!strcmp(netdev_get_type(&netdev->netdev), "tap")) { static const char tap_dev[] = "/dev/net/tun"; struct ifreq ifr; @@ -239,9 +322,9 @@ netdev_linux_open(const char *name, char *suffix, int ethertype, /* Create tap device. */ ifr.ifr_flags = IFF_TAP | IFF_NO_PI; - strncpy(ifr.ifr_name, suffix, sizeof ifr.ifr_name); + strncpy(ifr.ifr_name, name, sizeof ifr.ifr_name); if (ioctl(netdev->tap_fd, TUNSETIFF, &ifr) == -1) { - VLOG_WARN("%s: creating tap device failed: %s", suffix, + VLOG_WARN("%s: creating tap device failed: %s", name, strerror(errno)); error = errno; goto error; @@ -296,7 +379,7 @@ netdev_linux_open(const char *name, char *suffix, int ethertype, if (bind(netdev->netdev_fd, (struct sockaddr *) &sll, sizeof sll) < 0) { error = errno; - VLOG_ERR("bind to %s failed: %s", suffix, strerror(error)); + VLOG_ERR("bind to %s failed: %s", name, strerror(error)); goto error; } @@ -1377,13 +1460,16 @@ netdev_linux_poll_remove(struct netdev_notifier *notifier_) } const struct netdev_class netdev_linux_class = { - "", /* prefix */ - "linux", /* name */ + "system", /* type */ netdev_linux_init, netdev_linux_run, netdev_linux_wait, + netdev_linux_create, + netdev_linux_destroy, + NULL, /* reconfigure */ + netdev_linux_open, netdev_linux_close, @@ -1422,13 +1508,16 @@ const struct netdev_class netdev_linux_class = { }; const struct netdev_class netdev_tap_class = { - "tap", /* prefix */ - "tap", /* name */ + "tap", /* type */ netdev_linux_init, NULL, /* run */ NULL, /* wait */ + netdev_linux_create, + netdev_linux_destroy, + NULL, /* reconfigure */ + netdev_linux_open, netdev_linux_close, diff --git a/lib/netdev-provider.h b/lib/netdev-provider.h index e013e2026..64d227e8d 100644 --- a/lib/netdev-provider.h +++ b/lib/netdev-provider.h @@ -22,6 +22,26 @@ #include #include "netdev.h" #include "list.h" +#include "shash.h" + +/* A network device object that was created through the netdev_create() + * call. + * + * This structure should be treated as opaque by network device + * implementations. */ +struct netdev_obj { + const struct netdev_class *class; + int ref_cnt; + bool created; /* Was netdev_create() called? */ +}; + +void netdev_obj_init(struct netdev_obj *, const char *name, + const struct netdev_class *, bool created); +static inline void netdev_obj_assert_class(const struct netdev_obj *netdev_obj, + const struct netdev_class *class) +{ + assert(netdev_obj->class == class); +} /* A network device (e.g. an Ethernet device). * @@ -30,6 +50,7 @@ struct netdev { const struct netdev_class *class; char *name; /* e.g. "eth0" */ + enum netdev_flags save_flags; /* Initial device flags. */ enum netdev_flags changed_flags; /* Flags that we changed. */ struct list node; /* Element in global list. */ @@ -42,6 +63,7 @@ static inline void netdev_assert_class(const struct netdev *netdev, { assert(netdev->class == class); } +const char *netdev_get_type(const struct netdev *netdev); /* A network device notifier. * @@ -62,15 +84,13 @@ void netdev_notifier_init(struct netdev_notifier *, struct netdev *, * These functions return 0 if successful or a positive errno value on failure, * except where otherwise noted. */ struct netdev_class { - /* Prefix for names of netdevs in this class, e.g. "ndunix:". + /* Type of netdevs in this class, e.g. "system", "tap", "gre", etc. * - * One netdev class may have the empty string "" as its prefix, in which - * case that netdev class is associated with netdev names that do not - * contain a colon. */ - const char *prefix; - - /* Class name, for use in error messages. */ - const char *name; + * One of the providers should supply a "system" type, since this is + * the type assumed when a device name was not bound through the + * netdev_create() call. The "system" type corresponds to an + * existing network device on the system. */ + const char *type; /* Called only once, at program startup. Returning an error from this * function will prevent any network device in this class from being @@ -88,19 +108,45 @@ struct netdev_class { * to be called. May be null if nothing is needed here. */ void (*wait)(void); + /* Attempts to create a network device object of 'type' with 'name'. + * 'type' corresponds to the 'type' field used in the netdev_class + * structure. + * + * The 'created' flag indicates that the user called netdev_create() + * and thus will eventually call netdev_destroy(). If the flag is + * false, then the object was dynamically created based on a call to + * netdev_open() without first calling netdev_create() and will be + * automatically destroyed when no more netdevs have 'name' open. A + * provider implementation should pass this flag to netdev_obj_init(). */ + int (*create)(const char *name, const char *type, + const struct shash *args, bool created); + + /* Destroys 'netdev_obj'. + * + * Netdev objects maintain a reference count that is incremented on + * netdev_open() and decremented on netdev_close(). If 'netdev_obj' + * has a non-zero reference count, then this function will not be + * called. */ + void (*destroy)(struct netdev_obj *netdev_obj); + + /* Reconfigures the device object 'netdev_obj' with 'args'. + * + * If this netdev class does not support reconfiguring a netdev + * object, this may be a null pointer. + */ + int (*reconfigure)(struct netdev_obj *netdev_obj, + const struct shash *args); + /* Attempts to open a network device. On success, sets '*netdevp' to the - * new network device. 'name' is the full network device name provided by + * new network device. 'name' is the network device name provided by * the user. This name is useful for error messages but must not be * modified. * - * 'suffix' is a copy of 'name' following the netdev's 'prefix'. - * * 'ethertype' may be a 16-bit Ethernet protocol value in host byte order * to capture frames of that type received on the device. It may also be * one of the 'enum netdev_pseudo_ethertype' values to receive frames in * one of those categories. */ - int (*open)(const char *name, char *suffix, int ethertype, - struct netdev **netdevp); + int (*open)(const char *name, int ethertype, struct netdev **netdevp); /* Closes 'netdev'. */ void (*close)(struct netdev *netdev); diff --git a/lib/netdev.c b/lib/netdev.c index 222342fd2..fb0f98e6e 100644 --- a/lib/netdev.c +++ b/lib/netdev.c @@ -45,6 +45,9 @@ static const struct netdev_class *netdev_classes[] = { }; static int n_netdev_classes = ARRAY_SIZE(netdev_classes); +/* All created network devices. */ +static struct shash netdev_obj_shash = SHASH_INITIALIZER(&netdev_obj_shash); + /* All open network devices. */ static struct list netdev_list = LIST_INITIALIZER(&netdev_list); @@ -59,7 +62,8 @@ static int restore_flags(struct netdev *netdev); * 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 a network device. */ + * automatically be called upon the first attempt to open or create a + * network device. */ int netdev_initialize(void) { @@ -78,7 +82,7 @@ netdev_initialize(void) netdev_classes[j++] = class; } else { VLOG_ERR("failed to initialize %s network device " - "class: %s", class->name, strerror(retval)); + "class: %s", class->type, strerror(retval)); if (!status) { status = retval; } @@ -124,6 +128,92 @@ netdev_wait(void) } } +/* Attempts to create a network device object of 'type' with 'name'. 'type' + * corresponds to the 'type' field used in the netdev_class * structure. + * Arguments for creation are provided in 'args', which may be empty or NULL + * if none are needed. */ +int +netdev_create(const char *name, const char *type, const struct shash *args) +{ + struct shash empty_args = SHASH_INITIALIZER(&empty_args); + int i; + + netdev_initialize(); + + if (!args) { + args = &empty_args; + } + + if (shash_find(&netdev_obj_shash, name)) { + VLOG_WARN("attempted to create a netdev object with bound name: %s", + name); + return EEXIST; + } + + for (i = 0; i < n_netdev_classes; i++) { + const struct netdev_class *class = netdev_classes[i]; + if (!strcmp(type, class->type)) { + return class->create(name, type, args, true); + } + } + + VLOG_WARN("could not create netdev object of unknown type: %s", type); + + return EINVAL; +} + +/* Destroys netdev object 'name'. Netdev objects maintain a reference count + * which is incremented on netdev_open() and decremented on netdev_close(). + * If 'name' has a non-zero reference count, it will not destroy the object + * and return EBUSY. */ +int +netdev_destroy(const char *name) +{ + struct shash_node *node; + struct netdev_obj *netdev_obj; + + node = shash_find(&netdev_obj_shash, name); + if (!node) { + return ENODEV; + } + + netdev_obj = node->data; + if (netdev_obj->ref_cnt != 0) { + VLOG_WARN("attempt to destroy open netdev object (%d): %s", + netdev_obj->ref_cnt, name); + return EBUSY; + } + + shash_delete(&netdev_obj_shash, node); + netdev_obj->class->destroy(netdev_obj); + + return 0; +} + +/* Reconfigures the device object 'name' with 'args'. 'args' may be empty + * or NULL if none are needed. */ +int +netdev_reconfigure(const char *name, const struct shash *args) +{ + struct shash empty_args = SHASH_INITIALIZER(&empty_args); + struct netdev_obj *netdev_obj; + + if (!args) { + args = &empty_args; + } + + netdev_obj = shash_find_data(&netdev_obj_shash, name); + if (!netdev_obj) { + return ENODEV; + } + + if (netdev_obj->class->reconfigure) { + return netdev_obj->class->reconfigure(netdev_obj, args); + } + + return 0; +} + /* Opens the network device named 'name' (e.g. "eth0") and returns zero if * successful, otherwise a positive errno value. On success, sets '*netdevp' * to the new network device, otherwise to null. @@ -133,37 +223,43 @@ netdev_wait(void) * the 'enum netdev_pseudo_ethertype' values to receive frames in one of those * categories. */ int -netdev_open(const char *name_, int ethertype, struct netdev **netdevp) +netdev_open(const char *name, int ethertype, struct netdev **netdevp) { - char *name = xstrdup(name_); - char *prefix, *suffix, *colon; + struct netdev_obj *netdev_obj; struct netdev *netdev = NULL; int error; int i; netdev_initialize(); - colon = strchr(name, ':'); - if (colon) { - *colon = '\0'; - prefix = name; - suffix = colon + 1; + + netdev_obj = shash_find_data(&netdev_obj_shash, name); + if (netdev_obj) { + error = netdev_obj->class->open(name, ethertype, &netdev); } else { - prefix = ""; - suffix = name; - } + /* Default to "system". */ + error = EAFNOSUPPORT; + for (i = 0; i < n_netdev_classes; i++) { + const struct netdev_class *class = netdev_classes[i]; + if (!strcmp(class->type, "system")) { + struct shash empty_args = SHASH_INITIALIZER(&empty_args); - for (i = 0; i < n_netdev_classes; i++) { - const struct netdev_class *class = netdev_classes[i]; - if (!strcmp(prefix, class->prefix)) { - error = class->open(name_, suffix, ethertype, &netdev); - goto exit; + /* Dynamically create the netdev object, but indicate + * that it should be destroyed when the the last user + * closes its handle. */ + error = class->create(name, "system", &empty_args, false); + if (!error) { + error = class->open(name, ethertype, &netdev); + netdev_obj = shash_find_data(&netdev_obj_shash, name); + } + break; + } } } - error = EAFNOSUPPORT; + if (!error) { + netdev_obj->ref_cnt++; + } -exit: *netdevp = error ? NULL : netdev; - free(name); return error; } @@ -172,9 +268,24 @@ void netdev_close(struct netdev *netdev) { if (netdev) { - char *name; + struct netdev_obj *netdev_obj; + char *name = netdev->name; int error; + netdev_obj = shash_find_data(&netdev_obj_shash, name); + assert(netdev_obj); + if (netdev_obj->ref_cnt > 0) { + netdev_obj->ref_cnt--; + } else { + VLOG_WARN("netdev %s closed too many times", name); + } + + /* If the reference count for the netdev object is zero, and it + * was dynamically created by netdev_open(), destroy it. */ + if (!netdev_obj->ref_cnt && !netdev_obj->created) { + netdev_destroy(name); + } + /* Restore flags that we changed, if any. */ fatal_signal_block(); error = restore_flags(netdev); @@ -182,11 +293,10 @@ netdev_close(struct netdev *netdev) fatal_signal_unblock(); if (error) { VLOG_WARN("failed to restore network device flags on %s: %s", - netdev->name, strerror(error)); + name, strerror(error)); } /* Free. */ - name = netdev->name; netdev->class->close(netdev); free(name); } @@ -231,7 +341,7 @@ netdev_enumerate(struct svec *svec) int retval = class->enumerate(svec); if (retval) { VLOG_WARN("failed to enumerate %s network devices: %s", - class->name, strerror(retval)); + class->type, strerror(retval)); if (!error) { error = retval; } @@ -708,6 +818,24 @@ exit: return netdev; } +/* Initializes 'netdev_obj' as a netdev object named 'name' of the + * specified 'class'. + * + * This function adds 'netdev_obj' to a netdev-owned shash, so it is + * very important that 'netdev_obj' only be freed after calling + * netdev_destroy(). */ +void +netdev_obj_init(struct netdev_obj *netdev_obj, const char *name, + const struct netdev_class *class, bool created) +{ + assert(!shash_find(&netdev_obj_shash, name)); + + netdev_obj->class = class; + netdev_obj->ref_cnt = 0; + netdev_obj->created = created; + shash_add(&netdev_obj_shash, name, netdev_obj); +} + /* Initializes 'netdev' as a netdev named 'name' of the specified 'class'. * * This function adds 'netdev' to a netdev-owned linked list, so it is very @@ -723,6 +851,14 @@ netdev_init(struct netdev *netdev, const char *name, list_push_back(&netdev_list, &netdev->node); } +/* Returns the class type of 'netdev'. + * + * The caller must not free the returned value. */ +const char *netdev_get_type(const struct netdev *netdev) +{ + return netdev->class->type; +} + /* Initializes 'notifier' as a netdev notifier for 'netdev', for which * notification will consist of calling 'cb', with auxiliary data 'aux'. */ void diff --git a/lib/netdev.h b/lib/netdev.h index b87d5b999..b8c7dfb49 100644 --- a/lib/netdev.h +++ b/lib/netdev.h @@ -30,6 +30,7 @@ struct ofpbuf; struct in_addr; struct in6_addr; +struct shash; struct svec; enum netdev_flags { @@ -81,6 +82,11 @@ int netdev_initialize(void); void netdev_run(void); void netdev_wait(void); +int netdev_create(const char *name, const char *type, + const struct shash *args); +int netdev_destroy(const char *name); +int netdev_reconfigure(const char *name, const struct shash *args); + int netdev_open(const char *name, int ethertype, struct netdev **); void netdev_close(struct netdev *); diff --git a/vswitchd/bridge.c b/vswitchd/bridge.c index e6ea1a745..dbcf31250 100644 --- a/vswitchd/bridge.c +++ b/vswitchd/bridge.c @@ -50,6 +50,7 @@ #include "port-array.h" #include "proc-net-compat.h" #include "process.h" +#include "shash.h" #include "socket-util.h" #include "stp.h" #include "svec.h" @@ -369,6 +370,70 @@ bridge_configure_ssl(void) } #endif +/* Attempt to create the network device 'iface_name' through the netdev + * library. */ +static int +set_up_iface(const char *iface_name, bool create) +{ + const char *type; + const char *arg; + struct svec arg_svec; + struct shash args; + int error; + size_t i; + + /* If a type is not explicitly declared, then assume it's an existing + * "system" device. */ + type = cfg_get_string(0, "iface.%s.type", iface_name); + if (!type || !strcmp(type, "system")) { + return 0; + } + + svec_init(&arg_svec); + cfg_get_subsections(&arg_svec, "iface.%s.args", iface_name); + + shash_init(&args); + SVEC_FOR_EACH (i, arg, &arg_svec) { + const char *value; + + value = cfg_get_string(0, "iface.%s.args.%s", iface_name, arg); + if (value) { + shash_add(&args, arg, xstrdup(value)); + } + } + + if (create) { + error = netdev_create(iface_name, type, &args); + } else { + /* xxx Check to make sure that the type hasn't changed. */ + error = netdev_reconfigure(iface_name, &args); + } + + svec_destroy(&arg_svec); + shash_destroy(&args); + + return error; +} + +static int +create_iface(const char *iface_name) +{ + return set_up_iface(iface_name, true); +} + +static int +reconfigure_iface(const char *iface_name) +{ + return set_up_iface(iface_name, false); +} + +static void +destroy_iface(const char *iface_name) +{ + netdev_destroy(iface_name); +} + + /* iterate_and_prune_ifaces() callback function that opens the network device * for 'iface', if it is not already open, and retrieves the interface's MAC * address and carrier status. */ @@ -524,6 +589,7 @@ bridge_reconfigure(void) p->devname, dpif_name(br->dpif), strerror(retval)); } + destroy_iface(p->devname); } } svec_destroy(&want_ifaces); @@ -544,11 +610,25 @@ bridge_reconfigure(void) bridge_get_all_ifaces(br, &want_ifaces); svec_diff(&want_ifaces, &cur_ifaces, &add_ifaces, NULL, NULL); + for (i = 0; i < cur_ifaces.n; i++) { + const char *if_name = cur_ifaces.names[i]; + reconfigure_iface(if_name); + } + for (i = 0; i < add_ifaces.n; i++) { const char *if_name = add_ifaces.names[i]; bool internal; int error; + /* Attempt to create the network interface in case it + * doesn't exist yet. */ + error = create_iface(if_name); + if (error) { + VLOG_WARN("could not create iface %s: %s\n", if_name, + strerror(error)); + continue; + } + /* Add to datapath. */ internal = iface_is_internal(br, if_name); error = dpif_port_add(br->dpif, if_name,