1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411
|
/*
* 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 <config.h>
#include "vlandev.h"
#include <errno.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#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 <linux/if_vlan.h>
#include <linux/sockios.h>
#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);
}
|