Skip to content
Snippets Groups Projects
addrconf.c 126 KiB
Newer Older
  • Learn to ignore specific revisions
  • Linus Torvalds's avatar
    Linus Torvalds committed
    /*
     *	IPv6 Address [auto]configuration
     *	Linux INET6 implementation
     *
     *	Authors:
    
     *	Pedro Roque		<roque@di.fc.ul.pt>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     *	Alexey Kuznetsov	<kuznet@ms2.inr.ac.ru>
     *
     *	This program is free software; you can redistribute it and/or
     *      modify it under the terms of the GNU General Public License
     *      as published by the Free Software Foundation; either version
     *      2 of the License, or (at your option) any later version.
     */
    
    /*
     *	Changes:
     *
     *	Janos Farkas			:	delete timer on ifdown
     *	<chexum@bankinf.banki.hu>
     *	Andi Kleen			:	kill double kfree on module
     *						unload.
     *	Maciej W. Rozycki		:	FDDI support
     *	sekiya@USAGI			:	Don't send too many RS
     *						packets.
     *	yoshfuji@USAGI			:       Fixed interval between DAD
     *						packets.
     *	YOSHIFUJI Hideaki @USAGI	:	improved accuracy of
     *						address validation timer.
     *	YOSHIFUJI Hideaki @USAGI	:	Privacy Extensions (RFC3041)
     *						support.
     *	Yuji SEKIYA @USAGI		:	Don't assign a same IPv6
     *						address on a same interface.
     *	YOSHIFUJI Hideaki @USAGI	:	ARCnet support
     *	YOSHIFUJI Hideaki @USAGI	:	convert /proc/net/if_inet6 to
     *						seq_file.
    
     *	YOSHIFUJI Hideaki @USAGI	:	improved source address
     *						selection; consider scope,
     *						status etc.
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     */
    
    
    #define pr_fmt(fmt) "IPv6: " fmt
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #include <linux/errno.h>
    #include <linux/types.h>
    
    #include <linux/kernel.h>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #include <linux/socket.h>
    #include <linux/sockios.h>
    #include <linux/net.h>
    #include <linux/in6.h>
    #include <linux/netdevice.h>
    
    #include <linux/if_addr.h>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #include <linux/if_arp.h>
    #include <linux/if_arcnet.h>
    #include <linux/if_infiniband.h>
    #include <linux/route.h>
    #include <linux/inetdevice.h>
    #include <linux/init.h>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #ifdef CONFIG_SYSCTL
    #include <linux/sysctl.h>
    #endif
    
    #include <linux/capability.h>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #include <linux/delay.h>
    #include <linux/notifier.h>
    
    #include <linux/string.h>
    
    #include <linux/hash.h>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    #include <net/net_namespace.h>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #include <net/sock.h>
    #include <net/snmp.h>
    
    
    #include <net/af_ieee802154.h>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #include <net/ipv6.h>
    #include <net/protocol.h>
    #include <net/ndisc.h>
    #include <net/ip6_route.h>
    #include <net/addrconf.h>
    #include <net/tcp.h>
    #include <net/ip.h>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #include <linux/if_tunnel.h>
    #include <linux/rtnetlink.h>
    
    #include <linux/netconf.h>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    #ifdef CONFIG_IPV6_PRIVACY
    #include <linux/random.h>
    #endif
    
    
    #include <linux/uaccess.h>
    
    #include <asm/unaligned.h>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    #include <linux/proc_fs.h>
    #include <linux/seq_file.h>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    /* Set to 3 to get tracing... */
    #define ACONF_DEBUG 2
    
    #if ACONF_DEBUG >= 3
    
    #define ADBG(fmt, ...) printk(fmt, ##__VA_ARGS__)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #else
    
    #define ADBG(fmt, ...) do { if (0) printk(fmt, ##__VA_ARGS__); } while (0)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #endif
    
    #define	INFINITY_LIFE_TIME	0xFFFFFFFF
    
    
    static inline u32 cstamp_delta(unsigned long cstamp)
    {
    	return (cstamp - INITIAL_JIFFIES) * 100UL / HZ;
    }
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    #ifdef CONFIG_SYSCTL
    
    static void addrconf_sysctl_register(struct inet6_dev *idev);
    
    static void addrconf_sysctl_unregister(struct inet6_dev *idev);
    #else
    static inline void addrconf_sysctl_register(struct inet6_dev *idev)
    {
    }
    
    static inline void addrconf_sysctl_unregister(struct inet6_dev *idev)
    {
    }
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #endif
    
    #ifdef CONFIG_IPV6_PRIVACY
    
    static void __ipv6_regen_rndid(struct inet6_dev *idev);
    static void __ipv6_try_regen_rndid(struct inet6_dev *idev, struct in6_addr *tmpaddr);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    static void ipv6_regen_rndid(unsigned long data);
    #endif
    
    
    static int ipv6_generate_eui64(u8 *eui, struct net_device *dev);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    static int ipv6_count_addresses(struct inet6_dev *idev);
    
    /*
     *	Configured unicast address hash table
     */
    
    static struct hlist_head inet6_addr_lst[IN6_ADDR_HSIZE];
    
    static DEFINE_SPINLOCK(addrconf_hash_lock);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    static void addrconf_verify(unsigned long);
    
    
    static DEFINE_TIMER(addr_chk_timer, addrconf_verify, 0, 0);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    static DEFINE_SPINLOCK(addrconf_verify_lock);
    
    static void addrconf_join_anycast(struct inet6_ifaddr *ifp);
    static void addrconf_leave_anycast(struct inet6_ifaddr *ifp);
    
    
    static void addrconf_type_change(struct net_device *dev,
    				 unsigned long event);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    static int addrconf_ifdown(struct net_device *dev, int how);
    
    
    static struct rt6_info *addrconf_get_prefix_route(const struct in6_addr *pfx,
    						  int plen,
    						  const struct net_device *dev,
    						  u32 flags, u32 noflags);
    
    
    static void addrconf_dad_start(struct inet6_ifaddr *ifp);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    static void addrconf_dad_timer(unsigned long data);
    static void addrconf_dad_completed(struct inet6_ifaddr *ifp);
    
    static void addrconf_dad_run(struct inet6_dev *idev);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    static void addrconf_rs_timer(unsigned long data);
    static void __ipv6_ifa_notify(int event, struct inet6_ifaddr *ifa);
    static void ipv6_ifa_notify(int event, struct inet6_ifaddr *ifa);
    
    
    static void inet6_prefix_notify(int event, struct inet6_dev *idev,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				struct prefix_info *pinfo);
    
    static bool ipv6_chk_same_addr(struct net *net, const struct in6_addr *addr,
    			       struct net_device *dev);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    static struct ipv6_devconf ipv6_devconf __read_mostly = {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	.forwarding		= 0,
    	.hop_limit		= IPV6_DEFAULT_HOPLIMIT,
    	.mtu6			= IPV6_MIN_MTU,
    	.accept_ra		= 1,
    	.accept_redirects	= 1,
    	.autoconf		= 1,
    	.force_mld_version	= 0,
    
    	.mldv1_unsolicited_report_interval = 10 * HZ,
    	.mldv2_unsolicited_report_interval = HZ,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	.dad_transmits		= 1,
    	.rtr_solicits		= MAX_RTR_SOLICITATIONS,
    	.rtr_solicit_interval	= RTR_SOLICITATION_INTERVAL,
    	.rtr_solicit_delay	= MAX_RTR_SOLICITATION_DELAY,
    #ifdef CONFIG_IPV6_PRIVACY
    	.use_tempaddr 		= 0,
    	.temp_valid_lft		= TEMP_VALID_LIFETIME,
    	.temp_prefered_lft	= TEMP_PREFERRED_LIFETIME,
    	.regen_max_retry	= REGEN_MAX_RETRY,
    	.max_desync_factor	= MAX_DESYNC_FACTOR,
    #endif
    	.max_addresses		= IPV6_MAX_ADDRESSES,
    
    	.accept_ra_defrtr	= 1,
    
    	.accept_ra_pinfo	= 1,
    
    #ifdef CONFIG_IPV6_ROUTER_PREF
    	.accept_ra_rtr_pref	= 1,
    
    	.rtr_probe_interval	= 60 * HZ,
    
    #ifdef CONFIG_IPV6_ROUTE_INFO
    	.accept_ra_rt_info_max_plen = 0,
    #endif
    
    	.proxy_ndp		= 0,
    
    	.accept_source_route	= 0,	/* we do not accept RH0 by default. */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    };
    
    
    static struct ipv6_devconf ipv6_devconf_dflt __read_mostly = {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	.forwarding		= 0,
    	.hop_limit		= IPV6_DEFAULT_HOPLIMIT,
    	.mtu6			= IPV6_MIN_MTU,
    	.accept_ra		= 1,
    	.accept_redirects	= 1,
    	.autoconf		= 1,
    
    	.force_mld_version	= 0,
    	.mldv1_unsolicited_report_interval = 10 * HZ,
    	.mldv2_unsolicited_report_interval = HZ,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	.dad_transmits		= 1,
    	.rtr_solicits		= MAX_RTR_SOLICITATIONS,
    	.rtr_solicit_interval	= RTR_SOLICITATION_INTERVAL,
    	.rtr_solicit_delay	= MAX_RTR_SOLICITATION_DELAY,
    #ifdef CONFIG_IPV6_PRIVACY
    	.use_tempaddr		= 0,
    	.temp_valid_lft		= TEMP_VALID_LIFETIME,
    	.temp_prefered_lft	= TEMP_PREFERRED_LIFETIME,
    	.regen_max_retry	= REGEN_MAX_RETRY,
    	.max_desync_factor	= MAX_DESYNC_FACTOR,
    #endif
    	.max_addresses		= IPV6_MAX_ADDRESSES,
    
    	.accept_ra_defrtr	= 1,
    
    	.accept_ra_pinfo	= 1,
    
    #ifdef CONFIG_IPV6_ROUTER_PREF
    	.accept_ra_rtr_pref	= 1,
    
    	.rtr_probe_interval	= 60 * HZ,
    
    #ifdef CONFIG_IPV6_ROUTE_INFO
    	.accept_ra_rt_info_max_plen = 0,
    #endif
    
    	.proxy_ndp		= 0,
    
    	.accept_source_route	= 0,	/* we do not accept RH0 by default. */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    };
    
    /* IPv6 Wildcard Address and Loopback Address defined by RFC2553 */
    const struct in6_addr in6addr_any = IN6ADDR_ANY_INIT;
    const struct in6_addr in6addr_loopback = IN6ADDR_LOOPBACK_INIT;
    
    const struct in6_addr in6addr_linklocal_allnodes = IN6ADDR_LINKLOCAL_ALLNODES_INIT;
    const struct in6_addr in6addr_linklocal_allrouters = IN6ADDR_LINKLOCAL_ALLROUTERS_INIT;
    
    const struct in6_addr in6addr_interfacelocal_allnodes = IN6ADDR_INTERFACELOCAL_ALLNODES_INIT;
    const struct in6_addr in6addr_interfacelocal_allrouters = IN6ADDR_INTERFACELOCAL_ALLROUTERS_INIT;
    const struct in6_addr in6addr_sitelocal_allrouters = IN6ADDR_SITELOCAL_ALLROUTERS_INIT;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    /* Check if a valid qdisc is available */
    
    static inline bool addrconf_qdisc_ok(const struct net_device *dev)
    
    	return !qdisc_tx_is_noop(dev);
    
    static void addrconf_del_rs_timer(struct inet6_dev *idev)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	if (del_timer(&idev->rs_timer))
    		__in6_dev_put(idev);
    }
    
    static void addrconf_del_dad_timer(struct inet6_ifaddr *ifp)
    {
    	if (del_timer(&ifp->dad_timer))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		__in6_ifa_put(ifp);
    }
    
    
    static void addrconf_mod_rs_timer(struct inet6_dev *idev,
    				  unsigned long when)
    {
    	if (!timer_pending(&idev->rs_timer))
    		in6_dev_hold(idev);
    	mod_timer(&idev->rs_timer, jiffies + when);
    }
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    static void addrconf_mod_dad_timer(struct inet6_ifaddr *ifp,
    				   unsigned long when)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		in6_ifa_hold(ifp);
    
    	mod_timer(&ifp->dad_timer, jiffies + when);
    
    static int snmp6_alloc_dev(struct inet6_dev *idev)
    {
    
    	if (snmp_mib_init((void __percpu **)idev->stats.ipv6,
    
    			  sizeof(struct ipstats_mib),
    			  __alignof__(struct ipstats_mib)) < 0)
    
    		goto err_ip;
    
    	idev->stats.icmpv6dev = kzalloc(sizeof(struct icmpv6_mib_device),
    					GFP_KERNEL);
    	if (!idev->stats.icmpv6dev)
    
    		goto err_icmp;
    
    	idev->stats.icmpv6msgdev = kzalloc(sizeof(struct icmpv6msg_mib_device),
    					   GFP_KERNEL);
    	if (!idev->stats.icmpv6msgdev)
    
    	kfree(idev->stats.icmpv6dev);
    
    err_icmp:
    
    	snmp_mib_free((void __percpu **)idev->stats.ipv6);
    
    	return -ENOMEM;
    
    static void snmp6_free_dev(struct inet6_dev *idev)
    
    	kfree(idev->stats.icmpv6msgdev);
    	kfree(idev->stats.icmpv6dev);
    
    	snmp_mib_free((void __percpu **)idev->stats.ipv6);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    /* Nobody refers to this device, we may destroy it. */
    
    void in6_dev_finish_destroy(struct inet6_dev *idev)
    {
    	struct net_device *dev = idev->dev;
    
    	WARN_ON(!list_empty(&idev->addr_list));
    
    	WARN_ON(idev->mc_list != NULL);
    
    	WARN_ON(timer_pending(&idev->rs_timer));
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #ifdef NET_REFCNT_DEBUG
    
    	pr_debug("%s: %s\n", __func__, dev ? dev->name : "NIL");
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #endif
    	dev_put(dev);
    	if (!idev->dead) {
    
    		pr_warn("Freeing alive inet6 device %p\n", idev);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return;
    	}
    	snmp6_free_dev(idev);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    }
    
    EXPORT_SYMBOL(in6_dev_finish_destroy);
    
    
    static struct inet6_dev *ipv6_add_dev(struct net_device *dev)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct inet6_dev *ndev;
    
    	ASSERT_RTNL();
    
    	if (dev->mtu < IPV6_MIN_MTU)
    		return NULL;
    
    
    	ndev = kzalloc(sizeof(struct inet6_dev), GFP_KERNEL);
    
    	if (ndev == NULL)
    		return NULL;
    
    
    	rwlock_init(&ndev->lock);
    	ndev->dev = dev;
    
    	INIT_LIST_HEAD(&ndev->addr_list);
    
    	setup_timer(&ndev->rs_timer, addrconf_rs_timer,
    		    (unsigned long)ndev);
    
    	memcpy(&ndev->cnf, dev_net(dev)->ipv6.devconf_dflt, sizeof(ndev->cnf));
    
    	ndev->cnf.mtu6 = dev->mtu;
    	ndev->cnf.sysctl = NULL;
    	ndev->nd_parms = neigh_parms_alloc(dev, &nd_tbl);
    	if (ndev->nd_parms == NULL) {
    		kfree(ndev);
    		return NULL;
    	}
    
    	if (ndev->cnf.forwarding)
    		dev_disable_lro(dev);
    
    	/* We refer to the device */
    	dev_hold(dev);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	if (snmp6_alloc_dev(ndev) < 0) {
    
    			"%s: cannot allocate memory for statistics; dev=%s.\n",
    
    		neigh_parms_release(&nd_tbl, ndev->nd_parms);
    
    Roy Li's avatar
    Roy Li committed
    		dev_put(dev);
    		kfree(ndev);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	if (snmp6_register_dev(ndev) < 0) {
    
    			"%s: cannot create /proc/net/dev_snmp6/%s\n",
    
    		neigh_parms_release(&nd_tbl, ndev->nd_parms);
    		ndev->dead = 1;
    		in6_dev_finish_destroy(ndev);
    		return NULL;
    	}
    
    	/* One reference from device.  We must do this before
    	 * we invoke __ipv6_regen_rndid().
    	 */
    	in6_dev_hold(ndev);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	if (dev->flags & (IFF_NOARP | IFF_LOOPBACK))
    		ndev->cnf.accept_dad = -1;
    
    
    Amerigo Wang's avatar
    Amerigo Wang committed
    #if IS_ENABLED(CONFIG_IPV6_SIT)
    
    	if (dev->type == ARPHRD_SIT && (dev->priv_flags & IFF_ISATAP)) {
    
    		pr_info("%s: Disabled Multicast RS\n", dev->name);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #ifdef CONFIG_IPV6_PRIVACY
    
    	INIT_LIST_HEAD(&ndev->tempaddr_list);
    
    	setup_timer(&ndev->regen_timer, ipv6_regen_rndid, (unsigned long)ndev);
    
    	if ((dev->flags&IFF_LOOPBACK) ||
    	    dev->type == ARPHRD_TUNNEL ||
    
    	    dev->type == ARPHRD_SIT ||
    	    dev->type == ARPHRD_NONE) {
    
    		ndev->cnf.use_tempaddr = -1;
    	} else {
    		in6_dev_hold(ndev);
    		ipv6_regen_rndid((unsigned long) ndev);
    	}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #endif
    
    	ndev->token = in6addr_any;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	if (netif_running(dev) && addrconf_qdisc_ok(dev))
    
    		ndev->if_flags |= IF_READY;
    
    
    	ipv6_mc_init_dev(ndev);
    	ndev->tstamp = jiffies;
    
    	addrconf_sysctl_register(ndev);
    
    	/* protected by rtnl_lock */
    
    	rcu_assign_pointer(dev->ip6_ptr, ndev);
    
    	/* Join interface-local all-node multicast group */
    	ipv6_dev_mc_inc(dev, &in6addr_interfacelocal_allnodes);
    
    
    	ipv6_dev_mc_inc(dev, &in6addr_linklocal_allnodes);
    
    	/* Join all-router multicast group if forwarding is set */
    
    Li Wei's avatar
    Li Wei committed
    	if (ndev->cnf.forwarding && (dev->flags & IFF_MULTICAST))
    
    		ipv6_dev_mc_inc(dev, &in6addr_linklocal_allrouters);
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return ndev;
    }
    
    
    static struct inet6_dev *ipv6_find_idev(struct net_device *dev)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct inet6_dev *idev;
    
    	ASSERT_RTNL();
    
    
    	idev = __in6_dev_get(dev);
    	if (!idev) {
    		idev = ipv6_add_dev(dev);
    		if (!idev)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			return NULL;
    	}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (dev->flags&IFF_UP)
    		ipv6_mc_up(idev);
    	return idev;
    }
    
    
    static int inet6_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_MC_FORWARDING)
    		size += nla_total_size(4);
    
    
    	return size;
    }
    
    static int inet6_netconf_fill_devconf(struct sk_buff *skb, int ifindex,
    				      struct ipv6_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_INET6;
    
    	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, devconf->forwarding) < 0)
    		goto nla_put_failure;
    
    	if ((type == -1 || type == NETCONFA_MC_FORWARDING) &&
    	    nla_put_s32(skb, NETCONFA_MC_FORWARDING,
    			devconf->mc_forwarding) < 0)
    		goto nla_put_failure;
    
    	return nlmsg_end(skb, nlh);
    
    nla_put_failure:
    	nlmsg_cancel(skb, nlh);
    	return -EMSGSIZE;
    }
    
    
    void inet6_netconf_notify_devconf(struct net *net, int type, int ifindex,
    				  struct ipv6_devconf *devconf)
    
    {
    	struct sk_buff *skb;
    	int err = -ENOBUFS;
    
    	skb = nlmsg_new(inet6_netconf_msgsize_devconf(type), GFP_ATOMIC);
    	if (skb == NULL)
    		goto errout;
    
    	err = inet6_netconf_fill_devconf(skb, ifindex, devconf, 0, 0,
    					 RTM_NEWNETCONF, 0, type);
    	if (err < 0) {
    		/* -EMSGSIZE implies BUG in inet6_netconf_msgsize_devconf() */
    		WARN_ON(err == -EMSGSIZE);
    		kfree_skb(skb);
    		goto errout;
    	}
    	rtnl_notify(skb, net, 0, RTNLGRP_IPV6_NETCONF, NULL, GFP_ATOMIC);
    	return;
    errout:
    
    	rtnl_set_sk_err(net, RTNLGRP_IPV6_NETCONF, err);
    
    static const struct nla_policy devconf_ipv6_policy[NETCONFA_MAX+1] = {
    	[NETCONFA_IFINDEX]	= { .len = sizeof(int) },
    	[NETCONFA_FORWARDING]	= { .len = sizeof(int) },
    };
    
    static int inet6_netconf_get_devconf(struct sk_buff *in_skb,
    
    {
    	struct net *net = sock_net(in_skb->sk);
    	struct nlattr *tb[NETCONFA_MAX+1];
    	struct netconfmsg *ncm;
    	struct sk_buff *skb;
    	struct ipv6_devconf *devconf;
    	struct inet6_dev *in6_dev;
    	struct net_device *dev;
    	int ifindex;
    	int err;
    
    	err = nlmsg_parse(nlh, sizeof(*ncm), tb, NETCONFA_MAX,
    			  devconf_ipv6_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->ipv6.devconf_all;
    		break;
    	case NETCONFA_IFINDEX_DEFAULT:
    		devconf = net->ipv6.devconf_dflt;
    		break;
    	default:
    		dev = __dev_get_by_index(net, ifindex);
    		if (dev == NULL)
    			goto errout;
    		in6_dev = __in6_dev_get(dev);
    		if (in6_dev == NULL)
    			goto errout;
    		devconf = &in6_dev->cnf;
    		break;
    	}
    
    	err = -ENOBUFS;
    	skb = nlmsg_new(inet6_netconf_msgsize_devconf(-1), GFP_ATOMIC);
    	if (skb == NULL)
    		goto errout;
    
    	err = inet6_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 inet6_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 inet6_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 inet6_dev *idev;
    	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->ipv6.dev_addr_genid) ^
    			  net->dev_base_seq;
    
    		hlist_for_each_entry_rcu(dev, head, index_hlist) {
    			if (idx < s_idx)
    				goto cont;
    			idev = __in6_dev_get(dev);
    			if (!idev)
    				goto cont;
    
    			if (inet6_netconf_fill_devconf(skb, dev->ifindex,
    						       &idev->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 (inet6_netconf_fill_devconf(skb, NETCONFA_IFINDEX_ALL,
    					       net->ipv6.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 (inet6_netconf_fill_devconf(skb, NETCONFA_IFINDEX_DEFAULT,
    					       net->ipv6.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;
    }
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #ifdef CONFIG_SYSCTL
    static void dev_forward_change(struct inet6_dev *idev)
    {
    	struct net_device *dev;
    	struct inet6_ifaddr *ifa;
    
    	if (!idev)
    		return;
    	dev = idev->dev;
    
    	if (idev->cnf.forwarding)
    		dev_disable_lro(dev);
    
    	if (dev->flags & IFF_MULTICAST) {
    
    			ipv6_dev_mc_inc(dev, &in6addr_linklocal_allrouters);
    
    			ipv6_dev_mc_inc(dev, &in6addr_interfacelocal_allrouters);
    			ipv6_dev_mc_inc(dev, &in6addr_sitelocal_allrouters);
    		} else {
    
    			ipv6_dev_mc_dec(dev, &in6addr_linklocal_allrouters);
    
    			ipv6_dev_mc_dec(dev, &in6addr_interfacelocal_allrouters);
    			ipv6_dev_mc_dec(dev, &in6addr_sitelocal_allrouters);
    		}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    
    	list_for_each_entry(ifa, &idev->addr_list, if_list) {
    
    		if (ifa->flags&IFA_F_TENTATIVE)
    			continue;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		if (idev->cnf.forwarding)
    			addrconf_join_anycast(ifa);
    		else
    			addrconf_leave_anycast(ifa);
    	}
    
    	inet6_netconf_notify_devconf(dev_net(dev), NETCONFA_FORWARDING,
    				     dev->ifindex, &idev->cnf);
    
    static void addrconf_forward_change(struct net *net, __s32 newf)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct net_device *dev;
    	struct inet6_dev *idev;
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		idev = __in6_dev_get(dev);
    		if (idev) {
    
    			int changed = (!idev->cnf.forwarding) ^ (!newf);
    			idev->cnf.forwarding = newf;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			if (changed)
    				dev_forward_change(idev);
    		}
    	}
    }
    
    static int addrconf_fixup_forwarding(struct ctl_table *table, int *p, int newf)
    
    	struct net *net;
    
    	int old;
    
    	if (!rtnl_trylock())
    		return restart_syscall();
    
    
    	net = (struct net *)table->extra2;
    
    	if (p == &net->ipv6.devconf_dflt->forwarding) {
    
    		if ((!newf) ^ (!old))
    			inet6_netconf_notify_devconf(net, NETCONFA_FORWARDING,
    						     NETCONFA_IFINDEX_DEFAULT,
    						     net->ipv6.devconf_dflt);
    
    	if (p == &net->ipv6.devconf_all->forwarding) {
    		net->ipv6.devconf_dflt->forwarding = newf;
    		addrconf_forward_change(net, newf);
    
    		if ((!newf) ^ (!old))
    			inet6_netconf_notify_devconf(net, NETCONFA_FORWARDING,
    						     NETCONFA_IFINDEX_ALL,
    						     net->ipv6.devconf_all);
    
    		dev_forward_change((struct inet6_dev *)table->extra1);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #endif
    
    
    /* Nobody refers to this ifaddr, destroy it */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    void inet6_ifa_finish_destroy(struct inet6_ifaddr *ifp)
    {
    
    	WARN_ON(!hlist_unhashed(&ifp->addr_lst));
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #ifdef NET_REFCNT_DEBUG
    
    	pr_debug("%s\n", __func__);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #endif
    
    	in6_dev_put(ifp->idev);
    
    
    		pr_notice("Timer is still running, when freeing ifa=%p\n", ifp);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	if (ifp->state != INET6_IFADDR_STATE_DEAD) {
    
    		pr_warn("Freeing alive inet6 address %p\n", ifp);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return;
    	}
    
    	ip6_rt_put(ifp->rt);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    static void
    ipv6_link_dev_addr(struct inet6_dev *idev, struct inet6_ifaddr *ifp)
    {
    
    	struct list_head *p;
    
    	int ifp_scope = ipv6_addr_src_scope(&ifp->addr);
    
    
    	/*
    	 * Each device address list is sorted in order of scope -
    	 * global before linklocal.
    	 */
    
    	list_for_each(p, &idev->addr_list) {
    		struct inet6_ifaddr *ifa
    			= list_entry(p, struct inet6_ifaddr, if_list);
    
    		if (ifp_scope >= ipv6_addr_src_scope(&ifa->addr))
    
    	list_add_tail(&ifp->if_list, p);
    
    static u32 inet6_addr_hash(const struct in6_addr *addr)
    
    	return hash_32(ipv6_addr_hash(addr), IN6_ADDR_HSIZE_SHIFT);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    /* On success it returns ifp with increased reference count */
    
    static struct inet6_ifaddr *
    
    ipv6_add_addr(struct inet6_dev *idev, const struct in6_addr *addr,
    	      const struct in6_addr *peer_addr, int pfxlen,
    
    	      int scope, u32 flags, u32 valid_lft, u32 prefered_lft)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct inet6_ifaddr *ifa = NULL;
    	struct rt6_info *rt;
    
    	unsigned int hash;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	int err = 0;
    
    	int addr_type = ipv6_addr_type(addr);
    
    	if (addr_type == IPV6_ADDR_ANY ||
    	    addr_type & IPV6_ADDR_MULTICAST ||
    	    (!(idev->dev->flags & IFF_LOOPBACK) &&
    	     addr_type & IPV6_ADDR_LOOPBACK))
    		return ERR_PTR(-EADDRNOTAVAIL);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (idev->dead) {
    		err = -ENODEV;			/*XXX*/
    		goto out2;
    	}
    
    
    	if (idev->cnf.disable_ipv6) {
    
    		err = -EACCES;
    		goto out2;
    	}
    
    
    	spin_lock(&addrconf_hash_lock);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/* Ignore adding duplicate addresses on an interface */
    
    	if (ipv6_chk_same_addr(dev_net(idev->dev), addr, idev->dev)) {
    
    		ADBG("ipv6_add_addr: already assigned\n");
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		err = -EEXIST;
    		goto out;
    	}
    
    
    	ifa = kzalloc(sizeof(struct inet6_ifaddr), GFP_ATOMIC);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	if (ifa == NULL) {
    
    		ADBG("ipv6_add_addr: malloc failed\n");
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		err = -ENOBUFS;
    		goto out;
    	}
    
    
    	rt = addrconf_dst_alloc(idev, addr, false);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (IS_ERR(rt)) {
    		err = PTR_ERR(rt);
    		goto out;
    	}
    
    
    	ifa->addr = *addr;
    
    	if (peer_addr)
    		ifa->peer_addr = *peer_addr;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	spin_lock_init(&ifa->lock);
    
    	spin_lock_init(&ifa->state_lock);
    
    	setup_timer(&ifa->dad_timer, addrconf_dad_timer,
    		    (unsigned long)ifa);
    
    	INIT_HLIST_NODE(&ifa->addr_lst);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	ifa->scope = scope;
    	ifa->prefix_len = pfxlen;
    	ifa->flags = flags | IFA_F_TENTATIVE;
    
    	ifa->valid_lft = valid_lft;
    	ifa->prefered_lft = prefered_lft;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	ifa->cstamp = ifa->tstamp = jiffies;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	ifa->idev = idev;
    	in6_dev_hold(idev);
    	/* For caller */
    	in6_ifa_hold(ifa);
    
    	/* Add to big hash table */
    
    	hash = inet6_addr_hash(addr);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	hlist_add_head_rcu(&ifa->addr_lst, &inet6_addr_lst[hash]);
    	spin_unlock(&addrconf_hash_lock);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	write_lock(&idev->lock);
    	/* Add to inet6_dev unicast addr list. */
    
    	ipv6_link_dev_addr(idev, ifa);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    #ifdef CONFIG_IPV6_PRIVACY
    	if (ifa->flags&IFA_F_TEMPORARY) {
    
    		list_add(&ifa->tmp_list, &idev->tempaddr_list);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		in6_ifa_hold(ifa);
    	}
    #endif
    
    	in6_ifa_hold(ifa);
    	write_unlock(&idev->lock);
    out2:
    
    	rcu_read_unlock_bh();
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	if (likely(err == 0))
    
    		inet6addr_notifier_call_chain(NETDEV_UP, ifa);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	else {
    		kfree(ifa);
    		ifa = ERR_PTR(err);
    	}
    
    	return ifa;
    out:
    
    	spin_unlock(&addrconf_hash_lock);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	goto out2;
    }
    
    /* This function wants to get referenced ifp and releases it before return */
    
    static void ipv6_del_addr(struct inet6_ifaddr *ifp)
    {
    
    	struct inet6_ifaddr *ifa, *ifn;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct inet6_dev *idev = ifp->idev;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	int deleted = 0, onlink = 0;
    	unsigned long expires = jiffies;
    
    
    	spin_lock_bh(&ifp->state_lock);
    	state = ifp->state;
    
    	ifp->state = INET6_IFADDR_STATE_DEAD;
    
    	spin_unlock_bh(&ifp->state_lock);
    
    	if (state == INET6_IFADDR_STATE_DEAD)
    		goto out;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	spin_lock_bh(&addrconf_hash_lock);
    	hlist_del_init_rcu(&ifp->addr_lst);
    	spin_unlock_bh(&addrconf_hash_lock);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	write_lock_bh(&idev->lock);
    #ifdef CONFIG_IPV6_PRIVACY
    	if (ifp->flags&IFA_F_TEMPORARY) {
    
    		list_del(&ifp->tmp_list);
    		if (ifp->ifpub) {
    			in6_ifa_put(ifp->ifpub);
    			ifp->ifpub = NULL;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    #endif
    
    
    	list_for_each_entry_safe(ifa, ifn, &idev->addr_list, if_list) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		if (ifa == ifp) {
    
    			list_del_init(&ifp->if_list);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			__in6_ifa_put(ifp);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			if (!(ifp->flags & IFA_F_PERMANENT) || onlink > 0)
    				break;
    			deleted = 1;
    
    			continue;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		} else if (ifp->flags & IFA_F_PERMANENT) {
    			if (ipv6_prefix_equal(&ifa->addr, &ifp->addr,
    					      ifp->prefix_len)) {
    				if (ifa->flags & IFA_F_PERMANENT) {
    					onlink = 1;
    					if (deleted)
    						break;
    				} else {
    					unsigned long lifetime;
    
    					if (!onlink)
    						onlink = -1;
    
    					spin_lock(&ifa->lock);
    
    
    					lifetime = addrconf_timeout_fixup(ifa->valid_lft, HZ);
    					/*
    					 * Note: Because this address is
    					 * not permanent, lifetime <
    					 * LONG_MAX / HZ here.
    					 */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    					if (time_before(expires,
    							ifa->tstamp + lifetime * HZ))
    						expires = ifa->tstamp + lifetime * HZ;
    					spin_unlock(&ifa->lock);
    				}
    			}
    		}