Skip to content
Snippets Groups Projects
ndisc.c 43.5 KiB
Newer Older
  • Learn to ignore specific revisions
  • Linus Torvalds's avatar
    Linus Torvalds committed
    /*
     *	Neighbour Discovery for IPv6
    
     *	Linux INET6 implementation
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     *
     *	Authors:
    
     *	Pedro Roque		<roque@di.fc.ul.pt>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     *	Mike Shaver		<shaver@ingenia.com>
     *
     *	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:
     *
    
     *	Pierre Ynard			:	export userland ND options
     *						through netlink (RDNSS support)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     *	Lars Fenneberg			:	fixed MTU setting on receipt
     *						of an RA.
     *	Janos Farkas			:	kmalloc failure checks
     *	Alexey Kuznetsov		:	state machine reworked
     *						and moved to net/core.
     *	Pekka Savola			:	RFC2461 validation
     *	YOSHIFUJI Hideaki @USAGI	:	Verify ND options properly
     */
    
    /* Set to 3 to get tracing... */
    #define ND_DEBUG 1
    
    #define ND_PRINTK(fmt, args...) do { if (net_ratelimit()) { printk(fmt, ## args); } } while(0)
    #define ND_NOPRINTK(x...) do { ; } while(0)
    #define ND_PRINTK0 ND_PRINTK
    #define ND_PRINTK1 ND_NOPRINTK
    #define ND_PRINTK2 ND_NOPRINTK
    #define ND_PRINTK3 ND_NOPRINTK
    #if ND_DEBUG >= 1
    #undef ND_PRINTK1
    #define ND_PRINTK1 ND_PRINTK
    #endif
    #if ND_DEBUG >= 2
    #undef ND_PRINTK2
    #define ND_PRINTK2 ND_PRINTK
    #endif
    #if ND_DEBUG >= 3
    #undef ND_PRINTK3
    #define ND_PRINTK3 ND_PRINTK
    #endif
    
    #include <linux/module.h>
    #include <linux/errno.h>
    #include <linux/types.h>
    #include <linux/socket.h>
    #include <linux/sockios.h>
    #include <linux/sched.h>
    #include <linux/net.h>
    #include <linux/in6.h>
    #include <linux/route.h>
    #include <linux/init.h>
    #include <linux/rcupdate.h>
    #ifdef CONFIG_SYSCTL
    #include <linux/sysctl.h>
    #endif
    
    
    #include <linux/if_addr.h>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #include <linux/if_arp.h>
    #include <linux/ipv6.h>
    #include <linux/icmpv6.h>
    #include <linux/jhash.h>
    
    #include <net/sock.h>
    #include <net/snmp.h>
    
    #include <net/ipv6.h>
    #include <net/protocol.h>
    #include <net/ndisc.h>
    #include <net/ip6_route.h>
    #include <net/addrconf.h>
    #include <net/icmp.h>
    
    
    #include <net/netlink.h>
    #include <linux/rtnetlink.h>
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #include <net/flow.h>
    #include <net/ip6_checksum.h>
    #include <linux/proc_fs.h>
    
    #include <linux/netfilter.h>
    #include <linux/netfilter_ipv6.h>
    
    static struct socket *ndisc_socket;
    
    static u32 ndisc_hash(const void *pkey, const struct net_device *dev);
    static int ndisc_constructor(struct neighbour *neigh);
    static void ndisc_solicit(struct neighbour *neigh, struct sk_buff *skb);
    static void ndisc_error_report(struct neighbour *neigh, struct sk_buff *skb);
    static int pndisc_constructor(struct pneigh_entry *n);
    static void pndisc_destructor(struct pneigh_entry *n);
    static void pndisc_redo(struct sk_buff *skb);
    
    static struct neigh_ops ndisc_generic_ops = {
    	.family =		AF_INET6,
    	.solicit =		ndisc_solicit,
    	.error_report =		ndisc_error_report,
    	.output =		neigh_resolve_output,
    	.connected_output =	neigh_connected_output,
    	.hh_output =		dev_queue_xmit,
    	.queue_xmit =		dev_queue_xmit,
    };
    
    static struct neigh_ops ndisc_hh_ops = {
    	.family =		AF_INET6,
    	.solicit =		ndisc_solicit,
    	.error_report =		ndisc_error_report,
    	.output =		neigh_resolve_output,
    	.connected_output =	neigh_resolve_output,
    	.hh_output =		dev_queue_xmit,
    	.queue_xmit =		dev_queue_xmit,
    };
    
    
    static struct neigh_ops ndisc_direct_ops = {
    	.family =		AF_INET6,
    	.output =		dev_queue_xmit,
    	.connected_output =	dev_queue_xmit,
    	.hh_output =		dev_queue_xmit,
    	.queue_xmit =		dev_queue_xmit,
    };
    
    struct neigh_table nd_tbl = {
    	.family =	AF_INET6,
    	.entry_size =	sizeof(struct neighbour) + sizeof(struct in6_addr),
    	.key_len =	sizeof(struct in6_addr),
    	.hash =		ndisc_hash,
    	.constructor =	ndisc_constructor,
    	.pconstructor =	pndisc_constructor,
    	.pdestructor =	pndisc_destructor,
    	.proxy_redo =	pndisc_redo,
    	.id =		"ndisc_cache",
    	.parms = {
    		.tbl =			&nd_tbl,
    		.base_reachable_time =	30 * HZ,
    		.retrans_time =	 1 * HZ,
    		.gc_staletime =	60 * HZ,
    		.reachable_time =		30 * HZ,
    		.delay_probe_time =	 5 * HZ,
    		.queue_len =		 3,
    		.ucast_probes =	 3,
    		.mcast_probes =	 3,
    		.anycast_delay =	 1 * HZ,
    		.proxy_delay =		(8 * HZ) / 10,
    		.proxy_qlen =		64,
    	},
    	.gc_interval =	  30 * HZ,
    	.gc_thresh1 =	 128,
    	.gc_thresh2 =	 512,
    	.gc_thresh3 =	1024,
    };
    
    /* ND options */
    struct ndisc_options {
    
    	struct nd_opt_hdr *nd_opt_array[__ND_OPT_ARRAY_MAX];
    #ifdef CONFIG_IPV6_ROUTE_INFO
    	struct nd_opt_hdr *nd_opts_ri;
    	struct nd_opt_hdr *nd_opts_ri_end;
    #endif
    
    	struct nd_opt_hdr *nd_useropts;
    	struct nd_opt_hdr *nd_useropts_end;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    };
    
    #define nd_opts_src_lladdr	nd_opt_array[ND_OPT_SOURCE_LL_ADDR]
    #define nd_opts_tgt_lladdr	nd_opt_array[ND_OPT_TARGET_LL_ADDR]
    #define nd_opts_pi		nd_opt_array[ND_OPT_PREFIX_INFO]
    #define nd_opts_pi_end		nd_opt_array[__ND_OPT_PREFIX_INFO_END]
    #define nd_opts_rh		nd_opt_array[ND_OPT_REDIRECT_HDR]
    #define nd_opts_mtu		nd_opt_array[ND_OPT_MTU]
    
    #define NDISC_OPT_SPACE(len) (((len)+2+7)&~7)
    
    /*
     * Return the padding between the option length and the start of the
     * link addr.  Currently only IP-over-InfiniBand needs this, although
     * if RFC 3831 IPv6-over-Fibre Channel is ever implemented it may
     * also need a pad of 2.
     */
    static int ndisc_addr_option_pad(unsigned short type)
    {
    	switch (type) {
    	case ARPHRD_INFINIBAND: return 2;
    	default:                return 0;
    	}
    }
    
    static inline int ndisc_opt_addr_space(struct net_device *dev)
    {
    	return NDISC_OPT_SPACE(dev->addr_len + ndisc_addr_option_pad(dev->type));
    }
    
    static u8 *ndisc_fill_addr_option(u8 *opt, int type, void *data, int data_len,
    				  unsigned short addr_type)
    {
    	int space = NDISC_OPT_SPACE(data_len);
    	int pad   = ndisc_addr_option_pad(addr_type);
    
    	opt[0] = type;
    	opt[1] = space>>3;
    
    	memset(opt + 2, 0, pad);
    	opt   += pad;
    	space -= pad;
    
    	memcpy(opt+2, data, data_len);
    	data_len += 2;
    	opt += data_len;
    	if ((space -= data_len) > 0)
    		memset(opt, 0, space);
    	return opt + space;
    }
    
    static struct nd_opt_hdr *ndisc_next_option(struct nd_opt_hdr *cur,
    					    struct nd_opt_hdr *end)
    {
    	int type;
    	if (!cur || !end || cur >= end)
    		return NULL;
    	type = cur->nd_opt_type;
    	do {
    		cur = ((void *)cur) + (cur->nd_opt_len << 3);
    	} while(cur < end && cur->nd_opt_type != type);
    	return (cur <= end && cur->nd_opt_type == type ? cur : NULL);
    }
    
    
    static inline int ndisc_is_useropt(struct nd_opt_hdr *opt)
    {
    	return (opt->nd_opt_type == ND_OPT_RDNSS);
    }
    
    static struct nd_opt_hdr *ndisc_next_useropt(struct nd_opt_hdr *cur,
    					     struct nd_opt_hdr *end)
    {
    	if (!cur || !end || cur >= end)
    		return NULL;
    	do {
    		cur = ((void *)cur) + (cur->nd_opt_len << 3);
    	} while(cur < end && !ndisc_is_useropt(cur));
    	return (cur <= end && ndisc_is_useropt(cur) ? cur : NULL);
    }
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    static struct ndisc_options *ndisc_parse_options(u8 *opt, int opt_len,
    						 struct ndisc_options *ndopts)
    {
    	struct nd_opt_hdr *nd_opt = (struct nd_opt_hdr *)opt;
    
    	if (!nd_opt || opt_len < 0 || !ndopts)
    		return NULL;
    	memset(ndopts, 0, sizeof(*ndopts));
    	while (opt_len) {
    		int l;
    		if (opt_len < sizeof(struct nd_opt_hdr))
    			return NULL;
    		l = nd_opt->nd_opt_len << 3;
    		if (opt_len < l || l == 0)
    			return NULL;
    		switch (nd_opt->nd_opt_type) {
    		case ND_OPT_SOURCE_LL_ADDR:
    		case ND_OPT_TARGET_LL_ADDR:
    		case ND_OPT_MTU:
    		case ND_OPT_REDIRECT_HDR:
    			if (ndopts->nd_opt_array[nd_opt->nd_opt_type]) {
    				ND_PRINTK2(KERN_WARNING
    					   "%s(): duplicated ND6 option found: type=%d\n",
    					   __FUNCTION__,
    					   nd_opt->nd_opt_type);
    			} else {
    				ndopts->nd_opt_array[nd_opt->nd_opt_type] = nd_opt;
    			}
    			break;
    		case ND_OPT_PREFIX_INFO:
    			ndopts->nd_opts_pi_end = nd_opt;
    
    			if (!ndopts->nd_opt_array[nd_opt->nd_opt_type])
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				ndopts->nd_opt_array[nd_opt->nd_opt_type] = nd_opt;
    			break;
    
    #ifdef CONFIG_IPV6_ROUTE_INFO
    		case ND_OPT_ROUTE_INFO:
    			ndopts->nd_opts_ri_end = nd_opt;
    			if (!ndopts->nd_opts_ri)
    				ndopts->nd_opts_ri = nd_opt;
    			break;
    #endif
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		default:
    
    			if (ndisc_is_useropt(nd_opt)) {
    				ndopts->nd_useropts_end = nd_opt;
    				if (!ndopts->nd_useropts)
    					ndopts->nd_useropts = nd_opt;
    			} else {
    				/*
    				 * Unknown options must be silently ignored,
    				 * to accommodate future extension to the
    				 * protocol.
    				 */
    				ND_PRINTK2(KERN_NOTICE
    					   "%s(): ignored unsupported option; type=%d, len=%d\n",
    					   __FUNCTION__,
    					   nd_opt->nd_opt_type, nd_opt->nd_opt_len);
    			}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		}
    		opt_len -= l;
    		nd_opt = ((void *)nd_opt) + l;
    	}
    	return ndopts;
    }
    
    static inline u8 *ndisc_opt_addr_data(struct nd_opt_hdr *p,
    				      struct net_device *dev)
    {
    	u8 *lladdr = (u8 *)(p + 1);
    	int lladdrlen = p->nd_opt_len << 3;
    	int prepad = ndisc_addr_option_pad(dev->type);
    	if (lladdrlen != NDISC_OPT_SPACE(dev->addr_len + prepad))
    		return NULL;
    	return (lladdr + prepad);
    }
    
    int ndisc_mc_map(struct in6_addr *addr, char *buf, struct net_device *dev, int dir)
    {
    	switch (dev->type) {
    	case ARPHRD_ETHER:
    	case ARPHRD_IEEE802:	/* Not sure. Check it later. --ANK */
    	case ARPHRD_FDDI:
    		ipv6_eth_mc_map(addr, buf);
    		return 0;
    	case ARPHRD_IEEE802_TR:
    		ipv6_tr_mc_map(addr,buf);
    		return 0;
    	case ARPHRD_ARCNET:
    		ipv6_arcnet_mc_map(addr, buf);
    		return 0;
    	case ARPHRD_INFINIBAND:
    
    		ipv6_ib_mc_map(addr, dev->broadcast, buf);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return 0;
    	default:
    		if (dir) {
    			memcpy(buf, dev->broadcast, dev->addr_len);
    			return 0;
    		}
    	}
    	return -EINVAL;
    }
    
    
    EXPORT_SYMBOL(ndisc_mc_map);
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    static u32 ndisc_hash(const void *pkey, const struct net_device *dev)
    {
    	const u32 *p32 = pkey;
    	u32 addr_hash, i;
    
    	addr_hash = 0;
    	for (i = 0; i < (sizeof(struct in6_addr) / sizeof(u32)); i++)
    		addr_hash ^= *p32++;
    
    	return jhash_2words(addr_hash, dev->ifindex, nd_tbl.hash_rnd);
    }
    
    static int ndisc_constructor(struct neighbour *neigh)
    {
    	struct in6_addr *addr = (struct in6_addr*)&neigh->primary_key;
    	struct net_device *dev = neigh->dev;
    	struct inet6_dev *in6_dev;
    	struct neigh_parms *parms;
    	int is_multicast = ipv6_addr_is_multicast(addr);
    
    	rcu_read_lock();
    	in6_dev = in6_dev_get(dev);
    	if (in6_dev == NULL) {
    		rcu_read_unlock();
    		return -EINVAL;
    	}
    
    	parms = in6_dev->nd_parms;
    	__neigh_parms_put(neigh->parms);
    	neigh->parms = neigh_parms_clone(parms);
    	rcu_read_unlock();
    
    	neigh->type = is_multicast ? RTN_MULTICAST : RTN_UNICAST;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		neigh->nud_state = NUD_NOARP;
    		neigh->ops = &ndisc_direct_ops;
    		neigh->output = neigh->ops->queue_xmit;
    	} else {
    		if (is_multicast) {
    			neigh->nud_state = NUD_NOARP;
    			ndisc_mc_map(addr, neigh->ha, dev, 1);
    		} else if (dev->flags&(IFF_NOARP|IFF_LOOPBACK)) {
    			neigh->nud_state = NUD_NOARP;
    			memcpy(neigh->ha, dev->dev_addr, dev->addr_len);
    			if (dev->flags&IFF_LOOPBACK)
    				neigh->type = RTN_LOCAL;
    		} else if (dev->flags&IFF_POINTOPOINT) {
    			neigh->nud_state = NUD_NOARP;
    			memcpy(neigh->ha, dev->broadcast, dev->addr_len);
    		}
    
    		if (dev->header_ops->cache)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			neigh->ops = &ndisc_hh_ops;
    		else
    			neigh->ops = &ndisc_generic_ops;
    		if (neigh->nud_state&NUD_VALID)
    			neigh->output = neigh->ops->connected_output;
    		else
    			neigh->output = neigh->ops->output;
    	}
    	in6_dev_put(in6_dev);
    	return 0;
    }
    
    static int pndisc_constructor(struct pneigh_entry *n)
    {
    	struct in6_addr *addr = (struct in6_addr*)&n->key;
    	struct in6_addr maddr;
    	struct net_device *dev = n->dev;
    
    	if (dev == NULL || __in6_dev_get(dev) == NULL)
    		return -EINVAL;
    	addrconf_addr_solict_mult(addr, &maddr);
    	ipv6_dev_mc_inc(dev, &maddr);
    	return 0;
    }
    
    static void pndisc_destructor(struct pneigh_entry *n)
    {
    	struct in6_addr *addr = (struct in6_addr*)&n->key;
    	struct in6_addr maddr;
    	struct net_device *dev = n->dev;
    
    	if (dev == NULL || __in6_dev_get(dev) == NULL)
    		return;
    	addrconf_addr_solict_mult(addr, &maddr);
    	ipv6_dev_mc_dec(dev, &maddr);
    }
    
    /*
     *	Send a Neighbour Advertisement
     */
    
    static void __ndisc_send(struct net_device *dev,
    			 struct neighbour *neigh,
    			 struct in6_addr *daddr, struct in6_addr *saddr,
    			 struct icmp6hdr *icmp6h, struct in6_addr *target,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct flowi fl;
    
    	struct sock *sk = ndisc_socket->sk;
    	struct sk_buff *skb;
    
    	struct icmp6hdr *hdr;
    	struct inet6_dev *idev;
    	int len;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	int err;
    
    	u8 *opt, type;
    
    	type = icmp6h->icmp6_type;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	icmpv6_flow_init(ndisc_socket->sk, &fl, type,
    			 saddr, daddr, dev->ifindex);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	dst = icmp6_dst_alloc(dev, neigh, daddr);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (!dst)
    		return;
    
    	err = xfrm_lookup(&dst, &fl, NULL, 0);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return;
    
    
    	if (!dev->addr_len)
    		llinfo = 0;
    
    	len = sizeof(struct icmp6hdr) + (target ? sizeof(*target) : 0);
    	if (llinfo)
    		len += ndisc_opt_addr_space(dev);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	skb = sock_alloc_send_skb(sk,
    				  (MAX_HEADER + sizeof(struct ipv6hdr) +
    				   len + LL_RESERVED_SPACE(dev)),
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				  1, &err);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		ND_PRINTK0(KERN_ERR
    
    			   "ICMPv6 ND: %s() failed to allocate an skb.\n",
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			   __FUNCTION__);
    		dst_release(dst);
    		return;
    	}
    
    	skb_reserve(skb, LL_RESERVED_SPACE(dev));
    
    	ip6_nd_hdr(sk, skb, dev, saddr, daddr, IPPROTO_ICMPV6, len);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	skb->transport_header = skb->tail;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	hdr = (struct icmp6hdr *)skb_transport_header(skb);
    	memcpy(hdr, icmp6h, sizeof(*hdr));
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	opt = skb_transport_header(skb) + sizeof(struct icmp6hdr);
    	if (target) {
    		ipv6_addr_copy((struct in6_addr *)opt, target);
    		opt += sizeof(*target);
    	}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	if (llinfo)
    		ndisc_fill_addr_option(opt, llinfo, dev->dev_addr,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				       dev->addr_len, dev->type);
    
    
    	hdr->icmp6_cksum = csum_ipv6_magic(saddr, daddr, len,
    					   IPPROTO_ICMPV6,
    					   csum_partial((__u8 *) hdr,
    							len, 0));
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	skb->dst = dst;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	idev = in6_dev_get(dst->dev);
    
    	IP6_INC_STATS(idev, IPSTATS_MIB_OUTREQUESTS);
    
    	err = NF_HOOK(PF_INET6, NF_INET_LOCAL_OUT, skb, NULL, dst->dev,
    		      dst_output);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (!err) {
    
    		ICMP6MSGOUT_INC_STATS(idev, type);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		ICMP6_INC_STATS(idev, ICMP6_MIB_OUTMSGS);
    	}
    
    	if (likely(idev != NULL))
    		in6_dev_put(idev);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    static void ndisc_send_na(struct net_device *dev, struct neighbour *neigh,
    		   struct in6_addr *daddr, struct in6_addr *solicited_addr,
    		   int router, int solicited, int override, int inc_opt)
    {
    	struct in6_addr tmpaddr;
    	struct inet6_ifaddr *ifp;
    	struct in6_addr *src_addr;
    	struct icmp6hdr icmp6h = {
    		.icmp6_type = NDISC_NEIGHBOUR_ADVERTISEMENT,
    	};
    
    	/* for anycast or proxy, solicited_addr != src_addr */
    
    	ifp = ipv6_get_ifaddr(&init_net, solicited_addr, dev, 1);
    
    	if (ifp) {
    		src_addr = solicited_addr;
    		if (ifp->flags & IFA_F_OPTIMISTIC)
    			override = 0;
    		in6_ifa_put(ifp);
    	} else {
    		if (ipv6_dev_get_saddr(dev, daddr, &tmpaddr))
    			return;
    		src_addr = &tmpaddr;
    	}
    
    	icmp6h.icmp6_router = router;
    	icmp6h.icmp6_solicited = solicited;
    	icmp6h.icmp6_override = override;
    
    	__ndisc_send(dev, neigh, daddr, src_addr,
    		     &icmp6h, solicited_addr,
    
    		     inc_opt ? ND_OPT_TARGET_LL_ADDR : 0);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    void ndisc_send_ns(struct net_device *dev, struct neighbour *neigh,
    		   struct in6_addr *solicit,
    
    		   struct in6_addr *daddr, struct in6_addr *saddr)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct in6_addr addr_buf;
    
    	struct icmp6hdr icmp6h = {
    		.icmp6_type = NDISC_NEIGHBOUR_SOLICITATION,
    	};
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	if (saddr == NULL) {
    
    		if (ipv6_get_lladdr(dev, &addr_buf,
    				   (IFA_F_TENTATIVE|IFA_F_OPTIMISTIC)))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			return;
    		saddr = &addr_buf;
    	}
    
    
    	__ndisc_send(dev, neigh, daddr, saddr,
    		     &icmp6h, solicit,
    
    		     !ipv6_addr_any(saddr) ? ND_OPT_SOURCE_LL_ADDR : 0);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    }
    
    void ndisc_send_rs(struct net_device *dev, struct in6_addr *saddr,
    		   struct in6_addr *daddr)
    {
    
    	struct icmp6hdr icmp6h = {
    		.icmp6_type = NDISC_ROUTER_SOLICITATION,
    	};
    
    	int send_sllao = dev->addr_len;
    
    #ifdef CONFIG_IPV6_OPTIMISTIC_DAD
    	/*
    	 * According to section 2.2 of RFC 4429, we must not
    	 * send router solicitations with a sllao from
    	 * optimistic addresses, but we may send the solicitation
    	 * if we don't include the sllao.  So here we check
    	 * if our address is optimistic, and if so, we
    
    Joe Perches's avatar
    Joe Perches committed
    	 * suppress the inclusion of the sllao.
    
    		struct inet6_ifaddr *ifp = ipv6_get_ifaddr(&init_net, saddr,
    							   dev, 1);
    
    		if (ifp) {
    			if (ifp->flags & IFA_F_OPTIMISTIC)  {
    
    	__ndisc_send(dev, NULL, daddr, saddr,
    		     &icmp6h, NULL,
    
    		     send_sllao ? ND_OPT_SOURCE_LL_ADDR : 0);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    }
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    static void ndisc_error_report(struct neighbour *neigh, struct sk_buff *skb)
    {
    	/*
    	 *	"The sender MUST return an ICMP
    	 *	 destination unreachable"
    	 */
    	dst_link_failure(skb);
    	kfree_skb(skb);
    }
    
    /* Called with locked neigh: either read or both */
    
    static void ndisc_solicit(struct neighbour *neigh, struct sk_buff *skb)
    {
    	struct in6_addr *saddr = NULL;
    	struct in6_addr mcaddr;
    	struct net_device *dev = neigh->dev;
    	struct in6_addr *target = (struct in6_addr *)&neigh->primary_key;
    	int probes = atomic_read(&neigh->probes);
    
    
    	if (skb && ipv6_chk_addr(&init_net, &ipv6_hdr(skb)->saddr, dev, 1))
    
    		saddr = &ipv6_hdr(skb)->saddr;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	if ((probes -= neigh->parms->ucast_probes) < 0) {
    		if (!(neigh->nud_state & NUD_VALID)) {
    			ND_PRINTK1(KERN_DEBUG
    				   "%s(): trying to ucast probe in NUD_INVALID: "
    
    				   NIP6_FMT "\n",
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				   __FUNCTION__,
    				   NIP6(*target));
    		}
    		ndisc_send_ns(dev, neigh, target, target, saddr);
    	} else if ((probes -= neigh->parms->app_probes) < 0) {
    #ifdef CONFIG_ARPD
    		neigh_app_ns(neigh);
    #endif
    	} else {
    		addrconf_addr_solict_mult(target, &mcaddr);
    		ndisc_send_ns(dev, NULL, target, &mcaddr, saddr);
    	}
    }
    
    static void ndisc_recv_ns(struct sk_buff *skb)
    {
    
    	struct nd_msg *msg = (struct nd_msg *)skb_transport_header(skb);
    
    	struct in6_addr *saddr = &ipv6_hdr(skb)->saddr;
    	struct in6_addr *daddr = &ipv6_hdr(skb)->daddr;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	u8 *lladdr = NULL;
    
    	u32 ndoptlen = skb->tail - (skb->transport_header +
    				    offsetof(struct nd_msg, opt));
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct ndisc_options ndopts;
    	struct net_device *dev = skb->dev;
    	struct inet6_ifaddr *ifp;
    	struct inet6_dev *idev = NULL;
    	struct neighbour *neigh;
    
    	struct pneigh_entry *pneigh = NULL;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	int dad = ipv6_addr_any(saddr);
    	int inc;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	if (ipv6_addr_is_multicast(&msg->target)) {
    
    		ND_PRINTK2(KERN_WARNING
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			   "ICMPv6 NS: multicast target address");
    		return;
    	}
    
    	/*
    	 * RFC2461 7.1.1:
    	 * DAD has to be destined for solicited node multicast address.
    	 */
    	if (dad &&
    	    !(daddr->s6_addr32[0] == htonl(0xff020000) &&
    	      daddr->s6_addr32[1] == htonl(0x00000000) &&
    	      daddr->s6_addr32[2] == htonl(0x00000001) &&
    	      daddr->s6_addr [12] == 0xff )) {
    		ND_PRINTK2(KERN_WARNING
    			   "ICMPv6 NS: bad DAD packet (wrong destination)\n");
    		return;
    	}
    
    	if (!ndisc_parse_options(msg->opt, ndoptlen, &ndopts)) {
    
    		ND_PRINTK2(KERN_WARNING
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			   "ICMPv6 NS: invalid ND options\n");
    		return;
    	}
    
    	if (ndopts.nd_opts_src_lladdr) {
    		lladdr = ndisc_opt_addr_data(ndopts.nd_opts_src_lladdr, dev);
    		if (!lladdr) {
    			ND_PRINTK2(KERN_WARNING
    				   "ICMPv6 NS: invalid link-layer address length\n");
    			return;
    		}
    
    		/* RFC2461 7.1.1:
    
    		 *	If the IP source address is the unspecified address,
    		 *	there MUST NOT be source link-layer address option
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		 *	in the message.
    		 */
    		if (dad) {
    
    			ND_PRINTK2(KERN_WARNING
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				   "ICMPv6 NS: bad DAD packet (link-layer address option)\n");
    			return;
    		}
    	}
    
    	inc = ipv6_addr_is_multicast(daddr);
    
    
    	if ((ifp = ipv6_get_ifaddr(&init_net, &msg->target, dev, 1)) != NULL) {
    
    
    		if (ifp->flags & (IFA_F_TENTATIVE|IFA_F_OPTIMISTIC)) {
    			if (dad) {
    				if (dev->type == ARPHRD_IEEE802_TR) {
    
    					const unsigned char *sadr;
    					sadr = skb_mac_header(skb);
    
    					if (((sadr[8] ^ dev->dev_addr[0]) & 0x7f) == 0 &&
    					    sadr[9] == dev->dev_addr[1] &&
    					    sadr[10] == dev->dev_addr[2] &&
    					    sadr[11] == dev->dev_addr[3] &&
    					    sadr[12] == dev->dev_addr[4] &&
    					    sadr[13] == dev->dev_addr[5]) {
    						/* looped-back to us */
    						goto out;
    					}
    				}
    
    				/*
    				 * We are colliding with another node
    				 * who is doing DAD
    				 * so fail our DAD process
    				 */
    				addrconf_dad_failure(ifp);
    
    			} else {
    				/*
    				 * This is not a dad solicitation.
    				 * If we are an optimistic node,
    				 * we should respond.
    				 * Otherwise, we should ignore it.
    				 */
    				if (!(ifp->flags & IFA_F_OPTIMISTIC))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    					goto out;
    			}
    		}
    
    		idev = ifp->idev;
    	} else {
    		idev = in6_dev_get(dev);
    		if (!idev) {
    			/* XXX: count this drop? */
    			return;
    		}
    
    		if (ipv6_chk_acast_addr(dev, &msg->target) ||
    
    		    (idev->cnf.forwarding &&
    
    		     (ipv6_devconf.proxy_ndp || idev->cnf.proxy_ndp) &&
    
    					     &msg->target, dev, 0)) != NULL)) {
    
    			if (!(NEIGH_CB(skb)->flags & LOCALLY_ENQUEUED) &&
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			    skb->pkt_type != PACKET_HOST &&
    			    inc != 0 &&
    			    idev->nd_parms->proxy_delay != 0) {
    				/*
    				 * for anycast or proxy,
    
    				 * sender should delay its response
    				 * by a random time between 0 and
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				 * MAX_ANYCAST_DELAY_TIME seconds.
    				 * (RFC2461) -- yoshfuji
    				 */
    				struct sk_buff *n = skb_clone(skb, GFP_ATOMIC);
    				if (n)
    					pneigh_enqueue(&nd_tbl, idev->nd_parms, n);
    				goto out;
    			}
    		} else
    			goto out;
    	}
    
    
    	is_router = !!(pneigh ? pneigh->flags & NTF_ROUTER : idev->cnf.forwarding);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (dad) {
    		struct in6_addr maddr;
    
    		ipv6_addr_all_nodes(&maddr);
    		ndisc_send_na(dev, NULL, &maddr, &msg->target,
    
    			      is_router, 0, (ifp != NULL), 1);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		goto out;
    	}
    
    	if (inc)
    		NEIGH_CACHE_STAT_INC(&nd_tbl, rcv_probes_mcast);
    	else
    		NEIGH_CACHE_STAT_INC(&nd_tbl, rcv_probes_ucast);
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	 *	update / create cache entry
    	 *	for the source address
    	 */
    	neigh = __neigh_lookup(&nd_tbl, saddr, dev,
    			       !inc || lladdr || !dev->addr_len);
    	if (neigh)
    
    		neigh_update(neigh, lladdr, NUD_STALE,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			     NEIGH_UPDATE_F_WEAK_OVERRIDE|
    			     NEIGH_UPDATE_F_OVERRIDE);
    
    	if (neigh || !dev->header_ops) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		ndisc_send_na(dev, neigh, saddr, &msg->target,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			      1, (ifp != NULL && inc), inc);
    		if (neigh)
    			neigh_release(neigh);
    	}
    
    out:
    	if (ifp)
    		in6_ifa_put(ifp);
    	else
    		in6_dev_put(idev);
    
    	return;
    }
    
    static void ndisc_recv_na(struct sk_buff *skb)
    {
    
    	struct nd_msg *msg = (struct nd_msg *)skb_transport_header(skb);
    
    	struct in6_addr *saddr = &ipv6_hdr(skb)->saddr;
    	struct in6_addr *daddr = &ipv6_hdr(skb)->daddr;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	u8 *lladdr = NULL;
    
    	u32 ndoptlen = skb->tail - (skb->transport_header +
    				    offsetof(struct nd_msg, opt));
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct ndisc_options ndopts;
    	struct net_device *dev = skb->dev;
    	struct inet6_ifaddr *ifp;
    	struct neighbour *neigh;
    
    	if (skb->len < sizeof(struct nd_msg)) {
    		ND_PRINTK2(KERN_WARNING
    			   "ICMPv6 NA: packet too short\n");
    		return;
    	}
    
    	if (ipv6_addr_is_multicast(&msg->target)) {
    		ND_PRINTK2(KERN_WARNING
    			   "ICMPv6 NA: target address is multicast.\n");
    		return;
    	}
    
    	if (ipv6_addr_is_multicast(daddr) &&
    	    msg->icmph.icmp6_solicited) {
    		ND_PRINTK2(KERN_WARNING
    			   "ICMPv6 NA: solicited NA is multicasted.\n");
    		return;
    	}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (!ndisc_parse_options(msg->opt, ndoptlen, &ndopts)) {
    		ND_PRINTK2(KERN_WARNING
    			   "ICMPv6 NS: invalid ND option\n");
    		return;
    	}
    	if (ndopts.nd_opts_tgt_lladdr) {
    		lladdr = ndisc_opt_addr_data(ndopts.nd_opts_tgt_lladdr, dev);
    		if (!lladdr) {
    			ND_PRINTK2(KERN_WARNING
    				   "ICMPv6 NA: invalid link-layer address length\n");
    			return;
    		}
    	}
    
    	if ((ifp = ipv6_get_ifaddr(&init_net, &msg->target, dev, 1))) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		if (ifp->flags & IFA_F_TENTATIVE) {
    			addrconf_dad_failure(ifp);
    			return;
    		}
    		/* What should we make now? The advertisement
    		   is invalid, but ndisc specs say nothing
    		   about it. It could be misconfiguration, or
    		   an smart proxy agent tries to help us :-)
    		 */
    		ND_PRINTK1(KERN_WARNING
    			   "ICMPv6 NA: someone advertises our address on %s!\n",
    			   ifp->idev->dev->name);
    		in6_ifa_put(ifp);
    		return;
    	}
    	neigh = neigh_lookup(&nd_tbl, &msg->target, dev);
    
    	if (neigh) {
    		u8 old_flags = neigh->flags;
    
    		if (neigh->nud_state & NUD_FAILED)
    			goto out;
    
    
    		/*
    		 * Don't update the neighbor cache entry on a proxy NA from
    		 * ourselves because either the proxied node is off link or it
    		 * has already sent a NA to us.
    		 */
    		if (lladdr && !memcmp(lladdr, dev->dev_addr, dev->addr_len) &&
    
    		    ipv6_devconf.forwarding && ipv6_devconf.proxy_ndp &&
    
    		    pneigh_lookup(&nd_tbl, &init_net, &msg->target, dev, 0)) {
    
    			/* XXX: idev->cnf.prixy_ndp */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		neigh_update(neigh, lladdr,
    			     msg->icmph.icmp6_solicited ? NUD_REACHABLE : NUD_STALE,
    			     NEIGH_UPDATE_F_WEAK_OVERRIDE|
    			     (msg->icmph.icmp6_override ? NEIGH_UPDATE_F_OVERRIDE : 0)|
    			     NEIGH_UPDATE_F_OVERRIDE_ISROUTER|
    			     (msg->icmph.icmp6_router ? NEIGH_UPDATE_F_ISROUTER : 0));
    
    		if ((old_flags & ~neigh->flags) & NTF_ROUTER) {
    			/*
    			 * Change: router to host
    			 */
    			struct rt6_info *rt;
    			rt = rt6_get_dflt_router(saddr, dev);
    			if (rt)
    
    				ip6_del_rt(rt);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		}
    
    out:
    		neigh_release(neigh);
    	}
    }
    
    static void ndisc_recv_rs(struct sk_buff *skb)
    {
    
    	struct rs_msg *rs_msg = (struct rs_msg *)skb_transport_header(skb);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	unsigned long ndoptlen = skb->len - sizeof(*rs_msg);
    	struct neighbour *neigh;
    	struct inet6_dev *idev;
    
    	struct in6_addr *saddr = &ipv6_hdr(skb)->saddr;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct ndisc_options ndopts;
    	u8 *lladdr = NULL;
    
    	if (skb->len < sizeof(*rs_msg))
    		return;
    
    	idev = in6_dev_get(skb->dev);
    	if (!idev) {
    		if (net_ratelimit())
    			ND_PRINTK1("ICMP6 RS: can't find in6 device\n");
    		return;
    	}
    
    	/* Don't accept RS if we're not in router mode */
    	if (!idev->cnf.forwarding)
    		goto out;
    
    	/*
    	 * Don't update NCE if src = ::;
    	 * this implies that the source node has no ip address assigned yet.
    	 */
    	if (ipv6_addr_any(saddr))
    		goto out;
    
    	/* Parse ND options */
    	if (!ndisc_parse_options(rs_msg->opt, ndoptlen, &ndopts)) {
    		if (net_ratelimit())
    			ND_PRINTK2("ICMP6 NS: invalid ND option, ignored\n");
    		goto out;
    	}
    
    	if (ndopts.nd_opts_src_lladdr) {
    		lladdr = ndisc_opt_addr_data(ndopts.nd_opts_src_lladdr,
    					     skb->dev);
    		if (!lladdr)
    			goto out;
    	}
    
    	neigh = __neigh_lookup(&nd_tbl, saddr, skb->dev, 1);
    	if (neigh) {
    		neigh_update(neigh, lladdr, NUD_STALE,
    			     NEIGH_UPDATE_F_WEAK_OVERRIDE|
    			     NEIGH_UPDATE_F_OVERRIDE|
    			     NEIGH_UPDATE_F_OVERRIDE_ISROUTER);
    		neigh_release(neigh);
    	}
    out:
    	in6_dev_put(idev);