diff options
Diffstat (limited to 'net/ipv4/devinet.c')
| -rw-r--r-- | net/ipv4/devinet.c | 863 | 
1 files changed, 737 insertions, 126 deletions
diff --git a/net/ipv4/devinet.c b/net/ipv4/devinet.c index 3b067704ab3..e9449376b58 100644 --- a/net/ipv4/devinet.c +++ b/net/ipv4/devinet.c @@ -27,7 +27,6 @@  #include <asm/uaccess.h> -#include <asm/system.h>  #include <linux/bitops.h>  #include <linux/capability.h>  #include <linux/module.h> @@ -51,10 +50,12 @@  #include <linux/inetdevice.h>  #include <linux/igmp.h>  #include <linux/slab.h> +#include <linux/hash.h>  #ifdef CONFIG_SYSCTL  #include <linux/sysctl.h>  #endif  #include <linux/kmod.h> +#include <linux/netconf.h>  #include <net/arp.h>  #include <net/ip.h> @@ -62,6 +63,9 @@  #include <net/ip_fib.h>  #include <net/rtnetlink.h>  #include <net/net_namespace.h> +#include <net/addrconf.h> + +#include "fib_lookup.h"  static struct ipv4_devconf ipv4_devconf = {  	.data = { @@ -69,6 +73,8 @@ static struct ipv4_devconf ipv4_devconf = {  		[IPV4_DEVCONF_SEND_REDIRECTS - 1] = 1,  		[IPV4_DEVCONF_SECURE_REDIRECTS - 1] = 1,  		[IPV4_DEVCONF_SHARED_MEDIA - 1] = 1, +		[IPV4_DEVCONF_IGMPV2_UNSOLICITED_REPORT_INTERVAL - 1] = 10000 /*ms*/, +		[IPV4_DEVCONF_IGMPV3_UNSOLICITED_REPORT_INTERVAL - 1] =  1000 /*ms*/,  	},  }; @@ -79,6 +85,8 @@ static struct ipv4_devconf ipv4_devconf_dflt = {  		[IPV4_DEVCONF_SECURE_REDIRECTS - 1] = 1,  		[IPV4_DEVCONF_SHARED_MEDIA - 1] = 1,  		[IPV4_DEVCONF_ACCEPT_SOURCE_ROUTE - 1] = 1, +		[IPV4_DEVCONF_IGMPV2_UNSOLICITED_REPORT_INTERVAL - 1] = 10000 /*ms*/, +		[IPV4_DEVCONF_IGMPV3_UNSOLICITED_REPORT_INTERVAL - 1] =  1000 /*ms*/,  	},  }; @@ -90,8 +98,82 @@ static const struct nla_policy ifa_ipv4_policy[IFA_MAX+1] = {  	[IFA_ADDRESS]   	= { .type = NLA_U32 },  	[IFA_BROADCAST] 	= { .type = NLA_U32 },  	[IFA_LABEL]     	= { .type = NLA_STRING, .len = IFNAMSIZ - 1 }, +	[IFA_CACHEINFO]		= { .len = sizeof(struct ifa_cacheinfo) }, +	[IFA_FLAGS]		= { .type = NLA_U32 },  }; +#define IN4_ADDR_HSIZE_SHIFT	8 +#define IN4_ADDR_HSIZE		(1U << IN4_ADDR_HSIZE_SHIFT) + +static struct hlist_head inet_addr_lst[IN4_ADDR_HSIZE]; + +static u32 inet_addr_hash(struct net *net, __be32 addr) +{ +	u32 val = (__force u32) addr ^ net_hash_mix(net); + +	return hash_32(val, IN4_ADDR_HSIZE_SHIFT); +} + +static void inet_hash_insert(struct net *net, struct in_ifaddr *ifa) +{ +	u32 hash = inet_addr_hash(net, ifa->ifa_local); + +	ASSERT_RTNL(); +	hlist_add_head_rcu(&ifa->hash, &inet_addr_lst[hash]); +} + +static void inet_hash_remove(struct in_ifaddr *ifa) +{ +	ASSERT_RTNL(); +	hlist_del_init_rcu(&ifa->hash); +} + +/** + * __ip_dev_find - find the first device with a given source address. + * @net: the net namespace + * @addr: the source address + * @devref: if true, take a reference on the found device + * + * If a caller uses devref=false, it should be protected by RCU, or RTNL + */ +struct net_device *__ip_dev_find(struct net *net, __be32 addr, bool devref) +{ +	u32 hash = inet_addr_hash(net, addr); +	struct net_device *result = NULL; +	struct in_ifaddr *ifa; + +	rcu_read_lock(); +	hlist_for_each_entry_rcu(ifa, &inet_addr_lst[hash], hash) { +		if (ifa->ifa_local == addr) { +			struct net_device *dev = ifa->ifa_dev->dev; + +			if (!net_eq(dev_net(dev), net)) +				continue; +			result = dev; +			break; +		} +	} +	if (!result) { +		struct flowi4 fl4 = { .daddr = addr }; +		struct fib_result res = { 0 }; +		struct fib_table *local; + +		/* Fallback to FIB local table so that communication +		 * over loopback subnets work. +		 */ +		local = fib_get_table(net, RT_TABLE_LOCAL); +		if (local && +		    !fib_table_lookup(local, &fl4, &res, FIB_LOOKUP_NOREF) && +		    res.type == RTN_LOCAL) +			result = FIB_RES_DEV(res); +	} +	if (result && devref) +		dev_hold(result); +	rcu_read_unlock(); +	return result; +} +EXPORT_SYMBOL(__ip_dev_find); +  static void rtmsg_ifa(int event, struct in_ifaddr *, struct nlmsghdr *, u32);  static BLOCKING_NOTIFIER_HEAD(inetaddr_chain); @@ -101,10 +183,10 @@ static void inet_del_ifa(struct in_device *in_dev, struct in_ifaddr **ifap,  static void devinet_sysctl_register(struct in_device *idev);  static void devinet_sysctl_unregister(struct in_device *idev);  #else -static inline void devinet_sysctl_register(struct in_device *idev) +static void devinet_sysctl_register(struct in_device *idev)  {  } -static inline void devinet_sysctl_unregister(struct in_device *idev) +static void devinet_sysctl_unregister(struct in_device *idev)  {  }  #endif @@ -124,7 +206,7 @@ static void inet_rcu_free_ifa(struct rcu_head *head)  	kfree(ifa);  } -static inline void inet_free_ifa(struct in_ifaddr *ifa) +static void inet_free_ifa(struct in_ifaddr *ifa)  {  	call_rcu(&ifa->rcu_head, inet_rcu_free_ifa);  } @@ -135,9 +217,9 @@ void in_dev_finish_destroy(struct in_device *idev)  	WARN_ON(idev->ifa_list);  	WARN_ON(idev->mc_list); +	kfree(rcu_dereference_protected(idev->mc_hash, 1));  #ifdef NET_REFCNT_DEBUG -	printk(KERN_DEBUG "in_dev_finish_destroy: %p=%s\n", -	       idev, dev ? dev->name : "NIL"); +	pr_debug("%s: %p=%s\n", __func__, idev, dev ? dev->name : "NIL");  #endif  	dev_put(dev);  	if (!idev->dead) @@ -209,7 +291,7 @@ static void inetdev_destroy(struct in_device *in_dev)  		inet_free_ifa(ifa);  	} -	rcu_assign_pointer(dev->ip_ptr, NULL); +	RCU_INIT_POINTER(dev->ip_ptr, NULL);  	devinet_sysctl_unregister(in_dev);  	neigh_parms_release(&arp_tbl, in_dev->arp_parms); @@ -234,7 +316,7 @@ int inet_addr_onlink(struct in_device *in_dev, __be32 a, __be32 b)  }  static void __inet_del_ifa(struct in_device *in_dev, struct in_ifaddr **ifap, -			 int destroy, struct nlmsghdr *nlh, u32 pid) +			 int destroy, struct nlmsghdr *nlh, u32 portid)  {  	struct in_ifaddr *promote = NULL;  	struct in_ifaddr *ifa, *ifa1 = *ifap; @@ -265,9 +347,10 @@ static void __inet_del_ifa(struct in_device *in_dev, struct in_ifaddr **ifap,  			}  			if (!do_promote) { +				inet_hash_remove(ifa);  				*ifap1 = ifa->ifa_next; -				rtmsg_ifa(RTM_DELADDR, ifa, nlh, pid); +				rtmsg_ifa(RTM_DELADDR, ifa, nlh, portid);  				blocking_notifier_call_chain(&inetaddr_chain,  						NETDEV_DOWN, ifa);  				inet_free_ifa(ifa); @@ -278,9 +361,21 @@ static void __inet_del_ifa(struct in_device *in_dev, struct in_ifaddr **ifap,  		}  	} +	/* On promotion all secondaries from subnet are changing +	 * the primary IP, we must remove all their routes silently +	 * and later to add them back with new prefsrc. Do this +	 * while all addresses are on the device list. +	 */ +	for (ifa = promote; ifa; ifa = ifa->ifa_next) { +		if (ifa1->ifa_mask == ifa->ifa_mask && +		    inet_ifa_match(ifa1->ifa_address, ifa)) +			fib_del_ifaddr(ifa, ifa1); +	} +  	/* 2. Unlink it */  	*ifap = ifa1->ifa_next; +	inet_hash_remove(ifa1);  	/* 3. Announce address deletion */ @@ -292,10 +387,11 @@ static void __inet_del_ifa(struct in_device *in_dev, struct in_ifaddr **ifap,  	   is valid, it will try to restore deleted routes... Grr.  	   So that, this order is correct.  	 */ -	rtmsg_ifa(RTM_DELADDR, ifa1, nlh, pid); +	rtmsg_ifa(RTM_DELADDR, ifa1, nlh, portid);  	blocking_notifier_call_chain(&inetaddr_chain, NETDEV_DOWN, ifa1);  	if (promote) { +		struct in_ifaddr *next_sec = promote->ifa_next;  		if (prev_prom) {  			prev_prom->ifa_next = promote->ifa_next; @@ -304,10 +400,10 @@ static void __inet_del_ifa(struct in_device *in_dev, struct in_ifaddr **ifap,  		}  		promote->ifa_flags &= ~IFA_F_SECONDARY; -		rtmsg_ifa(RTM_NEWADDR, promote, nlh, pid); +		rtmsg_ifa(RTM_NEWADDR, promote, nlh, portid);  		blocking_notifier_call_chain(&inetaddr_chain,  				NETDEV_UP, promote); -		for (ifa = promote->ifa_next; ifa; ifa = ifa->ifa_next) { +		for (ifa = next_sec; ifa; ifa = ifa->ifa_next) {  			if (ifa1->ifa_mask != ifa->ifa_mask ||  			    !inet_ifa_match(ifa1->ifa_address, ifa))  					continue; @@ -325,8 +421,12 @@ static void inet_del_ifa(struct in_device *in_dev, struct in_ifaddr **ifap,  	__inet_del_ifa(in_dev, ifap, destroy, NULL, 0);  } +static void check_lifetime(struct work_struct *work); + +static DECLARE_DELAYED_WORK(check_lifetime_work, check_lifetime); +  static int __inet_insert_ifa(struct in_ifaddr *ifa, struct nlmsghdr *nlh, -			     u32 pid) +			     u32 portid)  {  	struct in_device *in_dev = ifa->ifa_dev;  	struct in_ifaddr *ifa1, **ifap, **last_primary; @@ -361,17 +461,22 @@ static int __inet_insert_ifa(struct in_ifaddr *ifa, struct nlmsghdr *nlh,  	}  	if (!(ifa->ifa_flags & IFA_F_SECONDARY)) { -		net_srandom(ifa->ifa_local); +		prandom_seed((__force u32) ifa->ifa_local);  		ifap = last_primary;  	}  	ifa->ifa_next = *ifap;  	*ifap = ifa; +	inet_hash_insert(dev_net(in_dev->dev), ifa); + +	cancel_delayed_work(&check_lifetime_work); +	queue_delayed_work(system_power_efficient_wq, &check_lifetime_work, 0); +  	/* Send message first, then call notifier.  	   Notifier will trigger FIB update, so that  	   listeners of netlink will know about new ifaddr */ -	rtmsg_ifa(RTM_NEWADDR, ifa, nlh, pid); +	rtmsg_ifa(RTM_NEWADDR, ifa, nlh, portid);  	blocking_notifier_call_chain(&inetaddr_chain, NETDEV_UP, ifa);  	return 0; @@ -393,6 +498,7 @@ static int inet_set_ifa(struct net_device *dev, struct in_ifaddr *ifa)  		return -ENOBUFS;  	}  	ipv4_devconf_setall(in_dev); +	neigh_parms_data_state_setall(in_dev->arp_parms);  	if (ifa->ifa_dev != in_dev) {  		WARN_ON(ifa->ifa_dev);  		in_dev_hold(in_dev); @@ -434,7 +540,7 @@ struct in_ifaddr *inet_ifa_byprefix(struct in_device *in_dev, __be32 prefix,  	return NULL;  } -static int inet_rtm_deladdr(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg) +static int inet_rtm_deladdr(struct sk_buff *skb, struct nlmsghdr *nlh)  {  	struct net *net = sock_net(skb->sk);  	struct nlattr *tb[IFA_MAX+1]; @@ -470,7 +576,7 @@ static int inet_rtm_deladdr(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg  		    !inet_ifa_match(nla_get_be32(tb[IFA_ADDRESS]), ifa)))  			continue; -		__inet_del_ifa(in_dev, ifap, 1, nlh, NETLINK_CB(skb).pid); +		__inet_del_ifa(in_dev, ifap, 1, nlh, NETLINK_CB(skb).portid);  		return 0;  	} @@ -479,7 +585,132 @@ errout:  	return err;  } -static struct in_ifaddr *rtm_to_ifaddr(struct net *net, struct nlmsghdr *nlh) +#define INFINITY_LIFE_TIME	0xFFFFFFFF + +static void check_lifetime(struct work_struct *work) +{ +	unsigned long now, next, next_sec, next_sched; +	struct in_ifaddr *ifa; +	struct hlist_node *n; +	int i; + +	now = jiffies; +	next = round_jiffies_up(now + ADDR_CHECK_FREQUENCY); + +	for (i = 0; i < IN4_ADDR_HSIZE; i++) { +		bool change_needed = false; + +		rcu_read_lock(); +		hlist_for_each_entry_rcu(ifa, &inet_addr_lst[i], hash) { +			unsigned long age; + +			if (ifa->ifa_flags & IFA_F_PERMANENT) +				continue; + +			/* We try to batch several events at once. */ +			age = (now - ifa->ifa_tstamp + +			       ADDRCONF_TIMER_FUZZ_MINUS) / HZ; + +			if (ifa->ifa_valid_lft != INFINITY_LIFE_TIME && +			    age >= ifa->ifa_valid_lft) { +				change_needed = true; +			} else if (ifa->ifa_preferred_lft == +				   INFINITY_LIFE_TIME) { +				continue; +			} else if (age >= ifa->ifa_preferred_lft) { +				if (time_before(ifa->ifa_tstamp + +						ifa->ifa_valid_lft * HZ, next)) +					next = ifa->ifa_tstamp + +					       ifa->ifa_valid_lft * HZ; + +				if (!(ifa->ifa_flags & IFA_F_DEPRECATED)) +					change_needed = true; +			} else if (time_before(ifa->ifa_tstamp + +					       ifa->ifa_preferred_lft * HZ, +					       next)) { +				next = ifa->ifa_tstamp + +				       ifa->ifa_preferred_lft * HZ; +			} +		} +		rcu_read_unlock(); +		if (!change_needed) +			continue; +		rtnl_lock(); +		hlist_for_each_entry_safe(ifa, n, &inet_addr_lst[i], hash) { +			unsigned long age; + +			if (ifa->ifa_flags & IFA_F_PERMANENT) +				continue; + +			/* We try to batch several events at once. */ +			age = (now - ifa->ifa_tstamp + +			       ADDRCONF_TIMER_FUZZ_MINUS) / HZ; + +			if (ifa->ifa_valid_lft != INFINITY_LIFE_TIME && +			    age >= ifa->ifa_valid_lft) { +				struct in_ifaddr **ifap; + +				for (ifap = &ifa->ifa_dev->ifa_list; +				     *ifap != NULL; ifap = &(*ifap)->ifa_next) { +					if (*ifap == ifa) { +						inet_del_ifa(ifa->ifa_dev, +							     ifap, 1); +						break; +					} +				} +			} else if (ifa->ifa_preferred_lft != +				   INFINITY_LIFE_TIME && +				   age >= ifa->ifa_preferred_lft && +				   !(ifa->ifa_flags & IFA_F_DEPRECATED)) { +				ifa->ifa_flags |= IFA_F_DEPRECATED; +				rtmsg_ifa(RTM_NEWADDR, ifa, NULL, 0); +			} +		} +		rtnl_unlock(); +	} + +	next_sec = round_jiffies_up(next); +	next_sched = next; + +	/* If rounded timeout is accurate enough, accept it. */ +	if (time_before(next_sec, next + ADDRCONF_TIMER_FUZZ)) +		next_sched = next_sec; + +	now = jiffies; +	/* And minimum interval is ADDRCONF_TIMER_FUZZ_MAX. */ +	if (time_before(next_sched, now + ADDRCONF_TIMER_FUZZ_MAX)) +		next_sched = now + ADDRCONF_TIMER_FUZZ_MAX; + +	queue_delayed_work(system_power_efficient_wq, &check_lifetime_work, +			next_sched - now); +} + +static void set_ifa_lifetime(struct in_ifaddr *ifa, __u32 valid_lft, +			     __u32 prefered_lft) +{ +	unsigned long timeout; + +	ifa->ifa_flags &= ~(IFA_F_PERMANENT | IFA_F_DEPRECATED); + +	timeout = addrconf_timeout_fixup(valid_lft, HZ); +	if (addrconf_finite_timeout(timeout)) +		ifa->ifa_valid_lft = timeout; +	else +		ifa->ifa_flags |= IFA_F_PERMANENT; + +	timeout = addrconf_timeout_fixup(prefered_lft, HZ); +	if (addrconf_finite_timeout(timeout)) { +		if (timeout == 0) +			ifa->ifa_flags |= IFA_F_DEPRECATED; +		ifa->ifa_preferred_lft = timeout; +	} +	ifa->ifa_tstamp = jiffies; +	if (!ifa->ifa_cstamp) +		ifa->ifa_cstamp = ifa->ifa_tstamp; +} + +static struct in_ifaddr *rtm_to_ifaddr(struct net *net, struct nlmsghdr *nlh, +				       __u32 *pvalid_lft, __u32 *pprefered_lft)  {  	struct nlattr *tb[IFA_MAX+1];  	struct in_ifaddr *ifa; @@ -516,14 +747,17 @@ static struct in_ifaddr *rtm_to_ifaddr(struct net *net, struct nlmsghdr *nlh)  		goto errout;  	ipv4_devconf_setall(in_dev); +	neigh_parms_data_state_setall(in_dev->arp_parms);  	in_dev_hold(in_dev);  	if (tb[IFA_ADDRESS] == NULL)  		tb[IFA_ADDRESS] = tb[IFA_LOCAL]; +	INIT_HLIST_NODE(&ifa->hash);  	ifa->ifa_prefixlen = ifm->ifa_prefixlen;  	ifa->ifa_mask = inet_make_mask(ifm->ifa_prefixlen); -	ifa->ifa_flags = ifm->ifa_flags; +	ifa->ifa_flags = tb[IFA_FLAGS] ? nla_get_u32(tb[IFA_FLAGS]) : +					 ifm->ifa_flags;  	ifa->ifa_scope = ifm->ifa_scope;  	ifa->ifa_dev = in_dev; @@ -538,31 +772,87 @@ static struct in_ifaddr *rtm_to_ifaddr(struct net *net, struct nlmsghdr *nlh)  	else  		memcpy(ifa->ifa_label, dev->name, IFNAMSIZ); +	if (tb[IFA_CACHEINFO]) { +		struct ifa_cacheinfo *ci; + +		ci = nla_data(tb[IFA_CACHEINFO]); +		if (!ci->ifa_valid || ci->ifa_prefered > ci->ifa_valid) { +			err = -EINVAL; +			goto errout_free; +		} +		*pvalid_lft = ci->ifa_valid; +		*pprefered_lft = ci->ifa_prefered; +	} +  	return ifa; +errout_free: +	inet_free_ifa(ifa);  errout:  	return ERR_PTR(err);  } -static int inet_rtm_newaddr(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg) +static struct in_ifaddr *find_matching_ifa(struct in_ifaddr *ifa) +{ +	struct in_device *in_dev = ifa->ifa_dev; +	struct in_ifaddr *ifa1, **ifap; + +	if (!ifa->ifa_local) +		return NULL; + +	for (ifap = &in_dev->ifa_list; (ifa1 = *ifap) != NULL; +	     ifap = &ifa1->ifa_next) { +		if (ifa1->ifa_mask == ifa->ifa_mask && +		    inet_ifa_match(ifa1->ifa_address, ifa) && +		    ifa1->ifa_local == ifa->ifa_local) +			return ifa1; +	} +	return NULL; +} + +static int inet_rtm_newaddr(struct sk_buff *skb, struct nlmsghdr *nlh)  {  	struct net *net = sock_net(skb->sk);  	struct in_ifaddr *ifa; +	struct in_ifaddr *ifa_existing; +	__u32 valid_lft = INFINITY_LIFE_TIME; +	__u32 prefered_lft = INFINITY_LIFE_TIME;  	ASSERT_RTNL(); -	ifa = rtm_to_ifaddr(net, nlh); +	ifa = rtm_to_ifaddr(net, nlh, &valid_lft, &prefered_lft);  	if (IS_ERR(ifa))  		return PTR_ERR(ifa); -	return __inet_insert_ifa(ifa, nlh, NETLINK_CB(skb).pid); +	ifa_existing = find_matching_ifa(ifa); +	if (!ifa_existing) { +		/* It would be best to check for !NLM_F_CREATE here but +		 * userspace already relies on not having to provide this. +		 */ +		set_ifa_lifetime(ifa, valid_lft, prefered_lft); +		return __inet_insert_ifa(ifa, nlh, NETLINK_CB(skb).portid); +	} else { +		inet_free_ifa(ifa); + +		if (nlh->nlmsg_flags & NLM_F_EXCL || +		    !(nlh->nlmsg_flags & NLM_F_REPLACE)) +			return -EEXIST; +		ifa = ifa_existing; +		set_ifa_lifetime(ifa, valid_lft, prefered_lft); +		cancel_delayed_work(&check_lifetime_work); +		queue_delayed_work(system_power_efficient_wq, +				&check_lifetime_work, 0); +		rtmsg_ifa(RTM_NEWADDR, ifa, nlh, NETLINK_CB(skb).portid); +		blocking_notifier_call_chain(&inetaddr_chain, NETDEV_UP, ifa); +	} +	return 0;  }  /*   *	Determine a default network mask, based on the IP address.   */ -static inline int inet_abc_len(__be32 addr) +static int inet_abc_len(__be32 addr)  {  	int rc = -1;	/* Something else, probably a multicast. */ @@ -628,16 +918,16 @@ int devinet_ioctl(struct net *net, unsigned int cmd, void __user *arg)  		break;  	case SIOCSIFFLAGS: -		ret = -EACCES; -		if (!capable(CAP_NET_ADMIN)) +		ret = -EPERM; +		if (!ns_capable(net->user_ns, CAP_NET_ADMIN))  			goto out;  		break;  	case SIOCSIFADDR:	/* Set interface address (and family) */  	case SIOCSIFBRDADDR:	/* Set the broadcast address */  	case SIOCSIFDSTADDR:	/* Set the destination address */  	case SIOCSIFNETMASK: 	/* Set the netmask for the interface */ -		ret = -EACCES; -		if (!capable(CAP_NET_ADMIN)) +		ret = -EPERM; +		if (!ns_capable(net->user_ns, CAP_NET_ADMIN))  			goto out;  		ret = -EINVAL;  		if (sin->sin_family != AF_INET) @@ -670,7 +960,7 @@ int devinet_ioctl(struct net *net, unsigned int cmd, void __user *arg)  			     ifap = &ifa->ifa_next) {  				if (!strcmp(ifr.ifr_name, ifa->ifa_label) &&  				    sin_orig.sin_addr.s_addr == -							ifa->ifa_address) { +							ifa->ifa_local) {  					break; /* found */  				}  			} @@ -730,6 +1020,7 @@ int devinet_ioctl(struct net *net, unsigned int cmd, void __user *arg)  			ifa = inet_alloc_ifa();  			if (!ifa)  				break; +			INIT_HLIST_NODE(&ifa->hash);  			if (colon)  				memcpy(ifa->ifa_label, ifr.ifr_name, IFNAMSIZ);  			else @@ -756,6 +1047,7 @@ int devinet_ioctl(struct net *net, unsigned int cmd, void __user *arg)  			ifa->ifa_prefixlen = 32;  			ifa->ifa_mask = inet_make_mask(32);  		} +		set_ifa_lifetime(ifa, INFINITY_LIFE_TIME, INFINITY_LIFE_TIME);  		ret = inet_set_ifa(dev, ifa);  		break; @@ -841,10 +1133,7 @@ static int inet_gifconf(struct net_device *dev, char __user *buf, int len)  		if (len < (int) sizeof(ifr))  			break;  		memset(&ifr, 0, sizeof(struct ifreq)); -		if (ifa->ifa_label) -			strcpy(ifr.ifr_name, ifa->ifa_label); -		else -			strcpy(ifr.ifr_name, dev->name); +		strcpy(ifr.ifr_name, ifa->ifa_label);  		(*(struct sockaddr_in *)&ifr.ifr_addr).sin_family = AF_INET;  		(*(struct sockaddr_in *)&ifr.ifr_addr).sin_addr.s_addr = @@ -950,22 +1239,21 @@ static __be32 confirm_addr_indev(struct in_device *in_dev, __be32 dst,  /*   * Confirm that local IP address exists using wildcards: - * - in_dev: only on this interface, 0=any interface + * - net: netns to check, cannot be NULL + * - in_dev: only on this interface, NULL=any interface   * - dst: only in the same subnet as dst, 0=any dst   * - local: address, 0=autoselect the local address   * - scope: maximum allowed scope value for the local address   */ -__be32 inet_confirm_addr(struct in_device *in_dev, +__be32 inet_confirm_addr(struct net *net, struct in_device *in_dev,  			 __be32 dst, __be32 local, int scope)  {  	__be32 addr = 0;  	struct net_device *dev; -	struct net *net; -	if (scope != RT_SCOPE_LINK) +	if (in_dev != NULL)  		return confirm_addr_indev(in_dev, dst, local, scope); -	net = dev_net(in_dev->dev);  	rcu_read_lock();  	for_each_netdev_rcu(net, dev) {  		in_dev = __in_dev_get_rcu(dev); @@ -979,6 +1267,7 @@ __be32 inet_confirm_addr(struct in_device *in_dev,  	return addr;  } +EXPORT_SYMBOL(inet_confirm_addr);  /*   *	Device notifier @@ -1025,17 +1314,32 @@ skip:  	}  } -static inline bool inetdev_valid_mtu(unsigned mtu) +static bool inetdev_valid_mtu(unsigned int mtu)  {  	return mtu >= 68;  } +static void inetdev_send_gratuitous_arp(struct net_device *dev, +					struct in_device *in_dev) + +{ +	struct in_ifaddr *ifa; + +	for (ifa = in_dev->ifa_list; ifa; +	     ifa = ifa->ifa_next) { +		arp_send(ARPOP_REQUEST, ETH_P_ARP, +			 ifa->ifa_local, dev, +			 ifa->ifa_local, NULL, +			 dev->dev_addr, NULL); +	} +} +  /* Called only under RTNL semaphore */  static int inetdev_event(struct notifier_block *this, unsigned long event,  			 void *ptr)  { -	struct net_device *dev = ptr; +	struct net_device *dev = netdev_notifier_info_to_dev(ptr);  	struct in_device *in_dev = __in_dev_get_rtnl(dev);  	ASSERT_RTNL(); @@ -1059,8 +1363,8 @@ static int inetdev_event(struct notifier_block *this, unsigned long event,  	switch (event) {  	case NETDEV_REGISTER: -		printk(KERN_DEBUG "inetdev_event: bug\n"); -		rcu_assign_pointer(dev->ip_ptr, NULL); +		pr_debug("%s: bug\n", __func__); +		RCU_INIT_POINTER(dev->ip_ptr, NULL);  		break;  	case NETDEV_UP:  		if (!inetdev_valid_mtu(dev->mtu)) @@ -1069,6 +1373,7 @@ static int inetdev_event(struct notifier_block *this, unsigned long event,  			struct in_ifaddr *ifa = inet_alloc_ifa();  			if (ifa) { +				INIT_HLIST_NODE(&ifa->hash);  				ifa->ifa_local =  				  ifa->ifa_address = htonl(INADDR_LOOPBACK);  				ifa->ifa_prefixlen = 8; @@ -1077,23 +1382,22 @@ static int inetdev_event(struct notifier_block *this, unsigned long event,  				ifa->ifa_dev = in_dev;  				ifa->ifa_scope = RT_SCOPE_HOST;  				memcpy(ifa->ifa_label, dev->name, IFNAMSIZ); +				set_ifa_lifetime(ifa, INFINITY_LIFE_TIME, +						 INFINITY_LIFE_TIME); +				ipv4_devconf_setall(in_dev); +				neigh_parms_data_state_setall(in_dev->arp_parms);  				inet_insert_ifa(ifa);  			}  		}  		ip_mc_up(in_dev);  		/* fall through */ -	case NETDEV_NOTIFY_PEERS:  	case NETDEV_CHANGEADDR: +		if (!IN_DEV_ARP_NOTIFY(in_dev)) +			break; +		/* fall through */ +	case NETDEV_NOTIFY_PEERS:  		/* Send gratuitous ARP to notify of link change */ -		if (IN_DEV_ARP_NOTIFY(in_dev)) { -			struct in_ifaddr *ifa = in_dev->ifa_list; - -			if (ifa) -				arp_send(ARPOP_REQUEST, ETH_P_ARP, -					 ifa->ifa_address, dev, -					 ifa->ifa_address, NULL, -					 dev->dev_addr, NULL); -		} +		inetdev_send_gratuitous_arp(dev, in_dev);  		break;  	case NETDEV_DOWN:  		ip_mc_down(in_dev); @@ -1129,43 +1433,86 @@ static struct notifier_block ip_netdev_notifier = {  	.notifier_call = inetdev_event,  }; -static inline size_t inet_nlmsg_size(void) +static size_t inet_nlmsg_size(void)  {  	return NLMSG_ALIGN(sizeof(struct ifaddrmsg))  	       + nla_total_size(4) /* IFA_ADDRESS */  	       + nla_total_size(4) /* IFA_LOCAL */  	       + nla_total_size(4) /* IFA_BROADCAST */ -	       + nla_total_size(IFNAMSIZ); /* IFA_LABEL */ +	       + nla_total_size(IFNAMSIZ) /* IFA_LABEL */ +	       + nla_total_size(4)  /* IFA_FLAGS */ +	       + nla_total_size(sizeof(struct ifa_cacheinfo)); /* IFA_CACHEINFO */ +} + +static inline u32 cstamp_delta(unsigned long cstamp) +{ +	return (cstamp - INITIAL_JIFFIES) * 100UL / HZ; +} + +static int put_cacheinfo(struct sk_buff *skb, unsigned long cstamp, +			 unsigned long tstamp, u32 preferred, u32 valid) +{ +	struct ifa_cacheinfo ci; + +	ci.cstamp = cstamp_delta(cstamp); +	ci.tstamp = cstamp_delta(tstamp); +	ci.ifa_prefered = preferred; +	ci.ifa_valid = valid; + +	return nla_put(skb, IFA_CACHEINFO, sizeof(ci), &ci);  }  static int inet_fill_ifaddr(struct sk_buff *skb, struct in_ifaddr *ifa, -			    u32 pid, u32 seq, int event, unsigned int flags) +			    u32 portid, u32 seq, int event, unsigned int flags)  {  	struct ifaddrmsg *ifm;  	struct nlmsghdr  *nlh; +	u32 preferred, valid; -	nlh = nlmsg_put(skb, pid, seq, event, sizeof(*ifm), flags); +	nlh = nlmsg_put(skb, portid, seq, event, sizeof(*ifm), flags);  	if (nlh == NULL)  		return -EMSGSIZE;  	ifm = nlmsg_data(nlh);  	ifm->ifa_family = AF_INET;  	ifm->ifa_prefixlen = ifa->ifa_prefixlen; -	ifm->ifa_flags = ifa->ifa_flags|IFA_F_PERMANENT; +	ifm->ifa_flags = ifa->ifa_flags;  	ifm->ifa_scope = ifa->ifa_scope;  	ifm->ifa_index = ifa->ifa_dev->dev->ifindex; -	if (ifa->ifa_address) -		NLA_PUT_BE32(skb, IFA_ADDRESS, ifa->ifa_address); +	if (!(ifm->ifa_flags & IFA_F_PERMANENT)) { +		preferred = ifa->ifa_preferred_lft; +		valid = ifa->ifa_valid_lft; +		if (preferred != INFINITY_LIFE_TIME) { +			long tval = (jiffies - ifa->ifa_tstamp) / HZ; -	if (ifa->ifa_local) -		NLA_PUT_BE32(skb, IFA_LOCAL, ifa->ifa_local); - -	if (ifa->ifa_broadcast) -		NLA_PUT_BE32(skb, IFA_BROADCAST, ifa->ifa_broadcast); - -	if (ifa->ifa_label[0]) -		NLA_PUT_STRING(skb, IFA_LABEL, ifa->ifa_label); +			if (preferred > tval) +				preferred -= tval; +			else +				preferred = 0; +			if (valid != INFINITY_LIFE_TIME) { +				if (valid > tval) +					valid -= tval; +				else +					valid = 0; +			} +		} +	} else { +		preferred = INFINITY_LIFE_TIME; +		valid = INFINITY_LIFE_TIME; +	} +	if ((ifa->ifa_address && +	     nla_put_be32(skb, IFA_ADDRESS, ifa->ifa_address)) || +	    (ifa->ifa_local && +	     nla_put_be32(skb, IFA_LOCAL, ifa->ifa_local)) || +	    (ifa->ifa_broadcast && +	     nla_put_be32(skb, IFA_BROADCAST, ifa->ifa_broadcast)) || +	    (ifa->ifa_label[0] && +	     nla_put_string(skb, IFA_LABEL, ifa->ifa_label)) || +	    nla_put_u32(skb, IFA_FLAGS, ifa->ifa_flags) || +	    put_cacheinfo(skb, ifa->ifa_cstamp, ifa->ifa_tstamp, +			  preferred, valid)) +		goto nla_put_failure;  	return nlmsg_end(skb, nlh); @@ -1184,7 +1531,6 @@ static int inet_dump_ifaddr(struct sk_buff *skb, struct netlink_callback *cb)  	struct in_device *in_dev;  	struct in_ifaddr *ifa;  	struct hlist_head *head; -	struct hlist_node *node;  	s_h = cb->args[0];  	s_idx = idx = cb->args[1]; @@ -1194,7 +1540,9 @@ static int inet_dump_ifaddr(struct sk_buff *skb, struct netlink_callback *cb)  		idx = 0;  		head = &net->dev_index_head[h];  		rcu_read_lock(); -		hlist_for_each_entry_rcu(dev, node, head, index_hlist) { +		cb->seq = atomic_read(&net->ipv4.dev_addr_genid) ^ +			  net->dev_base_seq; +		hlist_for_each_entry_rcu(dev, head, index_hlist) {  			if (idx < s_idx)  				goto cont;  			if (h > s_h || idx > s_idx) @@ -1208,12 +1556,13 @@ static int inet_dump_ifaddr(struct sk_buff *skb, struct netlink_callback *cb)  				if (ip_idx < s_ip_idx)  					continue;  				if (inet_fill_ifaddr(skb, ifa, -					     NETLINK_CB(cb->skb).pid, +					     NETLINK_CB(cb->skb).portid,  					     cb->nlh->nlmsg_seq,  					     RTM_NEWADDR, NLM_F_MULTI) <= 0) {  					rcu_read_unlock();  					goto done;  				} +				nl_dump_check_consistent(cb, nlmsg_hdr(skb));  			}  cont:  			idx++; @@ -1230,7 +1579,7 @@ done:  }  static void rtmsg_ifa(int event, struct in_ifaddr *ifa, struct nlmsghdr *nlh, -		      u32 pid) +		      u32 portid)  {  	struct sk_buff *skb;  	u32 seq = nlh ? nlh->nlmsg_seq : 0; @@ -1242,14 +1591,14 @@ static void rtmsg_ifa(int event, struct in_ifaddr *ifa, struct nlmsghdr *nlh,  	if (skb == NULL)  		goto errout; -	err = inet_fill_ifaddr(skb, ifa, pid, seq, event, 0); +	err = inet_fill_ifaddr(skb, ifa, portid, seq, event, 0);  	if (err < 0) {  		/* -EMSGSIZE implies BUG in inet_nlmsg_size() */  		WARN_ON(err == -EMSGSIZE);  		kfree_skb(skb);  		goto errout;  	} -	rtnl_notify(skb, net, pid, RTNLGRP_IPV4_IFADDR, nlh, GFP_KERNEL); +	rtnl_notify(skb, net, portid, RTNLGRP_IPV4_IFADDR, nlh, GFP_KERNEL);  	return;  errout:  	if (err < 0) @@ -1258,7 +1607,7 @@ errout:  static size_t inet_get_link_af_size(const struct net_device *dev)  { -	struct in_device *in_dev = __in_dev_get_rtnl(dev); +	struct in_device *in_dev = rcu_dereference_rtnl(dev->ip_ptr);  	if (!in_dev)  		return 0; @@ -1268,7 +1617,7 @@ static size_t inet_get_link_af_size(const struct net_device *dev)  static int inet_fill_link_af(struct sk_buff *skb, const struct net_device *dev)  { -	struct in_device *in_dev = __in_dev_get_rtnl(dev); +	struct in_device *in_dev = rcu_dereference_rtnl(dev->ip_ptr);  	struct nlattr *nla;  	int i; @@ -1337,6 +1686,232 @@ static int inet_set_link_af(struct net_device *dev, const struct nlattr *nla)  	return 0;  } +static int inet_netconf_msgsize_devconf(int type) +{ +	int size = NLMSG_ALIGN(sizeof(struct netconfmsg)) +		   + nla_total_size(4);	/* NETCONFA_IFINDEX */ + +	/* type -1 is used for ALL */ +	if (type == -1 || type == NETCONFA_FORWARDING) +		size += nla_total_size(4); +	if (type == -1 || type == NETCONFA_RP_FILTER) +		size += nla_total_size(4); +	if (type == -1 || type == NETCONFA_MC_FORWARDING) +		size += nla_total_size(4); +	if (type == -1 || type == NETCONFA_PROXY_NEIGH) +		size += nla_total_size(4); + +	return size; +} + +static int inet_netconf_fill_devconf(struct sk_buff *skb, int ifindex, +				     struct ipv4_devconf *devconf, u32 portid, +				     u32 seq, int event, unsigned int flags, +				     int type) +{ +	struct nlmsghdr  *nlh; +	struct netconfmsg *ncm; + +	nlh = nlmsg_put(skb, portid, seq, event, sizeof(struct netconfmsg), +			flags); +	if (nlh == NULL) +		return -EMSGSIZE; + +	ncm = nlmsg_data(nlh); +	ncm->ncm_family = AF_INET; + +	if (nla_put_s32(skb, NETCONFA_IFINDEX, ifindex) < 0) +		goto nla_put_failure; + +	/* type -1 is used for ALL */ +	if ((type == -1 || type == NETCONFA_FORWARDING) && +	    nla_put_s32(skb, NETCONFA_FORWARDING, +			IPV4_DEVCONF(*devconf, FORWARDING)) < 0) +		goto nla_put_failure; +	if ((type == -1 || type == NETCONFA_RP_FILTER) && +	    nla_put_s32(skb, NETCONFA_RP_FILTER, +			IPV4_DEVCONF(*devconf, RP_FILTER)) < 0) +		goto nla_put_failure; +	if ((type == -1 || type == NETCONFA_MC_FORWARDING) && +	    nla_put_s32(skb, NETCONFA_MC_FORWARDING, +			IPV4_DEVCONF(*devconf, MC_FORWARDING)) < 0) +		goto nla_put_failure; +	if ((type == -1 || type == NETCONFA_PROXY_NEIGH) && +	    nla_put_s32(skb, NETCONFA_PROXY_NEIGH, +			IPV4_DEVCONF(*devconf, PROXY_ARP)) < 0) +		goto nla_put_failure; + +	return nlmsg_end(skb, nlh); + +nla_put_failure: +	nlmsg_cancel(skb, nlh); +	return -EMSGSIZE; +} + +void inet_netconf_notify_devconf(struct net *net, int type, int ifindex, +				 struct ipv4_devconf *devconf) +{ +	struct sk_buff *skb; +	int err = -ENOBUFS; + +	skb = nlmsg_new(inet_netconf_msgsize_devconf(type), GFP_ATOMIC); +	if (skb == NULL) +		goto errout; + +	err = inet_netconf_fill_devconf(skb, ifindex, devconf, 0, 0, +					RTM_NEWNETCONF, 0, type); +	if (err < 0) { +		/* -EMSGSIZE implies BUG in inet_netconf_msgsize_devconf() */ +		WARN_ON(err == -EMSGSIZE); +		kfree_skb(skb); +		goto errout; +	} +	rtnl_notify(skb, net, 0, RTNLGRP_IPV4_NETCONF, NULL, GFP_ATOMIC); +	return; +errout: +	if (err < 0) +		rtnl_set_sk_err(net, RTNLGRP_IPV4_NETCONF, err); +} + +static const struct nla_policy devconf_ipv4_policy[NETCONFA_MAX+1] = { +	[NETCONFA_IFINDEX]	= { .len = sizeof(int) }, +	[NETCONFA_FORWARDING]	= { .len = sizeof(int) }, +	[NETCONFA_RP_FILTER]	= { .len = sizeof(int) }, +	[NETCONFA_PROXY_NEIGH]	= { .len = sizeof(int) }, +}; + +static int inet_netconf_get_devconf(struct sk_buff *in_skb, +				    struct nlmsghdr *nlh) +{ +	struct net *net = sock_net(in_skb->sk); +	struct nlattr *tb[NETCONFA_MAX+1]; +	struct netconfmsg *ncm; +	struct sk_buff *skb; +	struct ipv4_devconf *devconf; +	struct in_device *in_dev; +	struct net_device *dev; +	int ifindex; +	int err; + +	err = nlmsg_parse(nlh, sizeof(*ncm), tb, NETCONFA_MAX, +			  devconf_ipv4_policy); +	if (err < 0) +		goto errout; + +	err = EINVAL; +	if (!tb[NETCONFA_IFINDEX]) +		goto errout; + +	ifindex = nla_get_s32(tb[NETCONFA_IFINDEX]); +	switch (ifindex) { +	case NETCONFA_IFINDEX_ALL: +		devconf = net->ipv4.devconf_all; +		break; +	case NETCONFA_IFINDEX_DEFAULT: +		devconf = net->ipv4.devconf_dflt; +		break; +	default: +		dev = __dev_get_by_index(net, ifindex); +		if (dev == NULL) +			goto errout; +		in_dev = __in_dev_get_rtnl(dev); +		if (in_dev == NULL) +			goto errout; +		devconf = &in_dev->cnf; +		break; +	} + +	err = -ENOBUFS; +	skb = nlmsg_new(inet_netconf_msgsize_devconf(-1), GFP_ATOMIC); +	if (skb == NULL) +		goto errout; + +	err = inet_netconf_fill_devconf(skb, ifindex, devconf, +					NETLINK_CB(in_skb).portid, +					nlh->nlmsg_seq, RTM_NEWNETCONF, 0, +					-1); +	if (err < 0) { +		/* -EMSGSIZE implies BUG in inet_netconf_msgsize_devconf() */ +		WARN_ON(err == -EMSGSIZE); +		kfree_skb(skb); +		goto errout; +	} +	err = rtnl_unicast(skb, net, NETLINK_CB(in_skb).portid); +errout: +	return err; +} + +static int inet_netconf_dump_devconf(struct sk_buff *skb, +				     struct netlink_callback *cb) +{ +	struct net *net = sock_net(skb->sk); +	int h, s_h; +	int idx, s_idx; +	struct net_device *dev; +	struct in_device *in_dev; +	struct hlist_head *head; + +	s_h = cb->args[0]; +	s_idx = idx = cb->args[1]; + +	for (h = s_h; h < NETDEV_HASHENTRIES; h++, s_idx = 0) { +		idx = 0; +		head = &net->dev_index_head[h]; +		rcu_read_lock(); +		cb->seq = atomic_read(&net->ipv4.dev_addr_genid) ^ +			  net->dev_base_seq; +		hlist_for_each_entry_rcu(dev, head, index_hlist) { +			if (idx < s_idx) +				goto cont; +			in_dev = __in_dev_get_rcu(dev); +			if (!in_dev) +				goto cont; + +			if (inet_netconf_fill_devconf(skb, dev->ifindex, +						      &in_dev->cnf, +						      NETLINK_CB(cb->skb).portid, +						      cb->nlh->nlmsg_seq, +						      RTM_NEWNETCONF, +						      NLM_F_MULTI, +						      -1) <= 0) { +				rcu_read_unlock(); +				goto done; +			} +			nl_dump_check_consistent(cb, nlmsg_hdr(skb)); +cont: +			idx++; +		} +		rcu_read_unlock(); +	} +	if (h == NETDEV_HASHENTRIES) { +		if (inet_netconf_fill_devconf(skb, NETCONFA_IFINDEX_ALL, +					      net->ipv4.devconf_all, +					      NETLINK_CB(cb->skb).portid, +					      cb->nlh->nlmsg_seq, +					      RTM_NEWNETCONF, NLM_F_MULTI, +					      -1) <= 0) +			goto done; +		else +			h++; +	} +	if (h == NETDEV_HASHENTRIES + 1) { +		if (inet_netconf_fill_devconf(skb, NETCONFA_IFINDEX_DEFAULT, +					      net->ipv4.devconf_dflt, +					      NETLINK_CB(cb->skb).portid, +					      cb->nlh->nlmsg_seq, +					      RTM_NEWNETCONF, NLM_F_MULTI, +					      -1) <= 0) +			goto done; +		else +			h++; +	} +done: +	cb->args[0] = h; +	cb->args[1] = idx; + +	return skb->len; +} +  #ifdef CONFIG_SYSCTL  static void devinet_copy_dflt_conf(struct net *net, int i) @@ -1362,6 +1937,12 @@ static void inet_forward_change(struct net *net)  	IPV4_DEVCONF_ALL(net, ACCEPT_REDIRECTS) = !on;  	IPV4_DEVCONF_DFLT(net, FORWARDING) = on; +	inet_netconf_notify_devconf(net, NETCONFA_FORWARDING, +				    NETCONFA_IFINDEX_ALL, +				    net->ipv4.devconf_all); +	inet_netconf_notify_devconf(net, NETCONFA_FORWARDING, +				    NETCONFA_IFINDEX_DEFAULT, +				    net->ipv4.devconf_dflt);  	for_each_netdev(net, dev) {  		struct in_device *in_dev; @@ -1369,33 +1950,69 @@ static void inet_forward_change(struct net *net)  			dev_disable_lro(dev);  		rcu_read_lock();  		in_dev = __in_dev_get_rcu(dev); -		if (in_dev) +		if (in_dev) {  			IN_DEV_CONF_SET(in_dev, FORWARDING, on); +			inet_netconf_notify_devconf(net, NETCONFA_FORWARDING, +						    dev->ifindex, &in_dev->cnf); +		}  		rcu_read_unlock();  	}  } -static int devinet_conf_proc(ctl_table *ctl, int write, +static int devinet_conf_ifindex(struct net *net, struct ipv4_devconf *cnf) +{ +	if (cnf == net->ipv4.devconf_dflt) +		return NETCONFA_IFINDEX_DEFAULT; +	else if (cnf == net->ipv4.devconf_all) +		return NETCONFA_IFINDEX_ALL; +	else { +		struct in_device *idev +			= container_of(cnf, struct in_device, cnf); +		return idev->dev->ifindex; +	} +} + +static int devinet_conf_proc(struct ctl_table *ctl, int write,  			     void __user *buffer,  			     size_t *lenp, loff_t *ppos)  { +	int old_value = *(int *)ctl->data;  	int ret = proc_dointvec(ctl, write, buffer, lenp, ppos); +	int new_value = *(int *)ctl->data;  	if (write) {  		struct ipv4_devconf *cnf = ctl->extra1;  		struct net *net = ctl->extra2;  		int i = (int *)ctl->data - cnf->data; +		int ifindex;  		set_bit(i, cnf->state);  		if (cnf == net->ipv4.devconf_dflt)  			devinet_copy_dflt_conf(net, i); +		if (i == IPV4_DEVCONF_ACCEPT_LOCAL - 1 || +		    i == IPV4_DEVCONF_ROUTE_LOCALNET - 1) +			if ((new_value == 0) && (old_value != 0)) +				rt_cache_flush(net); + +		if (i == IPV4_DEVCONF_RP_FILTER - 1 && +		    new_value != old_value) { +			ifindex = devinet_conf_ifindex(net, cnf); +			inet_netconf_notify_devconf(net, NETCONFA_RP_FILTER, +						    ifindex, cnf); +		} +		if (i == IPV4_DEVCONF_PROXY_ARP - 1 && +		    new_value != old_value) { +			ifindex = devinet_conf_ifindex(net, cnf); +			inet_netconf_notify_devconf(net, NETCONFA_PROXY_NEIGH, +						    ifindex, cnf); +		}  	}  	return ret;  } -static int devinet_sysctl_forward(ctl_table *ctl, int write, +static int devinet_sysctl_forward(struct ctl_table *ctl, int write,  				  void __user *buffer,  				  size_t *lenp, loff_t *ppos)  { @@ -1416,23 +2033,31 @@ static int devinet_sysctl_forward(ctl_table *ctl, int write,  			}  			if (valp == &IPV4_DEVCONF_ALL(net, FORWARDING)) {  				inet_forward_change(net); -			} else if (*valp) { +			} else {  				struct ipv4_devconf *cnf = ctl->extra1;  				struct in_device *idev =  					container_of(cnf, struct in_device, cnf); -				dev_disable_lro(idev->dev); +				if (*valp) +					dev_disable_lro(idev->dev); +				inet_netconf_notify_devconf(net, +							    NETCONFA_FORWARDING, +							    idev->dev->ifindex, +							    cnf);  			}  			rtnl_unlock(); -			rt_cache_flush(net, 0); -		} +			rt_cache_flush(net); +		} else +			inet_netconf_notify_devconf(net, NETCONFA_FORWARDING, +						    NETCONFA_IFINDEX_DEFAULT, +						    net->ipv4.devconf_dflt);  	}  	return ret;  } -int ipv4_doint_and_flush(ctl_table *ctl, int write, -			 void __user *buffer, -			 size_t *lenp, loff_t *ppos) +static int ipv4_doint_and_flush(struct ctl_table *ctl, int write, +				void __user *buffer, +				size_t *lenp, loff_t *ppos)  {  	int *valp = ctl->data;  	int val = *valp; @@ -1440,7 +2065,7 @@ int ipv4_doint_and_flush(ctl_table *ctl, int write,  	struct net *net = ctl->extra2;  	if (write && *valp != val) -		rt_cache_flush(net, 0); +		rt_cache_flush(net);  	return ret;  } @@ -1471,7 +2096,6 @@ int ipv4_doint_and_flush(ctl_table *ctl, int write,  static struct devinet_sysctl_table {  	struct ctl_table_header *sysctl_header;  	struct ctl_table devinet_vars[__IPV4_DEVCONF_MAX]; -	char *dev_name;  } devinet_sysctl = {  	.devinet_vars = {  		DEVINET_SYSCTL_COMPLEX_ENTRY(FORWARDING, "forwarding", @@ -1498,13 +2122,19 @@ static struct devinet_sysctl_table {  		DEVINET_SYSCTL_RW_ENTRY(ARP_ACCEPT, "arp_accept"),  		DEVINET_SYSCTL_RW_ENTRY(ARP_NOTIFY, "arp_notify"),  		DEVINET_SYSCTL_RW_ENTRY(PROXY_ARP_PVLAN, "proxy_arp_pvlan"), +		DEVINET_SYSCTL_RW_ENTRY(FORCE_IGMP_VERSION, +					"force_igmp_version"), +		DEVINET_SYSCTL_RW_ENTRY(IGMPV2_UNSOLICITED_REPORT_INTERVAL, +					"igmpv2_unsolicited_report_interval"), +		DEVINET_SYSCTL_RW_ENTRY(IGMPV3_UNSOLICITED_REPORT_INTERVAL, +					"igmpv3_unsolicited_report_interval"),  		DEVINET_SYSCTL_FLUSHING_ENTRY(NOXFRM, "disable_xfrm"),  		DEVINET_SYSCTL_FLUSHING_ENTRY(NOPOLICY, "disable_policy"), -		DEVINET_SYSCTL_FLUSHING_ENTRY(FORCE_IGMP_VERSION, -					      "force_igmp_version"),  		DEVINET_SYSCTL_FLUSHING_ENTRY(PROMOTE_SECONDARIES,  					      "promote_secondaries"), +		DEVINET_SYSCTL_FLUSHING_ENTRY(ROUTE_LOCALNET, +					      "route_localnet"),  	},  }; @@ -1513,16 +2143,7 @@ static int __devinet_sysctl_register(struct net *net, char *dev_name,  {  	int i;  	struct devinet_sysctl_table *t; - -#define DEVINET_CTL_PATH_DEV	3 - -	struct ctl_path devinet_ctl_path[] = { -		{ .procname = "net",  }, -		{ .procname = "ipv4", }, -		{ .procname = "conf", }, -		{ /* to be set */ }, -		{ }, -	}; +	char path[sizeof("net/ipv4/conf/") + IFNAMSIZ];  	t = kmemdup(&devinet_sysctl, sizeof(*t), GFP_KERNEL);  	if (!t) @@ -1534,27 +2155,15 @@ static int __devinet_sysctl_register(struct net *net, char *dev_name,  		t->devinet_vars[i].extra2 = net;  	} -	/* -	 * Make a copy of dev_name, because '.procname' is regarded as const -	 * by sysctl and we wouldn't want anyone to change it under our feet -	 * (see SIOCSIFNAME). -	 */ -	t->dev_name = kstrdup(dev_name, GFP_KERNEL); -	if (!t->dev_name) -		goto free; - -	devinet_ctl_path[DEVINET_CTL_PATH_DEV].procname = t->dev_name; +	snprintf(path, sizeof(path), "net/ipv4/conf/%s", dev_name); -	t->sysctl_header = register_net_sysctl_table(net, devinet_ctl_path, -			t->devinet_vars); +	t->sysctl_header = register_net_sysctl(net, path, t->devinet_vars);  	if (!t->sysctl_header) -		goto free_procname; +		goto free;  	p->sysctl = t;  	return 0; -free_procname: -	kfree(t->dev_name);  free:  	kfree(t);  out: @@ -1569,14 +2178,13 @@ static void __devinet_sysctl_unregister(struct ipv4_devconf *cnf)  		return;  	cnf->sysctl = NULL; -	unregister_sysctl_table(t->sysctl_header); -	kfree(t->dev_name); +	unregister_net_sysctl_table(t->sysctl_header);  	kfree(t);  }  static void devinet_sysctl_register(struct in_device *idev)  { -	neigh_sysctl_register(idev->dev, idev->arp_parms, "ipv4", NULL); +	neigh_sysctl_register(idev->dev, idev->arp_parms, NULL);  	__devinet_sysctl_register(dev_net(idev->dev), idev->dev->name,  					&idev->cnf);  } @@ -1600,12 +2208,6 @@ static struct ctl_table ctl_forward_entry[] = {  	},  	{ },  }; - -static __net_initdata struct ctl_path net_ipv4_path[] = { -	{ .procname = "net", }, -	{ .procname = "ipv4", }, -	{ }, -};  #endif  static __net_init int devinet_init_net(struct net *net) @@ -1651,7 +2253,7 @@ static __net_init int devinet_init_net(struct net *net)  		goto err_reg_dflt;  	err = -ENOMEM; -	forw_hdr = register_net_sysctl_table(net, net_ipv4_path, tbl); +	forw_hdr = register_net_sysctl(net, "net/ipv4", tbl);  	if (forw_hdr == NULL)  		goto err_reg_ctl;  	net->ipv4.forw_hdr = forw_hdr; @@ -1710,15 +2312,24 @@ static struct rtnl_af_ops inet_af_ops = {  void __init devinet_init(void)  { +	int i; + +	for (i = 0; i < IN4_ADDR_HSIZE; i++) +		INIT_HLIST_HEAD(&inet_addr_lst[i]); +  	register_pernet_subsys(&devinet_ops);  	register_gifconf(PF_INET, inet_gifconf);  	register_netdevice_notifier(&ip_netdev_notifier); +	queue_delayed_work(system_power_efficient_wq, &check_lifetime_work, 0); +  	rtnl_af_register(&inet_af_ops); -	rtnl_register(PF_INET, RTM_NEWADDR, inet_rtm_newaddr, NULL); -	rtnl_register(PF_INET, RTM_DELADDR, inet_rtm_deladdr, NULL); -	rtnl_register(PF_INET, RTM_GETADDR, NULL, inet_dump_ifaddr); +	rtnl_register(PF_INET, RTM_NEWADDR, inet_rtm_newaddr, NULL, NULL); +	rtnl_register(PF_INET, RTM_DELADDR, inet_rtm_deladdr, NULL, NULL); +	rtnl_register(PF_INET, RTM_GETADDR, NULL, inet_dump_ifaddr, NULL); +	rtnl_register(PF_INET, RTM_GETNETCONF, inet_netconf_get_devconf, +		      inet_netconf_dump_devconf, NULL);  }  | 
