version 1.0.1

This commit is contained in:
Olaf Rempel 2006-04-04 15:10:57 +02:00
commit 8d93b453b3
4 changed files with 831 additions and 0 deletions

View File

@ -0,0 +1,3 @@
#!/bin/sh
[ -f $KERNEL_DIR/include/linux/netfilter_ipv4/ipt_netquota.h ] && echo netquota

View File

@ -0,0 +1,215 @@
/*
* netquota match (libipt_netquota.c)
* netfilter userspace part (iptables)
*
* by 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.
*/
#include <stddef.h> /* offsetof() ... */
#include <stdio.h>
#include <stdlib.h>
#include <iptables.h>
#include <string.h>
#include <getopt.h>
#include <linux/netfilter_ipv4/ipt_netquota.h>
static void help(void)
{
printf("netquota v%s options:\n"
"--nq-name name\n"
" defines name of this netquota. defaults to \"DEFAULT\".\n"
"--nq-src network/netmask\n"
" defines a netquota by source IP address.\n"
"--nq-dst network/netmask\n"
" defines a netquota by destination IP address.\n",
IPTABLES_VERSION);
};
static struct option opts[] = {
{"nq-src", 1, NULL, 's'},
{"nq-dst", 1, NULL, 'd'},
{"nq-name", 1, NULL, 'n'},
{0}
};
/* Function which initializes the match */
static void init(struct ipt_entry_match *match, unsigned int *nfcache)
{
struct ipt_netquota_info *info =
(struct ipt_netquota_info *)match->data;
/* set default table name to DEFAULT */
strncpy(info->name, "DEFAULT", IPT_NETQUOTA_NAME_LEN);
*nfcache |= NFC_UNKNOWN;
}
/* Function which parses the match's arguments */
static int
parse(int c, char **argv, int invert, unsigned int *flags,
const struct ipt_entry *entry, unsigned int *nfcache,
struct ipt_entry_match **match)
{
struct ipt_netquota_info *info =
(struct ipt_netquota_info *)(*match)->data;
struct in_addr *addrs = NULL, mask;
unsigned int naddrs = 0, i;
switch (c) {
case 'n':
/* --nq-name */
if (strlen(optarg) < IPT_NETQUOTA_NAME_LEN)
strncpy(info->name, optarg, IPT_NETQUOTA_NAME_LEN);
else
exit_error(PARAMETER_PROBLEM,
"netquota: Quota name too long");
break;
case 's':
/* --nq-src */
parse_hostnetworkmask(optarg, &addrs, &mask, &naddrs);
if (naddrs > 0) {
info->network = ntohl(addrs->s_addr);
info->netmask = ntohl(mask.s_addr);
info->mode |= IPT_NETQUOTA_SRC_MATCH;
for (i = 0; i < naddrs; i++)
free(&addrs[i]);
*flags |= 1;
} else {
exit_error(PARAMETER_PROBLEM,
"netquota: Invalid '--nq-src' parameter");
}
break;
case 'd':
/* --nq-dst */
parse_hostnetworkmask(optarg, &addrs, &mask, &naddrs);
if (naddrs > 0) {
info->network = ntohl(addrs->s_addr);
info->netmask = ntohl(mask.s_addr);
info->mode |= IPT_NETQUOTA_DST_MATCH;
for (i = 0; i < naddrs; i++)
free(&addrs[i]);
*flags |= 2;
} else {
exit_error(PARAMETER_PROBLEM,
"netquota: Invalid '--nq-dst' parameter");
}
break;
default:
return 0;
}
return 1;
}
/* Final check whether network/netmask was specified */
static void final_check(unsigned int flags)
{
switch (flags) {
case 0:
exit_error(PARAMETER_PROBLEM,
"netquota: You must specify a '--nq-src' or '--nq-dst' "
"parameter");
break;
case 1:
case 2:
return;
break;
case 3:
exit_error(PARAMETER_PROBLEM,
"netquota: You can only specify either a"
" '--nq-src' or a '--nq-dst' parameter");
break;
}
}
/* Host byte order IP address to dotted string */
static char *haddr_to_dotted(u_int32_t ip)
{
struct in_addr tmp;
tmp.s_addr = htonl(ip);
return addr_to_dotted(&tmp);
}
/* Host byte order network mask to CIDR / dotted string */
static char *hmask_to_dotted(u_int32_t mask)
{
struct in_addr tmp;
tmp.s_addr = htonl(mask);
return mask_to_dotted(&tmp);
}
/* Function used for printing a rule with iptables -L */
static void
print(const struct ipt_ip *ip, const struct ipt_entry_match *match, int numeric)
{
struct ipt_netquota_info *info =
(struct ipt_netquota_info *)match->data;
printf("netquota: %s ", info->name);
switch (info->mode) {
case IPT_NETQUOTA_SRC_MATCH:
printf("src");
break;
case IPT_NETQUOTA_DST_MATCH:
printf("dst");
break;
}
printf("(%s%s)", haddr_to_dotted(info->network),
hmask_to_dotted(info->netmask));
}
/* Function used for saving a rule with iptables-save */
static void save(const struct ipt_ip *ip, const struct ipt_entry_match *match)
{
struct ipt_netquota_info *info =
(struct ipt_netquota_info *)match->data;
printf("--nq-name %s ", info->name);
switch (info->mode) {
case IPT_NETQUOTA_SRC_MATCH:
printf("--nq-src ");
break;
case IPT_NETQUOTA_DST_MATCH:
printf("--nq-dst ");
break;
}
printf("%s%s", haddr_to_dotted(info->network),
hmask_to_dotted(info->netmask));
}
static struct iptables_match netquota = {
NULL,
"netquota",
IPTABLES_VERSION,
IPT_ALIGN(sizeof(struct ipt_netquota_info)),
offsetof(struct ipt_netquota_info, mode),
&help,
&init,
&parse,
&final_check,
&print,
&save,
opts
};
/* Function which registers the match */
void _init(void)
{
register_match(&netquota);
}

View File

@ -0,0 +1,19 @@
#ifndef _IPT_NETQUOTA_H
#define _IPT_NETQUOTA_H
#define IPT_NETQUOTA_NAME_LEN 16
#define IPT_NETQUOTA_SRC_MATCH 1
#define IPT_NETQUOTA_DST_MATCH 2
struct ipt_netquota_info {
/* used by Userspace and Kernel */
char name[IPT_NETQUOTA_NAME_LEN];
u_int32_t network;
u_int32_t netmask;
unsigned char mode;
/* used only by Kernel */
unsigned char match_index;
};
#endif

View File

@ -0,0 +1,594 @@
/*
* 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);