235 lines
6.1 KiB
C
235 lines
6.1 KiB
C
|
/* drivers/misc/iface_stat.c
|
||
|
*
|
||
|
* Copyright (C) 2011 Google, Inc.
|
||
|
*
|
||
|
* This software is licensed under the terms of the GNU General Public
|
||
|
* License version 2, as published by the Free Software Foundation, and
|
||
|
* may be copied, distributed, and modified under those terms.
|
||
|
*
|
||
|
* 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.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
#include <linux/err.h>
|
||
|
#include <linux/init.h>
|
||
|
#include <linux/kernel.h>
|
||
|
#include <linux/in.h>
|
||
|
#include <linux/list.h>
|
||
|
#include <linux/proc_fs.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/stat.h>
|
||
|
#include <linux/netdevice.h>
|
||
|
#include <linux/inetdevice.h>
|
||
|
#include <linux/rtnetlink.h>
|
||
|
#include <linux/iface_stat.h>
|
||
|
|
||
|
static LIST_HEAD(iface_list);
|
||
|
static struct proc_dir_entry *iface_stat_procdir;
|
||
|
|
||
|
struct iface_stat {
|
||
|
struct list_head if_link;
|
||
|
char *iface_name;
|
||
|
unsigned long tx_bytes;
|
||
|
unsigned long rx_bytes;
|
||
|
unsigned long tx_packets;
|
||
|
unsigned long rx_packets;
|
||
|
bool active;
|
||
|
};
|
||
|
|
||
|
static int read_proc_entry(char *page, char **start, off_t off,
|
||
|
int count, int *eof, void *data)
|
||
|
{
|
||
|
int len;
|
||
|
unsigned long value;
|
||
|
char *p = page;
|
||
|
unsigned long *iface_entry = (unsigned long *) data;
|
||
|
if (!data)
|
||
|
return 0;
|
||
|
|
||
|
value = (unsigned long) (*iface_entry);
|
||
|
p += sprintf(p, "%lu\n", value);
|
||
|
len = (p - page) - off;
|
||
|
*eof = (len <= count) ? 1 : 0;
|
||
|
*start = page + off;
|
||
|
return len;
|
||
|
}
|
||
|
|
||
|
static int read_proc_bool_entry(char *page, char **start, off_t off,
|
||
|
int count, int *eof, void *data)
|
||
|
{
|
||
|
int len;
|
||
|
bool value;
|
||
|
char *p = page;
|
||
|
unsigned long *iface_entry = (unsigned long *) data;
|
||
|
if (!data)
|
||
|
return 0;
|
||
|
|
||
|
value = (bool) (*iface_entry);
|
||
|
p += sprintf(p, "%u\n", value ? 1 : 0);
|
||
|
len = (p - page) - off;
|
||
|
*eof = (len <= count) ? 1 : 0;
|
||
|
*start = page + off;
|
||
|
return len;
|
||
|
}
|
||
|
|
||
|
/* Find the entry for tracking the specified interface. */
|
||
|
static struct iface_stat *get_iface_stat(const char *ifname)
|
||
|
{
|
||
|
struct iface_stat *iface_entry;
|
||
|
if (!ifname)
|
||
|
return NULL;
|
||
|
|
||
|
list_for_each_entry(iface_entry, &iface_list, if_link) {
|
||
|
if (!strcmp(iface_entry->iface_name, ifname))
|
||
|
return iface_entry;
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Create a new entry for tracking the specified interface.
|
||
|
* Do nothing if the entry already exists.
|
||
|
* Called when an interface is configured with a valid IP address.
|
||
|
*/
|
||
|
void create_iface_stat(const struct in_device *in_dev)
|
||
|
{
|
||
|
struct iface_stat *new_iface;
|
||
|
struct proc_dir_entry *proc_entry;
|
||
|
const struct net_device *dev;
|
||
|
const char *ifname;
|
||
|
struct iface_stat *entry;
|
||
|
__be32 ipaddr = 0;
|
||
|
struct in_ifaddr *ifa = NULL;
|
||
|
|
||
|
ASSERT_RTNL(); /* No need for separate locking */
|
||
|
|
||
|
dev = in_dev->dev;
|
||
|
if (!dev) {
|
||
|
pr_err("iface_stat: This should never happen.\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
ifname = dev->name;
|
||
|
for (ifa = in_dev->ifa_list; ifa; ifa = ifa->ifa_next)
|
||
|
if (!strcmp(dev->name, ifa->ifa_label))
|
||
|
break;
|
||
|
|
||
|
if (ifa)
|
||
|
ipaddr = ifa->ifa_local;
|
||
|
else {
|
||
|
pr_err("iface_stat: Interface not found.\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
entry = get_iface_stat(dev->name);
|
||
|
if (entry != NULL) {
|
||
|
pr_debug("iface_stat: Already monitoring device %s\n", ifname);
|
||
|
if (ipv4_is_loopback(ipaddr)) {
|
||
|
entry->active = false;
|
||
|
pr_debug("iface_stat: Disabling monitor for "
|
||
|
"loopback device %s\n", ifname);
|
||
|
} else {
|
||
|
entry->active = true;
|
||
|
pr_debug("iface_stat: Re-enabling monitor for "
|
||
|
"device %s with ip %pI4\n",
|
||
|
ifname, &ipaddr);
|
||
|
}
|
||
|
return;
|
||
|
} else if (ipv4_is_loopback(ipaddr)) {
|
||
|
pr_debug("iface_stat: Ignoring monitor for "
|
||
|
"loopback device %s with ip %pI4\n",
|
||
|
ifname, &ipaddr);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* Create a new entry for tracking the specified interface. */
|
||
|
new_iface = kmalloc(sizeof(struct iface_stat), GFP_KERNEL);
|
||
|
if (new_iface == NULL)
|
||
|
return;
|
||
|
|
||
|
new_iface->iface_name = kmalloc((strlen(ifname)+1)*sizeof(char),
|
||
|
GFP_KERNEL);
|
||
|
if (new_iface->iface_name == NULL) {
|
||
|
kfree(new_iface);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
strcpy(new_iface->iface_name, ifname);
|
||
|
/* Counters start at 0, so we can track 4GB of network traffic. */
|
||
|
new_iface->tx_bytes = 0;
|
||
|
new_iface->rx_bytes = 0;
|
||
|
new_iface->rx_packets = 0;
|
||
|
new_iface->tx_packets = 0;
|
||
|
new_iface->active = true;
|
||
|
|
||
|
/* Append the newly created iface stat struct to the list. */
|
||
|
list_add_tail(&new_iface->if_link, &iface_list);
|
||
|
proc_entry = proc_mkdir(ifname, iface_stat_procdir);
|
||
|
|
||
|
/* Keep reference to iface_stat so we know where to read stats from. */
|
||
|
create_proc_read_entry("tx_bytes", S_IRUGO, proc_entry,
|
||
|
read_proc_entry, &new_iface->tx_bytes);
|
||
|
|
||
|
create_proc_read_entry("rx_bytes", S_IRUGO, proc_entry,
|
||
|
read_proc_entry, &new_iface->rx_bytes);
|
||
|
|
||
|
create_proc_read_entry("tx_packets", S_IRUGO, proc_entry,
|
||
|
read_proc_entry, &new_iface->tx_packets);
|
||
|
|
||
|
create_proc_read_entry("rx_packets", S_IRUGO, proc_entry,
|
||
|
read_proc_entry, &new_iface->rx_packets);
|
||
|
|
||
|
create_proc_read_entry("active", S_IRUGO, proc_entry,
|
||
|
read_proc_bool_entry, &new_iface->active);
|
||
|
|
||
|
pr_debug("iface_stat: Now monitoring device %s with ip %pI4\n",
|
||
|
ifname, &ipaddr);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Update stats for the specified interface. Do nothing if the entry
|
||
|
* does not exist (when a device was never configured with an IP address).
|
||
|
* Called when an device is being unregistered.
|
||
|
*/
|
||
|
void iface_stat_update(struct net_device *dev)
|
||
|
{
|
||
|
const struct net_device_stats *stats = dev_get_stats(dev);
|
||
|
struct iface_stat *entry;
|
||
|
|
||
|
ASSERT_RTNL();
|
||
|
|
||
|
entry = get_iface_stat(dev->name);
|
||
|
if (entry == NULL) {
|
||
|
pr_debug("iface_stat: dev %s monitor not found\n", dev->name);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (entry->active) { /* FIXME: Support for more than 4GB */
|
||
|
entry->tx_bytes += stats->tx_bytes;
|
||
|
entry->tx_packets += stats->tx_packets;
|
||
|
entry->rx_bytes += stats->rx_bytes;
|
||
|
entry->rx_packets += stats->rx_packets;
|
||
|
entry->active = false;
|
||
|
pr_debug("iface_stat: Updating stats for "
|
||
|
"dev %s which went down\n", dev->name);
|
||
|
} else
|
||
|
pr_debug("iface_stat: Didn't update stats for "
|
||
|
"dev %s which went down\n", dev->name);
|
||
|
}
|
||
|
|
||
|
static int __init iface_stat_init(void)
|
||
|
{
|
||
|
iface_stat_procdir = proc_mkdir("iface_stat", NULL);
|
||
|
if (!iface_stat_procdir) {
|
||
|
pr_err("iface_stat: failed to create proc entry\n");
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
device_initcall(iface_stat_init);
|