[RFC][PATCH] identify in_dev_get rcu read-side critical sections

From: Suzanne Wood
Date: Thu Sep 08 2005 - 12:14:06 EST



Please consider this request for suggestions on an attempt at a partial patch
based on the assumptions below to identify rcu read-side critical sections
for in_dev_get() defined in inetdevice.h. Thank you.

drivers/s390/net/lcs.c | 2 ++
drivers/s390/net/qeth_main.c | 4 ++++
include/linux/inetdevice.h | 2 +-
net/ipv4/arp.c | 17 +++++++++++------
net/ipv4/devinet.c | 6 +++++-
net/ipv4/icmp.c | 4 ++--
net/ipv4/igmp.c | 22 ++++++++++++++++++----
net/ipv4/ip_gre.c | 1 +
net/ipv4/ip_input.c | 6 ++++--
net/ipv4/route.c | 41 ++++++++++++++++++++++++++++-------------
10 files changed, 76 insertions(+), 29 deletions(-)

---------------------------------------------------

Assumptions made in this patch development:

1. Programmer who inserts rcu_lock/unlock around an in_dev_get or
__in_dev_get expects the protection of deferred destruction.

2. The marking of an rcu read-side critical section done by
the calling function has the vision of the extent of use of the
protected dereference.

3. Pairings of in_dev_get/in_dev_put and __in_dev_get/__in_dev_put
indicate the need to balance increment/decrement of refcnt,
so __in_dev_get which does not increment is likely paired in
error with __in_dev_put which decrements (unless in_dev_hold
or similar is in the path).

4. If programmer chooses __in_dev_get rather than in_dev_get,
the refcnt is not desired.

5. If a function returns an rcu protected pointer, the rcu_read_lock
is in place, so the rcu_read_unlock occurs in the caller.

Questions/Suggestions:

A pairing of in_dev_get with in_dev_put may indicate the addition
in the in_dev_put conditional of something like
call_rcu(&idev->rcu_head, in_dev->rcu_put)
after in_dev_finish_destroy or replace the latter with a call like
inetdev_destroy().

Differences between inetdevice.h in kernel 2.5.60 and linux-2.6.13-rc6
include the replacement in in_dev_get() of read_lock(&inetdev_lock)/unlock
with rcu_read_lock/unlock and the addition of the rcu_head to the
in_ifaddr struct.

In net/ipv4/route.c, consider __mkroute_output and follow the
three refcnt increments done to out_dev by dev_hold and in_dev_get,
then one decrement to in_dev. Correct refcnt may also be an issue in
xfrm4_policy.c to allow the atomic_dec_and_test in in_dev_put
to appropriately recognize the condition to free the in_device.

In net/ipv4/arp.c, arp_req_set() includes the following where,
with possible update, might this be risky to test the deref and
deref again:
if (__in_dev_get(dev)) {
__in_dev_get(dev)->cnf.proxy_arp = 1;
return 0;
}
and similarly in arp_req_delete? While the vast majority of uses
of __in_dev_get indicate rcu protection, the above may be the exception
that breaks the rule. It seems reasonable to have both in_dev_get
and __in_dev_get use rcu_dereference and have the difference between
them be the refcnt. For now, I'll submit a patch addressing only
in_dev_get and hope for feedback on patching __in_dev_get. Because
one way or another, there are __in_dev_get uses that probably need
to be addressed.

While this is a subjective attempt to understand programmer intent,
with your help and suggestions, the plan is to build an automated
checker. Thank you.

------------------------------------------------------------------

diff -urpNa -X dontdiff /mnt/shared/linuxKernel2_6/linux-2.6.13-rc6/drivers/s390/net/lcs.c linux-2.6.13-rc6/drivers/s390/net/lcs.c
--- /mnt/shared/linuxKernel2_6/linux-2.6.13-rc6/drivers/s390/net/lcs.c 2005-08-07 11:18:56.000000000 -0700
+++ linux-2.6.13-rc6/drivers/s390/net/lcs.c 2005-09-07 21:37:41.000000000 -0700
@@ -1270,6 +1270,7 @@ lcs_register_mc_addresses(void *data)
return 0;
LCS_DBF_TEXT(4, trace, "regmulti");

+ rcu_read_lock();
in4_dev = in_dev_get(card->dev);
if (in4_dev == NULL)
goto out;
@@ -1278,6 +1279,7 @@ lcs_register_mc_addresses(void *data)
lcs_set_mc_addresses(card, in4_dev);
read_unlock(&in4_dev->mc_list_lock);
in_dev_put(in4_dev);
+ rcu_read_unlock();

lcs_fix_multicast_list(card);
out:
diff -urpNa -X dontdiff /mnt/shared/linuxKernel2_6/linux-2.6.13-rc6/drivers/s390/net/qeth_main.c linux-2.6.13-rc6/drivers/s390/net/qeth_main.c
--- /mnt/shared/linuxKernel2_6/linux-2.6.13-rc6/drivers/s390/net/qeth_main.c 2005-08-07 11:18:56.000000000 -0700
+++ linux-2.6.13-rc6/drivers/s390/net/qeth_main.c 2005-09-07 22:22:10.000000000 -0700
@@ -5441,6 +5441,7 @@ qeth_add_vlan_mc(struct qeth_card *card)
if (vg->vlan_devices[i] == NULL ||
!(vg->vlan_devices[i]->flags & IFF_UP))
continue;
+ rcu_read_lock();
in_dev = in_dev_get(vg->vlan_devices[i]);
if (!in_dev)
continue;
@@ -5448,6 +5449,7 @@ qeth_add_vlan_mc(struct qeth_card *card)
qeth_add_mc(card,in_dev);
read_unlock(&in_dev->mc_list_lock);
in_dev_put(in_dev);
+ rcu_read_unlock();
}
#endif
}
@@ -5458,6 +5460,7 @@ qeth_add_multicast_ipv4(struct qeth_card
struct in_device *in4_dev;

QETH_DBF_TEXT(trace,4,"chkmcv4");
+ rcu_read_lock();
in4_dev = in_dev_get(card->dev);
if (in4_dev == NULL)
return;
@@ -5466,6 +5469,7 @@ qeth_add_multicast_ipv4(struct qeth_card
qeth_add_vlan_mc(card);
read_unlock(&in4_dev->mc_list_lock);
in_dev_put(in4_dev);
+ rcu_read_unlock();
}

#ifdef CONFIG_QETH_IPV6
diff -urpNa -X dontdiff /mnt/shared/linuxKernel2_6/linux-2.6.13-rc6/include/linux/inetdevice.h linux-2.6.13-rc6/include/linux/inetdevice.h
--- /mnt/shared/linuxKernel2_6/linux-2.6.13-rc6/include/linux/inetdevice.h 2005-08-07 11:18:56.000000000 -0700
+++ linux-2.6.13-rc6/include/linux/inetdevice.h 2005-08-19 14:27:02.000000000 -0700
@@ -148,7 +148,7 @@ in_dev_get(const struct net_device *dev)
struct in_device *in_dev;

rcu_read_lock();
- in_dev = dev->ip_ptr;
+ in_dev = rcu_dereference(dev->ip_ptr);
if (in_dev)
atomic_inc(&in_dev->refcnt);
rcu_read_unlock();
diff -urpNa -X dontdiff /mnt/shared/linuxKernel2_6/linux-2.6.13-rc6/net/ipv4/arp.c linux-2.6.13-rc6/net/ipv4/arp.c
--- /mnt/shared/linuxKernel2_6/linux-2.6.13-rc6/net/ipv4/arp.c 2005-08-07 11:18:56.000000000 -0700
+++ linux-2.6.13-rc6/net/ipv4/arp.c 2005-09-07 22:49:09.000000000 -0700
@@ -334,9 +334,10 @@ static void arp_solicit(struct neighbour
struct net_device *dev = neigh->dev;
u32 target = *(u32*)neigh->primary_key;
int probes = atomic_read(&neigh->probes);
- struct in_device *in_dev = in_dev_get(dev);
+ struct in_device *in_dev;

- if (!in_dev)
+ rcu_read_lock();
+ if ((in_dev = in_dev_get(dev)) == NULL)
return;

switch (IN_DEV_ARP_ANNOUNCE(in_dev)) {
@@ -362,6 +363,8 @@ static void arp_solicit(struct neighbour

if (in_dev)
in_dev_put(in_dev);
+ rcu_read_unlock();
+
if (!saddr)
saddr = inet_select_addr(dev, target, RT_SCOPE_LINK);

@@ -543,11 +546,12 @@ static inline int arp_fwd_proxy(struct i
return 0;

/* place to check for proxy_arp for routes */
-
+ rcu_read_lock();
if ((out_dev = in_dev_get(rt->u.dst.dev)) != NULL) {
omi = IN_DEV_MEDIUM_ID(out_dev);
in_dev_put(out_dev);
}
+ rcu_read_unlock();
return (omi != imi && omi != -1);
}

@@ -710,7 +714,7 @@ static void parp_redo(struct sk_buff *sk
static int arp_process(struct sk_buff *skb)
{
struct net_device *dev = skb->dev;
- struct in_device *in_dev = in_dev_get(dev);
+ struct in_device *in_dev;
struct arphdr *arp;
unsigned char *arp_ptr;
struct rtable *rt;
@@ -723,8 +727,8 @@ static int arp_process(struct sk_buff *s
/* arp_rcv below verifies the ARP header and verifies the device
* is ARP'able.
*/
-
- if (in_dev == NULL)
+ rcu_read_lock();
+ if ((in_dev = in_dev_get(dev)) == NULL)
goto out;

arp = skb->nh.arph;
@@ -918,6 +922,7 @@ static int arp_process(struct sk_buff *s
out:
if (in_dev)
in_dev_put(in_dev);
+ rcu_read_unlock();
kfree_skb(skb);
return 0;
}
diff -urpNa -X dontdiff /mnt/shared/linuxKernel2_6/linux-2.6.13-rc6/net/ipv4/devinet.c linux-2.6.13-rc6/net/ipv4/devinet.c
--- /mnt/shared/linuxKernel2_6/linux-2.6.13-rc6/net/ipv4/devinet.c 2005-08-07 11:18:56.000000000 -0700
+++ linux-2.6.13-rc6/net/ipv4/devinet.c 2005-09-08 00:38:01.000000000 -0700
@@ -372,12 +372,15 @@ static int inet_set_ifa(struct net_devic
return inet_insert_ifa(ifa);
}

+/* returns with rcu read-critical section open, so rcu_read_unlock() in caller */
+
struct in_device *inetdev_by_index(int ifindex)
{
struct net_device *dev;
struct in_device *in_dev = NULL;
read_lock(&dev_base_lock);
dev = __dev_get_by_index(ifindex);
+ rcu_read_lock();
if (dev)
in_dev = in_dev_get(dev);
read_unlock(&dev_base_lock);
@@ -409,7 +412,8 @@ static int inet_rtm_deladdr(struct sk_bu

if ((in_dev = inetdev_by_index(ifm->ifa_index)) == NULL)
goto out;
- __in_dev_put(in_dev);
+ in_dev_put(in_dev);
+ rcu_read_unlock();

for (ifap = &in_dev->ifa_list; (ifa = *ifap) != NULL;
ifap = &ifa->ifa_next) {
diff -urpNa -X dontdiff /mnt/shared/linuxKernel2_6/linux-2.6.13-rc6/net/ipv4/icmp.c linux-2.6.13-rc6/net/ipv4/icmp.c
--- /mnt/shared/linuxKernel2_6/linux-2.6.13-rc6/net/ipv4/icmp.c 2005-08-07 11:18:56.000000000 -0700
+++ linux-2.6.13-rc6/net/ipv4/icmp.c 2005-09-07 23:37:08.000000000 -0700
@@ -890,10 +890,10 @@ static void icmp_address_reply(struct sk
if (skb->len < 4 || !(rt->rt_flags&RTCF_DIRECTSRC))
goto out;

+ rcu_read_lock();
in_dev = in_dev_get(dev);
if (!in_dev)
goto out;
- rcu_read_lock();
if (in_dev->ifa_list &&
IN_DEV_LOG_MARTIANS(in_dev) &&
IN_DEV_FORWARD(in_dev)) {
@@ -913,8 +913,8 @@ static void icmp_address_reply(struct sk
NIPQUAD(*mp), dev->name, NIPQUAD(rt->rt_src));
}
}
- rcu_read_unlock();
in_dev_put(in_dev);
+ rcu_read_unlock();
out:;
}

diff -urpNa -X dontdiff /mnt/shared/linuxKernel2_6/linux-2.6.13-rc6/net/ipv4/igmp.c linux-2.6.13-rc6/net/ipv4/igmp.c
--- /mnt/shared/linuxKernel2_6/linux-2.6.13-rc6/net/ipv4/igmp.c 2005-08-07 11:18:56.000000000 -0700
+++ linux-2.6.13-rc6/net/ipv4/igmp.c 2005-09-08 00:35:52.000000000 -0700
@@ -864,10 +864,12 @@ int igmp_rcv(struct sk_buff *skb)
{
/* This basically follows the spec line by line -- see RFC1112 */
struct igmphdr *ih;
- struct in_device *in_dev = in_dev_get(skb->dev);
+ struct in_device *in_dev;
int len = skb->len;

- if (in_dev==NULL) {
+ rcu_read_lock();
+ if ((in_dev = in_dev_get(skb->dev) == NULL) {
+ rcu_read_unlock();
kfree_skb(skb);
return 0;
}
@@ -875,6 +877,7 @@ int igmp_rcv(struct sk_buff *skb)
if (!pskb_may_pull(skb, sizeof(struct igmphdr)) ||
(u16)csum_fold(skb_checksum(skb, 0, len, 0))) {
in_dev_put(in_dev);
+ rcu_read_unlock();
kfree_skb(skb);
return 0;
}
@@ -907,6 +910,7 @@ int igmp_rcv(struct sk_buff *skb)
NETDEBUG(printk(KERN_DEBUG "New IGMP type=%d, why we do not know about it?\n", ih->type));
}
in_dev_put(in_dev);
+ rcu_read_unlock();
kfree_skb(skb);
return 0;
}
@@ -1307,8 +1311,8 @@ static struct in_device * ip_mc_find_dev
if (imr->imr_ifindex) {
idev = inetdev_by_index(imr->imr_ifindex);
if (idev)
- __in_dev_put(idev);
- return idev;
+ __in_dev_put(idev); /* decrement before return? */
+ return idev; /* caller should rcu_read_unlock */
}
if (imr->imr_address.s_addr) {
dev = ip_dev_find(imr->imr_address.s_addr);
@@ -2098,6 +2102,7 @@ void ip_mc_drop_socket(struct sock *sk)
(void) ip_mc_leave_src(sk, iml, in_dev);
ip_mc_dec_group(in_dev, iml->multi.imr_multiaddr.s_addr);
in_dev_put(in_dev);
+ rcu_read_unlock();
}
sock_kfree_s(sk, iml, sizeof(*iml));

@@ -2154,6 +2159,7 @@ static inline struct ip_mc_list *igmp_mc
state->dev;
state->dev = state->dev->next) {
struct in_device *in_dev;
+ rcu_read_lock();
in_dev = in_dev_get(state->dev);
if (!in_dev)
continue;
@@ -2165,6 +2171,7 @@ static inline struct ip_mc_list *igmp_mc
}
read_unlock(&in_dev->mc_list_lock);
in_dev_put(in_dev);
+ rcu_read_unlock();
}
return im;
}
@@ -2183,11 +2190,13 @@ static struct ip_mc_list *igmp_mc_get_ne
state->in_dev = NULL;
break;
}
+ rcu_read_lock();
state->in_dev = in_dev_get(state->dev);
if (!state->in_dev)
continue;
read_lock(&state->in_dev->mc_list_lock);
im = state->in_dev->mc_list;
+ rcu_read_unlock();
}
return im;
}
@@ -2317,6 +2326,7 @@ static inline struct ip_sf_list *igmp_mc
state->dev;
state->dev = state->dev->next) {
struct in_device *idev;
+ rcu_read_lock();
idev = in_dev_get(state->dev);
if (unlikely(idev == NULL))
continue;
@@ -2334,6 +2344,7 @@ static inline struct ip_sf_list *igmp_mc
}
read_unlock(&idev->mc_list_lock);
in_dev_put(idev);
+ rcu_read_unlock();
}
return psf;
}
@@ -2356,11 +2367,14 @@ static struct ip_sf_list *igmp_mcf_get_n
state->idev = NULL;
goto out;
}
+ rcu_read_lock();
state->idev = in_dev_get(state->dev);
if (!state->idev)
+ rcu_read_unlock();
continue;
read_lock(&state->idev->mc_list_lock);
state->im = state->idev->mc_list;
+ rcu_read_unlock();
}
if (!state->im)
break;
diff -urpNa -X dontdiff /mnt/shared/linuxKernel2_6/linux-2.6.13-rc6/net/ipv4/ip_gre.c linux-2.6.13-rc6/net/ipv4/ip_gre.c
--- /mnt/shared/linuxKernel2_6/linux-2.6.13-rc6/net/ipv4/ip_gre.c 2005-08-07 11:18:56.000000000 -0700
+++ linux-2.6.13-rc6/net/ipv4/ip_gre.c 2005-09-08 00:42:38.000000000 -0700
@@ -1121,6 +1121,7 @@ static int ipgre_close(struct net_device
ip_mc_dec_group(in_dev, t->parms.iph.daddr);
in_dev_put(in_dev);
}
+ rcu_read_unlock();
}
return 0;
}
diff -urpNa -X dontdiff /mnt/shared/linuxKernel2_6/linux-2.6.13-rc6/net/ipv4/ip_input.c linux-2.6.13-rc6/net/ipv4/ip_input.c
--- /mnt/shared/linuxKernel2_6/linux-2.6.13-rc6/net/ipv4/ip_input.c 2005-08-07 11:18:56.000000000 -0700
+++ linux-2.6.13-rc6/net/ipv4/ip_input.c 2005-09-08 00:45:42.000000000 -0700
@@ -330,8 +330,9 @@ static inline int ip_rcv_finish(struct s

opt = &(IPCB(skb)->opt);
if (opt->srr) {
- struct in_device *in_dev = in_dev_get(dev);
- if (in_dev) {
+ struct in_device *in_dev;
+ rcu_read_lock();
+ if ((in_dev = in_dev_get(dev)) != NULL) {
if (!IN_DEV_SOURCE_ROUTE(in_dev)) {
if (IN_DEV_LOG_MARTIANS(in_dev) && net_ratelimit())
printk(KERN_INFO "source route option %u.%u.%u.%u -> %u.%u.%u.%u\n",
@@ -341,6 +342,7 @@ static inline int ip_rcv_finish(struct s
}
in_dev_put(in_dev);
}
+ rcu_read_unlock();
if (ip_options_rcv_srr(skb))
goto drop;
}
diff -urpNa -X dontdiff /mnt/shared/linuxKernel2_6/linux-2.6.13-rc6/net/ipv4/route.c linux-2.6.13-rc6/net/ipv4/route.c
--- /mnt/shared/linuxKernel2_6/linux-2.6.13-rc6/net/ipv4/route.c 2005-08-07 11:18:56.000000000 -0700
+++ linux-2.6.13-rc6/net/ipv4/route.c 2005-09-08 08:39:01.000000000 -0700
@@ -1112,14 +1112,15 @@ void ip_rt_redirect(u32 old_gw, u32 dadd
u32 saddr, u8 tos, struct net_device *dev)
{
int i, k;
- struct in_device *in_dev = in_dev_get(dev);
+ struct in_device *in_dev;
struct rtable *rth, **rthp;
u32 skeys[2] = { saddr, 0 };
int ikeys[2] = { dev->ifindex, 0 };

tos &= IPTOS_RT_MASK;

- if (!in_dev)
+ rcu_read_lock();
+ if ((in_dev = in_dev_get(dev)) == NULL)
return;

if (new_gw == old_gw || !IN_DEV_RX_REDIRECTS(in_dev)
@@ -1171,6 +1172,7 @@ void ip_rt_redirect(u32 old_gw, u32 dadd
if (rt == NULL) {
ip_rt_put(rth);
in_dev_put(in_dev);
+ rcu_read_unlock();
return;
}

@@ -1223,6 +1225,7 @@ void ip_rt_redirect(u32 old_gw, u32 dadd
}
}
in_dev_put(in_dev);
+ rcu_read_unlock();
return;

reject_redirect:
@@ -1236,6 +1239,7 @@ reject_redirect:
NIPQUAD(saddr), NIPQUAD(daddr), tos);
#endif
in_dev_put(in_dev);
+ rcu_read_unlock();
}

static struct dst_entry *ipv4_negative_advice(struct dst_entry *dst)
@@ -1284,9 +1288,9 @@ static struct dst_entry *ipv4_negative_a
void ip_rt_send_redirect(struct sk_buff *skb)
{
struct rtable *rt = (struct rtable*)skb->dst;
- struct in_device *in_dev = in_dev_get(rt->u.dst.dev);
-
- if (!in_dev)
+ struct in_device *in_dev;
+ rcu_read_lock();
+ if ((in_dev = in_dev_get(rt->u.dst.dev)) == NULL)
return;

if (!IN_DEV_TX_REDIRECTS(in_dev))
@@ -1327,6 +1331,7 @@ void ip_rt_send_redirect(struct sk_buff
}
out:
in_dev_put(in_dev);
+ rcu_read_unlock();
}

static int ip_error(struct sk_buff *skb)
@@ -1482,10 +1487,12 @@ static void ipv4_dst_ifdown(struct dst_e
struct rtable *rt = (struct rtable *) dst;
struct in_device *idev = rt->idev;
if (dev != &loopback_dev && idev && idev->dev == dev) {
- struct in_device *loopback_idev = in_dev_get(&loopback_dev);
- if (loopback_idev) {
+ struct in_device *loopback_idev;
+ rcu_read_lock();
+ if (loopback_idev = in_dev_get(&loopback_dev) != NULL) {
rt->idev = loopback_idev;
in_dev_put(idev);
+ rcu_read_unlock();
}
}
}
@@ -1593,12 +1600,12 @@ static int ip_route_input_mc(struct sk_b
unsigned hash;
struct rtable *rth;
u32 spec_dst;
- struct in_device *in_dev = in_dev_get(dev);
+ struct in_device *in_dev;
u32 itag = 0;

/* Primary sanity checks. */
-
- if (in_dev == NULL)
+ rcu_read_lock();
+ if ((in_dev = in_dev_get(dev)) == NULL)
return -EINVAL;

if (MULTICAST(saddr) || BADCLASS(saddr) || LOOPBACK(saddr) ||
@@ -1656,15 +1663,18 @@ static int ip_route_input_mc(struct sk_b
RT_CACHE_STAT_INC(in_slow_mc);

in_dev_put(in_dev);
+ rcu_read_unlock();
hash = rt_hash_code(daddr, saddr ^ (dev->ifindex << 5), tos);
return rt_intern_hash(hash, rth, (struct rtable**) &skb->dst);

e_nobufs:
in_dev_put(in_dev);
+ rcu_read_unlock();
return -ENOBUFS;

e_inval:
in_dev_put(in_dev);
+ rcu_read_unlock();
return -EINVAL;
}

@@ -1714,6 +1724,7 @@ static inline int __mkroute_input(struct
u32 spec_dst, itag;

/* get a working reference to the output device */
+ rcu_read_lock()
out_dev = in_dev_get(FIB_RES_DEV(*res));
if (out_dev == NULL) {
if (net_ratelimit())
@@ -1796,6 +1807,7 @@ static inline int __mkroute_input(struct
cleanup:
/* release the working reference to the output device */
in_dev_put(out_dev);
+ rcu_read_unlock();
return err;
}

@@ -1899,7 +1911,7 @@ static int ip_route_input_slow(struct sk
u8 tos, struct net_device *dev)
{
struct fib_result res;
- struct in_device *in_dev = in_dev_get(dev);
+ struct in_device *in_dev;
struct flowi fl = { .nl_u = { .ip4_u =
{ .daddr = daddr,
.saddr = saddr,
@@ -1919,8 +1931,8 @@ static int ip_route_input_slow(struct sk
int free_res = 0;

/* IP on this device is disabled. */
-
- if (!in_dev)
+ rcu_read_lock();
+ if ((in_dev = in_dev_get(dev)) == NULL)
goto out;

/* Check for the most weird martians, which can be not detected
@@ -1983,6 +1995,7 @@ static int ip_route_input_slow(struct sk

done:
in_dev_put(in_dev);
+ rcu_read_unlock();
if (free_res)
fib_res_put(&res);
out: return err;
@@ -2174,6 +2187,7 @@ static inline int __mkroute_output(struc
flags |= RTCF_LOCAL;

/* get work reference to inet device */
+ rcu_read_lock();
in_dev = in_dev_get(dev_out);
if (!in_dev)
return -EINVAL;
@@ -2270,6 +2284,7 @@ static inline int __mkroute_output(struc
*result = rth;
cleanup:
/* release work reference to inet device */
+ rcu_read_unlock();
in_dev_put(in_dev);

return err;
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/