/* * Copyright (c) 2011, 2013, 2014 Nicira, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include "vlandev.h" #include #include #include #include #include "dummy.h" #include "hash.h" #include "shash.h" #include "socket-util.h" #include "vlog.h" VLOG_DEFINE_THIS_MODULE(vlandev); /* A vlandev implementation. */ struct vlandev_class { int (*vd_refresh)(void); int (*vd_add)(const char *real_dev, int vid); int (*vd_del)(const char *vlan_dev); }; #ifdef __linux__ static const struct vlandev_class vlandev_linux_class; #endif static const struct vlandev_class vlandev_stub_class; static const struct vlandev_class vlandev_dummy_class; /* The in-use vlandev implementation. */ static const struct vlandev_class *vd_class; /* Maps from a VLAN device name (e.g. "eth0.10") to struct vlan_dev. */ static struct shash vlan_devs = SHASH_INITIALIZER(&vlan_devs); /* Maps from a VLAN real device name (e.g. "eth0") to struct vlan_real_dev. */ static struct shash vlan_real_devs = SHASH_INITIALIZER(&vlan_real_devs); static int vlandev_add__(const char *vlan_dev, const char *real_dev, int vid); static int vlandev_del__(const char *vlan_dev); static void vlandev_clear__(void); static const struct vlandev_class * vlandev_get_class(void) { if (!vd_class) { #if __linux__ vd_class = &vlandev_linux_class; #else vd_class = &vlandev_stub_class; #endif } return vd_class; } /* On Linux, the default implementation of VLAN devices creates and destroys * Linux VLAN devices. On other OSess, the default implementation is a * nonfunctional stub. In either case, this function replaces this default * implementation by a "dummy" implementation that simply reports back whatever * the client sets up with vlandev_add() and vlandev_del(). * * Don't call this function directly; use dummy_enable() from dummy.h. */ void vlandev_dummy_enable(void) { if (vd_class != &vlandev_dummy_class) { vd_class = &vlandev_dummy_class; vlandev_clear__(); } } /* Creates a new VLAN device for VLAN 'vid' on top of real Ethernet device * 'real_dev'. Returns 0 if successful, otherwise a positive errno value. On * OSes other than Linux, in the absence of dummies (see * vlandev_dummy_enable()), this always fails. * * The name of the new VLAN device is not easily predictable, because Linux * provides multiple naming schemes, does not allow the client to specify a * name, and does not directly report the new VLAN device's name. Use * vlandev_refresh() then vlandev_get_name() to find out the new VLAN device's * name,. */ int vlandev_add(const char *real_dev, int vid) { return vlandev_get_class()->vd_add(real_dev, vid); } /* Deletes the VLAN device named 'vlan_dev'. Returns 0 if successful, * otherwise a positive errno value. On OSes other than Linux, in the absence * of dummies (see vlandev_dummy_enable()), this always fails. */ int vlandev_del(const char *vlan_dev) { return vlandev_get_class()->vd_del(vlan_dev); } /* Refreshes the cache of real device to VLAN device mappings reported by * vlandev_get_real_devs() and vlandev_get_name(). Without calling this * function, changes made by vlandev_add() and vlandev_del() may not be * reflected by vlandev_get_real_devs() and vlandev_get_name() output. */ int vlandev_refresh(void) { const struct vlandev_class *class = vlandev_get_class(); return class->vd_refresh ? class->vd_refresh() : 0; } /* Returns a shash mapping from the name of real Ethernet devices used as the * basis of VLAN devices to struct vlan_real_devs. The caller must not modify * or free anything in the returned shash. * * Changes made by vlandev_add() and vlandev_del() may not be reflected in this * function's output without an intervening call to vlandev_refresh(). */ struct shash * vlandev_get_real_devs(void) { return &vlan_real_devs; } /* Returns the name of the VLAN device for VLAN 'vid' on top of * 'real_dev_name', or NULL if there is no such VLAN device. * * Changes made by vlandev_add() and vlandev_del() may not be reflected in this * function's output without an intervening call to vlandev_refresh(). */ const char * vlandev_get_name(const char *real_dev_name, int vid) { const struct vlan_real_dev *real_dev; real_dev = shash_find_data(&vlan_real_devs, real_dev_name); if (real_dev) { const struct vlan_dev *vlan_dev; HMAP_FOR_EACH_WITH_HASH (vlan_dev, hmap_node, hash_int(vid, 0), &real_dev->vlan_devs) { if (vlan_dev->vid == vid) { return vlan_dev->name; } } } return NULL; } /* The Linux vlandev implementation. */ #ifdef __linux__ #include "rtnetlink-link.h" #include #include #include "netdev-linux.h" static struct nln_notifier *vlan_cache_notifier; static bool cache_valid; static void vlan_cache_cb(const struct rtnetlink_link_change *change OVS_UNUSED, void *aux OVS_UNUSED) { cache_valid = false; } static int vlandev_linux_refresh(void) { const char *fn = "/proc/net/vlan/config"; char line[128]; FILE *stream; if (!vlan_cache_notifier) { vlan_cache_notifier = rtnetlink_link_notifier_create(vlan_cache_cb, NULL); if (!vlan_cache_notifier) { return EINVAL; } } if (cache_valid) { return 0; } vlandev_clear__(); /* Repopulate cache. */ stream = fopen(fn, "r"); if (!stream) { static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); int error = errno; struct stat s; if (error == ENOENT && !stat("/proc", &s)) { /* Probably the vlan module just isn't loaded, and probably that's * because no VLAN devices have been created. * * Not really an error. */ return 0; } VLOG_WARN_RL(&rl, "%s: open failed (%s)", fn, ovs_strerror(error)); return error; } while (fgets(line, sizeof line, stream)) { char vlan_dev[16], real_dev[16]; int vid; if (ovs_scan(line, "%15[^ |] | %d | %15s", vlan_dev, &vid, real_dev)) { vlandev_add__(vlan_dev, real_dev, vid); } } fclose(stream); cache_valid = true; return 0; } static int do_vlan_ioctl(const char *netdev_name, struct vlan_ioctl_args *via, int cmd, const char *cmd_name) { static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); int error; via->cmd = cmd; ovs_strlcpy(via->device1, netdev_name, sizeof via->device1); error = af_inet_ioctl(SIOCSIFVLAN, via); if (error) { VLOG_WARN_RL(&rl, "%s: VLAN ioctl %s failed (%s)", netdev_name, cmd_name, ovs_strerror(error)); } return error; } static int vlandev_linux_add(const char *real_dev, int vid) { struct vlan_ioctl_args via; int error; memset(&via, 0, sizeof via); via.u.VID = vid; error = do_vlan_ioctl(real_dev, &via, ADD_VLAN_CMD, "ADD_VLAN_CMD"); if (!error) { cache_valid = false; } return error; } static int vlandev_linux_del(const char *vlan_dev) { struct vlan_ioctl_args via; int error; memset(&via, 0, sizeof via); error = do_vlan_ioctl(vlan_dev, &via, DEL_VLAN_CMD, "DEL_VLAN_CMD"); if (!error) { cache_valid = false; } return error; } static const struct vlandev_class vlandev_linux_class = { vlandev_linux_refresh, vlandev_linux_add, vlandev_linux_del }; #endif /* Stub implementation. */ static int vlandev_stub_add(const char *real_dev OVS_UNUSED, int vid OVS_UNUSED) { VLOG_ERR("not supported on non-Linux platform"); return EOPNOTSUPP; } static int vlandev_stub_del(const char *vlan_dev OVS_UNUSED) { VLOG_ERR("not supported on non-Linux platform"); return EOPNOTSUPP; } static const struct vlandev_class OVS_UNUSED vlandev_stub_class = { NULL, /* vd_refresh */ vlandev_stub_add, vlandev_stub_del }; /* Dummy implementation. */ static int vlandev_dummy_add(const char *real_dev, int vid) { char name[IFNAMSIZ]; if (snprintf(name, sizeof name, "%s.%d", real_dev, vid) >= sizeof name) { return ENAMETOOLONG; } return vlandev_add__(name, real_dev, vid); } static int vlandev_dummy_del(const char *vlan_dev) { return vlandev_del__(vlan_dev); } static const struct vlandev_class vlandev_dummy_class = { NULL, /* vd_refresh */ vlandev_dummy_add, vlandev_dummy_del }; static int vlandev_add__(const char *vlan_dev, const char *real_dev, int vid) { uint32_t vid_hash = hash_int(vid, 0); struct vlan_real_dev *vrd; struct vlan_dev *vd; if (vid < 0 || vid > 4095) { return EINVAL; } else if (shash_find(&vlan_devs, vlan_dev)) { return EEXIST; } vrd = shash_find_data(&vlan_real_devs, real_dev); if (!vrd) { vrd = xmalloc(sizeof *vrd); vrd->name = xstrdup(real_dev); hmap_init(&vrd->vlan_devs); shash_add_nocopy(&vlan_real_devs, vrd->name, vrd); } else { HMAP_FOR_EACH_WITH_HASH (vd, hmap_node, vid_hash, &vrd->vlan_devs) { if (vd->vid == vid) { return EEXIST; } } } vd = xmalloc(sizeof *vd); hmap_insert(&vrd->vlan_devs, &vd->hmap_node, vid_hash); vd->name = xstrdup(vlan_dev); vd->vid = vid; vd->real_dev = vrd; shash_add_nocopy(&vlan_devs, vd->name, vd); return 0; } static int vlandev_del__(const char *vlan_dev) { struct shash_node *vd_node = shash_find(&vlan_devs, vlan_dev); if (vd_node) { struct vlan_dev *vd = vd_node->data; struct vlan_real_dev *vrd = vd->real_dev; hmap_remove(&vrd->vlan_devs, &vd->hmap_node); if (hmap_is_empty(&vrd->vlan_devs)) { shash_find_and_delete_assert(&vlan_real_devs, vrd->name); free(vrd); } shash_delete(&vlan_devs, vd_node); free(vd); return 0; } else { return ENOENT; } } /* Clear 'vlan_devs' and 'vlan_real_devs' in preparation for repopulating. */ static void vlandev_clear__(void) { /* We do not free the 'name' members of struct vlan_dev and struct * vlan_real_dev, because the "shash"es own them.. */ struct shash_node *node; shash_clear_free_data(&vlan_devs); SHASH_FOR_EACH (node, &vlan_real_devs) { struct vlan_real_dev *vrd = node->data; hmap_destroy(&vrd->vlan_devs); } shash_clear_free_data(&vlan_real_devs); }