/* Copyright (c) 2009 Nicira Networks
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
*/
#include
#include "proc-net-compat.h"
#include
#include
#include
#include
#include
#include "dynamic-string.h"
#include "hash.h"
#include "netlink-protocol.h"
#include "netlink.h"
#include "ofpbuf.h"
#include "openvswitch/brcompat-netlink.h"
#include "hmap.h"
#include "shash.h"
#include "svec.h"
#define THIS_MODULE VLM_proc_net_compat
#include "vlog.h"
/* Netlink socket to bridge compatibility kernel module. */
static struct nl_sock *brc_sock;
/* The Generic Netlink family number used for bridge compatibility. */
static int brc_family = 0;
/* Rate limiting for log messages. */
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5);
static void flush_dir(const char *dir);
static int set_proc_file(const char *dir, const char *file, const char *data);
/* Initializes the /proc/net compatibility layer. Returns 0 if successful,
* otherwise a positive errno value. */
int
proc_net_compat_init(void)
{
if (!brc_sock) {
int retval = nl_lookup_genl_family(BRC_GENL_FAMILY_NAME, &brc_family);
if (retval) {
return retval;
}
retval = nl_sock_create(NETLINK_GENERIC, 0, 0, 0, &brc_sock);
if (retval) {
return retval;
}
flush_dir("/proc/net/vlan");
flush_dir("/proc/net/bonding");
}
return 0;
}
static int
set_proc_file(const char *dir, const char *file, const char *data)
{
struct ofpbuf request, *reply;
int retval;
ofpbuf_init(&request, 0);
nl_msg_put_genlmsghdr(&request, brc_sock, 1024, brc_family, NLM_F_REQUEST,
BRC_GENL_C_SET_PROC, 1);
nl_msg_put_string(&request, BRC_GENL_A_PROC_DIR, dir);
nl_msg_put_string(&request, BRC_GENL_A_PROC_NAME, file);
if (data) {
nl_msg_put_string(&request, BRC_GENL_A_PROC_DATA, data);
}
retval = nl_sock_transact(brc_sock, &request, &reply);
ofpbuf_uninit(&request);
ofpbuf_delete(reply);
if (retval) {
VLOG_WARN_RL(&rl, "failed to %s /proc/%s/%s (%s)",
data ? "update" : "remove", dir, file, strerror(retval));
}
return retval;
}
static void
flush_dir(const char *dir)
{
const char *subdir;
struct dirent *de;
DIR *stream;
assert(!memcmp(dir, "/proc/", 6));
subdir = dir + 6;
stream = opendir(dir);
if (!stream) {
if (errno != ENOENT) {
VLOG_WARN_RL(&rl, "%s: open failed (%s)", dir, strerror(errno));
}
return;
}
while ((de = readdir(stream)) != NULL) {
if (strcmp(de->d_name, ".") && strcmp(de->d_name, "..")) {
set_proc_file(subdir, de->d_name, NULL);
}
}
closedir(stream);
}
/* If 'bond' is nonnull, creates a file in /proc/net/bonding for a bond with
* the given 'name' and the details in 'bond'. If 'bond' is null, deletes
* the /proc/net/bonding file with the given 'name'.
*
* This function has no effect unless proc_net_compat_init() has been
* called. */
void
proc_net_compat_update_bond(const char *name, const struct compat_bond *bond)
{
struct ds ds;
int i;
if (!brc_sock) {
return;
}
if (!bond) {
set_proc_file("net/bonding", name, NULL);
return;
}
ds_init(&ds);
ds_put_format(
&ds,
"Ethernet Channel Bonding Driver: ovs-vswitchd "
VERSION BUILDNR" ("__DATE__" "__TIME__")\n"
"Bonding Mode: source load balancing\n"
"Primary Slave: None\n"
"Currently Active Slave: None\n"
"MII Status: %s\n"
"MII Polling Interval (ms): 100\n"
"Up Delay (ms): %d\n"
"Down Delay (ms): %d\n"
"\n"
"Source load balancing info:\n",
bond->up ? "up" : "down", bond->updelay, bond->downdelay);
for (i = 0; i < bond->n_slaves; i++) {
const struct compat_bond_slave *slave = &bond->slaves[i];
ds_put_format(
&ds,
"\n"
"Slave Interface: %s\n"
"MII Status: %s\n"
"Link Failure Count: 0\n"
"Permanent HW addr: "ETH_ADDR_FMT"\n",
slave->name, slave->up ? "up" : "down",
ETH_ADDR_ARGS(slave->mac));
}
set_proc_file("net/bonding", name, ds_cstr(&ds));
ds_destroy(&ds);
}
/* /proc/net/vlan compatibility.
*
* This is much more complex than I expected it to be. */
struct compat_vlan {
/* Hash key. */
struct hmap_node trunk_node; /* Hash map node. */
char *trunk_dev; /* Name of trunk network device. */
int vid; /* VLAN number. */
/* Auxiliary data. */
char *vlan_dev; /* sprintf("%s.%d", trunk_dev, vid); */
struct svec tagged_devs; /* Name of tagged network device(s). */
};
/* Current set of VLAN devices, indexed two different ways. */
static struct hmap vlans_by_trunk = HMAP_INITIALIZER(&vlans_by_trunk);
static struct shash vlans_by_tagged = SHASH_INITIALIZER(&vlans_by_tagged);
static bool remove_tagged_dev(struct shash_node *, const char *tagged_dev);
static void update_vlan_config(void);
static void set_vlan_proc_file(const struct compat_vlan *);
static uint32_t hash_vlan(const char *trunk_dev, uint32_t vid);
/* Updates the /proc/net/vlan compatibility layer's idea of what trunk device
* and VLAN the given 'tagged_dev' is associated with. If 'tagged_dev' has an
* implicit VLAN tag, then 'trunk_dev' should be the name of a network device
* on the same bridge that trunks that VLAN, and 'vid' should be the VLAN tag
* number. If 'tagged_dev' does not have an implicit VLAN tag, then
* 'trunk_dev' should be NULL and 'vid' should be -1.
*
* This function has no effect unless proc_net_compat_init() has been
* called. */
void
proc_net_compat_update_vlan(const char *tagged_dev, const char *trunk_dev,
int vid)
{
struct compat_vlan *vlan;
struct shash_node *node;
if (!brc_sock) {
return;
}
/* Find the compat_vlan that we currently have for 'tagged_dev' (if
* any). */
node = shash_find(&vlans_by_tagged, tagged_dev);
vlan = node ? node->data : NULL;
if (vid <= 0 || !trunk_dev) {
if (vlan) {
if (remove_tagged_dev(node, tagged_dev)) {
update_vlan_config();
}
}
} else {
if (vlan) {
if (!strcmp(trunk_dev, vlan->trunk_dev) && vid == vlan->vid) {
/* No change. */
return;
} else {
/* 'tagged_dev' is attached to the wrong compat_vlan. Start
* by removing it from that one. */
remove_tagged_dev(node, tagged_dev);
node = NULL;
vlan = NULL;
}
}
/* 'tagged_dev' is not attached to any compat_vlan. Find the
* compat_vlan corresponding to (trunk_dev,vid) to attach it to, or
* create a new compat_vlan if none exists for (trunk_dev,vid). */
HMAP_FOR_EACH_WITH_HASH (vlan, struct compat_vlan, trunk_node,
hash_vlan(trunk_dev, vid),
&vlans_by_trunk) {
if (!strcmp(trunk_dev, vlan->trunk_dev) && vid == vlan->vid) {
break;
}
}
if (!vlan) {
/* Create a new compat_vlan for (trunk_dev,vid). */
vlan = xcalloc(1, sizeof *vlan);
vlan->trunk_dev = xstrdup(trunk_dev);
vlan->vid = vid;
vlan->vlan_dev = xasprintf("%s.%d", trunk_dev, vid);
svec_init(&vlan->tagged_devs);
hmap_insert(&vlans_by_trunk, &vlan->trunk_node,
hash_vlan(trunk_dev, vid));
set_vlan_proc_file(vlan);
}
/* Attach 'tagged_dev' to 'vlan'. */
svec_add(&vlan->tagged_devs, tagged_dev);
shash_add(&vlans_by_tagged, tagged_dev, vlan);
svec_sort(&vlan->tagged_devs);
update_vlan_config();
}
}
/* Remove 'tagged_dev' from the compat_vlan in 'node'. If that causes the
* compat_vlan to have no tagged_devs left, destroy the compat_vlan too. */
static bool
remove_tagged_dev(struct shash_node *node, const char *tagged_dev)
{
struct compat_vlan *vlan = node->data;
svec_del(&vlan->tagged_devs, tagged_dev);
shash_delete(&vlans_by_tagged, node);
if (!vlan->tagged_devs.n) {
set_proc_file("net/vlan", vlan->vlan_dev, NULL);
hmap_remove(&vlans_by_trunk, &vlan->trunk_node);
svec_destroy(&vlan->tagged_devs);
free(vlan->trunk_dev);
free(vlan->vlan_dev);
free(vlan);
return true;
}
return false;
}
/* Returns a hash value for (trunk_dev,vid). */
static uint32_t
hash_vlan(const char *trunk_dev, uint32_t vid)
{
return hash_int(vid, hash_string(trunk_dev, 0));
}
/* Update /proc/net/vlan/ for 'vlan'. */
static void
set_vlan_proc_file(const struct compat_vlan *vlan)
{
struct ds ds;
ds_init(&ds);
ds_put_format(
&ds,
"%s VID: %d\t REORDER_HDR: 1 dev->priv_flags: 81\n"
" total frames received 0\n"
" total bytes received 0\n"
" Broadcast/Multicast Rcvd 0\n"
"\n"
" total frames transmitted 0\n"
" total bytes transmitted 0\n"
" total headroom inc 0\n"
" total encap on xmit 0\n"
"Device: %s\n"
"INGRESS priority mappings: 0:0 1:0 2:0 3:0 4:0 5:0 6:0 7:0\n"
"EGRESSS priority Mappings: \n",
vlan->vlan_dev, vlan->vid, vlan->trunk_dev);
set_proc_file("net/vlan", vlan->vlan_dev, ds_cstr(&ds));
ds_destroy(&ds);
}
/* Update /proc/net/vlan/config. */
static void
update_vlan_config(void)
{
struct compat_vlan *vlan;
struct ds ds;
ds_init(&ds);
ds_put_cstr(&ds, "VLAN Dev name | VLAN ID\n"
"Name-Type: VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD\n");
HMAP_FOR_EACH (vlan, struct compat_vlan, trunk_node, &vlans_by_trunk) {
ds_put_format(&ds, "%-15s| %d | %s\n",
vlan->vlan_dev, vlan->vid, vlan->trunk_dev);
}
set_proc_file("net/vlan", "config", ds_cstr(&ds));
ds_destroy(&ds);
}