Skip to contentSkip to conversation

netfilter: use idr instead of list to speed up packet lookup by id

PATCH In review Changli Gao 3 participants 11 messages Apr 7, 2010

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

PATCH 1/1 netfilter: use idr instead of list to speed up packet lookup by id

use idr instead of list to speed up packet lookup by id.

The current implementations of nfnetlink_queue, ip_queue and ip6_queue are all use list to save the packets queued. If the verdicts aren't received in order, the lookup for the corresponding packets isn't efficient. As the ids is generated and maintained in kernel, we can use idr to speed up the lookup. The side effect of this patch is fixing the potential id overlap in nfnetlink_queue.

----
include/net/netfilter/nf_queue.h | 1 net/ipv4/netfilter/ip_queue.c | 64 +++++++++++++++++++++------------------ net/ipv6/netfilter/ip6_queue.c | 64 +++++++++++++++++++++------------------ net/netfilter/nfnetlink_queue.c | 59 +++++++++++++++++------------------

4 files changed, 99 insertions(+), 89 deletions(-)
include/net/netfilter/nf_queue.h +0 -1
index 252fd10..c90db4a 100644
@@ -3,7 +3,6 @@
 
 /* Each queued (to userspace) skbuff has one of these. */
 struct nf_queue_entry {
-	struct list_head	list;
 	struct sk_buff		*skb;
 	unsigned int		id;
 
net/ipv4/netfilter/ip_queue.c +35 -29

172 lines not on this page · raw message

net/ipv6/netfilter/ip6_queue.c +35 -29

172 lines not on this page · raw message

net/netfilter/nfnetlink_queue.c +29 -30

172 lines not on this page · raw message

#1 lore raw
Quoted 8 lines · Changli Gao

use idr instead of list to speed up packet lookup by id.

The current implementations of nfnetlink_queue, ip_queue and ip6_queue are all use list to save the packets queued. If the verdicts aren't received in order, the lookup for the corresponding packets isn't efficient. As the ids is generated and maintained in kernel, we can use idr to speed up the lookup. The side effect of this patch is fixing the potential id overlap in nfnetlink_queue.

This doesn't compile:

net/netfilter/nfnetlink_queue.c:57: error: field 'idr' has incomplete type net/netfilter/nfnetlink_queue.c: In function 'instance_create':
net/netfilter/nfnetlink_queue.c:112: error: implicit declaration of function 'idr_init'
net/netfilter/nfnetlink_queue.c: In function 'instance_destroy_rcu':
net/netfilter/nfnetlink_queue.c:143: error: implicit declaration of function 'idr_destroy'
net/netfilter/nfnetlink_queue.c: In function 'find_dequeue_entry':
net/netfilter/nfnetlink_queue.c:170: error: implicit declaration of function 'idr_find'
net/netfilter/nfnetlink_queue.c:170: warning: assignment makes pointer from integer without a cast net/netfilter/nfnetlink_queue.c:172: error: implicit declaration of function 'idr_remove'
net/netfilter/nfnetlink_queue.c: In function 'nfqnl_flush':
net/netfilter/nfnetlink_queue.c:188: error: implicit declaration of function 'idr_get_next'
net/netfilter/nfnetlink_queue.c:188: warning: assignment makes pointer from integer without a cast
net/netfilter/nfnetlink_queue.c: In function 'nfqnl_build_packet_message':
net/netfilter/nfnetlink_queue.c:253: error: implicit declaration of function 'idr_pre_get'
net/netfilter/nfnetlink_queue.c:254: error: implicit declaration of function 'idr_get_new'

I'm interested in how this affects performance for the vast majority of users, which process messages in order. A simple hash table looks like a better choice here since we know the maximum number of entries in advance and also could have the user specify the desired hash size.

Quoted 10 lines
net/netfilter/nfnetlink_queue.c +1 -0
@@ -223,6 +211,7 @@ nfqnl_build_packet_message(struct nfqnl_instance *queue,
 	struct sk_buff *entskb = entry->skb;
 	struct net_device *indev;
 	struct net_device *outdev;
+	u32 id;
 
 	size =    NLMSG_SPACE(sizeof(struct nfgenmsg))
 		+ nla_total_size(sizeof(struct nfqnl_msg_packet_hdr))
@@ -262,7 +251,11 @@ nfqnl_build_packet_message(struct nfqnl_instance *queue,
 		break;

net/netfilter/nfnetlink_queue.c nfqnl_build_packet_message(struct nfqnl_instance *queue, · line 260

Quoted 10 lines
net/netfilter/nfnetlink_queue.c +5 -1
 	}
 
-	entry->id = queue->id_sequence++;
+	if (!idr_pre_get(&queue->idr, GFP_ATOMIC) ||
+	    idr_get_new(&queue->idr, entry, &id) != 0) {
+	    	spin_unlock_bh(&queue->lock);
+		return NULL;
+	}
 
 	spin_unlock_bh(&queue->lock);

You probably shouldn't be making the entry visible before the message is successfully built and sent.

Quoted 77 lines
net/netfilter/nfnetlink_queue.c +10 -5
@@ -279,7 +272,7 @@ nfqnl_build_packet_message(struct nfqnl_instance *queue,
 	nfmsg->version = NFNETLINK_V0;
 	nfmsg->res_id = htons(queue->queue_num);
 
-	pmsg.packet_id 		= htonl(entry->id);
+	pmsg.packet_id 		= htonl(id);
 	pmsg.hw_protocol	= entskb->protocol;
 	pmsg.hook		= entry->hook;
 
@@ -375,6 +368,7 @@ nfqnl_build_packet_message(struct nfqnl_instance *queue,
 	}
 
 	nlh->nlmsg_len = skb->tail - old_tail;
+	*pid = id;
 	return skb;
 
 nlmsg_failure:
@@ -383,6 +377,9 @@ nla_put_failure:
 		kfree_skb(skb);
 	if (net_ratelimit())
 		printk(KERN_ERR "nf_queue: error creating packet message\n");
+	spin_lock_bh(&queue->lock);
+	idr_remove(&queue->idr, id);
+	spin_unlock_bh(&queue->lock);
 	return NULL;
 }
 
@@ -392,6 +389,7 @@ nfqnl_enqueue_packet(struct nf_queue_entry *entry, unsigned int queuenum)
 	struct sk_buff *nskb;
 	struct nfqnl_instance *queue;
 	int err;
+	u32 id;
 
 	/* rcu_read_lock()ed by nf_hook_slow() */
 	queue = instance_lookup(queuenum);
@@ -401,7 +399,7 @@ nfqnl_enqueue_packet(struct nf_queue_entry *entry, unsigned int queuenum)
 	if (queue->copy_mode == NFQNL_COPY_NONE)
 		goto err_out;
 
-	nskb = nfqnl_build_packet_message(queue, entry);
+	nskb = nfqnl_build_packet_message(queue, entry, &id);
 	if (nskb == NULL)
 		goto err_out;
 
@@ -426,7 +424,7 @@ nfqnl_enqueue_packet(struct nf_queue_entry *entry, unsigned int queuenum)
 		goto err_out_unlock;
 	}
 
-	__enqueue_entry(queue, entry);
+	queue->queue_total++;
 
 	spin_unlock_bh(&queue->lock);
 	return 0;
@@ -434,6 +432,7 @@ nfqnl_enqueue_packet(struct nf_queue_entry *entry, unsigned int queuenum)
 err_out_free_nskb:
 	kfree_skb(nskb);
 err_out_unlock:
+	idr_remove(&queue->idr, id);
 	spin_unlock_bh(&queue->lock);
 err_out:
 	return -1;
@@ -867,7 +866,7 @@ static int seq_show(struct seq_file *s, void *v)
 			  inst->peer_pid, inst->queue_total,
 			  inst->copy_mode, inst->copy_range,
 			  inst->queue_dropped, inst->queue_user_dropped,
-			  inst->id_sequence, 1);
+			  0, 1);
 }
 
 static const struct seq_operations nfqnl_seq_ops = {
 
--

To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

#2 lore raw
Quoted 10 lines · Patrick McHardy
Quoted 8 lines · Changli Gao

use idr instead of list to speed up packet lookup by id.

The current implementations of nfnetlink_queue, ip_queue and ip6_queue are all use list to save the packets queued. If the verdicts aren't received in order, the lookup for the corresponding packets isn't efficient. As the ids is generated and maintained in kernel, we can use idr to speed up the lookup. The side effect of this patch is fixing the potential id overlap in nfnetlink_queue.

Quoted 4 lines

I'm interested in how this affects performance for the vast majority of users, which process messages in order. A simple hash table looks like a better choice here since we know the maximum number of entries in advance and also could have the user specify the desired hash size.

Yes, a hash table would be good, but cost 8 bytes per slot.

Changli, did you tried RBL tree ? It might fit better both your needs and Patrick concerns...

--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

#3 lore raw
Quoted 15 lines · Eric Dumazet
Quoted 9 lines · Patrick McHardy
Quoted 8 lines · Changli Gao

use idr instead of list to speed up packet lookup by id.

The current implementations of nfnetlink_queue, ip_queue and ip6_queue are all use list to save the packets queued. If the verdicts aren't received in order, the lookup for the corresponding packets isn't efficient. As the ids is generated and maintained in kernel, we can use idr to speed up the lookup. The side effect of this patch is fixing the potential id overlap in nfnetlink_queue.

Quoted 4 lines

I'm interested in how this affects performance for the vast majority of users, which process messages in order. A simple hash table looks like a better choice here since we know the maximum number of entries in advance and also could have the user specify the desired hash size.

Quoted 4 lines

Yes, a hash table would be good, but cost 8 bytes per slot.

Changli, did you tried RBL tree ? It might fit better both your needs and Patrick concerns...

RB-trees OTOH cost 8 bytes extra per element. If we'd use a hash, I think the size should be configurable by userspace and default to 1 (simple list as used currently). That way only those people actually processing packets out of order have to pay the price. But an RB-tree would be fine too I guess.
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

#5 lore raw
In reply to Patrick McHardy
Quoted 20 lines · Patrick McHardy

Changli Gao wrote:

This doesn't compile:

net/netfilter/nfnetlink_queue.c:57: error: field 'idr' has incomplete type net/netfilter/nfnetlink_queue.c: In function 'instance_create':
net/netfilter/nfnetlink_queue.c:112: error: implicit declaration of function 'idr_init'
net/netfilter/nfnetlink_queue.c: In function 'instance_destroy_rcu':
net/netfilter/nfnetlink_queue.c:143: error: implicit declaration of function 'idr_destroy'
net/netfilter/nfnetlink_queue.c: In function 'find_dequeue_entry':
net/netfilter/nfnetlink_queue.c:170: error: implicit declaration of function 'idr_find'
net/netfilter/nfnetlink_queue.c:170: warning: assignment makes pointer from integer without a cast net/netfilter/nfnetlink_queue.c:172: error: implicit declaration of function 'idr_remove'
net/netfilter/nfnetlink_queue.c: In function 'nfqnl_flush':
net/netfilter/nfnetlink_queue.c:188: error: implicit declaration of

Quoted 8 lines

function 'idr_get_next'
net/netfilter/nfnetlink_queue.c:188: warning: assignment makes pointer from integer without a cast
net/netfilter/nfnetlink_queue.c: In function 'nfqnl_build_packet_message':
net/netfilter/nfnetlink_queue.c:253: error: implicit declaration of function 'idr_pre_get'
net/netfilter/nfnetlink_queue.c:254: error: implicit declaration of function 'idr_get_new'

I cooked this patch aginst linux-next. And I have noticed net-next wasn't synced with linux-next yet.

Quoted 6 lines

I'm interested in how this affects performance for the vast majority of users, which process messages in order. A simple hash table looks like a better choice here since we know the maximum number of entries in advance and also could have the user specify the desired hash size.

If there aren't much packets queued, the packet ids will be around 0, and idr isn't much slower.

Quoted 3 lines

You probably shouldn't be making the entry visible before the message is successfully built and sent.

I wanted to keep the changeset small. But it seems wrong.

Signature · 6 lines
Regards,
Changli Gao([email protected])
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
#6 lore raw
Quoted 11 lines · Changli Gao
Quoted 7 lines · Patrick McHardy

Changli Gao wrote:

I'm interested in how this affects performance for the vast majority of users, which process messages in order. A simple hash table looks like a better choice here since we know the maximum number of entries in advance and also could have the user specify the desired hash size.

If there aren't much packets queued, the packet ids will be around 0, and idr isn't much slower.

Processing packets in order doesn't necessarily mean that there aren't many packets queued. Its just that the current scheme is pretty much optimal for this case since the verdict will always refer to the first entry in the list.

Quoted 5 lines
Quoted 2 lines

You probably shouldn't be making the entry visible before the message is successfully built and sent.

I wanted to keep the changeset small. But it seems wrong.

#4 lore raw
In reply to Eric Dumazet
Quoted 5 lines · Eric Dumazet

Yes, a hash table would be good, but cost 8 bytes per slot.

Changli, did you tried RBL tree ? It might fit better both your needs and Patrick concerns...

I don't think rbtree is better than idr. rbtree's time complexity is O(log2(n)), but idr is a kind of radix tree with O(1) time complexity.

Signature · 6 lines
Regards,
Changli Gao([email protected])
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
#7 lore raw
In reply to Patrick McHardy
Quoted 5 lines · Patrick McHardy

Processing packets in order doesn't necessarily mean that there aren't many packets queued. Its just that the current scheme is pretty much optimal for this case since the verdict will always refer to the first entry in the list.

How about keeping the old behavior by default, and replace the single list with idr when an unorder vedict is received.

Signature · 6 lines
Regards,
Changli Gao([email protected])
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
#8 lore raw
Quoted 9 lines · Changli Gao
Quoted 5 lines · Patrick McHardy

Processing packets in order doesn't necessarily mean that there aren't many packets queued. Its just that the current scheme is pretty much optimal for this case since the verdict will always refer to the first entry in the list.

How about keeping the old behavior by default, and replace the single list with idr when an unorder vedict is received.

You only know that during runtime, so this will get more complicated than necessary. Why not simply use a hash table with a size specified by userspace in the queue creation command? The default behaviour would be a size of 1, which is equivalent to the currently used single list.

#9 lore raw
Quoted 7 lines · Patrick McHardy

You only know that during runtime, so this will get more complicated than necessary. Why not simply use a hash table with a size specified by userspace in the queue creation command?  The default behaviour would be a size of 1, which is equivalent to the currently used single list.

We'd better not expose too many internal implementation details to userspace. If we do so, we can't change its implementation easily later. And letting user choose the size of hash table is much like the orginal epoll(2) design, there will be security consern, such as too much memory usage.

Signature · 6 lines
Regards,
Changli Gao([email protected])
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
#10 lore raw
Quoted 8 lines · Changli Gao
Quoted 6 lines · Patrick McHardy

You only know that during runtime, so this will get more complicated than necessary. Why not simply use a hash table with a size specified by userspace in the queue creation command? The default behaviour would be a size of 1, which is equivalent to the currently used single list.

Quoted 5 lines

We'd better not expose too many internal implementation details to userspace. If we do so, we can't change its implementation easily later. And letting user choose the size of hash table is much like the orginal epoll(2) design, there will be security consern, such as too much memory usage.

Userspace queueing is limited to root, so there's no concern about memory usage. Regarding implementation details: alternatively add a flag to specify out of order handling and size the hash table based on the maximum number of queue entries.