summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStephen Hemminger <shemminger@osdl.org>2003-04-28 12:34:32 -0700
committerDavid S. Miller <davem@nuts.ninka.net>2003-04-28 12:34:32 -0700
commit64e535eed284139c2f686cf891dd7f81af6305bf (patch)
treeeedc494a51e369e728296236ac6bbbf0479fbfe4
parent7242b16ab3437ad052bb4fa3cb867dc38eb17a34 (diff)
[NETFILTER]: Use Read Copy Update.
-rw-r--r--include/linux/list.h10
-rw-r--r--net/core/netfilter.c71
2 files changed, 59 insertions, 22 deletions
diff --git a/include/linux/list.h b/include/linux/list.h
index 9b8485930659..a724f9bcbe4d 100644
--- a/include/linux/list.h
+++ b/include/linux/list.h
@@ -335,6 +335,16 @@ static inline void list_splice_init(struct list_head *list,
prefetch(pos->member.next))
+/**
+ * list_for_each_continue_rcu - iterate over an rcu-protected list
+ * continuing from existing point.
+ * @pos: the &struct list_head to use as a loop counter.
+ * @head: the head for your list.
+ */
+#define list_for_each_continue_rcu(pos, head) \
+ for ((pos) = (pos)->next, prefetch((pos)->next); (pos) != (head); \
+ (pos) = (pos)->next, ({ smp_read_barrier_depends(); 0;}), prefetch((pos)->next))
+
/*
* Double linked lists with a single pointer list head.
* Mostly useful for hash tables where the two pointer list head is
diff --git a/net/core/netfilter.c b/net/core/netfilter.c
index faada9788394..1ef2cf1b71e3 100644
--- a/net/core/netfilter.c
+++ b/net/core/netfilter.c
@@ -19,7 +19,6 @@
#include <linux/interrupt.h>
#include <linux/if.h>
#include <linux/netdevice.h>
-#include <linux/brlock.h>
#include <linux/inetdevice.h>
#include <net/sock.h>
#include <net/route.h>
@@ -40,12 +39,13 @@
#endif
/* Sockopts only registered and called from user context, so
- BR_NETPROTO_LOCK would be overkill. Also, [gs]etsockopt calls may
+ net locking would be overkill. Also, [gs]etsockopt calls may
sleep. */
static DECLARE_MUTEX(nf_sockopt_mutex);
struct list_head nf_hooks[NPROTO][NF_MAX_HOOKS];
static LIST_HEAD(nf_sockopts);
+static spinlock_t nf_hook_lock = SPIN_LOCK_UNLOCKED;
/*
* A queue handler may be registered for each protocol. Each is protected by
@@ -56,28 +56,31 @@ static struct nf_queue_handler_t {
nf_queue_outfn_t outfn;
void *data;
} queue_handler[NPROTO];
+static rwlock_t queue_handler_lock = RW_LOCK_UNLOCKED;
int nf_register_hook(struct nf_hook_ops *reg)
{
struct list_head *i;
- br_write_lock_bh(BR_NETPROTO_LOCK);
- for (i = nf_hooks[reg->pf][reg->hooknum].next;
- i != &nf_hooks[reg->pf][reg->hooknum];
- i = i->next) {
+ spin_lock_bh(&nf_hook_lock);
+ list_for_each(i, &nf_hooks[reg->pf][reg->hooknum]) {
if (reg->priority < ((struct nf_hook_ops *)i)->priority)
break;
}
- list_add(&reg->list, i->prev);
- br_write_unlock_bh(BR_NETPROTO_LOCK);
+ list_add_rcu(&reg->list, i->prev);
+ spin_unlock_bh(&nf_hook_lock);
+
+ synchronize_net();
return 0;
}
void nf_unregister_hook(struct nf_hook_ops *reg)
{
- br_write_lock_bh(BR_NETPROTO_LOCK);
- list_del(&reg->list);
- br_write_unlock_bh(BR_NETPROTO_LOCK);
+ spin_lock_bh(&nf_hook_lock);
+ list_del_rcu(&reg->list);
+ spin_unlock_bh(&nf_hook_lock);
+
+ synchronize_net();
}
/* Do exclusive ranges overlap? */
@@ -344,7 +347,11 @@ static unsigned int nf_iterate(struct list_head *head,
int (*okfn)(struct sk_buff *),
int hook_thresh)
{
- for (*i = (*i)->next; *i != head; *i = (*i)->next) {
+ /*
+ * The caller must not block between calls to this
+ * function because of risk of continuing from deleted element.
+ */
+ list_for_each_continue_rcu(*i, head) {
struct nf_hook_ops *elem = (struct nf_hook_ops *)*i;
if (hook_thresh > elem->priority)
@@ -383,7 +390,7 @@ int nf_register_queue_handler(int pf, nf_queue_outfn_t outfn, void *data)
{
int ret;
- br_write_lock_bh(BR_NETPROTO_LOCK);
+ write_lock_bh(&queue_handler_lock);
if (queue_handler[pf].outfn)
ret = -EBUSY;
else {
@@ -391,7 +398,7 @@ int nf_register_queue_handler(int pf, nf_queue_outfn_t outfn, void *data)
queue_handler[pf].data = data;
ret = 0;
}
- br_write_unlock_bh(BR_NETPROTO_LOCK);
+ write_unlock_bh(&queue_handler_lock);
return ret;
}
@@ -399,10 +406,11 @@ int nf_register_queue_handler(int pf, nf_queue_outfn_t outfn, void *data)
/* The caller must flush their queue before this */
int nf_unregister_queue_handler(int pf)
{
- br_write_lock_bh(BR_NETPROTO_LOCK);
+ write_lock_bh(&queue_handler_lock);
queue_handler[pf].outfn = NULL;
queue_handler[pf].data = NULL;
- br_write_unlock_bh(BR_NETPROTO_LOCK);
+ write_unlock_bh(&queue_handler_lock);
+
return 0;
}
@@ -425,7 +433,9 @@ static int nf_queue(struct sk_buff *skb,
#endif
/* QUEUE == DROP if noone is waiting, to be safe. */
+ read_lock(&queue_handler_lock);
if (!queue_handler[pf].outfn) {
+ read_unlock(&queue_handler_lock);
kfree_skb(skb);
return 1;
}
@@ -435,6 +445,7 @@ static int nf_queue(struct sk_buff *skb,
if (net_ratelimit())
printk(KERN_ERR "OOM queueing packet %p\n",
skb);
+ read_unlock(&queue_handler_lock);
kfree_skb(skb);
return 1;
}
@@ -443,8 +454,11 @@ static int nf_queue(struct sk_buff *skb,
(struct nf_hook_ops *)elem, pf, hook, indev, outdev, okfn };
/* If it's going away, ignore hook. */
- if (!try_module_get(info->elem->owner))
+ 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);
@@ -460,6 +474,8 @@ static int nf_queue(struct sk_buff *skb,
#endif
status = queue_handler[pf].outfn(skb, info, queue_handler[pf].data);
+ read_unlock(&queue_handler_lock);
+
if (status < 0) {
/* James M doesn't say fuck enough. */
if (indev) dev_put(indev);
@@ -495,7 +511,7 @@ int nf_hook_slow(int pf, unsigned int hook, struct sk_buff *skb,
}
/* We may already have this, but read-locks nest anyway */
- br_read_lock_bh(BR_NETPROTO_LOCK);
+ rcu_read_lock();
#ifdef CONFIG_NETFILTER_DEBUG
if (skb->nf_debug & (1 << hook)) {
@@ -526,7 +542,7 @@ int nf_hook_slow(int pf, unsigned int hook, struct sk_buff *skb,
break;
}
- br_read_unlock_bh(BR_NETPROTO_LOCK);
+ rcu_read_unlock();
return ret;
}
@@ -535,12 +551,23 @@ void nf_reinject(struct sk_buff *skb, struct nf_info *info,
{
struct list_head *elem = &info->elem->list;
- /* We don't have BR_NETPROTO_LOCK here */
- br_read_lock_bh(BR_NETPROTO_LOCK);
+ rcu_read_lock();
/* 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 (elem == &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;
@@ -570,7 +597,7 @@ void nf_reinject(struct sk_buff *skb, struct nf_info *info,
kfree_skb(skb);
break;
}
- br_read_unlock_bh(BR_NETPROTO_LOCK);
+ rcu_read_unlock();
/* Release those devices we held, or Alexey will kill me. */
if (info->indev) dev_put(info->indev);