![Patrick McHardy](/assets/img/avatar_default.png)
The comparison wants to find out if the last list iteration reached the end of the list. It needs to compare the iterator with the list head to do this, not the element it is looking for. Signed-off-by: Patrick McHardy <kaber@trash.net> Signed-off-by: David S. Miller <davem@davemloft.net>
342 lines
7.5 KiB
C
342 lines
7.5 KiB
C
#include <linux/config.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/skbuff.h>
|
|
#include <linux/netfilter.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/rcupdate.h>
|
|
#include <net/protocol.h>
|
|
|
|
#include "nf_internals.h"
|
|
|
|
/*
|
|
* A queue handler may be registered for each protocol. Each is protected by
|
|
* long term mutex. The handler must provide an an outfn() to accept packets
|
|
* for queueing and must reinject all packets it receives, no matter what.
|
|
*/
|
|
static struct nf_queue_handler *queue_handler[NPROTO];
|
|
static struct nf_queue_rerouter *queue_rerouter[NPROTO];
|
|
|
|
static DEFINE_RWLOCK(queue_handler_lock);
|
|
|
|
/* return EBUSY when somebody else is registered, return EEXIST if the
|
|
* same handler is registered, return 0 in case of success. */
|
|
int nf_register_queue_handler(int pf, struct nf_queue_handler *qh)
|
|
{
|
|
int ret;
|
|
|
|
if (pf >= NPROTO)
|
|
return -EINVAL;
|
|
|
|
write_lock_bh(&queue_handler_lock);
|
|
if (queue_handler[pf] == qh)
|
|
ret = -EEXIST;
|
|
else if (queue_handler[pf])
|
|
ret = -EBUSY;
|
|
else {
|
|
queue_handler[pf] = qh;
|
|
ret = 0;
|
|
}
|
|
write_unlock_bh(&queue_handler_lock);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(nf_register_queue_handler);
|
|
|
|
/* The caller must flush their queue before this */
|
|
int nf_unregister_queue_handler(int pf)
|
|
{
|
|
if (pf >= NPROTO)
|
|
return -EINVAL;
|
|
|
|
write_lock_bh(&queue_handler_lock);
|
|
queue_handler[pf] = NULL;
|
|
write_unlock_bh(&queue_handler_lock);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(nf_unregister_queue_handler);
|
|
|
|
int nf_register_queue_rerouter(int pf, struct nf_queue_rerouter *rer)
|
|
{
|
|
if (pf >= NPROTO)
|
|
return -EINVAL;
|
|
|
|
write_lock_bh(&queue_handler_lock);
|
|
rcu_assign_pointer(queue_rerouter[pf], rer);
|
|
write_unlock_bh(&queue_handler_lock);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(nf_register_queue_rerouter);
|
|
|
|
int nf_unregister_queue_rerouter(int pf)
|
|
{
|
|
if (pf >= NPROTO)
|
|
return -EINVAL;
|
|
|
|
write_lock_bh(&queue_handler_lock);
|
|
rcu_assign_pointer(queue_rerouter[pf], NULL);
|
|
write_unlock_bh(&queue_handler_lock);
|
|
synchronize_rcu();
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(nf_unregister_queue_rerouter);
|
|
|
|
void nf_unregister_queue_handlers(struct nf_queue_handler *qh)
|
|
{
|
|
int pf;
|
|
|
|
write_lock_bh(&queue_handler_lock);
|
|
for (pf = 0; pf < NPROTO; pf++) {
|
|
if (queue_handler[pf] == qh)
|
|
queue_handler[pf] = NULL;
|
|
}
|
|
write_unlock_bh(&queue_handler_lock);
|
|
}
|
|
EXPORT_SYMBOL_GPL(nf_unregister_queue_handlers);
|
|
|
|
/*
|
|
* Any packet that leaves via this function must come back
|
|
* through nf_reinject().
|
|
*/
|
|
int nf_queue(struct sk_buff **skb,
|
|
struct list_head *elem,
|
|
int pf, unsigned int hook,
|
|
struct net_device *indev,
|
|
struct net_device *outdev,
|
|
int (*okfn)(struct sk_buff *),
|
|
unsigned int queuenum)
|
|
{
|
|
int status;
|
|
struct nf_info *info;
|
|
#ifdef CONFIG_BRIDGE_NETFILTER
|
|
struct net_device *physindev = NULL;
|
|
struct net_device *physoutdev = NULL;
|
|
#endif
|
|
struct nf_queue_rerouter *rerouter;
|
|
|
|
/* QUEUE == DROP if noone is waiting, to be safe. */
|
|
read_lock(&queue_handler_lock);
|
|
if (!queue_handler[pf]) {
|
|
read_unlock(&queue_handler_lock);
|
|
kfree_skb(*skb);
|
|
return 1;
|
|
}
|
|
|
|
info = kmalloc(sizeof(*info)+queue_rerouter[pf]->rer_size, GFP_ATOMIC);
|
|
if (!info) {
|
|
if (net_ratelimit())
|
|
printk(KERN_ERR "OOM queueing packet %p\n",
|
|
*skb);
|
|
read_unlock(&queue_handler_lock);
|
|
kfree_skb(*skb);
|
|
return 1;
|
|
}
|
|
|
|
*info = (struct nf_info) {
|
|
(struct nf_hook_ops *)elem, pf, hook, indev, outdev, okfn };
|
|
|
|
/* If it's going away, ignore hook. */
|
|
if (!try_module_get(info->elem->owner)) {
|
|
read_unlock(&queue_handler_lock);
|
|
kfree(info);
|
|
return 0;
|
|
}
|
|
|
|
/* Bump dev refs so they don't vanish while packet is out */
|
|
if (indev) dev_hold(indev);
|
|
if (outdev) dev_hold(outdev);
|
|
|
|
#ifdef CONFIG_BRIDGE_NETFILTER
|
|
if ((*skb)->nf_bridge) {
|
|
physindev = (*skb)->nf_bridge->physindev;
|
|
if (physindev) dev_hold(physindev);
|
|
physoutdev = (*skb)->nf_bridge->physoutdev;
|
|
if (physoutdev) dev_hold(physoutdev);
|
|
}
|
|
#endif
|
|
rerouter = rcu_dereference(queue_rerouter[pf]);
|
|
if (rerouter)
|
|
rerouter->save(*skb, info);
|
|
|
|
status = queue_handler[pf]->outfn(*skb, info, queuenum,
|
|
queue_handler[pf]->data);
|
|
|
|
read_unlock(&queue_handler_lock);
|
|
|
|
if (status < 0) {
|
|
/* James M doesn't say fuck enough. */
|
|
if (indev) dev_put(indev);
|
|
if (outdev) dev_put(outdev);
|
|
#ifdef CONFIG_BRIDGE_NETFILTER
|
|
if (physindev) dev_put(physindev);
|
|
if (physoutdev) dev_put(physoutdev);
|
|
#endif
|
|
module_put(info->elem->owner);
|
|
kfree(info);
|
|
kfree_skb(*skb);
|
|
|
|
return 1;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
void nf_reinject(struct sk_buff *skb, struct nf_info *info,
|
|
unsigned int verdict)
|
|
{
|
|
struct list_head *elem = &info->elem->list;
|
|
struct list_head *i;
|
|
struct nf_queue_rerouter *rerouter;
|
|
|
|
rcu_read_lock();
|
|
|
|
/* Release those devices we held, or Alexey will kill me. */
|
|
if (info->indev) dev_put(info->indev);
|
|
if (info->outdev) dev_put(info->outdev);
|
|
#ifdef CONFIG_BRIDGE_NETFILTER
|
|
if (skb->nf_bridge) {
|
|
if (skb->nf_bridge->physindev)
|
|
dev_put(skb->nf_bridge->physindev);
|
|
if (skb->nf_bridge->physoutdev)
|
|
dev_put(skb->nf_bridge->physoutdev);
|
|
}
|
|
#endif
|
|
|
|
/* Drop reference to owner of hook which queued us. */
|
|
module_put(info->elem->owner);
|
|
|
|
list_for_each_rcu(i, &nf_hooks[info->pf][info->hook]) {
|
|
if (i == elem)
|
|
break;
|
|
}
|
|
|
|
if (i == &nf_hooks[info->pf][info->hook]) {
|
|
/* The module which sent it to userspace is gone. */
|
|
NFDEBUG("%s: module disappeared, dropping packet.\n",
|
|
__FUNCTION__);
|
|
verdict = NF_DROP;
|
|
}
|
|
|
|
/* Continue traversal iff userspace said ok... */
|
|
if (verdict == NF_REPEAT) {
|
|
elem = elem->prev;
|
|
verdict = NF_ACCEPT;
|
|
}
|
|
|
|
if (verdict == NF_ACCEPT) {
|
|
rerouter = rcu_dereference(queue_rerouter[info->pf]);
|
|
if (rerouter && rerouter->reroute(&skb, info) < 0)
|
|
verdict = NF_DROP;
|
|
}
|
|
|
|
if (verdict == NF_ACCEPT) {
|
|
next_hook:
|
|
verdict = nf_iterate(&nf_hooks[info->pf][info->hook],
|
|
&skb, info->hook,
|
|
info->indev, info->outdev, &elem,
|
|
info->okfn, INT_MIN);
|
|
}
|
|
|
|
switch (verdict & NF_VERDICT_MASK) {
|
|
case NF_ACCEPT:
|
|
info->okfn(skb);
|
|
break;
|
|
|
|
case NF_QUEUE:
|
|
if (!nf_queue(&skb, elem, info->pf, info->hook,
|
|
info->indev, info->outdev, info->okfn,
|
|
verdict >> NF_VERDICT_BITS))
|
|
goto next_hook;
|
|
break;
|
|
}
|
|
rcu_read_unlock();
|
|
|
|
if (verdict == NF_DROP)
|
|
kfree_skb(skb);
|
|
|
|
kfree(info);
|
|
return;
|
|
}
|
|
EXPORT_SYMBOL(nf_reinject);
|
|
|
|
#ifdef CONFIG_PROC_FS
|
|
static void *seq_start(struct seq_file *seq, loff_t *pos)
|
|
{
|
|
if (*pos >= NPROTO)
|
|
return NULL;
|
|
|
|
return pos;
|
|
}
|
|
|
|
static void *seq_next(struct seq_file *s, void *v, loff_t *pos)
|
|
{
|
|
(*pos)++;
|
|
|
|
if (*pos >= NPROTO)
|
|
return NULL;
|
|
|
|
return pos;
|
|
}
|
|
|
|
static void seq_stop(struct seq_file *s, void *v)
|
|
{
|
|
|
|
}
|
|
|
|
static int seq_show(struct seq_file *s, void *v)
|
|
{
|
|
int ret;
|
|
loff_t *pos = v;
|
|
struct nf_queue_handler *qh;
|
|
|
|
read_lock_bh(&queue_handler_lock);
|
|
qh = queue_handler[*pos];
|
|
if (!qh)
|
|
ret = seq_printf(s, "%2lld NONE\n", *pos);
|
|
else
|
|
ret = seq_printf(s, "%2lld %s\n", *pos, qh->name);
|
|
read_unlock_bh(&queue_handler_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static struct seq_operations nfqueue_seq_ops = {
|
|
.start = seq_start,
|
|
.next = seq_next,
|
|
.stop = seq_stop,
|
|
.show = seq_show,
|
|
};
|
|
|
|
static int nfqueue_open(struct inode *inode, struct file *file)
|
|
{
|
|
return seq_open(file, &nfqueue_seq_ops);
|
|
}
|
|
|
|
static struct file_operations nfqueue_file_ops = {
|
|
.owner = THIS_MODULE,
|
|
.open = nfqueue_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = seq_release,
|
|
};
|
|
#endif /* PROC_FS */
|
|
|
|
|
|
int __init netfilter_queue_init(void)
|
|
{
|
|
#ifdef CONFIG_PROC_FS
|
|
struct proc_dir_entry *pde;
|
|
|
|
pde = create_proc_entry("nf_queue", S_IRUGO, proc_net_netfilter);
|
|
if (!pde)
|
|
return -1;
|
|
pde->proc_fops = &nfqueue_file_ops;
|
|
#endif
|
|
return 0;
|
|
}
|
|
|