ipt_netquota/linux/net/ipv4/netfilter/ipt_netquota.c

595 lines
15 KiB
C

/*
* netfilter module 'ipt_netquota'
* handles one quota per IP within a larger network.
*
* 06/2004 Olaf Rempel <razzor at kopf minus tisch dot de>
*
* 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.
*
* USAGE:
* $ iptables -A FORWARD -m netquota --nq-name internet --nq-src 192.168.1.0/24 -j ACCEPT
*
* quotas can be read via procfs:
* cat /proc/net/ipt/netquota/matchname
*
* quotas can be set via procfs:
* echo "192.168.1.1 =1000000" > /proc/net/ipt_netquota/matchname
* sets quota of 192.168.1.1 to 1000000 bytes (of data within IP datagram)
*
* echo "192.168.1.1 +1000000" > /proc/net/ipt_netquota/matchname
* adds 1000000 bytes to quota of 192.168.1.1
*
* echo "192.168.1.1 -1000000" > /proc/net/ipt_netquota/matchname
* decreases quota of 192.168.1.1 by 1000000 bytes
*
* NOTE:
* netquotas can be referenced from different tables/chains.
* e.g. to limit traffic from and to a proxy using only one quota, add this:
* $iptables -A INPUT -p tcp --dport 3128 -m netquota --nq-name proxy --nq-src 192.168.1.0/24 -j ACCEPT
* $iptables -A OUTPUT -p tcp --sport 3128 -m netquota --nq-name proxy --nq-dst 192.168.1.0/24 -j ACCEPT
*
* Network and quote name must be the same to allow this.
*
* TODO:
* - module parameter check
* - read_locks in nq_*_proc?
*/
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/proc_fs.h>
#include <linux/spinlock.h>
#include <linux/vmalloc.h>
#include <linux/ctype.h>
#include <linux/ip.h>
#include <linux/netfilter_ipv4/ip_tables.h>
#include <linux/netfilter_ipv4/ipt_netquota.h>
/*
* DEBUG == 0 -> no debugging
* DEBUG |= 1 -> module and match insert/remove debugging
* DEBUG |= 2 -> proc interface write debugging
* DEBUG |= 4 -> proc interface read debugging
* DEBUG |= 8 -> netfilter match debugging
*/
#define DEBUG 0
/* default module parameters */
static unsigned int max_num_of_matches = 16;
static unsigned int proc_file_perms = 0644;
static unsigned int max_netmask = 16;
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Netwide quota match");
MODULE_AUTHOR("Olaf Rempel <razzor at kopf minus tisch dot de>");
MODULE_PARM(max_num_of_matches, "b");
MODULE_PARM_DESC(max_num_of_matches,
" maximum number of netquota matches (default: 16)");
MODULE_PARM(proc_file_perms, "i");
MODULE_PARM_DESC(proc_file_perms,
" permissions of /proc/net/ipt_netquota/* files (default: 0644)");
MODULE_PARM(max_netmask, "i");
MODULE_PARM_DESC(max_netmask,
" maximum possible network mask of each match (default: 16 == 255.255.0.0)");
/* per IP struct */
struct nq_stat {
u_int64_t quota;
spinlock_t lock;
};
/* per match struct */
struct nq_match {
char name[IPT_NETQUOTA_NAME_LEN];
struct nq_stat *stat;
u_int32_t network;
u_int32_t netmask;
u_int32_t use_count;
struct proc_dir_entry *proc_entry;
};
/* global match array */
static struct nq_match **nq_match_arr = NULL;
static rwlock_t nq_match_arr_lock = RW_LOCK_UNLOCKED;
/* /proc/net/ipt_netquota/ directory */
static struct proc_dir_entry *proc_net_ipt_netquota = NULL;
/* procfs read function */
static int
nq_read_proc(char *buffer, char **start, off_t offset,
int length, int *eof, void *data)
{
struct nq_match *this_match = (struct nq_match *)data;
unsigned int index, count = 0, inc = 0;
u_int32_t address;
while (length - count > 40) {
index = offset + inc;
address = index + this_match->network;
if ((address & this_match->netmask) != this_match->network) {
*eof = 1;
break;
}
spin_lock(&this_match->stat[index].lock);
count += snprintf(buffer + count, length - count,
"%u.%u.%u.%u - %llu\n",
HIPQUAD(address),
this_match->stat[index].quota);
spin_unlock(&this_match->stat[index].lock);
inc++;
}
#if ((DEBUG & 4) == 4)
printk(KERN_INFO "ipt_netquota: proc_read: offset=%lu length=%d "
"inc=%d count=%d *eof= %d\n", offset, length, inc, count, *eof);
#endif
/* offset will be offset += inc on next call */
*start = (char *)inc;
return count;
}
/* parse IP address */
static int
parse_ip(char *cp, unsigned int *address)
{
unsigned int val, retval = 0;
char *old_cp = cp, digit, parts = 4;
while (parts--) {
digit = 3;
val = 0;
while (isdigit(*cp) && digit--) {
val = (val * 10) + (*cp - '0');
cp++;
}
if (val > 255 || digit == 3)
return old_cp - cp;
if (*cp == '.') {
retval = (retval << 8) | (val & 0xFF);
cp++;
} else {
cp++;
break;
}
}
if (parts == 0 && *cp != 0) {
*address = (retval << 8) | (val & 0xFF);
return cp - old_cp;
}
return old_cp - cp;
}
/* procfs write function */
static int
nq_write_proc(struct file *file, const char __user * buffer,
unsigned long length, void *data)
{
struct nq_match *this_match = (struct nq_match *)data;
unsigned long len = (length > 40) ? 40 : length;
unsigned int address = 0, index;
char buf[40], *cp, mode;
u_int64_t quota = 0;
if (copy_from_user(buf, buffer, len))
return -EFAULT;
if (len < 40)
buf[len] = 0;
/* parse the IP */
cp = buf;
if ((len = parse_ip(cp, &address)) < 0)
return -len;
/* get mode (add/subtract/set) */
cp += len;
mode = *cp++;
/* get value */
quota = simple_strtoull(cp, &cp, 10);
if ((address & this_match->netmask) != this_match->network) {
#if ((DEBUG & 2) == 2)
printk(KERN_INFO "ipt_netquota: proc_write: address %u.%u.%u.%u "
"is not a member of match '%s'\n", HIPQUAD(address),
this_match->name);
#endif
return cp - buf;
}
#if ((DEBUG & 2) == 2)
printk(KERN_INFO "ipt_netquota: proc_write: address=%u.%u.%u.%u "
"mode='%c' quota=%llu\n", HIPQUAD(address), mode, quota);
#endif
index = address - this_match->network;
spin_lock(&this_match->stat[index].lock);
switch (mode) {
/* ADD to quota */
case '+':
/* check for overflow */
if (this_match->stat[index].quota + quota > quota)
this_match->stat[index].quota += quota;
break;
/* SUBTRACT from quota */
case '-':
if (this_match->stat[index].quota >= quota) {
this_match->stat[index].quota -= quota;
} else {
this_match->stat[index].quota = 0;
}
break;
/* SET quota */
case '=':
this_match->stat[index].quota = quota;
break;
default:
break;
}
spin_unlock(&this_match->stat[index].lock);
return cp - buffer;
}
/* netfilter match hook, called on every packet that hits us */
static int
match(const struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
const void *matchinfo, int offset, int *hotdrop)
{
struct ipt_netquota_info *info = (struct ipt_netquota_info *)matchinfo;
struct nq_match *this_match;
u_int32_t address, index;
u_int16_t datalen;
if (skb->len < sizeof(struct iphdr))
return 1;
/* datalen = ip datagram length - ip header */
datalen = skb->len - skb->nh.iph->ihl * 4;
read_lock(&nq_match_arr_lock);
this_match = nq_match_arr[info->match_index];
if (this_match == NULL) {
printk(KERN_ERR "ipt_netquota: no such quota: '%s'\n", info->name);
read_unlock(&nq_match_arr_lock);
return 0;
}
/* look at source or destination IP? */
switch (info->mode) {
case IPT_NETQUOTA_SRC_MATCH:
address = ntohl(skb->nh.iph->saddr);
break;
case IPT_NETQUOTA_DST_MATCH:
address = ntohl(skb->nh.iph->daddr);
break;
default:
read_unlock(&nq_match_arr_lock);
return 0;
break;
}
if ((address & this_match->netmask) != this_match->network) {
#if ((DEBUG & 8) == 8)
printk(KERN_INFO "ipt_netquota: %u.%u.%u.%u is not a member of "
"match '%s'\n", HIPQUAD(address), info->name);
#endif
read_unlock(&nq_match_arr_lock);
return 0;
}
index = address - this_match->network;
spin_lock(&this_match->stat[index].lock);
/* check if we can afford this packet */
if (this_match->stat[index].quota >= datalen) {
this_match->stat[index].quota -= datalen;
spin_unlock(&this_match->stat[index].lock);
#if ((DEBUG & 8) == 8)
printk(KERN_INFO "ipt_netquota: quota of %u.%u.%u.%u reduced "
"by %u\n", HIPQUAD(address), datalen);
#endif
read_unlock(&nq_match_arr_lock);
return 1;
}
/* quota reached - do not allow any packets from now on */
this_match->stat[index].quota = 0;
spin_unlock(&this_match->stat[index].lock);
#if ((DEBUG & 8) == 8)
printk(KERN_INFO "ipt_netquota: quota of %u.%u.%u.%u is now zero\n",
HIPQUAD(address));
#endif
read_unlock(&nq_match_arr_lock);
return 0;
}
/* netfilter checkentry hook, called to insert a new match */
static int
checkentry(const char *tablename,
const struct ipt_ip *ip,
void *matchinfo, unsigned int matchinfosize, unsigned int hook_mask)
{
struct ipt_netquota_info *info = (struct ipt_netquota_info *)matchinfo;
struct nq_match *new_match;
unsigned int i, unused = -1, host_count;
if (matchinfosize != IPT_ALIGN(sizeof(struct ipt_netquota_info)))
return 0;
if (!info->name)
return 0;
/* find quota name in match array */
write_lock(&nq_match_arr_lock);
for (i = 0; i < max_num_of_matches; i++) {
/* no match / match deleted */
if (nq_match_arr[i] == NULL) {
/* remember first unsused position */
if (unused == -1)
unused = i;
continue;
}
/* different name */
if (strncmp
(info->name, nq_match_arr[i]->name,
IPT_NETQUOTA_NAME_LEN)) {
continue;
}
/* different parameters (only check network/netmask) */
if (info->network != nq_match_arr[i]->network ||
info->netmask != nq_match_arr[i]->netmask) {
printk(KERN_ERR "ipt_netquota: quota '%s' already exists "
"with different network.\n",
nq_match_arr[i]->name);
write_unlock(&nq_match_arr_lock);
return 0;
}
/* ok, this must be the same match, increase reference count */
nq_match_arr[i]->use_count++;
#if ((DEBUG & 1) == 1)
printk(KERN_INFO "ipt_netquota: incremented use_count of "
"quota '%s' to %d\n", nq_match_arr[i]->name,
nq_match_arr[i]->use_count);
#endif
write_unlock(&nq_match_arr_lock);
return 1;
}
/* no free index found */
if (unused == -1) {
printk(KERN_ERR "ipt_netquota: All matches in use. "
"(try increasing max_num_of_matches parameter)\n");
write_unlock(&nq_match_arr_lock);
return 0;
}
/* check netmask */
host_count = INADDR_BROADCAST - info->netmask + 1;
if (host_count > (1 << (32 - max_netmask))) {
printk(KERN_ERR "ipt_netquota: Requested network mask is too big. "
"(try increasing max_netmask parameter)\n");
write_unlock(&nq_match_arr_lock);
return 0;
}
#if ((DEBUG & 1) == 1)
printk(KERN_INFO
"ipt_netquota: trying to create new match (alloc %d + %d bytes)\n",
sizeof(struct nq_match), host_count * sizeof(struct nq_stat));
#endif
/* alloc mem for a new netquota */
new_match = vmalloc(sizeof(struct nq_match));
if (new_match == NULL) {
write_unlock(&nq_match_arr_lock);
return -ENOMEM;
}
/* alloc mem for stat counters */
new_match->stat = vmalloc(host_count * sizeof(struct nq_stat));
if (new_match->stat == NULL) {
write_unlock(&nq_match_arr_lock);
vfree(new_match);
return -ENOMEM;
}
/* populate match struct */
strncpy(new_match->name, info->name, IPT_NETQUOTA_NAME_LEN);
new_match->network = info->network;
new_match->netmask = info->netmask;
new_match->use_count = 1;
/* create proc entry */
new_match->proc_entry =
create_proc_entry(new_match->name, proc_file_perms,
proc_net_ipt_netquota);
if (new_match->proc_entry == NULL) {
write_unlock(&nq_match_arr_lock);
vfree(new_match->stat);
vfree(new_match);
return -ENOMEM;
}
/* populate proc struct */
new_match->proc_entry->owner = THIS_MODULE;
new_match->proc_entry->read_proc = nq_read_proc;
new_match->proc_entry->write_proc = nq_write_proc;
new_match->proc_entry->data = new_match;
/* init quotas & spinlocks */
for (i = 0; i < host_count; i++) {
new_match->stat[i].quota = 0;
new_match->stat[i].lock = SPIN_LOCK_UNLOCKED;
}
/* finally insert new netquota into match array */
nq_match_arr[unused] = new_match;
info->match_index = unused;
#if ((DEBUG & 1) == 1)
printk(KERN_INFO "ipt_netquota: new match '%s' created\n",
new_match->name);
#endif
write_unlock(&nq_match_arr_lock);
return 1;
}
/* Called when an entry of this type is deleted. */
static void destroy(void *matchinfo, unsigned int matchinfosize)
{
struct ipt_netquota_info *info = matchinfo;
unsigned int i, j, host_count;
if (matchinfosize != IPT_ALIGN(sizeof(struct ipt_netquota_info)))
return;
/* find quota name in match array */
write_lock(&nq_match_arr_lock);
for (i = 0; i < max_num_of_matches; i++) {
/* no match / match deleted */
if (nq_match_arr[i] == NULL) {
continue;
}
/* different name */
if (strncmp(info->name, nq_match_arr[i]->name,
IPT_NETQUOTA_NAME_LEN)) {
continue;
}
/* different parameters (note: only network/netmask check) */
if (info->network != nq_match_arr[i]->network ||
info->netmask != nq_match_arr[i]->netmask) {
write_unlock(&nq_match_arr_lock);
return;
}
/* ok, must be the same match, decrease reference count */
nq_match_arr[i]->use_count--;
#if ((DEBUG & 1) == 1)
printk(KERN_INFO "ipt_netquota: decremented use_count of match "
"'%s' to %d\n", nq_match_arr[i]->name,
nq_match_arr[i]->use_count);
#endif
if (nq_match_arr[i]->use_count > 0) {
write_unlock(&nq_match_arr_lock);
return;
}
#if ((DEBUG & 1) == 1)
printk(KERN_INFO "ipt_netquota: destroy match '%s'\n",
nq_match_arr[i]->name);
#endif
host_count = INADDR_BROADCAST - info->netmask + 1;
/* first, try to lock all counters
* -> all other contexts (if any) in match() must leave (unlock)
* -> new contexts cannot enter match() due to global write_lock
* -> remove the counter locks
* -> we are alone[tm] and can remove this match
*/
for (j = 0; j < host_count; j++)
spin_lock(&nq_match_arr[i]->stat[j].lock);
for (j = 0; j < host_count; j++)
spin_unlock(&nq_match_arr[i]->stat[j].lock);
remove_proc_entry(nq_match_arr[i]->name, proc_net_ipt_netquota);
vfree(nq_match_arr[i]->stat);
vfree(nq_match_arr[i]);
nq_match_arr[i] = NULL;
write_unlock(&nq_match_arr_lock);
return;
}
printk(KERN_ERR "ipt_netquota: no such quota: '%s'\n",
info->name);
write_unlock(&nq_match_arr_lock);
return;
}
static struct ipt_match netquota_match = {
.name = "netquota",
.match = match,
.checkentry = checkentry,
.destroy = destroy,
.me = THIS_MODULE
};
static int __init init(void)
{
#if ((DEBUG & 1) == 1)
u_int32_t netmask = ~((1 << (32 - max_netmask)) - 1);
printk(KERN_INFO "ipt_netquota: module loaded (max. matches: %d "
"max. netmask: %u.%u.%u.%u)\n", max_num_of_matches,
HIPQUAD(netmask));
#endif
/* create match array */
nq_match_arr = vmalloc(sizeof(struct nq_match) * max_num_of_matches);
if (nq_match_arr == NULL) {
return -ENOMEM;
}
memset(nq_match_arr, 0, sizeof(struct nq_match) * max_num_of_matches);
/* create /proc/net/ipt_netquota/ */
proc_net_ipt_netquota = proc_mkdir("ipt_netquota", proc_net);
if (proc_net_ipt_netquota == NULL) {
vfree(nq_match_arr);
return -ENOMEM;
}
if (ipt_register_match(&netquota_match)) {
vfree(nq_match_arr);
return -EINVAL;
}
return 0;
}
static void __exit fini(void)
{
ipt_unregister_match(&netquota_match);
remove_proc_entry("ipt_netquota", proc_net);
vfree(nq_match_arr);
#if ((DEBUG & 1) == 1)
printk(KERN_INFO "ipt_netquota: module unloaded\n");
#endif
}
module_init(init);
module_exit(fini);