Refcount problem fixed

Fri, 11 Aug 2006 02:00:00 CEST

I've finally found the refcount problem after adding tons of printk's everywhere. It turns out that the event cache increases the refcount for each packet in the L3 protocol handler, and then it decreases it in nf_conntrack_confirm(), this refcount isn't coupled to the skb in question as the "regular" refcount is which means that "this" refcount isn't decreased automagically when the skb is freed.

When we register the hooks we pass in an array of 'struct nf_hook_ops' and then we loop over the array calling nf_register_hook() for each hook registration. nf_register_hook() calls spin_lock_bh()/spin_unlock_bh(), spin_unlock_bh() executes any pending softirqs after it unlocks the lock.

This means that we can register nf_conntrack_in() at PREROUTING and during this hook registration we get an interrupt from the NIC that schedules an softirq to be executed as soon as possible. When the hook registration of nf_conntrack_in() at PREROUTING is complete and the spin_unlock_bh() is called the pending softirq is executed. And now we just passed a packet through nf_conntrack_in() which calls the event cache which increases the refcount of the conntrack entry. But nf_conntrack_confirm() wasn't registered yet so the event cache never decreased the refcount since the packet never passed through nf_conntrack_confirm().

One workaround could be to extend the spin_lock()/spin_unlock() to cover the entire nf_conntrack_hooks(), that would prevent the above scenario from occuring... but only on UP kernels. On SMP kernels one cpu could be going through the hooks at the same time as we are registering/unregistering them as we are using RCU for the linked lists of hooks. So that's not a solution.

A better solution is to make sure that we order the hook registrations in the 'struct nf_hook_ops' array in the order the hooks will be called in the net stack. And then register them in reverse order, we still unregister them in the forward order. This means that nf_conntrack_confirm() is registered before nf_conntrack_in(), and unregistered after, and after each unregistration we call synchronize_net() to make sure that all packets currently in the net stack are finished before we continue to unregister the next hook. We must make sure that a packet that passed through nf_conntrack_in() completes its journey through the net stack before we unregister the nf_conntrack_confirm() hook, thus the synchronization.

Registering in reverse order and unregistering in forward order means that it's possible that packets for example are passed through nf_conntrack_confirm() without having been passed through nf_conntrack_in(), this is the reverse of the original problem, but it's much easier to handle :) We need to handle cases like this where skbs are supposed to have been passed through an earlier hook but that hasn't happened because of the racy registration and unregistration.

I've implemented the above solution and so far it has passed my testcase, consisting of unloading/loading nf_conntrack_ipv4 in a loop, without problems. Without the fix it's very easy to trigger the refcount problem, even without my RCU patches. I've been able to trigger it with as few as 3 icmp echo requests while reloading the module in a loop, sometimes up to 20 packets are needed. I've only tested the fix in QEMU but I hope it also works for real SMP machines. And I need to implement the same fix for the ipv6 part as well.

Sometime in the future we'll hopefully be able to get rid of the extra refcounts the event cache brings, time will tell. Now it's time for some sleep.