patch-2.1.79 linux/net/ipv6/ndisc.c

Next file: linux/net/ipv6/raw.c
Previous file: linux/net/ipv6/mcast.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.1.78/linux/net/ipv6/ndisc.c linux/net/ipv6/ndisc.c
@@ -19,15 +19,25 @@
  *						of an RA.
  *
  *	Janos Farkas			:	kmalloc failure checks
+ *	Alexey Kuznetsov		:	state machine reworked
+ *						and moved to net/core.
  */
 
 /* Set to 3 to get tracing... */
 #define ND_DEBUG 1
 
-#if ND_DEBUG >= 3
-#define NDBG(x) printk x
-#else
-#define NDBG(x)
+#define ND_PRINTK(x...) printk(KERN_DEBUG x)
+#define ND_NOPRINTK(x...) do { ; } while(0)
+#define ND_PRINTK0 ND_PRINTK
+#define ND_PRINTK1 ND_NOPRINTK
+#define ND_PRINTK2 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
 
 #define __NO_VERSION__
@@ -42,6 +52,9 @@
 #include <linux/in6.h>
 #include <linux/route.h>
 #include <linux/init.h>
+#ifdef CONFIG_SYSCTL
+#include <linux/sysctl.h>
+#endif
 
 #include <linux/if_arp.h>
 #include <linux/ipv6.h>
@@ -61,380 +74,186 @@
 #include <net/checksum.h>
 #include <linux/proc_fs.h>
 
-#define NCACHE_NUM_BUCKETS 32
-
 static struct inode ndisc_inode;
 static struct socket *ndisc_socket=&ndisc_inode.u.socket_i;
 
-unsigned long nd_rand_seed = 152L;
-
-struct ndisc_statistics nd_stats;
-
-static struct neigh_table nd_tbl;
-
-unsigned int	ndisc_hash(void *primary_key);
-int		ndisc_eth_resolv(unsigned char *h_dest, struct sk_buff *skb);
-
-static struct neigh_ops nd_neigh_ops = {
-	ETH_P_IPV6,
-	ndisc_hash,
-	ndisc_eth_resolv,
-	NULL
+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 =
+{
+	AF_INET6,
+	NULL,
+	ndisc_solicit,
+	ndisc_error_report,
+	neigh_resolve_output,
+	neigh_connected_output,
+	dev_queue_xmit,
+	dev_queue_xmit
 };
 
-static struct timer_list ndisc_timer;
-static struct timer_list ndisc_gc_timer;
-
-/*
- *	Protocol variables
- */
-
-unsigned long	nd_reachable_time		= RECHABLE_TIME;
-int		nd_gc_interval			= 5 * HZ;
-
-/* 
- *	garbage collection timeout must be greater than reachable time
- *	since tstamp is updated by reachable confirmations only.
- *	gc_staletime actually means the time after last confirmation
- *	*NOT* after the last time the entry was used.
- */
-
-int	nd_gc_staletime			= 3 * RECHABLE_TIME;
-
-
-static int  ndisc_event_timer(struct nd_neigh *ndn);
-
-unsigned long ipv6_random(void)
+static struct neigh_ops ndisc_hh_ops =
 {
-	nd_rand_seed=nd_rand_seed*69069L+1;
-        return nd_rand_seed^jiffies;
-}
-
-static __inline__ unsigned long rand_reach_time(void)
-{
-	unsigned long val;
-
-	val = ipv6_random() % (MAX_RANDOM_FACTOR *
-			       ipv6_config.nd_base_reachable_time);
+	AF_INET6,
+	NULL,
+	ndisc_solicit,
+	ndisc_error_report,
+	neigh_resolve_output,
+	neigh_resolve_output,
+	dev_queue_xmit,
+	dev_queue_xmit
+};
 
-	if (val < (MIN_RANDOM_FACTOR * ipv6_config.nd_base_reachable_time))
-		val+= (MIN_RANDOM_FACTOR * ipv6_config.nd_base_reachable_time);
 
-	return val;
-}
-
-unsigned int ndisc_hash(void *primary_key)
+static struct neigh_ops ndisc_direct_ops =
 {
-        struct in6_addr *addr = (struct in6_addr *) primary_key;
-        __u32 hash_val;
-        
-	addr = (struct in6_addr *) primary_key;
-	
-        hash_val = addr->s6_addr32[2] ^ addr->s6_addr32[3];
-
-        hash_val ^= hash_val >> 16;
-        
-        return (hash_val & (NCACHE_NUM_BUCKETS - 1));
-}
-
-static int ndisc_gc_func(struct neighbour *neigh, void *arg);
+	AF_INET6,
+	NULL,
+	NULL,
+	NULL,
+	dev_queue_xmit,
+	dev_queue_xmit,
+	dev_queue_xmit,
+	dev_queue_xmit
+};
 
-static void ndisc_periodic_timer(unsigned long arg)
+struct neigh_table nd_tbl =
 {
-	static unsigned long last_rand = 0;
-	unsigned long now = jiffies;
-	
-	/*
-	 *	periodicly compute ReachableTime from random function
-	 */
-	
-	if ((now - last_rand) > REACH_RANDOM_INTERVAL) {
-		last_rand = now;
-		nd_reachable_time = rand_reach_time();
-	}
-
-	neigh_table_lock(&nd_tbl);
+	NULL,
+	AF_INET6,
+	sizeof(struct neighbour) + sizeof(struct in6_addr),
+	sizeof(struct in6_addr),
+	ndisc_constructor,
+	pndisc_constructor,
+	pndisc_destructor,
+	pndisc_redo,
+        { NULL, NULL, 30*HZ, 1*HZ, 60*HZ, 30*HZ, 5*HZ, 3, 3, 0, 3, 1*HZ, (8*HZ)/10, 0, 64 },
+	30*HZ, 128, 512, 1024,
+};
 
-	start_bh_atomic();
-	if (atomic_read(&nd_tbl.tbl_lock) == 1) {
-		ntbl_walk_table(&nd_tbl, ndisc_gc_func, 0, 0, NULL);
-		ndisc_gc_timer.expires = now + nd_gc_interval;
-	} else {
-#if ND_DEBUG >= 2
-		printk(KERN_DEBUG "ndisc_gc delayed: table locked\n");
-#endif
-		ndisc_gc_timer.expires = now + HZ;
-	}
-	end_bh_atomic();
-	
-	neigh_table_unlock(&nd_tbl);
-	
-	add_timer(&ndisc_gc_timer);
-}
+#define NDISC_OPT_SPACE(len) (((len)+2+7)&~7)
 
-static int ndisc_gc_func(struct neighbour *neigh, void *arg)
+static u8 *ndisc_fill_option(u8 *opt, int type, void *data, int data_len)
 {
-	struct nd_neigh *ndn = (struct nd_neigh *) neigh;
-        unsigned long now = jiffies;
+	int space = NDISC_OPT_SPACE(data_len);
 
-	if (atomic_read(&ndn->ndn_refcnt) == 0) {
-		switch (ndn->ndn_nud_state) {
-		
-		case NUD_REACHABLE:
-		case NUD_STALE:
-			if (now - ndn->ndn_tstamp < nd_gc_staletime)
-				break;
-		case NUD_FAILED:
-			return 1;
-		default:
-		};
-	}
-	return 0;
+	opt[0] = type;
+	opt[1] = space>>3;
+	memcpy(opt+2, data, data_len);
+	data_len += 2;
+	if ((space -= data_len) > 0)
+		memset(opt + data_len, 0, space);
+	return opt + space;
 }
 
-static __inline__ void ndisc_add_timer(struct nd_neigh *ndn, int timer)
+int ndisc_mc_map(struct in6_addr *addr, char *buf, struct device *dev, int dir)
 {
-	unsigned long now = jiffies;
-	unsigned long tval = ~0UL;
-
-	ndn->ndn_expires = now + timer;
-	
-	if (del_timer(&ndisc_timer))
-		tval = ndisc_timer.expires;
-
-	tval = min(tval, ndn->ndn_expires);
-
-	ndisc_timer.expires = tval;
-	add_timer(&ndisc_timer);
-}
-        
-static void ndisc_del_timer(struct nd_neigh *ndn)
-{
-	unsigned long tval = ~0UL;
-	unsigned long neigh_val;
-
-	if (del_timer(&ndisc_timer))
-		tval = ndisc_timer.expires;
-
-	neigh_val = ndn->ndn_expires;
-	ndn->ndn_expires = 0;
-
-	if (tval == neigh_val) {
-		int i;
-		
-		tval = ~0UL;
-
-		neigh_table_lock(&nd_tbl);
-		
-		/* need to search the entire neighbour cache */
-		for (i=0; i < nd_tbl.tbl_size; i++) {
-			struct neighbour *neigh, *head;
-			head = nd_tbl.hash_buckets[i];
-				
-			if ((neigh = head) == NULL)
-				continue;
-
-			do {
-				struct nd_neigh *n;
-
-				n = (struct nd_neigh *) neigh;
-
-				if ((n->ndn_nud_state & NUD_IN_TIMER) &&
-				     n->ndn_expires)
-					tval = min(tval, n->ndn_expires);
-
-				neigh = neigh->next;
-
-			} while (neigh != head);
+	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;
+	default:
+		if (dir) {
+			memcpy(buf, dev->broadcast, dev->addr_len);
+			return 0;
 		}
-		neigh_table_unlock(&nd_tbl);
-	}
-
-	if (tval == ~(0UL))
-		return;
-
-	ndisc_timer.expires = tval;
-	add_timer(&ndisc_timer);
-}
-
-static int ndisc_forced_gc(struct neighbour *neigh, void *arg)
-{
-	struct nd_neigh *ndn = (struct nd_neigh *) neigh;
-
-	if (atomic_read(&ndn->ndn_refcnt) == 0) {
-		if (ndn->ndn_nud_state & NUD_IN_TIMER)
-			ndisc_del_timer(ndn);
-		
-		return 1;
 	}
-	return 0;
+	return -EINVAL;
 }
 
-static struct nd_neigh * ndisc_new_neigh(struct device *dev,
-					 struct in6_addr *addr)
+static int ndisc_constructor(struct neighbour *neigh)
 {
-	struct nd_neigh *ndn;
+	struct in6_addr *addr = (struct in6_addr*)&neigh->primary_key;
+	struct device *dev = neigh->dev;
+	struct inet6_dev *in6_dev = ipv6_get_idev(dev);
+	int addr_type;
+
+	if (in6_dev == NULL)
+		return -EINVAL;
+
+	addr_type = ipv6_addr_type(addr);
+	if (in6_dev->nd_parms)
+		neigh->parms = in6_dev->nd_parms;
 
-	NDBG(("ndisc_new_neigh("));
-	if(dev)
-		NDBG(("%s,", dev->name));
+	if (addr_type&IPV6_ADDR_MULTICAST)
+		neigh->type = RTN_MULTICAST;
 	else
-		NDBG(("[NULL],"));
-	NDBG(("[%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x]): ",
-	      addr->s6_addr16[0], addr->s6_addr16[1], addr->s6_addr16[2],
-	      addr->s6_addr16[3], addr->s6_addr16[4], addr->s6_addr16[5],
-	      addr->s6_addr16[6], addr->s6_addr16[7]));
-
-	ndn = (struct nd_neigh *) neigh_alloc(sizeof(struct nd_neigh),
-					      &nd_neigh_ops);
-	if (ndn == NULL) {
-
-#if ND_DEBUG >= 2
-		printk(KERN_DEBUG "neigh_alloc: out of memory\n");
-#endif
-
-		start_bh_atomic();
-		if (atomic_read(&nd_tbl.tbl_lock) == 1) {
-#if ND_DEBUG >= 2
-			printk(KERN_DEBUG "ndisc_alloc: forcing gc\n");
-#endif
-			ntbl_walk_table(&nd_tbl, ndisc_forced_gc, 0, 0, NULL);
+		neigh->type = RTN_UNICAST;
+	if (dev->hard_header == NULL) {
+		neigh->nud_state = NUD_NOARP;
+		neigh->ops = &ndisc_direct_ops;
+		neigh->output = neigh->ops->queue_xmit;
+	} else {
+		if (addr_type&IPV6_ADDR_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);
 		}
-		
-		end_bh_atomic();
-#if ND_DEBUG >= 1
-		printk(KERN_DEBUG "ndisc_alloc failed\n");
-#endif
-		return NULL;
-	}
-
-	nd_stats.allocs++;
-
-	ipv6_addr_copy(&ndn->ndn_addr, addr);
-	ndn->ndn_plen = 128;
-	ndn->ndn_type = ipv6_addr_type(addr);
-	ndn->ndn_dev = dev;
-	ndn->ndn_tstamp = jiffies;
-
-	if ((ndn->ndn_type & IPV6_ADDR_MULTICAST)) {
-		NDBG(("MULTICAST(NCF_NOARP) "));
-		ndn->ndn_flags |= NCF_NOARP;
-	}
-
-	if (dev->type == ARPHRD_LOOPBACK || dev->type == ARPHRD_SIT) {
-		NDBG(("%s(NCF_NOARP) ",
-		      (dev->type==ARPHRD_LOOPBACK) ? "LOOPBACK" : "SIT"));
-		ndn->ndn_flags |= NCF_NOARP;
+		if (dev->hard_header_cache)
+			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;
 	}
 
-	neigh_insert(&nd_tbl, (struct neighbour *) ndn);
-	NDBG(("returning ndn(%p)\n", ndn));
-	return ndn;
+	return 0;
 }
 
-/*
- *	Called when creating a new dest_cache entry for a given destination
- *	is likely that an entry for the refered gateway exists in cache
- *
- */
-
-struct neighbour * ndisc_get_neigh(struct device *dev, struct in6_addr *addr)
+static int pndisc_constructor(struct pneigh_entry *n)
 {
-	struct nd_neigh *neigh;
-
-	/*
-	 *	neighbour cache:
-	 *	cached information about nexthop and addr resolution
-	 */
-
-	if (dev == NULL) {
-#if ND_DEBUG >= 1
-		printk(KERN_DEBUG "ndisc_get_neigh: NULL device\n");
+	struct in6_addr *addr = (struct in6_addr*)&n->key;
+	struct in6_addr maddr;
+	struct device *dev = n->dev;
+
+	if (dev == NULL || ipv6_get_idev(dev) == NULL)
+		return -EINVAL;
+#ifndef CONFIG_IPV6_NO_PB
+	addrconf_addr_solict_mult_old(addr, &maddr);
+	ipv6_dev_mc_inc(dev, &maddr);
+#endif
+#ifdef CONFIG_IPV6_EUI64
+	addrconf_addr_solict_mult_new(addr, &maddr);
+	ipv6_dev_mc_inc(dev, &maddr);
 #endif
-		return NULL;
-	}
-
-	neigh_table_lock(&nd_tbl);
-
-        neigh = (struct nd_neigh *) neigh_lookup(&nd_tbl, (void *) addr,
-						 sizeof(struct in6_addr), dev);
-	if (neigh == NULL) {
-		neigh = ndisc_new_neigh(dev, addr);
-
-		if (neigh == NULL)
-			return NULL;
-	}
-
-	neigh_table_unlock(&nd_tbl);
-
-	return neighbour_clone((struct neighbour *) neigh);
+	return 0;
 }
 
-/*
- *	return values
- *	0 - Address Resolution succeded, send packet
- *	1 - Address Resolution unfinished / packet queued
- */
-
-int ndisc_eth_resolv(unsigned char *h_dest, struct sk_buff *skb)
+static void pndisc_destructor(struct pneigh_entry *n)
 {
-	struct nd_neigh *ndn = NULL;
-
-	if (skb->dst)
-		ndn = (struct nd_neigh *) skb->dst->neighbour;
+	struct in6_addr *addr = (struct in6_addr*)&n->key;
+	struct in6_addr maddr;
+	struct device *dev = n->dev;
 
-	if (ndn == NULL) {
-#if ND_DEBUG >= 2
-		printk(KERN_DEBUG "ndisc_eth_resolv: nexthop is NULL\n");
+	if (dev == NULL || ipv6_get_idev(dev) == NULL)
+		return;
+#ifndef CONFIG_IPV6_NO_PB
+	addrconf_addr_solict_mult_old(addr, &maddr);
+	ipv6_dev_mc_dec(dev, &maddr);
 #endif
-		goto discard;
-	}
-
-	if ((ndn->ndn_type & IPV6_ADDR_MULTICAST)) {
-		struct in6_addr *daddr;
-
-		daddr = &skb->nh.ipv6h->daddr;
-		if (skb->dev->type == ARPHRD_ETHER)
-			ipv6_mc_map(daddr, h_dest);
-		else
-			memcpy(h_dest, skb->dev->broadcast, skb->dev->addr_len);
-		return 0;
-	}
-
-	switch (ndn->ndn_nud_state) {	
-	case NUD_FAILED:
-	case NUD_NONE:
-		ndisc_event_send((struct neighbour *)ndn, skb);
-
-	case NUD_INCOMPLETE:			
-		if (skb_queue_len(&ndn->neigh.arp_queue) >= NDISC_QUEUE_LEN) {
-			struct sk_buff *buff;
-			
-			buff = ndn->neigh.arp_queue.prev;
-			skb_unlink(buff);
-			dev_kfree_skb(buff, FREE_WRITE);
-		}
-		skb_queue_head(&ndn->neigh.arp_queue, skb);
-		return 1;
-	default:
-		ndisc_event_send((struct neighbour *)ndn, skb);
-	};
-
-	if ((ndn->ndn_flags & NTF_COMPLETE) == 0) {
-#if ND_DEBUG >=1
-		/* This shouldn't happen */
-		printk(KERN_DEBUG "ND: using incomplete entry\n");
+#ifdef CONFIG_IPV6_EUI64
+	addrconf_addr_solict_mult_new(addr, &maddr);
+	ipv6_dev_mc_dec(dev, &maddr);
 #endif
-	}
-	memcpy(h_dest, ndn->ndn_ha, skb->dev->addr_len);
-	return 0;
-
-  discard:
-	
-	dev_kfree_skb(skb, FREE_WRITE);
-	return 1;
 }
 
+
+
 static int
 ndisc_build_ll_hdr(struct sk_buff *skb, struct device *dev,
 		   struct in6_addr *daddr, struct neighbour *neigh, int len)
@@ -442,44 +261,30 @@
 	unsigned char ha[MAX_ADDR_LEN];
 	unsigned char *h_dest = NULL;
 
-	skb->arp = 1;
-	if (dev->hard_header_len) {
-		skb_reserve(skb, (dev->hard_header_len + 15) & ~15);
-
-		if (dev->hard_header) {
-			if (ipv6_addr_type(daddr) & IPV6_ADDR_MULTICAST) {
-				nd_stats.snt_probes_mcast++;
-				if (dev->type == ARPHRD_ETHER)
-					ipv6_mc_map(daddr, ha);
-				else
-					memcpy(ha, dev->broadcast, dev->addr_len);
-				h_dest = ha;
-			} else if (neigh) {
-				h_dest = neigh->ha;
-				nd_stats.snt_probes_ucast++;
-			} else {
-				struct nd_neigh *ndn;
-
-				neigh_table_lock(&nd_tbl);
+	skb_reserve(skb, (dev->hard_header_len + 15) & ~15);
 
-				neigh = neigh_lookup(&nd_tbl, (void *) daddr,
-						     sizeof(struct in6_addr), dev);
-				if (neigh) {
-					ndn = (struct nd_neigh*)neigh;
-					if (ndn->ndn_flags&NTF_COMPLETE) {
-						memcpy(ha, ndn->ndn_ha, dev->addr_len);
-						h_dest = ha;
-					}
+	if (dev->hard_header) {
+		if (ipv6_addr_type(daddr) & IPV6_ADDR_MULTICAST) {
+			ndisc_mc_map(daddr, ha, dev, 1);
+			h_dest = ha;
+		} else if (neigh) {
+			h_dest = neigh->ha;
+		} else {
+			neigh = neigh_lookup(&nd_tbl, daddr, dev);
+			if (neigh) {
+				if (neigh->nud_state&NUD_VALID) {
+					memcpy(ha, neigh->ha, dev->addr_len);
+					h_dest = ha;
 				}
-				neigh_table_unlock(&nd_tbl);
+				neigh_release(neigh);
 			}
-
-			if (dev->hard_header(skb, dev, ETH_P_IPV6, h_dest, NULL, len) < 0)
-				skb->arp = 0;
 		}
+
+		if (dev->hard_header(skb, dev, ETH_P_IPV6, h_dest, NULL, len) < 0)
+			return 0;
 	}
 
-	return skb->arp;
+	return 1;
 }
 
 
@@ -487,56 +292,34 @@
  *	Send a Neighbour Advertisement
  */
 
-void ndisc_send_na(struct device *dev, struct nd_neigh *ndn,
+void ndisc_send_na(struct device *dev, struct neighbour *neigh,
 		   struct in6_addr *daddr, struct in6_addr *solicited_addr,
 		   int router, int solicited, int override, int inc_opt) 
 {
         struct sock *sk = ndisc_socket->sk;
         struct nd_msg *msg;
-        int len, opt_len;
+        int len;
         struct sk_buff *skb;
 	int err;
 
-	NDBG(("ndisc_send_na("));
-	if(dev)
-		NDBG(("%s,", dev->name));
-	else
-		NDBG(("[NULL]"));
-	NDBG(("%p): ", ndn));
-	if(daddr)
-		NDBG(("daddr[%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x] ",
-		      daddr->s6_addr16[0], daddr->s6_addr16[1], daddr->s6_addr16[2],
-		      daddr->s6_addr16[3], daddr->s6_addr16[4], daddr->s6_addr16[5],
-		      daddr->s6_addr16[6], daddr->s6_addr16[7]));
-	if(solicited_addr)
-		NDBG(("solicit_addr[%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x] ",
-		      solicited_addr->s6_addr16[0], solicited_addr->s6_addr16[1],
-		      solicited_addr->s6_addr16[2], solicited_addr->s6_addr16[3],
-		      solicited_addr->s6_addr16[4], solicited_addr->s6_addr16[5],
-		      solicited_addr->s6_addr16[6], solicited_addr->s6_addr16[7]));
-	NDBG(("rtr(%d)sol(%d)ovr(%d)iopt(%d)\n", router, solicited, override, inc_opt));
-
-	opt_len = ((dev->addr_len + 1) >> 3) + 1;
 	len = sizeof(struct icmp6hdr) + sizeof(struct in6_addr);
 
-#if ND_DEBUG >=1
-	if (dev == NULL) {
-		printk(KERN_DEBUG "send_na: null device\n");
-		return;
+	if (inc_opt) {
+		if (dev->addr_len)
+			len += NDISC_OPT_SPACE(dev->addr_len);
+		else
+			inc_opt = 0;
 	}
-#endif
-	if (inc_opt)
-		len += opt_len << 3;
 
 	skb = sock_alloc_send_skb(sk, MAX_HEADER + len + dev->hard_header_len + 15,
 				  0, 0, &err);
 
 	if (skb == NULL) {
-		printk(KERN_DEBUG "send_na: alloc skb failed\n");
+		ND_PRINTK1("send_na: alloc skb failed\n");
 		return;
 	}
 
-	if (ndisc_build_ll_hdr(skb, dev, daddr, (struct neighbour*)ndn, len) == 0) {
+	if (ndisc_build_ll_hdr(skb, dev, daddr, neigh, len) == 0) {
 		kfree_skb(skb, FREE_WRITE);
 		return;
 	}
@@ -557,17 +340,8 @@
         /* Set the target address. */
 	ipv6_addr_copy(&msg->target, solicited_addr);
 
-	if (inc_opt) {
-		/* Set the source link-layer address option. */
-		msg->opt.opt_type = ND_OPT_TARGET_LL_ADDR;
-		msg->opt.opt_len = opt_len;
-		memcpy(msg->opt.link_addr, dev->dev_addr, dev->addr_len);
-
-		if ((opt_len << 3) - (2 + dev->addr_len)) {
-			memset(msg->opt.link_addr + dev->addr_len, 0,
-			       (opt_len << 3) - (2 + dev->addr_len));
-		}
-	}
+	if (inc_opt)
+		ndisc_fill_option((void*)&msg->opt, ND_OPT_TARGET_LL_ADDR, dev->dev_addr, dev->addr_len);
 
 	/* checksum */
 	msg->icmph.icmp6_cksum = csum_ipv6_magic(solicited_addr, daddr, len, 
@@ -585,48 +359,20 @@
         struct sock *sk = ndisc_socket->sk;
         struct sk_buff *skb;
         struct nd_msg *msg;
-        int len, opt_len;	
+        int len;
 	int err;
 
-	NDBG(("ndisc_send_ns(%s,%p): ", (dev ? dev->name : "[NULL]"), neigh));
-	if(daddr)
-		NDBG(("daddr[%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x] ",
-		      daddr->s6_addr16[0], daddr->s6_addr16[1], daddr->s6_addr16[2],
-		      daddr->s6_addr16[3], daddr->s6_addr16[4], daddr->s6_addr16[5],
-		      daddr->s6_addr16[6], daddr->s6_addr16[7]));
-	if(saddr)
-		NDBG(("saddr[%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x] ",
-		      saddr->s6_addr16[0], saddr->s6_addr16[1], saddr->s6_addr16[2],
-		      saddr->s6_addr16[3], saddr->s6_addr16[4], saddr->s6_addr16[5],
-		      saddr->s6_addr16[6], saddr->s6_addr16[7]));
-	if(solicit)
-		NDBG(("solicit[%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x] ",
-		      solicit->s6_addr16[0], solicit->s6_addr16[1],
-		      solicit->s6_addr16[2], solicit->s6_addr16[3],
-		      solicit->s6_addr16[4], solicit->s6_addr16[5],
-		      solicit->s6_addr16[6], solicit->s6_addr16[7]));
-	NDBG(("\n"));
-
-	/* length of addr in 8 octet groups.*/
-	opt_len = ((dev->addr_len + 1) >> 3) + 1;
-	len = sizeof(struct icmp6hdr) + sizeof(struct in6_addr) +
-                (opt_len << 3);
+	len = sizeof(struct icmp6hdr) + sizeof(struct in6_addr);
+	if (dev->addr_len)
+		len += NDISC_OPT_SPACE(dev->addr_len);
 
 	skb = sock_alloc_send_skb(sk, MAX_HEADER + len + dev->hard_header_len + 15,
 				  0, 0, &err);
 	if (skb == NULL) {
-#if ND_DEBUG >= 1
-		printk(KERN_DEBUG "send_ns: alloc skb failed\n");
-#endif
+		ND_PRINTK1("send_ns: alloc skb failed\n");
 		return;
 	}
 
-#if 0
-	/* Why Pedro did it? Is it remnant of early
-	   attempts to avoid looping back? I have no idea. --ANK */
-	skb->pkt_type = PACKET_NDISC;
-#endif
-
 	if (saddr == NULL) {
 		struct inet6_ifaddr *ifa;
 
@@ -643,7 +389,7 @@
 	}
 
 	ip6_nd_hdr(sk, skb, dev, saddr, daddr, IPPROTO_ICMPV6, len);
-	
+
 	msg = (struct nd_msg *)skb_put(skb, len);
 	msg->icmph.icmp6_type = NDISC_NEIGHBOUR_SOLICITATION;
 	msg->icmph.icmp6_code = 0;
@@ -653,16 +399,8 @@
 	/* Set the target address. */
 	ipv6_addr_copy(&msg->target, solicit);
 
-	/* Set the source link-layer address option. */
-	msg->opt.opt_type = ND_OPT_SOURCE_LL_ADDR;
-	msg->opt.opt_len = opt_len;
-
-	memcpy(msg->opt.link_addr, dev->dev_addr, dev->addr_len);
-
-	if ((opt_len << 3) - (2 + dev->addr_len)) {
-		memset(msg->opt.link_addr + dev->addr_len, 0,
-		       (opt_len << 3) - (2 + dev->addr_len));
-	}
+	if (dev->addr_len)
+		ndisc_fill_option((void*)&msg->opt, ND_OPT_SOURCE_LL_ADDR, dev->dev_addr, dev->addr_len);
 
 	/* checksum */
 	msg->icmph.icmp6_cksum = csum_ipv6_magic(&skb->nh.ipv6h->saddr,
@@ -681,30 +419,17 @@
         struct sk_buff *skb;
         struct icmp6hdr *hdr;
 	__u8 * opt;
-        int len, opt_len;
+        int len;
 	int err;
 
-	NDBG(("ndisc_send_rs(%s): ", (dev ? dev->name : "[NULL]")));
-	if(daddr)
-		NDBG(("daddr[%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x] ",
-		      daddr->s6_addr16[0], daddr->s6_addr16[1], daddr->s6_addr16[2],
-		      daddr->s6_addr16[3], daddr->s6_addr16[4], daddr->s6_addr16[5],
-		      daddr->s6_addr16[6], daddr->s6_addr16[7]));
-	if(saddr)
-		NDBG(("saddr[%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x] ",
-		      saddr->s6_addr16[0], saddr->s6_addr16[1], saddr->s6_addr16[2],
-		      saddr->s6_addr16[3], saddr->s6_addr16[4], saddr->s6_addr16[5],
-		      saddr->s6_addr16[6], saddr->s6_addr16[7]));
-	NDBG(("\n"));
-
-	/* length of addr in 8 octet groups.*/
-	opt_len = ((dev->addr_len + 1) >> 3) + 1;
-	len = sizeof(struct icmp6hdr) + (opt_len << 3);
+	len = sizeof(struct icmp6hdr);
+	if (dev->addr_len)
+		len += NDISC_OPT_SPACE(dev->addr_len);
 
         skb = sock_alloc_send_skb(sk, MAX_HEADER + len + dev->hard_header_len + 15,
 				  0, 0, &err);
 	if (skb == NULL) {
-		printk(KERN_DEBUG "send_ns: alloc skb failed\n");
+		ND_PRINTK1("send_ns: alloc skb failed\n");
 		return;
 	}
 
@@ -714,7 +439,7 @@
 	}
 
 	ip6_nd_hdr(sk, skb, dev, saddr, daddr, IPPROTO_ICMPV6, len);
-	
+
         hdr = (struct icmp6hdr *) skb_put(skb, len);
         hdr->icmp6_type = NDISC_ROUTER_SOLICITATION;
         hdr->icmp6_code = 0;
@@ -723,16 +448,8 @@
 
 	opt = (u8*) (hdr + 1);
 
-        /* Set the source link-layer address option. */
-        opt[0] = ND_OPT_SOURCE_LL_ADDR;
-        opt[1] = opt_len;
-
-        memcpy(opt + 2, dev->dev_addr, dev->addr_len);
-
-	if ((opt_len << 3) - (2 + dev->addr_len)) {
-		memset(opt + 2 + dev->addr_len, 0,
-		       (opt_len << 3) - (2 + dev->addr_len));
-	}
+	if (dev->addr_len)
+		ndisc_fill_option(opt, ND_OPT_SOURCE_LL_ADDR, dev->dev_addr, dev->addr_len);
 
 	/* checksum */
 	hdr->icmp6_cksum = csum_ipv6_magic(&skb->nh.ipv6h->saddr, daddr, len,
@@ -744,330 +461,79 @@
 }
 		   
 
-static int ndisc_store_hwaddr(struct nd_neigh *ndn, __u8 *opt, int opt_len,
-			      int option)
+static u8 * ndisc_find_option(u8 *opt, int opt_len, int len, int option)
 {
-	while (*opt != option && opt_len) {
-		int len;
+	while (opt_len <= len) {
+		int l = opt[1]<<3;
 
-		len = opt[1] << 3;
-		
-		if (len == 0)
-		{
-			printk(KERN_WARNING "nd: option has 0 len\n");
-			return -EINVAL;
-		}
+		if (opt[0] == option && l >= opt_len)
+			return opt + 2;
 
-		opt += len;
-		opt_len -= len;
-	}
+		if (l == 0) {
+			if (net_ratelimit())
+			    printk(KERN_WARNING "ndisc: option has 0 len\n");
+			return NULL;
+		}
 
-	if (*opt == option) {
-		memcpy(ndn->neigh.ha, opt + 2, ndn->ndn_dev->addr_len); 
-		return 0;
+		opt += l;
+		len -= l;
 	}
-
-	return -EINVAL;
+	return NULL;
 }
 
-/* Called when a timer expires for a neighbour entry. */
 
-static void ndisc_timer_handler(unsigned long arg) 
+static void ndisc_error_report(struct neighbour *neigh, struct sk_buff *skb)
 {
-	unsigned long now = jiffies;
-	unsigned long ntimer = ~0UL;
-        int i;
-
-	neigh_table_lock(&nd_tbl);
-	
-	for (i=0; i < nd_tbl.tbl_size; i++) {
-		struct nd_neigh *ndn, *head;
-
-		head = (struct nd_neigh *) nd_tbl.hash_buckets[i];
-
-		if ((ndn = head) == NULL)
-			continue;
-
-		do {
-                        if (ndn->ndn_nud_state & NUD_IN_TIMER) {
-				unsigned long time;
-
-				time = ndn->ndn_expires - now;
-
-				if ((long) time <= 0)
-					time = ndisc_event_timer(ndn);
-				
-				if (time)
-					ntimer = min(ntimer, time);
-			}
-			ndn = (struct nd_neigh *) ndn->neigh.next;
-		} while (ndn != head);
-	}
-
-	if (ntimer != (~0UL)) {
-		unsigned long tval = jiffies + ntimer;
-		if (del_timer(&ndisc_timer)) {
-			if (ndisc_timer.expires - tval < 0)
-				tval = ndisc_timer.expires;
-		}
-		ndisc_timer.expires = tval;
-		add_timer(&ndisc_timer);
-	}
-
-	neigh_table_unlock(&nd_tbl);
+	/*
+	 *	"The sender MUST return an ICMP
+	 *	 destination unreachable"
+	 */
+	icmpv6_send(skb, ICMPV6_DEST_UNREACH, ICMPV6_ADDR_UNREACH, 0, skb->dev);
+	kfree_skb(skb, FREE_WRITE);
 }
 
-
-static int ndisc_event_timer(struct nd_neigh *ndn)
+static void ndisc_solicit(struct neighbour *neigh, struct sk_buff *skb)
 {
-	struct in6_addr *daddr;
-	struct in6_addr *target;
+	struct in6_addr *saddr = NULL;
 	struct in6_addr mcaddr;
-	struct device *dev;
-	int max_probes;
-
-	if (ndn->ndn_nud_state == NUD_DELAY)
-		ndn->ndn_nud_state = NUD_PROBE;
-
-	max_probes = (ndn->ndn_nud_state == NUD_PROBE ?
-		      ipv6_config.nd_max_ucast_solicit:
-		      ipv6_config.nd_max_mcast_solicit);
-
-	if (ndn->ndn_probes == max_probes) {
-		struct sk_buff *skb;
-
-		ndn->ndn_nud_state = NUD_FAILED;
-		ndn->ndn_flags &= ~NTF_COMPLETE;
-		nd_stats.res_failed++;
-
-		while((skb=skb_dequeue(&ndn->neigh.arp_queue))) {
-			/*
-			 *	"The sender MUST return an ICMP
-			 *	 destination unreachable"
-			 */
-			icmpv6_send(skb, ICMPV6_DEST_UNREACH,
-				    ICMPV6_ADDR_UNREACH, 0, ndn->ndn_dev);
-
-			dev_kfree_skb(skb, FREE_WRITE);
-		}
-		return 0;
-	}
-
-	ndn->ndn_probes++;
-
-	dev = ndn->ndn_dev;
-	target = &ndn->ndn_addr;
-
-	if (ndn->ndn_nud_state == NUD_INCOMPLETE) {
-		addrconf_addr_solict_mult(&ndn->ndn_addr, &mcaddr);
-		daddr = &mcaddr;
-		ndn = NULL;
+	struct device *dev = neigh->dev;
+	struct in6_addr *target = (struct in6_addr *)&neigh->primary_key;
+	int probes = neigh->probes;
+
+	if (skb && ipv6_chk_addr(&skb->nh.ipv6h->saddr, dev, 0))
+		saddr = &skb->nh.ipv6h->saddr;
+
+	if ((probes -= neigh->parms->ucast_probes) < 0) {
+		if (!(neigh->nud_state&NUD_VALID))
+			ND_PRINTK1("trying to ucast probe in NUD_INVALID\n");
+		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 {
-		daddr = &ndn->ndn_addr;
-	}
-
-	ndisc_send_ns(dev, (struct neighbour *) ndn, target, daddr, NULL);
-
-	return ipv6_config.nd_retrans_time;
-}
-
-void ndisc_event_send(struct neighbour *neigh, struct sk_buff *skb)
-{
-	struct nd_neigh *ndn = (struct nd_neigh *) neigh;
-	struct in6_addr daddr;
-	unsigned long now = jiffies;
-	struct in6_addr *saddr = NULL;
-
-	if ((ndn->ndn_flags & NCF_NOARP))
-		return;
-
-	switch (ndn->ndn_nud_state) {
-	case NUD_FAILED:
-		ndn->ndn_probes = 0;
-	case NUD_NONE:
-		if (skb && !skb->stamp.tv_sec) {
-			/*
-			 *	skb->stamp allows us to know if we are
-			 *	originating the skb or forwarding it.
-			 *	(it is set on netif_rx)
-			 */
-			saddr = &skb->nh.ipv6h->saddr;
-		}
-
-		ndn->ndn_nud_state = NUD_INCOMPLETE;
-		addrconf_addr_solict_mult(&ndn->ndn_addr, &daddr);
-		ndisc_send_ns(ndn->ndn_dev, NULL, &ndn->ndn_addr, &daddr,
-			      saddr);
-		ndisc_add_timer(ndn, ipv6_config.nd_retrans_time);
-
-		break;
-
-	case NUD_REACHABLE:
-		if ((now - ndn->ndn_tstamp) < nd_reachable_time)
-			break;
-
-	case NUD_STALE:
-		ndn->ndn_nud_state = NUD_DELAY;
-		ndisc_add_timer(ndn, ipv6_config.nd_delay_probe_time);
-	}
-}
-
-/*
- *	Received a neighbour announce
- */
-void ndisc_event_na(struct nd_neigh *ndn, unsigned char *opt, int opt_len,
-		    int solicited, int override)
-{
-	struct sk_buff *skb;
-
-	NDBG(("ndisc_event_na(%p,%p,%d,%d,%d)\n", ndn, opt, opt_len,
-	      solicited, override));
-
-	if (ndn->ndn_nud_state == NUD_NONE)
-		ndn->ndn_nud_state = NUD_INCOMPLETE;
-
-	if (ndn->ndn_nud_state == NUD_INCOMPLETE || override) {
-		if (opt_len == 0) {
-			printk(KERN_DEBUG "no opt on NA\n");
-		} else {
-			/* Record hardware address. */
-			ndn->ndn_flags |= NTF_COMPLETE;
-
-			if (ndisc_store_hwaddr(ndn, opt, opt_len,
-					       ND_OPT_TARGET_LL_ADDR)) {
-#if ND_DEBUG >= 2
-				printk(KERN_DEBUG
-				       "event_na: invalid TARGET_LL_ADDR\n");
+#ifdef CONFIG_IPV6_EUI64
+		addrconf_addr_solict_mult_new(target, &mcaddr);
+		ndisc_send_ns(dev, NULL, target, &mcaddr, saddr);
 #endif
-				ndn->ndn_flags &= ~NTF_COMPLETE;
-				ndn->ndn_nud_state = NUD_NONE;
-				return;
-			}
-		}
-	}
-
-	if (solicited || override || ndn->ndn_nud_state == NUD_INCOMPLETE) {
-		ndn->ndn_probes = 0;
-		ndn->ndn_tstamp = jiffies;
-
-		if (ndn->ndn_nud_state & NUD_IN_TIMER)
-			ndisc_del_timer(ndn);
-
-		if (solicited)
-			ndn->ndn_nud_state = NUD_REACHABLE;
-		else
-			ndn->ndn_nud_state = NUD_STALE;
-	}
-			
-	while ((skb=skb_dequeue(&ndn->neigh.arp_queue)))
-		dev_queue_xmit(skb);
-}
-
-static struct nd_neigh * ndisc_event_ns(struct in6_addr *saddr,
-					struct sk_buff *skb)
-{
-	struct nd_neigh *ndn;
-	u8 *opt;
-	int len;
-
-	NDBG(("ndisc_event_ns: "));
-	if(saddr)
-		NDBG(("saddr[%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x] ",
-		      saddr->s6_addr16[0], saddr->s6_addr16[1], saddr->s6_addr16[2],
-		      saddr->s6_addr16[3], saddr->s6_addr16[4], saddr->s6_addr16[5],
-		      saddr->s6_addr16[6], saddr->s6_addr16[7]));
-	NDBG(("\n"));
-
-	opt = skb->h.raw;
-	opt += sizeof(struct icmp6hdr) + sizeof(struct in6_addr);
-
-	len = skb->tail - opt;
-
-	neigh_table_lock(&nd_tbl);
-	
-	ndn = (struct nd_neigh *) neigh_lookup(&nd_tbl, saddr,
-					       sizeof(struct in6_addr),
-					       skb->dev);
-
-	if (ndn == NULL)
-		ndn = ndisc_new_neigh(skb->dev, saddr);
-
-       	neigh_table_unlock(&nd_tbl);
-
-	if (ndn == NULL)
-		return NULL;
-
-	switch(ndn->ndn_nud_state) {
-	case NUD_REACHABLE:
-	case NUD_STALE:
-	case NUD_DELAY:
-		if (*opt != ND_OPT_SOURCE_LL_ADDR ||
-		    len != ndn->ndn_dev->addr_len ||
-		    memcmp(ndn->neigh.ha, opt + 2, len))
-			break;
-
-		if (ndn->ndn_nud_state & NUD_IN_TIMER)
-			ndisc_del_timer(ndn);
-
-		/* FALLTHROUGH */
-	default:
-		ndn->ndn_flags |= NTF_COMPLETE;
-			
-		if (ndisc_store_hwaddr(ndn, opt, len, ND_OPT_SOURCE_LL_ADDR)) {
-#if ND_DEBUG >= 1
-			printk(KERN_DEBUG
-			       "event_ns: invalid SOURCE_LL_ADDR\n");
+#ifndef CONFIG_IPV6_NO_PB
+		addrconf_addr_solict_mult_old(target, &mcaddr);
+		ndisc_send_ns(dev, NULL, target, &mcaddr, saddr);
 #endif
-
-			ndn->ndn_flags &= ~NTF_COMPLETE;
-			ndn->ndn_nud_state = NUD_NONE;
-			return ndn;
-		}
-
-		ndn->ndn_nud_state = NUD_STALE;
-		ndn->ndn_tstamp = jiffies;
-		ndn->ndn_probes = 0;
-	};
-
-	return ndn;
+	}
 }
 
 
-static void ndisc_ll_addr_update(struct nd_neigh *ndn, u8* opt, int len,
-				 int type)
+static void ndisc_update(struct neighbour *neigh, u8* opt, int len, int type)
 {
-	switch(ndn->ndn_nud_state) {
-	case NUD_REACHABLE:
-	case NUD_STALE:
-	case NUD_DELAY:
-		if (len == ndn->ndn_dev->addr_len &&
-		    memcmp(ndn->neigh.ha, opt + 2, len) == 0)
-			break;
-
-		if (ndn->ndn_nud_state & NUD_IN_TIMER)
-			ndisc_del_timer(ndn);
-	default:
-		ndn->ndn_flags |= NTF_COMPLETE;
-		
-		if (ndisc_store_hwaddr(ndn, opt, len, type)) {
-#if ND_DEBUG >=1
-			printk(KERN_DEBUG "NDISC: invalid LL_ADDR\n");
-#endif
-			ndn->ndn_flags &= ~NTF_COMPLETE;
-			ndn->ndn_nud_state = NUD_NONE;
-			break;
-		}
-		
-		ndn->ndn_nud_state = NUD_STALE;
-		ndn->ndn_tstamp = jiffies;
-		ndn->ndn_probes = 0;
-	};
+	opt = ndisc_find_option(opt, neigh->dev->addr_len+2, len, type);
+	neigh_update(neigh, opt, NUD_STALE, 1, 1);
 }
 
 static void ndisc_router_discovery(struct sk_buff *skb)
 {
         struct ra_msg *ra_msg = (struct ra_msg *) skb->h.raw;
-	struct nd_neigh *ndn;
+	struct neighbour *neigh;
 	struct inet6_dev *in6_dev;
 	struct rt6_info *rt;
 	int lifetime;
@@ -1075,8 +541,6 @@
 
 	__u8 * opt = (__u8 *)(ra_msg + 1);
 
-	NDBG(("ndisc_router_discovery(%p)\n", skb));
-
 	optlen = (skb->tail - skb->h.raw) - sizeof(struct ra_msg);
 
 	if (skb->nh.ipv6h->hop_limit != 255) {
@@ -1091,7 +555,7 @@
 
 	in6_dev = ipv6_get_idev(skb->dev);
 	if (in6_dev == NULL) {
-		printk(KERN_DEBUG "RA: can't find in6 device\n");
+		ND_PRINTK1("RA: can't find in6 device\n");
 		return;
 	}
 	
@@ -1113,28 +577,25 @@
 	}
 
 	if (rt == NULL && lifetime) {
-#if ND_DEBUG >= 2
-		printk(KERN_DEBUG "ndisc_rdisc: adding default router\n");
-#endif
+		ND_PRINTK2("ndisc_rdisc: adding default router\n");
 
 		rt = rt6_add_dflt_router(&skb->nh.ipv6h->saddr, skb->dev);
 
-		if (rt == NULL) {
-#if ND_DEBUG >= 1
-			printk(KERN_DEBUG "route_add failed\n");
+#if 1
+		/* BUGGGGG! Previous routine can return invalid pointer. */
+		rt = rt6_get_dflt_router(&skb->nh.ipv6h->saddr, skb->dev);
 #endif
+		if (rt == NULL) {
+			ND_PRINTK1("route_add failed\n");
 			return;
 		}
 
-		ndn = (struct nd_neigh *) rt->rt6i_nexthop;
-		if (ndn == NULL) {
-#if ND_DEBUG >= 1
-			printk(KERN_DEBUG "nd: add default router: null "
-			       "neighbour\n");
-#endif
+		neigh = rt->rt6i_nexthop;
+		if (neigh == NULL) {
+			ND_PRINTK1("nd: add default router: null neighbour\n");
 			return;
 		}
-		ndn->ndn_flags |= NCF_ROUTER;
+		neigh->flags |= NTF_ROUTER;
 	}
 
 	if (rt)
@@ -1147,31 +608,30 @@
 	 *	Update Reachable Time and Retrans Timer
 	 */
 
-	if (ra_msg->retrans_timer)
-		ipv6_config.nd_retrans_time = ntohl(ra_msg->retrans_timer);
-
-	if (ra_msg->reachable_time) {
-		__u32 rtime = ntohl(ra_msg->reachable_time);
-
-		if (rtime != ipv6_config.nd_base_reachable_time) {
-			ipv6_config.nd_base_reachable_time = rtime;
-			nd_gc_staletime	= 3 * rtime;
-			nd_reachable_time = rand_reach_time();
+	if (in6_dev->nd_parms) {
+		if (ra_msg->retrans_timer)
+			in6_dev->nd_parms->retrans_time = (ntohl(ra_msg->retrans_timer)*HZ)/1000;
+
+		if (ra_msg->reachable_time) {
+			__u32 rtime = (ntohl(ra_msg->reachable_time)*HZ)/1000;
+
+			if (rtime != in6_dev->nd_parms->base_reachable_time) {
+				in6_dev->nd_parms->base_reachable_time = rtime;
+				in6_dev->nd_parms->gc_staletime = 3 * rtime;
+				in6_dev->nd_parms->reachable_time = neigh_rand_reach_time(rtime);
+			}
 		}
-		
 	}
 
 	/*
 	 *	Process options.
 	 */
 
-        while(optlen > 0) {
-                int len;
-
-                len = (opt[1] << 3);
+        while (optlen > 0) {
+                int len = (opt[1] << 3);
 
 		if (len == 0) {
-			printk(KERN_DEBUG "RA: opt has 0 len\n");
+			ND_PRINTK0("RA: opt has 0 len\n");
 			break;
 		}
 
@@ -1181,11 +641,9 @@
 			if (rt == NULL)
 				break;
 			
-			ndn = (struct nd_neigh *) rt->rt6i_nexthop;
-
-			if (ndn)
-				ndisc_ll_addr_update(ndn, opt, len,
-						     ND_OPT_SOURCE_LL_ADDR);
+			if ((neigh = rt->rt6i_nexthop) != NULL &&
+			    skb->dev->addr_len + 2 >= len)
+				neigh_update(neigh, opt+2, NUD_STALE, 1, 1);
 			break;
 
                 case ND_OPT_PREFIX_INFO:
@@ -1204,25 +662,30 @@
 					break;
 
 				if (mtu < 576) {
-					printk(KERN_DEBUG "NDISC: router "
-					       "announcement with mtu = %d\n",
-					       mtu);
+					ND_PRINTK0("NDISC: router "
+						   "announcement with mtu = %d\n",
+						   mtu);
 					break;
 				}
 
+#if 0
+				/* Bad idea. Sorry, this thing is not
+				   so easy to implement		--ANK
+				 */
 				if (dev->change_mtu)
 					dev->change_mtu(dev, mtu);
 				else
 					dev->mtu = mtu;
+#endif
 			}
                         break;
 
 		case ND_OPT_TARGET_LL_ADDR:
 		case ND_OPT_REDIRECT_HDR:
-			printk(KERN_DEBUG "got illegal option with RA");
+			ND_PRINTK0("got illegal option with RA");
 			break;
 		default:
-			printk(KERN_DEBUG "unkown option in RA\n");
+			ND_PRINTK0("unkown option in RA\n");
                 };
                 optlen -= len;
                 opt += len;
@@ -1251,13 +714,10 @@
 	struct icmp6hdr *icmph;
 	struct in6_addr *dest;
 	struct in6_addr *target;	/* new first hop to destination */
-	struct nd_neigh *ndn;
+	struct neighbour *neigh;
 	struct rt6_info *rt;
 	int on_link = 0;
 	int optlen;
-	u8 * opt;
-
-	NDBG(("ndisc_redirect_rcv(%p)\n", skb));
 
 	if (skb->nh.ipv6h->hop_limit != 255) {
 		printk(KERN_WARNING "NDISC: fake ICMP redirect received\n");
@@ -1293,28 +753,20 @@
 		return;
 	}
 
-	/* passed validation tests */
+	/* passed validation tests
+
+	   NOTE We should not install redirect if sender did not supply
+	   ll address on link, which requires it. It would break, if
+	   we have non-transitive address resolution protocol.
+	   Fix it later. --ANK
+	 */
 	rt = rt6_redirect(dest, &skb->nh.ipv6h->saddr, target, skb->dev, on_link);
 
 	if (rt == NULL)
 		return;
 
-	ndn = (struct nd_neigh *) rt->rt6i_nexthop;
-
-	opt = (u8 *) (dest + 1);
-
-	while (optlen > 0) {
-		int len;
-
-		len = (opt[1] << 3);
-
-		if (*opt == ND_OPT_TARGET_LL_ADDR)
-			ndisc_ll_addr_update(ndn, opt, len,
-					     ND_OPT_TARGET_LL_ADDR);
-
-		opt += len;
-		optlen -= len;
-	}
+	neigh = rt->rt6i_nexthop;
+	ndisc_update(neigh, (u8*)(dest + 1), optlen, ND_OPT_TARGET_LL_ADDR);
 }
 
 void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh,
@@ -1323,13 +775,11 @@
 	struct sock *sk = ndisc_socket->sk;
 	int len = sizeof(struct icmp6hdr) + 2 * sizeof(struct in6_addr);
 	struct sk_buff *buff;
-	struct nd_neigh *ndn = (struct nd_neigh *) neigh;
 	struct inet6_ifaddr *ifp;
 	struct icmp6hdr *icmph;
 	struct in6_addr *addrp;
 	struct device *dev;
 	struct rt6_info *rt;
-	int ta_len = 0;
 	u8 *opt;
 	int rd_len;
 	int err;
@@ -1339,22 +789,25 @@
 	rt = rt6_lookup(&skb->nh.ipv6h->saddr, NULL, dev, 0);
 
 	if (rt == NULL || rt->u.dst.error) {
-#if ND_DEBUG >= 1
-		printk(KERN_DEBUG "ndisc_send_redirect: hostunreach\n");
-#endif
+		ND_PRINTK1("ndisc_send_redirect: hostunreach\n");
 		return;
 	}
 
 	if (rt->rt6i_flags & RTF_GATEWAY) {
-#if ND_DEBUG >= 1
-		printk(KERN_DEBUG "ndisc_send_redirect: not a neighbour\n");
-#endif
+		ND_PRINTK1("ndisc_send_redirect: not a neighbour\n");
 		return;
 	}
 
-	if (ndn->ndn_nud_state == NUD_REACHABLE) {
-		ta_len  = ((dev->addr_len + 1) >> 3) + 1;
-		len += (ta_len << 3);
+	if (dev->addr_len) {
+		if (neigh->nud_state&NUD_VALID) {
+			len  += NDISC_OPT_SPACE(dev->addr_len);
+		} else {
+			/* If nexthop is not valid, do not redirect!
+			   We will make it later, when will be sure,
+			   that it is alive.
+			 */
+			return;
+		}
 	}
 
 	rd_len = min(536 - len, ntohs(skb->nh.ipv6h->payload_len) + 8);
@@ -1364,18 +817,14 @@
 	ifp = ipv6_get_lladdr(dev);
 
 	if (ifp == NULL) {
-#if ND_DEBUG >= 1
-		printk(KERN_DEBUG "redirect: no link_local addr for dev\n");
-#endif
+		ND_PRINTK1("redirect: no link_local addr for dev\n");
 		return;
 	}
 
 	buff = sock_alloc_send_skb(sk, MAX_HEADER + len + dev->hard_header_len + 15,
 				   0, 0, &err);
 	if (buff == NULL) {
-#if ND_DEBUG >= 2
-		printk(KERN_DEBUG "ndisc_send_redirect: alloc_skb failed\n");
-#endif
+		ND_PRINTK1("ndisc_send_redirect: alloc_skb failed\n");
 		return;
 	}
 	
@@ -1409,29 +858,8 @@
 	 *	include target_address option
 	 */
 
-	if (ta_len) {
-		int zb;
-		
-		*(opt++) = ND_OPT_TARGET_LL_ADDR;
-		*(opt++) = ta_len;
-
-		memcpy(opt, neigh->ha, neigh->dev->addr_len);
-		opt += neigh->dev->addr_len;
-
-		/* 
-		 *	if link layer address doesn't end on a 8 byte
-		 *	boundary memset(0) the remider
-		 */
-
-		zb = (neigh->dev->addr_len + 2) & 0x7; 
-		if (zb) {
-			int comp;
-
-			comp = 8 - zb;
-			memset(opt, 0, comp);
-			opt += comp;
-		}
-	}
+	if (dev->addr_len)
+		opt = ndisc_fill_option(opt, ND_OPT_TARGET_LL_ADDR, neigh->ha, dev->addr_len);
 
 	/*
 	 *	build redirect option and copy skb over to the new packet.
@@ -1451,24 +879,37 @@
 	dev_queue_xmit(buff);
 }
 
-/* Called by upper layers to validate neighbour cache entries. */
-
-void ndisc_validate(struct neighbour *neigh)
+static __inline__ struct neighbour *
+ndisc_recv_ns(struct in6_addr *saddr, struct sk_buff *skb)
 {
-	struct nd_neigh *ndn = (struct nd_neigh *) neigh;
+	u8 *opt;
 
-	if (neigh == NULL)
-		return;
+	opt = skb->h.raw;
+	opt += sizeof(struct icmp6hdr) + sizeof(struct in6_addr);
+	opt = ndisc_find_option(opt, skb->dev->addr_len+2, skb->tail - opt, ND_OPT_SOURCE_LL_ADDR);
+
+	return neigh_event_ns(&nd_tbl, opt, saddr, skb->dev);
+}
+
+static __inline__ int ndisc_recv_na(struct neighbour *neigh, struct sk_buff *skb)
+{
+	struct nd_msg *msg = (struct nd_msg *) skb->h.raw;
+	u8 *opt;
 
-        if (ndn->ndn_nud_state == NUD_INCOMPLETE)
-                return;
+	opt = skb->h.raw;
+	opt += sizeof(struct icmp6hdr) + sizeof(struct in6_addr);
+	opt = ndisc_find_option(opt, skb->dev->addr_len+2, skb->tail - opt, ND_OPT_TARGET_LL_ADDR);
 
-        if (ndn->ndn_nud_state == NUD_DELAY) 
-                ndisc_del_timer(ndn);
+	return neigh_update(neigh, opt,
+			    msg->icmph.icmp6_solicited ? NUD_REACHABLE : NUD_STALE,
+			    msg->icmph.icmp6_override, 1);
+}
 
-        nd_stats.rcv_upper_conf++;
-        ndn->ndn_nud_state = NUD_REACHABLE;
-        ndn->ndn_tstamp = jiffies;
+static void pndisc_redo(struct sk_buff *skb)
+{
+	ndisc_rcv(skb, skb->dev, &skb->nh.ipv6h->saddr, &skb->nh.ipv6h->daddr,
+		  NULL, skb->len);
+	kfree_skb(skb, FREE_WRITE);
 }
 
 int ndisc_rcv(struct sk_buff *skb, struct device *dev,
@@ -1476,27 +917,24 @@
 	      struct ipv6_options *opt, unsigned short len)
 {
 	struct nd_msg *msg = (struct nd_msg *) skb->h.raw;
-	struct nd_neigh *ndn;
+	struct neighbour *neigh;
 	struct inet6_ifaddr *ifp;
 
-	NDBG(("ndisc_rcv(type=%d) ", msg->icmph.icmp6_type));
 	switch (msg->icmph.icmp6_type) {
 	case NDISC_NEIGHBOUR_SOLICITATION:
-		NDBG(("NS "));
-		if ((ifp = ipv6_chk_addr(&msg->target)) != NULL) {
+		if ((ifp = ipv6_chk_addr(&msg->target, dev, 1)) != NULL) {
 			int addr_type = ipv6_addr_type(saddr);
 
+			if (ifp->flags & ADDR_INVALID)
+				return 0;
 			if (ifp->flags & DAD_INCOMPLETE) {
 				/* Address is tentative. If the source
 				   is unspecified address, it is someone
 				   does DAD, otherwise we ignore solicitations
 				   until DAD timer expires.
 				 */
-				if (addr_type == IPV6_ADDR_ANY) {
-					printk(KERN_INFO "%s: duplicate address detected!\n",
-					       ifp->idev->dev->name);
-					del_timer(&ifp->timer);
-				}
+				if (addr_type == IPV6_ADDR_ANY)
+					addrconf_dad_failure(ifp);
 				return 0;
 			}
 
@@ -1510,46 +948,75 @@
 			}
 
 			if (addr_type & IPV6_ADDR_UNICAST) {
-				int inc;
+				int inc = ipv6_addr_type(daddr)&IPV6_ADDR_MULTICAST;
+
+				if (inc)
+					nd_tbl.stats.rcv_probes_mcast++;
+				else
+					nd_tbl.stats.rcv_probes_ucast++;
 
 				/* 
 				 *	update / create cache entry
 				 *	for the source adddress
 				 */
 
-				nd_stats.rcv_probes_ucast++;
+				neigh = ndisc_recv_ns(saddr, skb);
 
-				ndn = ndisc_event_ns(saddr, skb);
+				if (neigh) {
+					ndisc_send_na(dev, neigh, saddr, &ifp->addr, 
+						      ifp->idev->router, 1, inc, inc);
+					neigh_release(neigh);
+				}
+			}
+		} else {
+			struct inet6_dev *in6_dev = ipv6_get_idev(dev);
+			int addr_type = ipv6_addr_type(saddr);
 
-				if (ndn == NULL)
+			if (in6_dev && in6_dev->router &&
+			    (addr_type & IPV6_ADDR_UNICAST) &&
+			    pneigh_lookup(&nd_tbl, &msg->target, dev, 0)) {
+				int inc = ipv6_addr_type(daddr)&IPV6_ADDR_MULTICAST;
+
+				if (skb->stamp.tv_sec == 0 ||
+				    skb->pkt_type == PACKET_HOST ||
+				    inc == 0 ||
+				    in6_dev->nd_parms->proxy_delay == 0) {
+					if (inc)
+						nd_tbl.stats.rcv_probes_mcast++;
+					else
+						nd_tbl.stats.rcv_probes_ucast++;
+
+					neigh = ndisc_recv_ns(saddr, skb);
+
+					if (neigh) {
+						ndisc_send_na(dev, neigh, saddr, &msg->target,
+							      1, 0, inc, inc);
+						neigh_release(neigh);
+					}
+				} else {
+					/* Hack. It will be freed upon exit from
+					   ndisc_rcv
+					 */
+					atomic_inc(&skb->users);
+					pneigh_enqueue(&nd_tbl, in6_dev->nd_parms, skb);
 					return 0;
-
-				inc = ipv6_addr_type(daddr);
-				inc &= IPV6_ADDR_MULTICAST;
-
-				ndisc_send_na(dev, ndn, saddr, &ifp->addr, 
-					      ifp->idev->router, 1, inc, inc);
-			} else {
-#if ND_DEBUG >= 1
-				printk(KERN_DEBUG "ns: non unicast saddr\n");
-#endif
+				}
 			}
 		}
-		break;
+		return 0;
 
 	case NDISC_NEIGHBOUR_ADVERTISEMENT:
-		NDBG(("NA "));
 		if ((ipv6_addr_type(saddr)&IPV6_ADDR_MULTICAST) &&
 		    msg->icmph.icmp6_solicited) {
-			printk(KERN_DEBUG "NDISC: solicited NA is multicasted\n");
+			ND_PRINTK0("NDISC: solicited NA is multicasted\n");
 			return 0;
 		}
-		if ((ifp = ipv6_chk_addr(&msg->target))) {
+		/* BUG! Target can be link-local on ANOTHER interface. Fixed. */
+		if ((ifp = ipv6_chk_addr(&msg->target, dev, 1))) {
+			if (ifp->flags & ADDR_INVALID)
+				return 0;
 			if (ifp->flags & DAD_INCOMPLETE) {
-				/* Address is duplicate. */
-				printk(KERN_INFO "%s: duplicate address detected!\n",
-				       ifp->idev->dev->name);
-				del_timer(&ifp->timer);
+				addrconf_dad_failure(ifp);
 				return 0;
 			}
 			/* What should we make now? The advertisement
@@ -1557,18 +1024,14 @@
 			   about it. It could be misconfiguration, or
 			   an smart proxy agent tries to help us :-)
 			 */
-			printk(KERN_DEBUG "%s: someone avertise our address!\n",
-			       ifp->idev->dev->name);
+			ND_PRINTK0("%s: someone avertise our address!\n",
+				   ifp->idev->dev->name);
 			return 0;
 		}
-		neigh_table_lock(&nd_tbl);	       
-		ndn = (struct nd_neigh *) 
-			neigh_lookup(&nd_tbl, (void *) &msg->target,
-				     sizeof(struct in6_addr), skb->dev);
-		neigh_table_unlock(&nd_tbl);
+		neigh = __neigh_lookup(&nd_tbl, &msg->target, skb->dev, 0);
 
-		if (ndn) {
-			if (ndn->ndn_flags & NCF_ROUTER) {
+		if (neigh) {
+			if (neigh->flags & NTF_ROUTER) {
 				if (msg->icmph.icmp6_router == 0) {
 					/*
 					 *	Change: router to host
@@ -1583,27 +1046,23 @@
 				}
 			} else {
 				if (msg->icmph.icmp6_router)
-					ndn->ndn_flags |= NCF_ROUTER;
+					neigh->flags |= NTF_ROUTER;
 			}
-			ndisc_event_na(ndn, (unsigned char *) &msg->opt,
-				       skb->tail - (u8 *)&msg->opt /*opt_len*/,
-				       msg->icmph.icmp6_solicited,
-				       msg->icmph.icmp6_override);
+
+			ndisc_recv_na(neigh, skb);
+			neigh_release(neigh);
 		}
 		break;
-
 	};
 
 	if (ipv6_config.forwarding == 0) {
 		switch (msg->icmph.icmp6_type) {
 		case NDISC_ROUTER_ADVERTISEMENT:
-			NDBG(("RA "));
 			if (ipv6_config.accept_ra)
 				ndisc_router_discovery(skb);
 			break;
 
 		case NDISC_REDIRECT:
-			NDBG(("REDIR "));
 			if (ipv6_config.accept_redirects)
 				ndisc_redirect_rcv(skb);
 			break;
@@ -1614,68 +1073,70 @@
 }
 
 #ifdef CONFIG_PROC_FS
-int ndisc_get_info(char *buffer, char **start, off_t offset, int length,
-		   int dummy)
+#ifndef CONFIG_RTNETLINK
+int ndisc_get_info(char *buffer, char **start, off_t offset, int length, int dummy)
 {
+	int len=0;
+	off_t pos=0;
+	int size;
 	unsigned long now = jiffies;
-	int len = 0;
 	int i;
 
 	neigh_table_lock(&nd_tbl);
 
-	for (i = 0; i < nd_tbl.tbl_size; i++) {
-		struct neighbour *neigh, *head;
-		head = nd_tbl.hash_buckets[i];
-		
-		if ((neigh = head) == NULL)
-			continue;
+	for (i = 0; i <= NEIGH_HASHMASK; i++) {
+		struct neighbour *neigh;
 
-		do {
-			struct nd_neigh *ndn = (struct nd_neigh *) neigh;
+		for (neigh = nd_tbl.hash_buckets[i]; neigh; neigh = neigh->next) {
 			int j;
 
+			size = 0;
 			for (j=0; j<16; j++) {
-				sprintf(buffer + len, "%02x",
-					ndn->ndn_addr.s6_addr[j]);
-				len += 2;
+				sprintf(buffer+len+size, "%02x", neigh->primary_key[j]);
+				size += 2;
 			}
 
-			len += sprintf(buffer + len,
-				       " %02x %02x %02x %02x %08lx %08lx %08lx %04x %04x %04lx %8s ", i,
-				       ndn->ndn_plen,
-				       ndn->ndn_type,
-				       ndn->ndn_nud_state,
-				       ndn->ndn_expires ? ndn->ndn_expires - now : 0,
-				       now - ndn->ndn_tstamp,
-				       nd_reachable_time,
-				       nd_gc_staletime,
-				       atomic_read(&ndn->ndn_refcnt),
-				       ndn->ndn_flags,
-				       ndn->ndn_dev ? ndn->ndn_dev->name : "NULLDEV");
-
-			if ((ndn->ndn_flags & NTF_COMPLETE)) {
-				for (j=0; j< neigh->dev->addr_len; j++) {
-					sprintf(buffer + len, "%02x",
-						neigh->ha[j]);
-					len += 2;
+			size += sprintf(buffer+len+size,
+				       " %02x %02x %02x %02x %08lx %08lx %08x %04x %04x %04x %8s ", i,
+				       128,
+				       neigh->type,
+				       neigh->nud_state,
+				       now - neigh->used,
+				       now - neigh->confirmed,
+				       neigh->parms->reachable_time,
+				       neigh->parms->gc_staletime,
+				       atomic_read(&neigh->refcnt),
+				       neigh->flags | (!neigh->hh ? 0 : (neigh->hh->hh_output==dev_queue_xmit ? 4 : 2)),
+				       neigh->dev->name);
+
+			if ((neigh->nud_state&NUD_VALID) && neigh->dev->addr_len) {
+				for (j=0; j < neigh->dev->addr_len; j++) {
+					sprintf(buffer+len+size, "%02x", neigh->ha[j]);
+					size += 2;
 				}
 			} else {
-                                len += sprintf(buffer + len, "000000000000");
+                                size += sprintf(buffer+len+size, "000000000000");
 			}
-			len += sprintf(buffer + len, "\n");
-			
-			neigh = neigh->next;
-		} while (neigh != head);
+			size += sprintf(buffer+len+size, "\n");
+			len += size;
+			pos += size;
+		  
+			if (pos <= offset)
+				len=0;
+			if (pos >= offset+length)
+				goto done;
+		}
 	}
 
+done:
 	neigh_table_unlock(&nd_tbl);
-	
-	*start = buffer + offset;
-
-	len -= offset;
 
-	if (len > length)
-		len = length;
+	*start = buffer+len-(pos-offset);	/* Start of wanted data */
+	len = pos-offset;			/* Start slop */
+	if (len>length)
+		len = length;			/* Ending slop */
+	if (len<0)
+		len = 0;
 	return len;
 }
 
@@ -1686,8 +1147,11 @@
         0, NULL,
         &ndisc_get_info
 };
+#endif
 #endif	/* CONFIG_PROC_FS */
 
+
+
 __initfunc(void ndisc_init(struct net_proto_family *ops))
 {
 	struct sock *sk;
@@ -1700,12 +1164,13 @@
 
         ndisc_socket->inode = &ndisc_inode;
         ndisc_socket->state = SS_UNCONNECTED;
-        ndisc_socket->type=SOCK_RAW;
+        ndisc_socket->type  = SOCK_RAW;
 
 	if((err=ops->create(ndisc_socket, IPPROTO_ICMPV6))<0)
 		printk(KERN_DEBUG 
 		       "Failed to create the NDISC control socket.\n");
 
+	/* Eeeh... What is it? --ANK */
 	MOD_DEC_USE_COUNT;
 
 	sk = ndisc_socket->sk;
@@ -1718,25 +1183,16 @@
          * Initialize the neighbour table
          */
 	
-	neigh_table_init(&nd_tbl, &nd_neigh_ops, NCACHE_NUM_BUCKETS);
- 
-        /* General ND state machine timer. */
-	init_timer(&ndisc_timer);
-	ndisc_timer.function = ndisc_timer_handler;
-	ndisc_timer.data = 0L;
-	ndisc_timer.expires = 0L;
-
-        /* ND GC timer */
-        init_timer(&ndisc_gc_timer);
-        ndisc_gc_timer.function = ndisc_periodic_timer;
-        ndisc_gc_timer.data = 0L;
-        ndisc_gc_timer.expires = jiffies + nd_gc_interval;
-
-	add_timer(&ndisc_gc_timer);
+	neigh_table_init(&nd_tbl);
 
 #ifdef CONFIG_PROC_FS
+#ifndef CONFIG_RTNETLINK
 	proc_net_register(&ndisc_proc_entry);
 #endif
+#endif
+#ifdef CONFIG_SYSCTL
+	nd_tbl.parms.sysctl_table = neigh_sysctl_register(NULL, &nd_tbl.parms, NET_IPV6, NET_IPV6_NEIGH, "ipv6");
+#endif
 }
 
 #ifdef MODULE
@@ -1745,7 +1201,6 @@
 #ifdef CONFIG_PROC_FS
         proc_net_unregister(ndisc_proc_entry.low_ino);
 #endif
-	del_timer(&ndisc_gc_timer);
-	del_timer(&ndisc_timer);
+	neigh_table_clear(&nd_tbl);
 }
 #endif

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov