blob: 070969e689e5d4bba3c61dea1beacc7f8864dab1 [file] [log] [blame]
/* cdu_seq_file.c
*
* This file defines client data usage proc file stats reporting.
*
* Author: Cradlepoint Technology, Inc. <source@cradlepoint.com>
* Adrian Sitterle <asitterle@cradlepoint.com>
*
* Copyright (C) 2019 Cradlepoint Technology, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* 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/version.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/jhash.h>
#include <linux/etherdevice.h>
#include <linux/proc_fs.h>
#include <linux/netfilter/x_tables.h>
#include <net/netfilter/nf_conntrack.h>
#include <net/netfilter/nf_conntrack_core.h>
#include "cdu_seq_file.h"
#include "cdu_db.h"
static void *seq_start(struct seq_file *s, loff_t *pos)
{
unsigned int *bucket;
spin_lock_bh(&cdu_db_hash_lock);
if (*pos >= USAGE_HASH_BITS)
return NULL;
bucket = kmalloc(sizeof(unsigned int), GFP_ATOMIC);
if (!bucket)
return ERR_PTR(-ENOMEM);
*bucket = *pos;
return bucket;
}
static void *seq_next(struct seq_file *s, void *v, loff_t *pos)
{
unsigned int *bucket = (unsigned int *)v;
*pos = ++(*bucket);
if (*pos >= USAGE_HASH_BITS) {
kfree(v);
return NULL;
}
return bucket;
}
static void seq_stop(struct seq_file *s, void *v)
{
unsigned int *bucket = (unsigned int *)v;
if (!IS_ERR_OR_NULL(bucket))
kfree(bucket);
spin_unlock_bh(&cdu_db_hash_lock);
}
#define CDU_DEBUGIMEOUT 300
static int seq_show(struct seq_file *s, void *v)
{
struct usage_htable *htable = s->private;
unsigned int *bucket = (unsigned int *)v;
struct usage_entry *obj;
struct hlist_node *n;
struct timespec64 now;
struct usage_stats upload, download;
if (!hlist_empty(&cdu_db_usage_hash[*bucket])) {
ktime_get_boottime_ts64(&now);
hlist_for_each_entry(obj, &cdu_db_usage_hash[*bucket], node) {
cdu_db_get_stats(&upload, &download, obj);
CDU_DEBUG(
"MAC = %pm, IP = %pI4, IP6 = %pI6, Up_Bytes = %llu, Up_Packets = %llu, Down_Bytes = %llu, Down_Packets = %llu, Last_Time = %lu, First_Time = %lu, Connect_Time = %lu, Timeout = %s\n",
obj->mac, &obj->addr.ip, &obj->addr.ip6,
upload.bytes, upload.packets,
download.bytes, download.packets,
obj->last_time, obj->first_time, obj->last_time - obj->first_time,
(CDU_DEBUGIMEOUT + obj->last_time) < (unsigned long) now.tv_sec ? "True" : "False");
seq_printf(s, "MAC = %pM, ", obj->mac);
if (obj->family == NFPROTO_IPV4)
seq_printf(s, "IP = %pI4, ", &obj->addr.ip);
if (obj->family == NFPROTO_IPV6)
seq_printf(s, "IP = %pI6, ", &obj->addr.ip6);
seq_printf(s, "Up_Bytes = %llu, Up_Packets = %llu, ", upload.bytes, upload.packets);
seq_printf(s, "Down_Bytes = %llu, Down_Packets = %llu, ", download.bytes, download.packets);
seq_printf(s, "Last_Time = %lu, First_Time = %lu, Connect_Time = %lu, ",
obj->last_time, obj->first_time, obj->last_time - obj->first_time);
seq_printf(s, "Timeout = %s\n", (CDU_DEBUGIMEOUT + obj->last_time) < (unsigned long) now.tv_sec ? "True" : "False");
/* If we've overflowed our buffer, let's not bother overflowing it
* more with additional entries. We'll get called again with a
* larger buffer. */
if (seq_has_overflowed(s))
return 0;
}
/* We've managed to fill the buffer with all the information, and since
* we don't expect to be called again (since we've not overflowed), we
* can safely clear the stats out of this bucket */
hlist_for_each_entry_safe(obj, n, &cdu_db_usage_hash[*bucket], node) {
if (htable->reset == 1) {
if ((CDU_DEBUGIMEOUT + obj->last_time) < (unsigned long) now.tv_sec) {
cdu_db_remove(obj);
} else {
cdu_db_clear_stats(obj, CDU_DB_UPDATE_PERMA_SET);
cdu_db_clear_stats(obj, CDU_DB_UPDATE_EPHEM_SET);
}
} else {
cdu_db_clear_stats(obj, CDU_DB_UPDATE_EPHEM_SET);
}
}
}
return 0;
}
static const struct seq_operations usage_seq_ops = {
.start = seq_start,
.next = seq_next,
.stop = seq_stop,
.show = seq_show,
};
static int usage_open(struct inode *inode, struct file *file)
{
struct seq_file *s;
int ret = seq_open(file, &usage_seq_ops);
if (!ret) {
s = file->private_data;
s->private = PDE_DATA(inode);
cdu_db_read_conn(((struct usage_htable *)(s->private))->reset);
}
return ret;
}
static const struct file_operations usage_file_ops = {
.owner = THIS_MODULE,
.open = usage_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
};
static struct proc_dir_entry *ipt_usage_procdir;
void cdu_seq_file_uninit(void)
{
if (ipt_usage_procdir) {
proc_remove(ipt_usage_procdir);
ipt_usage_procdir = NULL;
}
}
struct usage_htable *cdu_seq_file_create(const char *name, const u_int8_t readreset)
{
struct usage_htable *priv;
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (priv == NULL)
return NULL;
priv->usecount = 0;
priv->reset = readreset == 1;
priv->name = kstrdup(name, GFP_KERNEL);
if (!priv->name) {
kfree(priv);
return NULL;
}
priv->pde = proc_create_data(name, 0600, ipt_usage_procdir, &usage_file_ops, priv);
if (priv->pde == NULL) {
kfree(priv->name);
kfree(priv);
return NULL;
}
return priv;
}
void cdu_seq_file_destroy(const struct usage_htable *priv)
{
/* if destroy is called, empty the list */
cdu_db_flush();
if (priv) {
kfree(priv->name);
if (priv->pde)
proc_remove(priv->pde);
kfree(priv);
}
}
int cdu_seq_file_init(void)
{
ipt_usage_procdir = proc_mkdir("xt_usage", init_net.proc_net);
if (!ipt_usage_procdir) {
CDU_ERROR("proc_mkdir failed.\n");
return -ENOMEM;
}
return 0;
}