From 025e874a9cc814820aadba7083fdbcfbaa959259 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Wed, 9 Nov 2011 16:16:10 -0800 Subject: [PATCH] vlandev: New library for working with Linux VLAN devices. --- lib/automake.mk | 2 + lib/netdev-linux.c | 8 ++ lib/netdev-linux.h | 1 + lib/vlandev.c | 247 +++++++++++++++++++++++++++++++++++++++++++++ lib/vlandev.h | 52 ++++++++++ 5 files changed, 310 insertions(+) create mode 100644 lib/vlandev.c create mode 100644 lib/vlandev.h diff --git a/lib/automake.mk b/lib/automake.mk index da95acb9c..c0cc9065a 100644 --- a/lib/automake.mk +++ b/lib/automake.mk @@ -181,6 +181,8 @@ lib_libopenvswitch_a_SOURCES = \ lib/vconn.h \ lib/vlan-bitmap.c \ lib/vlan-bitmap.h \ + lib/vlandev.c \ + lib/vlandev.h \ lib/vlog.c \ lib/vlog.h nodist_lib_libopenvswitch_a_SOURCES = \ diff --git a/lib/netdev-linux.c b/lib/netdev-linux.c index 134d99b41..44167da4a 100644 --- a/lib/netdev-linux.c +++ b/lib/netdev-linux.c @@ -3932,6 +3932,14 @@ tc_calc_buffer(unsigned int Bps, int mtu, uint64_t burst_bytes) /* Linux-only functions declared in netdev-linux.h */ +/* Returns a fd for an AF_INET socket or a negative errno value. */ +int +netdev_linux_get_af_inet_sock(void) +{ + int error = netdev_linux_init(); + return error ? -error : af_inet_sock; +} + /* Modifies the 'flag' bit in ethtool's flags field for 'netdev'. If * 'enable' is true, the bit is set. Otherwise, it is cleared. */ int diff --git a/lib/netdev-linux.h b/lib/netdev-linux.h index c8caa4e3c..21a2db258 100644 --- a/lib/netdev-linux.h +++ b/lib/netdev-linux.h @@ -29,5 +29,6 @@ struct rtnl_link_stats; int netdev_linux_ethtool_set_flag(struct netdev *netdev, uint32_t flag, const char *flag_name, bool enable); +int netdev_linux_get_af_inet_sock(void); #endif /* netdev-linux.h */ diff --git a/lib/vlandev.c b/lib/vlandev.c new file mode 100644 index 000000000..736b77947 --- /dev/null +++ b/lib/vlandev.c @@ -0,0 +1,247 @@ +/* + * Copyright (c) 2011 Nicira Networks. + * + * 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 "hash.h" +#include "rtnetlink-link.h" +#include "shash.h" +#include "vlog.h" + +VLOG_DEFINE_THIS_MODULE(vlandev); + +#ifdef __linux__ +#include +#include +#include "netdev-linux.h" + +static struct nln_notifier *vlan_cache_notifier; +static struct shash vlan_devs = SHASH_INITIALIZER(&vlan_devs); +static struct shash vlan_real_devs = SHASH_INITIALIZER(&vlan_real_devs); +static bool cache_valid; + +static void +vlan_cache_cb(const struct rtnetlink_link_change *change OVS_UNUSED, + void *aux OVS_UNUSED) +{ + cache_valid = false; +} + +int +vlandev_refresh(void) +{ + const char *fn = "/proc/net/vlan/config"; + struct shash_node *node; + 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; + } + + /* Free old cache. + * + * The 'name' members point to strings owned by the "shash"es so we do not + * free them ourselves. */ + 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); + + /* 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, strerror(error)); + return error; + } + + while (fgets(line, sizeof line, stream)) { + char vlan_dev[16], real_dev[16]; + int vid; + + if (sscanf(line, "%15[^ |] | %d | %15s", vlan_dev, &vid, real_dev) == 3 + && vid >= 0 && vid <= 4095 + && !shash_find(&vlan_devs, vlan_dev)) { + struct vlan_real_dev *vrd; + struct vlan_dev *vd; + + 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); + } + + vd = xmalloc(sizeof *vd); + hmap_insert(&vrd->vlan_devs, &vd->hmap_node, hash_int(vid, 0)); + vd->name = xstrdup(vlan_dev); + vd->vid = vid; + vd->real_dev = vrd; + shash_add_nocopy(&vlan_devs, vd->name, vd); + } + } + fclose(stream); + + cache_valid = true; + return 0; +} + +struct shash * +vlandev_get_real_devs(void) +{ + return &vlan_real_devs; +} + +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; +} + +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; + int sock; + + via->cmd = cmd; + ovs_strlcpy(via->device1, netdev_name, sizeof via->device1); + + sock = netdev_linux_get_af_inet_sock(); + if (sock < 0) { + return -sock; + } + + error = ioctl(sock, SIOCSIFVLAN, via) < 0 ? errno : 0; + if (error) { + VLOG_WARN_RL(&rl, "%s: VLAN ioctl %s failed (%s)", + netdev_name, cmd_name, strerror(error)); + } + return error; +} + +int +vlandev_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; +} + +int +vlandev_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; +} +#else /* !__linux__ */ +/* Stubs. */ + +int +vlandev_refresh(void) +{ + return 0; +} + +struct shash * +vlandev_get_real_devs(void) +{ + static struct shash vlan_real_devs = SHASH_INITIALIZER(&vlan_real_devs); + + return &vlan_real_devs; +} + +const char * +vlandev_get_name(const char *real_dev_name OVS_UNUSED, int vid OVS_UNUSED) +{ + return NULL; +} + +int +vlandev_add(const char *real_dev OVS_UNUSED, int vid OVS_UNUSED) +{ + VLOG_ERR("not supported on non-Linux platform"); + return EOPNOTSUPP; +} + +int +vlandev_del(const char *vlan_dev OVS_UNUSED) +{ + VLOG_ERR("not supported on non-Linux platform"); + return EOPNOTSUPP; +} +#endif diff --git a/lib/vlandev.h b/lib/vlandev.h new file mode 100644 index 000000000..cba0fa412 --- /dev/null +++ b/lib/vlandev.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2011 Nicira Networks. + * + * 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. + */ + +#ifndef VLANDEV_H +#define VLANDEV_H 1 + +#include "hmap.h" + +/* Linux VLAN device support (e.g. "eth0.10" for VLAN 10.) + * + * This is deprecated. It is only for compatibility with broken device + * drivers in old versions of Linux that do not properly support VLANs when + * VLAN devices are not used. When broken device drivers are no longer in + * widespread use, we will delete these interfaces. */ + +/* A VLAN device (e.g. "eth0.10" for VLAN 10 on eth0). */ +struct vlan_dev { + struct vlan_real_dev *real_dev; /* Parent, e.g. "eth0". */ + struct hmap_node hmap_node; /* In vlan_real_dev's "vlan_devs" map. */ + char *name; /* VLAN device name, e.g. "eth0.10". */ + int vid; /* VLAN ID, e.g. 10. */ +}; + +/* A device that has VLAN devices broken out of it. */ +struct vlan_real_dev { + char *name; /* Name, e.g. "eth0". */ + struct hmap vlan_devs; /* All child VLAN devices, hashed by VID. */ +}; + +int vlandev_refresh(void); + +struct shash *vlandev_get_real_devs(void); + +const char *vlandev_get_name(const char *real_dev_name, int vid); + +int vlandev_add(const char *real_dev, int vid); +int vlandev_del(const char *vlan_dev); + +#endif /* vlandev.h */ -- 2.43.0