Comment 22 for bug 284377

Revision history for this message
TJ (tj) wrote : Re: [Bug 284377] Re: No NET with 2.6.27: No buffer space available

I stepped back a bit and re-considered. Realised I'd missed something
important.

The -ENOBUFS in devinit_ioctl() isn't the only place this error value
could be set.

If the allocation in devinet_ioctl() succeeds execution continues
through to:

  ret = inet_set_ifa(dev, ifa);
  break;

That calls:

net/ipv4/devinet.c::inet_set_ifa()
{
 struct in_device *in_dev = __in_dev_get_rtnl(dev);

 ASSERT_RTNL();

 if (!in_dev) {
  inet_free_ifa(ifa);
  return -ENOBUFS;
 }

-ENOBUFS will happen if __in_dev_get_rtnl(dev) fails.

include/linux/inetdevice.h::__in_dev_get_rtnl():

static __inline__ struct in_device *
__in_dev_get_rtnl(const struct net_device *dev)
{
 return (struct in_device*)dev->ip_ptr;
}

So if dev->ip_ptr is NULL the -ENOBUFS could be fired.

There aren't many places where this will be set. One of them is

net/ipv4/devinet.c::inetdev_init()

...
 /* Account for reference dev->ip_ptr (below) */
 in_dev_hold(in_dev);
...
 /* we can receive as soon as ip_ptr is set -- do this last */
 rcu_assign_pointer(dev->ip_ptr, in_dev);

which is only ever called from

net/ipv4/devinit.c::inetdev_event()

 if (!in_dev) {
  if (event == NETDEV_REGISTER) {
   in_dev = inetdev_init(dev);
...
  } else if (event == NETDEV_CHANGEMTU) {
   /* Re-enabling IP */
   if (inetdev_valid_mtu(dev->mtu))
    in_dev = inetdev_init(dev);

I'm wondering if this is an obscure RCU issue. Having read up on other
changes to similar code it could be that inet_set_ifa() should be:

static int inet_set_ifa(struct net_device *dev, struct in_ifaddr *ifa)
{
 struct in_device *in_dev;
 rcu_read_lock();
 in_dev = __in_dev_get_rcu(dev);

 ASSERT_RTNL();