| /* 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; |
| } |